1use std::cmp::Ordering;
97use std::collections::BinaryHeap;
98
99use axiolid_core::Point3;
100use axiolid_mesh::TriangleMeshView;
101
102mod patches;
103
104use patches::{flat_patches, Patch};
105
106const EPS: f64 = f64::EPSILON;
107
108const MAX_SPLITS: usize = 400_000;
110
111const LEAF_SIZE: usize = 4;
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116#[non_exhaustive]
117pub enum HausdorffError {
118 EmptyMesh,
120 IndexOutOfRange,
122 NonFiniteInput,
124 InvalidAccuracy,
126}
127
128impl core::fmt::Display for HausdorffError {
129 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130 formatter.write_str(match self {
131 Self::EmptyMesh => "mesh has no triangles",
132 Self::IndexOutOfRange => "triangle index out of range",
133 Self::NonFiniteInput => "mesh position is not finite",
134 Self::InvalidAccuracy => "accuracy must be non-negative",
135 })
136 }
137}
138
139impl std::error::Error for HausdorffError {}
140
141#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct HausdorffBounds {
144 pub lower: f64,
148 pub upper: f64,
151 pub point_from: Point3,
154 pub point_to: Point3,
156}
157
158impl HausdorffBounds {
159 #[must_use]
161 pub fn width(&self) -> f64 {
162 self.upper - self.lower
163 }
164
165 #[must_use]
167 pub fn contains(&self, value: f64) -> bool {
168 self.lower <= value && value <= self.upper
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq)]
174pub struct MeshHausdorff {
175 pub distance: HausdorffBounds,
178 pub forward: HausdorffBounds,
180 pub backward: HausdorffBounds,
182}
183
184pub fn one_sided_hausdorff<A, B>(
194 from: &A,
195 to: &B,
196 accuracy: f64,
197) -> Result<HausdorffBounds, HausdorffError>
198where
199 A: TriangleMeshView + ?Sized,
200 B: TriangleMeshView + ?Sized,
201{
202 let accuracy = check_accuracy(accuracy)?;
203 let source = collect(from)?;
204 let target = Target::new(collect(to)?, accuracy);
205 Ok(search(&source, &target, accuracy))
206}
207
208pub fn hausdorff_distance<A, B>(
217 a: &A,
218 b: &B,
219 accuracy: f64,
220) -> Result<MeshHausdorff, HausdorffError>
221where
222 A: TriangleMeshView + ?Sized,
223 B: TriangleMeshView + ?Sized,
224{
225 let accuracy = check_accuracy(accuracy)?;
226 let first = collect(a)?;
227 let second = collect(b)?;
228 let forward = search(&first, &Target::new(second.clone(), accuracy), accuracy);
229 let backward = search(&second, &Target::new(first, accuracy), accuracy);
230 let mut distance = if backward.lower > forward.lower {
231 backward
232 } else {
233 forward
234 };
235 distance.upper = forward.upper.max(backward.upper);
236 Ok(MeshHausdorff {
237 distance,
238 forward,
239 backward,
240 })
241}
242
243fn check_accuracy(accuracy: f64) -> Result<f64, HausdorffError> {
244 if accuracy.is_nan() || accuracy < 0.0 {
245 Err(HausdorffError::InvalidAccuracy)
246 } else {
247 Ok(accuracy)
248 }
249}
250
251#[derive(Clone)]
253struct Surface {
254 triangles: Vec<[Point3; 3]>,
255 indices: Vec<[usize; 3]>,
256 positions: Vec<Point3>,
257 used: Vec<bool>,
259}
260
261fn collect<M: TriangleMeshView + ?Sized>(mesh: &M) -> Result<Surface, HausdorffError> {
262 let positions = mesh.position_count();
263 let mut used = vec![false; positions];
264 let mut triangles = Vec::with_capacity(mesh.triangle_count());
265 let mut indices = Vec::with_capacity(mesh.triangle_count());
266 for index in 0..mesh.triangle_count() {
267 let mut corners = [Point3::ZERO; 3];
268 let mut ids = [0; 3];
269 for (slot, corner) in mesh.triangle(index).iter().enumerate() {
270 let corner = usize::try_from(*corner)
271 .ok()
272 .filter(|corner| *corner < positions)
273 .ok_or(HausdorffError::IndexOutOfRange)?;
274 let point = mesh.position(corner);
275 if !point.is_finite() {
276 return Err(HausdorffError::NonFiniteInput);
277 }
278 corners[slot] = point;
279 ids[slot] = corner;
280 used[corner] = true;
281 }
282 triangles.push(corners);
283 indices.push(ids);
284 }
285 if triangles.is_empty() {
286 return Err(HausdorffError::EmptyMesh);
287 }
288 let positions = (0..positions).map(|index| mesh.position(index)).collect();
289 Ok(Surface {
290 triangles,
291 indices,
292 positions,
293 used,
294 })
295}
296
297struct Facet {
299 corners: [Point3; 3],
300 scale: f64,
302 reach: f64,
304 thin: bool,
306 lo: Point3,
307 hi: Point3,
308}
309
310enum NodeKind {
311 Leaf { start: usize, end: usize },
312 Branch { left: usize, right: usize },
313}
314
315struct Node {
316 lo: Point3,
317 hi: Point3,
318 kind: NodeKind,
319}
320
321struct Target {
323 facets: Vec<Facet>,
324 order: Vec<usize>,
326 nodes: Vec<Node>,
327 patches: Vec<Patch>,
329 patches_of: Vec<Vec<usize>>,
331 patch_boxes: Vec<(Point3, Point3)>,
333}
334
335impl Target {
336 fn new(surface: Surface, accuracy: f64) -> Self {
339 let facets: Vec<Facet> = surface
340 .triangles
341 .into_iter()
342 .map(|corners| {
343 let [a, b, c] = corners;
344 let (ab, ac) = (b - a, c - a);
345 let area = ab.cross(ac).length_squared();
346 Facet {
347 corners,
348 scale: a.length() + b.length() + c.length(),
349 reach: a.length().max(b.length()).max(c.length()),
350 thin: area <= 1e-20 * ab.length_squared() * ac.length_squared(),
351 lo: a.min(b).min(c),
352 hi: a.max(b).max(c),
353 }
354 })
355 .collect();
356 let size = facets.iter().map(|f| f.reach).fold(0.0, f64::max);
357 let corners: Vec<[Point3; 3]> = facets.iter().map(|f| f.corners).collect();
358 let thin: Vec<bool> = facets.iter().map(|f| f.thin).collect();
359 let (patches, patches_of) =
360 flat_patches(&corners, &thin, (accuracy / 4.0).max(1024.0 * EPS * size));
361 let patch_boxes = patches
362 .iter()
363 .map(|patch| {
364 patch.members.iter().fold(
365 (
366 Point3::splat(f64::INFINITY),
367 Point3::splat(f64::NEG_INFINITY),
368 ),
369 |(lo, hi), &m| (lo.min(facets[m].lo), hi.max(facets[m].hi)),
370 )
371 })
372 .collect();
373 let mut target = Self {
374 order: (0..facets.len()).collect(),
375 facets,
376 nodes: Vec::new(),
377 patches,
378 patches_of,
379 patch_boxes,
380 };
381 let count = target.order.len();
382 target.build(0, count);
383 target
384 }
385
386 fn build(&mut self, start: usize, end: usize) -> usize {
387 let (mut lo, mut hi) = (
388 Point3::splat(f64::INFINITY),
389 Point3::splat(f64::NEG_INFINITY),
390 );
391 let (mut clo, mut chi) = (lo, hi);
392 for &index in &self.order[start..end] {
393 for corner in self.facets[index].corners {
394 lo = lo.min(corner);
395 hi = hi.max(corner);
396 }
397 let centre = centroid(&self.facets[index].corners);
398 clo = clo.min(centre);
399 chi = chi.max(centre);
400 }
401 let node = self.nodes.len();
402 self.nodes.push(Node {
403 lo,
404 hi,
405 kind: NodeKind::Leaf { start, end },
406 });
407 if end - start <= LEAF_SIZE {
408 return node;
409 }
410 let extent = chi - clo;
411 let axis = if extent.x >= extent.y && extent.x >= extent.z {
412 0
413 } else if extent.y >= extent.z {
414 1
415 } else {
416 2
417 };
418 let middle = start + (end - start) / 2;
419 let facets = &self.facets;
420 self.order[start..end].select_nth_unstable_by(middle - start, |&i, &j| {
421 centroid(&facets[i].corners)[axis].total_cmp(¢roid(&facets[j].corners)[axis])
422 });
423 let left = self.build(start, middle);
424 let right = self.build(middle, end);
425 self.nodes[node].kind = NodeKind::Branch { left, right };
426 node
427 }
428
429 fn point(&self, v: Point3) -> Nearest {
432 let mut best = f64::INFINITY;
433 let mut nearest = (self.facets[0].corners[0], 0);
434 let mut lower = f64::INFINITY;
435 let root = &self.nodes[0];
436 let mut stack = vec![(0usize, box_gap(v, root.lo, root.hi))];
437 while let Some((node, gap)) = stack.pop() {
438 if gap >= best {
439 lower = lower.min(gap);
441 continue;
442 }
443 match self.nodes[node].kind {
444 NodeKind::Leaf { start, end } => {
445 for &index in &self.order[start..end] {
446 let facet = &self.facets[index];
447 let gap = box_gap(v, facet.lo, facet.hi);
448 if gap >= best {
449 lower = lower.min(gap);
450 continue;
451 }
452 let p = nearest_on(v, facet);
453 let up = upper_at(v, p, facet);
454 lower = lower.min(lower_at(v, p, facet));
455 if up < best {
456 best = up;
457 nearest = (p, index);
458 }
459 }
460 }
461 NodeKind::Branch { left, right } => {
462 self.push_children(&mut stack, left, right, |n| box_gap(v, n.lo, n.hi));
463 }
464 }
465 }
466 Nearest {
467 lower: lower.max(0.0).min(best),
468 point: nearest.0,
469 facet: nearest.1,
470 }
471 }
472
473 fn piece(&self, corners: &[Point3; 3], hints: [usize; 3]) -> f64 {
476 let gap = |lo: Point3, hi: Point3| {
477 corners
478 .iter()
479 .map(|&v| box_gap(v, lo, hi))
480 .fold(0.0, f64::max)
481 };
482 let mut best = hints
483 .iter()
484 .map(|&hint| self.worst(corners, &[hint]))
485 .fold(f64::INFINITY, f64::min);
486 let mut seen = Vec::new();
487 let root = &self.nodes[0];
488 let mut stack = vec![(0usize, gap(root.lo, root.hi))];
489 while let Some((node, lower)) = stack.pop() {
490 if lower >= best {
491 continue;
492 }
493 match self.nodes[node].kind {
494 NodeKind::Leaf { start, end } => {
495 for &index in &self.order[start..end] {
496 let facet = &self.facets[index];
497 if gap(facet.lo, facet.hi) < best {
498 best = best.min(self.worst(corners, &[index]));
499 }
500 for &id in &self.patches_of[index] {
503 let (lo, hi) = self.patch_boxes[id];
504 if seen.contains(&id) || gap(lo, hi) >= best {
505 continue;
506 }
507 seen.push(id);
508 let patch = &self.patches[id];
509 best = best.min(self.worst(corners, &patch.members) + patch.excess);
510 }
511 }
512 }
513 NodeKind::Branch { left, right } => {
514 self.push_children(&mut stack, left, right, |n| gap(n.lo, n.hi));
515 }
516 }
517 }
518 best
519 }
520
521 fn worst(&self, corners: &[Point3; 3], members: &[usize]) -> f64 {
523 corners
524 .iter()
525 .map(|&v| {
526 members
527 .iter()
528 .map(|&member| {
529 let facet = &self.facets[member];
530 upper_at(v, nearest_on(v, facet), facet)
531 })
532 .fold(f64::INFINITY, f64::min)
533 })
534 .fold(0.0, f64::max)
535 }
536
537 fn push_children(
539 &self,
540 stack: &mut Vec<(usize, f64)>,
541 left: usize,
542 right: usize,
543 gap: impl Fn(&Node) -> f64,
544 ) {
545 let (gl, gr) = (gap(&self.nodes[left]), gap(&self.nodes[right]));
546 if gl <= gr {
547 stack.push((right, gr));
548 stack.push((left, gl));
549 } else {
550 stack.push((left, gl));
551 stack.push((right, gr));
552 }
553 }
554}
555
556fn centroid(corners: &[Point3; 3]) -> Point3 {
557 (corners[0] + corners[1] + corners[2]) / 3.0
558}
559
560struct Nearest {
562 lower: f64,
564 point: Point3,
566 facet: usize,
567}
568
569fn box_gap(v: Point3, lo: Point3, hi: Point3) -> f64 {
571 let gap = (lo - v).max(v - hi).max(Point3::ZERO);
572 gap.length() * (1.0 - 4.0 * EPS)
573}
574
575fn upper_at(v: Point3, p: Point3, facet: &Facet) -> f64 {
577 (v - p).length() + 16.0 * EPS * (v.length() + facet.scale)
578}
579
580fn lower_at(v: Point3, p: Point3, facet: &Facet) -> f64 {
583 let w = v - p;
584 let length = w.length();
585 if length <= 0.0 {
586 return 0.0;
587 }
588 let u = w * ((1.0 - 4.0 * EPS) / length);
589 let support = facet
590 .corners
591 .iter()
592 .map(|&corner| u.dot(v - corner))
593 .fold(f64::INFINITY, f64::min);
594 (support - 4.0 * EPS * (v.length() + facet.reach)).max(0.0)
595}
596
597fn nearest_on(v: Point3, facet: &Facet) -> Point3 {
600 let [a, b, c] = facet.corners;
601 let (ab, ac) = (b - a, c - a);
602 let (s, t) = if facet.thin {
603 thin_params(v, a, b, c)
604 } else {
605 let (s, t) = region_params(v, a, b, c);
606 if s.is_finite() && t.is_finite() {
607 (s, t)
608 } else {
609 thin_params(v, a, b, c)
610 }
611 };
612 let (mut s, mut t) = (s.clamp(0.0, 1.0), t.clamp(0.0, 1.0));
613 let sum = s + t;
614 if sum > 1.0 {
615 s /= sum;
616 t /= sum;
617 }
618 a + ab * s + ac * t
619}
620
621fn region_params(v: Point3, a: Point3, b: Point3, c: Point3) -> (f64, f64) {
623 let (ab, ac) = (b - a, c - a);
624 let ap = v - a;
625 let (d1, d2) = (ab.dot(ap), ac.dot(ap));
626 if d1 <= 0.0 && d2 <= 0.0 {
627 return (0.0, 0.0);
628 }
629 let bp = v - b;
630 let (d3, d4) = (ab.dot(bp), ac.dot(bp));
631 if d3 >= 0.0 && d4 <= d3 {
632 return (1.0, 0.0);
633 }
634 let vc = d1 * d4 - d3 * d2;
635 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
636 return (d1 / (d1 - d3), 0.0);
637 }
638 let cp = v - c;
639 let (d5, d6) = (ab.dot(cp), ac.dot(cp));
640 if d6 >= 0.0 && d5 <= d6 {
641 return (0.0, 1.0);
642 }
643 let vb = d5 * d2 - d1 * d6;
644 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
645 return (0.0, d2 / (d2 - d6));
646 }
647 let va = d3 * d6 - d5 * d4;
648 if va <= 0.0 && d4 - d3 >= 0.0 && d5 - d6 >= 0.0 {
649 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
650 return (1.0 - w, w);
651 }
652 let denominator = va + vb + vc;
653 (vb / denominator, vc / denominator)
654}
655
656fn thin_params(v: Point3, a: Point3, b: Point3, c: Point3) -> (f64, f64) {
658 let along = |from: Point3, to: Point3| {
659 let edge = to - from;
660 let length = edge.length_squared();
661 if length > 0.0 {
662 ((v - from).dot(edge) / length).clamp(0.0, 1.0)
663 } else {
664 0.0
665 }
666 };
667 let (x, y, z) = (along(a, b), along(a, c), along(b, c));
668 let candidates = [(x, 0.0), (0.0, y), (1.0 - z, z)];
669 let point = |(s, t): (f64, f64)| a + (b - a) * s + (c - a) * t;
670 candidates
671 .into_iter()
672 .min_by(|&p, &q| (v - point(p)).length().total_cmp(&(v - point(q)).length()))
673 .unwrap_or((0.0, 0.0))
674}
675
676struct Piece {
679 corners: [Point3; 3],
680 drift: [f64; 3],
681 hints: [usize; 3],
682 upper: f64,
683}
684
685impl PartialEq for Piece {
686 fn eq(&self, other: &Self) -> bool {
687 self.cmp(other) == Ordering::Equal
688 }
689}
690
691impl Eq for Piece {}
692
693impl PartialOrd for Piece {
694 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
695 Some(self.cmp(other))
696 }
697}
698
699impl Ord for Piece {
700 fn cmp(&self, other: &Self) -> Ordering {
701 self.upper.total_cmp(&other.upper)
702 }
703}
704
705fn search(source: &Surface, target: &Target, accuracy: f64) -> HausdorffBounds {
706 let first = source.triangles[0][0];
707 let mut best = HausdorffBounds {
708 lower: 0.0,
709 upper: f64::INFINITY,
710 point_from: first,
711 point_to: target.point(first).point,
712 };
713 let sample = |v: Point3, drift: f64, best: &mut HausdorffBounds| {
715 let nearest = target.point(v);
716 let lower = nearest.lower - drift;
717 if lower > best.lower {
718 best.lower = lower;
719 best.point_from = v;
720 best.point_to = nearest.point;
721 }
722 nearest.facet
723 };
724 let hints: Vec<usize> = (0..source.positions.len())
725 .map(|index| {
726 if source.used[index] {
727 sample(source.positions[index], 0.0, &mut best)
728 } else {
729 0
730 }
731 })
732 .collect();
733
734 let make = |corners: [Point3; 3], drift: [f64; 3], hints: [usize; 3]| {
735 let upper = target.piece(&corners, hints) + drift.iter().copied().fold(0.0, f64::max);
736 Piece {
737 corners,
738 drift,
739 hints,
740 upper,
741 }
742 };
743 let mut queue: BinaryHeap<Piece> = source
744 .triangles
745 .iter()
746 .zip(&source.indices)
747 .map(|(&corners, ids)| make(corners, [0.0; 3], ids.map(|id| hints[id])))
748 .collect();
749
750 let mut settled = 0.0_f64;
752 let mut splits = 0;
753 loop {
754 let open = queue.peek().map_or(0.0, |piece| piece.upper);
755 let upper = open.max(settled).max(best.lower);
756 if upper - best.lower <= accuracy || splits >= MAX_SPLITS {
757 best.upper = upper;
758 return best;
759 }
760 let Some(piece) = queue.pop() else {
761 best.upper = upper;
762 return best;
763 };
764 if piece.upper <= best.lower {
765 continue;
766 }
767 let [p0, p1, p2] = piece.corners;
768 let lengths = [
769 (p1 - p0).length_squared(),
770 (p2 - p1).length_squared(),
771 (p0 - p2).length_squared(),
772 ];
773 let edge = if lengths[0] >= lengths[1] && lengths[0] >= lengths[2] {
774 0
775 } else if lengths[1] >= lengths[2] {
776 1
777 } else {
778 2
779 };
780 let (i, j, k) = (edge, (edge + 1) % 3, (edge + 2) % 3);
781 let (vi, vj, vk) = (piece.corners[i], piece.corners[j], piece.corners[k]);
782 let size = vi.length().max(vj.length()).max(vk.length());
783 if lengths[edge].sqrt() <= 1024.0 * EPS * size {
784 settled = settled.max(piece.upper);
785 continue;
786 }
787 splits += 1;
788 let m = (vi + vj) * 0.5;
789 let dm = piece.drift[i].max(piece.drift[j]) + EPS * m.length();
790 let hm = sample(m, dm, &mut best);
791 let [di, dj, dk] = [piece.drift[i], piece.drift[j], piece.drift[k]];
792 let [hi, hj, hk] = [piece.hints[i], piece.hints[j], piece.hints[k]];
793 for (corners, drift, hints) in [
794 ([vi, m, vk], [di, dm, dk], [hi, hm, hk]),
795 ([m, vj, vk], [dm, dj, dk], [hm, hj, hk]),
796 ] {
797 let child = make(corners, drift, hints);
798 if child.upper > best.lower {
799 queue.push(child);
800 }
801 }
802 }
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808
809 fn facet(corners: [Point3; 3]) -> Facet {
810 let [a, b, c] = corners;
811 Facet {
812 corners,
813 scale: a.length() + b.length() + c.length(),
814 reach: a.length().max(b.length()).max(c.length()),
815 thin: false,
816 lo: a.min(b).min(c),
817 hi: a.max(b).max(c),
818 }
819 }
820
821 #[test]
822 fn point_bounds_bracket_the_distance_to_a_triangle() {
823 let t = facet([
824 Point3::new(0.0, 0.0, 0.0),
825 Point3::new(2.0, 0.0, 0.0),
826 Point3::new(0.0, 2.0, 0.0),
827 ]);
828 for (v, d) in [
830 (Point3::new(0.5, 0.5, 3.0), 3.0),
831 (Point3::new(1.0, -4.0, 3.0), 5.0),
832 (Point3::new(-3.0, -4.0, 0.0), 5.0),
833 (Point3::new(0.25, 0.25, 0.0), 0.0),
834 ] {
835 let p = nearest_on(v, &t);
836 let (lo, hi) = (lower_at(v, p, &t), upper_at(v, p, &t));
837 assert!(lo <= d && d <= hi, "{lo} <= {d} <= {hi}");
838 assert!(hi - lo < 1e-13, "{lo} {hi}");
839 }
840 }
841
842 #[test]
843 fn a_thin_triangle_is_measured_along_its_edges() {
844 let t = Facet {
845 thin: true,
846 ..facet([
847 Point3::new(0.0, 0.0, 0.0),
848 Point3::new(1.0, 0.0, 0.0),
849 Point3::new(2.0, 0.0, 0.0),
850 ])
851 };
852 let v = Point3::new(1.5, 2.0, 0.0);
853 let p = nearest_on(v, &t);
854 assert!((lower_at(v, p, &t) - 2.0).abs() < 1e-13);
855 assert!((upper_at(v, p, &t) - 2.0).abs() < 1e-13);
856 }
857
858 #[test]
859 fn box_gaps_never_exceed_the_distance_to_the_box() {
860 let (lo, hi) = (Point3::ZERO, Point3::ONE);
861 assert!(box_gap(Point3::new(4.0, 5.0, 0.5), lo, hi) <= 5.0);
862 assert!(box_gap(Point3::new(4.0, 5.0, 0.5), lo, hi) > 5.0 - 1e-14);
863 assert_eq!(box_gap(Point3::new(0.5, 0.5, 0.5), lo, hi), 0.0);
864 }
865
866 #[test]
867 fn a_folded_pair_bounds_a_piece_across_its_hull() {
868 let h = 0.3;
875 let (a, b) = (Point3::new(0.0, -1.0, 0.0), Point3::new(0.0, 1.0, 0.0));
876 let (c, d) = (Point3::new(-1.0, 0.0, h), Point3::new(1.0, 0.0, h));
877 let surface = Surface {
878 triangles: vec![[a, b, c], [a, b, d]],
879 indices: vec![[0, 1, 2], [0, 1, 3]],
880 positions: vec![a, b, c, d],
881 used: vec![true; 4],
882 };
883 let target = Target::new(surface, 100.0);
884 assert_eq!(
885 target.patches.len(),
886 1,
887 "the fold is a flat-enough pair here"
888 );
889 let piece = [c, d, (b + c) * 0.5];
892 let bound = target.piece(&piece, [0, 1, 0]);
893 let middle = (c + d) * 0.5;
895 let truth = [0, 1]
896 .map(|i| (middle - nearest_on(middle, &target.facets[i])).length())
897 .into_iter()
898 .fold(f64::INFINITY, f64::min);
899 assert!((truth - h / (1.0 + h * h).sqrt()).abs() < 1e-12);
900 assert!(bound >= truth, "bound {bound} below {truth}");
901 }
902}