1use fast_mesh_store::{ChangeSet, Mesh, TriangleId};
9
10use crate::Aabb;
11use crate::arena::{NONE, Node};
12use crate::build::{Prims, build_subtree, max_nodes, reserve_headroom};
13use crate::error::{BvhError, Result, try_reserve, try_reserve_map, try_vec};
14use crate::tree::{Bvh, Treelet, next_generation};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Strategy {
19 Unchanged,
21 Refit,
23 Partial,
25 Full,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct UpdateReport {
32 pub strategy: Strategy,
33 pub refit_nodes: usize,
35 pub rebuilt_treelets: usize,
37 pub compacted: bool,
39}
40
41impl UpdateReport {
42 fn new(strategy: Strategy) -> Self {
43 Self {
44 strategy,
45 refit_nodes: 0,
46 rebuilt_treelets: 0,
47 compacted: false,
48 }
49 }
50}
51
52fn merge(a: &[u32], b: &[u32]) -> Result<Vec<u32>> {
54 let mut out = Vec::new();
55 try_reserve(&mut out, a.len() + b.len())?;
56 out.extend_from_slice(a);
57 out.extend_from_slice(b);
58 out.sort_unstable();
59 out.dedup();
60 Ok(out)
61}
62
63impl Bvh {
64 pub fn update(&mut self, mesh: &mut Mesh, changes: &ChangeSet) -> Result<UpdateReport> {
79 if mesh.uid() != self.mesh_uid {
80 return Err(BvhError::MeshMismatch);
81 }
82 if mesh.epoch() != self.epoch {
83 self.rebuild_from(mesh)?;
84 return Ok(UpdateReport::new(Strategy::Full));
85 }
86 if mesh.revision() == self.revision {
87 return Ok(UpdateReport::new(Strategy::Unchanged));
88 }
89 if !changes.vertices_moved.is_empty() {
90 mesh.ensure_incidence()?;
91 }
92 let mesh = &*mesh;
93 let removed = merge(&changes.triangles_removed, &changes.triangles_changed)?;
94 let added = merge(&changes.triangles_added, &changes.triangles_changed)?;
95 let mut moved = Vec::new();
96 if let Some(inc) = mesh
97 .incidence()
98 .filter(|_| !changes.vertices_moved.is_empty())
99 {
100 let total: usize = changes
101 .vertices_moved
102 .iter()
103 .map(|&v| inc.triangles(v).len())
104 .sum();
105 try_reserve(&mut moved, total)?;
106 for &v in &changes.vertices_moved {
107 moved.extend_from_slice(inc.triangles(v));
108 }
109 moved.sort_unstable();
110 moved.dedup();
111 moved.retain(|t| removed.binary_search(t).is_err() && added.binary_search(t).is_err());
113 }
114
115 let dirty = removed.len().max(added.len()) + moved.len();
116 if removed.is_empty() && added.is_empty() && moved.is_empty() {
117 self.revision = mesh.revision();
118 return Ok(UpdateReport::new(Strategy::Unchanged));
119 }
120 let alive = mesh.triangle_count().max(1);
121 if self.root == NONE || dirty as f32 > self.policy.full_rebuild_fraction * alive as f32 {
122 self.rebuild_from(mesh)?;
123 return Ok(UpdateReport::new(Strategy::Full));
124 }
125
126 let mut report = UpdateReport::new(if removed.is_empty() && added.is_empty() {
127 Strategy::Refit
128 } else {
129 Strategy::Partial
130 });
131 let mut leaves = Vec::new();
134 try_reserve(&mut leaves, moved.len())?;
135 let mut touched = self.rebuild_treelets(mesh, &removed, &added)?;
136 report.rebuilt_treelets = touched.rebuilt;
137 report.refit_nodes += touched.refit_nodes;
138
139 let (positions, triangles) = (mesh.positions(), mesh.triangles());
141 for &t in &moved {
142 match self.arena.tri_leaf.get(t as usize) {
143 Some(&leaf) if leaf != NONE => leaves.push(leaf),
144 _ => {}
145 }
146 }
147 leaves.sort_unstable();
148 leaves.dedup();
149 for &leaf in &leaves {
150 self.arena.refit_leaf(leaf, positions, triangles);
151 report.refit_nodes += self.arena.refit_ancestors(leaf);
152 if let Some(i) = self.treelet_of(leaf) {
153 touched.mark(i);
154 }
155 }
156
157 for i in 0..touched.refit.len() {
160 if !touched.refit[i] || touched.rebuilt_now[i] {
161 continue;
162 }
163 let t = self.treelets[i];
164 if self.arena.sah_cost(t.root) > self.policy.degradation_factor * t.build_cost
165 && self.rebuild_one(mesh, i).is_ok()
166 {
167 report.rebuilt_treelets += 1;
168 report.strategy = Strategy::Partial;
169 }
170 }
171
172 let live_refs = self.arena.refs.len() - self.garbage_refs;
174 if (2 * self.garbage_nodes > self.node_count() || 2 * self.garbage_refs > live_refs)
175 && self.compact_storage().is_ok()
176 {
177 report.compacted = true;
178 }
179 self.generation = next_generation();
183 self.revision = mesh.revision();
184 Ok(report)
185 }
186
187 pub fn refit(&mut self, mesh: &Mesh, triangles: &[TriangleId]) -> Result<()> {
194 if mesh.uid() != self.mesh_uid {
195 return Err(BvhError::MeshMismatch);
196 }
197 if mesh.epoch() != self.epoch {
198 return self.rebuild(mesh);
199 }
200 for &t in triangles {
201 let leaf = self.arena.tri_leaf.get(t as usize).copied().unwrap_or(NONE);
202 if leaf != NONE {
203 self.arena
204 .refit_leaf(leaf, mesh.positions(), mesh.triangles());
205 self.arena.refit_ancestors(leaf);
206 }
207 }
208 self.generation = next_generation();
209 Ok(())
210 }
211
212 pub fn rebuild_region(&mut self, mesh: &Mesh, triangles: &[TriangleId]) -> Result<()> {
221 if mesh.uid() != self.mesh_uid {
222 return Err(BvhError::MeshMismatch);
223 }
224 if mesh.epoch() != self.epoch {
225 return self.rebuild(mesh);
226 }
227 let mut which = Vec::new();
228 try_reserve(&mut which, triangles.len())?;
229 for &t in triangles {
230 if let Some(i) = self.treelet_of_triangle(t) {
231 which.push(i);
232 }
233 }
234 which.sort_unstable();
235 which.dedup();
236 let mut budget = Budget::default();
237 for &i in &which {
238 budget.add(
239 self.arena.subtree_size(self.treelets[i].root).1,
240 self.options.treelet_size,
241 );
242 }
243 let mut scratch = self.reserve_rebuilds(&budget)?;
244 for &i in &which {
246 self.replace_treelet(mesh, i, &[], &[], &[], &mut scratch);
247 }
248 self.generation = next_generation();
249 Ok(())
250 }
251
252 pub fn rebuild(&mut self, mesh: &Mesh) -> Result<()> {
254 if mesh.uid() != self.mesh_uid {
255 return Err(BvhError::MeshMismatch);
256 }
257 self.rebuild_from(mesh)
258 }
259
260 fn rebuild_treelets(&mut self, mesh: &Mesh, removed: &[u32], added: &[u32]) -> Result<Touched> {
263 let mut touched = Touched::new(self.treelets.len())?;
264 if removed.is_empty() && added.is_empty() {
265 return Ok(touched);
266 }
267 if self.arena.tri_leaf.len() < mesh.triangle_slots() {
269 let extra = mesh.triangle_slots() - self.arena.tri_leaf.len();
270 reserve_headroom(&mut self.arena.tri_leaf, extra)?;
271 self.arena.tri_leaf.resize(mesh.triangle_slots(), NONE);
272 }
273 let positions = mesh.positions();
274 let triangles = mesh.triangles();
275 let mut queued: Vec<Vec<u32>> = Vec::new();
280 try_reserve(&mut queued, self.treelets.len())?;
281 queued.resize_with(self.treelets.len(), Vec::new);
282 for &t in removed.iter().chain(added) {
283 if let Some(i) = self.treelet_of_triangle(t) {
284 touched.rebuild[i] = true;
285 }
286 }
287 for &t in added {
288 if !mesh.is_triangle_alive(t) {
289 continue;
290 }
291 let i = self.choose_treelet(&Aabb::of_triangle(positions, triangles[t as usize]));
292 touched.rebuild[i] = true;
293 try_reserve(&mut queued[i], 1)?;
294 queued[i].push(t);
295 }
296 let mut budget = Budget::default();
298 for (i, q) in queued.iter().enumerate() {
299 if touched.rebuild[i] {
300 let (_, refs) = self.arena.subtree_size(self.treelets[i].root);
301 budget.add(refs + q.len(), self.options.treelet_size);
302 }
303 }
304 let mut scratch = self.reserve_rebuilds(&budget)?;
305
306 for &t in removed.iter().chain(added) {
310 if let Some(leaf) = self.arena.tri_leaf.get_mut(t as usize) {
311 *leaf = NONE;
312 }
313 }
314 for (i, q) in queued.iter().enumerate() {
315 if touched.rebuild[i] {
316 touched.refit_nodes +=
317 self.replace_treelet(mesh, i, removed, added, q, &mut scratch);
318 touched.rebuilt += 1;
319 touched.rebuilt_now[i] = true;
320 }
321 }
322 Ok(touched)
323 }
324
325 fn rebuild_one(&mut self, mesh: &Mesh, i: usize) -> Result<()> {
328 let mut budget = Budget::default();
329 budget.add(
330 self.arena.subtree_size(self.treelets[i].root).1,
331 self.options.treelet_size,
332 );
333 let mut scratch = self.reserve_rebuilds(&budget)?;
334 self.replace_treelet(mesh, i, &[], &[], &[], &mut scratch);
335 Ok(())
336 }
337
338 fn treelet_of_triangle(&self, t: TriangleId) -> Option<usize> {
340 match self.arena.tri_leaf.get(t as usize) {
341 Some(&leaf) if leaf != NONE => self.treelet_of(leaf),
342 _ => None,
343 }
344 }
345
346 fn reserve_rebuilds(&mut self, budget: &Budget) -> Result<Scratch> {
350 let prims = Prims::with_capacity(budget.max_n)?;
351 reserve_headroom(&mut self.arena.nodes, budget.nodes)?;
352 reserve_headroom(&mut self.arena.parents, budget.nodes)?;
353 reserve_headroom(&mut self.counts, budget.nodes)?;
354 reserve_headroom(&mut self.arena.refs, budget.refs)?;
355 if budget.extra_treelets > 0 {
356 try_reserve(&mut self.treelets, budget.extra_treelets)?;
357 }
358 try_reserve_map(
360 &mut self.treelet_of_root,
361 budget.rebuilds + budget.extra_treelets,
362 )?;
363 Ok(Scratch { prims })
364 }
365
366 fn replace_treelet(
375 &mut self,
376 mesh: &Mesh,
377 i: usize,
378 removed: &[u32],
379 added: &[u32],
380 insert: &[u32],
381 scratch: &mut Scratch,
382 ) -> usize {
383 let old_root = self.treelets[i].root;
384 let (positions, triangles) = (mesh.positions(), mesh.triangles());
385 let prims = &mut scratch.prims;
386 prims.clear();
387 let (mut old_nodes, mut old_refs) = (0usize, 0usize);
388 let mut tri_leaf = std::mem::take(&mut self.arena.tri_leaf);
390 let arena = &self.arena;
391 arena.traverse(old_root, |node, n| {
392 old_nodes += 1;
393 if n.is_leaf() {
394 old_refs += n.leaf_range().len();
395 for &t in &arena.refs[n.leaf_range()] {
396 if mesh.is_triangle_alive(t)
397 && removed.binary_search(&t).is_err()
398 && added.binary_search(&t).is_err()
399 {
400 prims.push(t, Aabb::of_triangle(positions, triangles[t as usize]));
401 } else if tri_leaf[t as usize] == node {
402 tri_leaf[t as usize] = NONE;
403 }
404 }
405 }
406 true
407 });
408 self.arena.tri_leaf = tri_leaf;
409 for &t in insert {
410 prims.push(t, Aabb::of_triangle(positions, triangles[t as usize]));
411 }
412 let parent = self.arena.parents[old_root as usize];
413 let first = self.arena.nodes.len();
414 let new_root = build_subtree(&mut self.arena, prims, &self.options, parent)
415 .expect("capacity for the rebuild was reserved before mutating");
416 let nodes = &self.arena.nodes;
418 self.counts.resize(nodes.len(), 0);
419 for k in (first..nodes.len()).rev() {
420 let n = &nodes[k];
421 self.counts[k] = if n.is_leaf() {
422 n.leaf_range().len() as u32
423 } else {
424 self.counts[n.a as usize] + self.counts[n.b as usize]
425 };
426 }
427 if parent == NONE {
428 self.root = new_root;
429 } else {
430 let p = &mut self.arena.nodes[parent as usize];
431 if p.a == old_root {
432 p.a = new_root;
433 } else {
434 p.b = new_root;
435 }
436 }
437 self.garbage_nodes += old_nodes;
438 self.garbage_refs += old_refs;
439 self.treelet_of_root.remove(&old_root);
440 self.treelet_of_root.insert(new_root, i);
441 self.treelets[i] = Treelet {
442 root: new_root,
443 build_cost: self.arena.sah_cost(new_root),
444 };
445 if prims.len() > 2 * self.options.treelet_size as usize {
446 self.split_treelet(i);
447 }
448 let mut up = parent;
450 while up != NONE {
451 let n = &self.arena.nodes[up as usize];
452 self.counts[up as usize] = self.counts[n.a as usize] + self.counts[n.b as usize];
453 up = self.arena.parents[up as usize];
454 }
455 self.arena.refit_ancestors(new_root)
456 }
457
458 fn split_treelet(&mut self, i: usize) {
463 let root = self.treelets[i].root;
464 let limit = self.options.treelet_size;
465 let counts = &self.counts;
466 let is_root = |k: u32, n: &Node| n.is_leaf() || counts[k as usize] <= limit;
467 let mut found = 0usize;
468 self.arena.traverse(root, |k, n| {
469 let r = is_root(k, n);
470 found += usize::from(r);
471 !r
472 });
473 let extra = found.saturating_sub(1);
474 if extra == 0
477 || self.treelets.capacity() - self.treelets.len() < extra
478 || self.treelet_of_root.capacity() - self.treelet_of_root.len() < found
479 {
480 return;
481 }
482 let arena = &self.arena;
483 let treelets = &mut self.treelets;
484 let map = &mut self.treelet_of_root;
485 map.remove(&root);
486 let mut slot = Some(i);
487 arena.traverse(root, |k, n| {
488 let r = is_root(k, n);
489 if r {
490 let t = Treelet {
491 root: k,
492 build_cost: arena.sah_cost(k),
493 };
494 if let Some(i) = slot.take() {
495 treelets[i] = t;
496 map.insert(k, i);
497 } else {
498 map.insert(k, treelets.len());
499 treelets.push(t);
500 }
501 }
502 !r
503 });
504 }
505
506 fn choose_treelet(&self, b: &Aabb) -> usize {
509 let mut node = self.root;
510 loop {
511 if let Some(&i) = self.treelet_of_root.get(&node) {
512 return i;
513 }
514 let n = &self.arena.nodes[node as usize];
515 let growth = |c: u32| {
516 let cb = &self.arena.nodes[c as usize].bounds;
517 cb.union(b).surface_area() - cb.surface_area()
518 };
519 node = if growth(n.b) < growth(n.a) { n.b } else { n.a };
520 }
521 }
522}
523
524struct Touched {
526 rebuild: Vec<bool>,
527 rebuilt_now: Vec<bool>,
528 refit: Vec<bool>,
529 rebuilt: usize,
530 refit_nodes: usize,
531}
532
533impl Touched {
534 fn new(n: usize) -> Result<Self> {
535 Ok(Self {
536 rebuild: try_vec(n, false)?,
537 rebuilt_now: try_vec(n, false)?,
538 refit: try_vec(n, false)?,
539 rebuilt: 0,
540 refit_nodes: 0,
541 })
542 }
543
544 fn mark(&mut self, treelet: usize) {
547 if let Some(r) = self.refit.get_mut(treelet) {
548 *r = true;
549 }
550 }
551}
552
553#[derive(Default)]
555struct Budget {
556 max_n: usize,
558 nodes: usize,
559 refs: usize,
560 rebuilds: usize,
561 extra_treelets: usize,
563}
564
565impl Budget {
566 fn add(&mut self, n: usize, treelet_size: u32) {
568 self.max_n = self.max_n.max(n);
569 self.nodes += max_nodes(n);
570 self.refs += n;
571 self.rebuilds += 1;
572 let size = treelet_size as usize;
573 if n > 2 * size {
574 self.extra_treelets += n.div_ceil(size) * 2 + 1;
575 }
576 }
577}
578
579struct Scratch {
581 prims: Prims,
582}
583
584#[cfg(test)]
585mod tests {
586 use fast_mesh_store::{History, Mesh};
587
588 use crate::{Aabb, BuildOptions, Bvh, BvhError, Strategy, UpdatePolicy};
589
590 fn grid(n: u32) -> Mesh {
592 let mut positions = Vec::new();
593 for y in 0..=n {
594 for x in 0..=n {
595 positions.extend([x as f32, y as f32, 0.0]);
596 }
597 }
598 let mut indices = Vec::new();
599 for y in 0..n {
600 for x in 0..n {
601 let a = y * (n + 1) + x;
602 indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
603 }
604 }
605 Mesh::from_buffers(&positions, &indices).unwrap()
606 }
607
608 fn opts() -> BuildOptions {
609 BuildOptions {
610 max_leaf_size: 4,
611 treelet_size: 32,
612 ..Default::default()
613 }
614 }
615
616 fn hits(bvh: &Bvh, mesh: &Mesh, min: [f32; 3], max: [f32; 3]) -> Vec<u32> {
617 let mut out = Vec::new();
618 bvh.query_aabb(mesh, min, max, &mut out);
619 out.sort();
620 out
621 }
622
623 #[test]
624 fn moving_a_vertex_refits() {
625 let mut m = grid(16);
626 let mut bvh = Bvh::build(&m, &opts()).unwrap();
627 let mut tx = m.begin_edit();
628 tx.set_positions(&[0], &[[0.0, 0.0, 5.0]]).unwrap();
629 let p = tx.commit().unwrap();
630 let report = bvh.update(&mut m, p.changes()).unwrap();
631 assert_eq!(report.strategy, Strategy::Refit);
632 assert!(report.refit_nodes > 0);
633 bvh.validate(&m).unwrap();
634 assert!(bvh.is_current(&m));
635 assert_eq!(
636 hits(&bvh, &m, [-1.0, -1.0, 4.0], [0.5, 0.5, 6.0]),
637 vec![0, 1]
638 );
639 }
640
641 #[test]
642 fn local_topology_edits_rebuild_only_their_treelets() {
643 let mut m = grid(16);
644 let mut bvh = Bvh::build(&m, &opts()).unwrap();
645 let treelets = bvh.treelet_count();
646 assert!(treelets > 4);
647 let mut tx = m.begin_edit();
648 tx.remove_triangles(&[0, 1]).unwrap();
649 let v = tx.add_vertices(&[[0.5, 0.5, 2.0]]).unwrap()[0];
650 tx.add_triangles(&[[0, 1, v]]).unwrap();
651 let p = tx.commit().unwrap();
652 let report = bvh.update(&mut m, p.changes()).unwrap();
653 assert_eq!(report.strategy, Strategy::Partial);
654 assert!(report.rebuilt_treelets >= 1 && report.rebuilt_treelets < treelets);
655 bvh.validate(&m).unwrap();
656 assert_eq!(
657 hits(&bvh, &m, [0.0, 0.0, 1.5], [1.0, 1.0, 2.5]),
658 p.changes().triangles_added
659 );
660 }
661
662 #[test]
663 fn large_edits_rebuild_everything() {
664 let mut m = grid(8);
665 let mut bvh = Bvh::build(&m, &opts()).unwrap();
666 let all: Vec<u32> = (0..m.vertex_slots() as u32).collect();
667 let lifted: Vec<[f32; 3]> = all
668 .iter()
669 .map(|&v| {
670 let p = m.position(v).unwrap();
671 [p[0], p[1], 1.0]
672 })
673 .collect();
674 let mut tx = m.begin_edit();
675 tx.set_positions(&all, &lifted).unwrap();
676 let p = tx.commit().unwrap();
677 let report = bvh.update(&mut m, p.changes()).unwrap();
678 assert_eq!(report.strategy, Strategy::Full);
679 bvh.validate(&m).unwrap();
680 }
681
682 #[test]
683 fn empty_changes_only_record_the_revision() {
684 let mut m = grid(2);
685 let mut bvh = Bvh::build(&m, &opts()).unwrap();
686 let mut tx = m.begin_edit();
687 tx.add_vertices(&[[9.0; 3]]).unwrap();
688 let p = tx.commit().unwrap();
689 assert!(!bvh.is_current(&m));
690 let report = bvh.update(&mut m, p.changes()).unwrap();
691 assert_eq!(report.strategy, Strategy::Unchanged);
692 assert!(bvh.is_current(&m));
693 }
694
695 #[test]
696 fn undo_and_redo_change_sets_keep_the_tree_correct() {
697 let mut m = grid(12);
698 let mut bvh = Bvh::build(&m, &opts()).unwrap();
699 let mut h = History::new(1 << 20);
700 let mut tx = m.begin_edit();
701 tx.remove_triangles(&[10, 11, 12]).unwrap();
702 tx.set_positions(&[40], &[[3.0, 3.0, 3.0]]).unwrap();
703 let p = tx.commit().unwrap();
704 bvh.update(&mut m, p.changes()).unwrap();
705 h.push(&mut m, p).unwrap();
706 bvh.validate(&m).unwrap();
707
708 let undo = h.undo(&mut m).unwrap().unwrap();
709 bvh.update(&mut m, &undo).unwrap();
710 bvh.validate(&m).unwrap();
711 let redo = h.redo(&mut m).unwrap().unwrap();
712 bvh.update(&mut m, &redo).unwrap();
713 bvh.validate(&m).unwrap();
714 }
715
716 #[test]
717 fn emptying_and_refilling_the_mesh() {
718 let mut m = grid(2);
719 let mut bvh = Bvh::build(&m, &opts()).unwrap();
720 let all: Vec<u32> = (0..8).collect();
721 let mut tx = m.begin_edit();
722 tx.remove_triangles(&all).unwrap();
723 let p = tx.commit().unwrap();
724 bvh.update(&mut m, p.changes()).unwrap();
725 bvh.validate(&m).unwrap();
726 assert_eq!(bvh.root_bounds(), None);
727 let undo = p.revert(&mut m).unwrap();
728 bvh.update(&mut m, &undo).unwrap();
729 bvh.validate(&m).unwrap();
730 assert_eq!(
731 bvh.root_bounds(),
732 Some(Aabb::new([0.0; 3], [2.0, 2.0, 0.0]))
733 );
734 }
735
736 #[test]
737 fn another_mesh_is_rejected() {
738 let m = grid(2);
739 let mut other = grid(2);
740 let mut bvh = Bvh::build(&m, &opts()).unwrap();
741 let mut tx = other.begin_edit();
742 tx.set_positions(&[0], &[[1.0; 3]]).unwrap();
743 let p = tx.commit().unwrap();
744 assert_eq!(
745 bvh.update(&mut other, p.changes()),
746 Err(BvhError::MeshMismatch)
747 );
748 assert_eq!(bvh.refit(&other, &[0]), Err(BvhError::MeshMismatch));
749 assert_eq!(bvh.rebuild(&other), Err(BvhError::MeshMismatch));
750 bvh.validate(&m).unwrap();
751 }
752
753 #[test]
754 fn degradation_triggers_a_treelet_rebuild() {
755 let mut m = grid(16);
756 let mut bvh = Bvh::build(&m, &opts()).unwrap();
757 bvh.set_policy(UpdatePolicy {
758 degradation_factor: 1.0,
759 full_rebuild_fraction: 1.0,
760 })
761 .unwrap();
762 let ids = [0u32, 50, 100, 150];
764 let far: Vec<[f32; 3]> = ids
765 .iter()
766 .map(|&v| [v as f32, 0.0, 100.0 + v as f32])
767 .collect();
768 let mut tx = m.begin_edit();
769 tx.set_positions(&ids, &far).unwrap();
770 let p = tx.commit().unwrap();
771 let report = bvh.update(&mut m, p.changes()).unwrap();
772 assert!(report.rebuilt_treelets > 0, "{report:?}");
773 bvh.validate(&m).unwrap();
774 }
775
776 #[test]
777 fn generation_changes_on_every_structural_change() {
778 let mut m = grid(16);
779 let mut bvh = Bvh::build(&m, &opts()).unwrap();
780 let mut seen = vec![bvh.generation()];
781 let mut check = |bvh: &Bvh, what: &str| {
782 let g = bvh.generation();
783 assert!(!seen.contains(&g), "{what}: generation {g} repeated");
784 seen.push(g);
785 };
786
787 let mut tx = m.begin_edit();
788 tx.set_positions(&[0], &[[0.0, 0.0, 5.0]]).unwrap();
789 let p = tx.commit().unwrap();
790 assert_eq!(
791 bvh.update(&mut m, p.changes()).unwrap().strategy,
792 Strategy::Refit
793 );
794 check(&bvh, "update refit");
795
796 let mut tx = m.begin_edit();
797 tx.remove_triangles(&[0, 1]).unwrap();
798 let p = tx.commit().unwrap();
799 assert_eq!(
800 bvh.update(&mut m, p.changes()).unwrap().strategy,
801 Strategy::Partial
802 );
803 check(&bvh, "update partial");
804
805 bvh.refit(&m, &[2]).unwrap();
806 check(&bvh, "refit");
807 bvh.rebuild_region(&m, &[2]).unwrap();
808 check(&bvh, "rebuild_region");
809 bvh.compact_storage().unwrap();
810 check(&bvh, "compact_storage");
811 bvh.rebuild(&m).unwrap();
812 check(&bvh, "rebuild");
813
814 let all: Vec<u32> = (0..m.vertex_slots() as u32).collect();
817 let lifted: Vec<[f32; 3]> = all
818 .iter()
819 .map(|&v| {
820 let p = m.position(v).unwrap();
821 [p[0], p[1], 1.0]
822 })
823 .collect();
824 let mut tx = m.begin_edit();
825 tx.set_positions(&all, &lifted).unwrap();
826 let p = tx.commit().unwrap();
827 assert_eq!(
828 bvh.update(&mut m, p.changes()).unwrap().strategy,
829 Strategy::Full
830 );
831 check(&bvh, "update full");
832
833 m.compact().unwrap();
835 bvh.update(&mut m, &fast_mesh_store::ChangeSet::default())
836 .unwrap();
837 check(&bvh, "update after mesh compaction");
838 }
839
840 #[test]
841 fn generation_unchanged_when_update_is_unchanged() {
842 let mut m = grid(2);
843 let mut bvh = Bvh::build(&m, &opts()).unwrap();
844 let g = bvh.generation();
845 let none = fast_mesh_store::ChangeSet::default();
847 assert_eq!(
848 bvh.update(&mut m, &none).unwrap().strategy,
849 Strategy::Unchanged
850 );
851 assert_eq!(bvh.generation(), g);
852 let mut tx = m.begin_edit();
854 tx.add_vertices(&[[9.0; 3]]).unwrap();
855 let p = tx.commit().unwrap();
856 assert_eq!(
857 bvh.update(&mut m, p.changes()).unwrap().strategy,
858 Strategy::Unchanged
859 );
860 assert_eq!(bvh.generation(), g);
861 }
862
863 #[test]
864 fn manual_refit_and_region_rebuild() {
865 let mut m = grid(8);
866 let mut bvh = Bvh::build(&m, &opts()).unwrap();
867 let mut tx = m.begin_edit();
868 tx.set_positions(&[0], &[[0.0, 0.0, 2.0]]).unwrap();
869 tx.commit().unwrap();
870 bvh.refit(&m, &[0, 1]).unwrap();
871 bvh.validate(&m).unwrap();
872 bvh.rebuild_region(&m, &[0]).unwrap();
873 bvh.validate(&m).unwrap();
874 bvh.rebuild(&m).unwrap();
875 bvh.validate(&m).unwrap();
876 assert!(bvh.is_current(&m));
877 }
878
879 fn assert_counts(bvh: &Bvh, tag: &str) {
882 fn count(bvh: &Bvh, id: crate::NodeId) -> usize {
883 match bvh.node(id).kind {
884 crate::NodeKind::Leaf { triangles } => triangles.len(),
885 crate::NodeKind::Inner { left, right } => count(bvh, left) + count(bvh, right),
886 }
887 }
888 let mut nodes = 0;
889 bvh.traverse(|id, _| {
890 nodes += 1;
891 assert_eq!(
892 bvh.subtree_triangles(id),
893 count(bvh, id),
894 "{tag}: node {id:?}"
895 );
896 true
897 });
898 assert!(nodes > 0 || bvh.root_bounds().is_none(), "{tag}");
899 }
900
901 #[test]
902 fn subtree_triangle_counts_follow_updates() {
903 let mut m = grid(16);
904 let mut bvh = Bvh::build(&m, &opts()).unwrap();
905 assert_counts(&bvh, "build");
906 let mut compacted = false;
907 for round in 0..40u32 {
908 let t = (round * 37) % m.triangle_slots() as u32;
911 let old = m.triangle(t);
912 let mut tx = m.begin_edit();
913 if let Some(tri) = old {
914 tx.remove_triangles(&[t]).unwrap();
915 if round % 3 != 0 {
916 tx.add_triangles(&[tri, tri]).unwrap();
917 }
918 }
919 tx.set_positions(&[round], &[[round as f32, 0.5, 0.3]])
920 .unwrap();
921 let p = tx.commit().unwrap();
922 compacted |= bvh.update(&mut m, p.changes()).unwrap().compacted;
923 assert_counts(&bvh, &format!("round {round}"));
924 }
925 assert!(compacted);
926 let treelets = bvh.treelet_count();
928 let tri = m.triangle(m.triangle_slots() as u32 - 1).unwrap();
929 let mut tx = m.begin_edit();
930 tx.add_triangles(&[tri; 100]).unwrap();
931 let p = tx.commit().unwrap();
932 bvh.update(&mut m, p.changes()).unwrap();
933 assert!(bvh.treelet_count() > treelets + 1);
934 assert_counts(&bvh, "split");
935 bvh.rebuild_region(&m, &[5, 9, 200]).unwrap();
936 assert_counts(&bvh, "region");
937 bvh.refit(&m, &[1, 2]).unwrap();
938 assert_counts(&bvh, "refit");
939 bvh.rebuild(&m).unwrap();
940 assert_counts(&bvh, "rebuild");
941 }
942
943 #[test]
944 fn garbage_is_compacted_away() {
945 let mut m = grid(16);
946 let mut bvh = Bvh::build(&m, &opts()).unwrap();
947 let mut compacted = false;
948 for round in 0..40u32 {
949 let t = (round * 37) % m.triangle_slots() as u32;
950 let Some(tri) = m.triangle(t) else { continue };
951 let mut tx = m.begin_edit();
952 tx.remove_triangles(&[t]).unwrap();
953 tx.add_triangles(&[tri]).unwrap();
954 let p = tx.commit().unwrap();
955 compacted |= bvh.update(&mut m, p.changes()).unwrap().compacted;
956 bvh.validate(&m).unwrap();
957 }
958 assert!(compacted);
959 }
960
961 #[test]
962 fn replaying_a_change_set_does_not_duplicate_triangles() {
963 let mut m = grid(16);
964 let mut bvh = Bvh::build(&m, &opts()).unwrap();
965 let mut tx = m.begin_edit();
966 let v = tx.add_vertices(&[[0.5, 0.5, 2.0]]).unwrap()[0];
967 tx.add_triangles(&[[0, 1, v]]).unwrap();
968 let p = tx.commit().unwrap();
969 assert_eq!(
970 bvh.update(&mut m, p.changes()).unwrap().strategy,
971 Strategy::Partial
972 );
973 assert_eq!(
974 bvh.update(&mut m, p.changes()).unwrap().strategy,
975 Strategy::Unchanged
976 );
977 bvh.validate(&m).unwrap();
978 assert_eq!(bvh.revision(), m.revision());
979 }
980
981 #[test]
982 fn re_listed_additions_are_inserted_once() {
983 let mut m = grid(16);
984 let mut bvh = Bvh::build(&m, &opts()).unwrap();
985 let mut tx = m.begin_edit();
986 let v = tx.add_vertices(&[[0.5, 0.5, 2.0]]).unwrap()[0];
987 tx.add_triangles(&[[0, 1, v]]).unwrap();
988 let first = tx.commit().unwrap().changes().clone();
989 bvh.update(&mut m, &first).unwrap();
990 let mut tx = m.begin_edit();
991 tx.add_triangles(&[[20, 21, v]]).unwrap();
992 let second = tx.commit().unwrap().changes().clone();
993 let mut merged = second.clone();
995 merged
996 .triangles_added
997 .extend_from_slice(&first.triangles_added);
998 merged.triangles_added.sort_unstable();
999 merged.triangles_added.dedup();
1000 let report = bvh.update(&mut m, &merged).unwrap();
1001 assert_eq!(report.strategy, Strategy::Partial);
1002 bvh.validate(&m).unwrap();
1003 let all = hits(&bvh, &m, [-1.0; 3], [20.0, 20.0, 5.0]);
1004 assert_eq!(all.len(), m.triangle_count());
1005 }
1006
1007 #[test]
1008 fn compaction_of_the_mesh_forces_a_full_rebuild() {
1009 let mut m = grid(8);
1010 let mut bvh = Bvh::build(&m, &opts()).unwrap();
1011 let mut tx = m.begin_edit();
1012 tx.remove_triangles(&[0, 1, 2, 30]).unwrap();
1013 let p = tx.commit().unwrap();
1014 bvh.update(&mut m, p.changes()).unwrap();
1015 m.compact().unwrap();
1016 assert!(!bvh.is_current(&m));
1017 let report = bvh
1018 .update(&mut m, &fast_mesh_store::ChangeSet::default())
1019 .unwrap();
1020 assert_eq!(report.strategy, Strategy::Full);
1021 assert!(bvh.is_current(&m));
1022 bvh.validate(&m).unwrap();
1023 assert_eq!(
1024 hits(&bvh, &m, [-1.0; 3], [9.0, 9.0, 1.0]).len(),
1025 m.triangle_count()
1026 );
1027
1028 let mut tx = m.begin_edit();
1030 tx.remove_triangles(&[5, 6]).unwrap();
1031 tx.commit().unwrap();
1032 let mut a = Bvh::build(&m, &opts()).unwrap();
1033 let mut b = Bvh::build(&m, &opts()).unwrap();
1034 m.compact().unwrap();
1035 a.refit(&m, &[0]).unwrap();
1036 a.validate(&m).unwrap();
1037 assert!(a.is_current(&m));
1038 b.rebuild_region(&m, &[0]).unwrap();
1039 b.validate(&m).unwrap();
1040 assert!(b.is_current(&m));
1041 }
1042
1043 #[test]
1044 fn region_rebuild_on_a_stale_tree_then_update() {
1045 let mut m = grid(8);
1046 let mut bvh = Bvh::build(&m, &opts()).unwrap();
1047 let mut tx = m.begin_edit();
1048 tx.remove_triangles(&[5]).unwrap();
1049 let p = tx.commit().unwrap();
1050 bvh.rebuild_region(&m, &[4, 5]).unwrap();
1051 bvh.validate(&m).unwrap();
1052 bvh.update(&mut m, p.changes()).unwrap();
1053 bvh.validate(&m).unwrap();
1054 assert!(bvh.is_current(&m));
1055 }
1056
1057 #[test]
1058 fn treelets_stay_bounded_as_the_mesh_grows() {
1059 let mut m = grid(8);
1060 let options = BuildOptions {
1061 max_leaf_size: 4,
1062 treelet_size: 64,
1063 ..Default::default()
1064 };
1065 let mut bvh = Bvh::build(&m, &options).unwrap();
1066 for round in 0..14u32 {
1067 let alive: Vec<u32> = (0..m.triangle_slots() as u32)
1069 .filter(|&t| m.is_triangle_alive(t))
1070 .collect();
1071 let extra: Vec<[u32; 3]> = alive
1072 .iter()
1073 .step_by(5)
1074 .map(|&t| m.triangle(t).unwrap())
1075 .collect();
1076 let mut tx = m.begin_edit();
1077 tx.add_triangles(&extra).unwrap();
1078 let p = tx.commit().unwrap();
1079 let report = bvh.update(&mut m, p.changes()).unwrap();
1080 assert_eq!(report.strategy, Strategy::Partial, "round {round}");
1081 bvh.validate(&m).unwrap();
1082 let largest = bvh.treelet_triangle_counts().into_iter().max().unwrap();
1083 assert!(largest <= 128, "round {round}: treelet of {largest}");
1084 }
1085 let mut tx = m.begin_edit();
1087 tx.remove_triangles(&[7]).unwrap();
1088 let p = tx.commit().unwrap();
1089 assert_eq!(bvh.update(&mut m, p.changes()).unwrap().rebuilt_treelets, 1);
1090 bvh.validate(&m).unwrap();
1091 }
1092
1093 #[test]
1097 fn treelet_split_needs_room_for_every_new_root() {
1098 let m = grid(8);
1099 let options = BuildOptions {
1101 max_leaf_size: 4,
1102 treelet_size: 1024,
1103 ..Default::default()
1104 };
1105 let base = Bvh::build(&m, &options).unwrap();
1106 assert_eq!(base.treelet_count(), 1);
1107 let found = {
1109 let mut probe = Bvh::build(&m, &options).unwrap();
1110 probe.options.treelet_size = 32;
1111 probe.treelets.reserve(64);
1112 probe.treelet_of_root.reserve(64);
1113 probe.split_treelet(0);
1114 probe.validate(&m).unwrap();
1115 probe.treelet_count()
1116 };
1117 assert!(found > 1);
1118 for spare in [found - 1, found] {
1119 let mut bvh = Bvh::build(&m, &options).unwrap();
1120 bvh.options.treelet_size = 32;
1121 bvh.treelets.reserve(64);
1122 let mut map = std::collections::HashMap::with_capacity(found + 16);
1125 map.insert(bvh.root, 0usize);
1126 let mut filler = u32::MAX;
1127 while map.capacity() - map.len() > spare {
1128 map.insert(filler, usize::MAX);
1129 filler -= 1;
1130 }
1131 assert_eq!(map.capacity() - map.len(), spare);
1132 let capacity = map.capacity();
1133 bvh.treelet_of_root = map;
1134 bvh.split_treelet(0);
1135 assert!(
1138 bvh.treelet_of_root.capacity() <= capacity + 1,
1139 "spare {spare}: the map was reallocated"
1140 );
1141 let expected = if spare == found { found } else { 1 };
1142 assert_eq!(bvh.treelet_count(), expected, "spare {spare}");
1143 bvh.treelet_of_root.retain(|_, &mut i| i != usize::MAX);
1144 bvh.validate(&m).unwrap();
1145 }
1146 }
1147}
1148
1149#[cfg(test)]
1150mod oom_tests {
1151 use fast_mesh_store::Mesh;
1152
1153 use crate::error::fail_allocations_after;
1154 use crate::{BuildOptions, Bvh, BvhError};
1155
1156 fn wavy(n: u32) -> Mesh {
1157 let mut positions = Vec::new();
1158 for y in 0..=n {
1159 for x in 0..=n {
1160 positions.extend([x as f32, y as f32, ((x + y) % 3) as f32]);
1161 }
1162 }
1163 let mut indices = Vec::new();
1164 for y in 0..n {
1165 for x in 0..n {
1166 let a = y * (n + 1) + x;
1167 indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
1168 }
1169 }
1170 Mesh::from_buffers(&positions, &indices).unwrap()
1171 }
1172
1173 fn assert_atomic(edit: impl Fn(&mut Mesh) -> fast_mesh_store::ChangeSet) {
1176 let opts = BuildOptions {
1177 max_leaf_size: 2,
1178 treelet_size: 8,
1179 ..Default::default()
1180 };
1181 for k in 0.. {
1182 let mut m = wavy(8);
1183 let mut bvh = Bvh::build(&m, &opts).unwrap();
1184 m.ensure_incidence().unwrap();
1185 let changes = edit(&mut m);
1186 let before = bvh.fingerprint();
1187 fail_allocations_after(Some(k));
1188 let result = bvh.update(&mut m, &changes);
1189 fail_allocations_after(None);
1190 match result {
1191 Err(BvhError::OutOfMemory) => {
1192 assert_eq!(bvh.fingerprint(), before, "allocation {k}")
1193 }
1194 Err(e) => panic!("unexpected error {e}"),
1195 Ok(_) => {
1196 bvh.validate(&m).unwrap();
1197 assert!(k > 0, "the update must allocate at least once");
1198 return;
1199 }
1200 }
1201 }
1202 }
1203
1204 #[test]
1205 fn failed_partial_updates_leave_the_tree_unchanged() {
1206 assert_atomic(|m| {
1207 let mut tx = m.begin_edit();
1208 tx.remove_triangles(&[3, 40]).unwrap();
1209 tx.add_triangles(&[[0, 10, 20]]).unwrap();
1210 tx.set_positions(&[30], &[[3.0, 3.0, 4.0]]).unwrap();
1211 tx.commit().unwrap().changes().clone()
1212 });
1213 }
1214
1215 #[test]
1216 fn failed_region_rebuilds_leave_the_tree_unchanged() {
1217 let opts = BuildOptions {
1218 max_leaf_size: 2,
1219 treelet_size: 8,
1220 ..Default::default()
1221 };
1222 let region = [0u32, 40, 80, 120];
1224 for k in 0.. {
1225 let m = wavy(8);
1226 let mut bvh = Bvh::build(&m, &opts).unwrap();
1227 let mut which: Vec<_> = region
1228 .iter()
1229 .map(|&t| bvh.treelet_of_triangle(t).unwrap())
1230 .collect();
1231 which.dedup();
1232 assert!(which.len() > 1, "the region must span several treelets");
1233 let before = bvh.fingerprint();
1234 fail_allocations_after(Some(k));
1235 let result = bvh.rebuild_region(&m, ®ion);
1236 fail_allocations_after(None);
1237 match result {
1238 Err(BvhError::OutOfMemory) => {
1239 assert_eq!(bvh.fingerprint(), before, "allocation {k}")
1240 }
1241 Err(e) => panic!("unexpected error {e}"),
1242 Ok(()) => {
1243 assert!(k > 1, "the rebuild must allocate several times");
1244 assert_ne!(bvh.fingerprint(), before);
1245 bvh.validate(&m).unwrap();
1246 return;
1247 }
1248 }
1249 }
1250 }
1251
1252 #[test]
1253 fn failed_full_rebuilds_leave_the_tree_unchanged() {
1254 assert_atomic(|m| {
1255 let all: Vec<u32> = (0..m.triangle_slots() as u32).step_by(2).collect();
1256 let mut tx = m.begin_edit();
1257 tx.remove_triangles(&all).unwrap();
1258 tx.commit().unwrap().changes().clone()
1259 });
1260 }
1261}