fj_kernel/validate/
edge.rs

1use fj_math::{Point, Scalar};
2
3use crate::objects::{GlobalEdge, HalfEdge};
4
5use super::{Validate, ValidationConfig, ValidationError};
6
7impl Validate for HalfEdge {
8    fn validate_with_config(
9        &self,
10        config: &ValidationConfig,
11        errors: &mut Vec<ValidationError>,
12    ) {
13        HalfEdgeValidationError::check_vertex_coincidence(self, config, errors);
14    }
15}
16
17impl Validate for GlobalEdge {
18    fn validate_with_config(
19        &self,
20        _: &ValidationConfig,
21        _: &mut Vec<ValidationError>,
22    ) {
23    }
24}
25
26/// [`HalfEdge`] validation failed
27#[derive(Clone, Debug, thiserror::Error)]
28pub enum HalfEdgeValidationError {
29    /// [`HalfEdge`]'s vertices are coincident
30    #[error(
31        "Vertices of `HalfEdge` on curve are coincident\n\
32        - Position of back vertex: {back_position:?}\n\
33        - Position of front vertex: {front_position:?}\n\
34        - `HalfEdge`: {half_edge:#?}"
35    )]
36    VerticesAreCoincident {
37        /// The position of the back vertex
38        back_position: Point<1>,
39
40        /// The position of the front vertex
41        front_position: Point<1>,
42
43        /// The distance between the two vertices
44        distance: Scalar,
45
46        /// The half-edge
47        half_edge: HalfEdge,
48    },
49}
50
51impl HalfEdgeValidationError {
52    fn check_vertex_coincidence(
53        half_edge: &HalfEdge,
54        config: &ValidationConfig,
55        errors: &mut Vec<ValidationError>,
56    ) {
57        let [back_position, front_position] = half_edge.boundary();
58        let distance = (back_position - front_position).magnitude();
59
60        if distance < config.distinct_min_distance {
61            errors.push(
62                Self::VerticesAreCoincident {
63                    back_position,
64                    front_position,
65                    distance,
66                    half_edge: half_edge.clone(),
67                }
68                .into(),
69            );
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use fj_math::Point;
77
78    use crate::{
79        assert_contains_err,
80        objects::HalfEdge,
81        operations::BuildHalfEdge,
82        services::Services,
83        validate::{HalfEdgeValidationError, Validate, ValidationError},
84    };
85
86    #[test]
87    fn half_edge_vertices_are_coincident() -> anyhow::Result<()> {
88        let mut services = Services::new();
89
90        let valid =
91            HalfEdge::line_segment([[0., 0.], [1., 0.]], None, &mut services);
92        let invalid = {
93            let boundary = [Point::from([0.]); 2];
94
95            HalfEdge::new(
96                valid.curve(),
97                boundary,
98                valid.start_vertex().clone(),
99                valid.global_form().clone(),
100            )
101        };
102
103        valid.validate_and_return_first_error()?;
104        assert_contains_err!(
105            invalid,
106            ValidationError::HalfEdge(
107                HalfEdgeValidationError::VerticesAreCoincident { .. }
108            )
109        );
110
111        Ok(())
112    }
113}