use crate::math::BoundingBox;
const LEAF_SIZE: usize = 4;
#[derive(Debug, Clone, PartialEq)]
struct Node {
bbox: BoundingBox,
start: u32,
count: u32,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub(crate) struct Bvh {
nodes: Vec<Node>,
items: Vec<u32>,
}
impl Bvh {
pub(crate) fn build(boxes: &[BoundingBox]) -> Bvh {
let mut items: Vec<u32> = (0..boxes.len() as u32)
.filter(|&i| !boxes[i as usize].is_empty())
.collect();
let mut nodes = Vec::with_capacity(2 * items.len() / LEAF_SIZE + 1);
if !items.is_empty() {
let centers: Vec<[f64; 3]> = boxes.iter().map(|b| b.center().0).collect();
build_node(boxes, ¢ers, &mut items, 0, &mut nodes);
}
Bvh { nodes, items }
}
pub(crate) fn visit(
&self,
overlaps: impl Fn(&BoundingBox) -> bool,
mut f: impl FnMut(u32) -> bool,
) {
if self.nodes.is_empty() {
return;
}
let mut stack: Vec<u32> = Vec::with_capacity(64);
stack.push(0);
while let Some(n) = stack.pop() {
let node = &self.nodes[n as usize];
if !overlaps(&node.bbox) {
continue;
}
if node.count > 0 {
let s = node.start as usize;
for &it in &self.items[s..s + node.count as usize] {
if !f(it) {
return;
}
}
} else {
stack.push(node.start);
stack.push(n + 1);
}
}
}
}
fn build_node(
boxes: &[BoundingBox],
centers: &[[f64; 3]],
items: &mut [u32],
offset: usize,
nodes: &mut Vec<Node>,
) -> u32 {
let bbox = items
.iter()
.fold(BoundingBox::EMPTY, |a, &i| a.union(&boxes[i as usize]));
let me = nodes.len() as u32;
nodes.push(Node {
bbox,
start: offset as u32,
count: items.len() as u32,
});
if items.len() <= LEAF_SIZE {
return me;
}
let mut lo = [f64::INFINITY; 3];
let mut hi = [f64::NEG_INFINITY; 3];
for &i in items.iter() {
let c = centers[i as usize];
for k in 0..3 {
if c[k].is_finite() {
lo[k] = lo[k].min(c[k]);
hi[k] = hi[k].max(c[k]);
}
}
}
let axis = (0..3)
.max_by(|&a, &b| (hi[a] - lo[a]).total_cmp(&(hi[b] - lo[b])))
.unwrap_or(0);
let mid = items.len() / 2;
items.select_nth_unstable_by(mid, |&a, &b| {
centers[a as usize][axis].total_cmp(¢ers[b as usize][axis])
});
let (left, right) = items.split_at_mut(mid);
build_node(boxes, centers, left, offset, nodes);
let r = build_node(boxes, centers, right, offset + mid, nodes);
let node = &mut nodes[me as usize];
node.start = r;
node.count = 0;
me
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::Vec3;
#[test]
fn point_queries_match_brute_force() {
let mut seed = 7u64;
let mut rnd = || {
seed = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(seed >> 11) as f64 / (1u64 << 53) as f64
};
let boxes: Vec<BoundingBox> = (0..500)
.map(|_| {
let c = Vec3::new(rnd() * 10.0, rnd() * 10.0, rnd() * 10.0);
let d = Vec3::new(rnd(), rnd(), rnd());
BoundingBox::from_corners(c - d, c + d)
})
.collect();
let bvh = Bvh::build(&boxes);
for _ in 0..2000 {
let p = Vec3::new(rnd() * 10.0, rnd() * 10.0, rnd() * 10.0);
let mut got = Vec::new();
bvh.visit(
|b| b.contains(p),
|i| {
if boxes[i as usize].contains(p) {
got.push(i);
}
true
},
);
got.sort_unstable();
let want: Vec<u32> = (0..boxes.len() as u32)
.filter(|&i| boxes[i as usize].contains(p))
.collect();
assert_eq!(got, want);
}
assert!(Bvh::build(&[]).nodes.is_empty());
}
}