fj_kernel/validate/
cycle.rs

1use crate::objects::Cycle;
2use crate::objects::HalfEdge;
3use fj_math::Point;
4use fj_math::Scalar;
5use itertools::Itertools;
6
7use super::{Validate, ValidationConfig, ValidationError};
8
9impl Validate for Cycle {
10    fn validate_with_config(
11        &self,
12        config: &ValidationConfig,
13        errors: &mut Vec<ValidationError>,
14    ) {
15        CycleValidationError::check_half_edges_disconnected(
16            self, config, errors,
17        );
18        CycleValidationError::check_enough_half_edges(self, config, errors);
19    }
20}
21
22/// [`Cycle`] validation failed
23#[derive(Clone, Debug, thiserror::Error)]
24pub enum CycleValidationError {
25    /// [`Cycle`]'s half-edges are not connected
26    #[error(
27        "Adjacent `HalfEdge`s are distinct\n\
28        - End position of first `HalfEdge`: {end_of_first:?}\n\
29        - Start position of second `HalfEdge`: {start_of_second:?}\n\
30        - `HalfEdge`s: {half_edges:#?}"
31    )]
32    HalfEdgesDisconnected {
33        /// The end position of the first [`HalfEdge`]
34        end_of_first: Point<2>,
35
36        /// The start position of the second [`HalfEdge`]
37        start_of_second: Point<2>,
38
39        /// The distance between the two vertices
40        distance: Scalar,
41
42        /// The half-edge
43        half_edges: Box<(HalfEdge, HalfEdge)>,
44    },
45    /// [`Cycle`]'s should have at least one `HalfEdge`
46    #[error("Expected at least one `HalfEdge`\n")]
47    NotEnoughHalfEdges,
48}
49
50impl CycleValidationError {
51    fn check_enough_half_edges(
52        cycle: &Cycle,
53        _config: &ValidationConfig,
54        errors: &mut Vec<ValidationError>,
55    ) {
56        // If there are no half edges
57        if cycle.half_edges().next().is_none() {
58            errors.push(Self::NotEnoughHalfEdges.into());
59        }
60    }
61
62    fn check_half_edges_disconnected(
63        cycle: &Cycle,
64        config: &ValidationConfig,
65        errors: &mut Vec<ValidationError>,
66    ) {
67        for (first, second) in cycle.half_edges().circular_tuple_windows() {
68            let end_of_first = {
69                let [_, end] = first.boundary();
70                first.curve().point_from_path_coords(end)
71            };
72            let start_of_second = second.start_position();
73
74            let distance = (end_of_first - start_of_second).magnitude();
75
76            if distance > config.identical_max_distance {
77                errors.push(
78                    Self::HalfEdgesDisconnected {
79                        end_of_first,
80                        start_of_second,
81                        distance,
82                        half_edges: Box::new((
83                            first.clone_object(),
84                            second.clone_object(),
85                        )),
86                    }
87                    .into(),
88                );
89            }
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96
97    use crate::{
98        assert_contains_err,
99        objects::{Cycle, HalfEdge},
100        operations::{BuildCycle, BuildHalfEdge, Insert, UpdateCycle},
101        services::Services,
102        validate::{cycle::CycleValidationError, Validate, ValidationError},
103    };
104
105    #[test]
106    fn half_edges_connected() -> anyhow::Result<()> {
107        let mut services = Services::new();
108
109        let valid =
110            Cycle::polygon([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]], &mut services);
111
112        valid.validate_and_return_first_error()?;
113
114        let disconnected = {
115            let half_edges = [
116                HalfEdge::line_segment(
117                    [[0., 0.], [1., 0.]],
118                    None,
119                    &mut services,
120                ),
121                HalfEdge::line_segment(
122                    [[0., 0.], [1., 0.]],
123                    None,
124                    &mut services,
125                ),
126            ];
127            let half_edges =
128                half_edges.map(|half_edge| half_edge.insert(&mut services));
129
130            Cycle::empty().add_half_edges(half_edges)
131        };
132
133        assert_contains_err!(
134            disconnected,
135            ValidationError::Cycle(
136                CycleValidationError::HalfEdgesDisconnected { .. }
137            )
138        );
139
140        let empty = Cycle::new([]);
141        assert_contains_err!(
142            empty,
143            ValidationError::Cycle(CycleValidationError::NotEnoughHalfEdges)
144        );
145        Ok(())
146    }
147}