fj_core/algorithms/approx/
curve.rs1use 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 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 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#[derive(Clone)]
134pub struct CurveApprox {
135 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#[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}