diff --git a/crates/fj-core/src/validate/shell.rs b/crates/fj-core/src/validate/shell.rs index d07c97229..a767f09ce 100644 --- a/crates/fj-core/src/validate/shell.rs +++ b/crates/fj-core/src/validate/shell.rs @@ -1,16 +1,17 @@ -use std::fmt; - use fj_math::{Point, Scalar}; use crate::{ - geometry::{CurveBoundary, Geometry, SurfaceGeom}, + geometry::{Geometry, SurfaceGeom}, queries::{ AllHalfEdgesWithSurface, BoundingVerticesOfHalfEdge, SiblingOfHalfEdge, }, storage::Handle, - topology::{Curve, HalfEdge, Shell, Vertex}, + topology::{HalfEdge, Shell}, validation::{ - checks::{CurveGeometryMismatch, HalfEdgeHasNoSibling}, + checks::{ + CoincidentHalfEdgesAreNotSiblings, CurveGeometryMismatch, + HalfEdgeHasNoSibling, + }, ValidationCheck, }, }; @@ -117,90 +118,6 @@ impl ShellValidationError { } } -/// A [`Shell`] contains two [`HalfEdge`]s that are coincident but not siblings -/// -/// Coincident half-edges must reference the same curve, and have the same -/// boundaries on that curve. This provides clear, topological information, -/// which is important to handle the shell geometry in a robust way. -#[derive(Clone, Debug, thiserror::Error)] -pub struct CoincidentHalfEdgesAreNotSiblings { - /// The boundaries of the half-edges - pub boundaries: [CurveBoundary>; 2], - - /// The curves of the half-edges - pub curves: [Handle; 2], - - /// The vertices of the half-edges - pub vertices: [CurveBoundary; 2], - - /// The first half-edge - pub half_edge_a: Handle, - - /// The second half-edge - pub half_edge_b: Handle, -} - -impl fmt::Display for CoincidentHalfEdgesAreNotSiblings { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "`Shell` contains `HalfEdge`s that are coincident but are not \ - siblings", - )?; - - { - let [a, b] = &self.boundaries; - - if a != &b.reverse() { - writeln!( - f, - "Boundaries don't match.\n\ - \tHalf-edge 1 has boundary `{a:?}`\n\ - \tHalf-edge 2 has boundary `{b:?}`\n\ - \t(expecting same boundary, but reversed)" - )?; - } - } - - { - let [a, b] = &self.curves; - - if a.id() != b.id() { - writeln!( - f, - "Curves don't match.\n\ - \tHalf-edge 1 lies on {a:?}\n\ - \tHalf-edge 2 lies on {b:?}\n\ - \t(must be the same)" - )?; - } - } - - { - let [a, b] = &self.vertices; - - if a != &b.clone().reverse() { - writeln!( - f, - "Vertices don't match.\n\ - \tHalf-edge 1 is bounded by `{a:?}`\n\ - \tHalf-edge 2 is bounded by `{b:?}`\n\ - \t(expecting same vertices, but in reverse order)" - )?; - } - } - - write!( - f, - "Half-edge 1: {:#?}\n\ - Half-edge 2: {:#?}", - self.half_edge_a, self.half_edge_b, - )?; - - Ok(()) - } -} - /// Sample two edges at various (currently 3) points in 3D along them. /// /// Returns an [`Iterator`] of the distance at each sample. diff --git a/crates/fj-core/src/validation/checks/coincident_half_edges_are_not_siblings.rs b/crates/fj-core/src/validation/checks/coincident_half_edges_are_not_siblings.rs new file mode 100644 index 000000000..72a378797 --- /dev/null +++ b/crates/fj-core/src/validation/checks/coincident_half_edges_are_not_siblings.rs @@ -0,0 +1,95 @@ +use std::fmt; + +use fj_math::Point; + +use crate::{ + geometry::CurveBoundary, + storage::Handle, + topology::{Curve, HalfEdge, Vertex}, +}; + +/// A [`Shell`] contains two [`HalfEdge`]s that are coincident but not siblings +/// +/// Coincident half-edges must reference the same curve, and have the same +/// boundaries on that curve. This provides clear, topological information, +/// which is important to handle the shell geometry in a robust way. +/// +/// [`Shell`]: crate::topology::Shell +#[derive(Clone, Debug, thiserror::Error)] +pub struct CoincidentHalfEdgesAreNotSiblings { + /// The boundaries of the half-edges + pub boundaries: [CurveBoundary>; 2], + + /// The curves of the half-edges + pub curves: [Handle; 2], + + /// The vertices of the half-edges + pub vertices: [CurveBoundary; 2], + + /// The first half-edge + pub half_edge_a: Handle, + + /// The second half-edge + pub half_edge_b: Handle, +} + +impl fmt::Display for CoincidentHalfEdgesAreNotSiblings { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "`Shell` contains `HalfEdge`s that are coincident but are not \ + siblings", + )?; + + { + let [a, b] = &self.boundaries; + + if a != &b.reverse() { + writeln!( + f, + "Boundaries don't match.\n\ + \tHalf-edge 1 has boundary `{a:?}`\n\ + \tHalf-edge 2 has boundary `{b:?}`\n\ + \t(expecting same boundary, but reversed)" + )?; + } + } + + { + let [a, b] = &self.curves; + + if a.id() != b.id() { + writeln!( + f, + "Curves don't match.\n\ + \tHalf-edge 1 lies on {a:?}\n\ + \tHalf-edge 2 lies on {b:?}\n\ + \t(must be the same)" + )?; + } + } + + { + let [a, b] = &self.vertices; + + if a != &b.clone().reverse() { + writeln!( + f, + "Vertices don't match.\n\ + \tHalf-edge 1 is bounded by `{a:?}`\n\ + \tHalf-edge 2 is bounded by `{b:?}`\n\ + \t(expecting same vertices, but in reverse order)" + )?; + } + } + + write!( + f, + "Half-edge 1: {:#?}\n\ + Half-edge 2: {:#?}", + self.half_edge_a, self.half_edge_b, + )?; + + Ok(()) + } +} diff --git a/crates/fj-core/src/validation/checks/mod.rs b/crates/fj-core/src/validation/checks/mod.rs index 85ae615d0..7848e1ec8 100644 --- a/crates/fj-core/src/validation/checks/mod.rs +++ b/crates/fj-core/src/validation/checks/mod.rs @@ -2,6 +2,7 @@ //! //! See documentation of [parent module](super) for more information. +mod coincident_half_edges_are_not_siblings; mod curve_geometry_mismatch; mod face_boundary; mod face_winding; @@ -9,6 +10,7 @@ mod half_edge_connection; mod half_edge_has_no_sibling; pub use self::{ + coincident_half_edges_are_not_siblings::CoincidentHalfEdgesAreNotSiblings, curve_geometry_mismatch::CurveGeometryMismatch, face_boundary::FaceHasNoBoundary, face_winding::InteriorCycleHasInvalidWinding,