Skip to content

Commit dfc9989

Browse files
fix(bvh-region): get_closest now returns the correct closest element (#723)
1 parent d249ad9 commit dfc9989

File tree

12 files changed

+512
-352
lines changed

12 files changed

+512
-352
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ papaya = '0.1.4'
9595
parking_lot = '0.12.3'
9696
plotters-bitmap = '0.3.6'
9797
proc-macro2 = '1.0.89'
98+
proptest = "1.5.0"
9899
quote = '1.0.37'
99100
rand = '0.8.5'
100101
rayon = '1.10.0'

crates/bvh-region/Cargo.toml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,23 @@ name = "sort"
1111
#name = "side_by_side"
1212

1313
[dependencies]
14-
geometry = {workspace = true}
15-
glam = {workspace = true, features = ["serde"]}
16-
plotters = {workspace = true, features = [
17-
"plotters-bitmap",
18-
"image"
19-
], optional = true}
20-
plotters-bitmap = {workspace = true, optional = true}
21-
arrayvec = {workspace = true}
22-
fastrand = {workspace = true}
23-
ordered-float = {workspace = true}
24-
rayon = {workspace = true}
25-
tracing = {workspace = true}
14+
arrayvec = { workspace = true }
15+
derive_more = { workspace = true }
16+
fastrand = { workspace = true }
17+
geometry = { workspace = true }
18+
glam = { workspace = true, features = ["serde"] }
19+
ordered-float = { workspace = true }
20+
plotters = { workspace = true, features = ["plotters-bitmap", "image"], optional = true }
21+
plotters-bitmap = { workspace = true, optional = true }
22+
proptest = { workspace = true }
23+
rayon = { workspace = true }
24+
tracing = { workspace = true }
2625

2726
[dev-dependencies]
28-
criterion = {workspace = true}
27+
approx = { workspace = true }
28+
criterion = { workspace = true }
29+
rand = { workspace = true }
2930
#divan = {workspace = true}
30-
rand = {workspace = true}
3131
#tango-bench = {workspace = true}
3232

3333
[features]

crates/bvh-region/src/build.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use std::fmt::Debug;
2+
3+
use geometry::aabb::Aabb;
4+
5+
use crate::{
6+
Bvh, ELEMENTS_TO_ACTIVATE_LEAF, VOLUME_TO_ACTIVATE_LEAF, node::BvhNode, sort_by_largest_axis,
7+
utils, utils::GetAabb,
8+
};
9+
10+
/// Used to find the start addresses of the elements and nodes arrays
11+
#[derive(Debug)]
12+
pub struct StartAddresses<T> {
13+
/// Base pointer to the start of the elements array
14+
pub start_elements_ptr: *const T,
15+
pub start_nodes_ptr: *const BvhNode,
16+
}
17+
18+
impl<T> StartAddresses<T> {
19+
const fn element_start_index(&self, slice: &[T]) -> isize {
20+
unsafe { slice.as_ptr().offset_from(self.start_elements_ptr) }
21+
}
22+
23+
const fn node_start_index(&self, slice: &[BvhNode]) -> isize {
24+
unsafe { slice.as_ptr().offset_from(self.start_nodes_ptr) }
25+
}
26+
}
27+
28+
unsafe impl<T: Send> Send for StartAddresses<T> {}
29+
unsafe impl<T: Sync> Sync for StartAddresses<T> {}
30+
31+
impl<T> Bvh<T>
32+
where
33+
T: Debug + Send + Copy + Sync,
34+
{
35+
#[tracing::instrument(skip_all, fields(elements_len = elements.len()))]
36+
pub fn build(mut elements: Vec<T>, get_aabb: (impl GetAabb<T> + Sync)) -> Self {
37+
let max_threads = utils::thread_count_pow2();
38+
39+
let len = elements.len();
40+
41+
// // 1.7 works too, 2.0 is upper bound ... 1.8 is probably best
42+
// todo: make this more mathematically derived
43+
let capacity = ((len / ELEMENTS_TO_ACTIVATE_LEAF) as f64 * 8.0) as usize;
44+
45+
// [A]
46+
let capacity = capacity.max(16);
47+
48+
let mut nodes = vec![BvhNode::DUMMY; capacity];
49+
50+
let bvh = StartAddresses {
51+
start_elements_ptr: elements.as_ptr(),
52+
start_nodes_ptr: nodes.as_ptr(),
53+
};
54+
55+
#[expect(
56+
clippy::indexing_slicing,
57+
reason = "Look at [A]. The length is at least 16, so this is safe."
58+
)]
59+
let nodes_slice = &mut nodes[1..];
60+
61+
let (root, _) = build_in(&bvh, &mut elements, max_threads, 0, nodes_slice, &get_aabb);
62+
63+
Self {
64+
nodes,
65+
elements,
66+
root,
67+
}
68+
}
69+
}
70+
71+
#[allow(clippy::float_cmp)]
72+
pub fn build_in<T>(
73+
addresses: &StartAddresses<T>,
74+
elements: &mut [T],
75+
max_threads: usize,
76+
nodes_idx: usize,
77+
nodes: &mut [BvhNode],
78+
get_aabb: &(impl GetAabb<T> + Sync),
79+
) -> (i32, usize)
80+
where
81+
T: Send + Copy + Sync + Debug,
82+
{
83+
// aabb that encompasses all elements
84+
let aabb: Aabb = elements.iter().map(get_aabb).collect();
85+
let volume = aabb.volume();
86+
87+
if elements.len() <= ELEMENTS_TO_ACTIVATE_LEAF || volume <= VOLUME_TO_ACTIVATE_LEAF {
88+
let idx_start = addresses.element_start_index(elements);
89+
90+
let node = BvhNode::create_leaf(aabb, idx_start as usize, elements.len());
91+
92+
let set = &mut nodes[nodes_idx..=nodes_idx];
93+
set[0] = node;
94+
95+
let idx = addresses.node_start_index(set);
96+
97+
let idx = idx as i32;
98+
99+
debug_assert!(idx > 0);
100+
101+
return (idx, nodes_idx + 1);
102+
}
103+
104+
sort_by_largest_axis(elements, &aabb, get_aabb);
105+
106+
let element_split_idx = elements.len() / 2;
107+
108+
let (left_elems, right_elems) = elements.split_at_mut(element_split_idx);
109+
110+
debug_assert!(max_threads != 0);
111+
112+
let original_node_idx = nodes_idx;
113+
114+
let (left, right, nodes_idx, to_set) = if max_threads == 1 {
115+
let start_idx = nodes_idx;
116+
let (left, nodes_idx) = build_in(
117+
addresses,
118+
left_elems,
119+
max_threads,
120+
nodes_idx + 1,
121+
nodes,
122+
get_aabb,
123+
);
124+
125+
let (right, nodes_idx) = build_in(
126+
addresses,
127+
right_elems,
128+
max_threads,
129+
nodes_idx,
130+
nodes,
131+
get_aabb,
132+
);
133+
let end_idx = nodes_idx;
134+
135+
debug_assert!(start_idx != end_idx);
136+
137+
(
138+
left,
139+
right,
140+
nodes_idx,
141+
&mut nodes[original_node_idx..=original_node_idx],
142+
)
143+
} else {
144+
let max_threads = max_threads >> 1;
145+
146+
let (to_set, nodes) = nodes.split_at_mut(1);
147+
148+
let node_split_idx = nodes.len() / 2;
149+
let (left_nodes, right_nodes) = match true {
150+
true => {
151+
let (left, right) = nodes.split_at_mut(node_split_idx);
152+
(left, right)
153+
}
154+
false => {
155+
let (right, left) = nodes.split_at_mut(node_split_idx);
156+
(left, right)
157+
}
158+
};
159+
160+
let (left, right) = rayon::join(
161+
|| build_in(addresses, left_elems, max_threads, 0, left_nodes, get_aabb),
162+
|| {
163+
build_in(
164+
addresses,
165+
right_elems,
166+
max_threads,
167+
0,
168+
right_nodes,
169+
get_aabb,
170+
)
171+
},
172+
);
173+
174+
(left.0, right.0, 0, to_set)
175+
};
176+
177+
let node = BvhNode { aabb, left, right };
178+
179+
to_set[0] = node;
180+
let idx = unsafe { to_set.as_ptr().offset_from(addresses.start_nodes_ptr) };
181+
let idx = idx as i32;
182+
183+
// trace!("internal nodes_idx {:03}", original_node_idx);
184+
185+
debug_assert!(idx > 0);
186+
187+
(idx, nodes_idx + 1)
188+
}

0 commit comments

Comments
 (0)