fj_core/algorithms/approx/
curve.rs

1//! Curve approximation
2
3use std::collections::BTreeMap;
4
5use fj_math::Point;
6
7use crate::{
8    geometry::{CurveBoundary, GlobalPath, SurfaceGeometry, SurfacePath},
9    objects::Curve,
10    storage::{Handle, HandleWrapper},
11    Core,
12};
13
14use super::{Approx, ApproxPoint, Tolerance};
15
16impl Approx
17    for (
18        &Handle<Curve>,
19        SurfacePath,
20        &SurfaceGeometry,
21        CurveBoundary<Point<1>>,
22    )
23{
24    type Approximation = CurveApprox;
25    type Cache = CurveApproxCache;
26
27    fn approx_with_cache(
28        self,
29        tolerance: impl Into<Tolerance>,
30        cache: &mut Self::Cache,
31        core: &mut Core,
32    ) -> Self::Approximation {
33        let (curve, surface_path, surface, boundary) = self;
34
35        match cache.get(curve, boundary) {
36            Some(approx) => approx,
37            None => {
38                let approx = approx_curve(
39                    &surface_path,
40                    surface,
41                    boundary,
42                    tolerance,
43                    core,
44                );
45
46                cache.insert(curve.clone(), boundary, approx)
47            }
48        }
49    }
50}
51
52fn approx_curve(
53    path: &SurfacePath,
54    surface: &SurfaceGeometry,
55    boundary: CurveBoundary<Point<1>>,
56    tolerance: impl Into<Tolerance>,
57    core: &mut Core,
58) -> CurveApprox {
59    // There are different cases of varying complexity. Circles are the hard
60    // part here, as they need to be approximated, while lines don't need to be.
61    //
62    // This will probably all be unified eventually, as `SurfacePath` and
63    // `GlobalPath` grow APIs that are better suited to implementing this code
64    // in a more abstract way.
65    let points = match (path, surface.u) {
66        (SurfacePath::Circle(_), GlobalPath::Circle(_)) => {
67            todo!(
68                "Approximating a circle on a curved surface not supported yet."
69            )
70        }
71        (SurfacePath::Circle(_), GlobalPath::Line(_)) => {
72            (path, boundary)
73                .approx_with_cache(tolerance, &mut (), core)
74                .into_iter()
75                .map(|(point_curve, point_surface)| {
76                    // We're throwing away `point_surface` here, which is a
77                    // bit weird, as we're recomputing it later (outside of
78                    // this function).
79                    //
80                    // It should be fine though:
81                    //
82                    // 1. We're throwing this version away, so there's no
83                    //    danger of inconsistency between this and the later
84                    //    version.
85                    // 2. This version should have been computed using the
86                    //    same path and parameters and the later version
87                    //    will be, so they should be the same anyway.
88                    // 3. Not all other cases handled in this function have
89                    //    a surface point available, so it needs to be
90                    //    computed later anyway, in the general case.
91
92                    let point_global =
93                        surface.point_from_surface_coords(point_surface);
94                    (point_curve, point_global)
95                })
96                .collect()
97        }
98        (SurfacePath::Line(line), _) => {
99            let range_u =
100                CurveBoundary::from(boundary.inner.map(|point_curve| {
101                    [path.point_from_path_coords(point_curve).u]
102                }));
103
104            let approx_u = (surface.u, range_u).approx_with_cache(
105                tolerance,
106                &mut (),
107                core,
108            );
109
110            let mut points = Vec::new();
111            for (u, _) in approx_u {
112                let t = (u.t - line.origin().u) / line.direction().u;
113                let point_surface = path.point_from_path_coords([t]);
114                let point_global =
115                    surface.point_from_surface_coords(point_surface);
116                points.push((u, point_global));
117            }
118
119            points
120        }
121    };
122
123    let points = points
124        .into_iter()
125        .map(|(point_curve, point_global)| {
126            ApproxPoint::new(point_curve, point_global)
127        })
128        .collect();
129    CurveApprox { points }
130}
131
132/// Approximation of [`Curve`], within a specific boundary
133#[derive(Clone)]
134pub struct CurveApprox {
135    /// The points that approximate the curve within the boundary
136    pub points: Vec<ApproxPoint<1>>,
137}
138
139impl CurveApprox {
140    fn reverse(mut self) -> Self {
141        self.points.reverse();
142        self
143    }
144}
145
146/// Cache for curve approximations
147#[derive(Default)]
148pub struct CurveApproxCache {
149    inner:
150        BTreeMap<(HandleWrapper<Curve>, CurveBoundary<Point<1>>), CurveApprox>,
151}
152
153impl CurveApproxCache {
154    fn get(
155        &self,
156        handle: &Handle<Curve>,
157        boundary: CurveBoundary<Point<1>>,
158    ) -> Option<CurveApprox> {
159        let handle = HandleWrapper::from(handle.clone());
160
161        if let Some(approx) = self.inner.get(&(handle.clone(), boundary)) {
162            return Some(approx.clone());
163        }
164        if let Some(approx) = self.inner.get(&(handle, boundary.reverse())) {
165            return Some(approx.clone().reverse());
166        }
167
168        None
169    }
170
171    fn insert(
172        &mut self,
173        handle: Handle<Curve>,
174        boundary: CurveBoundary<Point<1>>,
175        approx: CurveApprox,
176    ) -> CurveApprox {
177        let handle = HandleWrapper::from(handle);
178        self.inner
179            .insert((handle, boundary), approx.clone())
180            .unwrap_or(approx)
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use std::f64::consts::TAU;
187
188    use pretty_assertions::assert_eq;
189
190    use crate::{
191        algorithms::approx::{Approx, ApproxPoint},
192        geometry::{CurveBoundary, GlobalPath, SurfaceGeometry, SurfacePath},
193        objects::Curve,
194        operations::insert::Insert,
195        Core,
196    };
197
198    #[test]
199    fn approx_line_on_flat_surface() {
200        let mut core = Core::new();
201
202        let curve = Curve::new().insert(&mut core);
203        let (surface_path, boundary) =
204            SurfacePath::line_from_points([[1., 1.], [2., 1.]]);
205        let boundary = CurveBoundary::from(boundary);
206        let surface = core.layers.geometry.xz_plane();
207
208        let tolerance = 1.;
209        let approx = (&curve, surface_path, &surface, boundary)
210            .approx(tolerance, &mut core);
211
212        assert_eq!(approx.points, vec![]);
213    }
214
215    #[test]
216    fn approx_line_on_curved_surface_but_not_along_curve() {
217        let mut core = Core::new();
218
219        let curve = Curve::new().insert(&mut core);
220        let (surface_path, boundary) =
221            SurfacePath::line_from_points([[1., 1.], [2., 1.]]);
222        let boundary = CurveBoundary::from(boundary);
223        let surface = SurfaceGeometry {
224            u: GlobalPath::circle_from_radius(1.),
225            v: [0., 0., 1.].into(),
226        };
227
228        let tolerance = 1.;
229        let approx = (&curve, surface_path, &surface, boundary)
230            .approx(tolerance, &mut core);
231
232        assert_eq!(approx.points, vec![]);
233    }
234
235    #[test]
236    fn approx_line_on_curved_surface_along_curve() {
237        let mut core = Core::new();
238
239        let global_path = GlobalPath::circle_from_radius(1.);
240        let curve = Curve::new().insert(&mut core);
241        let surface_path = SurfacePath::line_from_points_with_coords([
242            ([0.], [0., 1.]),
243            ([TAU], [TAU, 1.]),
244        ]);
245        let boundary = CurveBoundary::from([[0.], [TAU]]);
246        let surface = SurfaceGeometry {
247            u: global_path,
248            v: [0., 0., 1.].into(),
249        };
250
251        let tolerance = 1.;
252        let approx = (&curve, surface_path, &surface, boundary)
253            .approx(tolerance, &mut core);
254
255        let expected_approx = (global_path, boundary)
256            .approx(tolerance, &mut core)
257            .into_iter()
258            .map(|(point_local, _)| {
259                let point_surface =
260                    surface_path.point_from_path_coords(point_local);
261                let point_global =
262                    surface.point_from_surface_coords(point_surface);
263                ApproxPoint::new(point_local, point_global)
264            })
265            .collect::<Vec<_>>();
266        assert_eq!(approx.points, expected_approx);
267    }
268
269    #[test]
270    fn approx_circle_on_flat_surface() {
271        let mut core = Core::new();
272
273        let curve = Curve::new().insert(&mut core);
274        let surface_path =
275            SurfacePath::circle_from_center_and_radius([0., 0.], 1.);
276        let boundary = CurveBoundary::from([[0.], [TAU]]);
277        let surface = core.layers.geometry.xz_plane();
278
279        let tolerance = 1.;
280        let approx = (&curve, surface_path, &surface, boundary)
281            .approx(tolerance, &mut core);
282
283        let expected_approx = (&surface_path, boundary)
284            .approx(tolerance, &mut core)
285            .into_iter()
286            .map(|(point_local, _)| {
287                let point_surface =
288                    surface_path.point_from_path_coords(point_local);
289                let point_global =
290                    surface.point_from_surface_coords(point_surface);
291                ApproxPoint::new(point_local, point_global)
292            })
293            .collect::<Vec<_>>();
294        assert_eq!(approx.points, expected_approx);
295    }
296}