From 8c353107d8e81c7c6fa190b5a1d089b2f5b47ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Sun, 21 Jul 2024 22:32:15 +0200 Subject: [PATCH] feat: test stuff --- bampy/src/lib.rs | 113 +++---------- bampy/src/result.rs | 39 +++++ bampy/src/slicer/axis.rs | 29 ++++ bampy/src/slicer/base_slices.rs | 122 ++++++++------ bampy/src/slicer/line.rs | 16 +- bampy/src/slicer/mesh.rs | 86 +++++++++- bampy/src/slicer/mod.rs | 24 ++- bampy/src/slicer/sdf.rs | 253 ++++++++++++++++++++++++++++++ bampy/src/slicer/slice_path.rs | 151 ++++++++++++++++++ bampy/src/slicer/slice_rings.rs | 99 ------------ bampy/src/slicer/split_surface.rs | 1 + bampy/src/slicer/trace_surface.rs | 4 +- bampy/src/slicer/triangle.rs | 58 +++---- bampy/src/slicer/z_projection.rs | 14 +- src/lib/components/Scene.svelte | 3 + src/lib/slicer/worker-data.ts | 2 + src/lib/slicer/worker.ts | 4 +- 17 files changed, 723 insertions(+), 295 deletions(-) create mode 100644 bampy/src/result.rs create mode 100644 bampy/src/slicer/axis.rs create mode 100644 bampy/src/slicer/sdf.rs create mode 100644 bampy/src/slicer/slice_path.rs delete mode 100644 bampy/src/slicer/slice_rings.rs diff --git a/bampy/src/lib.rs b/bampy/src/lib.rs index 4fe6ede..ddced13 100644 --- a/bampy/src/lib.rs +++ b/bampy/src/lib.rs @@ -1,63 +1,25 @@ -use std::iter::empty; - use approx::relative_eq; -use nalgebra::{vector, SimdBool, Vector3}; -use serde::{Deserialize, Serialize}; -use slicer::{line::Line3, slice_rings::slice_rings}; -use tsify::Tsify; +use nalgebra::{point, vector, Vector3}; +use result::{Slice, SliceOptions, SliceResult}; +use slicer::axis::Axis; use wasm_bindgen::prelude::wasm_bindgen; -use crate::slicer::{ - base_slices::create_base_slices, mesh::Mesh, split_surface::split_surface, - trace_surface::trace_surface, triangle::Triangle, FloatValue, SlicerOptions, -}; +use crate::slicer::{mesh::Mesh, split_surface::split_surface, triangle::Triangle, FloatValue}; +mod result; mod slicer; mod util; const BED_NORMAL: Vector3 = vector![0f64, 0f64, 1f64]; -#[derive(Tsify, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[tsify(from_wasm_abi)] -pub struct SliceOptions { - #[tsify(type = "Float32Array")] - positions: Vec, - layer_height: f64, - max_angle: f64, -} - -#[derive(Tsify, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", tag = "type")] -#[tsify(into_wasm_abi)] -pub enum Slice { - Surface { - #[tsify(type = "Float32Array")] - position: Vec, - }, - Ring { - #[tsify(type = "Float32Array")] - position: Vec, - }, - Path { - #[tsify(type = "Float32Array")] - position: Vec, - }, -} - -#[derive(Tsify, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[tsify(into_wasm_abi)] -pub struct SliceResult { - slices: Vec, -} - #[wasm_bindgen] pub fn slice( SliceOptions { positions, layer_height, max_angle, + min_surface_path_length, + nozzle_diameter, }: SliceOptions, ) -> SliceResult { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); @@ -68,17 +30,17 @@ pub fn slice( let mut slicable_triangles = Vec::::with_capacity(positions.len() / 9); for i in (0..positions.len()).step_by(9) { let triangle = Triangle::new( - vector![ + point![ positions[i] as FloatValue, positions[i + 1] as FloatValue, positions[i + 2] as FloatValue ], - vector![ + point![ positions[i + 3] as FloatValue, positions[i + 4] as FloatValue, positions[i + 5] as FloatValue ], - vector![ + point![ positions[i + 6] as FloatValue, positions[i + 7] as FloatValue, positions[i + 8] as FloatValue @@ -99,50 +61,23 @@ pub fn slice( slicable_triangles.shrink_to_fit(); surface_triangles.shrink_to_fit(); - let slicer_options = SlicerOptions { layer_height }; - console_log!("Creating Surfaces"); let surfaces = split_surface(surface_triangles).into_iter().map(|mesh| { - slice_rings(1, &slicer_options, &mesh) - .flat_map(|mut rings| { - /*let mut rings = rings - .into_iter() - .map(|mut ring| { - ring.points.sort_unstable_by(|a, b| { - a[0].partial_cmp(&b[0]).unwrap_or(std::cmp::Ordering::Equal) - }); - ring - }) - .collect::>();*/ - rings.sort_unstable_by(|a, b| { - a.points - .first() - .unwrap() - .partial_cmp(b.points.first().unwrap()) - .unwrap_or(std::cmp::Ordering::Equal) - }); - rings - }) - .fold(Vec::>::new(), |mut acc, mut curr| { - if acc - .last() - .zip(curr.points.first()) - .zip(curr.points.last()) - .map_or(false, |((last, start), end)| { - start.metric_distance(last) > end.metric_distance(last) - }) - { - curr.points.reverse(); + mesh.slice_surface(Axis::X, nozzle_diameter).filter(|path| { + let mut length = 0.0; + for pair in path.path.windows(2) { + length += (pair[0].coords - pair[1].coords).norm(); + if length >= min_surface_path_length { + return true; } - acc.extend(curr.points); - acc - }) + } + return false; + }) }); - console_log!("Computing BVH"); - let slicable = Mesh::from(slicable_triangles); - console_log!("Creating Slices"); - let slices = slice_rings(2, &slicer_options, &slicable); + console_log!("Creating Walls"); + let wallMesh = Mesh::from(slicable_triangles); + let walls = wallMesh.slice_paths(Axis::Z, layer_height); /*console_log!("Tracing Surfaces"); let a = max_angle.tan(); @@ -157,13 +92,15 @@ pub fn slice( console_log!("Done"); SliceResult { slices: surfaces + .flatten() .map(|slice| Slice::Ring { position: slice + .path .into_iter() .flat_map(|point| [point.x as f32, point.y as f32, point.z as f32]) .collect(), }) - /*.chain(slices.flatten().map(|slice| { + /*.chain(walls.flatten().map(|slice| { Slice::Ring { position: slice .points diff --git a/bampy/src/result.rs b/bampy/src/result.rs new file mode 100644 index 0000000..35d7e3b --- /dev/null +++ b/bampy/src/result.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; +use tsify::Tsify; + +#[derive(Tsify, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[tsify(from_wasm_abi)] +pub struct SliceOptions { + #[tsify(type = "Float32Array")] + pub positions: Vec, + pub layer_height: f64, + pub nozzle_diameter: f64, + pub max_angle: f64, + pub min_surface_path_length: f64, +} + +#[derive(Tsify, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type")] +#[tsify(into_wasm_abi)] +pub enum Slice { + Surface { + #[tsify(type = "Float32Array")] + position: Vec, + }, + Ring { + #[tsify(type = "Float32Array")] + position: Vec, + }, + Path { + #[tsify(type = "Float32Array")] + position: Vec, + }, +} + +#[derive(Tsify, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi)] +pub struct SliceResult { + pub slices: Vec, +} diff --git a/bampy/src/slicer/axis.rs b/bampy/src/slicer/axis.rs new file mode 100644 index 0000000..a0bba49 --- /dev/null +++ b/bampy/src/slicer/axis.rs @@ -0,0 +1,29 @@ +use nalgebra::Vector3; + +use super::FloatValue; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(usize)] +pub enum Axis { + X = 0, + Y = 1, + Z = 2, +} + +impl Axis { + pub fn other(&self) -> (Self, Self) { + match self { + Axis::X => (Axis::Y, Axis::Z), + Axis::Y => (Axis::X, Axis::Z), + Axis::Z => (Axis::X, Axis::Y), + } + } + + pub fn normal(&self) -> Vector3 { + match self { + Axis::X => Vector3::x(), + Axis::Y => Vector3::y(), + Axis::Z => Vector3::z(), + } + } +} diff --git a/bampy/src/slicer/base_slices.rs b/bampy/src/slicer/base_slices.rs index 637e180..fbd8edf 100644 --- a/bampy/src/slicer/base_slices.rs +++ b/bampy/src/slicer/base_slices.rs @@ -1,62 +1,82 @@ -use super::{line::Line3, mesh::Mesh, FloatValue, SlicerOptions}; -use crate::console_log; -use bvh::bvh::BvhNode; +use super::{aabb_from_points, axis::Axis, line::Line3, slice_path::SlicePath, FloatValue}; +use approx::relative_eq; +use nalgebra::Point3; #[derive(Debug)] pub struct BaseSlice { + pub i: usize, pub d: FloatValue, + pub axis: Axis, pub lines: Vec, } -/// Creates base slices from the geometry -pub fn create_base_slices<'a>( - axis: usize, - options: &'a SlicerOptions, - slicable: &'a Mesh, -) -> impl Iterator + 'a { - let layer_count = ((slicable.aabb.max[axis] - slicable.aabb.min[axis]) / options.layer_height) - .floor() as usize; - - (0..layer_count).map(move |i| { - let layer = i as FloatValue * options.layer_height + slicable.aabb.min[axis]; - let mut base_slice = BaseSlice { - d: layer, - lines: vec![], - }; - - let mut stack = Vec::::with_capacity(slicable.bvh.nodes.len()); - stack.push(0); - while let Some(i) = stack.pop() { - match slicable.bvh.nodes[i] { - BvhNode::Node { - parent_index: _, - child_l_index, - child_l_aabb, - child_r_index, - child_r_aabb, - } => { - assert!(child_l_aabb.min[axis] <= child_l_aabb.max[axis]); - assert!(child_r_aabb.min[axis] <= child_r_aabb.max[axis]); - if layer >= child_l_aabb.min[axis] && layer <= child_l_aabb.max[axis] { - stack.push(child_l_index); - } - if layer >= child_r_aabb.min[axis] && layer <= child_r_aabb.max[axis] { - stack.push(child_r_index); - } - } - BvhNode::Leaf { - parent_index: _, - shape_index, - } => { - slicable.triangles[shape_index] - .intersect(layer, axis) - .map(|line| { - base_slice.lines.push(line); - }); - } +impl BaseSlice { + pub fn find_paths(mut self) -> Vec { + let (axis_a, axis_b) = self.axis.other(); + let mut rings = vec![]; + while let Some(line) = self.lines.pop() { + if relative_eq!(line.start, line.end) { + continue; } + let mut right = vec![line.end]; + let mut left = vec![line.start]; + let mut previous_len = usize::MAX; + let mut closed = false; + + while !closed { + if previous_len == self.lines.len() { + break; + } + previous_len = self.lines.len(); + + self.lines.retain_mut(|line| { + if closed { + return true; + } + + let test = |side: &mut Vec>| { + let last = side.last().unwrap(); + let s = relative_eq!(line.start, last); + let e = relative_eq!(line.end, last); + if s && !e { + side.push(line.end); + } else if !s && e { + side.push(line.start); + } + s || e + }; + + if test(&mut left) || test(&mut right) { + closed = relative_eq!(left.last().unwrap(), right.last().unwrap()); + false + } else { + true + } + }) + } + + left.reverse(); + left.extend(right); + let mut ring = SlicePath { + d: self.d, + i: self.i, + axis: self.axis, + closed, + aabb: aabb_from_points(left.iter()), + points: left, + }; + + if ring.points.windows(2).fold(0.0, |acc, curr| { + acc + (curr[1][axis_a as usize] - curr[0][axis_a as usize]) + * (curr[1][axis_b as usize] + curr[0][axis_b as usize]) + }) < 0.0 + { + ring.points.reverse(); + } + + rings.push(ring); } - base_slice - }) + rings + } } diff --git a/bampy/src/slicer/line.rs b/bampy/src/slicer/line.rs index 5c16b2d..065f5b3 100644 --- a/bampy/src/slicer/line.rs +++ b/bampy/src/slicer/line.rs @@ -1,4 +1,4 @@ -use nalgebra::Vector3; +use nalgebra::{Point3, Vector3}; use super::FloatValue; @@ -8,6 +8,16 @@ use super::FloatValue; /// meaning the inside is on the right hand side of the line. #[derive(Debug, PartialEq, Clone, Copy)] pub struct Line3 { - pub start: Vector3, - pub end: Vector3, + pub start: Point3, + pub end: Point3, +} + +impl Line3 { + pub fn norm(&self) -> FloatValue { + (self.end - self.start).norm() + } + + pub fn normal(&self) -> Vector3 { + (self.end - self.start).normalize() + } } diff --git a/bampy/src/slicer/mesh.rs b/bampy/src/slicer/mesh.rs index 068897d..d58dc32 100644 --- a/bampy/src/slicer/mesh.rs +++ b/bampy/src/slicer/mesh.rs @@ -1,5 +1,14 @@ -use super::{triangle::Triangle, FloatValue}; -use bvh::{aabb::Aabb, bvh::Bvh}; +use super::{ + axis::Axis, + base_slices::BaseSlice, + slice_path::{SlicePath, SurfacePathIterator}, + triangle::Triangle, + FloatValue, +}; +use bvh::{ + aabb::Aabb, + bvh::{Bvh, BvhNode}, +}; #[derive(Debug)] pub struct Mesh { @@ -26,3 +35,76 @@ impl From> for Mesh { } } } + +impl Mesh { + pub fn slice_paths<'a>( + self: &'a Mesh, + axis: Axis, + slice_height: FloatValue, + ) -> impl Iterator> + 'a { + self.slice_base_slices(axis, slice_height) + .map(|slice| slice.find_paths()) + .filter(|paths| !paths.is_empty()) + } + + pub fn slice_surface(&self, axis: Axis, nozzle_width: FloatValue) -> SurfacePathIterator { + SurfacePathIterator::new(self, axis, nozzle_width) + } + + pub fn slice_base_slices<'a>( + self: &'a Mesh, + axis: Axis, + slice_height: FloatValue, + ) -> impl Iterator + 'a { + let layer_count = ((self.aabb.max[axis as usize] - self.aabb.min[axis as usize]) + / slice_height) + .floor() as usize; + + (0..layer_count).map(move |i| { + let layer = i as FloatValue * slice_height + self.aabb.min[axis as usize]; + let mut base_slice = BaseSlice { + i, + d: layer, + axis, + lines: vec![], + }; + + let mut stack = Vec::::with_capacity(self.bvh.nodes.len()); + stack.push(0); + while let Some(i) = stack.pop() { + match self.bvh.nodes[i] { + BvhNode::Node { + parent_index: _, + child_l_index, + child_l_aabb, + child_r_index, + child_r_aabb, + } => { + assert!(child_l_aabb.min[axis as usize] <= child_l_aabb.max[axis as usize]); + assert!(child_r_aabb.min[axis as usize] <= child_r_aabb.max[axis as usize]); + if layer >= child_l_aabb.min[axis as usize] + && layer <= child_l_aabb.max[axis as usize] + { + stack.push(child_l_index); + } + if layer >= child_r_aabb.min[axis as usize] + && layer <= child_r_aabb.max[axis as usize] + { + stack.push(child_r_index); + } + } + BvhNode::Leaf { + parent_index: _, + shape_index, + } => { + for line in self.triangles[shape_index].intersect(layer, axis as usize) { + base_slice.lines.push(line); + } + } + } + } + + base_slice + }) + } +} diff --git a/bampy/src/slicer/mod.rs b/bampy/src/slicer/mod.rs index d19b3e2..611d9b5 100644 --- a/bampy/src/slicer/mod.rs +++ b/bampy/src/slicer/mod.rs @@ -1,9 +1,13 @@ -use std::fmt::Debug; +use bvh::aabb::{Aabb, Bounded}; +use bvh::bounding_hierarchy::BHValue; +use nalgebra::Point; +pub mod axis; pub mod base_slices; pub mod line; pub mod mesh; -pub mod slice_rings; +pub mod sdf; +pub mod slice_path; pub mod split_surface; pub mod trace_surface; pub mod triangle; @@ -11,7 +15,17 @@ pub mod z_projection; pub type FloatValue = f64; -#[derive(Debug)] -pub struct SlicerOptions { - pub layer_height: FloatValue, +pub fn aabb_from_points<'a, I, T: BHValue, const D: usize>(mut points: I) -> Aabb +where + I: Iterator>, +{ + if let Some(point) = points.next() { + let mut aabb = point.aabb(); + for point in points { + aabb.grow_mut(point); + } + aabb + } else { + Aabb::empty() + } } diff --git a/bampy/src/slicer/sdf.rs b/bampy/src/slicer/sdf.rs new file mode 100644 index 0000000..25c30e7 --- /dev/null +++ b/bampy/src/slicer/sdf.rs @@ -0,0 +1,253 @@ +/// https://iquilezles.org/articles/distfunctions/ +use nalgebra::{vector, Point, TAffine, Transform, Vector2, Vector3}; + +use super::FloatValue; + +pub trait Sdf { + fn sdf(&self, p: Point) -> FloatValue; +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfSphere { + radius: FloatValue, +} + +impl SdfSphere { + pub fn new(radius: FloatValue) -> Self { + Self { radius } + } +} + +impl Sdf<3> for SdfSphere { + fn sdf(&self, p: Point) -> FloatValue { + p.coords.norm() - self.radius + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfBox { + size: Point, +} + +impl SdfBox { + pub fn new(size: Point) -> Self { + Self { size } + } +} + +impl Sdf<3> for SdfBox { + fn sdf(&self, p: Point) -> FloatValue { + let q = p.coords.abs() - self.size.coords; + q.sup(&Vector3::zeros()).add_scalar(q.max().min(0.0)).norm() + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfInfiniteCone { + angle: Vector2, +} + +impl SdfInfiniteCone { + pub fn new(angle: FloatValue) -> Self { + Self { + angle: vector![angle.sin(), angle.cos()], + } + } +} + +impl Sdf<3> for SdfInfiniteCone { + fn sdf(&self, p: Point) -> FloatValue { + let q = vector![p.coords.xy().norm(), p.z]; + let d = (q - self.angle.scale(q.dot(&self.angle).max(0.0))).norm(); + if q.x * self.angle.y - q.y * self.angle.x > 0.0 { + d + } else { + -d + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfTransform> { + sdf: T, + transform: Transform, +} + +impl> SdfTransform { + fn new(sdf: T, transform: Transform) -> Self { + Self { sdf, transform } + } +} + +impl> Sdf<3> for SdfTransform { + fn sdf(&self, p: Point) -> FloatValue { + self.sdf.sdf(self.transform.inverse_transform_point(&p)) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfScale> { + sdf: T, + scale: FloatValue, +} + +impl> SdfScale { + fn new(sdf: T, scale: FloatValue) -> Self { + Self { sdf, scale } + } +} + +impl> Sdf for SdfScale { + fn sdf(&self, p: Point) -> FloatValue { + self.sdf.sdf(p / self.scale) * self.scale + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfOnion> { + sdf: T, + thickness: FloatValue, +} + +impl> SdfOnion { + fn new(sdf: T, thickness: FloatValue) -> Self { + Self { sdf, thickness } + } +} + +impl> Sdf<3> for SdfOnion { + fn sdf(&self, p: Point) -> FloatValue { + self.sdf.sdf(p).abs() - self.thickness + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfUnion, U: Sdf> { + sdf_a: T, + sdf_b: U, +} + +impl, U: Sdf> SdfUnion { + fn new(sdf_a: T, sdf_b: U) -> Self { + Self { sdf_a, sdf_b } + } +} + +impl, U: Sdf> Sdf for SdfUnion { + fn sdf(&self, p: Point) -> FloatValue { + self.sdf_a.sdf(p).min(self.sdf_b.sdf(p)) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfIntersection, U: Sdf> { + sdf_a: T, + sdf_b: U, +} + +impl, U: Sdf> SdfIntersection { + fn new(sdf_a: T, sdf_b: U) -> Self { + Self { sdf_a, sdf_b } + } +} + +impl, U: Sdf> Sdf for SdfIntersection { + fn sdf(&self, p: Point) -> FloatValue { + self.sdf_a.sdf(p).max(self.sdf_b.sdf(p)) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdfDifference, U: Sdf> { + sdf_a: T, + sdf_b: U, +} + +impl, U: Sdf> SdfDifference { + fn new(sdf_a: T, sdf_b: U) -> Self { + Self { sdf_a, sdf_b } + } +} + +impl, U: Sdf> Sdf for SdfDifference { + fn sdf(&self, p: Point) -> FloatValue { + self.sdf_a.sdf(p).max(-self.sdf_b.sdf(p)) + } +} + +pub trait SdfOperators: Sdf { + fn union(self, other: Self) -> SdfUnion + where + Self: Sized, + { + SdfUnion::new(self, other) + } + + fn intersection(self, other: Self) -> SdfIntersection + where + Self: Sized, + { + SdfIntersection::new(self, other) + } + + fn difference(self, other: Self) -> SdfDifference + where + Self: Sized, + { + SdfDifference::new(self, other) + } +} + +impl> SdfOperators for T {} + +pub trait Sdf3dModifiers: Sdf<3> { + /// exact + fn transform(self, transform: Transform) -> SdfTransform + where + Self: Sized, + { + SdfTransform::new(self, transform) + } + + /// exact + fn translate(self, translation: Point) -> SdfTransform + where + Self: Sized, + { + self.transform(Transform::from_matrix_unchecked( + nalgebra::Matrix4::new_translation(&translation.coords), + )) + } + + /// exact + fn rotate(self, rotation: nalgebra::UnitQuaternion) -> SdfTransform + where + Self: Sized, + { + self.transform(Transform::from_matrix_unchecked(rotation.to_homogeneous())) + } + + /// exact + fn scale(self, scale: FloatValue) -> SdfScale<3, Self> + where + Self: Sized, + { + SdfScale::new(self, scale) + } + + /// exact + /// + /// For carving interiors or giving thickness to primitives, + /// without performing expensive boolean operations + /// and without distorting the distance field into a bound, + /// one can use "onioning". + /// You can use it multiple times to create concentric layers in your SDF. + fn onion(self, thickness: FloatValue) -> SdfOnion + where + Self: Sized, + { + SdfOnion::new(self, thickness) + } +} + +impl> Sdf3dModifiers for T {} diff --git a/bampy/src/slicer/slice_path.rs b/bampy/src/slicer/slice_path.rs new file mode 100644 index 0000000..a0bc4fb --- /dev/null +++ b/bampy/src/slicer/slice_path.rs @@ -0,0 +1,151 @@ +use std::ops::RangeInclusive; + +use approx::relative_eq; +use bvh::aabb::Aabb; +use nalgebra::Point3; + +use super::{axis::Axis, mesh::Mesh, FloatValue}; + +#[derive(Debug)] +pub struct SlicePath { + pub i: usize, + pub d: FloatValue, + pub axis: Axis, + /// The points of the ring, in clockwise order. + pub points: Vec>, + pub closed: bool, + pub aabb: Aabb, +} + +pub struct SurfacePath { + pub i: RangeInclusive, + pub d: RangeInclusive, + pub axis: Axis, + pub path: Vec>, + pub aabb: Aabb, +} + +pub struct SurfacePathIterator { + slices: Vec>, + axis: Axis, + nozzle_width: FloatValue, +} + +impl SurfacePathIterator { + pub fn new(mesh: &Mesh, axis: Axis, nozzle_width: FloatValue) -> Self { + let (h_axis, _) = axis.other(); + + Self { + slices: mesh + .slice_paths(axis, nozzle_width) + .map(|mut slice| { + for ring in &mut slice { + ring.points.sort_unstable_by(|a, b| { + a.coords[h_axis as usize] + .partial_cmp(&b.coords[h_axis as usize]) + .unwrap() + }) + } + slice.sort_unstable_by(|a, b| { + a.points.first().unwrap().coords[h_axis as usize] + .partial_cmp(&b.points.first().unwrap().coords[h_axis as usize]) + .unwrap() + }); + let mut iter = slice.into_iter(); + let mut out = vec![iter.next().unwrap()]; + for ring in iter { + if relative_eq!( + out.last().unwrap().points.last().unwrap(), + ring.points.first().unwrap(), + epsilon = nozzle_width + ) { + let element = out.last_mut().unwrap(); + element.points.extend(ring.points); + element.aabb.join_mut(&ring.aabb); + } else { + out.push(ring); + } + } + out + }) + .collect(), + axis, + nozzle_width, + } + } +} + +impl Iterator for SurfacePathIterator { + type Item = SurfacePath; + + fn next(&mut self) -> Option { + self.slices.retain_mut(|slice| !slice.is_empty()); + + let (h_axis, _) = self.axis.other(); + + let ring = self.slices.first_mut()?.pop()?; + let mut item = Self::Item { + i: ring.i..=ring.i, + d: ring.d..=ring.d, + axis: ring.axis, + aabb: ring.aabb, + path: ring.points, + }; + + for slice in self.slices.iter_mut().skip(1) { + if *item.i.end() != slice[0].i - 1 { + break; + } + + let last = item.path.last().unwrap(); + + let mut d = FloatValue::MAX; + let mut needs_reverse = false; + let mut index = None; + + for (i, ring) in slice.iter().enumerate() { + macro_rules! item { + ($a:ident, $metric: ident) => { + $a.aabb.$metric[h_axis as usize] + }; + } + let a = item!(ring, max); + let b = item!(item, min); + if !(a > b || relative_eq!(a, b, epsilon = 0.1)) { + continue; + } + + let a = item!(ring, min); + let b = item!(item, max); + if !(a < b || relative_eq!(a, b, epsilon = 0.1)) { + continue; + } + + let d_left = last + .coords + .metric_distance(&ring.points.first().unwrap().coords); + let d_right = last + .coords + .metric_distance(&ring.points.last().unwrap().coords); + let d_min = d_left.min(d_right); + if d_min < d { + d = d_min; + needs_reverse = d_left > d_right; + index = Some(i); + } + } + + if let Some(mut ring) = index.map(|i| slice.remove(i)) { + if needs_reverse { + ring.points.reverse(); + } + item.i = *item.i.start()..=ring.i; + item.d = *item.d.start()..=ring.d; + item.path.append(&mut ring.points); + item.aabb.join_mut(&ring.aabb) + } + } + + Some(item) + } +} diff --git a/bampy/src/slicer/slice_rings.rs b/bampy/src/slicer/slice_rings.rs deleted file mode 100644 index 7b5ab9e..0000000 --- a/bampy/src/slicer/slice_rings.rs +++ /dev/null @@ -1,99 +0,0 @@ -use approx::relative_eq; -use nalgebra::Vector3; - -use crate::console_log; - -use super::{ - base_slices::{create_base_slices, BaseSlice}, - mesh::Mesh, - FloatValue, SlicerOptions, -}; - -#[derive(Debug)] -pub struct SliceRing { - pub d: FloatValue, - /// The points of the ring, in clockwise order. - pub points: Vec>, - pub closed: bool, -} - -pub fn slice_rings<'a>( - axis: usize, - options: &'a SlicerOptions, - slicable: &'a Mesh, -) -> impl Iterator> + 'a { - let mut layer_index = 0; - create_base_slices(axis, options, slicable) - .map(move |slice| find_slice_rings(axis, slice, &mut layer_index)) -} - -pub fn find_slice_rings( - axis: usize, - mut slice: BaseSlice, - layer_index: &mut u32, -) -> Vec { - let axis_a = (axis + 1) % 3; - let axis_b = (axis + 2) % 3; - let mut rings = vec![]; - while let Some(line) = slice.lines.pop() { - if relative_eq!(line.start, line.end) { - continue; - } - let mut right = vec![line.end]; - let mut left = vec![line.start]; - let mut previous_len = usize::MAX; - let mut closed = false; - - while !closed { - if previous_len == slice.lines.len() { - break; - } - previous_len = slice.lines.len(); - - slice.lines.retain_mut(|line| { - if closed { - return true; - } - - let test = |side: &mut Vec>| { - let last = side.last().unwrap(); - let s = relative_eq!(line.start, last); - let e = relative_eq!(line.end, last); - if s && !e { - side.push(line.end); - } else if !s && e { - side.push(line.start); - } - s || e - }; - - if test(&mut left) || test(&mut right) { - closed = relative_eq!(left.last().unwrap(), right.last().unwrap()); - false - } else { - true - } - }) - } - - left.reverse(); - left.extend(right); - let mut ring = SliceRing { - d: slice.d, - closed, - points: left, - }; - - if ring.points.windows(2).fold(0.0, |acc, curr| { - acc + (curr[1][axis_a] - curr[0][axis_a]) * (curr[1][axis_b] + curr[0][axis_b]) - }) < 0.0 - { - ring.points.reverse(); - } - - rings.push(ring); - *layer_index += 1; - } - - rings -} diff --git a/bampy/src/slicer/split_surface.rs b/bampy/src/slicer/split_surface.rs index 4af63fd..be8a467 100644 --- a/bampy/src/slicer/split_surface.rs +++ b/bampy/src/slicer/split_surface.rs @@ -2,6 +2,7 @@ use super::{mesh::Mesh, triangle::Triangle}; use bvh::bvh::{Bvh, BvhNode}; /// Splits a surface into connected surfaces. +/// TODO: self intersections pub fn split_surface(mut triangles: Vec) -> Vec { let mut surfaces = vec![]; while let Some(triangle) = triangles.pop() { diff --git a/bampy/src/slicer/trace_surface.rs b/bampy/src/slicer/trace_surface.rs index 17dfa34..56965fe 100644 --- a/bampy/src/slicer/trace_surface.rs +++ b/bampy/src/slicer/trace_surface.rs @@ -1,8 +1,8 @@ use bvh::bvh::BvhNode; -use super::{mesh::Mesh, slice_rings::SliceRing, z_projection::ToolpathIntersects, FloatValue}; +use super::{mesh::Mesh, slice_path::SlicePath, z_projection::ToolpathIntersects, FloatValue}; -pub fn trace_surface(slice: &mut SliceRing, surface: &Mesh, a: FloatValue) { +pub fn trace_surface(slice: &mut SlicePath, surface: &Mesh, a: FloatValue) { slice.points.retain_mut(|point| { let mut stack = Vec::::new(); stack.push(0); diff --git a/bampy/src/slicer/triangle.rs b/bampy/src/slicer/triangle.rs index e35c606..94ceb5e 100644 --- a/bampy/src/slicer/triangle.rs +++ b/bampy/src/slicer/triangle.rs @@ -9,72 +9,50 @@ use super::{line::Line3, FloatValue}; #[derive(Debug, Clone, Copy)] pub struct Triangle { - pub a: Vector3, - pub b: Vector3, - pub c: Vector3, + pub a: Point3, + pub b: Point3, + pub c: Point3, pub normal: Vector3, node_index: usize, pub aabb: Aabb, } -#[inline(always)] -fn vec_inside_aabb(vec: &Vector3, aabb: &Aabb) -> bool { - macro_rules! within { - ($axis:ident) => { - ((vec.$axis >= aabb.min.$axis && vec.$axis <= aabb.max.$axis) - || relative_eq!(vec.$axis, aabb.min.$axis) - || relative_eq!(vec.$axis, aabb.max.$axis)) - }; - } - within!(x) && within!(y) && within!(z) -} - impl Triangle { - pub fn new(a: Vector3, b: Vector3, c: Vector3) -> Self { + pub fn new(a: Point3, b: Point3, c: Point3) -> Self { + let mut aabb = a.aabb(); + aabb.grow_mut(&b); + aabb.grow_mut(&c); Self { a, b, c, normal: (b - a).cross(&(c - a)).into(), node_index: 0, - aabb: Aabb::with_bounds( - Point3::new( - FloatValue::min(FloatValue::max(a.x, b.x), c.x), - FloatValue::min(FloatValue::min(a.y, b.y), c.y), - FloatValue::min(FloatValue::min(a.z, b.z), c.z), - ), - Point3::new( - FloatValue::max(FloatValue::max(a.x, b.x), c.x), - FloatValue::max(FloatValue::max(a.y, b.y), c.y), - FloatValue::max(FloatValue::max(a.z, b.z), c.z), - ), - ), + aabb, } } pub fn has_point_in_aabb(&self, aabb: &Aabb) -> bool { - vec_inside_aabb(&self.a, aabb) - || vec_inside_aabb(&self.b, aabb) - || vec_inside_aabb(&self.c, aabb) + aabb.contains(&self.a) || aabb.contains(&self.b) || aabb.contains(&self.c) } - pub fn has_vec(&self, vec: Vector3) -> bool { + pub fn has_point(&self, vec: Point3) -> bool { relative_eq!(self.a, vec) || relative_eq!(self.b, vec) || relative_eq!(self.c, vec) } pub fn shares_point_with_triangle(&self, other: Triangle) -> bool { - self.has_vec(other.a) || self.has_vec(other.b) || self.has_vec(other.c) + self.has_point(other.a) || self.has_point(other.b) || self.has_point(other.c) } pub fn shares_edge_with_triangle(&self, other: Triangle) -> bool { - let a = self.has_vec(other.a); - let b = self.has_vec(other.b); - let c = self.has_vec(other.c); + let a = self.has_point(other.a); + let b = self.has_point(other.b); + let c = self.has_point(other.c); a && b || a && c || b && c } pub fn intersect(&self, value: FloatValue, axis: usize) -> Option { - let mut intersection = Vec::>::with_capacity(3); + let mut intersection = Vec::>::with_capacity(3); let mut last = &self.c; for point in [self.a, self.b, self.c].iter() { if relative_eq!(point[axis], value) { @@ -103,6 +81,12 @@ impl Triangle { None } } + + pub fn area(&self) -> FloatValue { + let ab = self.b - self.a; + let ac = self.c - self.a; + 0.5 * ab.cross(&ac).norm() + } } impl Bounded for Triangle { diff --git a/bampy/src/slicer/z_projection.rs b/bampy/src/slicer/z_projection.rs index 18b0291..a31787d 100644 --- a/bampy/src/slicer/z_projection.rs +++ b/bampy/src/slicer/z_projection.rs @@ -1,5 +1,5 @@ use bvh::aabb::Aabb; -use nalgebra::{Point2, Vector2, Vector3}; +use nalgebra::{Point2, Point3}; use super::{triangle::Triangle, FloatValue}; @@ -11,7 +11,7 @@ pub trait ProjectToolpath { pub trait ToolpathIntersects: ProjectToolpath { /// Checks if a hypothetical toolpath that draws the object could intersect /// with the given point, given the tangent of the angle of the toolhead - fn toolpath_intersects(&self, point: &Vector3, a: FloatValue) -> bool; + fn toolpath_intersects(&self, point: &Point3, a: FloatValue) -> bool; } pub trait ToolpathIntersection { @@ -38,7 +38,7 @@ impl ProjectToolpath> for Aabb { } impl ToolpathIntersects> for Aabb { - fn toolpath_intersects(&self, point: &Vector3, a: FloatValue) -> bool { + fn toolpath_intersects(&self, point: &Point3, a: FloatValue) -> bool { if let Some(aabb) = self.project_toolpath_onto_z(point.z, a) { aabb.approx_contains_eps(&Point2::new(point.x, point.y), FloatValue::EPSILON) } else { @@ -100,7 +100,7 @@ impl ProjectToolpath for Triangle { mod tests { use approx::assert_relative_eq; use bvh::aabb::Aabb; - use nalgebra::{Point3, Vector2, Vector3}; + use nalgebra::{point, Point3}; use crate::slicer::{triangle::Triangle, z_projection::ProjectToolpath, FloatValue}; @@ -129,9 +129,9 @@ mod tests { #[test] fn test_project_triangle_toolpath() { let triangle = Triangle::new( - Vector3::new(0.0, 0.0, 0.0), - Vector3::new(0.0, 1.5, 1.0), - Vector3::new(-0.6, -1.4, 0.2), + point![0.0, 0.0, 0.0], + point![0.0, 1.5, 1.0], + point![-0.6, -1.4, 0.2], ); let a = FloatValue::to_radians(30.0).tan(); diff --git a/src/lib/components/Scene.svelte b/src/lib/components/Scene.svelte index ad14255..6f0dd05 100644 --- a/src/lib/components/Scene.svelte +++ b/src/lib/components/Scene.svelte @@ -58,6 +58,7 @@ export let buildSurface = [300, 300, 300]; export let layerHeight = 0.2; + export let nozzleDiameter = 0.4; export let tolerance = 0.005; export let progress = writable(undefined); export let progressLayer = writable(0); @@ -79,6 +80,8 @@ layerHeight, tolerance, maxNonPlanarAngle, + nozzleDiameter, + minSurfacePathLength: nozzleDiameter * 4, bedNormal: bedNormal.toArray() } } satisfies SliceEvent); diff --git a/src/lib/slicer/worker-data.ts b/src/lib/slicer/worker-data.ts index 96fd438..096f445 100644 --- a/src/lib/slicer/worker-data.ts +++ b/src/lib/slicer/worker-data.ts @@ -6,6 +6,8 @@ export interface SliceArguments { maxNonPlanarAngle: number; tolerance: number; layerHeight: number; + nozzleDiameter: number; + minSurfacePathLength: number; } export interface SliceEvent { diff --git a/src/lib/slicer/worker.ts b/src/lib/slicer/worker.ts index cb25fd8..a9d21df 100644 --- a/src/lib/slicer/worker.ts +++ b/src/lib/slicer/worker.ts @@ -18,7 +18,9 @@ addEventListener('message', async (event: MessageEvent) => { const result = slice({ positions: geometry.attributes.position.array as Float32Array, layerHeight: event.data.data.layerHeight, - maxAngle: event.data.data.maxNonPlanarAngle + maxAngle: event.data.data.maxNonPlanarAngle, + nozzleDiameter: event.data.data.nozzleDiameter, + minSurfacePathLength: event.data.data.minSurfacePathLength }); for (const layer of result.slices) { self.postMessage({