Skip to main content

fast_mesh_query/
pairs.rs

1//! Mesh-vs-mesh queries: intersection, distance and proximity between two
2//! meshes placed relative to each other by a [`Rigid`] transform.
3//!
4//! Every query walks both BVHs at once ([`traverse_pairs_with`]) and prunes
5//! through the traversal's `key` as well as its `visit`, so that the
6//! stackless overflow fallback, which shows `visit` only leaf pairs, prunes
7//! too.
8
9use 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/// The closest pair of points between two meshes, found by [`mesh_distance`].
25///
26/// Both points are in mesh A's local space: `point_b` is the point of B's
27/// triangle `triangle_b` as placed by the query's [`Rigid`] transform, not a
28/// point in B's own coordinates. Map it back into B's space with
29/// [`Rigid::apply`] on [`Rigid::inverse`]; map B's own points into A's space
30/// with [`Rigid::apply`], the arithmetic the queries use.
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct MeshDistance {
33    /// The distance between the two surfaces. 0 when they touch, intersect,
34    /// or are closer than `f64` rounding resolves: a positive gap whose
35    /// computed distance rounds to 0 is reported as 0. See [`mesh_distance`].
36    pub distance: f32,
37    /// The closest point on A's triangle `triangle_a`, in A's local space.
38    pub point_a: [f32; 3],
39    /// The closest point on B's triangle `triangle_b`, in A's local space
40    /// (not B's).
41    pub point_b: [f32; 3],
42    /// The triangle of A that `point_a` lies on.
43    pub triangle_a: TriangleId,
44    /// The triangle of B that `point_b` lies on.
45    pub triangle_b: TriangleId,
46}
47
48/// Whether any alive triangle of A intersects or touches any alive triangle
49/// of B, with B placed in A's space by `xf`.
50///
51/// The test is closed and exact for B's vertices as transformed by `xf` in
52/// `f64`: touching at a single point counts, a gap of one `f64` ulp does not.
53/// The search stops at the first intersecting pair found.
54///
55/// Passing the same mesh and BVH as both A and B means two coincident copies
56/// of it, not one mesh tested against itself: with [`Rigid::IDENTITY`] every
57/// triangle intersects its own copy, so the result is `true` for any mesh with
58/// a triangle.
59///
60/// # Errors
61/// [`QueryError::InvalidInput`] when `xf` is not a finite proper rotation plus
62/// translation (see [`Rigid`]); then [`QueryError::MeshMismatch`] or
63/// [`QueryError::StaleBvh`] when `bvh_a` or `bvh_b` does not match its current
64/// mesh.
65pub 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
78/// The minimum distance between the surfaces of A and B, with B placed in A's
79/// space by `xf`, and a closest pair of points. `None` when the distance is
80/// greater than `max_distance`, or when either mesh has no alive triangle.
81///
82/// `max_distance` is inclusive and may be infinite; it is compared as
83/// `f64 distance <= f64::from(max_distance)`. Positive distances are computed
84/// in `f64` from B's vertices as transformed by `xf`, and are exact up to
85/// rounding. Among pairs of triangles at the same minimum distance (equal
86/// squared distance in `f64`), the smallest `(triangle_a, triangle_b)` wins.
87///
88/// A distance of 0 means the meshes touch, intersect, or are closer than `f64`
89/// rounding resolves: a positive gap whose computed distance rounds to 0 is
90/// reported as 0. The search stops at the first pair of triangles with
91/// distance 0 that it finds. That pair intersects (decided exactly) or has a
92/// computed distance of 0; it is not necessarily the smallest such pair, and
93/// its two points coincide up to rounding. Use [`intersecting_pairs`] for
94/// every intersecting pair.
95///
96/// Both points are in A's space; see [`MeshDistance`]. Passing the same mesh
97/// twice means two coincident copies, as for [`intersects`].
98///
99/// # Errors
100/// [`QueryError::InvalidInput`] for an invalid `xf` (see [`Rigid`]) or a
101/// `max_distance` that is negative or NaN; then [`QueryError::MeshMismatch`] or
102/// [`QueryError::StaleBvh`] as for [`intersects`].
103pub 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/// Appends to `out_a` every alive triangle of A within `margin` of B, and to
130/// `out_b` every alive triangle of B within `margin` of A, with B placed in
131/// A's space by `xf`. Elements already in the vectors are kept and stay in
132/// front; each appended part is in ascending order without duplicates.
133///
134/// A pair of triangles is near when it intersects or touches (decided
135/// exactly), or when its `f64` distance is at most `margin`, compared as
136/// `f64 distance <= f64::from(margin)`. So a `margin` of 0 finds the triangles
137/// that touch, intersect, or are closer than `f64` rounding resolves, as for a
138/// distance of 0 in [`mesh_distance`]: a pair with a positive gap whose
139/// computed distance rounds to 0 counts as near, so a `margin` of 0 can find
140/// pairs for which [`intersects`] (exact) would be `false`.
141///
142/// Both sides come from the same pair tests, so they agree pair for pair; a
143/// second call with the meshes swapped and [`Rigid::inverse`] could differ at
144/// the rounding level.
145///
146/// Before deduplication, each vector temporarily holds a triangle once per
147/// near pair it belongs to (a triangle near `k` triangles of the other mesh is
148/// held `k` times), and keeps that capacity afterwards.
149///
150/// Passing the same mesh twice means two coincident copies, as for
151/// [`intersects`].
152///
153/// # Errors
154/// [`QueryError::InvalidInput`] for an invalid `xf` (see [`Rigid`]) or a
155/// `margin` that is negative or NaN; then [`QueryError::MeshMismatch`] or
156/// [`QueryError::StaleBvh`] as for [`intersects`]; and
157/// [`QueryError::OutOfMemory`] when either vector cannot grow. On any error
158/// both vectors keep their original lengths.
159#[allow(clippy::too_many_arguments)] // The shared five, a bound and two outputs.
160pub 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/// [`triangles_near`] for A's side only, given up once more than `limit`
182/// near pairs are found: for callers that have a cheaper way to go on when
183/// the near set is large (the search visits every near pair, and their
184/// number grows with the margin much faster than the near set).
185///
186/// Returns `Ok(true)` with exactly the triangles [`triangles_near`] appends
187/// to `out_a` when there are at most `limit` near pairs, and `Ok(false)` with
188/// `out_a` unchanged otherwise. With the `parallel` feature, the pairs of
189/// all parts count towards the same limit, so the outcome does not depend
190/// on the threads.
191///
192/// # Errors
193/// As for [`triangles_near`]; on any error `out_a` keeps its original
194/// length.
195#[doc(hidden)]
196#[allow(clippy::too_many_arguments)] // As for `triangles_near`, with the limit.
197pub 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
220/// Appends to `out` every pair `(a, b)` of an alive triangle `a` of A and an
221/// alive triangle `b` of B that intersect or touch, with B placed in A's space
222/// by `xf`, and returns how many were appended. The appended pairs are sorted
223/// by `(a, b)`; elements already in `out` are kept and stay in front.
224///
225/// The test is exact, as for [`intersects`]. Passing the same mesh twice means
226/// two coincident copies: with [`Rigid::IDENTITY`] every `(t, t)` is a pair,
227/// along with every pair of neighbouring triangles.
228///
229/// # Errors
230/// As for [`intersects`], and [`QueryError::OutOfMemory`] when `out` cannot
231/// grow. On any error `out` keeps its original length.
232pub 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
244/// Checks the inputs in the documented order: the transform, then the bound
245/// (with its error message) if any, then each mesh against its BVH.
246pub(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
264/// The relative slack on pruning bounds; see [`beyond`].
265const PRUNE_SLACK: f64 = 1.0 / (1u64 << 30) as f64;
266
267/// The largest absolute coordinate of A's root box and of B's root box moved
268/// into A's space: the magnitude that the rounding error of the triangle
269/// distances scales with. 0 when either tree is empty (nothing is visited
270/// then). It is infinite only if a moved box overflows `f32`, and then
271/// [`beyond`] prunes nothing.
272fn 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
287/// Whether a pair of nodes whose boxes are `box_d` apart ([`box_distance`])
288/// can be dropped under `bound`, for meshes of coordinate magnitude
289/// `magnitude` ([`magnitude`]).
290///
291/// The box gap is a true lower bound on the distance between the boxes'
292/// contents up to `f64` rounding of its own, and with an exact transform
293/// (identity, 90° turns, translations that keep B's vertices on the `f32`
294/// grid) it is essentially exact. The triangle distances, however, carry an
295/// absolute error of a few `f64` ulps of the coordinates. So a pair whose
296/// computed distance is within `bound` can sit in boxes slightly more than
297/// `bound` apart. Pruning only beyond `bound + 2^-30 * (magnitude + bound)`
298/// covers that error with a wide margin, slivers included. The slack only
299/// adds work; which pairs are accepted is still decided by the computed
300/// distance against `bound` itself.
301fn beyond(box_d: f64, bound: f64, magnitude: f64) -> bool {
302    box_d > bound + PRUNE_SLACK * (magnitude + bound)
303}
304
305/// The Euclidean distance between boxes `p` and `q` in `f64`, 0 when they
306/// overlap or touch, and infinite when either is empty. A lower bound on the
307/// distance between any point of `p` and any point of `q`, up to `f64`
308/// rounding; queries prune on it through [`beyond`].
309fn 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
324/// The conditioning guard on [`plane_gap`]: a triangle's plane is used only
325/// when `|n|² >= PLANE_GUARD * |u|² * |v|²`, for its edge vectors `u`, `v`
326/// from its first corner and `n = u × v`. That is `sin θ >= 0.01` for the
327/// angle `θ` between `u` and `v`.
328///
329/// Each component of the computed `n` is off by at most a few `f64` ulps of
330/// `|u| |v|`, so its direction is off by about `ε / sin θ`: at most a few
331/// hundred ulps behind this guard, while for a sliver (`sin θ` near 0) the
332/// computed normal can point anywhere. [`plane_gap`] stays a lower bound for
333/// any direction (it measures the triangle's own extent along `n` too), but a
334/// wrong direction makes the bound useless, and a zero or underflowing `n` would
335/// divide by zero. So slivers fall back to the box gap alone.
336const PLANE_GUARD: f64 = 1e-4;
337
338/// A lower bound on the distance between triangles `a` and `b` (both in A's
339/// space), up to `f64` rounding of a few ulps of the coordinates: the larger
340/// of the gap between their boxes ([`Tri::box_gap`]) and the gap along each
341/// one's normal ([`Tri::plane_gap`]). 0 when nothing separates them.
342///
343/// Queries skip a pair whose bound is [`beyond`] their current bound, the rule
344/// they prune boxes by, so a skipped pair could not have been accepted: the
345/// slack covers this bound's rounding and the triangle distance's alike.
346/// Intersecting triangles get 0 up to rounding (both gaps are 0 in exact
347/// arithmetic), well under the slack. The queries test it with
348/// [`skip_pair`].
349#[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
357/// Whether a pair of triangles can be skipped under `bound`, for meshes of
358/// magnitude `m`: whether their `pair_lower_bound` (defined for the tests: the
359/// larger of [`Tri::box_gap`] and each one's [`Tri::plane_gap`]) is
360/// [`beyond`] `bound`.
361///
362/// The three gaps are tested one at a time, cheapest first, and the rest are
363/// not computed once one is beyond. That decides exactly as testing their
364/// maximum would: the maximum exceeds `bound` plus the slack exactly when one
365/// of them does (`f64::max` ignores a NaN, as `beyond` rejects one).
366fn 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/// A triangle in `f64` (in A's space) with everything [`skip_pair`] needs of
373/// it alone: its box and its slab along its computed normal. The queries compute it once per triangle and leaf pair,
374/// not once per triangle pair. The arithmetic is the same as computing it
375/// afresh for every pair, so the bounds are bit-identical either way.
376#[derive(Debug, Clone, Copy)]
377struct Tri {
378    /// The corners.
379    p: [P3; 3],
380    /// The low corner of the box: per axis the smallest of the corners.
381    lo: P3,
382    /// The high corner of the box: per axis the largest of the corners.
383    hi: P3,
384    /// The slab along the computed normal, or `None` when the triangle fails
385    /// [`PLANE_GUARD`].
386    slab: Option<Slab>,
387}
388
389/// A triangle's computed normal `n = u × v` (for its edge vectors `u`, `v`
390/// from its first corner), `|n|`, and the heights `lo..=hi` that its corners
391/// span along `n`, measured from its first corner: 0, `n·u` and `n·v`, all
392/// about 0.
393#[derive(Debug, Clone, Copy)]
394struct Slab {
395    /// The computed normal.
396    n: P3,
397    /// `|n|`.
398    len: f64,
399    /// The lowest height of the corners along `n`.
400    lo: f64,
401    /// The highest height of the corners along `n`.
402    hi: f64,
403}
404
405/// `p - q`.
406fn sub(p: P3, q: P3) -> P3 {
407    [p[0] - q[0], p[1] - q[1], p[2] - q[2]]
408}
409
410/// `p · q`.
411fn dot(p: P3, q: P3) -> f64 {
412    p[0] * q[0] + p[1] * q[1] + p[2] * q[2]
413}
414
415impl Tri {
416    /// A placeholder for unused slots of a [`Chunk`].
417    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    /// The triangle with corners `p`.
425    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        // Written so that NaN fails too.
436        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    /// The distance between this triangle's box and the box `lo..=hi`, as
449    /// [`box_distance`] does it but on `f64` corners.
450    ///
451    /// Every step is monotone under rounding (subtraction, `max`, squaring a
452    /// non-negative value, the sum, `sqrt`), so a box `lo..=hi` that holds
453    /// another one never gets a larger result than it: a gap to a union of
454    /// boxes is at most the gap to each of them, in `f64` as in exact
455    /// arithmetic.
456    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    /// The gap between this triangle `t` and the triangle with corners `o`
467    /// along `t`'s computed normal `n`: how far `o`'s corners all lie beyond
468    /// the slab that `t`'s corners span along `n`, over `|n|`. 0 when they
469    /// overlap along `n`, or when `t` fails [`PLANE_GUARD`].
470    ///
471    /// For any direction `n` (exactly as computed, rounding and all), every
472    /// point of `t` projects into `t`'s slab and every point of `o` into `o`'s,
473    /// so the gap between the slabs is at most the distance between the
474    /// triangles. With an exact normal `t`'s slab is just its plane (heights 0)
475    /// and this is the distance from the plane to `o`'s nearest corner, when
476    /// all of `o` lies strictly on one side. The heights carry `f64` rounding of
477    /// a few ulps of `|n|` times the coordinates, so the result is off by a few
478    /// ulps of the coordinates.
479    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
491/// The most triangles of one leaf that a [`Chunk`] holds. BVH leaves hold at
492/// most 16 triangles; a larger leaf would be taken in chunks of this size.
493const CHUNK: usize = 16;
494
495/// Up to [`CHUNK`] consecutive triangles of one leaf as [`Tri`]s, with the
496/// union of their boxes.
497///
498/// The traversal often visits several leaf pairs in a row that share a leaf
499/// (it splits one side down to its leaves against a fixed leaf of the other),
500/// so a chunk is only refilled when the triangles change.
501struct Chunk {
502    /// The address and length of the triangle ids it was filled from, which
503    /// identify them within one query (the BVHs are borrowed, so they cannot
504    /// move). `None` before the first fill.
505    from: Option<(*const TriangleId, usize)>,
506    /// The triangles, in the order of the ids; slots past `len` are unused.
507    tris: [Tri; CHUNK],
508    /// How many slots are in use.
509    len: usize,
510    /// The low corner of the union of the triangles' boxes.
511    lo: P3,
512    /// The high corner of the union of the triangles' boxes.
513    hi: P3,
514}
515
516impl Chunk {
517    /// An empty chunk.
518    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    /// Fills the chunk with the triangles `ids` (at most [`CHUNK`]), each
529    /// placed in A's space by `place`, unless it already holds them.
530    fn fill(&mut self, ids: &[TriangleId], place: impl Fn(TriangleId) -> [P3; 3]) {
531        // Only `CHUNK` slots are filled below, while `len` takes `ids.len()`.
532        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    /// The triangles in use.
551    fn tris(&self) -> &[Tri] {
552        &self.tris[..self.len]
553    }
554}
555
556/// The pair key for the intersection queries: 0 when the boxes overlap (closed),
557/// otherwise cull.
558pub(crate) fn overlap_key(p: &Aabb, q: &Aabb) -> Option<f64> {
559    p.overlaps(q).then_some(0.0)
560}
561
562/// The weight of the distance between box centres in [`distance_key`]'s
563/// order.
564const TIE_WEIGHT: f64 = 1e-12;
565
566/// `mesh_distance`'s pair key for A's box `p` and B's moved box `q`:
567/// `(order, gap)`, or `None` to cull the pair when `gap` is [`beyond`]
568/// `bound` (for meshes of magnitude `m`).
569///
570/// `gap` is [`box_distance`], the only value pruning reads, here and through
571/// the key in [`for_each_near_pair`]. `order` is
572/// `gap + TIE_WEIGHT * |centre(p) - centre(q)|`, and the traversal compares
573/// keys as tuples, so it orders by `order` alone unless two are equal. Since
574/// no pruning test reads `order`, the extra term cannot change what is
575/// pruned, and needs no slack of its own; it changes only the visiting order,
576/// which `mesh_distance`'s answer does not depend on (see
577/// `mesh_distance_with`).
578///
579/// The term breaks the ties among the many leaf box pairs of a near miss
580/// that touch (gap 0): the pair whose centres are closest overlaps the most
581/// and tends to hold the closest triangles, so the bound falls sooner. At
582/// `1e-12` per unit of centre distance it reorders only gaps that differ by
583/// less than about `1e-12` times the mesh size.
584fn 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
600/// The triangles of a leaf, or `None` for an inner node.
601fn 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
608/// A triangle of A in `f64`.
609fn tri_a(mesh: &Mesh, t: TriangleId) -> [P3; 3] {
610    points(mesh, t).map(|p| p.map(f64::from))
611}
612
613/// A triangle of B moved into A's space with [`Rigid::apply`], the transform
614/// [`Rigid::transform_box`] is conservative for.
615fn 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    /// Test-only: how many leaf pairs [`for_each_pair`] and
622    /// [`for_each_near_pair`] have tested on this thread. Only the calling
623    /// thread counts: work a parallel query hands to the pool is not seen,
624    /// so tests that read this (or [`count_skips`]) stay below the
625    /// parallel thresholds or force sequential.
626    static LEAF_VISITS: Cell<usize> = const { Cell::new(0) };
627    /// Test-only: how many triangle pairs [`for_each_near_pair`] has skipped
628    /// by a lower bound, and [`for_each_pair`] by disjoint boxes, on this
629    /// thread.
630    static PAIR_SKIPS: Cell<usize> = const { Cell::new(0) };
631}
632
633/// Test-only: counts `n` triangle pairs skipped without the exact test, on
634/// the calling thread only (see [`LEAF_VISITS`]): by a lower bound in
635/// [`for_each_near_pair` ([`chunk_pairs`]), and by disjoint boxes in
636/// [`for_each_pair`].
637fn 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
644/// Whether the closed boxes `lo..=hi` and `lo2..=hi2` are disjoint: on some
645/// axis one lies strictly beyond the other. Boxes that touch are not
646/// disjoint.
647fn 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
651/// [`traverse_pairs_with`] from the node pair `from` of A and B, or from
652/// the two roots with `None`.
653fn 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/// Calls `pair` for every triangle pair of every leaf pair that the traversal
668/// reaches with `key` whose triangles may share a point, B's triangle already
669/// in A's space, until `pair` returns `false`. For the intersection queries,
670/// which have no distance to bound. The traversal starts from the node pair
671/// `from`, or from the two roots with `None` ([`traverse_from`]).
672///
673/// Leaf pairs are taken a [`Chunk`] of each side at a time, as in
674/// [`for_each_near_pair`]. A pair is skipped only when its triangles' boxes are
675/// disjoint ([`boxes_apart`]), first a B triangle against the union box of
676/// A's chunk, then each A triangle against that B triangle. This is exact,
677/// with no rounding involved: a [`Tri`]'s box is the `min` and `max` of the
678/// very `f64` corners that `tri_tri_intersect` gets, so it holds the whole
679/// closed triangle, and a union box holds each of its boxes. Two triangles in
680/// disjoint closed boxes share no point, so a skipped pair could never have
681/// passed the exact test; a pair that merely touches has touching boxes, so it
682/// is never skipped. The pairs that `pair` accepts are therefore unchanged,
683/// and so are the queries' results.
684#[allow(clippy::too_many_arguments)] // The shared five, the start and two callbacks.
685pub(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/// The distance queries' traversal: calls `pair(a, pa, b, pb)` for every
728/// triangle pair of every leaf pair that the traversal reaches with `key`
729/// that [`skip_pair`] does not skip under `bound()` (for meshes of magnitude
730/// `m`), B's triangle already in A's space, until `pair` returns `false`.
731/// `bound()` is read afresh at every test, so it may shrink as `pair` runs.
732/// The traversal starts as in [`for_each_pair`].
733///
734/// A popped node pair is dropped when `gap(k)`, the box gap ([`box_distance`])
735/// carried in its key `k`, is [`beyond`] `bound()`.
736///
737/// Leaf pairs are taken a [`Chunk`] of each side at a time. Before any
738/// triangle pair is tested, each triangle's box is tested against the union
739/// box of the other side's chunk, and a triangle beyond it is skipped against
740/// that whole chunk. That gap is at most the box gap to each triangle of the
741/// chunk ([`Tri::box_gap`]: exactly, not just up to rounding), which is the
742/// first gap [`skip_pair`] tests, and `beyond` only grows with the gap. So
743/// every pair this skips, `skip_pair` would have skipped too, under the same
744/// `bound()`: the prefilter changes how much work is done, never which pairs
745/// reach `pair`.
746#[allow(clippy::too_many_arguments)] // The shared five, the start, three callbacks and `m`.
747fn 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
783/// The triangle pairs of two filled chunks for [`for_each_near_pair`], B's
784/// triangles in the outer loop. Returns `false` when `pair` did.
785fn 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    // Each A triangle's box gap to B's union box, once per chunk pair; the
795    // bound is checked against it afresh for each B triangle.
796    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
819/// The first intersecting pair `(a, b)` that the traversal finds, if any.
820fn 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    // Touching or intersecting meshes have distance 0, and the cheap exact
865    // intersection test finds a pair far faster than the distance search, which
866    // starts with no bound to prune by. Any intersecting pair will do.
867    //
868    // Measured against folding the test into the distance search (issue #22):
869    // `tri_tri_distance` already runs the exact test and the search stops at
870    // the first pair at distance 0, so the fold would only drop this pass. That
871    // saves the pass's cost on near misses, but took intersecting poses from
872    // 0.4 ms to 1.5 to 2.2 ms at 1M, since the search must first walk the
873    // pairs its order puts before an intersecting one.
874    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    // The distance of the best pair so far, or `max_distance` before one is
885    // found. Pairs whose boxes are farther (with slack) are pruned, in `key`
886    // and again in `visit`, as the bound shrinks after a pair is pushed.
887    //
888    // The answer does not depend on the order in which pairs are visited
889    // (except which pair is reported for a distance of 0, which is
890    // unspecified): the bound is always the computed distance of some pair or
891    // `max_distance`, so it never falls below the minimum, and a pair is only
892    // ever skipped under the `beyond` slack rule, so no pair within the bound
893    // is lost. The minimum `dist2`, ties to the smallest `(a, b)`, is then
894    // found whatever the order. The order only decides how soon the bound
895    // falls, hence how much is pruned; that is what `distance_key` tunes.
896    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        // Near misses leave many leaf pairs at key 0 (boxes touch), so most
911        // of their triangle pairs are dropped by the lower bounds in
912        // `for_each_near_pair`, not by the boxes.
913        |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            // Nothing beats 0; stop at the first touching pair.
927            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
939/// Triangles of A and B together from which [`triangles_near`] and
940/// [`intersecting_pairs`] split their traversal into parts searched in
941/// parallel ([`split_pairs`]), with the `parallel` feature.
942///
943/// Measured on the i5-13600K with a terrain and a flipped copy whose sheets
944/// cross along curves: at 2 x 648 triangles parallel is 1.1 to 1.25 times
945/// slower, at 2 x 800 even, at 2 x 968 1.7 times faster (`triangles_near`)
946/// and 1.3 times (`intersecting_pairs`), and at 2 x 131k 15 and 14 times.
947const PAR_MIN_PAIR_TRIS: usize = 2048;
948
949/// Whether a pair query on these meshes splits its traversal: with the
950/// feature, not forced off, and at least [`PAR_MIN_PAIR_TRIS`] triangles.
951fn 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)] // As for `triangles_near`.
958fn 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/// The search of [`triangles_near`] and [`triangles_near_limited`]: B's side
984/// only with `out_b`, and abandoned (`Ok(false)`, both vectors restored) as
985/// soon as more than `limit` near pairs are found. `usize::MAX` means no
986/// limit, and then the pairs are not counted.
987#[allow(clippy::too_many_arguments)] // As for `triangles_near`, with the limit.
988fn 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    // Near pairs found so far, across all parts, when there is a limit.
1006    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    // The first real failure of any part, which a part stopped by the limit
1014    // must not hide.
1015    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    // Calls `found(a, b)` for every near pair under `from`, until it fails
1021    // or passes the limit.
1022    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                // Exactly `tri_tri_distance(pa, pb).dist2.sqrt() <= margin`
1039                // (0 for pairs that intersect), but stops at the first
1040                // candidate within the margin.
1041                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        // Each part's triangles, sorted without duplicates, in part order;
1061        // appended together they sort to the same result.
1062        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        // A failed push restored its own vector; restore the other one too.
1103        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            // A real failure anywhere wins over the limit.
1108            (_, 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
1117/// Triangle pairs per part of a [`NearSearch`], at most: a node pair is
1118/// split until the product of its two sides' triangle counts is no larger
1119/// (or both are leaves), so that searching a part takes a bounded time even
1120/// where every pair is near (about 5 ms single-threaded on the i5-13600K).
1121const NEAR_PART_PAIRS: u64 = 1 << 18;
1122
1123/// What a triangle pair test counts for in a [`NearSearch::step`] budget,
1124/// against one node pair test (a box distance).
1125const NEAR_TEST_WORK: usize = 4;
1126
1127/// Parts a [`NearSearch`] step searches at once on the pool, with the
1128/// `parallel` feature.
1129const NEAR_PAR_BATCH: usize = 32;
1130
1131/// What a mesh and its BVH were when a [`NearSearch`] started.
1132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1133struct Stamp {
1134    uid: u64,
1135    epoch: u32,
1136    revision: u64,
1137    generation: u64,
1138    /// An undo brings the revision back but keeps slots it added.
1139    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/// [`triangles_near_limited`] cut into steps, so that a caller can yield
1155/// between them.
1156///
1157/// It works through a list of node pairs of the two BVHs, starting with the
1158/// roots: each [`NearSearch::step`] splits the large pairs it takes (the
1159/// side with more triangles, dropping children beyond the margin as the
1160/// traversal does) and searches the parts small enough, whole, until it has
1161/// done a given amount of work. A side's triangle count is its subtree's
1162/// ([`Bvh::subtree_triangles`]), exact on any tree, balanced or not, so a
1163/// part holds at most 2^18 triangle pairs (or two leaves), and the split
1164/// depends on nothing but the trees.
1165/// The parts partition the leaf pairs, so every near pair is found in
1166/// exactly one of them, and the near pairs are counted against the limit
1167/// across parts. The
1168/// result ([`NearSearch::finish`]) is therefore the same as from
1169/// [`triangles_near_limited`]: whether more than `limit` pairs are near, and
1170/// otherwise the same sorted triangles of A. With the `parallel` feature a
1171/// step searches several parts at once.
1172///
1173/// The search is tied to the meshes and BVHs as they were when it started;
1174/// stepping it on others fails.
1175#[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    /// The node pairs left; any order.
1184    parts: Vec<(NodeId, NodeId)>,
1185    /// The triangles of A found so far, each part's sorted without
1186    /// duplicates, in the order the parts were searched.
1187    near: Vec<TriangleId>,
1188    /// Near pairs found so far.
1189    found: usize,
1190    /// More than `limit` pairs are near.
1191    over: bool,
1192    /// Test-only: every part searched.
1193    #[cfg(test)]
1194    searched: Vec<(NodeId, NodeId)>,
1195}
1196
1197impl NearSearch {
1198    /// A search for the triangles of A within `margin` of B, with B placed
1199    /// in A's space by `xf`, giving up past `limit` near pairs, as
1200    /// [`triangles_near_limited`].
1201    ///
1202    /// # Errors
1203    /// As for [`triangles_near_limited`].
1204    #[allow(clippy::too_many_arguments)] // As for `triangles_near_limited`.
1205    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        // The roots, unless already beyond the margin (as `split_pairs`).
1230        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    /// Whether every part is searched, or the limit is passed.
1249    pub fn is_done(&self) -> bool {
1250        self.over || self.parts.is_empty()
1251    }
1252
1253    /// Searches whole parts until about `budget` units of work are done (at
1254    /// least one part, unless done), or the limit is passed: a node pair
1255    /// tested counts as one, a triangle pair tested as four. Returns whether
1256    /// the search is done.
1257    ///
1258    /// # Errors
1259    /// [`QueryError::InvalidInput`] when the meshes or BVHs are not those
1260    /// the search started on; [`QueryError::OutOfMemory`]. The search is
1261    /// unusable after an error.
1262    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            // Up to `batch` parts small enough to search; larger ones split.
1292            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                // The larger side, or the other when it is a leaf.
1306                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            // Each part's triangles of A, sorted without duplicates, or
1332            // `None` when it stopped at the limit.
1333            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    /// Once done: whether no more than `limit` pairs are near (as the result
1406    /// of [`triangles_near_limited`]), having appended A's near triangles to
1407    /// `out_a`, sorted without duplicates, when so. `None`, appending
1408    /// nothing, before the search is done.
1409    ///
1410    /// # Errors
1411    /// [`QueryError::OutOfMemory`] when `out_a` cannot grow; it then keeps
1412    /// its length.
1413    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
1427/// Why [`triangles_near_limited_with`] stopped early: past its limit (on
1428/// purpose), or on a real failure.
1429enum 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    // Calls `found(a, b)` for every intersecting pair under `from`, until it
1443    // fails.
1444    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        // Each part's pairs in part order, sorted below.
1471        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    // Each triangle is in exactly one leaf, so every pair is found once.
1484    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    /// Rotation by 180° about x: B is turned upside down to face A.
1499    const FLIP_X: M3 = [[1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]];
1500    /// Rotation by 90° about z, with exact entries.
1501    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    /// Identity, 30° and an exact 90° about z, and a random rotation.
1513    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    /// B turned by `rot` after the flip, with its lowest vertex (once turned;
1527    /// on ties the one nearest B's middle) right over A's vertex `(3, 2, 2)`,
1528    /// one of A's highest, near the middle, and `gap` above it. So B is
1529    /// `gap` above A when `gap > 0`, and its lowest vertex touches or dips
1530    /// below that peak of A otherwise.
1531    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    /// Two wavy meshes with their BVHs, for both build qualities.
1559    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    /// A scene posed by [`pose`].
1571    struct Posed {
1572        ma: Mesh,
1573        ba: Bvh,
1574        mb: Mesh,
1575        bb: Bvh,
1576        xf: Rigid,
1577        gap: f64,
1578        /// Whether the rotation is about z, so that B is level over A.
1579        level: bool,
1580    }
1581
1582    /// Every scene under every rotation, separated (0.3), touching (0) and
1583    /// overlapping (-0.3).
1584    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    /// Every pair of alive triangles: whether they intersect, and their distance.
1608    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    /// The minimum `dist2`, ties to the smallest pair.
1636    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            // Every overlapping pose meets, the tilted random one included,
1708            // so the contact paths run under a general rotation too.
1709            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                // Up to the limit, the same triangles of A as `triangles_near`.
1780                assert_eq!(run(usize::MAX), (true, want.clone()));
1781                assert_eq!(run(pairs), (true, want.clone()));
1782                if pairs > 0 {
1783                    // Past it, nothing appended.
1784                    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        // Large enough to split the traversal with the `parallel` feature.
1797        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        // The near pairs in all: the smallest limit the sequential search
1818        // completes under.
1819        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        // Exactly at the total, every part's pairs together just fit; one
1839        // fewer is passed, whatever the threads.
1840        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                    // The overflow fallback may find another intersecting
1880                    // pair than the stacked traversal, but it must be an
1881                    // intersecting one, with coincident points.
1882                    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                // The pair intersects, or its positive distance rounds to 0
1899                // (seen here: a vertex of B one f64 ulp below a peak of A,
1900                // where both closest points round to the peak).
1901                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            // B's lowest vertex is `gap` above A's highest; level poses put
1918            // one right over the other.
1919            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            // `max_distance` is inclusive.
1926            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    /// A flat triangle A and a single-triangle B that touches it with one vertex
1939    /// at `[0.25, 0.25, 0]`, under the given transform.
1940    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    /// The intersection traversal's box prefilter keeps pairs whose boxes only
1949    /// touch. A and B share just the corner `(1, 0, 0)`, where their boxes
1950    /// meet at that single point too (A's box ends at x = 1 and y = 0 from
1951    /// one side, B's starts there from the other); moved by one `f32` ulp
1952    /// along x, both the triangles and the boxes come apart.
1953    #[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    /// The intersection queries skip triangle pairs by their boxes, and still
1989    /// match the brute force with leaves of 1 to 16 triangles (so that a
1990    /// leaf's union box is much larger than its triangles' boxes): on the
1991    /// posed wavy meshes, and on bumpy sheets pushed into each other, turned
1992    /// and tilted.
1993    #[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    /// A random turn by up to about 3° about a random axis (not exactly
2052    /// orthonormal; see [`orthonormal`]).
2053    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            // Lifted by 2^-20, they no longer touch.
2095            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            // Lifted by 0.5, the distance is exactly 0.5 and `max_distance` is
2107            // inclusive.
2108            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    /// Exact transforms keep the box gap exact while the triangle distances
2127    /// carry `f64` rounding at the coordinates' magnitude. A's vertex `p` is
2128    /// A's extreme point along x, and B's triangles lie in the plane `gap`
2129    /// beyond it, around `p`'s projection. So the true distance equals the box
2130    /// gap, and the computed one (a projection onto B's face) can fall just
2131    /// below it. B's two triangles sit in separate leaves, so the second leaf
2132    /// pair is pruned against the bound set by the first.
2133    #[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); // The f32 spacing in [512, 1024).
2142        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            // Each triangle surrounds (p.y, p.z): corners below-left,
2153            // below-right and above.
2154            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            // The smallest f32 bound that admits the computed distance.
2180            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    /// Pairs whose box gap is above the bound by less than the slack are still
2198    /// tested, through each query's `key` and `mesh_distance`'s `visit` check.
2199    /// A's far triangle sets the magnitude to 1e6 (slack about 9.3e-4); B's two
2200    /// triangles face A's vertex at the origin from 0.5 and 0.5001, in
2201    /// separate leaves.
2202    #[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        // `mesh_distance`'s `visit` check: the second face is popped after
2235        // the bound fell to 0.5, with its key 0.5001 inside the slack.
2236        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        // Both `key`s: a bound just under 0.5 culls nothing near the origin.
2243        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    /// A near miss (B `1e-4` above A, so many leaf boxes touch): the per-pair
2255    /// lower bound skips most triangle pairs, and the results still match the
2256    /// brute force.
2257    #[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        // Under a margin, boxes cull more pairs (their bound is fixed from the
2295        // start), so a pose can leave nothing for the lower bound to skip.
2296        assert!(near_skips.iter().all(|&n| n > 0), "{near_skips:?}");
2297    }
2298
2299    /// A smooth bumpy `n` x `n` sheet, `z = 0.8 sin(0.7 x) cos(0.5 y)`, so that
2300    /// a flipped copy over it meets it at a single place, as the bench terrain
2301    /// does.
2302    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    /// The brute-force answer of `mesh_distance` with an infinite bound, for
2321    /// meshes that do not meet.
2322    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    /// Near misses, the case the leaf-pair caches, the leaf prefilters and the
2335    /// tie order speed up: B a tiny gap over A, level or tilted, on the wavy
2336    /// meshes and on smooth bumpy sheets, with leaves of 1 to 16 triangles.
2337    /// `mesh_distance` gives exactly the oracle's pair, points and distance,
2338    /// through the stack and the overflow fallback, and `max_distance` just
2339    /// above and below the distance keeps or drops it.
2340    #[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                // `triangles_near` shares the lower bound and the prefilters.
2379                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        // Wavy meshes: B's lowest vertex `gap` over a peak of A.
2389        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        // Bumpy sheets: B flipped over A, turned a little, and lowered until
2397        // the gap is near `gap`.
2398        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                // Orthonormalized, so that `Rigid::validate` accepts it.
2403                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    /// `m` with its rows made orthonormal (Gram–Schmidt), and the third row
2421    /// their cross product, so that it is a proper rotation.
2422    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        // Random pairs, slivers and coplanar ones included: the bound never
2442        // exceeds the computed distance by more than rounding.
2443        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                // A sliver: the third corner nearly on the first edge.
2451                1 => b[2] = std::array::from_fn(|k| 0.5 * (b[0][k] + b[1][k]) + 1e-9 * r()),
2452                // Coplanar with A: A moved within its own plane.
2453                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    /// The leaf prefilter's premise: a triangle's box gap to the union box of
2472    /// a chunk is never above its box gap to any triangle of the chunk, in
2473    /// `f64` exactly, so the prefilter skips only pairs `skip_pair` skips.
2474    #[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        // A box gap above the bound by less than the slack is kept; one above
2509        // it by more is pruned; an infinite bound prunes nothing.
2510        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    /// Runs all four queries, expecting an error from each, and checks that
2548    /// the outputs are untouched.
2549    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        // Neighbours (two triangles sharing an edge) touch along that edge,
2611        // so both orders are pairs.
2612        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        // The BVH of `m` is passed with `other`: validation comes before `check`.
2650        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        // Infinite bounds are fine.
2664        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        // `ba` was built for a different mesh with the same content.
2686        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        // The first push (to `out_a`) succeeds, the second (to `out_b`) fails.
2721        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        // A failed push in the limited search is an error, not a stop at the
2729        // limit; reaching the limit first is a stop.
2730        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            // `mb` lifted just over `ma`: the highest peak of `ma` (0.9976,
2756            // at x = 56) and the deepest dip of `mb` (-0.9825, at y = 7) are
2757            // 0.0499 apart at a lift of 2.03, just under the margin 0.05, and
2758            // the next pairs of peak and dip just over it.
2759            let touching = Rigid {
2760                rotation: rot_z(0.0),
2761                translation: [0.0, 0.0, 2.03],
2762            };
2763            // `mb` stood upright as a wavy curtain `x = 6.73 - sin(0.7 y)`
2764            // from `z = -0.5` up, its foot in the trough of `ma` around
2765            // `x = 6.73` (where `ma` stays below -0.7): the root boxes
2766            // overlap, so both searches split, but no pair is near.
2767            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            // Per pose: the bounds on each near list's length and on the
2772            // crossing count, and whether the crossing search splits (the
2773            // lifted sheet's box is clear of the other's).
2774            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                // Less the sentinel each list started with.
2787                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    /// Runs a [`NearSearch`] to the end in steps of `budget` tested pairs,
2804    /// appending to `out` (as [`triangles_near_limited`] does); returns its
2805    /// result and the number of steps.
2806    #[allow(clippy::too_many_arguments)] // The search's inputs and the budget.
2807    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        // The total number of near pairs, from the one-call search.
2879        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                    // One part at a time without the pool: several steps.
2902                    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    /// Groups of `group` tiny triangles at x = -q^g, g < `groups`: SAH
2914    /// peels off a few groups per level, so the BVH is a split chain far
2915    /// deeper than log2 of its triangles.
2916    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        // Issue #64: a part's size comes from the real subtree counts, so on
2935        // a degenerate tree too no part holds more than `NEAR_PART_PAIRS`
2936        // triangle pairs (unless both sides are leaves). Estimated from the
2937        // depth (each level halving), a chain's parts held most of the mesh.
2938        // Every pair is near (the meshes overlap well within the margin),
2939        // the worst case for a part's time. The result is the one call's.
2940        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        // Large enough for several parts per step's batch on the pool.
2983        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}