ifc_lite_geometry/analytic/
frame.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Endpoints and traversal tangents shared by analytic consumers.
6
7use super::AnalyticCurveSegment;
8
9/// A world-space point kept as an origin and a local offset. Comparing nearby
10/// points this way retains small arc offsets at large georeferenced origins.
11#[derive(Debug, Clone, Copy, PartialEq)]
12#[non_exhaustive]
13pub struct AnalyticPoint {
14    pub origin: [f64; 3],
15    pub offset: [f64; 3],
16}
17
18impl AnalyticPoint {
19    /// Distance in world metres, subtracting origins before local offsets.
20    pub fn distance_to(self, other: Self) -> Option<f64> {
21        let delta = std::array::from_fn::<_, 3, _>(|axis| {
22            (self.origin[axis] - other.origin[axis]) + (self.offset[axis] - other.offset[axis])
23        });
24        let length = delta[0].hypot(delta[1]).hypot(delta[2]);
25        length.is_finite().then_some(length)
26    }
27}
28
29/// Endpoints and unit tangents in directrix traversal direction. A zero-length
30/// line or zero-sweep arc has no defined tangent.
31#[derive(Debug, Clone, Copy, PartialEq)]
32#[non_exhaustive]
33pub struct AnalyticSegmentFrame {
34    pub start: AnalyticPoint,
35    pub end: AnalyticPoint,
36    pub start_tangent: Option<[f64; 3]>,
37    pub end_tangent: Option<[f64; 3]>,
38}
39
40impl AnalyticCurveSegment {
41    /// Exact unsigned length in the segment's current coordinate units.
42    /// Invalid or overflowing source geometry has no measurable length.
43    pub fn length(&self) -> Option<f64> {
44        let value = match self {
45            Self::Line { start, end } => {
46                let delta: [f64; 3] = std::array::from_fn(|axis| end[axis] - start[axis]);
47                delta[0].hypot(delta[1]).hypot(delta[2])
48            }
49            Self::Arc { radius, sweep_angle, .. } if *radius > 0.0 => radius * sweep_angle.abs(),
50            Self::Arc { .. } => return None,
51        };
52        value.is_finite().then_some(value)
53    }
54
55    /// Evaluate the source curve in its current coordinate space. Returns
56    /// `None` for non-finite or invalid curve frames.
57    pub fn endpoint_frame(&self) -> Option<AnalyticSegmentFrame> {
58        match self {
59            Self::Line { start, end } => {
60                let delta = std::array::from_fn::<_, 3, _>(|axis| end[axis] - start[axis]);
61                let length = delta[0].hypot(delta[1]).hypot(delta[2]);
62                if !length.is_finite() || !start.iter().chain(end).all(|value| value.is_finite()) {
63                    return None;
64                }
65                let tangent = (length > 0.0).then(|| delta.map(|value| value / length));
66                Some(AnalyticSegmentFrame {
67                    start: AnalyticPoint { origin: *start, offset: [0.0; 3] },
68                    end: AnalyticPoint { origin: *end, offset: [0.0; 3] },
69                    start_tangent: tangent,
70                    end_tangent: tangent,
71                })
72            }
73            Self::Arc { center, normal, x_axis, radius, start_angle, sweep_angle } => {
74                if !center.iter().chain(normal).chain(x_axis).all(|v| v.is_finite())
75                    || !radius.is_finite() || *radius <= 0.0
76                    || !start_angle.is_finite() || !sweep_angle.is_finite()
77                {
78                    return None;
79                }
80                let y_axis = [
81                    normal[1] * x_axis[2] - normal[2] * x_axis[1],
82                    normal[2] * x_axis[0] - normal[0] * x_axis[2],
83                    normal[0] * x_axis[1] - normal[1] * x_axis[0],
84                ];
85                let end_angle = start_angle + sweep_angle;
86                if !end_angle.is_finite() { return None; }
87                let at = |angle: f64| {
88                    let (sin, cos) = angle.sin_cos();
89                    let offset = std::array::from_fn::<_, 3, _>(|axis| {
90                        radius * (cos * x_axis[axis] + sin * y_axis[axis])
91                    });
92                    let tangent = std::array::from_fn::<_, 3, _>(|axis| {
93                        sweep_angle.signum() * (-sin * x_axis[axis] + cos * y_axis[axis])
94                    });
95                    (AnalyticPoint { origin: *center, offset }, tangent)
96                };
97                let (start, start_tangent) = at(*start_angle);
98                let (end, end_tangent) = at(end_angle);
99                if !start.offset.iter().chain(end.offset.iter())
100                    .chain(start_tangent.iter()).chain(end_tangent.iter())
101                    .all(|v| v.is_finite())
102                { return None; }
103                let tangent = |vector: [f64; 3]| {
104                    let length = vector[0].hypot(vector[1]).hypot(vector[2]);
105                    (length > 0.0 && length.is_finite()).then(|| vector.map(|v| v / length))
106                };
107                Some(AnalyticSegmentFrame {
108                    start, end,
109                    start_tangent: (*sweep_angle != 0.0).then(|| tangent(start_tangent)).flatten(),
110                    end_tangent: (*sweep_angle != 0.0).then(|| tangent(end_tangent)).flatten(),
111                })
112            }
113        }
114    }
115}