1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use std::iter::repeat;

use crate::{
    objects::{Solid, Vertex},
    storage::Handle,
};
use fj_math::Point;

use super::{Validate, ValidationConfig, ValidationError};

impl Validate for Solid {
    fn validate_with_config(
        &self,
        config: &ValidationConfig,
        errors: &mut Vec<ValidationError>,
    ) {
        SolidValidationError::check_vertices(self, config, errors)
    }
}

/// [`Solid`] validation failed
#[derive(Clone, Debug, thiserror::Error)]
pub enum SolidValidationError {
    /// [`Solid`] contains vertices that are coincident, but not identical
    #[error(
        "Solid contains Vertices that are coincident but not identical\n
        Vertex 1: {:#?} {:#?}
        Vertex 2: {:#?} {:#?}
        ", .0[0].0, .0[0].1,.0[1].0,.0[1].1
    )]
    DistinctVerticesCoincide([(Handle<Vertex>, Point<3>); 2]),

    /// [`Solid`] contains vertices that are identical, but do not coincide
    #[error(
        "Solid contains Vertices that are identical but do not coincide\n
        Vertex 1: {:#?} {:#?}
        Vertex 2: {:#?} {:#?}
        ", .0[0].0, .0[0].1,.0[1].0,.0[1].1
    )]
    IdenticalVerticesNotCoincident([(Handle<Vertex>, Point<3>); 2]),
}

impl SolidValidationError {
    fn check_vertices(
        solid: &Solid,
        config: &ValidationConfig,
        errors: &mut Vec<ValidationError>,
    ) {
        let vertices: Vec<(Point<3>, Handle<Vertex>)> = solid
            .shells()
            .flat_map(|s| s.faces())
            .flat_map(|face| {
                face.all_cycles()
                    .flat_map(|cycle| cycle.half_edges().cloned())
                    .zip(repeat(face.surface().geometry()))
            })
            .map(|(h, s)| {
                (
                    s.point_from_surface_coords(h.start_position()),
                    h.start_vertex().clone(),
                )
            })
            .collect();

        // This is O(N^2) which isn't great, but we can't use a HashMap since we
        // need to deal with float inaccuracies. Maybe we could use some smarter
        // data-structure like an octree.
        for a in &vertices {
            for b in &vertices {
                match a.1.id() == b.1.id() {
                    true => {
                        if a.0.distance_to(&b.0) > config.identical_max_distance
                        {
                            errors.push(
                                Self::IdenticalVerticesNotCoincident([
                                    (a.1.clone(), a.0),
                                    (b.1.clone(), b.0),
                                ])
                                .into(),
                            )
                        }
                    }
                    false => {
                        if a.0.distance_to(&b.0) < config.distinct_min_distance
                        {
                            errors.push(
                                Self::DistinctVerticesCoincide([
                                    (a.1.clone(), a.0),
                                    (b.1.clone(), b.0),
                                ])
                                .into(),
                            )
                        }
                    }
                }
            }
        }
    }
}