1use crate::aabb::Aabb3;
7use crate::vec::Point3;
8
9#[derive(Debug, Clone, Copy)]
11struct BvhNode {
12 aabb: Aabb3,
14 primitive: usize,
16 left: usize,
18 right: usize,
20}
21
22#[derive(Debug, Clone)]
24pub struct Bvh {
25 nodes: Vec<BvhNode>,
26}
27
28impl Bvh {
29 #[must_use]
34 pub fn build(aabbs: &[(usize, Aabb3)]) -> Self {
35 if aabbs.is_empty() {
36 return Self { nodes: Vec::new() };
37 }
38
39 let mut indices: Vec<usize> = (0..aabbs.len()).collect();
40 let mut nodes = Vec::with_capacity(2 * aabbs.len());
41 build_recursive(aabbs, &mut indices, &mut nodes);
42 Self { nodes }
43 }
44
45 #[must_use]
47 pub fn query_overlap(&self, test: &Aabb3) -> Vec<usize> {
48 let mut results = Vec::new();
49 self.query_overlap_into(test, &mut results);
50 results
51 }
52
53 pub fn query_overlap_into(&self, test: &Aabb3, results: &mut Vec<usize>) {
58 results.clear();
59 if self.nodes.is_empty() {
60 return;
61 }
62 let mut stack = vec![0usize];
63 while let Some(idx) = stack.pop() {
64 let node = &self.nodes[idx];
65 if !node.aabb.intersects(*test) {
66 continue;
67 }
68 if node.primitive == usize::MAX {
69 stack.push(node.left);
70 stack.push(node.right);
71 } else {
72 results.push(node.primitive);
73 }
74 }
75 }
76
77 #[must_use]
82 pub fn query_ray(&self, origin: Point3, dir: crate::vec::Vec3) -> Vec<usize> {
83 let mut results = Vec::new();
84 self.query_ray_into(origin, dir, &mut results);
85 results
86 }
87
88 pub fn query_ray_into(&self, origin: Point3, dir: crate::vec::Vec3, results: &mut Vec<usize>) {
93 results.clear();
94 if self.nodes.is_empty() {
95 return;
96 }
97 let inv_dir = crate::vec::Vec3::new(1.0 / dir.x(), 1.0 / dir.y(), 1.0 / dir.z());
99 let mut stack = vec![0usize];
100 while let Some(idx) = stack.pop() {
101 let node = &self.nodes[idx];
102 if !node.aabb.ray_intersects(origin, inv_dir) {
103 continue;
104 }
105 if node.primitive == usize::MAX {
106 stack.push(node.left);
107 stack.push(node.right);
108 } else {
109 results.push(node.primitive);
110 }
111 }
112 }
113
114 #[must_use]
123 pub fn query_closest(&self, point: Point3) -> Option<usize> {
124 if self.nodes.is_empty() {
125 return None;
126 }
127
128 let mut best_id = None;
129 let mut best_dist = f64::INFINITY;
130 let mut stack = vec![0usize];
131
132 while let Some(idx) = stack.pop() {
133 let node = &self.nodes[idx];
134 let node_dist = node.aabb.distance_squared_to_point(point);
135 if node_dist >= best_dist {
136 continue;
137 }
138 if node.primitive == usize::MAX {
139 let dl = self.nodes[node.left].aabb.distance_squared_to_point(point);
141 let dr = self.nodes[node.right].aabb.distance_squared_to_point(point);
142 if dl < dr {
143 stack.push(node.right);
144 stack.push(node.left);
145 } else {
146 stack.push(node.left);
147 stack.push(node.right);
148 }
149 } else {
150 best_id = Some(node.primitive);
151 best_dist = node_dist;
152 }
153 }
154
155 best_id
156 }
157
158 #[must_use]
168 pub fn query_closest_with_distance(
169 &self,
170 point: Point3,
171 distance_sq: &dyn Fn(usize) -> f64,
172 ) -> Option<usize> {
173 if self.nodes.is_empty() {
174 return None;
175 }
176
177 let mut best_id = None;
178 let mut best_dist = f64::INFINITY;
179 let mut stack = vec![0usize];
180
181 while let Some(idx) = stack.pop() {
182 let node = &self.nodes[idx];
183 let node_dist = node.aabb.distance_squared_to_point(point);
184 if node_dist >= best_dist {
185 continue;
186 }
187 if node.primitive == usize::MAX {
188 let dl = self.nodes[node.left].aabb.distance_squared_to_point(point);
189 let dr = self.nodes[node.right].aabb.distance_squared_to_point(point);
190 if dl < dr {
191 stack.push(node.right);
192 stack.push(node.left);
193 } else {
194 stack.push(node.left);
195 stack.push(node.right);
196 }
197 } else {
198 let actual_dist = distance_sq(node.primitive);
199 if actual_dist < best_dist {
200 best_dist = actual_dist;
201 best_id = Some(node.primitive);
202 }
203 }
204 }
205
206 best_id
207 }
208}
209
210#[allow(clippy::expect_used, clippy::cast_precision_loss)]
212fn build_recursive(
213 aabbs: &[(usize, Aabb3)],
214 indices: &mut [usize],
215 nodes: &mut Vec<BvhNode>,
216) -> usize {
217 let node_idx = nodes.len();
218
219 if indices.len() == 1 {
220 let i = indices[0];
221 nodes.push(BvhNode {
222 aabb: aabbs[i].1,
223 primitive: aabbs[i].0,
224 left: usize::MAX,
225 right: usize::MAX,
226 });
227 return node_idx;
228 }
229
230 let combined = indices
232 .iter()
233 .map(|&i| aabbs[i].1)
234 .reduce(super::aabb::Aabb3::union)
235 .expect("non-empty");
236
237 if indices.len() == 2 {
238 nodes.push(BvhNode {
240 aabb: combined,
241 primitive: usize::MAX,
242 left: 0,
243 right: 0,
244 });
245 let left = build_recursive(aabbs, &mut indices[..1], nodes);
246 let right = build_recursive(aabbs, &mut indices[1..], nodes);
247 nodes[node_idx].left = left;
248 nodes[node_idx].right = right;
249 return node_idx;
250 }
251
252 let parent_area = combined.surface_area();
254 let mut best_cost = f64::INFINITY;
255 let mut best_axis = 0;
256 let mut best_split = indices.len() / 2;
257
258 for axis in 0..3 {
259 indices.sort_by(|&a, &b| {
260 let ca = centroid_axis(aabbs[a].1, axis);
261 let cb = centroid_axis(aabbs[b].1, axis);
262 ca.partial_cmp(&cb).unwrap_or(std::cmp::Ordering::Equal)
263 });
264
265 let n = indices.len();
266
267 let mut suffix = vec![aabbs[indices[n - 1]].1; n];
269 for k in (0..n - 1).rev() {
270 suffix[k] = suffix[k + 1].union(aabbs[indices[k]].1);
271 }
272
273 let mut left_aabb = aabbs[indices[0]].1;
274 for split in 1..n {
275 left_aabb = left_aabb.union(aabbs[indices[split - 1]].1);
276 let right_aabb = suffix[split];
277
278 let cost = (split as f64).mul_add(
279 left_aabb.surface_area(),
280 (n - split) as f64 * right_aabb.surface_area(),
281 ) / parent_area;
282
283 if cost < best_cost {
284 best_cost = cost;
285 best_axis = axis;
286 best_split = split;
287 }
288 }
289 }
290
291 indices.sort_by(|&a, &b| {
293 let ca = centroid_axis(aabbs[a].1, best_axis);
294 let cb = centroid_axis(aabbs[b].1, best_axis);
295 ca.partial_cmp(&cb).unwrap_or(std::cmp::Ordering::Equal)
296 });
297
298 nodes.push(BvhNode {
300 aabb: combined,
301 primitive: usize::MAX,
302 left: 0,
303 right: 0,
304 });
305
306 let (left_indices, right_indices) = indices.split_at_mut(best_split);
307 let left = build_recursive(aabbs, left_indices, nodes);
308 let right = build_recursive(aabbs, right_indices, nodes);
309 nodes[node_idx].left = left;
310 nodes[node_idx].right = right;
311
312 node_idx
313}
314
315fn centroid_axis(aabb: Aabb3, axis: usize) -> f64 {
317 match axis {
318 0 => aabb.center().x(),
319 1 => aabb.center().y(),
320 _ => aabb.center().z(),
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 fn make_box(id: usize, x: f64, y: f64, z: f64, size: f64) -> (usize, Aabb3) {
329 (
330 id,
331 Aabb3::from_points([
332 Point3::new(x, y, z),
333 Point3::new(x + size, y + size, z + size),
334 ]),
335 )
336 }
337
338 #[test]
339 fn bvh_empty() {
340 let bvh = Bvh::build(&[]);
341 assert!(
342 bvh.query_overlap(&Aabb3::from_points([Point3::new(0.0, 0.0, 0.0)]))
343 .is_empty()
344 );
345 assert!(bvh.query_closest(Point3::new(0.0, 0.0, 0.0)).is_none());
346 }
347
348 #[test]
349 fn bvh_single() {
350 let aabbs = vec![make_box(42, 0.0, 0.0, 0.0, 1.0)];
351 let bvh = Bvh::build(&aabbs);
352
353 let hits = bvh.query_overlap(&Aabb3::from_points([
354 Point3::new(0.5, 0.5, 0.5),
355 Point3::new(0.6, 0.6, 0.6),
356 ]));
357 assert_eq!(hits, vec![42]);
358
359 let miss = bvh.query_overlap(&Aabb3::from_points([
360 Point3::new(5.0, 5.0, 5.0),
361 Point3::new(6.0, 6.0, 6.0),
362 ]));
363 assert!(miss.is_empty());
364 }
365
366 #[test]
367 fn bvh_multiple_overlap() {
368 let aabbs = vec![
369 make_box(0, 0.0, 0.0, 0.0, 1.0),
370 make_box(1, 2.0, 0.0, 0.0, 1.0),
371 make_box(2, 4.0, 0.0, 0.0, 1.0),
372 make_box(3, 0.0, 2.0, 0.0, 1.0),
373 ];
374 let bvh = Bvh::build(&aabbs);
375
376 let test = Aabb3::from_points([Point3::new(0.5, 0.5, 0.5), Point3::new(2.5, 0.5, 0.5)]);
378 let mut hits = bvh.query_overlap(&test);
379 hits.sort_unstable();
380 assert_eq!(hits, vec![0, 1]);
381 }
382
383 #[test]
384 fn bvh_closest() {
385 let aabbs = vec![
386 make_box(0, 0.0, 0.0, 0.0, 1.0),
387 make_box(1, 10.0, 10.0, 10.0, 1.0),
388 make_box(2, 5.0, 5.0, 5.0, 1.0),
389 ];
390 let bvh = Bvh::build(&aabbs);
391
392 assert_eq!(bvh.query_closest(Point3::new(0.5, 0.5, 0.5)), Some(0));
393 assert_eq!(bvh.query_closest(Point3::new(10.5, 10.5, 10.5)), Some(1));
394 }
395
396 #[test]
397 #[allow(clippy::cast_precision_loss)]
398 fn bvh_many_primitives() {
399 let aabbs: Vec<(usize, Aabb3)> = (0..100)
400 .map(|i| make_box(i, i as f64 * 2.0, 0.0, 0.0, 1.0))
401 .collect();
402 let bvh = Bvh::build(&aabbs);
403
404 let test = Aabb3::from_points([Point3::new(99.5, 0.0, 0.0), Point3::new(100.5, 1.0, 1.0)]);
406 let hits = bvh.query_overlap(&test);
407 assert!(
408 hits.contains(&50),
409 "expected primitive 50 in hits: {hits:?}"
410 );
411 }
412}