Skip to main content

fast_mesh_bvh/
node.rs

1//! Read-only view of the tree, for queries built on top of the BVH.
2
3use fast_mesh_store::TriangleId;
4
5use crate::arena::{NONE, Node};
6use crate::{Aabb, Bvh};
7
8/// How many pending nodes [`Bvh::traverse_by_key`] keeps in its fixed stack.
9/// Deeper pending work falls back to a stackless walk.
10#[doc(hidden)]
11pub const TRAVERSE_STACK: usize = 64;
12
13/// Identifies a node of one [`Bvh`]. Only valid until that BVH is next
14/// modified (updated, refit or rebuilt).
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct NodeId(u32);
17
18impl NodeId {
19    /// Dense index of the node, below [`Bvh::node_slots`], for keying
20    /// per-node data in a plain array. Valid only for the BVH generation
21    /// ([`Bvh::generation`]) it was obtained in.
22    pub fn index(self) -> usize {
23        self.0 as usize
24    }
25}
26
27/// What a node holds.
28#[derive(Debug, Clone, Copy)]
29pub enum NodeKind<'a> {
30    /// An inner node with two children.
31    Inner {
32        /// The left child.
33        left: NodeId,
34        /// The right child.
35        right: NodeId,
36    },
37    /// A leaf holding triangles.
38    Leaf {
39        /// The triangles stored in this leaf.
40        triangles: &'a [TriangleId],
41    },
42}
43
44/// A node's bounds and contents.
45#[derive(Debug, Clone, Copy)]
46pub struct NodeView<'a> {
47    /// Box enclosing everything below the node.
48    pub bounds: Aabb,
49    /// Children or triangles.
50    pub kind: NodeKind<'a>,
51}
52
53impl Bvh {
54    fn view(&self, node: &Node) -> NodeView<'_> {
55        let kind = if node.is_leaf() {
56            NodeKind::Leaf {
57                triangles: &self.arena.refs[node.leaf_range()],
58            }
59        } else {
60            NodeKind::Inner {
61                left: NodeId(node.a),
62                right: NodeId(node.b),
63            }
64        };
65        NodeView {
66            bounds: node.bounds,
67            kind,
68        }
69    }
70
71    /// The root node, or `None` for an empty tree.
72    pub fn root(&self) -> Option<NodeId> {
73        (self.root != NONE).then_some(NodeId(self.root))
74    }
75
76    /// The bounds and contents of `id`. `id` must come from this BVH and not
77    /// predate its last modification; otherwise this may panic or return
78    /// unrelated data.
79    pub fn node(&self, id: NodeId) -> NodeView<'_> {
80        self.view(&self.arena.nodes[id.0 as usize])
81    }
82
83    /// The parent of `id`, or `None` for the root.
84    pub fn parent(&self, id: NodeId) -> Option<NodeId> {
85        let p = self.arena.parents[id.0 as usize];
86        (p != NONE).then_some(NodeId(p))
87    }
88
89    /// Visits the tree in depth-first pre-order (left child first), without a
90    /// stack. `visit` returns whether to descend into the node's children
91    /// (ignored for leaves).
92    pub fn traverse(&self, mut visit: impl FnMut(NodeId, &NodeView) -> bool) {
93        self.arena
94            .traverse(self.root, |i, n| visit(NodeId(i), &self.view(n)));
95    }
96
97    /// Like [`Bvh::traverse`], but at each inner node `first_is_left` gets the
98    /// left and right child bounds and says whether the left child is visited
99    /// first. It must be pure: it may be called more than once per node, and an
100    /// impure predicate (one that gives different answers for the same bounds)
101    /// can make the traversal skip subtrees or loop forever. It is still
102    /// memory-safe.
103    pub fn traverse_ordered(
104        &self,
105        first_is_left: impl Fn(&Aabb, &Aabb) -> bool,
106        mut visit: impl FnMut(NodeId, &NodeView) -> bool,
107    ) {
108        self.arena
109            .traverse_ordered(self.root, first_is_left, |i, n| {
110                visit(NodeId(i), &self.view(n))
111            });
112    }
113
114    /// Visits the tree depth-first, nearest first: of two children, the one
115    /// with the smaller key is visited first (the left one on equal keys, or
116    /// when the keys do not compare).
117    ///
118    /// `key` maps a node's bounds to its key; a child whose key is `None` is
119    /// skipped together with its subtree, and so is the root. `visit` gets the
120    /// node, its view and its key, and returns whether to descend into its
121    /// children (ignored for leaves).
122    ///
123    /// `key` is evaluated once per node, when its parent is expanded (the root
124    /// first). The traversal never allocates on the heap and never fails: it
125    /// keeps up to [`TRAVERSE_STACK`] pending nodes in a fixed array, and a
126    /// subtree that does not fit is finished at once by a parent-link walk, in
127    /// the same order, where `key` may be evaluated more than once per node. So
128    /// `key` should be pure; an impure one there can skip subtrees or loop
129    /// forever, though it stays memory-safe.
130    pub fn traverse_by_key<K: Copy + PartialOrd>(
131        &self,
132        key: impl FnMut(&Aabb) -> Option<K>,
133        visit: impl FnMut(NodeId, &NodeView, K) -> bool,
134    ) {
135        self.traverse_by_key_with::<TRAVERSE_STACK, K>(key, visit);
136    }
137
138    /// [`Bvh::traverse_by_key`] with an `N`-entry stack, so tests can force
139    /// the overflow fallback.
140    pub(crate) fn traverse_by_key_with<const N: usize, K: Copy + PartialOrd>(
141        &self,
142        key: impl FnMut(&Aabb) -> Option<K>,
143        mut visit: impl FnMut(NodeId, &NodeView, K) -> bool,
144    ) {
145        self.arena
146            .traverse_by_key::<N, K>(self.root, key, |i, n, k| visit(NodeId(i), &self.view(n), k));
147    }
148
149    /// Visits the subtree rooted at `root` in depth-first pre-order (left child
150    /// first), without a stack. `visit` returns whether to descend into the
151    /// node's children (ignored for leaves). `root` must come from this BVH and
152    /// not predate its last modification; otherwise this may panic or visit
153    /// unrelated nodes.
154    pub fn traverse_from(&self, root: NodeId, mut visit: impl FnMut(NodeId, &NodeView) -> bool) {
155        self.arena
156            .traverse(root.0, |i, n| visit(NodeId(i), &self.view(n)));
157    }
158
159    /// Number of node slots in storage, garbage included: every
160    /// [`NodeId::index`] is below it.
161    pub fn node_slots(&self) -> usize {
162        self.arena.nodes.len()
163    }
164
165    /// Counter that changes whenever nodes, boxes, refs or layout change
166    /// (build, update, refit, rebuild, compaction), so per-node data keyed by
167    /// [`NodeId::index`] can detect that it went stale. Values are unique
168    /// across all BVHs in this process, so a replacement tree never shares one
169    /// with an earlier tree.
170    pub fn generation(&self) -> u64 {
171        self.generation
172    }
173
174    /// The uid of the mesh this BVH was built for (see `Mesh::uid`).
175    pub fn mesh_uid(&self) -> u64 {
176        self.mesh_uid
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use crate::BuildOptions;
184    use fast_mesh_store::Mesh;
185
186    /// n×n grid of quads in the XY plane.
187    fn grid(n: u32) -> Mesh {
188        let mut positions = Vec::new();
189        for y in 0..=n {
190            for x in 0..=n {
191                positions.extend([x as f32, y as f32, 0.0]);
192            }
193        }
194        let mut indices = Vec::new();
195        for y in 0..n {
196            for x in 0..n {
197                let a = y * (n + 1) + x;
198                indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
199            }
200        }
201        Mesh::from_buffers(&positions, &indices).unwrap()
202    }
203
204    fn build(mesh: &Mesh) -> Bvh {
205        let opts = BuildOptions {
206            max_leaf_size: 2,
207            ..Default::default()
208        };
209        Bvh::build(mesh, &opts).unwrap()
210    }
211
212    #[test]
213    fn node_indices_are_dense_and_unique() {
214        let bvh = build(&grid(6));
215        let mut seen = vec![false; bvh.node_slots()];
216        let mut count = 0;
217        bvh.traverse(|id, _| {
218            assert!(id.index() < bvh.node_slots());
219            assert!(!seen[id.index()], "index {} repeated", id.index());
220            seen[id.index()] = true;
221            count += 1;
222            true
223        });
224        assert_eq!(count, bvh.node_count());
225        assert!(bvh.node_slots() >= bvh.node_count());
226    }
227
228    #[test]
229    fn traverse_visits_every_node_once() {
230        let bvh = build(&grid(6));
231        let mut ids = Vec::new();
232        bvh.traverse(|id, _| {
233            ids.push(id);
234            true
235        });
236        assert_eq!(ids.len(), bvh.node_count());
237        assert_eq!(ids.first().copied(), bvh.root());
238        let mut unique = ids.clone();
239        unique.sort_by_key(|id| id.0);
240        unique.dedup();
241        assert_eq!(unique.len(), ids.len());
242    }
243
244    #[test]
245    fn leaves_hold_every_alive_triangle_once() {
246        let mesh = grid(6);
247        let bvh = build(&mesh);
248        let mut all = Vec::new();
249        bvh.traverse(|_, view| {
250            if let NodeKind::Leaf { triangles } = view.kind {
251                all.extend_from_slice(triangles);
252            }
253            true
254        });
255        all.sort();
256        let expected: Vec<u32> = (0..mesh.triangle_count() as u32).collect();
257        assert_eq!(all, expected);
258    }
259
260    #[test]
261    fn parent_agrees_with_children() {
262        let bvh = build(&grid(6));
263        let root = bvh.root().unwrap();
264        assert_eq!(bvh.parent(root), None);
265        let mut inner = 0;
266        bvh.traverse(|id, view| {
267            if let NodeKind::Inner { left, right } = view.kind {
268                inner += 1;
269                assert_eq!(bvh.parent(left), Some(id));
270                assert_eq!(bvh.parent(right), Some(id));
271            }
272            true
273        });
274        assert!(inner > 0);
275    }
276
277    fn pre_order_swapped(bvh: &Bvh, id: NodeId, out: &mut Vec<NodeId>) {
278        out.push(id);
279        if let NodeKind::Inner { left, right } = bvh.node(id).kind {
280            pre_order_swapped(bvh, right, out);
281            pre_order_swapped(bvh, left, out);
282        }
283    }
284
285    #[test]
286    fn ordered_traversal_follows_first_is_left() {
287        let bvh = build(&grid(6));
288        let mut expected = Vec::new();
289        pre_order_swapped(&bvh, bvh.root().unwrap(), &mut expected);
290        let mut got = Vec::new();
291        bvh.traverse_ordered(
292            |_, _| false,
293            |id, _| {
294                got.push(id);
295                true
296            },
297        );
298        assert_eq!(got, expected);
299        assert_eq!(got.len(), bvh.node_count());
300    }
301
302    /// Pre-order following `first_is_left` on the children's bounds.
303    fn pre_order_by(
304        bvh: &Bvh,
305        id: NodeId,
306        first_is_left: &impl Fn(&Aabb, &Aabb) -> bool,
307        out: &mut Vec<NodeId>,
308    ) {
309        out.push(id);
310        if let NodeKind::Inner { left, right } = bvh.node(id).kind {
311            let (l, r) = (bvh.node(left).bounds, bvh.node(right).bounds);
312            let (first, second) = if first_is_left(&l, &r) {
313                (left, right)
314            } else {
315                (right, left)
316            };
317            pre_order_by(bvh, first, first_is_left, out);
318            pre_order_by(bvh, second, first_is_left, out);
319        }
320    }
321
322    #[test]
323    fn ordered_traversal_with_mixed_choices() {
324        let bvh = build(&grid(6));
325        let pred = |l: &Aabb, r: &Aabb| l.min[0] < r.min[0];
326        // The predicate must pick left at some nodes and right at others.
327        let (mut lefts, mut rights) = (0, 0);
328        bvh.traverse(|_, view| {
329            if let NodeKind::Inner { left, right } = view.kind {
330                if pred(&bvh.node(left).bounds, &bvh.node(right).bounds) {
331                    lefts += 1;
332                } else {
333                    rights += 1;
334                }
335            }
336            true
337        });
338        assert!(lefts > 0 && rights > 0, "lefts {lefts}, rights {rights}");
339
340        let mut expected = Vec::new();
341        pre_order_by(&bvh, bvh.root().unwrap(), &pred, &mut expected);
342        let mut got = Vec::new();
343        bvh.traverse_ordered(pred, |id, _| {
344            got.push(id);
345            true
346        });
347        assert_eq!(got, expected);
348        assert_eq!(got.len(), bvh.node_count());
349    }
350
351    #[test]
352    fn pruning_an_inner_node_skips_only_its_subtree() {
353        let bvh = build(&grid(6));
354        let root = bvh.root().unwrap();
355        let NodeKind::Inner { left, right } = bvh.node(root).kind else {
356            panic!("expected an inner root");
357        };
358        let NodeKind::Inner { .. } = bvh.node(left).kind else {
359            panic!("expected an inner left child");
360        };
361        let mut subtree = Vec::new();
362        pre_order_by(&bvh, left, &|_, _| true, &mut subtree);
363        assert!(subtree.len() > 1);
364
365        let run = |ordered: bool| {
366            let mut got = Vec::new();
367            let visit = |id: NodeId, _: &NodeView| {
368                got.push(id);
369                id != left
370            };
371            if ordered {
372                bvh.traverse_ordered(|_, _| true, visit);
373            } else {
374                bvh.traverse(visit);
375            }
376            got
377        };
378        for got in [run(false), run(true)] {
379            assert_eq!(got.len(), bvh.node_count() - (subtree.len() - 1));
380            assert!(got.contains(&left) && got.contains(&right));
381            for id in &subtree[1..] {
382                assert!(!got.contains(id));
383            }
384            let mut unique = got.clone();
385            unique.sort_by_key(|id| id.0);
386            unique.dedup();
387            assert_eq!(unique.len(), got.len());
388        }
389    }
390
391    #[test]
392    fn single_leaf_tree() {
393        let single = Mesh::from_buffers(&[0., 0., 0., 1., 0., 0., 0., 1., 0.], &[0, 1, 2]).unwrap();
394        let bvh = build(&single);
395        let root = bvh.root().unwrap();
396        assert!(matches!(
397            bvh.node(root).kind,
398            NodeKind::Leaf { triangles } if triangles == [0]
399        ));
400        assert_eq!(bvh.parent(root), None);
401        let mut calls = 0;
402        bvh.traverse(|_, _| {
403            calls += 1;
404            true
405        });
406        bvh.traverse_ordered(
407            |_, _| true,
408            |_, _| {
409                calls += 1;
410                true
411            },
412        );
413        assert_eq!(calls, 2);
414    }
415
416    #[test]
417    fn ordered_traversal_prunes() {
418        let bvh = build(&grid(6));
419        let mut visited = 0;
420        bvh.traverse_ordered(
421            |_, _| true,
422            |_, _| {
423                visited += 1;
424                false
425            },
426        );
427        assert_eq!(visited, 1);
428    }
429
430    /// Reference for [`Bvh::traverse_by_key`]: recursive pre-order that visits
431    /// the child with the smaller key first (left on ties), skips children
432    /// whose key is `None`, and descends below `id` only if `descend(id)`.
433    fn by_key_ref<K: Copy + PartialOrd>(
434        bvh: &Bvh,
435        id: NodeId,
436        k: K,
437        key: &impl Fn(&Aabb) -> Option<K>,
438        descend: &impl Fn(NodeId) -> bool,
439        out: &mut Vec<(NodeId, K)>,
440    ) {
441        out.push((id, k));
442        if !descend(id) {
443            return;
444        }
445        if let NodeKind::Inner { left, right } = bvh.node(id).kind {
446            let kl = key(&bvh.node(left).bounds);
447            let kr = key(&bvh.node(right).bounds);
448            let order = if matches!((kl, kr), (Some(l), Some(r)) if r < l) {
449                [(right, kr), (left, kl)]
450            } else {
451                [(left, kl), (right, kr)]
452            };
453            for (c, kc) in order {
454                if let Some(kc) = kc {
455                    by_key_ref(bvh, c, kc, key, descend, out);
456                }
457            }
458        }
459    }
460
461    fn reference<K: Copy + PartialOrd>(
462        bvh: &Bvh,
463        key: &impl Fn(&Aabb) -> Option<K>,
464        descend: &impl Fn(NodeId) -> bool,
465    ) -> Vec<(NodeId, K)> {
466        let mut out = Vec::new();
467        let root = bvh.root().unwrap();
468        if let Some(k) = key(&bvh.node(root).bounds) {
469            by_key_ref(bvh, root, k, key, descend, &mut out);
470        }
471        out
472    }
473
474    /// Runs the keyed traversal with an `N`-entry stack. Returns the visits and
475    /// how many times `key` was called.
476    fn run_by_key<const N: usize, K: Copy + PartialOrd + std::fmt::Debug>(
477        bvh: &Bvh,
478        key: &impl Fn(&Aabb) -> Option<K>,
479        descend: &impl Fn(NodeId) -> bool,
480    ) -> (Vec<(NodeId, K)>, usize) {
481        let mut got = Vec::new();
482        let mut calls = 0;
483        bvh.traverse_by_key_with::<N, K>(
484            |b| {
485                calls += 1;
486                key(b)
487            },
488            |id, view, k| {
489                assert_eq!(key(&view.bounds), Some(k));
490                got.push((id, k));
491                descend(id)
492            },
493        );
494        (got, calls)
495    }
496
497    /// A distance-like key from a point inside the grid, so that some nodes
498    /// visit their left child first and others their right one.
499    fn slanted(b: &Aabb) -> Option<f32> {
500        Some((b.min[0] - 3.1).abs() + 2.0 * (b.max[1] - 2.6).abs())
501    }
502
503    #[test]
504    fn by_key_matches_recursive_reference() {
505        let bvh = build(&grid(6));
506        let all = |_: NodeId| true;
507        let expected = reference(&bvh, &slanted, &all);
508        assert_eq!(expected.len(), bvh.node_count());
509        // The key must pick the left child at some nodes and the right one at
510        // others, or the test proves nothing.
511        let (mut lefts, mut rights) = (0, 0);
512        bvh.traverse(|_, view| {
513            if let NodeKind::Inner { left, right } = view.kind {
514                if slanted(&bvh.node(right).bounds) < slanted(&bvh.node(left).bounds) {
515                    rights += 1;
516                } else {
517                    lefts += 1;
518                }
519            }
520            true
521        });
522        assert!(lefts > 0 && rights > 0, "lefts {lefts}, rights {rights}");
523
524        let mut got = Vec::new();
525        bvh.traverse_by_key(slanted, |id, _, k| {
526            got.push((id, k));
527            true
528        });
529        assert_eq!(got, expected);
530        let mut unique: Vec<u32> = got.iter().map(|(id, _)| id.0).collect();
531        unique.sort();
532        unique.dedup();
533        assert_eq!(unique.len(), bvh.node_count());
534    }
535
536    /// An inner node other than the root whose bounds no other node shares.
537    fn unique_inner_non_root(bvh: &Bvh) -> NodeId {
538        let mut all = Vec::new();
539        bvh.traverse(|id, view| {
540            all.push((id, view.bounds, matches!(view.kind, NodeKind::Inner { .. })));
541            true
542        });
543        all.iter()
544            .find(|&&(id, b, inner)| {
545                inner
546                    && Some(id) != bvh.root()
547                    && all.iter().filter(|&&(_, o, _)| o == b).count() == 1
548            })
549            .expect("no inner node with unique bounds")
550            .0
551    }
552
553    fn subtree(bvh: &Bvh, id: NodeId) -> Vec<NodeId> {
554        let mut out = Vec::new();
555        pre_order_by(bvh, id, &|_, _| true, &mut out);
556        out
557    }
558
559    #[test]
560    fn by_key_none_culls_subtree() {
561        let bvh = build(&grid(6));
562        let culled = unique_inner_non_root(&bvh);
563        let hidden = subtree(&bvh, culled);
564        assert!(hidden.len() > 1);
565        let cull_bounds = bvh.node(culled).bounds;
566        let key = |b: &Aabb| if *b == cull_bounds { None } else { slanted(b) };
567        let all = |_: NodeId| true;
568        let expected = reference(&bvh, &key, &all);
569        assert_eq!(expected.len(), bvh.node_count() - hidden.len());
570
571        let mut got = Vec::new();
572        bvh.traverse_by_key(key, |id, _, k| {
573            got.push((id, k));
574            true
575        });
576        assert_eq!(got, expected);
577        for id in &hidden {
578            assert!(!got.iter().any(|(g, _)| g == id));
579        }
580    }
581
582    #[test]
583    fn key_evaluated_once_per_node() {
584        let bvh = build(&grid(6));
585        let mut calls = 0;
586        let mut visits = 0;
587        bvh.traverse_by_key(
588            |b| {
589                calls += 1;
590                slanted(b)
591            },
592            |_, _, _| {
593                visits += 1;
594                true
595            },
596        );
597        assert_eq!(visits, bvh.node_count());
598        assert_eq!(calls, bvh.node_count());
599    }
600
601    #[test]
602    fn small_stack_overflow_falls_back() {
603        // SAH and Morton trees stay far shallower than `TRAVERSE_STACK`, so the
604        // fallback is forced with a tiny stack instead.
605        let bvh = build(&grid(8));
606        assert!(bvh.depth() > 4);
607        let mut plain = Vec::new();
608        bvh.traverse(|id, _| {
609            plain.push(id.0);
610            true
611        });
612        plain.sort();
613
614        let culled = unique_inner_non_root(&bvh);
615        let cull_bounds = bvh.node(culled).bounds;
616        let culling = |b: &Aabb| if *b == cull_bounds { None } else { slanted(b) };
617
618        let all = |_: NodeId| true;
619        let prune_one = |id: NodeId| id != culled;
620        let prune_root = |_: NodeId| false;
621        type Key<'a> = &'a dyn Fn(&Aabb) -> Option<f32>;
622        type Descend<'a> = &'a dyn Fn(NodeId) -> bool;
623        let cases: [(Key, Descend); 4] = [
624            (&slanted, &all),
625            (&slanted, &prune_one),
626            (&culling, &all),
627            (&slanted, &prune_root),
628        ];
629        for (key, descend) in cases {
630            let expected = reference(&bvh, &key, &descend);
631            let runs = [
632                run_by_key::<0, f32>(&bvh, &key, &descend),
633                run_by_key::<1, f32>(&bvh, &key, &descend),
634                run_by_key::<2, f32>(&bvh, &key, &descend),
635                run_by_key::<3, f32>(&bvh, &key, &descend),
636                run_by_key::<4, f32>(&bvh, &key, &descend),
637                run_by_key::<TRAVERSE_STACK, f32>(&bvh, &key, &descend),
638            ];
639            for (got, _) in &runs {
640                // Nearest first everywhere, the fallback subtrees included.
641                assert_eq!(*got, expected);
642            }
643            // The full stack never overflows here, so no key is evaluated
644            // twice.
645            assert!(runs[5].1 <= bvh.node_count());
646        }
647
648        let (got, calls) = run_by_key::<2, f32>(&bvh, &slanted, &all);
649        assert!(calls > bvh.node_count(), "the fallback never ran");
650        let mut ids: Vec<u32> = got.iter().map(|(id, _)| id.0).collect();
651        ids.sort();
652        assert_eq!(ids, plain);
653
654        let (got, _) = run_by_key::<2, f32>(&bvh, &slanted, &prune_root);
655        assert_eq!(got.len(), 1);
656    }
657
658    #[test]
659    fn traverse_from_visits_subtree_only() {
660        let bvh = build(&grid(6));
661        let root = bvh.root().unwrap();
662        let NodeKind::Inner { left, .. } = bvh.node(root).kind else {
663            panic!("expected an inner root");
664        };
665        for id in [left, root] {
666            let expected = subtree(&bvh, id);
667            let mut got = Vec::new();
668            bvh.traverse_from(id, |i, _| {
669                got.push(i);
670                true
671            });
672            assert_eq!(got, expected);
673        }
674        let mut got = Vec::new();
675        bvh.traverse_from(left, |i, _| {
676            got.push(i);
677            false
678        });
679        assert_eq!(got, [left]);
680    }
681
682    #[test]
683    fn by_key_on_empty_and_single_leaf_trees() {
684        let empty = build(&Mesh::new());
685        let (mut keys, mut visits) = (0, 0);
686        empty.traverse_by_key(
687            |_| {
688                keys += 1;
689                Some(0.0)
690            },
691            |_, _, _| {
692                visits += 1;
693                true
694            },
695        );
696        assert_eq!((keys, visits), (0, 0));
697
698        let single = Mesh::from_buffers(&[0., 0., 0., 1., 0., 0., 0., 1., 0.], &[0, 1, 2]).unwrap();
699        let bvh = build(&single);
700        let mut visits = 0;
701        let root_key = slanted(&bvh.root_bounds().unwrap());
702        bvh.traverse_by_key(slanted, |_, _, k| {
703            assert_eq!(Some(k), root_key);
704            visits += 1;
705            true
706        });
707        assert_eq!(visits, 1);
708        bvh.traverse_by_key(|_| None::<f32>, |_, _, _| panic!("culled root visited"));
709    }
710
711    #[test]
712    fn empty_tree_has_no_root() {
713        let mesh = Mesh::new();
714        let bvh = build(&mesh);
715        assert_eq!(bvh.root(), None);
716        let mut calls = 0;
717        bvh.traverse(|_, _| {
718            calls += 1;
719            true
720        });
721        bvh.traverse_ordered(
722            |_, _| true,
723            |_, _| {
724                calls += 1;
725                true
726            },
727        );
728        assert_eq!(calls, 0);
729    }
730}