Skip to main content

fast_mesh_bvh/
tree.rs

1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4use fast_mesh_store::{Mesh, TriangleId};
5
6use crate::arena::{Arena, NONE, Node};
7use crate::build::{Prims, TreeBuild, build_full, expected_nodes};
8use crate::error::{Result, try_reserve, try_reserve_exact, try_reserve_map, try_vec};
9use crate::{Aabb, BuildOptions, NodeId, NodeKind, UpdatePolicy};
10
11/// Source of [`Bvh::generation`] values, shared by every BVH in the process so
12/// that no two trees (or states of one tree) ever share a value.
13static NEXT_GENERATION: AtomicU64 = AtomicU64::new(0);
14
15/// A fresh, process-unique generation value.
16pub(crate) fn next_generation() -> u64 {
17    NEXT_GENERATION.fetch_add(1, Ordering::Relaxed)
18}
19
20/// A subtree that is rebuilt as one unit.
21#[derive(Debug, Clone, Copy)]
22pub(crate) struct Treelet {
23    pub(crate) root: u32,
24    /// SAH cost when the treelet was last built.
25    pub(crate) build_cost: f32,
26}
27
28/// Bounding volume hierarchy over the alive triangles of one [`Mesh`], in
29/// mesh-local coordinates. Keep it current with [`Bvh::update`].
30pub struct Bvh {
31    pub(crate) arena: Arena,
32    pub(crate) root: u32,
33    pub(crate) treelets: Vec<Treelet>,
34    /// Treelet root node → index into `treelets`.
35    pub(crate) treelet_of_root: HashMap<u32, usize>,
36    /// The triangles below each node slot (see [`Bvh::subtree_triangles`]);
37    /// meaningless for garbage slots.
38    pub(crate) counts: Vec<u32>,
39    /// Nodes and refs no longer reachable from the root.
40    pub(crate) garbage_nodes: usize,
41    pub(crate) garbage_refs: usize,
42    pub(crate) mesh_uid: u64,
43    /// `Mesh::epoch` at the last full build; slot ids are stale once it differs.
44    pub(crate) epoch: u32,
45    pub(crate) revision: u64,
46    /// Fresh process-wide value on every structural change; see [`Bvh::generation`].
47    pub(crate) generation: u64,
48    pub(crate) options: BuildOptions,
49    pub(crate) policy: UpdatePolicy,
50}
51
52impl std::fmt::Debug for Bvh {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.debug_struct("Bvh")
55            .field("nodes", &self.node_count())
56            .field("treelets", &self.treelets.len())
57            .field("depth", &self.depth())
58            .field("bytes", &self.byte_size())
59            .finish()
60    }
61}
62
63/// Collects every alive triangle of `mesh` into `prims`.
64pub(crate) fn alive_prims(mesh: &Mesh) -> Result<Prims> {
65    let mut prims = Prims::with_capacity(mesh.triangle_count())?;
66    let (positions, triangles) = (mesh.positions(), mesh.triangles());
67    for (t, &tri) in triangles.iter().enumerate() {
68        if mesh.is_triangle_alive(t as TriangleId) {
69            prims.push(t as u32, Aabb::of_triangle(positions, tri));
70        }
71    }
72    Ok(prims)
73}
74
75/// Subtree counts computed per unit of a [`BvhBuild::step`] budget (see
76/// [`Assign`]).
77const COUNTS_PER_UNIT: usize = 64;
78
79/// Triangles of a treelet whose cost is computed per unit of a
80/// [`BvhBuild::step`] budget (see [`Assign`]).
81const COST_TRIANGLES_PER_UNIT: usize = 32;
82
83/// Where the treelet assignment of a freshly built tree stands
84/// ([`Bvh::assign_step`]).
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub(crate) enum Assign {
87    Start,
88    /// Counting each node's triangles, node slots below this one left
89    /// (children have larger slots than their parents, so a sweep down
90    /// sees children first).
91    Counts(usize),
92    /// Choosing the treelet roots: the topmost subtrees holding at most
93    /// `treelet_size` triangles, or single leaves.
94    Roots,
95    /// Computing each treelet's build cost, from this treelet on.
96    Costs(usize),
97    Done,
98}
99
100/// Where a [`BvhBuild`] stands.
101enum BuildStage {
102    /// Collecting the alive triangles' boxes (and zeroing the leaf
103    /// table with the same scan), from this triangle slot on.
104    Prims(Prims, usize),
105    /// Building the tree.
106    Tree(Box<TreeBuild>),
107    /// The tree is built; assigning its treelets.
108    Treelets(Box<Bvh>, Assign),
109    /// Done: the BVH is ready to be taken.
110    Done(Box<Bvh>),
111    /// Taken, or failed.
112    Gone,
113}
114
115/// [`Bvh::build`] cut into steps, so that a caller can yield between them:
116/// the triangles' boxes are collected, then the tree is built a bounded
117/// amount of work per step, then its treelets are assigned (subtree
118/// counts, roots and costs), also a bounded amount per step.
119///
120/// The tree is the same, slot for slot and bit for bit, as from
121/// [`Bvh::build`], with or without the `parallel` feature (with it, a step
122/// builds several small subtrees at once). The build is tied to the mesh
123/// state it started from ([`BvhBuild::is_for`]).
124pub struct BvhBuild {
125    stage: BuildStage,
126    options: BuildOptions,
127    arena: Option<Arena>,
128    mesh_uid: u64,
129    epoch: u32,
130    revision: u64,
131    /// The mesh's triangle slots: an undo brings the revision back but
132    /// keeps slots it added.
133    triangle_slots: usize,
134}
135
136impl std::fmt::Debug for BvhBuild {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        f.debug_struct("BvhBuild")
139            .field("done", &self.is_done())
140            .finish()
141    }
142}
143
144impl BvhBuild {
145    /// A build of a BVH over the alive triangles of `mesh` as it is now,
146    /// with nothing done yet. Only memory is reserved here: the leaf
147    /// table (`tri_leaf`, one entry per triangle slot) is zeroed by the
148    /// prim scan in [`BvhBuild::step`] instead, so one call of `new`
149    /// stays short even on a large mesh (a background add runs it inside
150    /// a step).
151    ///
152    /// # Errors
153    /// [`BvhError::InvalidOptions`](crate::BvhError::InvalidOptions); out of
154    /// memory.
155    pub fn new(mesh: &Mesh, options: &BuildOptions) -> Result<BvhBuild> {
156        options.validate()?;
157        let prims = Prims::with_capacity(mesh.triangle_count())?;
158        let mut tri_leaf = Vec::new();
159        try_reserve_exact(&mut tri_leaf, mesh.triangle_slots())?;
160        let arena = Arena {
161            nodes: Vec::new(),
162            parents: Vec::new(),
163            refs: Vec::new(),
164            tri_leaf,
165        };
166        Ok(BvhBuild {
167            stage: BuildStage::Prims(prims, 0),
168            options: *options,
169            arena: Some(arena),
170            mesh_uid: mesh.uid(),
171            epoch: mesh.epoch(),
172            revision: mesh.revision(),
173            triangle_slots: mesh.triangle_slots(),
174        })
175    }
176
177    /// Whether the build started from `mesh` as it is now.
178    pub fn is_for(&self, mesh: &Mesh) -> bool {
179        self.mesh_uid == mesh.uid()
180            && self.epoch == mesh.epoch()
181            && self.revision == mesh.revision()
182            && self.triangle_slots == mesh.triangle_slots()
183    }
184
185    /// Whether the BVH is built.
186    pub fn is_done(&self) -> bool {
187        matches!(self.stage, BuildStage::Done(_))
188    }
189
190    /// Builds about `budget` more units of work (at least some, unless
191    /// done): a triangle's box collected, a triangle visited while the
192    /// tree is built (each triangle about once per tree level), 64 nodes'
193    /// subtree counts, or 32 triangles of a treelet's cost. Returns whether
194    /// the BVH is built.
195    ///
196    /// # Errors
197    /// [`BvhError::InvalidOptions`](crate::BvhError::InvalidOptions) when
198    /// `mesh` is not the state the build started from; out of memory. The
199    /// build is unusable after an error.
200    pub fn step(&mut self, mesh: &Mesh, budget: usize) -> Result<bool> {
201        if !self.is_for(mesh) {
202            self.stage = BuildStage::Gone;
203            return Err(crate::BvhError::InvalidOptions(
204                "the BVH build started from another mesh state",
205            ));
206        }
207        let mut left = budget.max(1);
208        loop {
209            match std::mem::replace(&mut self.stage, BuildStage::Gone) {
210                BuildStage::Prims(mut prims, from) => {
211                    let (positions, triangles) = (mesh.positions(), mesh.triangles());
212                    let to = triangles.len().min(from.saturating_add(left));
213                    for (t, &tri) in triangles.iter().enumerate().take(to).skip(from) {
214                        if mesh.is_triangle_alive(t as TriangleId) {
215                            prims.push(t as u32, Aabb::of_triangle(positions, tri));
216                        }
217                    }
218                    // Zero the leaf table with the scan (same triangle
219                    // slots, in order), so `new` stays O(1): within the
220                    // reserved capacity, so this allocates nothing.
221                    if let Some(arena) = self.arena.as_mut()
222                        && arena.tri_leaf.len() < to
223                    {
224                        arena.tri_leaf.resize(to, NONE);
225                    }
226                    left = left.saturating_sub(to - from);
227                    if to < triangles.len() {
228                        self.stage = BuildStage::Prims(prims, to);
229                        return Ok(false);
230                    }
231                    let arena = self.arena.take().ok_or(crate::BvhError::OutOfMemory)?;
232                    if prims.len() == 0 {
233                        let mut bvh = self.finish_tree(mesh, arena, NONE)?;
234                        bvh.assign_treelets()?;
235                        self.stage = BuildStage::Done(Box::new(bvh));
236                        return Ok(true);
237                    }
238                    self.stage =
239                        BuildStage::Tree(Box::new(TreeBuild::new(arena, prims, &self.options)?));
240                    if left == 0 {
241                        return Ok(false);
242                    }
243                }
244                BuildStage::Tree(mut tree) => {
245                    if !tree.step(left)? {
246                        self.stage = BuildStage::Tree(tree);
247                        return Ok(false);
248                    }
249                    let root = tree.root;
250                    let bvh = self.finish_tree(mesh, tree.arena, root)?;
251                    self.stage = BuildStage::Treelets(Box::new(bvh), Assign::Start);
252                    return Ok(false);
253                }
254                BuildStage::Treelets(mut bvh, mut at) => {
255                    if bvh.assign_step(&mut at, left)? {
256                        self.stage = BuildStage::Done(bvh);
257                        return Ok(true);
258                    }
259                    self.stage = BuildStage::Treelets(bvh, at);
260                    return Ok(false);
261                }
262                done @ BuildStage::Done(_) => {
263                    self.stage = done;
264                    return Ok(true);
265                }
266                BuildStage::Gone => return Err(crate::BvhError::OutOfMemory),
267            }
268        }
269    }
270
271    /// The BVH, once built; `None` before.
272    pub fn finish(self) -> Option<Bvh> {
273        match self.stage {
274            BuildStage::Done(bvh) => Some(*bvh),
275            _ => None,
276        }
277    }
278
279    /// The BVH around a built tree, as [`Bvh::assemble`] makes it, its
280    /// treelets not assigned yet.
281    fn finish_tree(&self, mesh: &Mesh, mut arena: Arena, root: u32) -> Result<Bvh> {
282        if root == NONE {
283            let expected = expected_nodes(0, self.options.max_leaf_size);
284            try_reserve_exact(&mut arena.nodes, expected)?;
285            try_reserve_exact(&mut arena.parents, expected)?;
286        }
287        Ok(Bvh {
288            arena,
289            root,
290            treelets: Vec::new(),
291            treelet_of_root: HashMap::new(),
292            counts: Vec::new(),
293            garbage_nodes: 0,
294            garbage_refs: 0,
295            mesh_uid: mesh.uid(),
296            epoch: mesh.epoch(),
297            revision: mesh.revision(),
298            generation: next_generation(),
299            options: self.options,
300            policy: UpdatePolicy::default(),
301        })
302    }
303}
304
305impl Bvh {
306    /// Builds a BVH over the alive triangles of `mesh`.
307    pub fn build(mesh: &Mesh, options: &BuildOptions) -> Result<Bvh> {
308        options.validate()?;
309        let mut bvh = Self::assemble(mesh, options, &UpdatePolicy::default())?;
310        bvh.revision = mesh.revision();
311        Ok(bvh)
312    }
313
314    /// Replaces the update policy (see [`Bvh::update`]).
315    pub fn set_policy(&mut self, policy: UpdatePolicy) -> Result<()> {
316        policy.validate()?;
317        self.policy = policy;
318        Ok(())
319    }
320
321    pub fn options(&self) -> &BuildOptions {
322        &self.options
323    }
324
325    pub fn policy(&self) -> &UpdatePolicy {
326        &self.policy
327    }
328
329    /// The mesh revision this BVH matches (see [`Mesh::revision`]). Manual
330    /// [`Bvh::refit`] and [`Bvh::rebuild_region`] calls do not change it.
331    pub fn revision(&self) -> u64 {
332        self.revision
333    }
334
335    /// True when the BVH matches the mesh's current revision (and the mesh has
336    /// not been compacted since).
337    pub fn is_current(&self, mesh: &Mesh) -> bool {
338        self.mesh_uid == mesh.uid()
339            && self.epoch == mesh.epoch()
340            && self.revision == mesh.revision()
341    }
342
343    /// Builds a complete new tree. Nothing outside the returned value is touched,
344    /// so callers can swap it in only on success.
345    pub(crate) fn assemble(
346        mesh: &Mesh,
347        options: &BuildOptions,
348        policy: &UpdatePolicy,
349    ) -> Result<Bvh> {
350        let mut prims = alive_prims(mesh)?;
351        let n = prims.len();
352        let mut arena = Arena {
353            nodes: Vec::new(),
354            parents: Vec::new(),
355            refs: Vec::new(),
356            tri_leaf: try_vec(mesh.triangle_slots(), NONE)?,
357        };
358        let root = if n == 0 {
359            let expected = expected_nodes(n, options.max_leaf_size);
360            try_reserve_exact(&mut arena.nodes, expected)?;
361            try_reserve_exact(&mut arena.parents, expected)?;
362            NONE
363        } else {
364            build_full(&mut arena, &mut prims, options)?
365        };
366        let mut bvh = Bvh {
367            arena,
368            root,
369            treelets: Vec::new(),
370            treelet_of_root: HashMap::new(),
371            counts: Vec::new(),
372            garbage_nodes: 0,
373            garbage_refs: 0,
374            mesh_uid: mesh.uid(),
375            epoch: mesh.epoch(),
376            revision: mesh.revision(),
377            generation: next_generation(),
378            options: *options,
379            policy: *policy,
380        };
381        bvh.assign_treelets()?;
382        Ok(bvh)
383    }
384
385    /// Counts each node's triangles and marks the topmost subtrees holding at
386    /// most `treelet_size` triangles (or single leaves) as treelets.
387    /// Requires a tree without garbage.
388    fn assign_treelets(&mut self) -> Result<()> {
389        let mut at = Assign::Start;
390        while !self.assign_step(&mut at, usize::MAX)? {}
391        Ok(())
392    }
393
394    /// [`Bvh::assign_treelets`] in steps of about `budget` units (64 nodes
395    /// counted, or 32 triangles of a treelet whose cost is computed; at least
396    /// one), from `at`. Returns whether it is done. The treelets' costs are
397    /// meaningless until then.
398    pub(crate) fn assign_step(&mut self, at: &mut Assign, budget: usize) -> Result<bool> {
399        let mut left = budget.max(1);
400        loop {
401            match *at {
402                Assign::Start => {
403                    self.treelets.clear();
404                    self.treelet_of_root.clear();
405                    self.counts = Vec::new();
406                    if self.root == NONE {
407                        *at = Assign::Done;
408                        return Ok(true);
409                    }
410                    self.counts = try_vec(self.arena.nodes.len(), 0u32)?;
411                    *at = Assign::Counts(self.arena.nodes.len());
412                }
413                Assign::Counts(end) => {
414                    let start = end.saturating_sub(left.saturating_mul(COUNTS_PER_UNIT));
415                    left = left.saturating_sub((end - start).div_ceil(COUNTS_PER_UNIT));
416                    let (nodes, counts) = (&self.arena.nodes, &mut self.counts);
417                    for i in (start..end).rev() {
418                        let n = &nodes[i];
419                        counts[i] = if n.is_leaf() {
420                            n.leaf_range().len() as u32
421                        } else {
422                            counts[n.a as usize] + counts[n.b as usize]
423                        };
424                    }
425                    *at = if start == 0 {
426                        Assign::Roots
427                    } else {
428                        Assign::Counts(start)
429                    };
430                }
431                Assign::Roots => {
432                    // The tree above the treelets: small, in one go.
433                    let limit = self.options.treelet_size;
434                    let counts = &self.counts;
435                    let is_treelet_root =
436                        |i: u32, n: &Node| n.is_leaf() || counts[i as usize] <= limit;
437                    let mut found = 0;
438                    self.arena.traverse(self.root, |i, n| {
439                        let root = is_treelet_root(i, n);
440                        found += usize::from(root);
441                        !root
442                    });
443                    try_reserve_map(&mut self.treelet_of_root, found)?;
444                    try_reserve(&mut self.treelets, found)?;
445                    let (treelets, map) = (&mut self.treelets, &mut self.treelet_of_root);
446                    self.arena.traverse(self.root, |i, n| {
447                        let root = is_treelet_root(i, n);
448                        if root {
449                            map.insert(i, treelets.len());
450                            treelets.push(Treelet {
451                                root: i,
452                                build_cost: 0.0,
453                            });
454                        }
455                        !root
456                    });
457                    left = left.saturating_sub(found.div_ceil(COUNTS_PER_UNIT));
458                    *at = Assign::Costs(0);
459                }
460                Assign::Costs(from) => {
461                    let mut i = from;
462                    while i < self.treelets.len() && left > 0 {
463                        let t = &mut self.treelets[i];
464                        t.build_cost = self.arena.sah_cost(t.root);
465                        let tris = self.counts[t.root as usize] as usize;
466                        left = left.saturating_sub(tris.div_ceil(COST_TRIANGLES_PER_UNIT).max(1));
467                        i += 1;
468                    }
469                    *at = if i == self.treelets.len() {
470                        Assign::Done
471                    } else {
472                        Assign::Costs(i)
473                    };
474                }
475                Assign::Done => return Ok(true),
476            }
477            if left == 0 {
478                return Ok(*at == Assign::Done);
479            }
480        }
481    }
482
483    /// The triangles in the subtree of `id`: exact, kept up to date by
484    /// builds, updates and compactions (refits change none). Lets a caller
485    /// bound the work of a subtree on any tree, balanced or not.
486    ///
487    /// As for [`Bvh::node`], `id` must come from this BVH and not predate its
488    /// last modification.
489    ///
490    /// # Panics
491    /// When `id` is past the BVH's nodes (an id from another BVH, or from
492    /// before a compaction); an id that is in range but stale gives an
493    /// unrelated count.
494    pub fn subtree_triangles(&self, id: NodeId) -> usize {
495        self.counts[id.index()] as usize
496    }
497
498    /// Treelet containing `node` (a node inside or at the root of a treelet), or
499    /// `None` when the walk up reaches the root without meeting a treelet root
500    /// (which only happens for nodes outside every treelet).
501    pub(crate) fn treelet_of(&self, mut node: u32) -> Option<usize> {
502        while node != NONE {
503            if let Some(&t) = self.treelet_of_root.get(&node) {
504                return Some(t);
505            }
506            node = self.arena.parents[node as usize];
507        }
508        None
509    }
510
511    /// Appends every alive triangle whose bounding box overlaps the box
512    /// `[min, max]` (touching counts), in no particular order.
513    ///
514    /// The BVH must be current for `mesh` ([`Bvh::is_current`]); a stale tree
515    /// may return wrong results or panic.
516    pub fn query_aabb(&self, mesh: &Mesh, min: [f32; 3], max: [f32; 3], out: &mut Vec<TriangleId>) {
517        debug_assert!(self.is_current(mesh), "query_aabb on a stale BVH");
518        let query = Aabb::new(min, max);
519        let (positions, triangles) = (mesh.positions(), mesh.triangles());
520        self.traverse(|_, node| {
521            if !node.bounds.overlaps(&query) {
522                return false;
523            }
524            if let NodeKind::Leaf { triangles: leaf } = node.kind {
525                for &t in leaf {
526                    if Aabb::of_triangle(positions, triangles[t as usize]).overlaps(&query) {
527                        out.push(t);
528                    }
529                }
530            }
531            true
532        });
533    }
534
535    /// Reachable nodes.
536    pub fn node_count(&self) -> usize {
537        self.arena.nodes.len() - self.garbage_nodes
538    }
539
540    pub fn treelet_count(&self) -> usize {
541        self.treelets.len()
542    }
543
544    /// Bounds of all triangles, or `None` for an empty tree.
545    pub fn root_bounds(&self) -> Option<Aabb> {
546        (self.root != NONE).then(|| self.arena.nodes[self.root as usize].bounds)
547    }
548
549    /// Longest root-to-leaf path, counting nodes (0 for an empty tree).
550    pub fn depth(&self) -> usize {
551        let mut deepest = 0;
552        self.arena.traverse(self.root, |i, n| {
553            if n.is_leaf() {
554                let mut d = 1;
555                let mut cur = i;
556                while self.arena.parents[cur as usize] != NONE {
557                    cur = self.arena.parents[cur as usize];
558                    d += 1;
559                }
560                deepest = deepest.max(d);
561            }
562            true
563        });
564        deepest
565    }
566
567    /// Heap memory held by the BVH, garbage included.
568    pub fn byte_size(&self) -> usize {
569        let a = &self.arena;
570        a.nodes.capacity() * size_of::<Node>()
571            + (a.parents.capacity() + a.refs.capacity() + a.tri_leaf.capacity()) * 4
572            + self.counts.capacity() * 4
573            + self.treelets.capacity() * size_of::<Treelet>()
574            + self.treelet_of_root.capacity() * 16
575    }
576
577    /// Copies the reachable nodes and refs into fresh storage in depth-first
578    /// order, dropping garbage. On allocation failure nothing changes.
579    pub(crate) fn compact_storage(&mut self) -> Result<()> {
580        let live_nodes = self.node_count();
581        let live_refs = self.arena.refs.len() - self.garbage_refs;
582        let mut new_index = try_vec(self.arena.nodes.len(), NONE)?;
583        let mut order = Vec::new();
584        try_reserve(&mut order, live_nodes)?;
585        let mut nodes = Vec::new();
586        try_reserve(&mut nodes, live_nodes)?;
587        let mut parents = Vec::new();
588        try_reserve(&mut parents, live_nodes)?;
589        let mut counts = Vec::new();
590        try_reserve(&mut counts, live_nodes)?;
591        let mut refs = Vec::new();
592        try_reserve(&mut refs, live_refs)?;
593        let mut tri_leaf = try_vec(self.arena.tri_leaf.len(), NONE)?;
594        let mut roots = HashMap::new();
595        try_reserve_map(&mut roots, self.treelets.len())?;
596
597        // Phase 2: nothing below allocates.
598        self.arena.traverse(self.root, |i, _| {
599            new_index[i as usize] = order.len() as u32;
600            order.push(i);
601            true
602        });
603        for &old in &order {
604            let n = self.arena.nodes[old as usize];
605            let me = nodes.len() as u32;
606            let parent = self.arena.parents[old as usize];
607            parents.push(if parent == NONE {
608                NONE
609            } else {
610                new_index[parent as usize]
611            });
612            counts.push(self.counts[old as usize]);
613            if n.is_leaf() {
614                let first = refs.len() as u32;
615                for &t in &self.arena.refs[n.leaf_range()] {
616                    refs.push(t);
617                    tri_leaf[t as usize] = me;
618                }
619                nodes.push(Node::leaf(n.bounds, first, n.leaf_range().len() as u32));
620            } else {
621                nodes.push(Node {
622                    bounds: n.bounds,
623                    a: new_index[n.a as usize],
624                    b: new_index[n.b as usize],
625                });
626            }
627        }
628        for (i, t) in self.treelets.iter_mut().enumerate() {
629            t.root = new_index[t.root as usize];
630            roots.insert(t.root, i);
631        }
632        if self.root != NONE {
633            self.root = new_index[self.root as usize];
634        }
635        self.arena = Arena {
636            nodes,
637            parents,
638            refs,
639            tri_leaf,
640        };
641        self.treelet_of_root = roots;
642        self.counts = counts;
643        self.garbage_nodes = 0;
644        self.garbage_refs = 0;
645        self.generation = next_generation();
646        Ok(())
647    }
648
649    /// Replaces the whole tree with a fresh build (which gets a fresh
650    /// generation). On error nothing changes.
651    pub(crate) fn rebuild_from(&mut self, mesh: &Mesh) -> Result<()> {
652        *self = Bvh::assemble(mesh, &self.options, &self.policy)?;
653        Ok(())
654    }
655
656    /// Whether `node` is reachable from the root, by walking parent links and
657    /// checking that each parent really links to the child.
658    fn reachable(&self, mut node: u32) -> bool {
659        let a = &self.arena;
660        for _ in 0..a.nodes.len() {
661            if node == self.root {
662                return true;
663            }
664            let parent = a.parents[node as usize];
665            if parent == NONE {
666                return false;
667            }
668            let p = &a.nodes[parent as usize];
669            if p.is_leaf() || (p.a != node && p.b != node) {
670                return false;
671            }
672            node = parent;
673        }
674        false
675    }
676
677    /// Checks every structural invariant against `mesh`. Meant for tests and
678    /// debugging; returns a description of the first violation.
679    pub fn validate(&self, mesh: &Mesh) -> std::result::Result<(), String> {
680        let a = &self.arena;
681        let (positions, triangles) = (mesh.positions(), mesh.triangles());
682        if self.root != NONE && a.parents[self.root as usize] != NONE {
683            return Err("root has a parent".into());
684        }
685        let mut error: Option<String> = None;
686        let (mut reachable, mut refs_seen) = (0usize, 0usize);
687        a.traverse(self.root, |i, n| {
688            if error.is_some() {
689                return false;
690            }
691            reachable += 1;
692            let count = if n.is_leaf() {
693                n.leaf_range().len() as u32
694            } else {
695                let c = |k: u32| self.counts.get(k as usize).copied().unwrap_or(u32::MAX);
696                c(n.a).saturating_add(c(n.b))
697            };
698            if self.counts.get(i as usize) != Some(&count) {
699                error = Some(format!("node {i} has the wrong subtree count"));
700                return false;
701            }
702            if n.is_leaf() {
703                for &t in &a.refs[n.leaf_range()] {
704                    refs_seen += 1;
705                    if !mesh.is_triangle_alive(t) {
706                        error = Some(format!("leaf {i} references dead triangle {t}"));
707                    } else if a.tri_leaf.get(t as usize) != Some(&i) {
708                        error = Some(format!(
709                            "tri_leaf of triangle {t} does not point at leaf {i}"
710                        ));
711                    } else if !n
712                        .bounds
713                        .contains(&Aabb::of_triangle(positions, triangles[t as usize]))
714                    {
715                        error = Some(format!("leaf {i} box does not contain triangle {t}"));
716                    }
717                }
718            } else {
719                for c in [n.a, n.b] {
720                    if a.parents[c as usize] != i {
721                        error = Some(format!("child {c} of node {i} has the wrong parent"));
722                    } else if !n.bounds.contains(&a.nodes[c as usize].bounds) {
723                        error = Some(format!("node {i} box does not contain child {c}"));
724                    }
725                }
726            }
727            true
728        });
729        if let Some(e) = error {
730            return Err(e);
731        }
732        if refs_seen != mesh.triangle_count() {
733            return Err(format!(
734                "tree holds {refs_seen} triangles, mesh has {}",
735                mesh.triangle_count()
736            ));
737        }
738        if reachable + self.garbage_nodes != a.nodes.len() {
739            return Err(format!(
740                "{reachable} reachable + {} garbage nodes != {} stored",
741                self.garbage_nodes,
742                a.nodes.len()
743            ));
744        }
745        if refs_seen + self.garbage_refs != a.refs.len() {
746            return Err("reachable + garbage refs != stored refs".into());
747        }
748        // Every triangle → leaf entry must point at a reachable leaf holding it.
749        for (t, &leaf) in a.tri_leaf.iter().enumerate() {
750            if leaf == NONE {
751                continue;
752            }
753            let holds = a
754                .nodes
755                .get(leaf as usize)
756                .is_some_and(|n| n.is_leaf() && a.refs[n.leaf_range()].contains(&(t as u32)));
757            if !holds || !self.reachable(leaf) {
758                return Err(format!(
759                    "tri_leaf of triangle {t} points at node {leaf}, which is not a reachable leaf holding it"
760                ));
761            }
762        }
763        for (i, t) in self.treelets.iter().enumerate() {
764            if self.treelet_of_root.get(&t.root) != Some(&i) {
765                return Err(format!("treelet {i} is not indexed by its root"));
766            }
767        }
768        if self.root != NONE {
769            // Every leaf must sit inside exactly one treelet.
770            let mut leaves_in_treelets = 0;
771            for t in &self.treelets {
772                a.traverse(t.root, |_, n| {
773                    if n.is_leaf() {
774                        leaves_in_treelets += 1;
775                    }
776                    true
777                });
778            }
779            let mut leaves = 0;
780            a.traverse(self.root, |_, n| {
781                if n.is_leaf() {
782                    leaves += 1;
783                }
784                true
785            });
786            if leaves != leaves_in_treelets {
787                return Err(format!(
788                    "{leaves} leaves but {leaves_in_treelets} inside treelets"
789                ));
790            }
791        }
792        Ok(())
793    }
794}
795
796#[cfg(test)]
797impl Bvh {
798    /// Test-only: number of triangles in each treelet.
799    pub(crate) fn treelet_triangle_counts(&self) -> Vec<usize> {
800        self.treelets
801            .iter()
802            .map(|t| self.arena.subtree_size(t.root).1)
803            .collect()
804    }
805
806    /// Test-only: everything that defines the tree, to compare before and after
807    /// a failed operation. `tri_leaf` entries are compared as (triangle, leaf)
808    /// pairs so growing it with `NONE` does not count as a change.
809    pub(crate) fn fingerprint(&self) -> String {
810        let a = &self.arena;
811        let leaves: Vec<(usize, u32)> = a
812            .tri_leaf
813            .iter()
814            .enumerate()
815            .filter(|&(_, &l)| l != NONE)
816            .map(|(t, &l)| (t, l))
817            .collect();
818        let mut treelets: Vec<(u32, usize)> =
819            self.treelet_of_root.iter().map(|(&r, &i)| (r, i)).collect();
820        treelets.sort();
821        format!(
822            "{:?}|{:?}|{:?}|{:?}|{:?}|{}|{:?}|{}|{}|{}|{}",
823            a.nodes
824                .iter()
825                .map(|n| (n.bounds, n.a, n.b))
826                .collect::<Vec<_>>(),
827            a.parents,
828            self.counts,
829            a.refs,
830            leaves,
831            self.root,
832            treelets,
833            self.garbage_nodes,
834            self.garbage_refs,
835            self.epoch,
836            self.revision,
837        )
838    }
839}
840
841#[cfg(test)]
842mod tests {
843    use super::*;
844    use crate::{BvhError, Quality};
845
846    /// n×n grid of quads in the XY plane.
847    pub(crate) fn grid(n: u32) -> Mesh {
848        let mut positions = Vec::new();
849        for y in 0..=n {
850            for x in 0..=n {
851                positions.extend([x as f32, y as f32, 0.0]);
852            }
853        }
854        let mut indices = Vec::new();
855        for y in 0..n {
856            for x in 0..n {
857                let a = y * (n + 1) + x;
858                indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
859            }
860        }
861        Mesh::from_buffers(&positions, &indices).unwrap()
862    }
863
864    fn brute(mesh: &Mesh, q: &Aabb) -> Vec<u32> {
865        (0..mesh.triangle_slots() as u32)
866            .filter(|&t| {
867                mesh.triangle(t)
868                    .is_some_and(|tri| Aabb::of_triangle(mesh.positions(), tri).overlaps(q))
869            })
870            .collect()
871    }
872
873    #[test]
874    fn builds_and_queries_both_qualities() {
875        let mesh = grid(20);
876        for quality in [Quality::Sah, Quality::Fast] {
877            let opts = BuildOptions {
878                quality,
879                treelet_size: 64,
880                ..Default::default()
881            };
882            let bvh = Bvh::build(&mesh, &opts).unwrap();
883            bvh.validate(&mesh).unwrap();
884            assert!(bvh.treelet_count() > 1);
885            assert_eq!(
886                bvh.root_bounds(),
887                Some(Aabb::new([0.0; 3], [20.0, 20.0, 0.0]))
888            );
889            let q = Aabb::new([3.5, 3.5, -1.0], [7.0, 5.0, 1.0]);
890            let mut got = Vec::new();
891            bvh.query_aabb(&mesh, q.min, q.max, &mut got);
892            got.sort();
893            assert_eq!(got, brute(&mesh, &q), "{quality:?}");
894        }
895    }
896
897    #[test]
898    fn empty_mesh_and_single_triangle() {
899        let empty = Mesh::new();
900        let bvh = Bvh::build(&empty, &BuildOptions::default()).unwrap();
901        bvh.validate(&empty).unwrap();
902        assert_eq!(
903            (bvh.node_count(), bvh.depth(), bvh.root_bounds()),
904            (0, 0, None)
905        );
906        let mut out = Vec::new();
907        bvh.query_aabb(&empty, [-1.0; 3], [1.0; 3], &mut out);
908        assert!(out.is_empty());
909
910        let one = Mesh::from_buffers(&[0., 0., 0., 1., 0., 0., 0., 1., 0.], &[0, 1, 2]).unwrap();
911        let bvh = Bvh::build(&one, &BuildOptions::default()).unwrap();
912        bvh.validate(&one).unwrap();
913        assert_eq!(
914            (bvh.node_count(), bvh.depth(), bvh.treelet_count()),
915            (1, 1, 1)
916        );
917    }
918
919    #[test]
920    fn dead_slots_are_skipped() {
921        let mut mesh = grid(4);
922        let mut tx = mesh.begin_edit();
923        tx.remove_triangles(&[0, 5, 9]).unwrap();
924        tx.commit().unwrap();
925        let bvh = Bvh::build(&mesh, &BuildOptions::default()).unwrap();
926        bvh.validate(&mesh).unwrap();
927        let mut out = Vec::new();
928        bvh.query_aabb(&mesh, [-1.0; 3], [10.0; 3], &mut out);
929        assert_eq!(out.len(), mesh.triangle_count());
930        assert!(!out.contains(&5));
931    }
932
933    #[test]
934    fn degenerate_and_non_finite_triangles_do_not_break_the_build() {
935        let mesh = Mesh::from_buffers(
936            &[
937                0.,
938                0.,
939                0.,
940                1.,
941                0.,
942                0.,
943                f32::NAN,
944                1.,
945                0.,
946                1e30,
947                1e30,
948                1e30,
949                2.,
950                2.,
951                2.,
952            ],
953            &[0, 0, 0, 0, 1, 2, 1, 3, 4, 4, 4, 4, 0, 1, 4],
954        )
955        .unwrap();
956        for quality in [Quality::Sah, Quality::Fast] {
957            let opts = BuildOptions {
958                quality,
959                max_leaf_size: 1,
960                ..Default::default()
961            };
962            let bvh = Bvh::build(&mesh, &opts).unwrap();
963            bvh.validate(&mesh).unwrap();
964            let mut out = Vec::new();
965            let q = Aabb::new([0.5, -1.0, -1.0], [1.5, 1.0, 1.0]);
966            bvh.query_aabb(&mesh, q.min, q.max, &mut out);
967            out.sort();
968            assert_eq!(out, brute(&mesh, &q));
969        }
970    }
971
972    #[test]
973    fn invalid_options_are_rejected() {
974        let opts = BuildOptions {
975            max_leaf_size: 17,
976            ..Default::default()
977        };
978        assert!(matches!(
979            Bvh::build(&grid(1), &opts),
980            Err(BvhError::InvalidOptions(_))
981        ));
982    }
983
984    #[test]
985    fn separate_builds_get_different_generations() {
986        let mesh = grid(4);
987        let a = Bvh::build(&mesh, &BuildOptions::default()).unwrap();
988        let b = Bvh::build(&mesh, &BuildOptions::default()).unwrap();
989        assert_ne!(a.generation(), b.generation());
990    }
991
992    #[test]
993    fn compaction_preserves_the_tree() {
994        let mesh = grid(10);
995        let mut bvh = Bvh::build(
996            &mesh,
997            &BuildOptions {
998                treelet_size: 16,
999                ..Default::default()
1000            },
1001        )
1002        .unwrap();
1003        let before = bvh.node_count();
1004        bvh.compact_storage().unwrap();
1005        bvh.validate(&mesh).unwrap();
1006        assert_eq!(bvh.node_count(), before);
1007    }
1008
1009    /// A wavy grid with some triangles removed, so slots and alive
1010    /// triangles differ and boxes are not coplanar.
1011    fn wavy(n: u32) -> Mesh {
1012        let mut positions = Vec::new();
1013        for y in 0..=n {
1014            for x in 0..=n {
1015                let (fx, fy) = (x as f32, y as f32);
1016                positions.extend([fx, fy, (fx * 0.31).sin() * (fy * 0.17).cos() * 3.0]);
1017            }
1018        }
1019        let mut indices = Vec::new();
1020        for y in 0..n {
1021            for x in 0..n {
1022                let a = y * (n + 1) + x;
1023                indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
1024            }
1025        }
1026        let mut mesh = Mesh::from_buffers(&positions, &indices).unwrap();
1027        let mut tx = mesh.begin_edit();
1028        let gone: Vec<u32> = [0, 7, 100, 101, 2 * n * n - 1]
1029            .into_iter()
1030            .filter(|&t| t < 2 * n * n)
1031            .collect();
1032        tx.remove_triangles(&gone).unwrap();
1033        tx.commit().unwrap();
1034        mesh
1035    }
1036
1037    /// Node numbers, ids, the root and the treelets of a BVH, as bits.
1038    type Fingerprint = (Vec<u64>, Vec<u32>, u32, Vec<(u32, u32)>);
1039
1040    /// Everything a BVH holds, as bits.
1041    fn fingerprint(b: &Bvh) -> Fingerprint {
1042        let a = &b.arena;
1043        let nodes = a
1044            .nodes
1045            .iter()
1046            .flat_map(|n| {
1047                n.bounds
1048                    .min
1049                    .iter()
1050                    .chain(&n.bounds.max)
1051                    .map(|c| u64::from(c.to_bits()))
1052                    .chain([u64::from(n.a), u64::from(n.b)])
1053                    .collect::<Vec<_>>()
1054            })
1055            .collect();
1056        let ids = a
1057            .parents
1058            .iter()
1059            .chain(&a.refs)
1060            .chain(&a.tri_leaf)
1061            .chain(&b.counts)
1062            .copied()
1063            .collect();
1064        let treelets = b
1065            .treelets
1066            .iter()
1067            .map(|t| (t.root, t.build_cost.to_bits()))
1068            .collect();
1069        (nodes, ids, b.root, treelets)
1070    }
1071
1072    fn stepped(mesh: &Mesh, opts: &BuildOptions, budget: usize) -> (Bvh, usize) {
1073        let mut build = BvhBuild::new(mesh, opts).unwrap();
1074        let mut steps = 1;
1075        while !build.step(mesh, budget).unwrap() {
1076            steps += 1;
1077        }
1078        assert!(build.is_done());
1079        (build.finish().unwrap(), steps)
1080    }
1081
1082    #[test]
1083    fn stepped_build_matches_one_call() {
1084        for quality in [Quality::Sah, Quality::Fast] {
1085            for (n, leaf) in [(3, 1), (40, 4), (90, 8)] {
1086                let mesh = wavy(n);
1087                let opts = BuildOptions {
1088                    quality,
1089                    max_leaf_size: leaf,
1090                    treelet_size: 64,
1091                    ..Default::default()
1092                };
1093                let want = fingerprint(&Bvh::build(&mesh, &opts).unwrap());
1094                for budget in [1, 50, 3000, usize::MAX] {
1095                    let (got, steps) = stepped(&mesh, &opts, budget);
1096                    got.validate(&mesh).unwrap();
1097                    assert!(
1098                        fingerprint(&got) == want,
1099                        "{quality:?} n={n} budget {budget}"
1100                    );
1101                    if budget == 1 && n == 90 {
1102                        assert!(steps > 1000, "{steps}");
1103                    }
1104                }
1105            }
1106        }
1107        // Empty and single-triangle meshes.
1108        for mesh in [Mesh::new(), grid(1)] {
1109            let opts = BuildOptions::default();
1110            let want = fingerprint(&Bvh::build(&mesh, &opts).unwrap());
1111            assert!(fingerprint(&stepped(&mesh, &opts, 1).0) == want);
1112        }
1113    }
1114
1115    #[test]
1116    fn new_only_reserves_the_leaf_table() {
1117        // #64: zeroing the leaf table (one entry per triangle slot) was
1118        // an O(n) memset inside `new` — the 12.6 ms single step of a 10M
1119        // background add's store-to-BVH transition. It now fills with the
1120        // prim scan, so `new` stays short; the built tree is unchanged.
1121        let mesh = grid(10);
1122        let opts = BuildOptions::default();
1123        let mut build = BvhBuild::new(&mesh, &opts).unwrap();
1124        assert!(build.arena.as_ref().unwrap().tri_leaf.is_empty());
1125        let mut last = 0;
1126        while !build.step(&mesh, 7).unwrap() {
1127            // The arena moves into the tree once the prim scan ends.
1128            if matches!(build.stage, BuildStage::Prims(..)) {
1129                let len = build.arena.as_ref().unwrap().tri_leaf.len();
1130                assert!(len >= last);
1131                last = len;
1132            }
1133        }
1134        let got = build.finish().unwrap();
1135        got.validate(&mesh).unwrap();
1136        assert!(fingerprint(&got) == fingerprint(&Bvh::build(&mesh, &opts).unwrap()));
1137    }
1138
1139    #[test]
1140    fn stepped_build_assigns_treelets_in_steps() {
1141        // The last stage (subtree counts, treelet roots and their costs) is
1142        // stepped too: one pass over every node would be one long step on
1143        // a large mesh.
1144        let mesh = wavy(90);
1145        let opts = BuildOptions {
1146            max_leaf_size: 1,
1147            treelet_size: 64,
1148            ..Default::default()
1149        };
1150        let want = Bvh::build(&mesh, &opts).unwrap();
1151        let mut build = BvhBuild::new(&mesh, &opts).unwrap();
1152        while !matches!(build.stage, BuildStage::Treelets(..)) {
1153            assert!(!build.step(&mesh, 1).unwrap());
1154        }
1155        let mut steps = 1;
1156        while !build.step(&mesh, 1).unwrap() {
1157            steps += 1;
1158        }
1159        assert!(steps > want.node_count() / 64, "{steps}");
1160        let got = build.finish().unwrap();
1161        got.validate(&mesh).unwrap();
1162        assert!(fingerprint(&got) == fingerprint(&want));
1163    }
1164
1165    #[test]
1166    fn stepped_build_bit_identical_in_parallel() {
1167        use fast_mesh_store::par;
1168        // Large enough for chunked reductions and parallel parts.
1169        let mesh = wavy(260);
1170        for quality in [Quality::Sah, Quality::Fast] {
1171            let opts = BuildOptions {
1172                quality,
1173                ..Default::default()
1174            };
1175            par::set_force_sequential(true);
1176            let want = fingerprint(&Bvh::build(&mesh, &opts).unwrap());
1177            par::set_force_sequential(false);
1178            assert!(fingerprint(&Bvh::build(&mesh, &opts).unwrap()) == want);
1179            for budget in [5_000, 20_000, 300_000, usize::MAX] {
1180                for k in [1, 8] {
1181                    let got =
1182                        par::with_threads(k, || fingerprint(&stepped(&mesh, &opts, budget).0));
1183                    assert!(got == want, "{quality:?} budget {budget}, {k} threads");
1184                }
1185            }
1186        }
1187    }
1188
1189    #[test]
1190    fn stepped_build_refuses_another_state() {
1191        let mut mesh = wavy(10);
1192        let mut build = BvhBuild::new(&mesh, &BuildOptions::default()).unwrap();
1193        assert!(!build.step(&mesh, 5).unwrap());
1194        let mut tx = mesh.begin_edit();
1195        tx.set_positions(&[0], &[[0.0, 0.0, 9.0]]).unwrap();
1196        tx.commit().unwrap();
1197        assert!(!build.is_for(&mesh));
1198        assert!(matches!(
1199            build.step(&mesh, 5),
1200            Err(BvhError::InvalidOptions(_))
1201        ));
1202        assert!(build.finish().is_none());
1203        let bad = BuildOptions {
1204            max_leaf_size: 0,
1205            ..Default::default()
1206        };
1207        assert!(matches!(
1208            BvhBuild::new(&mesh, &bad),
1209            Err(BvhError::InvalidOptions(_))
1210        ));
1211    }
1212
1213    #[test]
1214    fn stepped_build_refuses_a_grown_mesh_after_undo() {
1215        let mut mesh = wavy(10);
1216        let mut build = BvhBuild::new(&mesh, &BuildOptions::default()).unwrap();
1217        assert!(!build.step(&mesh, 5).unwrap());
1218        let revision = mesh.revision();
1219        let mut tx = mesh.begin_edit();
1220        let vs = tx
1221            .add_vertices(&[[20.0, 20.0, 0.0], [21.0, 20.0, 0.0], [20.0, 21.0, 0.0]])
1222            .unwrap();
1223        tx.add_triangles(&[[vs[0], vs[1], vs[2]]]).unwrap();
1224        let patch = tx.commit().unwrap();
1225        patch.revert(&mut mesh).unwrap();
1226        assert_eq!(mesh.revision(), revision);
1227        assert!(!build.is_for(&mesh));
1228        assert!(matches!(
1229            build.step(&mesh, 5),
1230            Err(BvhError::InvalidOptions(_))
1231        ));
1232    }
1233}