1use std::cmp::Reverse;
59use std::collections::BinaryHeap;
60
61use axiolid_brep::ExactBRep;
62use axiolid_core::{Point2, Point3, Scalar, Tolerance, Vec3};
63use axiolid_curve::Curve3;
64use axiolid_evaluate::evaluate3;
65use axiolid_evaluate::surface::{evaluate, normal};
66use axiolid_surface::Surface;
67
68use crate::exact::ExactMeasureError;
69use crate::exact_domain::Domain;
70
71const MAX_STEPS: usize = 400_000;
73
74#[derive(Debug, Clone, PartialEq)]
76pub struct DistanceBounds {
77 pub lower: Scalar,
79 pub upper: Scalar,
81 pub point_a: Point3,
83 pub point_b: Point3,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum Clearance {
90 Below,
92 Above,
94 Indeterminate,
96}
97
98impl DistanceBounds {
99 #[must_use]
101 pub fn against(&self, limit: Scalar) -> Clearance {
102 if self.upper < limit {
103 Clearance::Below
104 } else if self.lower > limit {
105 Clearance::Above
106 } else {
107 Clearance::Indeterminate
108 }
109 }
110}
111
112pub fn boundary_distance(
120 a: &ExactBRep,
121 b: &ExactBRep,
122 accuracy: Scalar,
123 tolerance: Tolerance,
124) -> Result<DistanceBounds, ExactMeasureError> {
125 let accuracy = accuracy.max(0.0);
126 search(a, b, tolerance, &mut |lower, upper| {
127 upper - lower <= accuracy
128 })
129}
130
131pub fn boundary_clearance(
137 a: &ExactBRep,
138 b: &ExactBRep,
139 limit: Scalar,
140 tolerance: Tolerance,
141) -> Result<(DistanceBounds, Clearance), ExactMeasureError> {
142 let bounds = search(a, b, tolerance, &mut |lower, upper| {
143 upper < limit || lower > limit
144 })?;
145 let clearance = bounds.against(limit);
146 Ok((bounds, clearance))
147}
148
149#[derive(Debug, Clone, Copy)]
151enum Shape {
152 Face {
155 face: usize,
156 lo: Point2,
157 hi: Point2,
158 inside: bool,
159 },
160 Edge { edge: usize, t0: Scalar, t1: Scalar },
162}
163
164#[derive(Debug, Clone, Copy)]
166struct Element {
167 shape: Shape,
168 centre: Point3,
169 radius: Scalar,
170 witness: Option<Point3>,
172 normal: Option<Vec3>,
174 spread: Option<Scalar>,
178}
179
180struct Side<'a> {
182 brep: &'a ExactBRep,
183 domains: Vec<Option<Domain<'a>>>,
184 edges_bounded: Vec<bool>,
187 elements: Vec<Element>,
188}
189
190impl<'a> Side<'a> {
191 fn new(brep: &'a ExactBRep, linear: Scalar) -> Result<Self, ExactMeasureError> {
192 let topology = brep.topology();
193 let mut side = Side {
194 brep,
195 domains: Vec::with_capacity(topology.faces().len()),
196 edges_bounded: Vec::with_capacity(topology.faces().len()),
197 elements: Vec::new(),
198 };
199 for face in topology.faces() {
200 let mut bounded = true;
201 for bound in &face.bounds {
202 let wire = topology
203 .loops()
204 .get(bound.loop_id.index())
205 .ok_or(ExactMeasureError::DanglingReference)?;
206 for use_ in &wire.edges {
207 let curve = topology.edges()[use_.edge.index()]
208 .curve
209 .and_then(|id| brep.curves3().get(id.index()));
210 bounded &= matches!(
211 curve,
212 Some(Curve3::Line(_) | Curve3::Circle(_) | Curve3::Ellipse(_))
213 );
214 }
215 }
216 side.edges_bounded.push(bounded);
217 }
218 for face in topology.faces() {
219 let surface = surface_of(brep, face.surface)?;
220 side.domains.push(Domain::new(brep, face, surface, linear)?);
221 }
222 for (index, face) in topology.faces().iter().enumerate() {
223 let surface = surface_of(brep, face.surface)?;
224 let (lo, hi) = match &side.domains[index] {
225 Some(domain) => (domain.min, domain.max),
226 None => natural_range(surface).ok_or(ExactMeasureError::NonPlanarFace(
227 "an unbounded face trimmed by a pcurve family the distance query cannot bound",
228 ))?,
229 };
230 if let Some(element) = side.face_element(index, lo, hi, false)? {
231 side.elements.push(element);
232 }
233 }
234 for (index, edge) in topology.edges().iter().enumerate() {
235 let Some(curve) = edge.curve.and_then(|id| brep.curves3().get(id.index())) else {
236 continue;
237 };
238 let Some(span) = topology
239 .edge_id_at(index)
240 .and_then(|id| brep.edge_interval(id))
241 else {
242 continue;
243 };
244 if let Some(element) = edge_element(curve, index, span.start, span.end)? {
245 side.elements.push(element);
246 }
247 }
248 if side.elements.is_empty() {
249 return Err(ExactMeasureError::Degenerate);
250 }
251 Ok(side)
252 }
253
254 fn face_element(
256 &self,
257 face: usize,
258 lo: Point2,
259 hi: Point2,
260 inside: bool,
261 ) -> Result<Option<Element>, ExactMeasureError> {
262 let topology = self.brep.topology();
263 let surface = surface_of(self.brep, topology.faces()[face].surface)?;
264 let mid = (lo + hi) * 0.5;
265 let mut inside = inside;
266 if !inside {
267 if let Some(domain) = &self.domains[face] {
268 if !domain.touches(lo, hi)? {
269 match domain.contains(mid)? {
270 Some(false) => return Ok(None),
271 Some(true) => inside = true,
272 None => {}
273 }
274 }
275 }
276 }
277 let (centre, radius) = patch_sphere(surface, lo, hi)?;
278 let witness = if inside {
279 Some(point_on(surface, mid)?)
280 } else {
281 None
282 };
283 let normal = normal(surface, mid.x, mid.y).ok();
284 let spread = if self.edges_bounded[face] {
285 normal_spread(surface, lo, hi)
286 } else {
287 None
288 };
289 Ok(Some(Element {
290 normal,
291 spread,
292 shape: Shape::Face {
293 face,
294 lo,
295 hi,
296 inside,
297 },
298 centre,
299 radius,
300 witness,
301 }))
302 }
303
304 fn split(&self, element: &Element) -> Result<Vec<Element>, ExactMeasureError> {
308 match element.shape {
309 Shape::Face {
310 face,
311 lo,
312 hi,
313 inside,
314 } => {
315 let surface = surface_of(self.brep, self.brep.topology().faces()[face].surface)?;
316 let (lu, lv) = lipschitz(surface, lo, hi)?;
317 let mid = (lo + hi) * 0.5;
318 let halves = if (hi.x - lo.x).abs() * lu >= (hi.y - lo.y).abs() * lv {
319 [
320 (lo, Point2::new(mid.x, hi.y)),
321 (Point2::new(mid.x, lo.y), hi),
322 ]
323 } else {
324 [
325 (lo, Point2::new(hi.x, mid.y)),
326 (Point2::new(lo.x, mid.y), hi),
327 ]
328 };
329 let mut out = Vec::with_capacity(2);
330 for (lo, hi) in halves {
331 if let Some(child) = self.face_element(face, lo, hi, inside)? {
332 out.push(child);
333 }
334 }
335 Ok(out)
336 }
337 Shape::Edge { edge, t0, t1 } => {
338 let curve = self.brep.topology().edges()[edge]
339 .curve
340 .and_then(|id| self.brep.curves3().get(id.index()))
341 .ok_or(ExactMeasureError::DanglingReference)?;
342 let tm = 0.5 * (t0 + t1);
343 let mut out = Vec::with_capacity(2);
344 for (a, b) in [(t0, tm), (tm, t1)] {
345 if let Some(child) = edge_element(curve, edge, a, b)? {
346 out.push(child);
347 }
348 }
349 Ok(out)
350 }
351 }
352 }
353}
354
355fn surface_of(
356 brep: &ExactBRep,
357 id: Option<axiolid_brep::SurfaceId>,
358) -> Result<&Surface, ExactMeasureError> {
359 let id = id.ok_or(ExactMeasureError::MissingSurface)?;
360 brep.surfaces()
361 .get(id.index())
362 .ok_or(ExactMeasureError::DanglingReference)
363}
364
365fn point_on(surface: &Surface, at: Point2) -> Result<Point3, ExactMeasureError> {
366 evaluate(surface, at.x, at.y).map_err(|_| crate::exact::EVALUATION)
367}
368
369fn natural_range(surface: &Surface) -> Option<(Point2, Point2)> {
372 use core::f64::consts::{FRAC_PI_2, TAU};
373 match surface {
374 Surface::Sphere(_) => Some((Point2::new(0.0, -FRAC_PI_2), Point2::new(TAU, FRAC_PI_2))),
375 Surface::Torus(_) => Some((Point2::ZERO, Point2::new(TAU, TAU))),
376 _ => None,
377 }
378}
379
380fn frame_scale(frame: &axiolid_core::Frame3) -> Scalar {
382 frame.x.length().max(frame.y.length()).max(frame.z.length())
383}
384
385fn lipschitz(
387 surface: &Surface,
388 lo: Point2,
389 hi: Point2,
390) -> Result<(Scalar, Scalar), ExactMeasureError> {
391 let bounds = match surface {
392 Surface::Plane(p) => (p.frame.x.length(), p.frame.y.length()),
393 Surface::Cylinder(c) => (c.radius.abs() * frame_scale(&c.frame), c.frame.z.length()),
394 Surface::EllipticalCylinder(c) => (
395 c.semi_axis_x.abs().max(c.semi_axis_y.abs()) * frame_scale(&c.frame),
396 c.frame.z.length(),
397 ),
398 Surface::Cone(c) => {
399 let slope = c.semi_angle.tan();
400 let radius = (c.radius + lo.y * slope)
401 .abs()
402 .max((c.radius + hi.y * slope).abs());
403 let scale = frame_scale(&c.frame);
404 (radius * scale, scale * (1.0 + slope * slope).sqrt())
405 }
406 Surface::Sphere(s) => {
407 let r = s.radius.abs() * frame_scale(&s.frame);
410 (r * max_cos(lo.y, hi.y), r)
411 }
412 Surface::Torus(t) => {
413 let scale = frame_scale(&t.frame);
414 (
415 (t.major_radius.abs() + t.minor_radius.abs() * max_cos(lo.y, hi.y)) * scale,
416 t.minor_radius.abs() * scale,
417 )
418 }
419 Surface::BSpline(_) => (0.0, 0.0),
421 _ => {
422 return Err(ExactMeasureError::NonPlanarFace(crate::exact::family(
423 surface,
424 )))
425 }
426 };
427 if bounds.0.is_finite() && bounds.1.is_finite() {
428 Ok(bounds)
429 } else {
430 Err(crate::exact::EVALUATION)
431 }
432}
433
434fn max_cos(a: Scalar, b: Scalar) -> Scalar {
436 let (a, b) = (a.min(b), a.max(b));
437 let k = (a / core::f64::consts::PI).ceil();
438 if k * core::f64::consts::PI <= b {
439 1.0
440 } else {
441 a.cos().abs().max(b.cos().abs())
442 }
443}
444
445fn patch_sphere(
452 surface: &Surface,
453 lo: Point2,
454 hi: Point2,
455) -> Result<(Point3, Scalar), ExactMeasureError> {
456 if let Surface::BSpline(spline) = surface {
457 if let Some(weights) = &spline.weights {
458 if weights.iter().flatten().any(|w| w.is_nan() || *w <= 0.0) {
459 return Err(ExactMeasureError::NonPlanarFace(
460 "non-positive-weight B-spline",
461 ));
462 }
463 }
464 let points: Vec<Point3> = spline.control_points.iter().flatten().copied().collect();
465 if points.is_empty() {
466 return Err(ExactMeasureError::Degenerate);
467 }
468 let (mut min, mut max) = (points[0], points[0]);
469 for p in &points {
470 min = min.min(*p);
471 max = max.max(*p);
472 }
473 let centre = (min + max) * 0.5;
474 let radius = points
475 .iter()
476 .map(|p| (*p - centre).length())
477 .fold(0.0, Scalar::max);
478 return Ok((centre, pad(centre, radius)));
479 }
480 let centre = point_on(surface, (lo + hi) * 0.5)?;
481 let (lu, lv) = lipschitz(surface, lo, hi)?;
482 let radius = 0.5 * ((hi.x - lo.x).abs() * lu + (hi.y - lo.y).abs() * lv);
483 Ok((centre, pad(centre, radius)))
484}
485
486fn pad(centre: Point3, radius: Scalar) -> Scalar {
488 radius + 1e-12 * (centre.length() + radius) + Scalar::MIN_POSITIVE
489}
490
491fn edge_element(
494 curve: &Curve3,
495 edge: usize,
496 t0: Scalar,
497 t1: Scalar,
498) -> Result<Option<Element>, ExactMeasureError> {
499 let speed = match curve {
500 Curve3::Line(line) => line.direction.length(),
501 Curve3::Circle(circle) => circle.radius.abs() * frame_scale(&circle.frame),
502 Curve3::Ellipse(ellipse) => {
503 ellipse.semi_axis_x.abs().max(ellipse.semi_axis_y.abs()) * frame_scale(&ellipse.frame)
504 }
505 _ => return Ok(None),
506 };
507 let centre = evaluate3(curve, 0.5 * (t0 + t1)).map_err(|_| crate::exact::EVALUATION)?;
508 let radius = pad(centre, 0.5 * (t1 - t0).abs() * speed);
509 Ok(Some(Element {
510 normal: None,
511 spread: None,
512 shape: Shape::Edge { edge, t0, t1 },
513 centre,
514 radius,
515 witness: Some(centre),
516 }))
517}
518
519fn normal_spread(surface: &Surface, lo: Point2, hi: Point2) -> Option<Scalar> {
521 let (du, dv) = ((hi.x - lo.x).abs(), (hi.y - lo.y).abs());
522 let spread = match surface {
523 Surface::Plane(_) => 0.0,
524 Surface::Cylinder(_) => 0.5 * du,
526 Surface::Cone(c) => {
527 let slope = c.semi_angle.tan();
531 let apex = -c.radius / slope;
532 if !apex.is_finite() || (apex >= lo.y.min(hi.y) - 1e-9 && apex <= lo.y.max(hi.y) + 1e-9)
533 {
534 return None;
535 }
536 0.5 * du
537 }
538 Surface::EllipticalCylinder(c) => {
540 let (a, b) = (c.semi_axis_x.abs(), c.semi_axis_y.abs());
541 0.5 * du * a.max(b) / a.min(b)
542 }
543 Surface::Sphere(_) | Surface::Torus(_) => 0.5 * (du + dv),
544 _ => return None,
545 };
546 spread.is_finite().then_some(spread + 1e-9)
547}
548
549fn critical_possible(face: &Element, other: &Element) -> bool {
562 let (Some(normal), Some(spread)) = (face.normal, face.spread) else {
563 return true;
564 };
565 let offset = other.centre - face.centre;
566 let gap = offset.length();
567 let reach = face.radius + other.radius;
568 if gap.is_nan() || gap <= reach {
569 return true;
570 }
571 let aperture = (reach / gap).asin();
572 let angle = spread + aperture + 1e-9;
573 if angle >= core::f64::consts::FRAC_PI_2 {
574 return true;
575 }
576 (offset / gap).dot(normal).abs() >= angle.cos() * normal.length()
577}
578
579fn trig_range(a: Scalar, b: Scalar, t0: Scalar, t1: Scalar) -> (Scalar, Scalar) {
581 let (t0, t1) = (t0.min(t1), t0.max(t1));
582 let f = |t: Scalar| a * t.cos() + b * t.sin();
583 let (mut lo, mut hi) = (f(t0).min(f(t1)), f(t0).max(f(t1)));
584 let amplitude = a.hypot(b);
585 let peak = b.atan2(a);
586 let reaches = |angle: Scalar| {
587 let k = ((t0 - angle) / core::f64::consts::TAU).ceil();
588 angle + k * core::f64::consts::TAU <= t1
589 };
590 if reaches(peak) {
591 hi = amplitude;
592 }
593 if reaches(peak + core::f64::consts::PI) {
594 lo = -amplitude;
595 }
596 (lo, hi)
597}
598
599fn sphere_range(element: &Element, d: Vec3) -> (Scalar, Scalar) {
601 let c = element.centre.dot(d);
602 (c - element.radius, c + element.radius)
603}
604
605impl Side<'_> {
606 fn project(&self, element: &Element, d: Vec3) -> Result<(Scalar, Scalar), ExactMeasureError> {
609 let sphere = sphere_range(element, d);
610 let exact = match element.shape {
611 Shape::Face { face, lo, hi, .. } => {
612 let surface = surface_of(self.brep, self.brep.topology().faces()[face].surface)?;
613 match surface {
614 Surface::Plane(p) => {
615 let base = p.frame.origin.dot(d);
616 let (x, y) = (p.frame.x.dot(d), p.frame.y.dot(d));
617 let values = [
618 base + x * lo.x + y * lo.y,
619 base + x * hi.x + y * lo.y,
620 base + x * lo.x + y * hi.y,
621 base + x * hi.x + y * hi.y,
622 ];
623 Some(
624 values
625 .iter()
626 .fold((Scalar::INFINITY, Scalar::NEG_INFINITY), |(a, b), v| {
627 (a.min(*v), b.max(*v))
628 }),
629 )
630 }
631 Surface::Cylinder(c) => {
632 let (a, b) = trig_range(
633 c.radius * c.frame.x.dot(d),
634 c.radius * c.frame.y.dot(d),
635 lo.x,
636 hi.x,
637 );
638 let z = c.frame.z.dot(d);
639 let base = c.frame.origin.dot(d);
640 Some((
641 base + a + (z * lo.y).min(z * hi.y),
642 base + b + (z * lo.y).max(z * hi.y),
643 ))
644 }
645 Surface::EllipticalCylinder(c) => {
646 let (a, b) = trig_range(
647 c.semi_axis_x * c.frame.x.dot(d),
648 c.semi_axis_y * c.frame.y.dot(d),
649 lo.x,
650 hi.x,
651 );
652 let z = c.frame.z.dot(d);
653 let base = c.frame.origin.dot(d);
654 Some((
655 base + a + (z * lo.y).min(z * hi.y),
656 base + b + (z * lo.y).max(z * hi.y),
657 ))
658 }
659 Surface::Cone(c) => {
660 let slope = c.semi_angle.tan();
663 let base = c.frame.origin.dot(d);
664 let (x, y, z) = (c.frame.x.dot(d), c.frame.y.dot(d), c.frame.z.dot(d));
665 let mut range = (Scalar::INFINITY, Scalar::NEG_INFINITY);
666 for v in [lo.y, hi.y] {
667 let r = c.radius + v * slope;
668 let (a, b) = trig_range(r * x, r * y, lo.x, hi.x);
669 range = (range.0.min(base + a + z * v), range.1.max(base + b + z * v));
670 }
671 Some(range)
672 }
673 Surface::Sphere(sphere) => {
674 let r = sphere.radius;
677 let base = sphere.frame.origin.dot(d);
678 let (w_lo, w_hi) =
679 trig_range(sphere.frame.x.dot(d), sphere.frame.y.dot(d), lo.x, hi.x);
680 let z = sphere.frame.z.dot(d);
681 let mut range = (Scalar::INFINITY, Scalar::NEG_INFINITY);
682 for w in [w_lo, w_hi] {
683 let (a, b) = trig_range(r * w, r * z, lo.y, hi.y);
684 range = (range.0.min(base + a), range.1.max(base + b));
685 }
686 Some(range)
687 }
688 Surface::Torus(torus) => {
689 let (big, small) = (torus.major_radius, torus.minor_radius);
693 if big.is_nan() || big <= small.abs() {
694 return Ok(sphere_range(element, d));
695 }
696 let base = torus.frame.origin.dot(d);
697 let (w_lo, w_hi) =
698 trig_range(torus.frame.x.dot(d), torus.frame.y.dot(d), lo.x, hi.x);
699 let z = torus.frame.z.dot(d);
700 let mut range = (Scalar::INFINITY, Scalar::NEG_INFINITY);
701 for w in [w_lo, w_hi] {
702 let (a, b) = trig_range(small * w, small * z, lo.y, hi.y);
703 range = (
704 range.0.min(base + big * w + a),
705 range.1.max(base + big * w + b),
706 );
707 }
708 Some(range)
709 }
710 _ => None,
711 }
712 }
713 Shape::Edge { edge, t0, t1 } => {
714 let curve = self.brep.topology().edges()[edge]
715 .curve
716 .and_then(|id| self.brep.curves3().get(id.index()))
717 .ok_or(ExactMeasureError::DanglingReference)?;
718 match curve {
719 Curve3::Line(line) => {
720 let (a, b) = (
721 (line.origin + line.direction * t0).dot(d),
722 (line.origin + line.direction * t1).dot(d),
723 );
724 Some((a.min(b), a.max(b)))
725 }
726 Curve3::Circle(c) => {
727 let (a, b) = trig_range(
728 c.radius * c.frame.x.dot(d),
729 c.radius * c.frame.y.dot(d),
730 t0,
731 t1,
732 );
733 let base = c.frame.origin.dot(d);
734 Some((base + a, base + b))
735 }
736 Curve3::Ellipse(e) => {
737 let (a, b) = trig_range(
738 e.semi_axis_x * e.frame.x.dot(d),
739 e.semi_axis_y * e.frame.y.dot(d),
740 t0,
741 t1,
742 );
743 let base = e.frame.origin.dot(d);
744 Some((base + a, base + b))
745 }
746 _ => None,
747 }
748 }
749 };
750 Ok(match exact {
751 Some((lo, hi)) => {
752 let pad =
754 1e-12 * (element.centre.length() + element.radius + lo.abs().max(hi.abs()));
755 (lo.max(sphere.0) - pad, hi.min(sphere.1) + pad)
756 }
757 None => sphere,
758 })
759 }
760}
761
762fn lower_bound(
765 side_a: &Side<'_>,
766 a: &Element,
767 side_b: &Side<'_>,
768 b: &Element,
769) -> Result<Scalar, ExactMeasureError> {
770 let gap = (a.centre - b.centre).length();
771 let rounding = 1e-12 * (a.centre.length() + b.centre.length() + gap);
772 let mut best = (gap - a.radius - b.radius - rounding).max(0.0);
773 let directions = [
774 (gap > 0.0).then(|| (b.centre - a.centre) / gap),
775 a.normal,
776 b.normal,
777 ];
778 for d in directions.into_iter().flatten() {
779 let (a_lo, a_hi) = side_a.project(a, d)?;
780 let (b_lo, b_hi) = side_b.project(b, d)?;
781 best = best.max(b_lo - a_hi).max(a_lo - b_hi);
782 }
783 Ok(best)
784}
785
786#[derive(Debug, Clone, Copy, PartialEq)]
788struct Key(Scalar);
789
790impl Eq for Key {}
791
792impl PartialOrd for Key {
793 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
794 Some(self.cmp(other))
795 }
796}
797
798impl Ord for Key {
799 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
800 self.0.total_cmp(&other.0)
801 }
802}
803
804fn search(
805 a: &ExactBRep,
806 b: &ExactBRep,
807 tolerance: Tolerance,
808 done: &mut dyn FnMut(Scalar, Scalar) -> bool,
809) -> Result<DistanceBounds, ExactMeasureError> {
810 let linear = tolerance.linear().max(1e-12);
811 let side_a = Side::new(a, linear)?;
812 let side_b = Side::new(b, linear)?;
813 let mut elements_a = side_a.elements.clone();
814 let mut elements_b = side_b.elements.clone();
815
816 let mut best: Option<(Scalar, Point3, Point3)> = None;
817 let mut heap = BinaryHeap::new();
818 let viable = |a: &Element, b: &Element| critical_possible(a, b) && critical_possible(b, a);
819 for (i, ea) in elements_a.iter().enumerate() {
820 for (j, eb) in elements_b.iter().enumerate() {
821 if viable(ea, eb) {
822 heap.push(Reverse((Key(lower_bound(&side_a, ea, &side_b, eb)?), i, j)));
823 }
824 }
825 }
826
827 let mut lower = 0.0;
828 let mut steps = 0;
829 while let Some(Reverse((Key(bound), i, j))) = heap.pop() {
830 lower = bound;
831 let (ea, eb) = (elements_a[i], elements_b[j]);
832 if let (Some(wa), Some(wb)) = (ea.witness, eb.witness) {
833 let d = (wa - wb).length();
834 if best.is_none_or(|(current, _, _)| d < current) {
835 best = Some((d, wa, wb));
836 }
837 }
838 let upper = best.map_or(Scalar::INFINITY, |(d, _, _)| d);
839 if upper.is_finite() && (bound >= upper || done(bound, upper)) {
840 break;
841 }
842 steps += 1;
843 if steps > MAX_STEPS {
844 break;
845 }
846 let split_a = ea.radius >= eb.radius;
851 let children = if split_a {
852 side_a.split(&ea)?
853 } else {
854 side_b.split(&eb)?
855 };
856 let parent = if split_a { ea.radius } else { eb.radius };
857 if children.iter().any(|child| child.radius >= parent) {
858 heap.push(Reverse((Key(bound), i, j)));
859 break;
860 }
861 for child in children {
862 if split_a {
863 elements_a.push(child);
864 let index = elements_a.len() - 1;
865 if viable(&child, &eb) {
866 heap.push(Reverse((
867 Key(lower_bound(&side_a, &child, &side_b, &eb)?),
868 index,
869 j,
870 )));
871 }
872 } else {
873 elements_b.push(child);
874 let index = elements_b.len() - 1;
875 if viable(&ea, &child) {
876 heap.push(Reverse((
877 Key(lower_bound(&side_a, &ea, &side_b, &child)?),
878 i,
879 index,
880 )));
881 }
882 }
883 }
884 }
885 if let Some(Reverse((Key(bound), _, _))) = heap.peek() {
887 lower = lower.min(*bound);
888 }
889 let (upper, point_a, point_b) = best.ok_or(crate::exact::NOT_CONVERGED)?;
890 Ok(DistanceBounds {
891 lower: lower.min(upper),
892 upper,
893 point_a,
894 point_b,
895 })
896}
897
898#[cfg(test)]
899mod tests {
900 use super::{normal_spread, patch_sphere};
905 use axiolid_core::{Frame3, Point2, Point3, Vec3};
906 use axiolid_evaluate::surface::{evaluate, normal};
907 use axiolid_surface::{Cone, Cylinder, EllipticalCylinder, Plane, Sphere, Surface, Torus};
908
909 fn frame() -> Frame3 {
910 let x = Vec3::new(0.6, 0.8, 0.0);
912 let z = Vec3::new(0.0, 0.0, 1.0);
913 Frame3 {
914 origin: Point3::new(1.5, -2.0, 0.75),
915 x,
916 y: z.cross(x),
917 z,
918 }
919 }
920
921 fn families() -> Vec<(Surface, Point2, Point2)> {
922 let f = frame();
923 vec![
924 (
925 Surface::Plane(Plane { frame: f }),
926 Point2::new(-1.0, 0.5),
927 Point2::new(2.0, 1.25),
928 ),
929 (
930 Surface::Cylinder(Cylinder {
931 frame: f,
932 radius: 2.5,
933 }),
934 Point2::new(0.3, -1.0),
935 Point2::new(1.9, 2.0),
936 ),
937 (
938 Surface::EllipticalCylinder(EllipticalCylinder {
939 frame: f,
940 semi_axis_x: 3.0,
941 semi_axis_y: 1.0,
942 }),
943 Point2::new(0.2, 0.0),
944 Point2::new(1.4, 1.0),
945 ),
946 (
947 Surface::Cone(Cone {
948 frame: f,
949 radius: 1.5,
950 semi_angle: 0.4,
951 }),
952 Point2::new(0.5, 0.2),
953 Point2::new(2.5, 1.5),
954 ),
955 (
956 Surface::Sphere(Sphere {
957 frame: f,
958 radius: 2.0,
959 }),
960 Point2::new(0.4, -0.3),
961 Point2::new(2.0, 1.1),
962 ),
963 (
964 Surface::Torus(Torus {
965 frame: f,
966 major_radius: 3.0,
967 minor_radius: 1.0,
968 }),
969 Point2::new(0.1, 0.5),
970 Point2::new(1.7, 2.9),
971 ),
972 ]
973 }
974
975 fn samples(lo: Point2, hi: Point2) -> impl Iterator<Item = Point2> {
976 const N: usize = 24;
977 (0..=N).flat_map(move |i| {
978 (0..=N).map(move |j| {
979 Point2::new(
980 lo.x + (hi.x - lo.x) * i as f64 / N as f64,
981 lo.y + (hi.y - lo.y) * j as f64 / N as f64,
982 )
983 })
984 })
985 }
986
987 #[test]
988 fn every_patch_point_lies_in_its_sphere() {
989 for (surface, lo, hi) in families() {
990 let (centre, radius) = patch_sphere(&surface, lo, hi).expect("bounded");
991 let mut reach: f64 = 0.0;
992 for p in samples(lo, hi) {
993 let point = evaluate(&surface, p.x, p.y).expect("point");
994 reach = reach.max((point - centre).length());
995 }
996 assert!(
997 reach <= radius,
998 "{surface:?}: reach {reach} > radius {radius}"
999 );
1000 assert!(
1002 radius <= 4.0 * reach + 1e-9,
1003 "{surface:?}: {radius} vs {reach}"
1004 );
1005 }
1006 }
1007
1008 #[test]
1009 fn every_patch_normal_lies_in_its_cone() {
1010 let mid = |lo: Point2, hi: Point2| (lo + hi) * 0.5;
1011 for (surface, lo, hi) in families() {
1012 let Some(spread) = normal_spread(&surface, lo, hi) else {
1013 continue;
1014 };
1015 let c = mid(lo, hi);
1016 let axis = normal(&surface, c.x, c.y).expect("normal");
1017 let mut widest: f64 = 0.0;
1018 for p in samples(lo, hi) {
1019 let n = normal(&surface, p.x, p.y).expect("normal");
1020 widest = widest.max(n.dot(axis).clamp(-1.0, 1.0).acos());
1021 }
1022 assert!(
1023 widest <= spread,
1024 "{surface:?}: normals turn {widest} > {spread}"
1025 );
1026 }
1027 }
1028}