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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use std::fmt;

use fj_math::{Point, Scalar};

use crate::objects::{Curve, Vertex};

pub fn validate_curve(
    curve: &Curve,
    max_distance: impl Into<Scalar>,
) -> Result<(), CoherenceIssues> {
    let max_distance = max_distance.into();

    let points_curve = [-2., -1., 0., 1., 2.].map(|point| Point::from([point]));

    for point_curve in points_curve {
        let point_surface = curve.path().point_from_path_coords(point_curve);
        let point_surface_as_global =
            curve.surface().point_from_surface_coords(point_surface);
        let point_global = curve
            .global_form()
            .path()
            .point_from_path_coords(point_curve);

        let distance = (point_surface_as_global - point_global).magnitude();

        if distance > max_distance {
            Err(CurveCoherenceMismatch {
                point_curve,
                point_surface,
                point_surface_as_global,
                point_global,
                curve: curve.clone(),
            })?
        }
    }

    Ok(())
}

pub fn validate_vertex(
    vertex: &Vertex,
    max_distance: impl Into<Scalar>,
) -> Result<(), CoherenceIssues> {
    let max_distance = max_distance.into();

    // Validate that the local and global forms of the vertex match. As a side
    // effect, this also happens to validate that the global form of the vertex
    // lies on the curve.

    let local = vertex.position();
    let local_as_surface = vertex.curve().path().point_from_path_coords(local);
    let local_as_global = vertex
        .curve()
        .surface()
        .point_from_surface_coords(local_as_surface);
    let global = vertex.global_form().position();
    let distance = (local_as_global - global).magnitude();

    if distance > max_distance {
        Err(VertexCoherenceMismatch {
            local,
            local_as_global,
            global,
        })?
    }

    Ok(())
}

/// Issues in coherence validation
#[allow(clippy::large_enum_variant)]
#[derive(Debug, thiserror::Error)]
pub enum CoherenceIssues {
    /// Mismatch between the surface and global forms of a curve
    #[error("Mismatch between surface and global forms of curve")]
    Curve(#[from] CurveCoherenceMismatch),

    /// Mismatch between the local and global coordinates of a vertex
    #[error("Mismatch between local and global coordinates of vertex")]
    Vertex(#[from] VertexCoherenceMismatch),
}

/// A mismatch between the surface and global forms of a curve
///
/// Used in [`CoherenceIssues`].
#[derive(Debug, thiserror::Error)]
pub struct CurveCoherenceMismatch {
    /// The curve coordinate for which a mismatch was found
    pub point_curve: Point<1>,

    /// The curve coordinate, converted to surface coordinates
    pub point_surface: Point<2>,

    /// The surface coordinates, converted to global coordinates
    pub point_surface_as_global: Point<3>,

    /// The curve coordinate, converted to global coordinates
    pub point_global: Point<3>,

    /// The incoherent curve
    pub curve: Curve,
}

impl fmt::Display for CurveCoherenceMismatch {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "local: {:?} (converted to surface: {:?}; to global: {:?}), global: {:?},",
            self.point_curve, self.point_surface, self.point_surface_as_global, self.point_global,
        )
    }
}

/// A mismatch between the local and global forms of a vertex
///
/// Used in [`CoherenceIssues`].
#[derive(Debug, Default, thiserror::Error)]
pub struct VertexCoherenceMismatch {
    /// The local form of the object
    pub local: Point<1>,

    /// The local form of the object, converted into the global form
    pub local_as_global: Point<3>,

    /// The global form of the object
    pub global: Point<3>,
}

impl fmt::Display for VertexCoherenceMismatch {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "local: {:?} (converted to global: {:?}), global: {:?},",
            self.local, self.local_as_global, self.global,
        )
    }
}