fj_kernel/algorithms/approx/
edge.rs1use std::collections::BTreeMap;
9
10use fj_math::Point;
11
12use crate::{
13 geometry::curve::{Curve, GlobalPath},
14 objects::{GlobalEdge, HalfEdge, Surface, Vertex},
15 storage::{Handle, ObjectId},
16};
17
18use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance};
19
20impl Approx for (&HalfEdge, &Surface) {
21 type Approximation = HalfEdgeApprox;
22 type Cache = EdgeCache;
23
24 fn approx_with_cache(
25 self,
26 tolerance: impl Into<Tolerance>,
27 cache: &mut Self::Cache,
28 ) -> Self::Approximation {
29 let (half_edge, surface) = self;
30
31 let boundary = half_edge.boundary();
32 let range = RangeOnPath { boundary };
33
34 let position_surface = half_edge.start_position();
35 let position_global = match cache.get_position(half_edge.start_vertex())
36 {
37 Some(position) => position,
38 None => {
39 let position_global = surface
40 .geometry()
41 .point_from_surface_coords(position_surface);
42 cache.insert_position(half_edge.start_vertex(), position_global)
43 }
44 };
45
46 let first = ApproxPoint::new(position_surface, position_global);
47
48 let points = {
49 let approx =
50 match cache.get_edge(half_edge.global_form().clone(), range) {
51 Some(approx) => approx,
52 None => {
53 let approx = approx_edge(
54 &half_edge.curve(),
55 surface,
56 range,
57 tolerance,
58 );
59 cache.insert_edge(
60 half_edge.global_form().clone(),
61 range,
62 approx,
63 )
64 }
65 };
66
67 approx
68 .points
69 .into_iter()
70 .map(|point| {
71 let point_surface = half_edge
72 .curve()
73 .point_from_path_coords(point.local_form);
74
75 ApproxPoint::new(point_surface, point.global_form)
76 })
77 .collect()
78 };
79
80 HalfEdgeApprox { first, points }
81 }
82}
83
84#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
86pub struct HalfEdgeApprox {
87 pub first: ApproxPoint<2>,
89
90 pub points: Vec<ApproxPoint<2>>,
92}
93
94impl HalfEdgeApprox {
95 pub fn points(&self) -> Vec<ApproxPoint<2>> {
97 let mut points = Vec::new();
98
99 points.push(self.first.clone());
100 points.extend(self.points.iter().cloned());
101
102 points
103 }
104}
105
106fn approx_edge(
107 curve: &Curve,
108 surface: &Surface,
109 range: RangeOnPath,
110 tolerance: impl Into<Tolerance>,
111) -> GlobalEdgeApprox {
112 let points = match (curve, surface.geometry().u) {
119 (Curve::Circle(_), GlobalPath::Circle(_)) => {
120 todo!(
121 "Approximating a circle on a curved surface not supported yet."
122 )
123 }
124 (Curve::Circle(_), GlobalPath::Line(_)) => {
125 (curve, range)
126 .approx_with_cache(tolerance, &mut ())
127 .into_iter()
128 .map(|(point_curve, point_surface)| {
129 let point_global = surface
145 .geometry()
146 .point_from_surface_coords(point_surface);
147 (point_curve, point_global)
148 })
149 .collect()
150 }
151 (Curve::Line(line), _) => {
152 let range_u =
153 RangeOnPath::from(range.boundary.map(|point_curve| {
154 [curve.point_from_path_coords(point_curve).u]
155 }));
156
157 let approx_u = (surface.geometry().u, range_u)
158 .approx_with_cache(tolerance, &mut ());
159
160 let mut points = Vec::new();
161 for (u, _) in approx_u {
162 let t = (u.t - line.origin().u) / line.direction().u;
163 let point_surface = curve.point_from_path_coords([t]);
164 let point_global =
165 surface.geometry().point_from_surface_coords(point_surface);
166 points.push((u, point_global));
167 }
168
169 points
170 }
171 };
172
173 let points = points
174 .into_iter()
175 .map(|(point_curve, point_global)| {
176 ApproxPoint::new(point_curve, point_global)
177 })
178 .collect();
179 GlobalEdgeApprox { points }
180}
181
182#[derive(Default)]
184pub struct EdgeCache {
185 edge_approx: BTreeMap<(ObjectId, RangeOnPath), GlobalEdgeApprox>,
186 vertex_approx: BTreeMap<ObjectId, Point<3>>,
187}
188
189impl EdgeCache {
190 pub fn new() -> Self {
192 Self::default()
193 }
194
195 pub fn get_edge(
197 &self,
198 handle: Handle<GlobalEdge>,
199 range: RangeOnPath,
200 ) -> Option<GlobalEdgeApprox> {
201 if let Some(approx) = self.edge_approx.get(&(handle.id(), range)) {
202 return Some(approx.clone());
203 }
204 if let Some(approx) =
205 self.edge_approx.get(&(handle.id(), range.reverse()))
206 {
207 return Some(approx.clone().reverse());
210 }
211
212 None
213 }
214
215 pub fn insert_edge(
217 &mut self,
218 handle: Handle<GlobalEdge>,
219 range: RangeOnPath,
220 approx: GlobalEdgeApprox,
221 ) -> GlobalEdgeApprox {
222 self.edge_approx
223 .insert((handle.id(), range), approx.clone())
224 .unwrap_or(approx)
225 }
226
227 fn get_position(&self, handle: &Handle<Vertex>) -> Option<Point<3>> {
228 self.vertex_approx.get(&handle.id()).cloned()
229 }
230
231 fn insert_position(
232 &mut self,
233 handle: &Handle<Vertex>,
234 position: Point<3>,
235 ) -> Point<3> {
236 self.vertex_approx
237 .insert(handle.id(), position)
238 .unwrap_or(position)
239 }
240}
241
242#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
244pub struct GlobalEdgeApprox {
245 pub points: Vec<ApproxPoint<1>>,
247}
248
249impl GlobalEdgeApprox {
250 pub fn reverse(mut self) -> Self {
252 self.points.reverse();
253 self
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use std::{f64::consts::TAU, ops::Deref};
260
261 use pretty_assertions::assert_eq;
262
263 use crate::{
264 algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
265 geometry::{curve::GlobalPath, surface::SurfaceGeometry},
266 objects::{HalfEdge, Surface},
267 operations::{BuildHalfEdge, Insert},
268 services::Services,
269 };
270
271 #[test]
272 fn approx_line_on_flat_surface() {
273 let mut services = Services::new();
274
275 let surface = services.objects.surfaces.xz_plane();
276 let half_edge =
277 HalfEdge::line_segment([[1., 1.], [2., 1.]], None, &mut services);
278
279 let tolerance = 1.;
280 let approx = (&half_edge, surface.deref()).approx(tolerance);
281
282 assert_eq!(approx.points, Vec::new());
283 }
284
285 #[test]
286 fn approx_line_on_curved_surface_but_not_along_curve() {
287 let mut services = Services::new();
288
289 let surface = Surface::new(SurfaceGeometry {
290 u: GlobalPath::circle_from_radius(1.),
291 v: [0., 0., 1.].into(),
292 })
293 .insert(&mut services);
294 let half_edge =
295 HalfEdge::line_segment([[1., 1.], [2., 1.]], None, &mut services);
296
297 let tolerance = 1.;
298 let approx = (&half_edge, surface.deref()).approx(tolerance);
299
300 assert_eq!(approx.points, Vec::new());
301 }
302
303 #[test]
304 fn approx_line_on_curved_surface_along_curve() {
305 let mut services = Services::new();
306
307 let path = GlobalPath::circle_from_radius(1.);
308 let range = RangeOnPath::from([[0.], [TAU]]);
309
310 let surface = Surface::new(SurfaceGeometry {
311 u: path,
312 v: [0., 0., 1.].into(),
313 })
314 .insert(&mut services);
315 let half_edge = HalfEdge::line_segment(
316 [[0., 1.], [TAU, 1.]],
317 Some(range.boundary),
318 &mut services,
319 );
320
321 let tolerance = 1.;
322 let approx = (&half_edge, surface.deref()).approx(tolerance);
323
324 let expected_approx = (path, range)
325 .approx(tolerance)
326 .into_iter()
327 .map(|(point_local, _)| {
328 let point_surface =
329 half_edge.curve().point_from_path_coords(point_local);
330 let point_global =
331 surface.geometry().point_from_surface_coords(point_surface);
332 ApproxPoint::new(point_surface, point_global)
333 })
334 .collect::<Vec<_>>();
335 assert_eq!(approx.points, expected_approx);
336 }
337
338 #[test]
339 fn approx_circle_on_flat_surface() {
340 let mut services = Services::new();
341
342 let surface = services.objects.surfaces.xz_plane();
343 let half_edge = HalfEdge::circle(1., &mut services);
344
345 let tolerance = 1.;
346 let approx = (&half_edge, surface.deref()).approx(tolerance);
347
348 let expected_approx =
349 (&half_edge.curve(), RangeOnPath::from([[0.], [TAU]]))
350 .approx(tolerance)
351 .into_iter()
352 .map(|(_, point_surface)| {
353 let point_global = surface
354 .geometry()
355 .point_from_surface_coords(point_surface);
356 ApproxPoint::new(point_surface, point_global)
357 })
358 .collect::<Vec<_>>();
359 assert_eq!(approx.points, expected_approx);
360 }
361}