Skip to content

Commit

Permalink
Add winding validation for sketches (hannobraun#2158)
Browse files Browse the repository at this point in the history
  • Loading branch information
emka authored and hannobraun committed Mar 13, 2024
1 parent 0aa266e commit 9794df8
Showing 1 changed file with 118 additions and 0 deletions.
118 changes: 118 additions & 0 deletions crates/fj-core/src/validate/sketch.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{objects::Sketch, validate_references};
use fj_math::Winding;

use super::{
references::{ReferenceCountError, ReferenceCounter},
Expand All @@ -12,6 +13,8 @@ impl Validate for Sketch {
errors: &mut Vec<ValidationError>,
) {
SketchValidationError::check_object_references(self, config, errors);
SketchValidationError::check_exterior_cycle(self, config, errors);
SketchValidationError::check_interior_cycles(self, config, errors);
}
}

Expand All @@ -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 {
Expand All @@ -47,6 +58,38 @@ impl SketchValidationError {
referenced_cycles, Cycle;
);
}

fn check_exterior_cycle(
sketch: &Sketch,
_config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
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<ValidationError>,
) {
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)]
Expand Down Expand Up @@ -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(())
}
}

0 comments on commit 9794df8

Please sign in to comment.