diff --git a/crates/fj-kernel/src/algorithms/approx/face.rs b/crates/fj-kernel/src/algorithms/approx/face.rs index d3f9819e1..30b902a62 100644 --- a/crates/fj-kernel/src/algorithms/approx/face.rs +++ b/crates/fj-kernel/src/algorithms/approx/face.rs @@ -6,10 +6,58 @@ use std::collections::BTreeSet; use fj_interop::mesh::Color; -use crate::objects::Face; +use crate::{ + algorithms::validate::ValidationConfig, + objects::{Face, Faces}, +}; use super::{cycle::CycleApprox, Approx, ApproxPoint, Tolerance}; +impl Approx for &Faces { + type Approximation = BTreeSet; + + fn approx(self, tolerance: Tolerance) -> Self::Approximation { + let approx = self + .into_iter() + .map(|face| face.approx(tolerance)) + .collect(); + + let min_distance = ValidationConfig::default().distinct_min_distance; + let mut all_points: BTreeSet> = BTreeSet::new(); + + for approx in &approx { + let approx: &FaceApprox = approx; + + for point in &approx.points() { + for p in &all_points { + let distance = + (p.global_form - point.global_form).magnitude(); + + if p.global_form != point.global_form + && distance < min_distance + { + let a = p; + let b = point; + + panic!( + "Invalid approximation: \ + Distinct points are too close \ + (a: {:?}, b: {:?}, distance: {distance})\n\ + source of `a`: {:#?}\n\ + source of `b`: {:#?}\n", + a.global_form, b.global_form, a.source, b.source + ); + } + } + + all_points.insert(point.clone()); + } + } + + approx + } +} + impl Approx for &Face { type Approximation = FaceApprox; diff --git a/crates/fj-kernel/src/algorithms/approx/mod.rs b/crates/fj-kernel/src/algorithms/approx/mod.rs index fc6bafcb1..0284b2b51 100644 --- a/crates/fj-kernel/src/algorithms/approx/mod.rs +++ b/crates/fj-kernel/src/algorithms/approx/mod.rs @@ -4,6 +4,9 @@ pub mod curve; pub mod cycle; pub mod edge; pub mod face; +pub mod shell; +pub mod sketch; +pub mod solid; pub mod tolerance; use std::{ diff --git a/crates/fj-kernel/src/algorithms/approx/shell.rs b/crates/fj-kernel/src/algorithms/approx/shell.rs new file mode 100644 index 000000000..fcce75511 --- /dev/null +++ b/crates/fj-kernel/src/algorithms/approx/shell.rs @@ -0,0 +1,15 @@ +//! Shell approximation + +use std::collections::BTreeSet; + +use crate::objects::Shell; + +use super::{face::FaceApprox, Approx, Tolerance}; + +impl Approx for &Shell { + type Approximation = BTreeSet; + + fn approx(self, tolerance: Tolerance) -> Self::Approximation { + self.faces().approx(tolerance) + } +} diff --git a/crates/fj-kernel/src/algorithms/approx/sketch.rs b/crates/fj-kernel/src/algorithms/approx/sketch.rs new file mode 100644 index 000000000..43938b4b4 --- /dev/null +++ b/crates/fj-kernel/src/algorithms/approx/sketch.rs @@ -0,0 +1,15 @@ +//! Sketch approximation + +use std::collections::BTreeSet; + +use crate::objects::Sketch; + +use super::{face::FaceApprox, Approx, Tolerance}; + +impl Approx for &Sketch { + type Approximation = BTreeSet; + + fn approx(self, tolerance: Tolerance) -> Self::Approximation { + self.faces().approx(tolerance) + } +} diff --git a/crates/fj-kernel/src/algorithms/approx/solid.rs b/crates/fj-kernel/src/algorithms/approx/solid.rs new file mode 100644 index 000000000..34126f82c --- /dev/null +++ b/crates/fj-kernel/src/algorithms/approx/solid.rs @@ -0,0 +1,17 @@ +//! Solid approximation + +use std::collections::BTreeSet; + +use crate::objects::Solid; + +use super::{face::FaceApprox, Approx, Tolerance}; + +impl Approx for &Solid { + type Approximation = BTreeSet; + + fn approx(self, tolerance: Tolerance) -> Self::Approximation { + self.shells() + .flat_map(|shell| shell.approx(tolerance)) + .collect() + } +} diff --git a/crates/fj-kernel/src/algorithms/triangulate/mod.rs b/crates/fj-kernel/src/algorithms/triangulate/mod.rs index 2355c4c61..67dc3eafc 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/mod.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/mod.rs @@ -3,25 +3,19 @@ mod delaunay; mod polygon; -use fj_interop::{debug::DebugInfo, mesh::Mesh}; +use fj_interop::mesh::Mesh; use fj_math::Point; -use crate::objects::Face; - use self::{delaunay::TriangulationPoint, polygon::Polygon}; -use super::approx::{Approx, Tolerance}; +use super::approx::{face::FaceApprox, Approx, Tolerance}; /// Triangulate a shape pub trait Triangulate: Sized { /// Triangulate the shape - fn triangulate( - self, - tolerance: impl Into, - debug_info: &mut DebugInfo, - ) -> Mesh> { + fn triangulate(self, tolerance: impl Into) -> Mesh> { let mut mesh = Mesh::new(); - self.triangulate_into_mesh(tolerance, &mut mesh, debug_info); + self.triangulate_into_mesh(tolerance, &mut mesh); mesh } @@ -33,39 +27,35 @@ pub trait Triangulate: Sized { self, tolerance: impl Into, mesh: &mut Mesh>, - debug_info: &mut DebugInfo, ); } impl Triangulate for T where - T: IntoIterator, + T: Approx, + T::Approximation: IntoIterator, { fn triangulate_into_mesh( self, tolerance: impl Into, mesh: &mut Mesh>, - debug_info: &mut DebugInfo, ) { let tolerance = tolerance.into(); + let approx = self.approx(tolerance); - for face in self { - face.triangulate_into_mesh(tolerance, mesh, debug_info); + for approx in approx { + approx.triangulate_into_mesh(tolerance, mesh); } } } -impl Triangulate for Face { +impl Triangulate for FaceApprox { fn triangulate_into_mesh( self, - tolerance: impl Into, + _: impl Into, mesh: &mut Mesh>, - debug_info: &mut DebugInfo, ) { - let surface = self.surface(); - let approx = self.approx(tolerance.into()); - - let points: Vec<_> = approx + let points: Vec<_> = self .points() .into_iter() .map(|point| TriangulationPoint { @@ -73,40 +63,37 @@ impl Triangulate for Face { point_global: point.global_form, }) .collect(); - let face_as_polygon = Polygon::new(*surface) + let face_as_polygon = Polygon::new() .with_exterior( - approx - .exterior + self.exterior .points() .into_iter() .map(|point| point.local_form), ) - .with_interiors(approx.interiors.into_iter().map(|interior| { + .with_interiors(self.interiors.into_iter().map(|interior| { interior.points().into_iter().map(|point| point.local_form) })); let mut triangles = delaunay::triangulate(points); triangles.retain(|triangle| { - face_as_polygon.contains_triangle( - triangle.map(|point| point.point_surface), - debug_info, - ) + face_as_polygon + .contains_triangle(triangle.map(|point| point.point_surface)) }); for triangle in triangles { let points = triangle.map(|point| point.point_global); - mesh.push_triangle(points, self.color()); + mesh.push_triangle(points, self.color); } } } #[cfg(test)] mod tests { - use fj_interop::{debug::DebugInfo, mesh::Mesh}; + use fj_interop::mesh::Mesh; use fj_math::{Point, Scalar}; use crate::{ - algorithms::approx::Tolerance, + algorithms::approx::{Approx, Tolerance}, objects::{Face, Surface}, }; @@ -221,8 +208,6 @@ mod tests { fn triangulate(face: impl Into) -> anyhow::Result>> { let tolerance = Tolerance::from_scalar(Scalar::ONE)?; - - let mut debug_info = DebugInfo::new(); - Ok(vec![face.into()].triangulate(tolerance, &mut debug_info)) + Ok(face.into().approx(tolerance).triangulate(tolerance)) } } diff --git a/crates/fj-kernel/src/algorithms/triangulate/polygon.rs b/crates/fj-kernel/src/algorithms/triangulate/polygon.rs index d3043bc12..21e057c69 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/polygon.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/polygon.rs @@ -1,38 +1,18 @@ -use fj_interop::debug::{DebugInfo, TriangleEdgeCheck}; use fj_math::{Point, PolyChain, Segment}; -use crate::{ - algorithms::intersect::{ - ray_segment::RaySegmentIntersection, HorizontalRayToTheRight, Intersect, - }, - objects::Surface, +use crate::algorithms::intersect::{ + ray_segment::RaySegmentIntersection, HorizontalRayToTheRight, Intersect, }; pub struct Polygon { - surface: Surface, exterior: PolyChain<2>, interiors: Vec>, } impl Polygon { /// Construct an instance of `Polygon` - /// - /// # Implementation note - /// - /// This method takes a `Surface`, but `Polygon` only uses that for - /// generating debug info. It might be better, if `Polygon` had a field - /// where it stored debug info specific to its algorithm. Then code using - /// `Polygon` could access that `Polygon`-specific debug info and translate - /// that into `DebugInfo`, as necessary. - /// - /// This would have the advantage of removing this dependency on `Surface`. - /// It would also make the test code a bit cleaner, as it wouldn't have to - /// bother with the `DebugInfo` anymore. Also, the `Polygon`-specific debug - /// info could potentially be more useful in test code, as a debugging tool - /// there. - pub fn new(surface: Surface) -> Self { + pub fn new() -> Self { Self { - surface, exterior: PolyChain::new(), interiors: Vec::new(), } @@ -65,7 +45,6 @@ impl Polygon { pub fn contains_triangle( &self, triangle: [impl Into>; 3], - debug_info: &mut DebugInfo, ) -> bool { let [a, b, c] = triangle.map(Into::into); @@ -99,7 +78,7 @@ impl Polygon { // polygon edge (and if we reached this point, it isn't), we don't // need to care about the distinction between "inside the polygon" // and "on the polygon boundary". - if !self.contains_point(edge.center(), debug_info) { + if !self.contains_point(edge.center()) { // The segment is outside of the face. This means we can throw // away the whole triangle. return false; @@ -140,19 +119,11 @@ impl Polygon { /// This code is being duplicated by the `Contains>` implementation /// for `Face`. It would be nice to be able to consolidate the duplication, /// but this has turned out to be difficult. - pub fn contains_point( - &self, - point: impl Into>, - debug_info: &mut DebugInfo, - ) -> bool { + pub fn contains_point(&self, point: impl Into>) -> bool { let ray = HorizontalRayToTheRight { origin: point.into(), }; - let mut check = TriangleEdgeCheck::new( - self.surface.point_from_surface_coords(ray.origin), - ); - let mut num_hits = 0; for chain in Some(&self.exterior).into_iter().chain(&self.interiors) { @@ -228,31 +199,20 @@ impl Polygon { if count_hit { num_hits += 1; - - let edge = - Segment::from_points(edge.points().map(|point| { - self.surface.point_from_surface_coords(point) - })); - check.hits.push(edge); } previous_hit = hit; } } - debug_info.triangle_edge_checks.push(check); - num_hits % 2 == 1 } } #[cfg(test)] mod tests { - use fj_interop::debug::DebugInfo; use fj_math::{Point, PolyChain}; - use crate::objects::Surface; - use super::Polygon; #[test] @@ -265,11 +225,11 @@ mod tests { let e = [2., 1.]; let f = [1., 2.]; - let polygon = Polygon::new(Surface::xy_plane()) + let polygon = Polygon::new() .with_exterior(PolyChain::from([a, b, c]).close()) .with_interiors([PolyChain::from([d, e, f]).close()]); - assert!(!polygon.contains_triangle([d, e, f], &mut DebugInfo::new())); + assert!(!polygon.contains_triangle([d, e, f])); } #[test] @@ -278,8 +238,8 @@ mod tests { let b = [2., 1.]; let c = [0., 2.]; - let polygon = Polygon::new(Surface::xy_plane()) - .with_exterior(PolyChain::from([a, b, c]).close()); + let polygon = + Polygon::new().with_exterior(PolyChain::from([a, b, c]).close()); assert_contains_point(polygon, [1., 1.]); } @@ -294,7 +254,7 @@ mod tests { let e = [2., 1.]; let f = [1., 3.]; - let polygon = Polygon::new(Surface::xy_plane()) + let polygon = Polygon::new() .with_exterior(PolyChain::from([a, b, c]).close()) .with_interiors([PolyChain::from([d, e, f]).close()]); @@ -308,8 +268,8 @@ mod tests { let c = [3., 0.]; let d = [3., 4.]; - let polygon = Polygon::new(Surface::xy_plane()) - .with_exterior(PolyChain::from([a, b, c, d]).close()); + let polygon = + Polygon::new().with_exterior(PolyChain::from([a, b, c, d]).close()); assert_contains_point(polygon, [1., 1.]); } @@ -321,8 +281,8 @@ mod tests { let b = [2., 1.]; let c = [3., 1.]; let d = [0., 2.]; - let polygon = Polygon::new(Surface::xy_plane()) - .with_exterior(PolyChain::from([a, b, c, d]).close()); + let polygon = + Polygon::new().with_exterior(PolyChain::from([a, b, c, d]).close()); assert_contains_point(polygon, [1., 1.]); // Ray hits a vertex, but doesn't pass polygon boundary there. @@ -331,7 +291,7 @@ mod tests { let c = [3., 1.]; let d = [4., 0.]; let e = [4., 5.]; - let polygon = Polygon::new(Surface::xy_plane()) + let polygon = Polygon::new() .with_exterior(PolyChain::from([a, b, c, d, e]).close()); assert_contains_point(polygon, [1., 1.]); } @@ -339,9 +299,7 @@ mod tests { fn assert_contains_point(polygon: Polygon, point: impl Into>) { let point = point.into(); - assert!(polygon.contains_point(point, &mut DebugInfo::new())); - assert!(polygon - .invert_winding() - .contains_point(point, &mut DebugInfo::new())); + assert!(polygon.contains_point(point)); + assert!(polygon.invert_winding().contains_point(point,)); } } diff --git a/crates/fj-operations/src/shape_processor.rs b/crates/fj-operations/src/shape_processor.rs index 77b1b7b97..559d8792f 100644 --- a/crates/fj-operations/src/shape_processor.rs +++ b/crates/fj-operations/src/shape_processor.rs @@ -42,7 +42,7 @@ impl ShapeProcessor { let config = ValidationConfig::default(); let mut debug_info = DebugInfo::new(); let shape = shape.compute_brep(&config, tolerance, &mut debug_info)?; - let mesh = shape.into_inner().triangulate(tolerance, &mut debug_info); + let mesh = shape.into_inner().triangulate(tolerance); Ok(ProcessedShape { aabb,