1use std::cell::Cell;
10use std::sync::Mutex;
11use std::sync::atomic::{AtomicUsize, Ordering};
12
13use fast_mesh_bvh::{Aabb, Bvh, NodeId, NodeKind, NodeView};
14use fast_mesh_store::{Mesh, TriangleId, par};
15
16use crate::pair::{
17 PAIR_STACK, PAR_PARTS, Step, oom, split_pairs, traverse_pairs_with,
18 traverse_subtree_pairs_with, try_push,
19};
20use crate::ray::points;
21use crate::tritri::{P3, TriTriDist, tri_tri_distance, tri_tri_intersect, tri_tri_within};
22use crate::{Appender, QueryError, Result, Rigid, check};
23
24#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct MeshDistance {
33 pub distance: f32,
37 pub point_a: [f32; 3],
39 pub point_b: [f32; 3],
42 pub triangle_a: TriangleId,
44 pub triangle_b: TriangleId,
46}
47
48pub fn intersects(
66 mesh_a: &Mesh,
67 bvh_a: &Bvh,
68 mesh_b: &Mesh,
69 bvh_b: &Bvh,
70 xf: &Rigid,
71) -> Result<bool> {
72 validate(mesh_a, bvh_a, mesh_b, bvh_b, xf, None)?;
73 Ok(intersects_with::<PAIR_STACK>(
74 mesh_a, bvh_a, mesh_b, bvh_b, xf,
75 ))
76}
77
78pub fn mesh_distance(
104 mesh_a: &Mesh,
105 bvh_a: &Bvh,
106 mesh_b: &Mesh,
107 bvh_b: &Bvh,
108 xf: &Rigid,
109 max_distance: f32,
110) -> Result<Option<MeshDistance>> {
111 validate(
112 mesh_a,
113 bvh_a,
114 mesh_b,
115 bvh_b,
116 xf,
117 Some((max_distance, "max_distance must not be negative or NaN")),
118 )?;
119 Ok(mesh_distance_with::<PAIR_STACK>(
120 mesh_a,
121 bvh_a,
122 mesh_b,
123 bvh_b,
124 xf,
125 max_distance,
126 ))
127}
128
129#[allow(clippy::too_many_arguments)] pub fn triangles_near(
161 mesh_a: &Mesh,
162 bvh_a: &Bvh,
163 mesh_b: &Mesh,
164 bvh_b: &Bvh,
165 xf: &Rigid,
166 margin: f32,
167 out_a: &mut Vec<TriangleId>,
168 out_b: &mut Vec<TriangleId>,
169) -> Result<()> {
170 validate(
171 mesh_a,
172 bvh_a,
173 mesh_b,
174 bvh_b,
175 xf,
176 Some((margin, "margin must not be negative or NaN")),
177 )?;
178 triangles_near_with::<PAIR_STACK>(mesh_a, bvh_a, mesh_b, bvh_b, xf, margin, out_a, out_b)
179}
180
181#[doc(hidden)]
196#[allow(clippy::too_many_arguments)] pub fn triangles_near_limited(
198 mesh_a: &Mesh,
199 bvh_a: &Bvh,
200 mesh_b: &Mesh,
201 bvh_b: &Bvh,
202 xf: &Rigid,
203 margin: f32,
204 limit: usize,
205 out_a: &mut Vec<TriangleId>,
206) -> Result<bool> {
207 validate(
208 mesh_a,
209 bvh_a,
210 mesh_b,
211 bvh_b,
212 xf,
213 Some((margin, "margin must not be negative or NaN")),
214 )?;
215 triangles_near_limited_with::<PAIR_STACK>(
216 mesh_a, bvh_a, mesh_b, bvh_b, xf, margin, limit, out_a, None,
217 )
218}
219
220pub fn intersecting_pairs(
233 mesh_a: &Mesh,
234 bvh_a: &Bvh,
235 mesh_b: &Mesh,
236 bvh_b: &Bvh,
237 xf: &Rigid,
238 out: &mut Vec<(TriangleId, TriangleId)>,
239) -> Result<usize> {
240 validate(mesh_a, bvh_a, mesh_b, bvh_b, xf, None)?;
241 intersecting_pairs_with::<PAIR_STACK>(mesh_a, bvh_a, mesh_b, bvh_b, xf, out)
242}
243
244pub(crate) fn validate(
247 mesh_a: &Mesh,
248 bvh_a: &Bvh,
249 mesh_b: &Mesh,
250 bvh_b: &Bvh,
251 xf: &Rigid,
252 bound: Option<(f32, &'static str)>,
253) -> Result<()> {
254 xf.validate()?;
255 if let Some((v, msg)) = bound
256 && (v.is_nan() || v < 0.0)
257 {
258 return Err(QueryError::InvalidInput(msg));
259 }
260 check(mesh_a, bvh_a)?;
261 check(mesh_b, bvh_b)
262}
263
264const PRUNE_SLACK: f64 = 1.0 / (1u64 << 30) as f64;
266
267fn magnitude(bvh_a: &Bvh, bvh_b: &Bvh, xf: &Rigid) -> f64 {
273 let (Some(ra), Some(rb)) = (bvh_a.root(), bvh_b.root()) else {
274 return 0.0;
275 };
276 let boxes = [
277 bvh_a.node(ra).bounds,
278 xf.transform_box(&bvh_b.node(rb).bounds),
279 ];
280 boxes
281 .iter()
282 .flat_map(|b| b.min.iter().chain(&b.max))
283 .map(|v| f64::from(v.abs()))
284 .fold(0.0, f64::max)
285}
286
287fn beyond(box_d: f64, bound: f64, magnitude: f64) -> bool {
302 box_d > bound + PRUNE_SLACK * (magnitude + bound)
303}
304
305fn box_distance(p: &Aabb, q: &Aabb) -> f64 {
310 if p.is_empty() || q.is_empty() {
311 return f64::INFINITY;
312 }
313 (0..3)
314 .map(|k| {
315 let gap = (f64::from(p.min[k]) - f64::from(q.max[k]))
316 .max(f64::from(q.min[k]) - f64::from(p.max[k]))
317 .max(0.0);
318 gap * gap
319 })
320 .sum::<f64>()
321 .sqrt()
322}
323
324const PLANE_GUARD: f64 = 1e-4;
337
338#[cfg(test)]
350fn pair_lower_bound(a: &[P3; 3], b: &[P3; 3]) -> f64 {
351 let (a, b) = (Tri::new(*a), Tri::new(*b));
352 a.box_gap(&b.lo, &b.hi)
353 .max(a.plane_gap(&b.p))
354 .max(b.plane_gap(&a.p))
355}
356
357fn skip_pair(a: &Tri, b: &Tri, bound: f64, m: f64) -> bool {
367 beyond(a.box_gap(&b.lo, &b.hi), bound, m)
368 || beyond(a.plane_gap(&b.p), bound, m)
369 || beyond(b.plane_gap(&a.p), bound, m)
370}
371
372#[derive(Debug, Clone, Copy)]
377struct Tri {
378 p: [P3; 3],
380 lo: P3,
382 hi: P3,
384 slab: Option<Slab>,
387}
388
389#[derive(Debug, Clone, Copy)]
394struct Slab {
395 n: P3,
397 len: f64,
399 lo: f64,
401 hi: f64,
403}
404
405fn sub(p: P3, q: P3) -> P3 {
407 [p[0] - q[0], p[1] - q[1], p[2] - q[2]]
408}
409
410fn dot(p: P3, q: P3) -> f64 {
412 p[0] * q[0] + p[1] * q[1] + p[2] * q[2]
413}
414
415impl Tri {
416 const UNUSED: Tri = Tri {
418 p: [[0.0; 3]; 3],
419 lo: [0.0; 3],
420 hi: [0.0; 3],
421 slab: None,
422 };
423
424 fn new(p: [P3; 3]) -> Tri {
426 let lo = std::array::from_fn(|k| p[0][k].min(p[1][k]).min(p[2][k]));
427 let hi = std::array::from_fn(|k| p[0][k].max(p[1][k]).max(p[2][k]));
428 let (u, v) = (sub(p[1], p[0]), sub(p[2], p[0]));
429 let n = [
430 u[1] * v[2] - u[2] * v[1],
431 u[2] * v[0] - u[0] * v[2],
432 u[0] * v[1] - u[1] * v[0],
433 ];
434 let nn = dot(n, n);
435 let slab = (nn > 0.0 && nn >= PLANE_GUARD * dot(u, u) * dot(v, v)).then(|| {
437 let (hu, hv) = (dot(n, u), dot(n, v));
438 Slab {
439 n,
440 len: nn.sqrt(),
441 lo: hu.min(hv).min(0.0),
442 hi: hu.max(hv).max(0.0),
443 }
444 });
445 Tri { p, lo, hi, slab }
446 }
447
448 fn box_gap(&self, lo: &P3, hi: &P3) -> f64 {
457 (0..3)
458 .map(|k| {
459 let gap = (self.lo[k] - hi[k]).max(lo[k] - self.hi[k]).max(0.0);
460 gap * gap
461 })
462 .sum::<f64>()
463 .sqrt()
464 }
465
466 fn plane_gap(&self, o: &[P3; 3]) -> f64 {
480 let Some(s) = &self.slab else {
481 return 0.0;
482 };
483 let h = o.map(|p| dot(s.n, sub(p, self.p[0])));
484 let o_lo = h[0].min(h[1]).min(h[2]);
485 let o_hi = h[0].max(h[1]).max(h[2]);
486 let gap = (o_lo - s.hi).max(s.lo - o_hi);
487 if gap > 0.0 { gap / s.len } else { 0.0 }
488 }
489}
490
491const CHUNK: usize = 16;
494
495struct Chunk {
502 from: Option<(*const TriangleId, usize)>,
506 tris: [Tri; CHUNK],
508 len: usize,
510 lo: P3,
512 hi: P3,
514}
515
516impl Chunk {
517 fn new() -> Chunk {
519 Chunk {
520 from: None,
521 tris: [Tri::UNUSED; CHUNK],
522 len: 0,
523 lo: [f64::INFINITY; 3],
524 hi: [f64::NEG_INFINITY; 3],
525 }
526 }
527
528 fn fill(&mut self, ids: &[TriangleId], place: impl Fn(TriangleId) -> [P3; 3]) {
531 debug_assert!(ids.len() <= CHUNK);
533 let from = Some((ids.as_ptr(), ids.len()));
534 if self.from == from {
535 return;
536 }
537 self.from = from;
538 self.len = ids.len();
539 self.lo = [f64::INFINITY; 3];
540 self.hi = [f64::NEG_INFINITY; 3];
541 for (slot, &t) in self.tris.iter_mut().zip(ids) {
542 *slot = Tri::new(place(t));
543 for k in 0..3 {
544 self.lo[k] = self.lo[k].min(slot.lo[k]);
545 self.hi[k] = self.hi[k].max(slot.hi[k]);
546 }
547 }
548 }
549
550 fn tris(&self) -> &[Tri] {
552 &self.tris[..self.len]
553 }
554}
555
556pub(crate) fn overlap_key(p: &Aabb, q: &Aabb) -> Option<f64> {
559 p.overlaps(q).then_some(0.0)
560}
561
562const TIE_WEIGHT: f64 = 1e-12;
565
566fn distance_key(p: &Aabb, q: &Aabb, bound: f64, m: f64) -> Option<(f64, f64)> {
585 let gap = box_distance(p, q);
586 if beyond(gap, bound, m) {
587 return None;
588 }
589 let (cp, cq) = (p.center(), q.center());
590 let centres = (0..3)
591 .map(|k| {
592 let d = f64::from(cp[k]) - f64::from(cq[k]);
593 d * d
594 })
595 .sum::<f64>()
596 .sqrt();
597 Some((gap + TIE_WEIGHT * centres, gap))
598}
599
600fn leaf<'a>(view: &NodeView<'a>) -> Option<&'a [TriangleId]> {
602 match view.kind {
603 NodeKind::Leaf { triangles } => Some(triangles),
604 NodeKind::Inner { .. } => None,
605 }
606}
607
608fn tri_a(mesh: &Mesh, t: TriangleId) -> [P3; 3] {
610 points(mesh, t).map(|p| p.map(f64::from))
611}
612
613fn tri_b(mesh: &Mesh, xf: &Rigid, t: TriangleId) -> [P3; 3] {
616 points(mesh, t).map(|p| xf.apply(p))
617}
618
619#[cfg(test)]
620thread_local! {
621 static LEAF_VISITS: Cell<usize> = const { Cell::new(0) };
627 static PAIR_SKIPS: Cell<usize> = const { Cell::new(0) };
631}
632
633fn count_skips(n: usize) {
638 #[cfg(test)]
639 PAIR_SKIPS.with(|c| c.set(c.get() + n));
640 #[cfg(not(test))]
641 let _ = n;
642}
643
644fn boxes_apart(lo: &P3, hi: &P3, lo2: &P3, hi2: &P3) -> bool {
648 (0..3).any(|k| lo[k] > hi2[k] || lo2[k] > hi[k])
649}
650
651fn traverse_from<const N: usize, K: Copy + PartialOrd>(
654 a: &Bvh,
655 b: &Bvh,
656 xf: &Rigid,
657 from: Option<(NodeId, NodeId)>,
658 key: impl FnMut(&Aabb, &Aabb) -> Option<K>,
659 visit: impl FnMut(&NodeView, &NodeView, K) -> Step,
660) {
661 match from {
662 Some((ra, rb)) => traverse_subtree_pairs_with::<N, K>(a, ra, b, rb, xf, key, visit),
663 None => traverse_pairs_with::<N, K>(a, b, xf, key, visit),
664 }
665}
666
667#[allow(clippy::too_many_arguments)] pub(crate) fn for_each_pair<const N: usize>(
686 mesh_a: &Mesh,
687 bvh_a: &Bvh,
688 mesh_b: &Mesh,
689 bvh_b: &Bvh,
690 xf: &Rigid,
691 from: Option<(NodeId, NodeId)>,
692 key: impl FnMut(&Aabb, &Aabb) -> Option<f64>,
693 mut pair: impl FnMut(TriangleId, [P3; 3], TriangleId, [P3; 3]) -> bool,
694) {
695 let (mut ca, mut cb) = (Chunk::new(), Chunk::new());
696 traverse_from::<N, f64>(bvh_a, bvh_b, xf, from, key, |va, vb, _| {
697 let (Some(ta), Some(tb)) = (leaf(va), leaf(vb)) else {
698 return Step::Descend;
699 };
700 #[cfg(test)]
701 LEAF_VISITS.with(|c| c.set(c.get() + 1));
702 for ids_a in ta.chunks(CHUNK) {
703 ca.fill(ids_a, |a| tri_a(mesh_a, a));
704 for ids_b in tb.chunks(CHUNK) {
705 cb.fill(ids_b, |b| tri_b(mesh_b, xf, b));
706 for (&b, tb) in ids_b.iter().zip(cb.tris()) {
707 if boxes_apart(&tb.lo, &tb.hi, &ca.lo, &ca.hi) {
708 count_skips(ids_a.len());
709 continue;
710 }
711 for (&a, ta) in ids_a.iter().zip(ca.tris()) {
712 if boxes_apart(&ta.lo, &ta.hi, &tb.lo, &tb.hi) {
713 count_skips(1);
714 continue;
715 }
716 if !pair(a, ta.p, b, tb.p) {
717 return Step::Stop;
718 }
719 }
720 }
721 }
722 }
723 Step::Skip
724 });
725}
726
727#[allow(clippy::too_many_arguments)] fn for_each_near_pair<const N: usize, K: Copy + PartialOrd>(
748 mesh_a: &Mesh,
749 bvh_a: &Bvh,
750 mesh_b: &Mesh,
751 bvh_b: &Bvh,
752 xf: &Rigid,
753 from: Option<(NodeId, NodeId)>,
754 key: impl FnMut(&Aabb, &Aabb) -> Option<K>,
755 gap: impl Fn(K) -> f64,
756 bound: impl Fn() -> f64,
757 m: f64,
758 mut pair: impl FnMut(TriangleId, [P3; 3], TriangleId, [P3; 3]) -> bool,
759) {
760 let (mut ca, mut cb) = (Chunk::new(), Chunk::new());
761 traverse_from::<N, K>(bvh_a, bvh_b, xf, from, key, |va, vb, k| {
762 if beyond(gap(k), bound(), m) {
763 return Step::Skip;
764 }
765 let (Some(ta), Some(tb)) = (leaf(va), leaf(vb)) else {
766 return Step::Descend;
767 };
768 #[cfg(test)]
769 LEAF_VISITS.with(|c| c.set(c.get() + 1));
770 for ids_a in ta.chunks(CHUNK) {
771 ca.fill(ids_a, |a| tri_a(mesh_a, a));
772 for ids_b in tb.chunks(CHUNK) {
773 cb.fill(ids_b, |b| tri_b(mesh_b, xf, b));
774 if !chunk_pairs(ids_a, &ca, ids_b, &cb, &bound, m, &mut pair) {
775 return Step::Stop;
776 }
777 }
778 }
779 Step::Skip
780 });
781}
782
783fn chunk_pairs(
786 ids_a: &[TriangleId],
787 ca: &Chunk,
788 ids_b: &[TriangleId],
789 cb: &Chunk,
790 bound: &impl Fn() -> f64,
791 m: f64,
792 pair: &mut impl FnMut(TriangleId, [P3; 3], TriangleId, [P3; 3]) -> bool,
793) -> bool {
794 let mut gap_a = [0.0; CHUNK];
797 for (g, t) in gap_a.iter_mut().zip(ca.tris()) {
798 *g = t.box_gap(&cb.lo, &cb.hi);
799 }
800 for (&b, tb) in ids_b.iter().zip(cb.tris()) {
801 if beyond(tb.box_gap(&ca.lo, &ca.hi), bound(), m) {
802 count_skips(ids_a.len());
803 continue;
804 }
805 for ((&a, ta), &g) in ids_a.iter().zip(ca.tris()).zip(&gap_a) {
806 let bound = bound();
807 if beyond(g, bound, m) || skip_pair(ta, tb, bound, m) {
808 count_skips(1);
809 continue;
810 }
811 if !pair(a, ta.p, b, tb.p) {
812 return false;
813 }
814 }
815 }
816 true
817}
818
819fn first_intersection<const N: usize>(
821 mesh_a: &Mesh,
822 bvh_a: &Bvh,
823 mesh_b: &Mesh,
824 bvh_b: &Bvh,
825 xf: &Rigid,
826) -> Option<(TriangleId, TriangleId)> {
827 let mut found = None;
828 for_each_pair::<N>(
829 mesh_a,
830 bvh_a,
831 mesh_b,
832 bvh_b,
833 xf,
834 None,
835 overlap_key,
836 |a, pa, b, pb| {
837 if tri_tri_intersect(pa, pb) {
838 found = Some((a, b));
839 }
840 found.is_none()
841 },
842 );
843 found
844}
845
846fn intersects_with<const N: usize>(
847 mesh_a: &Mesh,
848 bvh_a: &Bvh,
849 mesh_b: &Mesh,
850 bvh_b: &Bvh,
851 xf: &Rigid,
852) -> bool {
853 first_intersection::<N>(mesh_a, bvh_a, mesh_b, bvh_b, xf).is_some()
854}
855
856fn mesh_distance_with<const N: usize>(
857 mesh_a: &Mesh,
858 bvh_a: &Bvh,
859 mesh_b: &Mesh,
860 bvh_b: &Bvh,
861 xf: &Rigid,
862 max_distance: f32,
863) -> Option<MeshDistance> {
864 if let Some((a, b)) = first_intersection::<N>(mesh_a, bvh_a, mesh_b, bvh_b, xf) {
875 let d = tri_tri_distance(tri_a(mesh_a, a), tri_b(mesh_b, xf, b));
876 return Some(MeshDistance {
877 distance: 0.0,
878 point_a: d.pa.map(|v| v as f32),
879 point_b: d.pb.map(|v| v as f32),
880 triangle_a: a,
881 triangle_b: b,
882 });
883 }
884 let bound = Cell::new(f64::from(max_distance));
897 let m = magnitude(bvh_a, bvh_b, xf);
898 let mut best: Option<(TriangleId, TriangleId, TriTriDist)> = None;
899 for_each_near_pair::<N, (f64, f64)>(
900 mesh_a,
901 bvh_a,
902 mesh_b,
903 bvh_b,
904 xf,
905 None,
906 |p, q| distance_key(p, q, bound.get(), m),
907 |(_, gap)| gap,
908 || bound.get(),
909 m,
910 |a, pa, b, pb| {
914 let d = tri_tri_distance(pa, pb);
915 let dist = d.dist2.sqrt();
916 if dist > bound.get() {
917 return true;
918 }
919 let better = best.is_none_or(|(ba, bb, bd)| {
920 d.dist2 < bd.dist2 || (d.dist2 == bd.dist2 && (a, b) < (ba, bb))
921 });
922 if better {
923 best = Some((a, b, d));
924 bound.set(dist);
925 }
926 d.dist2 != 0.0
928 },
929 );
930 best.map(|(a, b, d)| MeshDistance {
931 distance: d.dist2.sqrt() as f32,
932 point_a: d.pa.map(|v| v as f32),
933 point_b: d.pb.map(|v| v as f32),
934 triangle_a: a,
935 triangle_b: b,
936 })
937}
938
939const PAR_MIN_PAIR_TRIS: usize = 2048;
948
949fn split_traversal(mesh_a: &Mesh, mesh_b: &Mesh) -> bool {
952 par::enabled()
953 && !par::force_sequential()
954 && mesh_a.triangle_count() + mesh_b.triangle_count() >= PAR_MIN_PAIR_TRIS
955}
956
957#[allow(clippy::too_many_arguments)] fn triangles_near_with<const N: usize>(
959 mesh_a: &Mesh,
960 bvh_a: &Bvh,
961 mesh_b: &Mesh,
962 bvh_b: &Bvh,
963 xf: &Rigid,
964 margin: f32,
965 out_a: &mut Vec<TriangleId>,
966 out_b: &mut Vec<TriangleId>,
967) -> Result<()> {
968 let done = triangles_near_limited_with::<N>(
969 mesh_a,
970 bvh_a,
971 mesh_b,
972 bvh_b,
973 xf,
974 margin,
975 usize::MAX,
976 out_a,
977 Some(out_b),
978 )?;
979 debug_assert!(done, "no limit to pass");
980 Ok(())
981}
982
983#[allow(clippy::too_many_arguments)] fn triangles_near_limited_with<const N: usize>(
989 mesh_a: &Mesh,
990 bvh_a: &Bvh,
991 mesh_b: &Mesh,
992 bvh_b: &Bvh,
993 xf: &Rigid,
994 margin: f32,
995 limit: usize,
996 out_a: &mut Vec<TriangleId>,
997 out_b: Option<&mut Vec<TriangleId>>,
998) -> Result<bool> {
999 let margin = f64::from(margin);
1000 let m = magnitude(bvh_a, bvh_b, xf);
1001 let key = |p: &Aabb, q: &Aabb| {
1002 let d = box_distance(p, q);
1003 (!beyond(d, margin, m)).then_some(d)
1004 };
1005 let found_pairs = AtomicUsize::new(0);
1007 let within_limit = || -> std::result::Result<(), Stop> {
1008 if limit != usize::MAX && found_pairs.fetch_add(1, Ordering::Relaxed) >= limit {
1009 return Err(Stop::Limit);
1010 }
1011 Ok(())
1012 };
1013 let failure: Mutex<Option<QueryError>> = Mutex::new(None);
1016 let record = |e: QueryError| {
1017 let mut f = failure.lock().unwrap_or_else(|p| p.into_inner());
1018 f.get_or_insert(e);
1019 };
1020 let search = |from: Option<(NodeId, NodeId)>,
1023 found: &mut dyn FnMut(TriangleId, TriangleId) -> Result<()>|
1024 -> std::result::Result<(), Stop> {
1025 let mut stopped = None;
1026 for_each_near_pair::<N, f64>(
1027 mesh_a,
1028 bvh_a,
1029 mesh_b,
1030 bvh_b,
1031 xf,
1032 from,
1033 key,
1034 |gap| gap,
1035 || margin,
1036 m,
1037 |a, pa, b, pb| {
1038 if tri_tri_within(pa, pb, margin)
1042 && let Err(e) = within_limit().and_then(|()| found(a, b).map_err(Stop::Failed))
1043 {
1044 if let Stop::Failed(e) = &e {
1045 record(*e);
1046 }
1047 stopped = Some(e);
1048 return false;
1049 }
1050 true
1051 },
1052 );
1053 stopped.map_or(Ok(()), Err)
1054 };
1055 let want_b = out_b.is_some();
1056 let mut spare = Vec::new();
1057 let mut ap_a = Appender::new(out_a);
1058 let mut ap_b = Appender::new(out_b.unwrap_or(&mut spare));
1059 let searched: std::result::Result<(), Stop> = if split_traversal(mesh_a, mesh_b) {
1060 split_pairs(bvh_a, bvh_b, xf, &key, PAR_PARTS)
1063 .map_err(Stop::Failed)
1064 .and_then(|parts| {
1065 let found = par::map_chunks(
1066 &parts,
1067 1,
1068 0,
1069 || Stop::Failed(oom()),
1070 |_, part| {
1071 let (mut near_a, mut near_b) = (Vec::new(), Vec::new());
1072 search(Some(part[0]), &mut |a, b| {
1073 try_push(&mut near_a, a)?;
1074 if want_b {
1075 try_push(&mut near_b, b)?;
1076 }
1077 Ok(())
1078 })?;
1079 for v in [&mut near_a, &mut near_b] {
1080 v.sort_unstable();
1081 v.dedup();
1082 }
1083 Ok((near_a, near_b))
1084 },
1085 )?;
1086 for (near_a, near_b) in &found {
1087 ap_a.extend(near_a).map_err(Stop::Failed)?;
1088 ap_b.extend(near_b).map_err(Stop::Failed)?;
1089 }
1090 Ok(())
1091 })
1092 } else {
1093 search(None, &mut |a, b| {
1094 ap_a.push(a)?;
1095 if want_b {
1096 ap_b.push(b)?;
1097 }
1098 Ok(())
1099 })
1100 };
1101 if let Err(stop) = searched {
1102 ap_a.truncate_appended(0);
1104 ap_b.truncate_appended(0);
1105 let failed = failure.into_inner().unwrap_or_else(|p| p.into_inner());
1106 return match (stop, failed) {
1107 (_, Some(e)) | (Stop::Failed(e), None) => Err(e),
1109 (Stop::Limit, None) => Ok(false),
1110 };
1111 }
1112 ap_a.sort_dedup_appended();
1113 ap_b.sort_dedup_appended();
1114 Ok(true)
1115}
1116
1117const NEAR_PART_PAIRS: u64 = 1 << 18;
1122
1123const NEAR_TEST_WORK: usize = 4;
1126
1127const NEAR_PAR_BATCH: usize = 32;
1130
1131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1133struct Stamp {
1134 uid: u64,
1135 epoch: u32,
1136 revision: u64,
1137 generation: u64,
1138 triangle_slots: usize,
1140}
1141
1142impl Stamp {
1143 fn of(mesh: &Mesh, bvh: &Bvh) -> Stamp {
1144 Stamp {
1145 uid: mesh.uid(),
1146 epoch: mesh.epoch(),
1147 revision: mesh.revision(),
1148 generation: bvh.generation(),
1149 triangle_slots: mesh.triangle_slots(),
1150 }
1151 }
1152}
1153
1154#[doc(hidden)]
1176#[derive(Debug)]
1177pub struct NearSearch {
1178 stamps: [Stamp; 2],
1179 xf: Rigid,
1180 margin: f64,
1181 m: f64,
1182 limit: usize,
1183 parts: Vec<(NodeId, NodeId)>,
1185 near: Vec<TriangleId>,
1188 found: usize,
1190 over: bool,
1192 #[cfg(test)]
1194 searched: Vec<(NodeId, NodeId)>,
1195}
1196
1197impl NearSearch {
1198 #[allow(clippy::too_many_arguments)] pub fn new(
1206 mesh_a: &Mesh,
1207 bvh_a: &Bvh,
1208 mesh_b: &Mesh,
1209 bvh_b: &Bvh,
1210 xf: &Rigid,
1211 margin: f32,
1212 limit: usize,
1213 ) -> Result<NearSearch> {
1214 validate(
1215 mesh_a,
1216 bvh_a,
1217 mesh_b,
1218 bvh_b,
1219 xf,
1220 Some((margin, "margin must not be negative or NaN")),
1221 )?;
1222 let margin = f64::from(margin);
1223 let m = magnitude(bvh_a, bvh_b, xf);
1224 let key = |p: &Aabb, q: &Aabb| {
1225 let d = box_distance(p, q);
1226 (!beyond(d, margin, m)).then_some(d)
1227 };
1228 let mut parts = Vec::new();
1229 let roots = split_pairs(bvh_a, bvh_b, xf, &key, 1)?;
1231 parts.try_reserve(roots.len()).map_err(|_| oom())?;
1232 parts.extend(roots);
1233 Ok(NearSearch {
1234 stamps: [Stamp::of(mesh_a, bvh_a), Stamp::of(mesh_b, bvh_b)],
1235 xf: *xf,
1236 margin,
1237 m,
1238 limit,
1239 parts,
1240 near: Vec::new(),
1241 found: 0,
1242 over: false,
1243 #[cfg(test)]
1244 searched: Vec::new(),
1245 })
1246 }
1247
1248 pub fn is_done(&self) -> bool {
1250 self.over || self.parts.is_empty()
1251 }
1252
1253 pub fn step(
1263 &mut self,
1264 mesh_a: &Mesh,
1265 bvh_a: &Bvh,
1266 mesh_b: &Mesh,
1267 bvh_b: &Bvh,
1268 budget: usize,
1269 ) -> Result<bool> {
1270 if self.stamps != [Stamp::of(mesh_a, bvh_a), Stamp::of(mesh_b, bvh_b)] {
1271 return Err(QueryError::InvalidInput(
1272 "the near search started on other meshes or BVHs",
1273 ));
1274 }
1275 check(mesh_a, bvh_a)?;
1276 check(mesh_b, bvh_b)?;
1277 let (margin, m, limit, xf) = (self.margin, self.m, self.limit, self.xf);
1278 let key = |p: &Aabb, q: &Aabb| {
1279 let d = box_distance(p, q);
1280 (!beyond(d, margin, m)).then_some(d)
1281 };
1282 let batch = if par::enabled() && !par::force_sequential() {
1283 NEAR_PAR_BATCH
1284 } else {
1285 1
1286 };
1287 let mut tested = 0usize;
1288 let mut ready = Vec::new();
1289 ready.try_reserve(batch).map_err(|_| oom())?;
1290 while !self.is_done() && tested < budget.max(1) {
1291 ready.clear();
1293 while ready.len() < batch
1294 && let Some((pa, pb)) = self.parts.pop()
1295 {
1296 let (ea, eb) = (
1297 bvh_a.subtree_triangles(pa) as u64,
1298 bvh_b.subtree_triangles(pb) as u64,
1299 );
1300 let inner = |bvh: &Bvh, id: NodeId| match bvh.node(id).kind {
1301 NodeKind::Inner { left, right } => Some([left, right]),
1302 NodeKind::Leaf { .. } => None,
1303 };
1304 let (ka, kb) = (inner(bvh_a, pa), inner(bvh_b, pb));
1305 let side = match (ka, kb) {
1307 _ if ea.saturating_mul(eb) <= NEAR_PART_PAIRS => None,
1308 (Some(c), Some(_)) if ea >= eb => Some((true, c)),
1309 (_, Some(c)) => Some((false, c)),
1310 (Some(c), None) => Some((true, c)),
1311 (None, None) => None,
1312 };
1313 let Some((split_a, children)) = side else {
1314 ready.push((pa, pb));
1315 #[cfg(test)]
1316 self.searched.push((pa, pb));
1317 continue;
1318 };
1319 tested += 2;
1320 self.parts.try_reserve(2).map_err(|_| oom())?;
1321 for c in children {
1322 let (na, nb) = if split_a { (c, pb) } else { (pa, c) };
1323 let b_box = xf.transform_box(&bvh_b.node(nb).bounds);
1324 if key(&bvh_a.node(na).bounds, &b_box).is_some() {
1325 self.parts.push((na, nb));
1326 }
1327 }
1328 }
1329 let found_pairs = AtomicUsize::new(self.found);
1330 let work_done = AtomicUsize::new(0);
1331 let parts = par::map_chunks(&ready, 1, 0, oom, |_, part| {
1334 let mut near_a = Vec::new();
1335 let mut failed = None;
1336 let mut limited = false;
1337 let mut count = 0usize;
1338 let boxes = Cell::new(0usize);
1339 for_each_near_pair::<PAIR_STACK, f64>(
1340 mesh_a,
1341 bvh_a,
1342 mesh_b,
1343 bvh_b,
1344 &xf,
1345 Some(part[0]),
1346 |p: &Aabb, q: &Aabb| {
1347 boxes.set(boxes.get() + 1);
1348 key(p, q)
1349 },
1350 |gap| gap,
1351 || margin,
1352 m,
1353 |a, pa, _, pb| {
1354 count += 1;
1355 if !tri_tri_within(pa, pb, margin) {
1356 return true;
1357 }
1358 if limit != usize::MAX
1359 && found_pairs.fetch_add(1, Ordering::Relaxed) >= limit
1360 {
1361 limited = true;
1362 return false;
1363 }
1364 if let Err(e) = try_push(&mut near_a, a) {
1365 failed = Some(e);
1366 return false;
1367 }
1368 true
1369 },
1370 );
1371 let work = count
1372 .saturating_mul(NEAR_TEST_WORK)
1373 .saturating_add(boxes.get());
1374 work_done.fetch_add(work, Ordering::Relaxed);
1375 if let Some(e) = failed {
1376 return Err(e);
1377 }
1378 if limited {
1379 return Ok(None);
1380 }
1381 near_a.sort_unstable();
1382 near_a.dedup();
1383 Ok(Some(near_a))
1384 })?;
1385 tested = tested.saturating_add(work_done.into_inner());
1386 self.found = found_pairs.into_inner();
1387 for part in parts {
1388 match part {
1389 Some(near_a) => {
1390 self.near
1391 .try_reserve(near_a.len())
1392 .map_err(|_| QueryError::OutOfMemory)?;
1393 self.near.extend_from_slice(&near_a);
1394 }
1395 None => self.over = true,
1396 }
1397 }
1398 if self.over {
1399 self.near = Vec::new();
1400 }
1401 }
1402 Ok(self.is_done())
1403 }
1404
1405 pub fn finish(self, out_a: &mut Vec<TriangleId>) -> Result<Option<bool>> {
1414 if !self.is_done() {
1415 return Ok(None);
1416 }
1417 if self.over {
1418 return Ok(Some(false));
1419 }
1420 let mut ap = Appender::new(out_a);
1421 ap.extend(&self.near)?;
1422 ap.sort_dedup_appended();
1423 Ok(Some(true))
1424 }
1425}
1426
1427enum Stop {
1430 Limit,
1431 Failed(QueryError),
1432}
1433
1434fn intersecting_pairs_with<const N: usize>(
1435 mesh_a: &Mesh,
1436 bvh_a: &Bvh,
1437 mesh_b: &Mesh,
1438 bvh_b: &Bvh,
1439 xf: &Rigid,
1440 out: &mut Vec<(TriangleId, TriangleId)>,
1441) -> Result<usize> {
1442 let search = |from: Option<(NodeId, NodeId)>,
1445 found: &mut dyn FnMut(TriangleId, TriangleId) -> Result<()>|
1446 -> Result<()> {
1447 let mut failed = None;
1448 for_each_pair::<N>(
1449 mesh_a,
1450 bvh_a,
1451 mesh_b,
1452 bvh_b,
1453 xf,
1454 from,
1455 overlap_key,
1456 |a, pa, b, pb| {
1457 if tri_tri_intersect(pa, pb)
1458 && let Err(e) = found(a, b)
1459 {
1460 failed = Some(e);
1461 return false;
1462 }
1463 true
1464 },
1465 );
1466 failed.map_or(Ok(()), Err)
1467 };
1468 let mut ap = Appender::new(out);
1469 if split_traversal(mesh_a, mesh_b) {
1470 let parts = split_pairs(bvh_a, bvh_b, xf, &overlap_key, PAR_PARTS)?;
1472 let found = par::map_chunks(&parts, 1, 0, oom, |_, part| {
1473 let mut pairs = Vec::new();
1474 search(Some(part[0]), &mut |a, b| try_push(&mut pairs, (a, b)))?;
1475 Ok(pairs)
1476 })?;
1477 for pairs in &found {
1478 ap.extend(pairs)?;
1479 }
1480 } else {
1481 search(None, &mut |a, b| ap.push((a, b)))?;
1482 }
1483 ap.appended().sort_unstable();
1485 Ok(ap.count())
1486}
1487
1488#[cfg(test)]
1489mod tests {
1490 use fast_mesh_bvh::Quality;
1491
1492 use super::*;
1493 use crate::fail_allocations_after;
1494 use crate::test_util::{Lcg, build, random_rotation, wavy};
1495
1496 type M3 = [[f64; 3]; 3];
1497
1498 const FLIP_X: M3 = [[1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]];
1500 const ROT_Z90: M3 = [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]];
1502
1503 fn rot_z(deg: f64) -> M3 {
1504 let (s, c) = deg.to_radians().sin_cos();
1505 [[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]]
1506 }
1507
1508 fn mul(a: &M3, b: &M3) -> M3 {
1509 std::array::from_fn(|i| std::array::from_fn(|j| (0..3).map(|k| a[i][k] * b[k][j]).sum()))
1510 }
1511
1512 fn rotations() -> [M3; 4] {
1514 [
1515 Rigid::IDENTITY.rotation,
1516 rot_z(30.0),
1517 ROT_Z90,
1518 random_rotation(&mut Lcg(7)),
1519 ]
1520 }
1521
1522 fn alive(mesh: &Mesh) -> impl Iterator<Item = TriangleId> + '_ {
1523 (0..mesh.triangle_slots() as TriangleId).filter(|&t| mesh.is_triangle_alive(t))
1524 }
1525
1526 fn pose(ma: &Mesh, mb: &Mesh, rot: &M3, gap: f64) -> Rigid {
1532 let rotation = mul(rot, &FLIP_X);
1533 let mut xf = Rigid {
1534 rotation,
1535 translation: [0.0; 3],
1536 };
1537 let off_middle = |p: &[f32; 3]| (p[0] - 2.5).abs() + (p[1] - 2.5).abs();
1538 let low = *mb
1539 .positions()
1540 .iter()
1541 .min_by(|p, q| {
1542 let (zp, zq) = (xf.apply(**p)[2], xf.apply(**q)[2]);
1543 zp.total_cmp(&zq)
1544 .then(off_middle(p).total_cmp(&off_middle(q)))
1545 })
1546 .unwrap();
1547 let top_a = ma
1548 .positions()
1549 .iter()
1550 .map(|p| f64::from(p[2]))
1551 .fold(f64::NEG_INFINITY, f64::max);
1552 assert_eq!(top_a, 2.0);
1553 let moved = xf.apply(low);
1554 xf.translation = [3.0 - moved[0], 2.0 - moved[1], top_a + gap - moved[2]];
1555 xf
1556 }
1557
1558 fn scenes() -> Vec<(Mesh, Bvh, Mesh, Bvh)> {
1560 [Quality::Sah, Quality::Fast]
1561 .into_iter()
1562 .map(|q| {
1563 let (ma, mb) = (wavy(6), wavy(5));
1564 let (ba, bb) = (build(&ma, q), build(&mb, q));
1565 (ma, ba, mb, bb)
1566 })
1567 .collect()
1568 }
1569
1570 struct Posed {
1572 ma: Mesh,
1573 ba: Bvh,
1574 mb: Mesh,
1575 bb: Bvh,
1576 xf: Rigid,
1577 gap: f64,
1578 level: bool,
1580 }
1581
1582 fn posed() -> Vec<Posed> {
1585 let mut out = Vec::new();
1586 for (i, rot) in rotations().iter().enumerate() {
1587 for gap in [0.3, 0.0, -0.3] {
1588 for (ma, ba, mb, bb) in scenes() {
1589 let xf = pose(&ma, &mb, rot, gap);
1590 assert_eq!(xf.validate(), Ok(()));
1591 let level = i < 3;
1592 out.push(Posed {
1593 ma,
1594 ba,
1595 mb,
1596 bb,
1597 xf,
1598 gap,
1599 level,
1600 });
1601 }
1602 }
1603 }
1604 out
1605 }
1606
1607 fn brute(ma: &Mesh, mb: &Mesh, xf: &Rigid) -> Vec<(TriangleId, TriangleId, bool, TriTriDist)> {
1609 let mut out = Vec::new();
1610 for a in alive(ma) {
1611 for b in alive(mb) {
1612 let (pa, pb) = (tri_a(ma, a), tri_b(mb, xf, b));
1613 out.push((a, b, tri_tri_intersect(pa, pb), tri_tri_distance(pa, pb)));
1614 }
1615 }
1616 out
1617 }
1618
1619 fn brute_near(
1620 all: &[(TriangleId, TriangleId, bool, TriTriDist)],
1621 margin: f32,
1622 ) -> (Vec<TriangleId>, Vec<TriangleId>) {
1623 let near = all
1624 .iter()
1625 .filter(|(_, _, hit, d)| *hit || d.dist2.sqrt() <= f64::from(margin));
1626 let mut a: Vec<_> = near.clone().map(|e| e.0).collect();
1627 let mut b: Vec<_> = near.map(|e| e.1).collect();
1628 a.sort_unstable();
1629 a.dedup();
1630 b.sort_unstable();
1631 b.dedup();
1632 (a, b)
1633 }
1634
1635 fn brute_min(
1637 all: &[(TriangleId, TriangleId, bool, TriTriDist)],
1638 ) -> Option<(TriangleId, TriangleId, TriTriDist)> {
1639 let mut best: Option<(TriangleId, TriangleId, TriTriDist)> = None;
1640 for &(a, b, _, d) in all {
1641 if best.is_none_or(|(_, _, bd)| d.dist2 < bd.dist2) {
1642 best = Some((a, b, d));
1643 }
1644 }
1645 best
1646 }
1647
1648 fn near(
1649 ma: &Mesh,
1650 ba: &Bvh,
1651 mb: &Mesh,
1652 bb: &Bvh,
1653 xf: &Rigid,
1654 margin: f32,
1655 ) -> (Vec<TriangleId>, Vec<TriangleId>) {
1656 let (mut a, mut b) = (Vec::new(), Vec::new());
1657 triangles_near(ma, ba, mb, bb, xf, margin, &mut a, &mut b).unwrap();
1658 (a, b)
1659 }
1660
1661 #[test]
1662 fn intersects_matches_brute_force() {
1663 let mut hits = 0;
1664 for Posed {
1665 ma,
1666 ba,
1667 mb,
1668 bb,
1669 xf,
1670 gap,
1671 level,
1672 } in posed()
1673 {
1674 let want = brute(&ma, &mb, &xf).iter().any(|e| e.2);
1675 assert_eq!(intersects(&ma, &ba, &mb, &bb, &xf), Ok(want), "gap {gap}");
1676 assert_eq!(intersects_with::<0>(&ma, &ba, &mb, &bb, &xf), want);
1677 assert_eq!(intersects_with::<2>(&ma, &ba, &mb, &bb, &xf), want);
1678 if gap > 0.0 {
1679 assert!(!want);
1680 }
1681 if gap <= 0.0 && level {
1682 assert!(want, "gap {gap}");
1683 }
1684 hits += usize::from(want);
1685 }
1686 assert!(hits >= 12, "{hits}");
1687 }
1688
1689 #[test]
1690 fn intersecting_pairs_matches_brute_force() {
1691 let mut total = 0;
1692 for Posed {
1693 ma,
1694 ba,
1695 mb,
1696 bb,
1697 xf,
1698 gap,
1699 ..
1700 } in posed()
1701 {
1702 let want: Vec<_> = brute(&ma, &mb, &xf)
1703 .iter()
1704 .filter(|e| e.2)
1705 .map(|e| (e.0, e.1))
1706 .collect();
1707 if gap < 0.0 {
1710 assert!(!want.is_empty(), "gap {gap}");
1711 }
1712 let mut out = vec![(999, 999)];
1713 let n = intersecting_pairs(&ma, &ba, &mb, &bb, &xf, &mut out).unwrap();
1714 assert_eq!(n, want.len());
1715 assert_eq!(out[0], (999, 999));
1716 assert_eq!(out[1..], want[..]);
1717 for got in [
1718 intersecting_pairs_with::<0>(&ma, &ba, &mb, &bb, &xf, &mut out),
1719 intersecting_pairs_with::<2>(&ma, &ba, &mb, &bb, &xf, &mut out),
1720 ] {
1721 assert_eq!(got, Ok(want.len()));
1722 }
1723 assert_eq!(out[1 + want.len()..1 + 2 * want.len()], want[..]);
1724 assert_eq!(out[1 + 2 * want.len()..], want[..]);
1725 total += want.len();
1726 }
1727 assert!(total > 100);
1728 }
1729
1730 #[test]
1731 fn triangles_near_matches_brute_force() {
1732 let mut found = [0; 3];
1733 for Posed {
1734 ma, ba, mb, bb, xf, ..
1735 } in posed()
1736 {
1737 let all = brute(&ma, &mb, &xf);
1738 for (i, margin) in [0.0, 0.05, 0.5].into_iter().enumerate() {
1739 let want = brute_near(&all, margin);
1740 let (mut a, mut b) = (vec![999], vec![998]);
1741 triangles_near(&ma, &ba, &mb, &bb, &xf, margin, &mut a, &mut b).unwrap();
1742 assert_eq!((a[0], b[0]), (999, 998));
1743 assert_eq!((&a[1..], &b[1..]), (&want.0[..], &want.1[..]));
1744 let (mut a, mut b) = (Vec::new(), Vec::new());
1745 triangles_near_with::<0>(&ma, &ba, &mb, &bb, &xf, margin, &mut a, &mut b).unwrap();
1746 assert_eq!((a, b), want);
1747 let (mut a, mut b) = (Vec::new(), Vec::new());
1748 triangles_near_with::<2>(&ma, &ba, &mb, &bb, &xf, margin, &mut a, &mut b).unwrap();
1749 assert_eq!((a, b), want);
1750 found[i] += want.0.len() + want.1.len();
1751 }
1752 }
1753 assert!(found.iter().all(|&n| n > 50), "{found:?}");
1754 assert!(found[0] <= found[1] && found[1] < found[2], "{found:?}");
1755 }
1756
1757 #[test]
1758 fn triangles_near_limited_stops_past_the_limit() {
1759 let mut aborted = 0;
1760 for Posed {
1761 ma, ba, mb, bb, xf, ..
1762 } in posed()
1763 {
1764 let all = brute(&ma, &mb, &xf);
1765 for margin in [0.0, 0.05, 0.5] {
1766 let want = brute_near(&all, margin).0;
1767 let pairs = all
1768 .iter()
1769 .filter(|(_, _, hit, d)| *hit || d.dist2.sqrt() <= f64::from(margin))
1770 .count();
1771 let run = |limit: usize| {
1772 let mut a = vec![999];
1773 let done =
1774 triangles_near_limited(&ma, &ba, &mb, &bb, &xf, margin, limit, &mut a)
1775 .unwrap();
1776 assert_eq!(a[0], 999);
1777 (done, a[1..].to_vec())
1778 };
1779 assert_eq!(run(usize::MAX), (true, want.clone()));
1781 assert_eq!(run(pairs), (true, want.clone()));
1782 if pairs > 0 {
1783 assert_eq!(run(pairs - 1), (false, Vec::new()));
1785 assert_eq!(run(0), (false, Vec::new()));
1786 aborted += 1;
1787 }
1788 }
1789 }
1790 assert!(aborted > 20, "{aborted}");
1791 }
1792
1793 #[test]
1794 fn triangles_near_limited_bit_identical() {
1795 use crate::test_util::assert_parallel_identical;
1796 let ma = wavy(40);
1798 let ba = build(&ma, Quality::Sah);
1799 let xf = Rigid {
1800 translation: [0.25, 0.5, 0.75],
1801 ..Rigid::IDENTITY
1802 };
1803 let mut want = (Vec::new(), Vec::new());
1804 triangles_near(&ma, &ba, &ma, &ba, &xf, 0.5, &mut want.0, &mut want.1).unwrap();
1805 let full = assert_parallel_identical("triangles_near_limited", || {
1806 let mut a = Vec::new();
1807 let done = triangles_near_limited(&ma, &ba, &ma, &ba, &xf, 0.5, usize::MAX, &mut a);
1808 (done.unwrap(), a)
1809 });
1810 assert_eq!(full, (true, want.0));
1811 let cut = assert_parallel_identical("triangles_near_limited, cut", || {
1812 let mut a = Vec::new();
1813 let done = triangles_near_limited(&ma, &ba, &ma, &ba, &xf, 0.5, 100, &mut a);
1814 (done.unwrap(), a)
1815 });
1816 assert_eq!(cut, (false, Vec::new()));
1817 let completes = |limit| {
1820 fast_mesh_store::par::set_force_sequential(true);
1821 let mut a = Vec::new();
1822 let done = triangles_near_limited(&ma, &ba, &ma, &ba, &xf, 0.5, limit, &mut a);
1823 fast_mesh_store::par::set_force_sequential(false);
1824 done.unwrap()
1825 };
1826 let (mut lo, mut hi) = (0, 1 << 24);
1827 assert!(completes(hi));
1828 while lo < hi {
1829 let mid = (lo + hi) / 2;
1830 if completes(mid) {
1831 hi = mid
1832 } else {
1833 lo = mid + 1
1834 }
1835 }
1836 let total = lo;
1837 assert!(total > 1000, "{total}");
1838 for (limit, want) in [
1841 (total, (true, full.1.clone())),
1842 (total - 1, (false, Vec::new())),
1843 ] {
1844 let got = assert_parallel_identical("triangles_near_limited at the total", || {
1845 let mut a = Vec::new();
1846 let done = triangles_near_limited(&ma, &ba, &ma, &ba, &xf, 0.5, limit, &mut a);
1847 (done.unwrap(), a)
1848 });
1849 assert_eq!(got, want, "limit {limit}");
1850 }
1851 }
1852
1853 #[test]
1854 fn mesh_distance_matches_brute_force() {
1855 let mut separated = 0;
1856 for Posed {
1857 ma,
1858 ba,
1859 mb,
1860 bb,
1861 xf,
1862 gap,
1863 level,
1864 } in posed()
1865 {
1866 let all = brute(&ma, &mb, &xf);
1867 let (wa, wb, wd) = brute_min(&all).unwrap();
1868 let got = mesh_distance(&ma, &ba, &mb, &bb, &xf, f32::INFINITY)
1869 .unwrap()
1870 .unwrap();
1871 for other in [
1872 mesh_distance_with::<0>(&ma, &ba, &mb, &bb, &xf, f32::INFINITY),
1873 mesh_distance_with::<2>(&ma, &ba, &mb, &bb, &xf, f32::INFINITY),
1874 ] {
1875 let other = other.unwrap();
1876 if wd.dist2 > 0.0 {
1877 assert_eq!(other, got);
1878 } else {
1879 assert_eq!(other.distance, 0.0);
1883 assert_eq!(other.point_a, other.point_b);
1884 let (pa, pb) = (
1885 tri_a(&ma, other.triangle_a),
1886 tri_b(&mb, &xf, other.triangle_b),
1887 );
1888 assert!(
1889 tri_tri_intersect(pa, pb) || tri_tri_distance(pa, pb).dist2 == 0.0,
1890 "gap {gap}, {pa:?} {pb:?}"
1891 );
1892 }
1893 }
1894 if wd.dist2 == 0.0 {
1895 assert!(gap <= 0.0);
1896 assert_eq!(got.distance, 0.0);
1897 let (pa, pb) = (tri_a(&ma, got.triangle_a), tri_b(&mb, &xf, got.triangle_b));
1898 assert!(
1902 tri_tri_intersect(pa, pb) || tri_tri_distance(pa, pb).dist2 == 0.0,
1903 "gap {gap}, {pa:?} {pb:?}"
1904 );
1905 continue;
1906 }
1907 separated += 1;
1908 let d = wd.dist2.sqrt();
1909 let want = MeshDistance {
1910 distance: d as f32,
1911 point_a: wd.pa.map(|v| v as f32),
1912 point_b: wd.pb.map(|v| v as f32),
1913 triangle_a: wa,
1914 triangle_b: wb,
1915 };
1916 assert_eq!(got, want, "gap {gap}");
1917 if gap > 0.0 {
1920 assert!(d >= gap - 1e-9, "{d} vs gap {gap}");
1921 }
1922 if level {
1923 assert!((d - gap).abs() < 1e-6, "{d} vs gap {gap}");
1924 }
1925 let above = (d as f32).next_up();
1927 let below = (d as f32).next_down();
1928 assert!(f64::from(below) < d && d <= f64::from(above));
1929 assert_eq!(
1930 mesh_distance(&ma, &ba, &mb, &bb, &xf, above),
1931 Ok(Some(want))
1932 );
1933 assert_eq!(mesh_distance(&ma, &ba, &mb, &bb, &xf, below), Ok(None));
1934 }
1935 assert!(separated >= 8);
1936 }
1937
1938 fn touching(b_points: [[f32; 3]; 3]) -> (Mesh, Bvh, Mesh, Bvh) {
1941 let a = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
1942 let ma = Mesh::from_buffers(&a, &[0, 1, 2]).unwrap();
1943 let mb = Mesh::from_buffers(b_points.as_flattened(), &[0, 1, 2]).unwrap();
1944 let (ba, bb) = (build(&ma, Quality::Sah), build(&mb, Quality::Sah));
1945 (ma, ba, mb, bb)
1946 }
1947
1948 #[test]
1954 fn box_prefilter_keeps_touching_pairs() {
1955 let a = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1956 let b = [[1.0, 0.0, 0.0], [2.0, -1.0, 0.0], [2.0, 0.0, 1.0]];
1957 let (ma, ba, mb, bb) = touching(b);
1958 assert_eq!(ma.positions()[..3], a[..]);
1959 let (pa, pb) = (
1960 Tri::new(tri_a(&ma, 0)),
1961 Tri::new(tri_b(&mb, &Rigid::IDENTITY, 0)),
1962 );
1963 assert!(!boxes_apart(&pa.lo, &pa.hi, &pb.lo, &pb.hi));
1964 for n in [0, 2, PAIR_STACK] {
1965 let found = match n {
1966 0 => intersects_with::<0>(&ma, &ba, &mb, &bb, &Rigid::IDENTITY),
1967 2 => intersects_with::<2>(&ma, &ba, &mb, &bb, &Rigid::IDENTITY),
1968 _ => intersects(&ma, &ba, &mb, &bb, &Rigid::IDENTITY).unwrap(),
1969 };
1970 assert!(found, "stack {n}");
1971 }
1972 let mut pairs = Vec::new();
1973 let n = intersecting_pairs(&ma, &ba, &mb, &bb, &Rigid::IDENTITY, &mut pairs);
1974 assert_eq!((n, pairs), (Ok(1), vec![(0, 0)]));
1975 let d = mesh_distance(&ma, &ba, &mb, &bb, &Rigid::IDENTITY, 1.0);
1976 assert_eq!(d.unwrap().unwrap().distance, 0.0);
1977
1978 let mut apart = Rigid::IDENTITY;
1979 apart.translation[0] = f64::from(1f32.next_up() - 1.0);
1980 let pb = Tri::new(tri_b(&mb, &apart, 0));
1981 assert!(boxes_apart(&pa.lo, &pa.hi, &pb.lo, &pb.hi));
1982 assert_eq!(intersects(&ma, &ba, &mb, &bb, &apart), Ok(false));
1983 let mut pairs = Vec::new();
1984 let n = intersecting_pairs(&ma, &ba, &mb, &bb, &apart, &mut pairs);
1985 assert_eq!((n, pairs), (Ok(0), vec![]));
1986 }
1987
1988 #[test]
1994 fn intersection_queries_skip_pairs_by_boxes() {
1995 use fast_mesh_bvh::BuildOptions;
1996 let check = |ma: &Mesh, mb: &Mesh, xf: &Rigid| {
1997 let want: Vec<_> = brute(ma, mb, xf)
1998 .iter()
1999 .filter(|e| e.2)
2000 .map(|e| (e.0, e.1))
2001 .collect();
2002 let mut skipped = 0;
2003 for leaf in [1, 4, 16] {
2004 let opts = BuildOptions {
2005 max_leaf_size: leaf,
2006 ..Default::default()
2007 };
2008 let (ba, bb) = (
2009 Bvh::build(ma, &opts).unwrap(),
2010 Bvh::build(mb, &opts).unwrap(),
2011 );
2012 PAIR_SKIPS.with(|c| c.set(0));
2013 for got in [
2014 intersecting_pairs_with::<PAIR_STACK>(ma, &ba, mb, &bb, xf, &mut Vec::new()),
2015 intersecting_pairs_with::<0>(ma, &ba, mb, &bb, xf, &mut Vec::new()),
2016 ] {
2017 assert_eq!(got, Ok(want.len()), "leaf {leaf}");
2018 }
2019 let mut out = Vec::new();
2020 intersecting_pairs(ma, &ba, mb, &bb, xf, &mut out).unwrap();
2021 assert_eq!(out, want, "leaf {leaf}");
2022 assert_eq!(intersects(ma, &ba, mb, &bb, xf), Ok(!want.is_empty()));
2023 assert_eq!(intersects_with::<1>(ma, &ba, mb, &bb, xf), !want.is_empty());
2024 skipped += PAIR_SKIPS.with(|c| c.get());
2025 }
2026 (want.len(), skipped)
2027 };
2028 let (mut pairs, mut skipped) = (0, 0);
2029 for Posed { ma, mb, xf, .. } in posed() {
2030 let (p, s) = check(&ma, &mb, &xf);
2031 pairs += p;
2032 skipped += s;
2033 }
2034 let (ma, mb) = (bumps(8), bumps(7));
2035 let mut rng = Lcg(0x15);
2036 for (deg, lift) in [(0.0, 0.3), (3.0, 0.0), (11.0, -0.3)] {
2037 let tilt = orthonormal(&mul(&rot_z(deg), &random_small_turn(&mut rng)));
2038 let xf = Rigid {
2039 rotation: mul(&tilt, &FLIP_X),
2040 translation: [0.3, 7.4, lift],
2041 };
2042 assert_eq!(xf.validate(), Ok(()));
2043 let (p, s) = check(&ma, &mb, &xf);
2044 assert!(p > 0, "{deg}");
2045 pairs += p;
2046 skipped += s;
2047 }
2048 assert!(pairs > 200 && skipped > 1000, "{pairs} {skipped}");
2049 }
2050
2051 fn random_small_turn(rng: &mut Lcg) -> M3 {
2054 let r = random_rotation(rng);
2055 std::array::from_fn(|i| {
2056 std::array::from_fn(|j| {
2057 let id = if i == j { 1.0 } else { 0.0 };
2058 id + 0.05 * (r[i][j] - r[j][i])
2059 })
2060 })
2061 }
2062
2063 #[test]
2064 fn vertex_exactly_on_face_touches() {
2065 let cases = [
2066 (
2067 [[0.25, 0.25, 0.0], [1.0, 0.25, 1.0], [0.25, 1.0, 1.0]],
2068 Rigid::IDENTITY,
2069 ),
2070 (
2071 [[0.0, 0.0, 0.0], [0.5, 0.0, 1.0], [0.0, 0.5, 1.0]],
2072 Rigid {
2073 rotation: ROT_Z90,
2074 translation: [0.25, 0.25, 0.0],
2075 },
2076 ),
2077 ];
2078 for (b, xf) in cases {
2079 let (ma, ba, mb, bb) = touching(b);
2080 assert_eq!(intersects(&ma, &ba, &mb, &bb, &xf), Ok(true));
2081 let d = mesh_distance(&ma, &ba, &mb, &bb, &xf, 1.0)
2082 .unwrap()
2083 .unwrap();
2084 assert_eq!(d.distance, 0.0);
2085 assert_eq!((d.triangle_a, d.triangle_b), (0, 0));
2086 assert_eq!(d.point_a, [0.25, 0.25, 0.0]);
2087 assert_eq!(near(&ma, &ba, &mb, &bb, &xf, 0.0), (vec![0], vec![0]));
2088 let mut pairs = Vec::new();
2089 assert_eq!(
2090 intersecting_pairs(&ma, &ba, &mb, &bb, &xf, &mut pairs),
2091 Ok(1)
2092 );
2093
2094 let lift = 2f64.powi(-20);
2096 let mut up = xf;
2097 up.translation[2] += lift;
2098 assert_eq!(intersects(&ma, &ba, &mb, &bb, &up), Ok(false));
2099 assert_eq!(near(&ma, &ba, &mb, &bb, &up, 0.0), (vec![], vec![]));
2100 let d = mesh_distance(&ma, &ba, &mb, &bb, &up, 1.0)
2101 .unwrap()
2102 .unwrap();
2103 assert_eq!(d.distance, lift as f32);
2104 assert_eq!(d.point_b, [0.25, 0.25, lift as f32]);
2105
2106 let mut up = xf;
2109 up.translation[2] += 0.5;
2110 let d = mesh_distance(&ma, &ba, &mb, &bb, &up, 0.5)
2111 .unwrap()
2112 .unwrap();
2113 assert_eq!(d.distance, 0.5);
2114 assert_eq!(
2115 mesh_distance(&ma, &ba, &mb, &bb, &up, 0.5f32.next_down()),
2116 Ok(None)
2117 );
2118 assert_eq!(near(&ma, &ba, &mb, &bb, &up, 0.5), (vec![0], vec![0]));
2119 assert_eq!(
2120 near(&ma, &ba, &mb, &bb, &up, 0.5f32.next_down()),
2121 (vec![], vec![])
2122 );
2123 }
2124 }
2125
2126 #[test]
2134 fn pruning_keeps_pairs_at_rounding_level() {
2135 use fast_mesh_bvh::BuildOptions;
2136 let opts = BuildOptions {
2137 max_leaf_size: 1,
2138 ..Default::default()
2139 };
2140 let mut rng = Lcg(2024);
2141 let ulp = 2f32.powi(-14); for _ in 0..500 {
2143 let mut f = |lo: f32, span: f32| lo + rng.next() * span;
2144 let p = [800.0, f(600.0, 300.0), f(600.0, 300.0)];
2145 let gap = ulp * (1 + (f(0.0, 4.0) as i32)) as f32;
2146 let x = p[0] + gap;
2147 let a = [
2148 p,
2149 [f(700.0, 100.0), f(600.0, 300.0), f(600.0, 300.0)],
2150 [f(700.0, 100.0), f(600.0, 300.0), f(600.0, 300.0)],
2151 ];
2152 let mut b = Vec::new();
2155 for _ in 0..2 {
2156 b.push([x, p[1] - f(1.0, 99.0), p[2] - f(1.0, 99.0)]);
2157 b.push([x, p[1] + f(1.0, 99.0), p[2] - f(1.0, 99.0)]);
2158 b.push([x, p[1], p[2] + f(1.0, 99.0)]);
2159 }
2160 let ma = Mesh::from_buffers(a.as_flattened(), &[0, 1, 2]).unwrap();
2161 let mb = Mesh::from_buffers(b.as_flattened(), &[0, 1, 2, 3, 4, 5]).unwrap();
2162 let ba = Bvh::build(&ma, &opts).unwrap();
2163 let bb = Bvh::build(&mb, &opts).unwrap();
2164 let xf = Rigid::IDENTITY;
2165
2166 let all = brute(&ma, &mb, &xf);
2167 let (wa, wb, wd) = brute_min(&all).unwrap();
2168 let d = wd.dist2.sqrt();
2169 let want = MeshDistance {
2170 distance: d as f32,
2171 point_a: wd.pa.map(|v| v as f32),
2172 point_b: wd.pb.map(|v| v as f32),
2173 triangle_a: wa,
2174 triangle_b: wb,
2175 };
2176 let got = mesh_distance(&ma, &ba, &mb, &bb, &xf, f32::INFINITY).unwrap();
2177 assert_eq!(got, Some(want), "gap {gap}, oracle {d}");
2178
2179 let mut bound = d as f32;
2181 if f64::from(bound) < d {
2182 bound = bound.next_up();
2183 }
2184 assert!(
2185 mesh_distance(&ma, &ba, &mb, &bb, &xf, bound)
2186 .unwrap()
2187 .is_some()
2188 );
2189 let (na, nb) = near(&ma, &ba, &mb, &bb, &xf, bound);
2190 assert!(
2191 na.contains(&wa) && nb.contains(&wb),
2192 "gap {gap}, oracle {d}"
2193 );
2194 }
2195 }
2196
2197 #[test]
2203 fn pruning_uses_the_slack() {
2204 use fast_mesh_bvh::BuildOptions;
2205 let opts = BuildOptions {
2206 max_leaf_size: 1,
2207 ..Default::default()
2208 };
2209 let a = [
2210 [0.0, 0.0, 0.0],
2211 [-1.0, 1.0, 0.0],
2212 [-1.0, 0.0, 1.0],
2213 [-1e6, 0.0, 0.0],
2214 [-1e6, 1.0, 0.0],
2215 [-1e6, 0.0, 1.0],
2216 ];
2217 let face = |x: f32| [[x, -1.0, -1.0], [x, 1.0, -1.0], [x, 0.0, 1.0]];
2218 let b = [face(0.5), face(0.5001)].concat();
2219 let ma = Mesh::from_buffers(a.as_flattened(), &[0, 1, 2, 3, 4, 5]).unwrap();
2220 let mb = Mesh::from_buffers(b.as_flattened(), &[0, 1, 2, 3, 4, 5]).unwrap();
2221 let (ba, bb) = (
2222 Bvh::build(&ma, &opts).unwrap(),
2223 Bvh::build(&mb, &opts).unwrap(),
2224 );
2225 let xf = Rigid::IDENTITY;
2226 let m = magnitude(&ba, &bb, &xf);
2227 assert!(PRUNE_SLACK * m > 5e-4);
2228 let visits = |f: &mut dyn FnMut()| {
2229 LEAF_VISITS.with(|c| c.set(0));
2230 f();
2231 LEAF_VISITS.with(|c| c.get())
2232 };
2233
2234 let n = visits(&mut || {
2237 let d = mesh_distance(&ma, &ba, &mb, &bb, &xf, f32::INFINITY).unwrap();
2238 assert_eq!(d.unwrap().distance, 0.5);
2239 });
2240 assert_eq!(n, 2);
2241
2242 let n = visits(&mut || {
2244 let d = mesh_distance(&ma, &ba, &mb, &bb, &xf, 0.4999).unwrap();
2245 assert_eq!(d, None);
2246 });
2247 assert_eq!(n, 2);
2248 let n = visits(&mut || {
2249 assert_eq!(near(&ma, &ba, &mb, &bb, &xf, 0.4999), (vec![], vec![]));
2250 });
2251 assert_eq!(n, 2);
2252 }
2253
2254 #[test]
2258 fn near_miss_skips_pairs_by_lower_bound() {
2259 let skips = |f: &mut dyn FnMut()| {
2260 PAIR_SKIPS.with(|c| c.set(0));
2261 f();
2262 PAIR_SKIPS.with(|c| c.get())
2263 };
2264 let gap = 1e-4;
2265 let mut near_skips = [0; 3];
2266 for rot in rotations() {
2267 for (ma, ba, mb, bb) in scenes() {
2268 let xf = pose(&ma, &mb, &rot, gap);
2269 let all = brute(&ma, &mb, &xf);
2270 assert!(!all.iter().any(|e| e.2));
2271 let (wa, wb, wd) = brute_min(&all).unwrap();
2272 let want = MeshDistance {
2273 distance: wd.dist2.sqrt() as f32,
2274 point_a: wd.pa.map(|v| v as f32),
2275 point_b: wd.pb.map(|v| v as f32),
2276 triangle_a: wa,
2277 triangle_b: wb,
2278 };
2279 let n = skips(&mut || {
2280 let got = mesh_distance(&ma, &ba, &mb, &bb, &xf, f32::INFINITY);
2281 assert_eq!(got, Ok(Some(want)));
2282 let got = mesh_distance_with::<0>(&ma, &ba, &mb, &bb, &xf, f32::INFINITY);
2283 assert_eq!(got, Some(want));
2284 });
2285 assert!(n > 0, "{rot:?}");
2286 for (i, margin) in [0.0, 1e-4, 0.05].into_iter().enumerate() {
2287 near_skips[i] += skips(&mut || {
2288 let got = near(&ma, &ba, &mb, &bb, &xf, margin);
2289 assert_eq!(got, brute_near(&all, margin), "margin {margin}");
2290 });
2291 }
2292 }
2293 }
2294 assert!(near_skips.iter().all(|&n| n > 0), "{near_skips:?}");
2297 }
2298
2299 fn bumps(n: u32) -> Mesh {
2303 let mut positions = Vec::new();
2304 for y in 0..=n {
2305 for x in 0..=n {
2306 let (fx, fy) = (x as f32, y as f32);
2307 positions.extend([fx, fy, 0.8 * (0.7 * fx).sin() * (0.5 * fy).cos()]);
2308 }
2309 }
2310 let mut indices = Vec::new();
2311 for y in 0..n {
2312 for x in 0..n {
2313 let a = y * (n + 1) + x;
2314 indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
2315 }
2316 }
2317 Mesh::from_buffers(&positions, &indices).unwrap()
2318 }
2319
2320 fn oracle(all: &[(TriangleId, TriangleId, bool, TriTriDist)]) -> MeshDistance {
2323 assert!(!all.iter().any(|e| e.2));
2324 let (a, b, d) = brute_min(all).unwrap();
2325 MeshDistance {
2326 distance: d.dist2.sqrt() as f32,
2327 point_a: d.pa.map(|v| v as f32),
2328 point_b: d.pb.map(|v| v as f32),
2329 triangle_a: a,
2330 triangle_b: b,
2331 }
2332 }
2333
2334 #[test]
2341 fn near_misses_match_brute_force() {
2342 use fast_mesh_bvh::BuildOptions;
2343 let check = |ma: &Mesh, mb: &Mesh, xf: &Rigid| {
2344 let all = brute(ma, mb, xf);
2345 let want = oracle(&all);
2346 let d = brute_min(&all).unwrap().2.dist2.sqrt();
2347 assert!(d > 0.0 && d < 0.01, "{d}");
2348 for (leaf, quality) in [1, 4, 16]
2349 .into_iter()
2350 .flat_map(|l| [(l, Quality::Sah), (l, Quality::Fast)])
2351 {
2352 let opts = BuildOptions {
2353 quality,
2354 max_leaf_size: leaf,
2355 ..Default::default()
2356 };
2357 let (ba, bb) = (
2358 Bvh::build(ma, &opts).unwrap(),
2359 Bvh::build(mb, &opts).unwrap(),
2360 );
2361 let got = mesh_distance(ma, &ba, mb, &bb, xf, f32::INFINITY);
2362 assert_eq!(got, Ok(Some(want)), "leaf {leaf}, {xf:?}");
2363 for got in [
2364 mesh_distance_with::<0>(ma, &ba, mb, &bb, xf, f32::INFINITY),
2365 mesh_distance_with::<2>(ma, &ba, mb, &bb, xf, f32::INFINITY),
2366 mesh_distance_with::<5>(ma, &ba, mb, &bb, xf, f32::INFINITY),
2367 ] {
2368 assert_eq!(got, Some(want), "leaf {leaf}");
2369 }
2370 let mut above = want.distance;
2371 if f64::from(above) < d {
2372 above = above.next_up();
2373 }
2374 let got = mesh_distance(ma, &ba, mb, &bb, xf, above);
2375 assert_eq!(got, Ok(Some(want)));
2376 let below = above.next_down();
2377 assert_eq!(mesh_distance(ma, &ba, mb, &bb, xf, below), Ok(None));
2378 for margin in [0.0, above, 2.0 * above, 0.05] {
2380 let got = near(ma, &ba, mb, &bb, xf, margin);
2381 assert_eq!(got, brute_near(&all, margin), "margin {margin}");
2382 }
2383 }
2384 };
2385
2386 let mut cases = 0;
2387 let mut rng = Lcg(0x22);
2388 let (ma, mb) = (wavy(6), wavy(5));
2390 for gap in [1e-6, 1e-5, 3e-4, 3e-3] {
2391 for rot in [Rigid::IDENTITY.rotation, rot_z(30.0), ROT_Z90] {
2392 check(&ma, &mb, &pose(&ma, &mb, &rot, gap));
2393 cases += 1;
2394 }
2395 }
2396 let (ma, mb) = (bumps(8), bumps(7));
2399 for gap in [1e-5, 1e-4, 2e-3] {
2400 for deg in [0.0, 2.5, 7.0] {
2401 let skew = random_small_turn(&mut rng);
2402 let rot = orthonormal(&mul(&rot_z(deg), &skew));
2404 let mut xf = Rigid {
2405 rotation: mul(&rot, &FLIP_X),
2406 translation: [0.3, 7.4, 2.0],
2407 };
2408 for _ in 0..4 {
2409 let d = brute_min(&brute(&ma, &mb, &xf)).unwrap().2.dist2.sqrt();
2410 xf.translation[2] -= d - gap;
2411 }
2412 assert_eq!(xf.validate(), Ok(()));
2413 check(&ma, &mb, &xf);
2414 cases += 1;
2415 }
2416 }
2417 assert_eq!(cases, 21);
2418 }
2419
2420 fn orthonormal(m: &M3) -> M3 {
2423 let dot = |p: &[f64; 3], q: &[f64; 3]| (0..3).map(|k| p[k] * q[k]).sum::<f64>();
2424 let unit = |p: [f64; 3]| {
2425 let n = dot(&p, &p).sqrt();
2426 p.map(|v| v / n)
2427 };
2428 let r0 = unit(m[0]);
2429 let d = dot(&m[1], &r0);
2430 let r1 = unit(std::array::from_fn(|k| m[1][k] - d * r0[k]));
2431 let r2 = [
2432 r0[1] * r1[2] - r0[2] * r1[1],
2433 r0[2] * r1[0] - r0[0] * r1[2],
2434 r0[0] * r1[1] - r0[1] * r1[0],
2435 ];
2436 [r0, r1, r2]
2437 }
2438
2439 #[test]
2440 fn pair_lower_bound_is_below_the_distance() {
2441 let mut rng = Lcg(0x10b0);
2444 let mut useful = 0;
2445 for i in 0..20_000 {
2446 let mut r = || f64::from(rng.next());
2447 let a: [P3; 3] = std::array::from_fn(|_| [r(), r(), r()]);
2448 let mut b: [P3; 3] = std::array::from_fn(|_| [r() + 0.5, r(), r() + 0.3 * r()]);
2449 match i % 4 {
2450 1 => b[2] = std::array::from_fn(|k| 0.5 * (b[0][k] + b[1][k]) + 1e-9 * r()),
2452 2 => {
2454 let (s, t) = (0.5 + r(), r() - 0.5);
2455 b = a.map(|p| {
2456 std::array::from_fn(|k| {
2457 p[k] + s * (a[1][k] - a[0][k]) + t * (a[2][k] - a[0][k])
2458 })
2459 });
2460 }
2461 _ => {}
2462 }
2463 let lb = pair_lower_bound(&a, &b);
2464 let d = tri_tri_distance(a, b).dist2.sqrt();
2465 assert!(lb <= d + 1e-12, "{a:?} {b:?}: {lb} > {d}");
2466 useful += usize::from(lb > 0.5 * d && d > 0.0);
2467 }
2468 assert!(useful > 1000, "{useful}");
2469 }
2470
2471 #[test]
2475 fn union_box_gap_is_below_each_pair_gap() {
2476 let mut rng = Lcg(0x5e1f);
2477 let mut r = || f64::from(rng.next());
2478 let tri = |r: &mut dyn FnMut() -> f64, at: P3, size: f64| -> [P3; 3] {
2479 std::array::from_fn(|_| std::array::from_fn(|k| at[k] + size * r()))
2480 };
2481 let mut skipped = 0;
2482 for _ in 0..2000 {
2483 let at: P3 = [10.0 * r(), 10.0 * r(), 10.0 * r()];
2484 let ids: Vec<TriangleId> = (0..1 + (r() * 16.0) as TriangleId).collect();
2485 let chunk_tris: Vec<[P3; 3]> = ids.iter().map(|_| tri(&mut r, at, 3.0)).collect();
2486 let mut chunk = Chunk::new();
2487 chunk.fill(&ids, |t| chunk_tris[t as usize]);
2488 assert_eq!(chunk.tris().len(), ids.len());
2489 let near: P3 = [10.0 * r(), 10.0 * r(), 10.0 * r()];
2490 let other = Tri::new(tri(&mut r, near, 3.0));
2491 let union_gap = other.box_gap(&chunk.lo, &chunk.hi);
2492 let bound = 0.5 * r();
2493 for t in chunk.tris() {
2494 assert!(union_gap <= other.box_gap(&t.lo, &t.hi));
2495 assert!(union_gap <= t.box_gap(&other.lo, &other.hi));
2496 if beyond(union_gap, bound, 10.0) {
2497 assert!(skip_pair(t, &other, bound, 10.0));
2498 assert!(skip_pair(&other, t, bound, 10.0));
2499 skipped += 1;
2500 }
2501 }
2502 }
2503 assert!(skipped > 1000, "{skipped}");
2504 }
2505
2506 #[test]
2507 fn prune_slack_covers_rounding() {
2508 let (m, bound) = (1000.0, 1e-4);
2511 let slack = PRUNE_SLACK * (m + bound);
2512 assert!(slack > 1e-7 && slack < 1e-6);
2513 assert!(!beyond(bound + slack / 2.0, bound, m));
2514 assert!(beyond(bound + 2.0 * slack, bound, m));
2515 assert!(!beyond(1e300, f64::INFINITY, m));
2516 assert!(beyond(1e-9, 0.0, 0.0));
2517 }
2518
2519 #[test]
2520 fn cross_query_consistency() {
2521 let mut checked = 0;
2522 for Posed {
2523 ma,
2524 ba,
2525 mb,
2526 bb,
2527 xf,
2528 gap,
2529 ..
2530 } in posed()
2531 {
2532 if gap <= 0.0 {
2533 continue;
2534 }
2535 let d = mesh_distance(&ma, &ba, &mb, &bb, &xf, f32::INFINITY)
2536 .unwrap()
2537 .unwrap();
2538 assert!(d.distance > 0.0);
2539 let (a, b) = near(&ma, &ba, &mb, &bb, &xf, d.distance * (1.0 + 1e-6));
2540 assert!(a.contains(&d.triangle_a), "{d:?}");
2541 assert!(b.contains(&d.triangle_b), "{d:?}");
2542 checked += 1;
2543 }
2544 assert!(checked >= 8);
2545 }
2546
2547 fn errors(
2550 ma: &Mesh,
2551 ba: &Bvh,
2552 mb: &Mesh,
2553 bb: &Bvh,
2554 xf: &Rigid,
2555 margin: f32,
2556 max_distance: f32,
2557 ) -> [QueryError; 4] {
2558 let (mut a, mut b, mut p) = (vec![7], vec![8], vec![(9, 9)]);
2559 let out = [
2560 intersects(ma, ba, mb, bb, xf).unwrap_err(),
2561 mesh_distance(ma, ba, mb, bb, xf, max_distance).unwrap_err(),
2562 triangles_near(ma, ba, mb, bb, xf, margin, &mut a, &mut b).unwrap_err(),
2563 intersecting_pairs(ma, ba, mb, bb, xf, &mut p).unwrap_err(),
2564 ];
2565 assert_eq!((a, b, p), (vec![7], vec![8], vec![(9, 9)]));
2566 out
2567 }
2568
2569 #[test]
2570 fn empty_side_returns_nothing() {
2571 let check_empty = |ma: &Mesh, ba: &Bvh, mb: &Mesh, bb: &Bvh| {
2572 let xf = Rigid::IDENTITY;
2573 assert_eq!(intersects(ma, ba, mb, bb, &xf), Ok(false));
2574 assert_eq!(mesh_distance(ma, ba, mb, bb, &xf, f32::INFINITY), Ok(None));
2575 assert_eq!(near(ma, ba, mb, bb, &xf, 1e9), (vec![], vec![]));
2576 let mut p = Vec::new();
2577 assert_eq!(intersecting_pairs(ma, ba, mb, bb, &xf, &mut p), Ok(0));
2578 assert!(p.is_empty());
2579 };
2580 let full = wavy(4);
2581 let bfull = build(&full, Quality::Sah);
2582 let empty = Mesh::new();
2583 let bempty = build(&empty, Quality::Sah);
2584 let mut removed = wavy(4);
2585 let mut bremoved = build(&removed, Quality::Sah);
2586 let all: Vec<TriangleId> = (0..removed.triangle_slots() as TriangleId).collect();
2587 let mut tx = removed.begin_edit();
2588 tx.remove_triangles(&all).unwrap();
2589 let p = tx.commit().unwrap();
2590 bremoved.update(&mut removed, p.changes()).unwrap();
2591
2592 for (m, b) in [(&empty, &bempty), (&removed, &bremoved)] {
2593 check_empty(m, b, &full, &bfull);
2594 check_empty(&full, &bfull, m, b);
2595 check_empty(m, b, m, b);
2596 }
2597 }
2598
2599 #[test]
2600 fn same_mesh_twice_is_two_copies() {
2601 let m = wavy(4);
2602 let b = build(&m, Quality::Sah);
2603 let xf = Rigid::IDENTITY;
2604 assert_eq!(intersects(&m, &b, &m, &b, &xf), Ok(true));
2605 let mut pairs = Vec::new();
2606 intersecting_pairs(&m, &b, &m, &b, &xf, &mut pairs).unwrap();
2607 for t in alive(&m) {
2608 assert!(pairs.binary_search(&(t, t)).is_ok(), "({t}, {t}) missing");
2609 }
2610 let tris: Vec<_> = alive(&m).collect();
2613 let shares_edge = |t: TriangleId, u: TriangleId| {
2614 let (vt, vu) = (m.triangle(t).unwrap(), m.triangle(u).unwrap());
2615 vt.iter().filter(|v| vu.contains(v)).count() == 2
2616 };
2617 let (t, u) = tris
2618 .iter()
2619 .flat_map(|&t| tris.iter().map(move |&u| (t, u)))
2620 .find(|&(t, u)| shares_edge(t, u))
2621 .unwrap();
2622 assert!(pairs.binary_search(&(t, u)).is_ok(), "({t}, {u}) missing");
2623 assert!(pairs.binary_search(&(u, t)).is_ok(), "({u}, {t}) missing");
2624 assert_eq!(
2625 mesh_distance(&m, &b, &m, &b, &xf, 0.0)
2626 .unwrap()
2627 .unwrap()
2628 .distance,
2629 0.0
2630 );
2631 }
2632
2633 #[test]
2634 fn invalid_input() {
2635 let m = wavy(3);
2636 let b = build(&m, Quality::Sah);
2637 let other = wavy(3);
2638 let bother = build(&other, Quality::Sah);
2639 let ok = Rigid::IDENTITY;
2640 let mut off = ok;
2641 off.rotation[0][0] += 1e-3;
2642 let mut reflection = ok;
2643 reflection.rotation[2][2] = -1.0;
2644 let mut nan = ok;
2645 nan.translation[1] = f64::NAN;
2646 let mut inf = ok;
2647 inf.rotation[2][1] = f64::INFINITY;
2648 let is_invalid = |e: QueryError| matches!(e, QueryError::InvalidInput(_));
2649 for xf in [off, reflection, nan, inf] {
2651 for e in errors(&m, &b, &other, &b, &xf, 0.0, 0.0) {
2652 assert!(is_invalid(e), "{e:?}");
2653 }
2654 }
2655 for bad in [-1.0, f32::NAN, -f32::MIN_POSITIVE] {
2656 let mut x = (vec![7], vec![8]);
2657 let e = triangles_near(&m, &b, &other, &b, &ok, bad, &mut x.0, &mut x.1);
2658 assert!(is_invalid(e.unwrap_err()));
2659 assert_eq!(x, (vec![7], vec![8]));
2660 let e = mesh_distance(&m, &b, &other, &b, &ok, bad);
2661 assert!(is_invalid(e.unwrap_err()));
2662 }
2663 let mut x = (Vec::new(), Vec::new());
2665 let r = triangles_near(
2666 &m,
2667 &b,
2668 &other,
2669 &bother,
2670 &ok,
2671 f32::INFINITY,
2672 &mut x.0,
2673 &mut x.1,
2674 );
2675 assert_eq!(r, Ok(()));
2676 assert!(!x.0.is_empty());
2677 }
2678
2679 #[test]
2680 fn stale_and_mismatch_on_either_side() {
2681 let (ma, ba) = (wavy(3), build(&wavy(3), Quality::Sah));
2682 let (mb, bb) = (wavy(2), build(&wavy(2), Quality::Sah));
2683 let xf = Rigid::IDENTITY;
2684 let mm = [QueryError::MeshMismatch; 4];
2685 assert_eq!(errors(&ma, &ba, &mb, &bb, &xf, 0.1, 1.0), mm);
2687 let ba = build(&ma, Quality::Sah);
2688 assert_eq!(errors(&ma, &ba, &mb, &bb, &xf, 0.1, 1.0), mm);
2689 let bb = build(&mb, Quality::Sah);
2690 assert_eq!(intersects(&ma, &ba, &mb, &bb, &xf), Ok(true));
2691 assert_eq!(errors(&ma, &bb, &mb, &bb, &xf, 0.1, 1.0), mm);
2692 assert_eq!(errors(&ma, &ba, &mb, &ba, &xf, 0.1, 1.0), mm);
2693
2694 let stale = [QueryError::StaleBvh; 4];
2695 let edit = |m: &mut Mesh| {
2696 let mut tx = m.begin_edit();
2697 tx.set_positions(&[0], &[[0.0, 0.0, 0.5]]).unwrap();
2698 tx.commit().unwrap();
2699 };
2700 let (mut ma, mut mb) = (ma, mb);
2701 edit(&mut ma);
2702 assert_eq!(errors(&ma, &ba, &mb, &bb, &xf, 0.1, 1.0), stale);
2703 let ba = build(&ma, Quality::Sah);
2704 edit(&mut mb);
2705 assert_eq!(errors(&ma, &ba, &mb, &bb, &xf, 0.1, 1.0), stale);
2706 }
2707
2708 #[test]
2709 fn oom_restores_outputs() {
2710 let (ma, ba, mb, bb) = scenes().remove(0);
2711 let xf = pose(&ma, &mb, &Rigid::IDENTITY.rotation, -0.3);
2712
2713 let mut p = vec![(7, 7)];
2714 fail_allocations_after(Some(0));
2715 let r = intersecting_pairs(&ma, &ba, &mb, &bb, &xf, &mut p);
2716 fail_allocations_after(None);
2717 assert_eq!(r, Err(QueryError::OutOfMemory));
2718 assert_eq!(p, vec![(7, 7)]);
2719
2720 let (mut a, mut b) = (vec![7], vec![8]);
2722 fail_allocations_after(Some(1));
2723 let r = triangles_near(&ma, &ba, &mb, &bb, &xf, 0.0, &mut a, &mut b);
2724 fail_allocations_after(None);
2725 assert_eq!(r, Err(QueryError::OutOfMemory));
2726 assert_eq!((a, b), (vec![7], vec![8]));
2727
2728 for (limit, want) in [
2731 (usize::MAX, Err(QueryError::OutOfMemory)),
2732 (1000, Err(QueryError::OutOfMemory)),
2733 (0, Ok(false)),
2734 ] {
2735 let mut a = vec![7];
2736 fail_allocations_after(Some(0));
2737 let r = triangles_near_limited(&ma, &ba, &mb, &bb, &xf, 0.0, limit, &mut a);
2738 fail_allocations_after(None);
2739 assert_eq!(r, want, "limit {limit}");
2740 assert_eq!(a, vec![7]);
2741 }
2742 }
2743
2744 #[test]
2745 fn pair_queries_bit_identical() {
2746 use crate::test_util::{assert_parallel_identical, assert_runs_identical, ripple};
2747 let (ma, mb) = (ripple(60, false), ripple(60, true));
2748 assert!(ma.triangle_count() + mb.triangle_count() >= PAR_MIN_PAIR_TRIS);
2749 for quality in [Quality::Sah, Quality::Fast] {
2750 let (ba, bb) = (build(&ma, quality), build(&mb, quality));
2751 let turned = Rigid {
2752 rotation: rot_z(3.0),
2753 translation: [2.5, -2.0, 0.1],
2754 };
2755 let touching = Rigid {
2760 rotation: rot_z(0.0),
2761 translation: [0.0, 0.0, 2.03],
2762 };
2763 let apart = Rigid {
2768 rotation: [[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]],
2769 translation: [6.73, 0.0, -0.5],
2770 };
2771 let poses = [
2775 (Rigid::IDENTITY, 500..usize::MAX, 500..usize::MAX, true),
2776 (turned, 500..usize::MAX, 500..usize::MAX, true),
2777 (touching, 1..10, 0..1, false),
2778 (apart, 0..1, 0..1, true),
2779 ];
2780 for (i, (xf, near_len, crossings, splits)) in poses.into_iter().enumerate() {
2781 let near = assert_parallel_identical(&format!("triangles_near {i}"), || {
2782 let (mut a, mut b) = (vec![7], vec![9]);
2783 triangles_near(&ma, &ba, &mb, &bb, &xf, 0.05, &mut a, &mut b).unwrap();
2784 (a, b)
2785 });
2786 let lens = (near.0.len() - 1, near.1.len() - 1);
2788 assert!(
2789 near_len.contains(&lens.0) && near_len.contains(&lens.1),
2790 "{i}: {lens:?}"
2791 );
2792 let what = format!("intersecting_pairs {i}");
2793 let crossing = assert_runs_identical(&what, splits, || {
2794 let mut out = vec![(1, 2)];
2795 let n = intersecting_pairs(&ma, &ba, &mb, &bb, &xf, &mut out).unwrap();
2796 (n, out)
2797 });
2798 assert!(crossings.contains(&crossing.0), "{i}: {}", crossing.0);
2799 }
2800 }
2801 }
2802
2803 #[allow(clippy::too_many_arguments)] fn stepped_near(
2808 ma: &Mesh,
2809 ba: &Bvh,
2810 mb: &Mesh,
2811 bb: &Bvh,
2812 xf: &Rigid,
2813 margin: f32,
2814 limit: usize,
2815 budget: usize,
2816 out: &mut Vec<TriangleId>,
2817 ) -> (bool, usize) {
2818 let mut search = NearSearch::new(ma, ba, mb, bb, xf, margin, limit).unwrap();
2819 let mut steps = 1;
2820 while !search.step(ma, ba, mb, bb, budget).unwrap() {
2821 steps += 1;
2822 }
2823 (search.finish(out).unwrap().unwrap(), steps)
2824 }
2825
2826 #[test]
2827 fn stepped_near_search_matches_one_call() {
2828 let mut aborted = 0;
2829 for Posed {
2830 ma, ba, mb, bb, xf, ..
2831 } in posed()
2832 {
2833 let all = brute(&ma, &mb, &xf);
2834 for margin in [0.0, 0.05, 0.5] {
2835 let pairs = all
2836 .iter()
2837 .filter(|(_, _, hit, d)| *hit || d.dist2.sqrt() <= f64::from(margin))
2838 .count();
2839 let mut limits = vec![usize::MAX, pairs, pairs / 2];
2840 if pairs > 0 {
2841 limits.extend([pairs - 1, 0]);
2842 aborted += 1;
2843 }
2844 for limit in limits {
2845 let mut want = vec![999];
2846 let done =
2847 triangles_near_limited(&ma, &ba, &mb, &bb, &xf, margin, limit, &mut want)
2848 .unwrap();
2849 for budget in [1, 10, usize::MAX] {
2850 let mut got = vec![999];
2851 let (ok, _) =
2852 stepped_near(&ma, &ba, &mb, &bb, &xf, margin, limit, budget, &mut got);
2853 assert_eq!((ok, &got), (done, &want), "limit {limit}, budget {budget}");
2854 }
2855 }
2856 }
2857 }
2858 assert!(aborted > 20, "{aborted}");
2859 }
2860
2861 #[test]
2862 fn stepped_near_search_bit_identical() {
2863 use crate::test_util::assert_parallel_identical;
2864 let ma = wavy(40);
2865 let ba = build(&ma, Quality::Sah);
2866 let xf = Rigid {
2867 translation: [0.25, 0.5, 0.75],
2868 ..Rigid::IDENTITY
2869 };
2870 let one_call = |limit| {
2871 fast_mesh_store::par::set_force_sequential(true);
2872 let mut a = Vec::new();
2873 let done = triangles_near_limited(&ma, &ba, &ma, &ba, &xf, 0.5, limit, &mut a);
2874 fast_mesh_store::par::set_force_sequential(false);
2875 (done.unwrap(), a)
2876 };
2877 let (_, full) = one_call(usize::MAX);
2878 let (mut lo, mut hi) = (0, 1 << 24);
2880 while lo < hi {
2881 let mid = (lo + hi) / 2;
2882 if one_call(mid).0 {
2883 hi = mid
2884 } else {
2885 lo = mid + 1
2886 }
2887 }
2888 let total = lo;
2889 assert!(total > 1000, "{total}");
2890 for limit in [usize::MAX, total, total - 1, 100] {
2891 let want = one_call(limit);
2892 for budget in [1, 500, usize::MAX] {
2893 let got = assert_parallel_identical("stepped near search", || {
2894 let mut a = Vec::new();
2895 let (ok, _) = stepped_near(&ma, &ba, &ma, &ba, &xf, 0.5, limit, budget, &mut a);
2896 (ok, a)
2897 });
2898 assert_eq!(got, want, "limit {limit}, budget {budget}");
2899 if budget == 1 && limit == usize::MAX {
2900 assert_eq!(got.1, full);
2901 fast_mesh_store::par::set_force_sequential(true);
2903 let mut a = Vec::new();
2904 let (_, steps) =
2905 stepped_near(&ma, &ba, &ma, &ba, &xf, 0.5, limit, budget, &mut a);
2906 fast_mesh_store::par::set_force_sequential(false);
2907 assert!(steps > 10, "{steps} steps");
2908 }
2909 }
2910 }
2911 }
2912
2913 fn chain(groups: u32, group: u32) -> Mesh {
2917 let q = 1.0f32 - 2.0 / 16.0;
2918 let (mut positions, mut indices) = (Vec::new(), Vec::new());
2919 for g in 0..groups {
2920 let x = -q.powi(g as i32);
2921 let s = x.abs() * 0.01;
2922 for k in 0..group {
2923 let v = 3 * (g * group + k);
2924 let y = k as f32 * s * 0.1;
2925 positions.extend([x - s, y, 0.0, x + s, y, 0.0, x, y + s, s]);
2926 indices.extend([v, v + 1, v + 2]);
2927 }
2928 }
2929 Mesh::from_buffers(&positions, &indices).unwrap()
2930 }
2931
2932 #[test]
2933 fn stepped_near_search_bounds_parts_on_an_unbalanced_tree() {
2934 fn below(bvh: &Bvh, id: fast_mesh_bvh::NodeId) -> u64 {
2941 match bvh.node(id).kind {
2942 NodeKind::Leaf { triangles } => triangles.len() as u64,
2943 NodeKind::Inner { left, right } => below(bvh, left) + below(bvh, right),
2944 }
2945 }
2946 let is_leaf = |bvh: &Bvh, id| matches!(bvh.node(id).kind, NodeKind::Leaf { .. });
2947 let (groups, group) = (160, 4);
2948 let (ma, mb) = (chain(groups, group), chain(groups, group));
2949 let (ba, bb) = (build(&ma, Quality::Sah), build(&mb, Quality::Sah));
2950 let n = u64::from(groups * group);
2951 assert!(n * n > NEAR_PART_PAIRS);
2952 assert!(ba.depth() > 15, "depth {}", ba.depth());
2953 let xf = Rigid::IDENTITY;
2954 for limit in [usize::MAX, 1000] {
2955 let mut want = Vec::new();
2956 let done =
2957 triangles_near_limited(&ma, &ba, &mb, &bb, &xf, 3.0, limit, &mut want).unwrap();
2958 assert_eq!(done, limit == usize::MAX);
2959 for budget in [1, 1 << 16, usize::MAX] {
2960 let mut search = NearSearch::new(&ma, &ba, &mb, &bb, &xf, 3.0, limit).unwrap();
2961 while !search.step(&ma, &ba, &mb, &bb, budget).unwrap() {}
2962 let largest = search
2963 .searched
2964 .iter()
2965 .filter(|&&(a, b)| !(is_leaf(&ba, a) && is_leaf(&bb, b)))
2966 .map(|&(a, b)| below(&ba, a) * below(&bb, b))
2967 .max()
2968 .unwrap_or(0);
2969 assert!(
2970 largest <= NEAR_PART_PAIRS,
2971 "budget {budget}: a part of {largest} pairs"
2972 );
2973 let mut got = Vec::new();
2974 assert_eq!(search.finish(&mut got).unwrap(), Some(done));
2975 assert_eq!(got, want, "limit {limit}, budget {budget}");
2976 }
2977 }
2978 }
2979
2980 #[test]
2981 fn stepped_near_search_refuses_other_meshes() {
2982 let ma = wavy(120);
2984 let ba = build(&ma, Quality::Sah);
2985 let mut mb = wavy(120);
2986 let mut bb = build(&mb, Quality::Sah);
2987 let xf = Rigid::IDENTITY;
2988 let mut search = NearSearch::new(&ma, &ba, &mb, &bb, &xf, 0.5, usize::MAX).unwrap();
2989 assert!(!search.step(&ma, &ba, &mb, &bb, 1).unwrap());
2990 let mut tx = mb.begin_edit();
2991 tx.set_positions(&[0], &[[0.0, 0.0, 9.0]]).unwrap();
2992 let patch = tx.commit().unwrap();
2993 bb.update(&mut mb, patch.changes()).unwrap();
2994 assert!(matches!(
2995 search.step(&ma, &ba, &mb, &bb, 1),
2996 Err(QueryError::InvalidInput(_))
2997 ));
2998 let mut out = Vec::new();
2999 let unfinished = NearSearch::new(&ma, &ba, &mb, &bb, &xf, 0.5, usize::MAX).unwrap();
3000 assert_eq!(unfinished.finish(&mut out), Ok(None));
3001 assert!(out.is_empty());
3002 }
3003}