fj_core/validate/
solid.rs

1use std::iter::repeat;
2
3use crate::{
4    geometry::Geometry,
5    objects::{Solid, Vertex},
6    storage::Handle,
7    validate_references,
8};
9use fj_math::Point;
10
11use super::{
12    references::{ReferenceCountError, ReferenceCounter},
13    Validate, ValidationConfig, ValidationError,
14};
15
16impl Validate for Solid {
17    fn validate(
18        &self,
19        config: &ValidationConfig,
20        errors: &mut Vec<ValidationError>,
21        geometry: &Geometry,
22    ) {
23        SolidValidationError::check_vertices(self, geometry, config, errors);
24        SolidValidationError::check_object_references(self, config, errors);
25    }
26}
27
28/// [`Solid`] validation failed
29#[derive(Clone, Debug, thiserror::Error)]
30pub enum SolidValidationError {
31    /// [`Solid`] contains vertices that are coincident, but not identical
32    #[error(
33        "Solid contains Vertices that are coincident but not identical\n
34        Vertex 1: {vertex_a:#?} ({position_a:?})
35        Vertex 2: {vertex_b:#?} ({position_b:?})"
36    )]
37    DistinctVerticesCoincide {
38        /// The first vertex
39        vertex_a: Handle<Vertex>,
40
41        /// The second vertex
42        vertex_b: Handle<Vertex>,
43
44        /// Position of first vertex
45        position_a: Point<3>,
46
47        /// Position of second vertex
48        position_b: Point<3>,
49    },
50
51    /// [`Solid`] contains vertices that are identical, but do not coincide
52    #[error(
53        "Solid contains Vertices that are identical but do not coincide\n
54        Vertex 1: {vertex_a:#?} ({position_a:?})
55        Vertex 2: {vertex_b:#?} ({position_b:?})"
56    )]
57    IdenticalVerticesNotCoincident {
58        /// The first vertex
59        vertex_a: Handle<Vertex>,
60
61        /// The second vertex
62        vertex_b: Handle<Vertex>,
63
64        /// Position of first vertex
65        position_a: Point<3>,
66
67        /// Position of second vertex
68        position_b: Point<3>,
69    },
70
71    /// Object within solid referenced by more than one other object
72    #[error("Object within solid referenced by more than one other Object")]
73    MultipleReferences(#[from] ReferenceCountError),
74}
75
76impl SolidValidationError {
77    fn check_vertices(
78        solid: &Solid,
79        geometry: &Geometry,
80        config: &ValidationConfig,
81        errors: &mut Vec<ValidationError>,
82    ) {
83        let vertices: Vec<(Point<3>, Handle<Vertex>)> = solid
84            .shells()
85            .iter()
86            .flat_map(|s| s.faces())
87            .flat_map(|face| {
88                face.region()
89                    .all_cycles()
90                    .flat_map(|cycle| cycle.half_edges().iter().cloned())
91                    .zip(repeat(geometry.of_surface(face.surface())))
92            })
93            .map(|(h, s)| {
94                (
95                    s.point_from_surface_coords(h.start_position()),
96                    h.start_vertex().clone(),
97                )
98            })
99            .collect();
100
101        // This is O(N^2) which isn't great, but we can't use a HashMap since we
102        // need to deal with float inaccuracies. Maybe we could use some smarter
103        // data-structure like an octree.
104        for (position_a, vertex_a) in &vertices {
105            for (position_b, vertex_b) in &vertices {
106                let vertices_are_identical = vertex_a.id() == vertex_b.id();
107                let vertices_are_not_identical = !vertices_are_identical;
108
109                let too_far_to_be_identical = position_a
110                    .distance_to(position_b)
111                    > config.identical_max_distance;
112                let too_close_to_be_distinct = position_a
113                    .distance_to(position_b)
114                    < config.distinct_min_distance;
115
116                if vertices_are_identical && too_far_to_be_identical {
117                    errors.push(
118                        Self::IdenticalVerticesNotCoincident {
119                            vertex_a: vertex_a.clone(),
120                            vertex_b: vertex_b.clone(),
121                            position_a: *position_a,
122                            position_b: *position_b,
123                        }
124                        .into(),
125                    )
126                }
127
128                if vertices_are_not_identical && too_close_to_be_distinct {
129                    errors.push(
130                        Self::DistinctVerticesCoincide {
131                            vertex_a: vertex_a.clone(),
132                            vertex_b: vertex_b.clone(),
133                            position_a: *position_a,
134                            position_b: *position_b,
135                        }
136                        .into(),
137                    )
138                }
139            }
140        }
141    }
142
143    fn check_object_references(
144        solid: &Solid,
145        _config: &ValidationConfig,
146        errors: &mut Vec<ValidationError>,
147    ) {
148        let mut referenced_regions = ReferenceCounter::new();
149        let mut referenced_faces = ReferenceCounter::new();
150        let mut referenced_edges = ReferenceCounter::new();
151        let mut referenced_cycles = ReferenceCounter::new();
152
153        solid.shells().iter().for_each(|s| {
154            s.faces().into_iter().for_each(|f| {
155                referenced_faces.add_reference(f.clone(), s.clone());
156                referenced_regions.add_reference(f.region().clone(), f.clone());
157                f.region().all_cycles().for_each(|c| {
158                    referenced_cycles
159                        .add_reference(c.clone(), f.region().clone());
160                    c.half_edges().into_iter().for_each(|e| {
161                        referenced_edges.add_reference(e.clone(), c.clone());
162                    })
163                })
164            })
165        });
166
167        validate_references!(
168            errors, SolidValidationError;
169            referenced_regions, Region;
170            referenced_faces, Face;
171            referenced_edges, HalfEdge;
172            referenced_cycles, Cycle;
173        );
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use crate::{
180        assert_contains_err,
181        geometry::GlobalPath,
182        objects::{Cycle, Face, HalfEdge, Region, Shell, Solid, Surface},
183        operations::{
184            build::{BuildFace, BuildHalfEdge, BuildSurface},
185            insert::Insert,
186        },
187        validate::{
188            references::ReferenceCountError, SolidValidationError, Validate,
189            ValidationError,
190        },
191        Core,
192    };
193
194    #[test]
195    fn should_find_face_multiple_references() -> anyhow::Result<()> {
196        let mut core = Core::new();
197
198        let shared_face = Face::new(
199            Surface::surface_from_uv(
200                GlobalPath::circle_from_radius(1.),
201                [0., 1., 1.],
202                &mut core,
203            ),
204            Region::new(
205                Cycle::new(vec![HalfEdge::circle([0., 0.], 1., &mut core)])
206                    .insert(&mut core),
207                vec![],
208            )
209            .insert(&mut core),
210        )
211        .insert(&mut core);
212
213        let invalid_solid = Solid::new(vec![
214            Shell::new(vec![shared_face.clone()]).insert(&mut core),
215            Shell::new(vec![
216                shared_face,
217                Face::triangle(
218                    [[0., 0., 0.], [1., 0., 0.], [1., 1., 0.]],
219                    &mut core,
220                )
221                .insert(&mut core)
222                .face,
223            ])
224            .insert(&mut core),
225        ])
226        .insert(&mut core);
227
228        assert_contains_err!(
229            core,
230            invalid_solid,
231            ValidationError::Solid(SolidValidationError::MultipleReferences(
232                ReferenceCountError::Face { references: _ }
233            ))
234        );
235
236        let valid_solid = Solid::new(vec![]).insert(&mut core);
237        valid_solid.validate_and_return_first_error(&core.layers.geometry)?;
238
239        // Ignore remaining validation errors.
240        let _ = core.layers.validation.take_errors();
241
242        Ok(())
243    }
244
245    #[test]
246    fn should_find_region_multiple_references() -> anyhow::Result<()> {
247        let mut core = Core::new();
248
249        let shared_region = Region::new(
250            Cycle::new(vec![HalfEdge::circle([0., 0.], 1., &mut core)])
251                .insert(&mut core),
252            vec![],
253        )
254        .insert(&mut core);
255
256        let invalid_solid = Solid::new(vec![Shell::new(vec![
257            Face::new(
258                Surface::surface_from_uv(
259                    GlobalPath::circle_from_radius(1.),
260                    [0., 1., 1.],
261                    &mut core,
262                ),
263                shared_region.clone(),
264            )
265            .insert(&mut core),
266            Face::new(
267                Surface::surface_from_uv(
268                    GlobalPath::circle_from_radius(1.),
269                    [0., 0., 1.],
270                    &mut core,
271                ),
272                shared_region.clone(),
273            )
274            .insert(&mut core),
275        ])
276        .insert(&mut core)])
277        .insert(&mut core);
278
279        assert_contains_err!(
280            core,
281            invalid_solid,
282            ValidationError::Solid(SolidValidationError::MultipleReferences(
283                ReferenceCountError::Region { references: _ }
284            ))
285        );
286
287        let valid_solid = Solid::new(vec![]).insert(&mut core);
288        valid_solid.validate_and_return_first_error(&core.layers.geometry)?;
289
290        // Ignore remaining validation errors.
291        let _ = core.layers.validation.take_errors();
292
293        Ok(())
294    }
295
296    #[test]
297    fn should_find_cycle_multiple_references() -> anyhow::Result<()> {
298        let mut core = Core::new();
299
300        let shared_cycle =
301            Cycle::new(vec![HalfEdge::circle([0., 0.], 1., &mut core)])
302                .insert(&mut core);
303
304        let invalid_solid = Solid::new(vec![Shell::new(vec![
305            Face::new(
306                Surface::surface_from_uv(
307                    GlobalPath::circle_from_radius(1.),
308                    [0., 1., 1.],
309                    &mut core,
310                ),
311                Region::new(shared_cycle.clone(), vec![]).insert(&mut core),
312            )
313            .insert(&mut core),
314            Face::new(
315                Surface::surface_from_uv(
316                    GlobalPath::circle_from_radius(1.),
317                    [0., 0., 1.],
318                    &mut core,
319                ),
320                Region::new(shared_cycle, vec![]).insert(&mut core),
321            )
322            .insert(&mut core),
323        ])
324        .insert(&mut core)])
325        .insert(&mut core);
326
327        assert_contains_err!(
328            core,
329            invalid_solid,
330            ValidationError::Solid(SolidValidationError::MultipleReferences(
331                ReferenceCountError::Cycle { references: _ }
332            ))
333        );
334
335        let valid_solid = Solid::new(vec![]).insert(&mut core);
336        valid_solid.validate_and_return_first_error(&core.layers.geometry)?;
337
338        // Ignore remaining validation errors.
339        let _ = core.layers.validation.take_errors();
340
341        Ok(())
342    }
343
344    #[test]
345    fn should_find_half_edge_multiple_references() -> anyhow::Result<()> {
346        let mut core = Core::new();
347
348        let shared_edge = HalfEdge::circle([0., 0.], 1., &mut core);
349
350        let invalid_solid = Solid::new(vec![Shell::new(vec![Face::new(
351            Surface::surface_from_uv(
352                GlobalPath::circle_from_radius(1.),
353                [0., 0., 1.],
354                &mut core,
355            ),
356            Region::new(
357                Cycle::new(vec![shared_edge.clone()]).insert(&mut core),
358                vec![Cycle::new(vec![shared_edge.clone()]).insert(&mut core)],
359            )
360            .insert(&mut core),
361        )
362        .insert(&mut core)])
363        .insert(&mut core)])
364        .insert(&mut core);
365
366        assert_contains_err!(
367            core,
368            invalid_solid,
369            ValidationError::Solid(SolidValidationError::MultipleReferences(
370                ReferenceCountError::HalfEdge { references: _ }
371            ))
372        );
373
374        let valid_solid = Solid::new(vec![]).insert(&mut core);
375        valid_solid.validate_and_return_first_error(&core.layers.geometry)?;
376
377        // Ignore remaining validation errors.
378        let _ = core.layers.validation.take_errors();
379
380        Ok(())
381    }
382}