fj_kernel/validate/
cycle.rs1use 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#[derive(Clone, Debug, thiserror::Error)]
24pub enum CycleValidationError {
25 #[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 end_of_first: Point<2>,
35
36 start_of_second: Point<2>,
38
39 distance: Scalar,
41
42 half_edges: Box<(HalfEdge, HalfEdge)>,
44 },
45 #[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 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}