From 9794df8eadd8770660eb0d683df6bff0bd0f1a93 Mon Sep 17 00:00:00 2001 From: Mitja Kleider Date: Sun, 10 Mar 2024 12:25:56 +0100 Subject: [PATCH] Add winding validation for sketches (#2158) --- crates/fj-core/src/validate/sketch.rs | 118 ++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/crates/fj-core/src/validate/sketch.rs b/crates/fj-core/src/validate/sketch.rs index d7fe7c4a3..1724d7ae9 100644 --- a/crates/fj-core/src/validate/sketch.rs +++ b/crates/fj-core/src/validate/sketch.rs @@ -1,4 +1,5 @@ use crate::{objects::Sketch, validate_references}; +use fj_math::Winding; use super::{ references::{ReferenceCountError, ReferenceCounter}, @@ -12,6 +13,8 @@ impl Validate for Sketch { errors: &mut Vec, ) { SketchValidationError::check_object_references(self, config, errors); + SketchValidationError::check_exterior_cycle(self, config, errors); + SketchValidationError::check_interior_cycles(self, config, errors); } } @@ -21,6 +24,14 @@ pub enum SketchValidationError { /// Object within sketch referenced by more than one other object #[error("Object within sketch referenced by more than one other Object")] MultipleReferences(#[from] ReferenceCountError), + /// Region within sketch has exterior cycle with clockwise winding + #[error("Exterior cycle within sketch region has clockwise winding")] + ClockwiseExteriorCycle(), + /// Region within sketch has interior cycle with counter-clockwise winding + #[error( + "Interior cycle within sketch region has counter-clockwise winding" + )] + CounterClockwiseInteriorCycle(), } impl SketchValidationError { @@ -47,6 +58,38 @@ impl SketchValidationError { referenced_cycles, Cycle; ); } + + fn check_exterior_cycle( + sketch: &Sketch, + _config: &ValidationConfig, + errors: &mut Vec, + ) { + sketch.regions().iter().for_each(|region| { + if region.exterior().winding() == Winding::Cw { + errors.push(ValidationError::Sketch( + SketchValidationError::ClockwiseExteriorCycle(), + )) + } + }); + } + + fn check_interior_cycles( + sketch: &Sketch, + _config: &ValidationConfig, + errors: &mut Vec, + ) { + sketch.regions().iter().for_each(|region| { + region + .interiors() + .iter() + .filter(|interior| interior.winding() == Winding::Ccw) + .for_each(|_interior| { + errors.push(ValidationError::Sketch( + SketchValidationError::CounterClockwiseInteriorCycle(), + )); + }) + }); + } } #[cfg(test)] @@ -118,4 +161,79 @@ mod tests { Ok(()) } + + #[test] + fn should_find_clockwise_exterior_cycle() -> anyhow::Result<()> { + let mut core = Core::new(); + + let valid_outer_circle = + HalfEdge::circle([0., 0.], 1., &mut core).insert(&mut core); + let valid_exterior = + Cycle::new(vec![valid_outer_circle.clone()]).insert(&mut core); + let valid_sketch = + Sketch::new(vec![ + Region::new(valid_exterior.clone(), vec![]).insert(&mut core) + ]); + valid_sketch.validate_and_return_first_error()?; + + let invalid_outer_circle = HalfEdge::from_sibling( + &valid_outer_circle, + Vertex::new().insert(&mut core), + ) + .insert(&mut core); + let invalid_exterior = + Cycle::new(vec![invalid_outer_circle.clone()]).insert(&mut core); + let invalid_sketch = + Sketch::new(vec![ + Region::new(invalid_exterior.clone(), vec![]).insert(&mut core) + ]); + assert_contains_err!( + invalid_sketch, + ValidationError::Sketch( + SketchValidationError::ClockwiseExteriorCycle() + ) + ); + + Ok(()) + } + + #[test] + fn should_find_counterclockwise_interior_cycle() -> anyhow::Result<()> { + let mut core = Core::new(); + + let outer_circle = + HalfEdge::circle([0., 0.], 2., &mut core).insert(&mut core); + let inner_circle = + HalfEdge::circle([0., 0.], 1., &mut core).insert(&mut core); + let cw_inner_circle = HalfEdge::from_sibling( + &inner_circle, + Vertex::new().insert(&mut core), + ) + .insert(&mut core); + let exterior = Cycle::new(vec![outer_circle.clone()]).insert(&mut core); + + let valid_interior = + Cycle::new(vec![cw_inner_circle.clone()]).insert(&mut core); + let valid_sketch = Sketch::new(vec![Region::new( + exterior.clone(), + vec![valid_interior], + ) + .insert(&mut core)]); + valid_sketch.validate_and_return_first_error()?; + + let invalid_interior = + Cycle::new(vec![inner_circle.clone()]).insert(&mut core); + let invalid_sketch = Sketch::new(vec![Region::new( + exterior.clone(), + vec![invalid_interior], + ) + .insert(&mut core)]); + assert_contains_err!( + invalid_sketch, + ValidationError::Sketch( + SketchValidationError::CounterClockwiseInteriorCycle() + ) + ); + Ok(()) + } }