1use std::{cmp::Ordering, collections::BinaryHeap};
44
45use geop_core_math::{
46 geop_error::{GeopError, GeopResult, WithContext},
47 scalars::Scalar,
48 vector::Vector3,
49};
50
51use crate::{
52 contains::surface::surface_extents,
53 nurb_curve::{NurbCurve3D, dehomogenize},
54 nurb_surface::NurbSurface3D,
55};
56
57pub trait Piece: Sized {
59 type At: Clone;
61
62 fn hull(&self) -> GeopResult<Vec<Vector3>>;
64
65 fn point(&self) -> GeopResult<Option<(Self::At, Vector3)>>;
68
69 fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>>;
73}
74
75#[derive(Clone, Debug)]
77pub struct Nearest<A, B> {
78 pub a: (A, Vector3),
80 pub b: (B, Vector3),
82 pub distance: Scalar,
85 pub candidates: Vec<(A, B)>,
88}
89
90struct Pair<PA: Piece, PB: Piece> {
92 a: PA,
93 b: PB,
94 hull_a: Vec<Vector3>,
95 hull_b: Vec<Vector3>,
96 lower: Scalar,
98}
99
100impl<PA: Piece, PB: Piece> Pair<PA, PB> {
101 fn new(a: PA, b: PB) -> GeopResult<Self> {
102 let (hull_a, hull_b) = (a.hull()?, b.hull()?);
103 let lower = separation(&hull_a, &hull_b)?;
104 Ok(Self {
105 a,
106 b,
107 hull_a,
108 hull_b,
109 lower,
110 })
111 }
112
113 fn key(&self) -> f64 {
116 self.lower.lower().to_f64()
117 }
118}
119
120impl<PA: Piece, PB: Piece> PartialEq for Pair<PA, PB> {
121 fn eq(&self, other: &Self) -> bool {
122 self.key() == other.key()
123 }
124}
125impl<PA: Piece, PB: Piece> Eq for Pair<PA, PB> {}
126impl<PA: Piece, PB: Piece> PartialOrd for Pair<PA, PB> {
127 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
128 Some(self.cmp(other))
129 }
130}
131impl<PA: Piece, PB: Piece> Ord for Pair<PA, PB> {
132 fn cmp(&self, other: &Self) -> Ordering {
134 other.key().total_cmp(&self.key())
135 }
136}
137
138fn bounds(points: &[Vector3]) -> [Scalar; 3] {
140 std::array::from_fn(|k| {
141 points
142 .iter()
143 .map(|p| p[k])
144 .reduce(|a, b| a.union(b))
145 .unwrap_or(Scalar::ENTIRE)
146 })
147}
148
149fn separation(a: &[Vector3], b: &[Vector3]) -> GeopResult<Scalar> {
154 let (box_a, box_b) = (bounds(a), bounds(b));
155 let gap = |x: Scalar, y: Scalar| {
158 let ahead = y.lower().sub(x.upper());
159 let behind = x.lower().sub(y.upper());
160 if ahead.definitely_greater(Scalar::ZERO) {
161 ahead
162 } else if behind.definitely_greater(Scalar::ZERO) {
163 behind
164 } else {
165 Scalar::ZERO
166 }
167 };
168 let boxes = (0..3)
169 .map(|k| gap(box_a[k], box_b[k]))
170 .fold(Scalar::ZERO, |sum, g| sum.add(g.mul(g)))
171 .sqrt()?;
172
173 let centre = |b: &[Scalar; 3]| Vector3::from_array(b.map(|x| x.midpoint()));
174 let d = centre(&box_b).sub(¢re(&box_a)).sharpen();
175 let along = |points: &[Vector3]| -> Scalar {
176 points
177 .iter()
178 .map(|p| p.prod_dot(&d))
179 .reduce(|x, y| x.union(y))
180 .unwrap_or(Scalar::ENTIRE)
181 };
182 let apart = along(b).lower().sub(along(a).upper());
183 let length = d.norm();
184 let directed =
185 if apart.definitely_greater(Scalar::ZERO) && length.definitely_greater(Scalar::ZERO) {
186 apart.div(length.upper())?.lower()
187 } else {
188 Scalar::ZERO
189 };
190 Ok(if directed.to_f64() > boxes.lower().to_f64() {
191 directed
192 } else {
193 boxes.lower()
194 })
195}
196
197fn resolved(points: &[Vector3]) -> bool {
202 let midpoints: Vec<Vector3> = points
203 .iter()
204 .map(|p| Vector3::from_array(p.to_array().map(|x| x.midpoint())))
205 .collect();
206 let spread = bounds(&midpoints);
207 (0..3).all(|k| {
208 let widest = points
209 .iter()
210 .map(|p| p[k].width())
211 .fold(
212 Scalar::ZERO,
213 |m, w| if w.to_f64() > m.to_f64() { w } else { m },
214 );
215 !spread[k].width().definitely_greater(widest)
216 })
217}
218
219type Best<A, B> = Option<(Scalar, (A, Vector3), (B, Vector3))>;
221
222fn offer<PA: Piece, PB: Piece>(best: &mut Best<PA::At, PB::At>, a: &PA, b: &PB) -> GeopResult<()> {
226 let (Some(pa), Some(pb)) = (a.point()?, b.point()?) else {
227 return Ok(());
228 };
229 let d = pa.1.sub(&pb.1).norm();
230 if best
231 .as_ref()
232 .is_none_or(|(e, ..)| d.midpoint().to_f64() < e.midpoint().to_f64())
233 {
234 *best = Some((d, pa, pb));
235 }
236 Ok(())
237}
238
239fn beyond<PA: Piece, PB: Piece>(pair: &Pair<PA, PB>, best: &Best<PA::At, PB::At>) -> bool {
241 best.as_ref()
242 .is_some_and(|(d, ..)| pair.lower.definitely_greater(*d))
243}
244
245pub fn could_come_within<PA: Piece + Clone, PB: Piece + Clone>(
254 a: Vec<PA>,
255 b: Vec<PB>,
256 reach: Scalar,
257 handoff: Scalar,
258 max_nodes: usize,
259) -> GeopResult<bool> {
260 let mut pending = BinaryHeap::new();
261 let mut visited = 0usize;
262 let look = |pending: &mut BinaryHeap<Pair<PA, PB>>, pa: PA, pb: PB| -> GeopResult<bool> {
265 let pair = Pair::new(pa, pb)?;
266 if pair.lower.definitely_greater(reach) {
267 return Ok(false);
268 }
269 if let (Some((_, p)), Some((_, q))) = (pair.a.point()?, pair.b.point()?)
270 && !p.sub(&q).norm().definitely_greater(reach)
271 {
272 return Ok(true);
273 }
274 pending.push(pair);
275 Ok(false)
276 };
277 for pa in &a {
278 for pb in &b {
279 if look(&mut pending, pa.clone(), pb.clone())? {
280 return Ok(true);
281 }
282 }
283 }
284 while let Some(pair) = pending.pop() {
285 if visited >= max_nodes {
286 return Ok(true);
287 }
288 visited += 1;
289 let Some(children) = cut(&pair, handoff)? else {
290 return Ok(true);
291 };
292 for (ca, cb) in children {
293 if look(&mut pending, ca, cb)? {
294 return Ok(true);
295 }
296 }
297 }
298 Ok(false)
299}
300
301#[allow(clippy::type_complexity)]
305fn cut<PA: Piece + Clone, PB: Piece + Clone>(
306 pair: &Pair<PA, PB>,
307 handoff: Scalar,
308) -> GeopResult<Option<Vec<(PA, PB)>>> {
309 let size = |hull: &[Vector3]| -> f64 {
310 bounds(hull)
311 .iter()
312 .map(|x| x.width().to_f64().powi(2))
313 .sum::<f64>()
314 };
315 let a_first = size(&pair.hull_a) >= size(&pair.hull_b);
316 let cut_a = |pair: &Pair<PA, PB>| -> GeopResult<Option<Vec<(PA, PB)>>> {
317 if resolved(&pair.hull_a) {
318 return Ok(None);
319 }
320 Ok(pair
321 .a
322 .split(handoff)?
323 .map(|halves| halves.into_iter().map(|h| (h, pair.b.clone())).collect()))
324 };
325 let cut_b = |pair: &Pair<PA, PB>| -> GeopResult<Option<Vec<(PA, PB)>>> {
326 if resolved(&pair.hull_b) {
327 return Ok(None);
328 }
329 Ok(pair
330 .b
331 .split(handoff)?
332 .map(|halves| halves.into_iter().map(|h| (pair.a.clone(), h)).collect()))
333 };
334 if a_first {
335 match cut_a(pair)? {
336 Some(c) => Ok(Some(c)),
337 None => cut_b(pair),
338 }
339 } else {
340 match cut_b(pair)? {
341 Some(c) => Ok(Some(c)),
342 None => cut_a(pair),
343 }
344 }
345}
346
347pub fn least_distance<PA: Piece + Clone, PB: Piece + Clone>(
352 a: Vec<PA>,
353 b: Vec<PB>,
354 handoff: Scalar,
355 max_nodes: usize,
356) -> GeopResult<Nearest<PA::At, PB::At>> {
357 let mut best: Best<PA::At, PB::At> = None;
358
359 let mut pending = BinaryHeap::new();
360 for pa in &a {
361 for pb in &b {
362 offer(&mut best, pa, pb)?;
363 pending.push(Pair::new(pa.clone(), pb.clone())?);
364 }
365 }
366 let mut leaves: Vec<Pair<PA, PB>> = Vec::new();
367 let mut visited = 0usize;
368 while let Some(pair) = pending.pop() {
369 if beyond(&pair, &best) {
370 pending.clear();
372 break;
373 }
374 let converged = best
379 .as_ref()
380 .is_some_and(|(d, ..)| !d.definitely_greater(pair.lower));
381 if converged || visited >= max_nodes {
382 pending.push(pair);
383 break;
384 }
385 visited += 1;
386 let children = cut(&pair, handoff)?;
387 let Some(children) = children else {
388 leaves.push(pair);
389 continue;
390 };
391 for (ca, cb) in children {
392 offer(&mut best, &ca, &cb)?;
393 let child = Pair::new(ca, cb)?;
394 if !beyond(&child, &best) {
395 pending.push(child);
396 }
397 }
398 }
399
400 let (attained, pa, pb) = best.ok_or_else(|| {
401 GeopError::new("least_distance: no piece of one of the sets holds a point of it")
402 })?;
403 let standing = leaves.iter().chain(pending.iter());
405 let lower = standing
406 .map(|p| p.lower.lower())
407 .reduce(|x, y| if y.to_f64() < x.to_f64() { y } else { x })
408 .unwrap_or(attained);
409 let mut candidates = Vec::new();
410 for leaf in leaves
411 .iter()
412 .filter(|leaf| !leaf.lower.definitely_greater(attained))
413 {
414 if let (Some(a), Some(b)) = (leaf.a.point()?, leaf.b.point()?) {
415 candidates.push((a.0, b.0));
416 }
417 }
418 Ok(Nearest {
419 a: pa,
420 b: pb,
421 distance: lower.lower().union(attained),
422 candidates,
423 })
424}
425
426#[derive(Clone, Debug)]
428pub struct PointPiece(pub Vector3);
429
430impl Piece for PointPiece {
431 type At = ();
432
433 fn hull(&self) -> GeopResult<Vec<Vector3>> {
434 Ok(vec![self.0])
435 }
436
437 fn point(&self) -> GeopResult<Option<((), Vector3)>> {
438 Ok(Some(((), self.0)))
439 }
440
441 fn split(&self, _: Scalar) -> GeopResult<Option<Vec<Self>>> {
442 Ok(None)
443 }
444}
445
446#[derive(Clone, Debug)]
449pub struct CurveSpan(pub NurbCurve3D);
450
451impl Piece for CurveSpan {
452 type At = Scalar;
453
454 fn hull(&self) -> GeopResult<Vec<Vector3>> {
455 Ok(dehomogenize::<4, 3>(&self.0.control_points))
456 }
457
458 fn point(&self) -> GeopResult<Option<(Scalar, Vector3)>> {
459 let t = self.0.mid_parameter()?;
460 Ok(Some((t, self.0.evaluate(t)?)))
461 }
462
463 fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>> {
464 if !self.0.size()?.definitely_greater(handoff) {
465 return Ok(None);
466 }
467 let (a, b) = self.0.split_mid().with_context(&|e: GeopError| {
468 e.with_context(format!(
469 "CurveSpan::split(domain={:?}, handoff={handoff:?}, control_points={:?})",
470 self.0.domain(),
471 self.0.control_points
472 ))
473 })?;
474 Ok(Some(vec![CurveSpan(a), CurveSpan(b)]))
475 }
476}
477
478#[derive(Clone, Debug)]
481pub struct SurfacePatch(pub NurbSurface3D);
482
483impl SurfacePatch {
484 pub fn halves(&self) -> GeopResult<(Self, Self)> {
486 let [along_u, along_v] = surface_extents(&self.0);
487 let (a, b) = if along_u >= along_v {
488 self.0.split_u_mid()?
489 } else {
490 self.0.split_v_mid()?
491 };
492 Ok((SurfacePatch(a), SurfacePatch(b)))
493 }
494
495 pub fn middle(&self) -> GeopResult<(Scalar, Scalar)> {
497 let mid = |(lo, hi): (Scalar, Scalar)| -> GeopResult<Scalar> {
498 Ok(lo.add(hi).div(Scalar::TWO)?.sharpen())
499 };
500 Ok((mid(self.0.domain_u())?, mid(self.0.domain_v())?))
501 }
502}
503
504impl Piece for SurfacePatch {
505 type At = (Scalar, Scalar);
506
507 fn hull(&self) -> GeopResult<Vec<Vector3>> {
508 Ok(dehomogenize::<4, 3>(&self.0.control_points))
509 }
510
511 fn point(&self) -> GeopResult<Option<((Scalar, Scalar), Vector3)>> {
512 let (u, v) = self.middle()?;
513 Ok(Some(((u, v), self.0.evaluate(u, v)?)))
514 }
515
516 fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>> {
517 if !self.0.size()?.definitely_greater(handoff) {
518 return Ok(None);
519 }
520 let (a, b) = self.halves()?;
521 Ok(Some(vec![a, b]))
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528 use crate::nurb_curve::NurbCurve;
529 use geop_core_math::{scalars::Scalar, vector::Vector4};
530
531 #[derive(Clone)]
533 struct Segment(Scalar, Scalar);
534
535 impl Piece for Segment {
536 type At = Scalar;
537
538 fn hull(&self) -> GeopResult<Vec<Vector3>> {
539 let at = |x: Scalar| Vector3::from_array([x, Scalar::ZERO, Scalar::ZERO]);
540 Ok(vec![at(self.0), at(self.1)])
541 }
542
543 fn point(&self) -> GeopResult<Option<(Scalar, Vector3)>> {
544 let x = self.0.add(self.1).div(Scalar::TWO)?.sharpen();
545 Ok(Some((
546 x,
547 Vector3::from_array([x, Scalar::ZERO, Scalar::ZERO]),
548 )))
549 }
550
551 fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>> {
552 if !self.1.sub(self.0).definitely_greater(handoff) {
553 return Ok(None);
554 }
555 let mid = self.0.add(self.1).div(Scalar::TWO)?.sharpen();
556 Ok(Some(vec![Segment(self.0, mid), Segment(mid, self.1)]))
557 }
558 }
559
560 #[test]
571 fn traced_span_beside_a_sharp_edge_above_two_points() {
572 let cp = |x: [f64; 2], y: [f64; 2], z: [f64; 2], w: [f64; 2]| {
573 Vector4::from_array([
574 Scalar::new(x[0], x[1]),
575 Scalar::new(y[0], y[1]),
576 Scalar::new(z[0], z[1]),
577 Scalar::new(w[0], w[1]),
578 ])
579 };
580 let span = NurbCurve::try_new(
581 3,
582 vec![
583 cp(
584 [1.0502014289205366, 1.0502015605016914],
585 [1.3968357901928796, 1.3968380515018717],
586 [0.9999999999990593, 1.0000000000009528],
587 [0.9999999999999375, 1.0000000000000744],
588 ),
589 cp(
590 [1.0502014289205344, 1.0502015605016937],
591 [1.396835790192877, 1.3968380515018743],
592 [0.9999999999990575, 1.000000000000955],
593 [0.9999999999999357, 1.0000000000000766],
594 ),
595 cp(
596 [1.050201428920532, 1.0502015605016959],
597 [1.3968357901928743, 1.396838051501877],
598 [0.9999999999990558, 1.0000000000009572],
599 [0.9999999999999339, 1.0000000000000788],
600 ),
601 cp(
602 [1.0502014289205295, 1.050201560501698],
603 [1.3968357901928716, 1.3968380515018797],
604 [0.999999999999054, 1.0000000000009595],
605 [0.9999999999999322, 1.000000000000081],
606 ),
607 ],
608 [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]
609 .map(Scalar::from_f64)
610 .to_vec(),
611 )
612 .unwrap();
613 let at = |p: [f64; 3]| Vector3::from_array(p.map(Scalar::from_f64));
614 let edge = crate::test_fixtures::line([4.0, 4.0, 1.0], [5.0, 5.0, 1.0]);
615 let nearest = least_distance(
616 vec![
617 PointPiece(at([1.0502015, 1.3968369, 0.0])),
618 PointPiece(at([4.5, 4.5, 0.0])),
619 ],
620 vec![CurveSpan(span), CurveSpan(edge)],
621 Scalar::from_f64(1e-7),
622 10_000,
623 )
624 .unwrap();
625 assert!(
626 nearest.distance.could_be_equal(Scalar::ONE),
627 "{:?}",
628 nearest.distance
629 );
630 assert!(
631 !nearest
632 .distance
633 .width()
634 .definitely_greater(Scalar::from_f64(1e-5)),
635 "{:?}",
636 nearest.distance
637 );
638 }
639
640 #[test]
645 fn point_above_a_segment() {
646 let target = Vector3::from_array([0.3, 2.0, 0.0].map(Scalar::from_f64));
647 let nearest = least_distance(
648 vec![PointPiece(target)],
649 vec![Segment(Scalar::from_f64(-1.0), Scalar::ONE)],
650 Scalar::from_f64(1e-6),
651 10_000,
652 )
653 .unwrap();
654 assert!(
655 nearest.distance.could_be_equal(Scalar::TWO),
656 "{:?}",
657 nearest.distance
658 );
659 assert!(
660 !nearest
661 .distance
662 .width()
663 .definitely_greater(Scalar::from_f64(1e-6)),
664 "{:?}",
665 nearest.distance
666 );
667 }
668}