Skip to main content

ogeom_mesh/
bvh.rs

1//! A bounding-volume hierarchy over a triangle mesh: the nearest triangle to
2//! a point, every triangle within a ball, and the first triangle along a ray.
3//!
4//! Built once, immutable after, and shared freely across threads. Every
5//! answer is the answer brute force gives over the same triangles with the
6//! same arithmetic, ties going to the lowest triangle index, so it depends on
7//! neither the tree's shape, the order it is walked in, nor the thread count.
8//!
9//! # Why the answers equal brute force
10//!
11//! A query visits a node only if the node's box could hold a triangle that
12//! passes the query's own test. That is decided with the triangle test's own
13//! floating-point operations applied to the box's corners, and rounding is
14//! monotone (a larger operand never rounds to a smaller result), so a box's
15//! computed bound can never exclude a triangle inside it whose computed value
16//! passes:
17//!
18//! - **nearest and ball**: the closest point on a triangle is clamped into the
19//!   triangle's own box before its distance is taken, so each axis gap to it
20//!   is at least the computed gap to any box that holds the triangle. A
21//!   filtered nearest query passes over the triangles its filter refuses
22//!   before measuring them, and a box's gap bounds the triangles it holds
23//!   that pass as it bounds them all;
24//! - **ray**: the ray is sheared onto the `z` axis and every vertex
25//!   transformed by the same operations the box corners are, so the computed
26//!   vertices of a triangle lie within the box's computed sheared bounds. A
27//!   hit is decided by the exact signs of three orientations of the origin
28//!   against those computed vertices, so a hit needs the origin within their
29//!   hull, and the hit's depth is clamped into their depth range.
30//!
31//! # Watertight rays
32//!
33//! Two triangles that share an edge share its two vertices, so they compute
34//! the same sheared coordinates for it, and the exact orientation of the ray
35//! against it has opposite signs from the two sides. A ray through the edge
36//! (or a vertex) is therefore inside at least one of them: it cannot slip
37//! between triangles, however it is aimed.
38
39use ogeom_core::{Exact, OgeomResult, Predicates, Sign, ogeom_bail};
40use ogeom_math::{Aabb, Point, Vector};
41use ogeom_topo::{Model, Shape, Triangulation};
42
43use crate::discretize::Deflection;
44
45/// What a query found on the mesh.
46#[derive(Debug, Clone, Copy, PartialEq)]
47pub struct Hit {
48    /// The triangle, as its index in the mesh the hierarchy was built from.
49    pub triangle: u32,
50    /// The point on the triangle.
51    pub point: Point,
52    /// For a nearest query, the distance from the query point to
53    /// [`Hit::point`]; for a ray, the distance along the ray.
54    pub distance: f64,
55    /// [`Hit::point`]'s weights on the triangle's three corners, in the
56    /// triangle's own corner order. They sum to one.
57    pub barycentric: [f64; 3],
58}
59
60type P = [f64; 3];
61
62/// One node of the tree, its box tight around the triangles below it.
63#[derive(Debug, Clone, Copy)]
64struct Node {
65    low: P,
66    high: P,
67    /// A leaf's first slot, or an interior node's right child. Its left
68    /// child is the next node.
69    first: u32,
70    /// How many triangles a leaf holds; zero for an interior node.
71    count: u32,
72}
73
74/// A bounding-volume hierarchy over a mesh's triangles.
75///
76/// Built with a surface-area heuristic over binned centroids. Holds its own
77/// copy of the triangles' corners, so the mesh it was built from may be
78/// dropped or changed; queries answer about the mesh as it was.
79#[derive(Debug, Clone)]
80pub struct Bvh {
81    nodes: Vec<Node>,
82    /// Each slot's triangle corners, slots in leaf order.
83    corners: Vec<[P; 3]>,
84    /// The triangle in each slot.
85    order: Vec<u32>,
86    /// The slot holding each triangle.
87    slot: Vec<u32>,
88}
89
90/// The most triangles a leaf holds when splitting it would not pay.
91const LEAF_MOST: usize = 8;
92
93/// How many bins each axis's centroids are counted into to price a split.
94const BINS: usize = 16;
95
96/// How many queries a batch hands a worker at a time.
97pub(crate) const BATCH: usize = 256;
98
99impl Bvh {
100    /// Build the hierarchy over a mesh's triangles.
101    ///
102    /// Degenerate triangles are kept: a nearest query can still land on
103    /// one, and a ray through one finds it only where it is a proper
104    /// triangle as the ray sees it.
105    ///
106    /// # Errors
107    ///
108    /// [`OgeomError::Range`](ogeom_core::OgeomError::Range) if a triangle
109    /// names a vertex the mesh does not have, and
110    /// [`OgeomError::Numeric`](ogeom_core::OgeomError::Numeric) if a
111    /// triangle's corner is not finite.
112    pub fn build(mesh: &Triangulation) -> OgeomResult<Self> {
113        let mut corners = Vec::with_capacity(mesh.triangles.len());
114        for triangle in &mesh.triangles {
115            let mut three = [[0.0; 3]; 3];
116            for (corner, &k) in three.iter_mut().zip(triangle) {
117                let Some(p) = mesh.positions.get(k as usize) else {
118                    ogeom_bail!(
119                        Range,
120                        "a triangle names vertex {k} of {}",
121                        mesh.positions.len()
122                    );
123                };
124                if !(p.x.is_finite() && p.y.is_finite() && p.z.is_finite()) {
125                    ogeom_bail!(Numeric, "a triangle's corner {k} is not finite");
126                }
127                *corner = [p.x, p.y, p.z];
128            }
129            corners.push(three);
130        }
131        if u32::try_from(corners.len()).is_err() {
132            ogeom_bail!(Range, "a mesh of more than four billion triangles");
133        }
134        Ok(Self::over(&corners))
135    }
136
137    /// Build the hierarchy over a shape's triangulation at a deflection.
138    ///
139    /// The triangle indices are those of
140    /// [`triangulate`](crate::triangulate::triangulate) for the same
141    /// arguments, which is deterministic, and [`Bvh::triangle`] gives each
142    /// triangle's corners without it.
143    ///
144    /// # Errors
145    ///
146    /// As [`triangulate`](crate::triangulate::triangulate) and
147    /// [`Bvh::build`].
148    pub fn of_shape(
149        model: &Model,
150        shape: &Shape,
151        deflection: Deflection,
152        tol: ogeom_core::Tolerances,
153    ) -> OgeomResult<Self> {
154        Self::build(&crate::triangulate::triangulate(
155            model, shape, deflection, tol,
156        )?)
157    }
158
159    fn over(corners: &[[P; 3]]) -> Self {
160        let mut order: Vec<u32> = (0..corners.len())
161            .map(|i| u32::try_from(i).unwrap_or(u32::MAX))
162            .collect();
163        let boxes: Vec<(P, P)> = corners.iter().map(box_of).collect();
164        let nodes = build_nodes(&boxes, &mut order);
165        let mut slot = vec![0_u32; order.len()];
166        for (at, &t) in order.iter().enumerate() {
167            slot[t as usize] = u32::try_from(at).unwrap_or(u32::MAX);
168        }
169        Self {
170            nodes,
171            corners: order.iter().map(|&t| corners[t as usize]).collect(),
172            order,
173            slot,
174        }
175    }
176
177    /// How many triangles the hierarchy holds.
178    #[must_use]
179    pub fn triangle_count(&self) -> usize {
180        self.order.len()
181    }
182
183    /// Whether the hierarchy holds no triangle.
184    #[must_use]
185    pub fn is_empty(&self) -> bool {
186        self.order.is_empty()
187    }
188
189    /// A triangle's three corners, or `None` past the last triangle.
190    #[must_use]
191    pub fn triangle(&self, triangle: u32) -> Option<[Point; 3]> {
192        let slot = *self.slot.get(triangle as usize)?;
193        Some(self.corners[slot as usize].map(|[x, y, z]| Point::new(x, y, z)))
194    }
195
196    /// The bounds of every triangle.
197    #[must_use]
198    pub fn bounds(&self) -> Aabb {
199        self.nodes.first().map_or(Aabb::EMPTY, |root| {
200            Aabb::of_corners(point(root.low), point(root.high))
201        })
202    }
203
204    /// The point on the mesh nearest `p`, no farther than `max`; the lowest
205    /// triangle index among equally near ones. `max` may be infinite.
206    #[must_use]
207    pub fn nearest(&self, p: Point, max: f64) -> Option<Hit> {
208        self.nearest_with(p, max, &mut Vec::new(), |_| true)
209    }
210
211    /// [`Bvh::nearest`] for each point, in parallel, answers in point order.
212    #[must_use]
213    pub fn nearest_many(&self, points: &[Point], max: f64) -> Vec<Option<Hit>> {
214        batched(
215            points,
216            |&p| p,
217            |&p, stack| self.nearest_with(p, max, stack, |_| true),
218        )
219    }
220
221    /// [`Bvh::nearest`] among the triangles `keep` accepts: the point on
222    /// them nearest `p`, no farther than `max`, the lowest triangle index
223    /// among equally near ones. The answer is brute force's over the kept
224    /// triangles alone, however the tree holds the others.
225    #[must_use]
226    pub fn nearest_where(&self, p: Point, max: f64, keep: impl Fn(u32) -> bool) -> Option<Hit> {
227        self.nearest_with(p, max, &mut Vec::new(), keep)
228    }
229
230    /// [`Bvh::nearest_where`] for each point, in parallel, answers in point
231    /// order. `keep` is asked with the point's index and a triangle.
232    #[must_use]
233    pub fn nearest_many_where(
234        &self,
235        points: &[Point],
236        max: f64,
237        keep: impl Fn(usize, u32) -> bool + Sync,
238    ) -> Vec<Option<Hit>> {
239        let queries: Vec<usize> = (0..points.len()).collect();
240        batched(
241            &queries,
242            |&i| points[i],
243            |&i, stack| self.nearest_with(points[i], max, stack, |t| keep(i, t)),
244        )
245    }
246
247    fn nearest_with(
248        &self,
249        p: Point,
250        max: f64,
251        stack: &mut Vec<(u32, f64)>,
252        keep: impl Fn(u32) -> bool,
253    ) -> Option<Hit> {
254        let p = [p.x, p.y, p.z];
255        let reach = max * max;
256        if self.nodes.is_empty() || !p.iter().all(|c| c.is_finite()) || max.is_nan() || max < 0.0 {
257            return None;
258        }
259        // The best so far as (squared distance, triangle, slot); `reach`
260        // stands in until a triangle is found.
261        let mut best: Option<(f64, u32, usize)> = None;
262        let mut bound = reach;
263        stack.clear();
264        let root = box_gap(&self.nodes[0], p);
265        if root > bound {
266            return None;
267        }
268        stack.push((0, root));
269        while let Some((at, near)) = stack.pop() {
270            if near > bound {
271                continue;
272            }
273            let node = &self.nodes[at as usize];
274            if node.count > 0 {
275                let first = node.first as usize;
276                for slot in first..first + node.count as usize {
277                    let triangle = self.order[slot];
278                    if !keep(triangle) {
279                        continue;
280                    }
281                    let (_, _, d2) = closest(p, &self.corners[slot]);
282                    let better = match best {
283                        None => d2 <= bound,
284                        Some((held, index, _)) => d2 < held || (d2 == held && triangle < index),
285                    };
286                    if better {
287                        best = Some((d2, triangle, slot));
288                        bound = d2;
289                    }
290                }
291                continue;
292            }
293            let (left, right) = (at + 1, node.first);
294            let gl = box_gap(&self.nodes[left as usize], p);
295            let gr = box_gap(&self.nodes[right as usize], p);
296            // The nearer child is popped first.
297            let (near, far) = if gl <= gr {
298                ((left, gl), (right, gr))
299            } else {
300                ((right, gr), (left, gl))
301            };
302            if far.1 <= bound {
303                stack.push(far);
304            }
305            if near.1 <= bound {
306                stack.push(near);
307            }
308        }
309        let (_, triangle, slot) = best?;
310        let (q, barycentric, d2) = closest(p, &self.corners[slot]);
311        Some(Hit {
312            triangle,
313            point: point(q),
314            distance: d2.sqrt(),
315            barycentric,
316        })
317    }
318
319    /// Every triangle with a point within `radius` of `centre`, in index
320    /// order.
321    #[must_use]
322    pub fn within_ball(&self, centre: Point, radius: f64) -> Vec<u32> {
323        let c = [centre.x, centre.y, centre.z];
324        let reach = radius * radius;
325        let mut found = Vec::new();
326        if self.nodes.is_empty()
327            || !c.iter().all(|v| v.is_finite())
328            || radius.is_nan()
329            || radius < 0.0
330        {
331            return found;
332        }
333        let mut stack = vec![0_u32];
334        while let Some(at) = stack.pop() {
335            let node = &self.nodes[at as usize];
336            if box_gap(node, c) > reach {
337                continue;
338            }
339            if node.count > 0 {
340                let first = node.first as usize;
341                for slot in first..first + node.count as usize {
342                    if closest(c, &self.corners[slot]).2 <= reach {
343                        found.push(self.order[slot]);
344                    }
345                }
346            } else {
347                stack.push(node.first);
348                stack.push(at + 1);
349            }
350        }
351        found.sort_unstable();
352        found
353    }
354
355    /// Every triangle whose bounding box passes `test`, in index order.
356    ///
357    /// `test` prunes the walk: a node's box that fails it is not entered,
358    /// so `test` must pass every box that contains a box it passes (a
359    /// region overlap does, a containment test does not).
360    #[must_use]
361    pub fn triangles_where(&self, test: impl Fn(&Aabb) -> bool) -> Vec<u32> {
362        let mut found = Vec::new();
363        if self.nodes.is_empty() {
364            return found;
365        }
366        let mut stack = vec![0_u32];
367        while let Some(at) = stack.pop() {
368            let node = &self.nodes[at as usize];
369            if !test(&Aabb::of_corners(point(node.low), point(node.high))) {
370                continue;
371            }
372            if node.count > 0 {
373                let first = node.first as usize;
374                for slot in first..first + node.count as usize {
375                    let (low, high) = box_of(&self.corners[slot]);
376                    if test(&Aabb::of_corners(point(low), point(high))) {
377                        found.push(self.order[slot]);
378                    }
379                }
380            } else {
381                stack.push(node.first);
382                stack.push(at + 1);
383            }
384        }
385        found.sort_unstable();
386        found
387    }
388
389    /// The first triangle along a ray, no farther than `max` along it, and
390    /// the lowest triangle index among equally far ones.
391    ///
392    /// `direction` need not be unit length; [`Hit::distance`] is measured
393    /// along it in model units. A triangle faces the ray when the ray meets
394    /// the side its counter-clockwise winding points out of; with
395    /// `both_sides` false only those count. A hit at the origin itself
396    /// counts, at distance zero. A ray in a triangle's plane does not hit
397    /// it, having no area to cross there; in a closed mesh the triangles
398    /// across the edges it passes through are hit instead.
399    #[must_use]
400    pub fn ray(&self, origin: Point, direction: Vector, max: f64, both_sides: bool) -> Option<Hit> {
401        let shear = Shear::new(origin, direction)?;
402        self.ray_with(&shear, max, both_sides, &mut Vec::new())
403    }
404
405    /// [`Bvh::ray`] for each origin and direction, in parallel, answers in
406    /// input order.
407    ///
408    /// # Errors
409    ///
410    /// [`OgeomError::Dimension`](ogeom_core::OgeomError::Dimension) if there
411    /// are not as many directions as origins.
412    pub fn rays(
413        &self,
414        origins: &[Point],
415        directions: &[Vector],
416        max: f64,
417        both_sides: bool,
418    ) -> OgeomResult<Vec<Option<Hit>>> {
419        if origins.len() != directions.len() {
420            ogeom_bail!(
421                Dimension,
422                "{} ray origins against {} directions",
423                origins.len(),
424                directions.len()
425            );
426        }
427        let pairs: Vec<(Point, Vector)> = origins
428            .iter()
429            .copied()
430            .zip(directions.iter().copied())
431            .collect();
432        Ok(batched(
433            &pairs,
434            |&(o, _)| o,
435            |&(o, d), stack| {
436                let shear = Shear::new(o, d)?;
437                self.ray_with(&shear, max, both_sides, stack)
438            },
439        ))
440    }
441
442    /// Every triangle along a ray within `max`, nearest first, ties in
443    /// index order. Each is the hit [`Bvh::ray`] reports when it is first.
444    #[must_use]
445    pub fn hits_along(
446        &self,
447        origin: Point,
448        direction: Vector,
449        max: f64,
450        both_sides: bool,
451    ) -> Vec<Hit> {
452        let mut hits = Vec::new();
453        let Some(shear) = Shear::new(origin, direction) else {
454            return hits;
455        };
456        if self.nodes.is_empty() || max.is_nan() || max < 0.0 {
457            return hits;
458        }
459        let mut stack = vec![0_u32];
460        while let Some(at) = stack.pop() {
461            let node = &self.nodes[at as usize];
462            if shear.box_depth(node).is_none_or(|near| near > max) {
463                continue;
464            }
465            if node.count > 0 {
466                let first = node.first as usize;
467                for slot in first..first + node.count as usize {
468                    if let Some(hit) = shear.triangle(&self.corners[slot], max, both_sides) {
469                        hits.push(Hit {
470                            triangle: self.order[slot],
471                            ..hit
472                        });
473                    }
474                }
475            } else {
476                stack.push(node.first);
477                stack.push(at + 1);
478            }
479        }
480        hits.sort_by(|a, b| {
481            a.distance
482                .total_cmp(&b.distance)
483                .then(a.triangle.cmp(&b.triangle))
484        });
485        hits
486    }
487
488    fn ray_with(
489        &self,
490        shear: &Shear,
491        max: f64,
492        both_sides: bool,
493        stack: &mut Vec<(u32, f64)>,
494    ) -> Option<Hit> {
495        if self.nodes.is_empty() || max.is_nan() || max < 0.0 {
496            return None;
497        }
498        let mut best: Option<Hit> = None;
499        let mut bound = max;
500        stack.clear();
501        let near = shear.box_depth(&self.nodes[0])?;
502        if near > bound {
503            return None;
504        }
505        stack.push((0, near));
506        while let Some((at, near)) = stack.pop() {
507            if near > bound {
508                continue;
509            }
510            let node = &self.nodes[at as usize];
511            if node.count > 0 {
512                let first = node.first as usize;
513                for slot in first..first + node.count as usize {
514                    let Some(hit) = shear.triangle(&self.corners[slot], bound, both_sides) else {
515                        continue;
516                    };
517                    let triangle = self.order[slot];
518                    let better = best.is_none_or(|held| {
519                        hit.distance < held.distance
520                            || (hit.distance == held.distance && triangle < held.triangle)
521                    });
522                    if better {
523                        bound = hit.distance;
524                        best = Some(Hit { triangle, ..hit });
525                    }
526                }
527                continue;
528            }
529            let (left, right) = (at + 1, node.first);
530            let dl = shear.box_depth(&self.nodes[left as usize]);
531            let dr = shear.box_depth(&self.nodes[right as usize]);
532            let (first, second) = match (dl, dr) {
533                (Some(l), Some(r)) if r < l => (Some((right, r)), Some((left, l))),
534                (l, r) => (l.map(|l| (left, l)), r.map(|r| (right, r))),
535            };
536            for (index, depth) in [second, first].into_iter().flatten() {
537                if depth <= bound {
538                    stack.push((index, depth));
539                }
540            }
541        }
542        best
543    }
544}
545
546/// Answer `queries` on the kernel's workers, answers in query order.
547///
548/// The queries are taken in the order of their places along a Morton curve
549/// over their bounds, so that each worker's run of them walks the same few
550/// nodes and triangles while they are in cache. Each answer depends on its
551/// query alone, so the order changes only the time.
552pub(crate) fn batched<T: Sync, R: Send + Copy + Default>(
553    queries: &[T],
554    place: impl Fn(&T) -> Point,
555    answer: impl Fn(&T, &mut Vec<(u32, f64)>) -> R + Sync,
556) -> Vec<R> {
557    let places: Vec<Point> = queries.iter().map(place).collect();
558    let bounds = places
559        .iter()
560        .filter(|p| p.x.is_finite() && p.y.is_finite() && p.z.is_finite())
561        .fold(Aabb::EMPTY, |acc, &p| acc.with_point(p));
562    let mut order: Vec<(u64, usize)> = places
563        .iter()
564        .enumerate()
565        .map(|(i, &p)| (morton(&bounds, p), i))
566        .collect();
567    order.sort_unstable();
568    let chunks: Vec<&[(u64, usize)]> = order.chunks(BATCH).collect();
569    let answered = ogeom_core::parallel::map_ordered(&chunks, |_, chunk| {
570        let mut stack = Vec::new();
571        chunk
572            .iter()
573            .map(|&(_, i)| answer(&queries[i], &mut stack))
574            .collect::<Vec<R>>()
575    });
576    let mut out = vec![R::default(); queries.len()];
577    for (&(_, i), r) in order.iter().zip(answered.into_iter().flatten()) {
578        out[i] = r;
579    }
580    out
581}
582
583/// A point's place along a Morton curve over `bounds`: 21 bits per axis,
584/// interleaved. Off the bounds (or not finite), the nearest cell.
585fn morton(bounds: &Aabb, p: Point) -> u64 {
586    let (Some(low), Some(high)) = (bounds.low(), bounds.high()) else {
587        return 0;
588    };
589    let cell = |v: f64, lo: f64, hi: f64| -> u64 {
590        let span = hi - lo;
591        let unit = if span > 0.0 { (v - lo) / span } else { 0.0 };
592        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
593        let at = (unit.clamp(0.0, 1.0) * 2_097_151.0) as u64;
594        // Spread 21 bits three apart.
595        let mut x = at & 0x1f_ffff;
596        x = (x | x << 32) & 0x1f_0000_0000_ffff;
597        x = (x | x << 16) & 0x1f_0000_ff00_00ff;
598        x = (x | x << 8) & 0x100f_00f0_0f00_f00f;
599        x = (x | x << 4) & 0x10c3_0c30_c30c_30c3;
600        x = (x | x << 2) & 0x1249_2492_4924_9249;
601        x
602    };
603    cell(p.x, low.x, high.x) | cell(p.y, low.y, high.y) << 1 | cell(p.z, low.z, high.z) << 2
604}
605
606pub(crate) fn point(p: P) -> Point {
607    Point::new(p[0], p[1], p[2])
608}
609
610fn box_of(t: &[P; 3]) -> (P, P) {
611    let mut low = t[0];
612    let mut high = t[0];
613    for c in &t[1..] {
614        for axis in 0..3 {
615            low[axis] = low[axis].min(c[axis]);
616            high[axis] = high[axis].max(c[axis]);
617        }
618    }
619    (low, high)
620}
621
622/// The squared distance from `p` to a box, summed over the axes in order:
623/// the same operations [`closest`] takes its distance with.
624pub(crate) fn gap(low: &P, high: &P, p: P) -> f64 {
625    let mut sum = 0.0;
626    for axis in 0..3 {
627        let g = if p[axis] < low[axis] {
628            low[axis] - p[axis]
629        } else if p[axis] > high[axis] {
630            p[axis] - high[axis]
631        } else {
632            0.0
633        };
634        sum += g * g;
635    }
636    sum
637}
638
639fn box_gap(node: &Node, p: P) -> f64 {
640    gap(&node.low, &node.high, p)
641}
642
643/// The squared distance between two points, summed over the axes in order.
644pub(crate) fn distance2(a: P, b: P) -> f64 {
645    let mut sum = 0.0;
646    for axis in 0..3 {
647        let d = a[axis] - b[axis];
648        sum += d * d;
649    }
650    sum
651}
652
653fn sub(a: P, b: P) -> P {
654    [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
655}
656
657fn dot(a: P, b: P) -> f64 {
658    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
659}
660
661fn along(a: P, d: P, s: f64) -> P {
662    [a[0] + d[0] * s, a[1] + d[1] * s, a[2] + d[2] * s]
663}
664
665/// The squared distance from `p` to a triangle, as [`Bvh::nearest`] takes
666/// it: a nearest query over a mesh holding the triangle answers no more
667/// than this.
668pub(crate) fn distance2_to(p: Point, t: [Point; 3]) -> f64 {
669    closest([p.x, p.y, p.z], &t.map(|q| [q.x, q.y, q.z])).2
670}
671
672/// The point on a triangle nearest `p`, its barycentric weights, and its
673/// squared distance to `p`.
674///
675/// By the Voronoi regions of the corners, edges and face. A triangle too
676/// degenerate for its face region is answered by its nearest edge. The point
677/// is clamped into the triangle's box, which rounding can step outside of,
678/// so that no box holding the triangle is farther from `p` than the answer.
679fn closest(p: P, t: &[P; 3]) -> (P, [f64; 3], f64) {
680    let (q, w) = closest_unclamped(p, t);
681    let (low, high) = box_of(t);
682    let q = [0, 1, 2].map(|axis| q[axis].clamp(low[axis], high[axis]));
683    (q, w, distance2(q, p))
684}
685
686fn closest_unclamped(p: P, [a, b, c]: &[P; 3]) -> (P, [f64; 3]) {
687    let (a, b, c) = (*a, *b, *c);
688    let ab = sub(b, a);
689    let ac = sub(c, a);
690    let ap = sub(p, a);
691    let d1 = dot(ab, ap);
692    let d2 = dot(ac, ap);
693    if d1 <= 0.0 && d2 <= 0.0 {
694        return (a, [1.0, 0.0, 0.0]);
695    }
696    let bp = sub(p, b);
697    let d3 = dot(ab, bp);
698    let d4 = dot(ac, bp);
699    if d3 >= 0.0 && d4 <= d3 {
700        return (b, [0.0, 1.0, 0.0]);
701    }
702    let vc = d1 * d4 - d3 * d2;
703    if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 && d1 - d3 > 0.0 {
704        let v = d1 / (d1 - d3);
705        return (along(a, ab, v), [1.0 - v, v, 0.0]);
706    }
707    let cp = sub(p, c);
708    let d5 = dot(ab, cp);
709    let d6 = dot(ac, cp);
710    if d6 >= 0.0 && d5 <= d6 {
711        return (c, [0.0, 0.0, 1.0]);
712    }
713    let vb = d5 * d2 - d1 * d6;
714    if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 && d2 - d6 > 0.0 {
715        let w = d2 / (d2 - d6);
716        return (along(a, ac, w), [1.0 - w, 0.0, w]);
717    }
718    let va = d3 * d6 - d5 * d4;
719    let (e, f) = (d4 - d3, d5 - d6);
720    if va <= 0.0 && e >= 0.0 && f >= 0.0 && e + f > 0.0 {
721        let w = e / (e + f);
722        return (along(b, sub(c, b), w), [0.0, 1.0 - w, w]);
723    }
724    let sum = va + vb + vc;
725    if sum > 0.0 && sum.is_finite() {
726        let v = vb / sum;
727        let w = vc / sum;
728        if v >= 0.0 && w >= 0.0 && v + w <= 1.0 {
729            return (along(along(a, ab, v), ac, w), [1.0 - v - w, v, w]);
730        }
731    }
732    nearest_edge(p, a, b, c)
733}
734
735/// The nearest point on a triangle's three edges, the first edge winning a
736/// tie: the answer for a triangle with no face region of its own.
737fn nearest_edge(p: P, a: P, b: P, c: P) -> (P, [f64; 3]) {
738    let on = |from: P, to: P| -> (P, f64) {
739        let d = sub(to, from);
740        let length2 = dot(d, d);
741        let s = if length2 > 0.0 {
742            (dot(sub(p, from), d) / length2).clamp(0.0, 1.0)
743        } else {
744            0.0
745        };
746        (along(from, d, s), s)
747    };
748    let (qab, sab) = on(a, b);
749    let (qbc, sbc) = on(b, c);
750    let (qca, sca) = on(c, a);
751    let mut best = (qab, [1.0 - sab, sab, 0.0]);
752    let mut held = distance2(qab, p);
753    for (q, w) in [(qbc, [0.0, 1.0 - sbc, sbc]), (qca, [sca, 0.0, 1.0 - sca])] {
754        let d = distance2(q, p);
755        if d < held {
756            held = d;
757            best = (q, w);
758        }
759    }
760    best
761}
762
763/// A ray sheared onto the `z` axis: its origin moved to zero, its dominant
764/// axis made `z`, and the other two leaned so the ray runs straight up it.
765/// The two lateral axes are swapped when the ray runs down its dominant
766/// axis, so that a counter-clockwise triangle keeps its sense.
767#[derive(Debug, Clone, Copy)]
768struct Shear {
769    origin: P,
770    axes: [usize; 3],
771    lean: [f64; 2],
772    scale: f64,
773}
774
775impl Shear {
776    fn new(origin: Point, direction: Vector) -> Option<Self> {
777        let length = direction.magnitude();
778        let o = [origin.x, origin.y, origin.z];
779        if !(length > 0.0 && length.is_finite()) || !o.iter().all(|v| v.is_finite()) {
780            return None;
781        }
782        let d = [
783            direction.x / length,
784            direction.y / length,
785            direction.z / length,
786        ];
787        let mut kz = 0;
788        for axis in 1..3 {
789            if d[axis].abs() > d[kz].abs() {
790                kz = axis;
791            }
792        }
793        let mut kx = (kz + 1) % 3;
794        let mut ky = (kx + 1) % 3;
795        if d[kz] < 0.0 {
796            core::mem::swap(&mut kx, &mut ky);
797        }
798        Some(Self {
799            origin: o,
800            axes: [kx, ky, kz],
801            lean: [d[kx] / d[kz], d[ky] / d[kz]],
802            scale: 1.0 / d[kz],
803        })
804    }
805
806    /// A corner in sheared coordinates; `z` is the distance along the ray.
807    fn transform(&self, p: P) -> P {
808        let [kx, ky, kz] = self.axes;
809        let dz = p[kz] - self.origin[kz];
810        [
811            (p[kx] - self.origin[kx]) - self.lean[0] * dz,
812            (p[ky] - self.origin[ky]) - self.lean[1] * dz,
813            self.scale * dz,
814        ]
815    }
816
817    /// The nearest depth a box could hold a hit at, or `None` where the ray
818    /// passes it by or it lies wholly behind the origin.
819    ///
820    /// The bounds of each sheared coordinate over the box, taken with
821    /// [`Shear::transform`]'s own operations at the corners that make it
822    /// least and greatest.
823    fn box_depth(&self, node: &Node) -> Option<f64> {
824        let [kx, ky, kz] = self.axes;
825        let z0 = node.low[kz] - self.origin[kz];
826        let z1 = node.high[kz] - self.origin[kz];
827        let lateral = |k: usize, lean: f64| -> bool {
828            let (s0, s1) = (lean * z0, lean * z1);
829            let least = (node.low[k] - self.origin[k]) - s0.max(s1);
830            let most = (node.high[k] - self.origin[k]) - s0.min(s1);
831            least <= 0.0 && most >= 0.0
832        };
833        if !lateral(kx, self.lean[0]) || !lateral(ky, self.lean[1]) {
834            return None;
835        }
836        let (d0, d1) = (self.scale * z0, self.scale * z1);
837        let (near, far) = if d0 <= d1 { (d0, d1) } else { (d1, d0) };
838        (far >= 0.0).then_some(near.max(0.0))
839    }
840
841    /// The ray's hit on one triangle within `max`, its triangle index left
842    /// for the caller.
843    fn triangle(&self, corners: &[P; 3], max: f64, both_sides: bool) -> Option<Hit> {
844        let [a, b, c] = corners.map(|p| self.transform(p));
845        let origin = [0.0, 0.0];
846        let signs = [
847            Exact::orient2d([c[0], c[1]], [b[0], b[1]], origin),
848            Exact::orient2d([a[0], a[1]], [c[0], c[1]], origin),
849            Exact::orient2d([b[0], b[1]], [a[0], a[1]], origin),
850        ];
851        let any = |s: Sign| signs.contains(&s);
852        if (any(Sign::Negative) && any(Sign::Positive))
853            || !(any(Sign::Negative) || any(Sign::Positive))
854        {
855            return None;
856        }
857        let facing = any(Sign::Positive);
858        if !both_sides && !facing {
859            return None;
860        }
861        // The weights in floating point, each held to its exact sign: one
862        // the rounding put on the wrong side is too small to matter, and
863        // zero keeps the three of one sign.
864        let raw = [
865            c[0] * b[1] - c[1] * b[0],
866            a[0] * c[1] - a[1] * c[0],
867            b[0] * a[1] - b[1] * a[0],
868        ];
869        let weights: [f64; 3] = [0, 1, 2].map(|i| match signs[i] {
870            Sign::Zero => 0.0,
871            Sign::Positive if raw[i] > 0.0 => raw[i],
872            Sign::Negative if raw[i] < 0.0 => raw[i],
873            _ => 0.0,
874        });
875        let det = weights[0] + weights[1] + weights[2];
876        let barycentric = if det == 0.0 || !det.is_finite() {
877            [1.0 / 3.0; 3]
878        } else {
879            weights.map(|w| w / det)
880        };
881        let depth = barycentric[0] * a[2] + barycentric[1] * b[2] + barycentric[2] * c[2];
882        let least = a[2].min(b[2]).min(c[2]);
883        let most = a[2].max(b[2]).max(c[2]);
884        let depth = depth.clamp(least, most);
885        if !(depth >= 0.0 && depth <= max) {
886            return None;
887        }
888        let [pa, pb, pc] = *corners;
889        let at = [0, 1, 2]
890            .map(|k| barycentric[0] * pa[k] + barycentric[1] * pb[k] + barycentric[2] * pc[k]);
891        Some(Hit {
892            triangle: 0,
893            point: point(at),
894            distance: depth,
895            barycentric,
896        })
897    }
898}
899
900/// Build the nodes over the triangles' boxes, reordering `order` into leaf
901/// order: depth first, each left child right after its parent.
902/// The node over a range of the order, and where the range splits, as an
903/// offset into it, when it is not a leaf; the range is partitioned to
904/// match.
905fn node_of(boxes: &[(P, P)], range: &mut [u32], start: usize) -> (Node, Option<usize>) {
906    let centre = |t: u32| -> P {
907        let (low, high) = boxes[t as usize];
908        [0, 1, 2].map(|k| 0.5 * (low[k] + high[k]))
909    };
910    let (mut low, mut high) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
911    let (mut clow, mut chigh) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
912    for &t in range.iter() {
913        let (l, h) = boxes[t as usize];
914        let c = centre(t);
915        for k in 0..3 {
916            low[k] = low[k].min(l[k]);
917            high[k] = high[k].max(h[k]);
918            clow[k] = clow[k].min(c[k]);
919            chigh[k] = chigh[k].max(c[k]);
920        }
921    }
922    let count = range.len();
923    let leaf = Node {
924        low,
925        high,
926        first: u32::try_from(start).unwrap_or(u32::MAX),
927        count: u32::try_from(count).unwrap_or(u32::MAX),
928    };
929    if count <= 2 {
930        return (leaf, None);
931    }
932    let mid = match sah_split(boxes, range, &clow, &chigh, area(&low, &high)) {
933        Some(split) if split.pays || count > LEAF_MOST => {
934            let bin_of = binner(&clow, &chigh, split.axis);
935            partition(range, |t| bin_of(centre(t)[split.axis]) <= split.bin)
936        }
937        None if count > LEAF_MOST => count / 2,
938        _ => return (leaf, None),
939    };
940    (
941        Node {
942            first: 0,
943            count: 0,
944            ..leaf
945        },
946        Some(mid),
947    )
948}
949
950/// The nodes over `order[start..start + order.len()]`, depth first, each
951/// interior node's left child next and its right child at `first`, leaves'
952/// slots numbered from `start` and nodes from `base`.
953fn build_serial(boxes: &[(P, P)], order: &mut [u32], start: usize, base: usize) -> Vec<Node> {
954    let mut nodes: Vec<Node> = Vec::new();
955    if order.is_empty() {
956        return nodes;
957    }
958    // Each pending range carries the parent whose right child it is.
959    let mut pending: Vec<(usize, usize, Option<usize>)> = vec![(0, order.len(), None)];
960    while let Some((from, to, parent)) = pending.pop() {
961        let index = nodes.len();
962        if let Some(parent) = parent {
963            nodes[parent].first = u32::try_from(base + index).unwrap_or(u32::MAX);
964        }
965        let (node, mid) = node_of(boxes, &mut order[from..to], start + from);
966        nodes.push(node);
967        if let Some(mid) = mid {
968            pending.push((from + mid, to, Some(index)));
969            pending.push((from, from + mid, None));
970        }
971    }
972    nodes
973}
974
975/// A range no larger than this is built whole by one worker. The tree is
976/// the same at any size; it only sets how the work is shared.
977const SUBTREE: usize = 1 << 14;
978
979/// The top of the tree, depth first: a node built here, the piece its
980/// right child starts at, or a range left to build whole.
981enum Piece {
982    Node(Node, usize),
983    Range(usize, usize),
984}
985
986fn build_top(boxes: &[(P, P)], order: &mut [u32], start: usize, pieces: &mut Vec<Piece>) {
987    let end = start + order.len();
988    if order.len() <= SUBTREE {
989        pieces.push(Piece::Range(start, end));
990        return;
991    }
992    let (node, mid) = node_of(boxes, order, start);
993    let Some(mid) = mid else {
994        pieces.push(Piece::Node(node, 0));
995        return;
996    };
997    let at = pieces.len();
998    pieces.push(Piece::Node(node, 0));
999    let (left, right) = order.split_at_mut(mid);
1000    build_top(boxes, left, start, pieces);
1001    let right_piece = pieces.len();
1002    if let Piece::Node(_, r) = &mut pieces[at] {
1003        *r = right_piece;
1004    }
1005    build_top(boxes, right, start + mid, pieces);
1006}
1007
1008/// The tree over `order`, which it reorders into leaf order.
1009///
1010/// The ranges are split one at a time from the top, then the ranges small
1011/// enough are built on the kernel's workers and the tree put together in
1012/// the order a single walk down it makes, so the tree is the one built
1013/// whole.
1014fn build_nodes(boxes: &[(P, P)], order: &mut [u32]) -> Vec<Node> {
1015    if order.len() <= SUBTREE {
1016        return build_serial(boxes, order, 0, 0);
1017    }
1018    let mut pieces = Vec::new();
1019    build_top(boxes, order, 0, &mut pieces);
1020    let ranges: Vec<(usize, usize)> = pieces
1021        .iter()
1022        .filter_map(|p| match p {
1023            Piece::Range(a, b) => Some((*a, *b)),
1024            Piece::Node(..) => None,
1025        })
1026        .collect();
1027    let built = ogeom_core::parallel::map_ordered(&ranges, |_, &(a, b)| {
1028        let mut part = order[a..b].to_vec();
1029        let nodes = build_serial(boxes, &mut part, a, 0);
1030        (nodes, part)
1031    });
1032    // Where each piece's first node lands.
1033    let mut sizes = Vec::with_capacity(pieces.len());
1034    let mut built_at = 0;
1035    for p in &pieces {
1036        sizes.push(match p {
1037            Piece::Node(..) => 1,
1038            Piece::Range(..) => {
1039                built_at += 1;
1040                built[built_at - 1].0.len()
1041            }
1042        });
1043    }
1044    let mut offsets = Vec::with_capacity(pieces.len());
1045    let mut total = 0;
1046    for size in &sizes {
1047        offsets.push(total);
1048        total += size;
1049    }
1050    let mut nodes = Vec::with_capacity(total);
1051    let mut built = built.into_iter();
1052    for (k, p) in pieces.iter().enumerate() {
1053        match p {
1054            Piece::Node(node, right) => nodes.push(Node {
1055                first: if node.count == 0 {
1056                    u32::try_from(offsets[*right]).unwrap_or(u32::MAX)
1057                } else {
1058                    node.first
1059                },
1060                ..*node
1061            }),
1062            Piece::Range(a, b) => {
1063                let Some((part_nodes, part)) = built.next() else {
1064                    continue;
1065                };
1066                order[*a..*b].copy_from_slice(&part);
1067                let shift = u32::try_from(offsets[k]).unwrap_or(u32::MAX);
1068                nodes.extend(part_nodes.into_iter().map(|n| Node {
1069                    first: if n.count == 0 {
1070                        n.first + shift
1071                    } else {
1072                        n.first
1073                    },
1074                    ..n
1075                }));
1076            }
1077        }
1078    }
1079    nodes
1080}
1081
1082/// The cheapest split of a range: after `bin` along `axis`.
1083struct Split {
1084    axis: usize,
1085    bin: usize,
1086    /// Whether it is cheaper than keeping the range as one leaf.
1087    pays: bool,
1088}
1089
1090fn area(low: &P, high: &P) -> f64 {
1091    let d = [0, 1, 2].map(|k| (high[k] - low[k]).max(0.0));
1092    d[0] * d[1] + d[1] * d[2] + d[2] * d[0]
1093}
1094
1095/// The bin a centroid coordinate falls in along `axis`.
1096fn binner(clow: &P, chigh: &P, axis: usize) -> impl Fn(f64) -> usize {
1097    let low = clow[axis];
1098    let scale = bin_count_f64() / (chigh[axis] - low);
1099    move |c: f64| {
1100        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1101        let bin = ((c - low) * scale) as usize;
1102        bin.min(BINS - 1)
1103    }
1104}
1105
1106#[allow(clippy::cast_precision_loss)]
1107fn bin_count_f64() -> f64 {
1108    BINS as f64
1109}
1110
1111/// The cheapest split by the surface-area heuristic, binning the centroids
1112/// along each axis they spread over: a split costs one step down plus the
1113/// triangles of each side weighted by its share of the parent's area,
1114/// against a leaf's cost of every triangle. `None` where the centroids do
1115/// not spread along any axis.
1116fn sah_split(boxes: &[(P, P)], range: &[u32], clow: &P, chigh: &P, whole: f64) -> Option<Split> {
1117    let mut best: Option<(f64, usize, usize)> = None;
1118    for axis in 0..3 {
1119        if chigh[axis] <= clow[axis] {
1120            continue;
1121        }
1122        let bin_of = binner(clow, chigh, axis);
1123        let mut counts = [0_usize; BINS];
1124        let mut lows = [[f64::INFINITY; 3]; BINS];
1125        let mut highs = [[f64::NEG_INFINITY; 3]; BINS];
1126        for &t in range {
1127            let (l, h) = boxes[t as usize];
1128            let b = bin_of(0.5 * (l[axis] + h[axis]));
1129            counts[b] += 1;
1130            for k in 0..3 {
1131                lows[b][k] = lows[b][k].min(l[k]);
1132                highs[b][k] = highs[b][k].max(h[k]);
1133            }
1134        }
1135        // The right side's area and count for a split after each bin.
1136        let mut right = [(0.0, 0_usize); BINS];
1137        let (mut low, mut high, mut n) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3], 0);
1138        for b in (1..BINS).rev() {
1139            for k in 0..3 {
1140                low[k] = low[k].min(lows[b][k]);
1141                high[k] = high[k].max(highs[b][k]);
1142            }
1143            n += counts[b];
1144            right[b - 1] = (if n > 0 { area(&low, &high) } else { 0.0 }, n);
1145        }
1146        let (mut low, mut high, mut n) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3], 0);
1147        for b in 0..BINS - 1 {
1148            for k in 0..3 {
1149                low[k] = low[k].min(lows[b][k]);
1150                high[k] = high[k].max(highs[b][k]);
1151            }
1152            n += counts[b];
1153            let (ra, rn) = right[b];
1154            if n == 0 || rn == 0 {
1155                continue;
1156            }
1157            #[allow(clippy::cast_precision_loss)]
1158            let cost = area(&low, &high) * n as f64 + ra * rn as f64;
1159            if best.is_none_or(|(held, _, _)| cost < held) {
1160                best = Some((cost, axis, b));
1161            }
1162        }
1163    }
1164    let (cost, axis, bin) = best?;
1165    #[allow(clippy::cast_precision_loss)]
1166    let leaf = range.len() as f64;
1167    Some(Split {
1168        axis,
1169        bin,
1170        pays: whole <= 0.0 || 1.0 + cost / whole < leaf,
1171    })
1172}
1173
1174/// Move the items `left` keeps to the front, keeping the order within each
1175/// side; how many it kept.
1176fn partition(range: &mut [u32], left: impl Fn(u32) -> bool) -> usize {
1177    let mut kept = 0;
1178    let mut rest = Vec::new();
1179    for i in 0..range.len() {
1180        let t = range[i];
1181        if left(t) {
1182            range[kept] = t;
1183            kept += 1;
1184        } else {
1185            rest.push(t);
1186        }
1187    }
1188    range[kept..].copy_from_slice(&rest);
1189    kept
1190}
1191
1192#[cfg(test)]
1193#[allow(clippy::unwrap_used, clippy::expect_used, clippy::cast_precision_loss)]
1194pub(crate) mod tests {
1195    use super::*;
1196
1197    /// A small deterministic generator (xorshift), so every run draws the
1198    /// same meshes and queries.
1199    pub(crate) struct Seeded(pub(crate) u64);
1200
1201    impl Seeded {
1202        pub(crate) fn next(&mut self) -> u64 {
1203            let mut x = self.0;
1204            x ^= x << 13;
1205            x ^= x >> 7;
1206            x ^= x << 17;
1207            self.0 = x;
1208            x
1209        }
1210
1211        /// Uniform in `[lo, hi)`.
1212        pub(crate) fn range(&mut self, lo: f64, hi: f64) -> f64 {
1213            #[allow(clippy::cast_precision_loss)]
1214            let unit = (self.next() >> 11) as f64 / (1_u64 << 53) as f64;
1215            lo + (hi - lo) * unit
1216        }
1217
1218        pub(crate) fn below(&mut self, n: usize) -> usize {
1219            usize::try_from(self.next() % n as u64).unwrap()
1220        }
1221
1222        pub(crate) fn point(&mut self, lo: f64, hi: f64) -> Point {
1223            Point::new(self.range(lo, hi), self.range(lo, hi), self.range(lo, hi))
1224        }
1225    }
1226
1227    #[test]
1228    fn a_tree_built_in_pieces_is_the_tree_built_whole() {
1229        // Small triangles scattered through a cube, with runs of equal
1230        // centres to give the splits ties, many times a worker's share.
1231        let mut rng = Seeded(0x51ed_270b_d3c4_9a17);
1232        let mut boxes = Vec::new();
1233        for i in 0..20 * SUBTREE {
1234            let c = if i % 7 == 0 && i > 0 {
1235                boxes.last().map_or([0.0; 3], |b: &(P, P)| b.0)
1236            } else {
1237                let p = rng.point(-50.0, 50.0);
1238                [p.x, p.y, p.z]
1239            };
1240            let r = rng.range(0.0, 0.5);
1241            boxes.push((c.map(|v| v - r), c.map(|v| v + r)));
1242        }
1243        let all: Vec<u32> = (0..u32::try_from(boxes.len()).unwrap()).collect();
1244        let (mut whole, mut pieces) = (all.clone(), all);
1245        let one = build_serial(&boxes, &mut whole, 0, 0);
1246        let many = build_nodes(&boxes, &mut pieces);
1247        assert_eq!(whole, pieces);
1248        let key = |n: &Node| (n.low, n.high, n.first, n.count);
1249        assert_eq!(one.len(), many.len());
1250        assert!(one.iter().zip(&many).all(|(a, b)| key(a) == key(b)));
1251    }
1252
1253    fn corners_of(mesh: &Triangulation) -> Vec<[P; 3]> {
1254        mesh.triangles
1255            .iter()
1256            .map(|t| {
1257                t.map(|k| {
1258                    let p = mesh.positions[k as usize];
1259                    [p.x, p.y, p.z]
1260                })
1261            })
1262            .collect()
1263    }
1264
1265    fn brute_nearest(corners: &[[P; 3]], p: Point, max: f64) -> Option<Hit> {
1266        let q = [p.x, p.y, p.z];
1267        let reach = max * max;
1268        let mut best: Option<(f64, usize)> = None;
1269        for (i, t) in corners.iter().enumerate() {
1270            let d2 = closest(q, t).2;
1271            if d2 <= reach && best.is_none_or(|(held, _)| d2 < held) {
1272                best = Some((d2, i));
1273            }
1274        }
1275        let (_, i) = best?;
1276        let (at, barycentric, d2) = closest(q, &corners[i]);
1277        Some(Hit {
1278            triangle: u32::try_from(i).unwrap(),
1279            point: point(at),
1280            distance: d2.sqrt(),
1281            barycentric,
1282        })
1283    }
1284
1285    fn brute_ball(corners: &[[P; 3]], c: Point, r: f64) -> Vec<u32> {
1286        let q = [c.x, c.y, c.z];
1287        (0..corners.len())
1288            .filter(|&i| closest(q, &corners[i]).2 <= r * r)
1289            .map(|i| u32::try_from(i).unwrap())
1290            .collect()
1291    }
1292
1293    fn brute_ray(corners: &[[P; 3]], o: Point, d: Vector, max: f64, both: bool) -> Option<Hit> {
1294        let shear = Shear::new(o, d)?;
1295        let mut best: Option<Hit> = None;
1296        for (i, t) in corners.iter().enumerate() {
1297            if let Some(hit) = shear.triangle(t, max, both)
1298                && best.is_none_or(|held| hit.distance < held.distance)
1299            {
1300                best = Some(Hit {
1301                    triangle: u32::try_from(i).unwrap(),
1302                    ..hit
1303                });
1304            }
1305        }
1306        best
1307    }
1308
1309    /// A closed surface: a lumpy torus of `around` by `across` quads, two
1310    /// triangles each, wound outward.
1311    fn torus(around: u32, across: u32, rng: &mut Seeded) -> Triangulation {
1312        let mut mesh = Triangulation::new();
1313        for i in 0..around {
1314            for j in 0..across {
1315                let u = std::f64::consts::TAU * f64::from(i) / f64::from(around);
1316                let v = std::f64::consts::TAU * f64::from(j) / f64::from(across);
1317                let lump = 1.0 + 0.05 * rng.range(-1.0, 1.0);
1318                let r = (2.0 + 0.7 * v.cos()) * lump;
1319                mesh.positions
1320                    .push(Point::new(r * u.cos(), r * u.sin(), 0.7 * v.sin() * lump));
1321            }
1322        }
1323        let at = |i: u32, j: u32| (i % around) * across + (j % across);
1324        for i in 0..around {
1325            for j in 0..across {
1326                let (a, b, c, d) = (at(i, j), at(i + 1, j), at(i + 1, j + 1), at(i, j + 1));
1327                mesh.triangles.push([a, b, c]);
1328                mesh.triangles.push([a, c, d]);
1329            }
1330        }
1331        mesh
1332    }
1333
1334    /// A soup of random triangles, large and small, with degenerate ones
1335    /// mixed in: a repeated corner, three collinear corners, one point.
1336    fn soup(count: usize, rng: &mut Seeded) -> Triangulation {
1337        let mut mesh = Triangulation::new();
1338        for i in 0..count {
1339            let a = rng.point(-1.0, 1.0);
1340            let size = if i % 3 == 0 { 0.5 } else { 0.05 };
1341            let b = a + (rng.point(-1.0, 1.0) - Point::ORIGIN) * size;
1342            let c = a + (rng.point(-1.0, 1.0) - Point::ORIGIN) * size;
1343            let corners = match i % 17 {
1344                3 => [a, b, b],
1345                5 => [a, b, a + (b - a) * 0.25],
1346                7 => [a, a, a],
1347                _ => [a, b, c],
1348            };
1349            let first = u32::try_from(mesh.positions.len()).unwrap();
1350            mesh.positions.extend(corners);
1351            mesh.triangles.push([first, first + 1, first + 2]);
1352        }
1353        mesh
1354    }
1355
1356    fn direction(rng: &mut Seeded) -> Vector {
1357        loop {
1358            let v = rng.point(-1.0, 1.0) - Point::ORIGIN;
1359            if v.magnitude() > 0.1 {
1360                return v;
1361            }
1362        }
1363    }
1364
1365    /// Every query kind on every mesh agrees with brute force, bit for bit:
1366    /// 1e5 queries of each kind over closed meshes and soups.
1367    #[test]
1368    fn every_query_equals_brute_force_on_random_meshes() {
1369        let mut rng = Seeded(0x05ee_d577);
1370        let mut meshes = Vec::new();
1371        for _ in 0..5 {
1372            meshes.push(torus(
1373                12 + u32::try_from(rng.below(12)).unwrap(),
1374                8,
1375                &mut rng,
1376            ));
1377            meshes.push(soup(150 + rng.below(250), &mut rng));
1378        }
1379        let per = 100_000 / meshes.len();
1380        let mut found = [0_usize; 4];
1381        for mesh in &meshes {
1382            let bvh = Bvh::build(mesh).unwrap();
1383            let corners = corners_of(mesh);
1384            // On a corner, on an edge, near the surface, or anywhere.
1385            let target = |rng: &mut Seeded, kind: usize| -> Point {
1386                let t = &corners[rng.below(corners.len())];
1387                let [a, b, c] = t.map(point);
1388                match kind {
1389                    0 => a,
1390                    1 => a + (b - a) * rng.range(0.0, 1.0),
1391                    2 => {
1392                        let (u, v) = (rng.range(0.0, 1.0), rng.range(0.0, 1.0));
1393                        let (u, v) = if u + v > 1.0 {
1394                            (1.0 - u, 1.0 - v)
1395                        } else {
1396                            (u, v)
1397                        };
1398                        a + (b - a) * u + (c - a) * v + (rng.point(-0.05, 0.05) - Point::ORIGIN)
1399                    }
1400                    _ => rng.point(-3.5, 3.5),
1401                }
1402            };
1403            for q in 0..per {
1404                let p = target(&mut rng, q % 4);
1405                let max = if q % 4 == 0 {
1406                    f64::INFINITY
1407                } else {
1408                    rng.range(0.0, 1.5)
1409                };
1410                let near = bvh.nearest(p, max);
1411                assert_eq!(near, brute_nearest(&corners, p, max), "{p:?} {max}");
1412                found[0] += usize::from(near.is_some());
1413                let r = rng.range(0.0, 0.6);
1414                let ball = bvh.within_ball(p, r);
1415                assert_eq!(ball, brute_ball(&corners, p, r), "{p:?} {r}");
1416                found[1] += usize::from(!ball.is_empty());
1417                // From anywhere, through a corner, an edge or a face.
1418                let o = rng.point(-3.5, 3.5);
1419                let aim = target(&mut rng, (q / 4) % 3);
1420                let d = if aim == o {
1421                    direction(&mut rng)
1422                } else {
1423                    aim - o
1424                };
1425                let max = if q % 3 == 0 {
1426                    f64::INFINITY
1427                } else {
1428                    rng.range(0.0, 6.0)
1429                };
1430                for both in [false, true] {
1431                    let hit = bvh.ray(o, d, max, both);
1432                    assert_eq!(
1433                        hit,
1434                        brute_ray(&corners, o, d, max, both),
1435                        "{o:?} {d:?} {max} {both}"
1436                    );
1437                    found[2 + usize::from(both)] += usize::from(hit.is_some());
1438                }
1439            }
1440        }
1441        // Each kind found something often enough to have been tested.
1442        assert!(found.iter().all(|&n| n > 25_000), "{found:?}");
1443    }
1444
1445    /// A filtered nearest query answers as brute force over the triangles
1446    /// the filter keeps, bit for bit, singly and batched at any thread
1447    /// count: filters keeping every other triangle, the triangles facing
1448    /// one way, or none.
1449    #[test]
1450    fn filtered_nearest_equals_brute_force_over_the_kept_triangles() {
1451        let mut rng = Seeded(0x00f1_17e2);
1452        let mut checked = 0_usize;
1453        for round in 0..6 {
1454            let mesh = if round % 2 == 0 {
1455                torus(14 + u32::try_from(rng.below(10)).unwrap(), 8, &mut rng)
1456            } else {
1457                soup(150 + rng.below(250), &mut rng)
1458            };
1459            let bvh = Bvh::build(&mesh).unwrap();
1460            let corners = corners_of(&mesh);
1461            let facing: Vec<P> = corners
1462                .iter()
1463                .map(|[a, b, c]| {
1464                    let (u, v) = (sub(*b, *a), sub(*c, *a));
1465                    [
1466                        u[1] * v[2] - u[2] * v[1],
1467                        u[2] * v[0] - u[0] * v[2],
1468                        u[0] * v[1] - u[1] * v[0],
1469                    ]
1470                })
1471                .collect();
1472            let points: Vec<Point> = (0..4000).map(|_| rng.point(-3.5, 3.5)).collect();
1473            let aims: Vec<P> = (0..points.len())
1474                .map(|_| {
1475                    let d = direction(&mut rng);
1476                    [d.x, d.y, d.z]
1477                })
1478                .collect();
1479            let max = if round < 3 { f64::INFINITY } else { 1.0 };
1480            let filters: [&(dyn Fn(usize, u32) -> bool + Sync); 3] = [
1481                &|_, t| t % 2 == 1,
1482                &|i, t| dot(facing[t as usize], aims[i]) > 0.0,
1483                &|_, _| false,
1484            ];
1485            for keep in filters {
1486                let brute: Vec<Option<Hit>> = points
1487                    .iter()
1488                    .enumerate()
1489                    .map(|(i, &p)| {
1490                        let kept: Vec<usize> = (0..corners.len())
1491                            .filter(|&t| keep(i, u32::try_from(t).unwrap()))
1492                            .collect();
1493                        let picked: Vec<[P; 3]> = kept.iter().map(|&t| corners[t]).collect();
1494                        brute_nearest(&picked, p, max).map(|hit| Hit {
1495                            triangle: u32::try_from(kept[hit.triangle as usize]).unwrap(),
1496                            ..hit
1497                        })
1498                    })
1499                    .collect();
1500                let single: Vec<Option<Hit>> = points
1501                    .iter()
1502                    .enumerate()
1503                    .map(|(i, &p)| bvh.nearest_where(p, max, |t| keep(i, t)))
1504                    .collect();
1505                assert_eq!(single, brute);
1506                for threads in [1, 3, 8] {
1507                    ogeom_core::parallel::set_threads(threads);
1508                    assert_eq!(bvh.nearest_many_where(&points, max, keep), brute);
1509                }
1510                ogeom_core::parallel::set_threads(0);
1511                checked += brute.iter().filter(|h| h.is_some()).count();
1512            }
1513        }
1514        assert!(checked > 20_000, "{checked}");
1515    }
1516
1517    /// The batch queries answer exactly as the single ones, in input order,
1518    /// at any thread count.
1519    #[test]
1520    fn batches_answer_as_single_queries_at_any_thread_count() {
1521        let mut rng = Seeded(77);
1522        let mesh = torus(30, 12, &mut rng);
1523        let bvh = Bvh::build(&mesh).unwrap();
1524        let points: Vec<Point> = (0..3000).map(|_| rng.point(-3.0, 3.0)).collect();
1525        let dirs: Vec<Vector> = (0..3000).map(|_| direction(&mut rng)).collect();
1526        let near: Vec<Option<Hit>> = points.iter().map(|&p| bvh.nearest(p, 1.0)).collect();
1527        let rays: Vec<Option<Hit>> = points
1528            .iter()
1529            .zip(&dirs)
1530            .map(|(&p, &d)| bvh.ray(p, d, f64::INFINITY, true))
1531            .collect();
1532        for threads in [1, 3, 8] {
1533            ogeom_core::parallel::set_threads(threads);
1534            assert_eq!(bvh.nearest_many(&points, 1.0), near);
1535            assert_eq!(bvh.rays(&points, &dirs, f64::INFINITY, true).unwrap(), rays);
1536        }
1537        ogeom_core::parallel::set_threads(0);
1538        assert!(bvh.rays(&points, &dirs[1..], 1.0, true).is_err());
1539    }
1540
1541    /// A ray aimed exactly through a vertex or along an edge of a closed
1542    /// mesh, from inside it, always strikes the mesh: nothing slips between
1543    /// the triangles that meet there.
1544    #[test]
1545    fn rays_through_edges_and_vertices_do_not_slip_through() {
1546        let mut rng = Seeded(9);
1547        let mesh = torus(40, 16, &mut rng);
1548        let bvh = Bvh::build(&mesh).unwrap();
1549        let mut aimed = 0;
1550        for t in &mesh.triangles {
1551            let [a, b, _] = t.map(|k| mesh.positions[k as usize]);
1552            // From a point on the torus's core circle under the edge, so
1553            // the ray starts inside the solid.
1554            let mid = Point::new((a.x + b.x) / 2.0, (a.y + b.y) / 2.0, 0.0);
1555            let core = (mid - Point::ORIGIN) / (mid - Point::ORIGIN).magnitude() * 2.0;
1556            let inside = Point::ORIGIN + core;
1557            for target in [a, b, a + (b - a) * 0.5, a + (b - a) * 0.25] {
1558                // Outward-wound, so met from inside on its back.
1559                let hit = bvh.ray(inside, target - inside, f64::INFINITY, true);
1560                assert!(
1561                    hit.is_some(),
1562                    "a ray from {inside:?} through {target:?} slipped"
1563                );
1564                aimed += 1;
1565            }
1566        }
1567        assert_eq!(aimed, 4 * mesh.triangles.len());
1568        // Straight down onto a flat grid's vertices and edges, where
1569        // every corner is exactly representable and shared by six
1570        // triangles.
1571        let mut grid = Triangulation::new();
1572        for i in 0..5_u32 {
1573            for j in 0..5_u32 {
1574                grid.positions
1575                    .push(Point::new(f64::from(i) * 0.25, f64::from(j) * 0.25, 0.0));
1576            }
1577        }
1578        for i in 0..4 {
1579            for j in 0..4 {
1580                let (a, b, c, d) = (
1581                    i * 5 + j,
1582                    (i + 1) * 5 + j,
1583                    (i + 1) * 5 + j + 1,
1584                    i * 5 + j + 1,
1585                );
1586                grid.triangles.push([a, b, c]);
1587                grid.triangles.push([a, c, d]);
1588            }
1589        }
1590        let flat = Bvh::build(&grid).unwrap();
1591        for i in 0..=8 {
1592            for j in 0..=8 {
1593                let at = Point::new(f64::from(i) * 0.125, f64::from(j) * 0.125, 1.0);
1594                let hit = flat
1595                    .ray(at, Vector::new(0.0, 0.0, -1.0), 2.0, false)
1596                    .expect("a ray onto the grid hits it");
1597                assert_eq!(hit.distance, 1.0);
1598                // The lowest index among the triangles that meet there.
1599                let corners = corners_of(&grid);
1600                let least = brute_ray(&corners, at, Vector::new(0.0, 0.0, -1.0), 2.0, false);
1601                assert_eq!(Some(hit), least);
1602            }
1603        }
1604    }
1605
1606    /// A triangle's front is the side its counter-clockwise winding points
1607    /// out of. A back-facing hit counts only when both sides are asked for.
1608    #[test]
1609    fn a_ray_meets_the_front_or_both_sides_at_the_closed_form_depth() {
1610        let mut mesh = Triangulation::new();
1611        mesh.positions = vec![
1612            Point::new(0.0, 0.0, 0.0),
1613            Point::new(4.0, 0.0, 0.0),
1614            Point::new(0.0, 4.0, 0.0),
1615        ];
1616        mesh.triangles = vec![[0, 1, 2]];
1617        let bvh = Bvh::build(&mesh).unwrap();
1618        let down = bvh
1619            .ray(
1620                Point::new(1.0, 2.0, 3.0),
1621                Vector::new(0.0, 0.0, -2.0),
1622                10.0,
1623                false,
1624            )
1625            .unwrap();
1626        assert_eq!(down.distance, 3.0);
1627        assert_eq!(down.point, Point::new(1.0, 2.0, 0.0));
1628        assert_eq!(down.barycentric, [0.25, 0.25, 0.5]);
1629        let up = Vector::new(0.0, 0.0, 1.0);
1630        assert!(
1631            bvh.ray(Point::new(1.0, 2.0, -3.0), up, 10.0, false)
1632                .is_none()
1633        );
1634        assert_eq!(
1635            bvh.ray(Point::new(1.0, 2.0, -3.0), up, 10.0, true)
1636                .unwrap()
1637                .distance,
1638            3.0
1639        );
1640        // Every dominant axis and both senses of it agree on the front.
1641        for (o, d) in [
1642            (Point::new(1.0, 1.0, 1.0), Vector::new(0.1, -0.2, -1.0)),
1643            (Point::new(1.5, 1.0, 1.0), Vector::new(-1.0, 0.0, -0.9)),
1644            (Point::new(1.0, 2.0, 1.0), Vector::new(0.3, -1.0, -0.8)),
1645            (Point::new(3.0, 0.5, 1.0), Vector::new(-1.0, 0.1, -0.5)),
1646            (Point::new(0.5, 3.0, 1.0), Vector::new(0.0, -1.0, -0.5)),
1647            (Point::new(0.2, 1.0, 1.0), Vector::new(1.0, 0.2, -0.6)),
1648            (Point::new(1.0, 0.2, 1.0), Vector::new(0.2, 1.0, -0.6)),
1649        ] {
1650            assert!(bvh.ray(o, d, 10.0, false).is_some(), "{d:?}");
1651            let below = Point::new(o.x, o.y, -o.z);
1652            let rising = Vector::new(d.x, d.y, -d.z);
1653            assert!(bvh.ray(below, rising, 10.0, false).is_none(), "{rising:?}");
1654            assert!(bvh.ray(below, rising, 10.0, true).is_some(), "{rising:?}");
1655        }
1656        // Past `max`, or behind the origin, is no hit.
1657        assert!(
1658            bvh.ray(Point::new(1.0, 2.0, 3.0), -up, 2.9, false)
1659                .is_none()
1660        );
1661        assert!(bvh.ray(Point::new(1.0, 2.0, 3.0), up, 10.0, true).is_none());
1662        // Every hit along a line through the triangle, from either side.
1663        assert_eq!(
1664            bvh.hits_along(Point::new(1.0, 2.0, 3.0), -up, 10.0, true)
1665                .len(),
1666            1
1667        );
1668    }
1669
1670    /// Queries exactly on a vertex or an edge answer distance zero there,
1671    /// and a vertex six triangles share goes to the lowest of them.
1672    #[test]
1673    fn queries_on_vertices_and_edges_land_on_them() {
1674        let mut rng = Seeded(31);
1675        let mesh = torus(16, 8, &mut rng);
1676        let bvh = Bvh::build(&mesh).unwrap();
1677        for (k, &p) in mesh.positions.iter().enumerate() {
1678            let hit = bvh.nearest(p, f64::INFINITY).unwrap();
1679            assert_eq!(hit.distance, 0.0);
1680            assert_eq!(hit.point, p);
1681            let k = u32::try_from(k).unwrap();
1682            let least = mesh.triangles.iter().position(|t| t.contains(&k)).unwrap();
1683            assert_eq!(hit.triangle as usize, least);
1684            assert!(bvh.within_ball(p, 0.0).contains(&hit.triangle));
1685        }
1686        for t in &mesh.triangles {
1687            let [a, b, _] = t.map(|k| mesh.positions[k as usize]);
1688            let on = a + (b - a) * 0.5;
1689            let hit = bvh.nearest(on, f64::INFINITY).unwrap();
1690            assert!(hit.distance <= 1e-14, "{}", hit.distance);
1691        }
1692    }
1693
1694    /// A degenerate triangle is still a set of points a query can land on:
1695    /// a repeated corner is its segment, a collapsed one is its point.
1696    #[test]
1697    fn degenerate_triangles_answer_as_their_segments_and_points() {
1698        let mut mesh = Triangulation::new();
1699        mesh.positions = vec![
1700            Point::new(0.0, 0.0, 0.0),
1701            Point::new(2.0, 0.0, 0.0),
1702            Point::new(2.0, 0.0, 0.0),
1703            Point::new(50.0, 50.0, 50.0),
1704            Point::new(1.0, 0.0, 0.0),
1705        ];
1706        // A segment twice over (a repeated corner, then collinear corners)
1707        // and a single point.
1708        mesh.triangles = vec![[0, 1, 2], [0, 4, 1], [3, 3, 3]];
1709        let bvh = Bvh::build(&mesh).unwrap();
1710        let hit = bvh
1711            .nearest(Point::new(1.5, 3.0, 4.0), f64::INFINITY)
1712            .unwrap();
1713        assert_eq!(hit.triangle, 0);
1714        assert_eq!(hit.distance, 5.0);
1715        assert_eq!(hit.point, Point::new(1.5, 0.0, 0.0));
1716        let hit = bvh
1717            .nearest(Point::new(50.0, 50.0, 52.0), f64::INFINITY)
1718            .unwrap();
1719        assert_eq!((hit.triangle, hit.distance), (2, 2.0));
1720        assert_eq!(bvh.within_ball(Point::new(1.0, 1.0, 0.0), 1.0), vec![0, 1]);
1721        // A ray in no triangle's plane meets none of them: they have no
1722        // area to cross.
1723        assert!(
1724            bvh.ray(
1725                Point::new(1.0, 0.0, 1.0),
1726                Vector::new(0.0, 0.0, -1.0),
1727                5.0,
1728                true
1729            )
1730            .is_none()
1731        );
1732    }
1733
1734    #[test]
1735    fn an_empty_mesh_answers_nothing() {
1736        let bvh = Bvh::build(&Triangulation::new()).unwrap();
1737        assert!(bvh.is_empty());
1738        assert!(bvh.bounds().is_empty());
1739        let p = Point::new(0.0, 0.0, 0.0);
1740        assert!(bvh.nearest(p, f64::INFINITY).is_none());
1741        assert!(bvh.within_ball(p, 1e9).is_empty());
1742        assert!(
1743            bvh.ray(p, Vector::new(1.0, 0.0, 0.0), f64::INFINITY, true)
1744                .is_none()
1745        );
1746        assert!(
1747            bvh.hits_along(p, Vector::new(1.0, 0.0, 0.0), 1.0, true)
1748                .is_empty()
1749        );
1750        assert!(bvh.triangles_where(|_| true).is_empty());
1751        assert_eq!(bvh.nearest_many(&[p, p], 1.0), vec![None, None]);
1752        assert!(bvh.triangle(0).is_none());
1753    }
1754
1755    /// A negative or undefined reach finds nothing, even where a triangle
1756    /// is as far as its magnitude.
1757    #[test]
1758    fn a_negative_or_undefined_reach_finds_nothing() {
1759        let mut mesh = Triangulation::new();
1760        mesh.positions = vec![
1761            Point::new(0.0, 0.0, 0.0),
1762            Point::new(1.0, 0.0, 0.0),
1763            Point::new(0.0, 1.0, 0.0),
1764        ];
1765        mesh.triangles = vec![[0, 1, 2]];
1766        let bvh = Bvh::build(&mesh).unwrap();
1767        let above = Point::new(0.2, 0.2, 1.0);
1768        assert!(bvh.nearest(above, 1.0).is_some());
1769        for bad in [-1.0, -2.0, f64::NAN] {
1770            assert!(bvh.nearest(above, bad).is_none());
1771            assert!(bvh.within_ball(above, bad).is_empty());
1772            assert!(
1773                bvh.ray(above, Vector::new(0.0, 0.0, -1.0), bad, true)
1774                    .is_none()
1775            );
1776        }
1777    }
1778
1779    #[test]
1780    fn a_mesh_naming_a_missing_or_infinite_corner_is_refused() {
1781        let mut mesh = Triangulation::new();
1782        mesh.positions = vec![Point::new(0.0, 0.0, 0.0); 2];
1783        mesh.triangles = vec![[0, 1, 2]];
1784        assert!(Bvh::build(&mesh).is_err());
1785        mesh.positions.push(Point::new(f64::NAN, 0.0, 0.0));
1786        assert!(Bvh::build(&mesh).is_err());
1787    }
1788
1789    /// Over a meshed box the nearest point to one outside a face is its foot
1790    /// on that face, and each triangle's corners come back by index.
1791    #[test]
1792    fn a_shape_s_tessellation_indexes_as_its_triangulation() {
1793        use ogeom_core::Tolerances;
1794        let tol = Tolerances::millimetres();
1795        let mut model = Model::new();
1796        let solid =
1797            ogeom_algo::make_box(&mut model, ogeom_math::Frame::WORLD, (2.0, 3.0, 4.0), tol)
1798                .unwrap()
1799                .shape;
1800        let deflection = Deflection::default();
1801        let bvh = Bvh::of_shape(&model, &solid, deflection, tol).unwrap();
1802        let mesh = crate::triangulate::triangulate(&model, &solid, deflection, tol).unwrap();
1803        assert_eq!(bvh.triangle_count(), mesh.triangles.len());
1804        for (i, t) in mesh.triangles.iter().enumerate() {
1805            let corners = t.map(|k| mesh.positions[k as usize]);
1806            assert_eq!(bvh.triangle(u32::try_from(i).unwrap()), Some(corners));
1807        }
1808        let hit = bvh
1809            .nearest(Point::new(1.0, 1.5, 9.0), f64::INFINITY)
1810            .unwrap();
1811        assert_eq!(hit.distance, 5.0);
1812        assert_eq!(hit.point, Point::new(1.0, 1.5, 4.0));
1813        let found = bvh.triangles_where(|b| b.contains(Point::new(1.0, 1.5, 4.0)));
1814        assert!(found.contains(&hit.triangle));
1815    }
1816}