use crate::aabb::{Bounded, AABB};
use crate::bounding_hierarchy::{BHShape, BoundingHierarchy};
use crate::bvh::iter::BVHTraverseIterator;
use crate::ray::Ray;
use crate::utils::{concatenate_vectors, joint_aabb_of_shapes, Bucket};
use crate::Point3;
use crate::EPSILON;
use std::f32;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(clippy::upper_case_acronyms)]
pub enum BVHNode {
Leaf {
parent_index: usize,
depth: u32,
shape_index: usize,
},
Node {
parent_index: usize,
depth: u32,
child_l_index: usize,
child_l_aabb: AABB,
child_r_index: usize,
child_r_aabb: AABB,
},
}
impl PartialEq for BVHNode {
fn eq(&self, other: &BVHNode) -> bool {
match (self, other) {
(
&BVHNode::Node {
parent_index: self_parent_index,
depth: self_depth,
child_l_index: self_child_l_index,
child_r_index: self_child_r_index,
..
},
&BVHNode::Node {
parent_index: other_parent_index,
depth: other_depth,
child_l_index: other_child_l_index,
child_r_index: other_child_r_index,
..
},
) => {
self_parent_index == other_parent_index
&& self_depth == other_depth
&& self_child_l_index == other_child_l_index
&& self_child_r_index == other_child_r_index
}
(
&BVHNode::Leaf {
parent_index: self_parent_index,
depth: self_depth,
shape_index: self_shape_index,
},
&BVHNode::Leaf {
parent_index: other_parent_index,
depth: other_depth,
shape_index: other_shape_index,
},
) => {
self_parent_index == other_parent_index
&& self_depth == other_depth
&& self_shape_index == other_shape_index
}
_ => false,
}
}
}
impl BVHNode {
pub fn parent(&self) -> usize {
match *self {
BVHNode::Node { parent_index, .. } | BVHNode::Leaf { parent_index, .. } => parent_index,
}
}
pub fn parent_mut(&mut self) -> &mut usize {
match *self {
BVHNode::Node {
ref mut parent_index,
..
}
| BVHNode::Leaf {
ref mut parent_index,
..
} => parent_index,
}
}
pub fn child_l(&self) -> usize {
match *self {
BVHNode::Node { child_l_index, .. } => child_l_index,
_ => panic!("Tried to get the left child of a leaf node."),
}
}
pub fn child_l_aabb(&self) -> AABB {
match *self {
BVHNode::Node { child_l_aabb, .. } => child_l_aabb,
_ => panic!(),
}
}
pub fn child_l_aabb_mut(&mut self) -> &mut AABB {
match *self {
BVHNode::Node {
ref mut child_l_aabb,
..
} => child_l_aabb,
_ => panic!("Tried to get the left child's `AABB` of a leaf node."),
}
}
pub fn child_r(&self) -> usize {
match *self {
BVHNode::Node { child_r_index, .. } => child_r_index,
_ => panic!("Tried to get the right child of a leaf node."),
}
}
pub fn child_r_aabb(&self) -> AABB {
match *self {
BVHNode::Node { child_r_aabb, .. } => child_r_aabb,
_ => panic!(),
}
}
pub fn child_r_aabb_mut(&mut self) -> &mut AABB {
match *self {
BVHNode::Node {
ref mut child_r_aabb,
..
} => child_r_aabb,
_ => panic!("Tried to get the right child's `AABB` of a leaf node."),
}
}
pub fn depth(&self) -> u32 {
match *self {
BVHNode::Node { depth, .. } | BVHNode::Leaf { depth, .. } => depth,
}
}
pub fn get_node_aabb<Shape: BHShape>(&self, shapes: &[Shape]) -> AABB {
match *self {
BVHNode::Node {
child_l_aabb,
child_r_aabb,
..
} => child_l_aabb.join(&child_r_aabb),
BVHNode::Leaf { shape_index, .. } => shapes[shape_index].aabb(),
}
}
pub fn shape_index(&self) -> Option<usize> {
match *self {
BVHNode::Leaf { shape_index, .. } => Some(shape_index),
_ => None,
}
}
fn create_dummy() -> BVHNode {
BVHNode::Leaf {
parent_index: 0,
depth: 0,
shape_index: 0,
}
}
pub fn build<T: BHShape>(
shapes: &mut [T],
indices: &[usize],
nodes: &mut Vec<BVHNode>,
parent_index: usize,
depth: u32,
) -> usize {
fn grow_convex_hull(convex_hull: (AABB, AABB), shape_aabb: &AABB) -> (AABB, AABB) {
let center = &shape_aabb.center();
let convex_hull_aabbs = &convex_hull.0;
let convex_hull_centroids = &convex_hull.1;
(
convex_hull_aabbs.join(shape_aabb),
convex_hull_centroids.grow(center),
)
}
let mut convex_hull = Default::default();
for index in indices {
convex_hull = grow_convex_hull(convex_hull, &shapes[*index].aabb());
}
let (aabb_bounds, centroid_bounds) = convex_hull;
if indices.len() == 1 {
let shape_index = indices[0];
let node_index = nodes.len();
nodes.push(BVHNode::Leaf {
parent_index,
depth,
shape_index,
});
shapes[shape_index].set_bh_node_index(node_index);
return node_index;
}
let node_index = nodes.len();
nodes.push(BVHNode::create_dummy());
let split_axis = centroid_bounds.largest_axis();
let split_axis_size = centroid_bounds.max[split_axis] - centroid_bounds.min[split_axis];
let (child_l_index, child_l_aabb, child_r_index, child_r_aabb) = if split_axis_size
< EPSILON
{
let (child_l_indices, child_r_indices) = indices.split_at(indices.len() / 2);
let child_l_aabb = joint_aabb_of_shapes(child_l_indices, shapes);
let child_r_aabb = joint_aabb_of_shapes(child_r_indices, shapes);
let child_l_index =
BVHNode::build(shapes, child_l_indices, nodes, node_index, depth + 1);
let child_r_index =
BVHNode::build(shapes, child_r_indices, nodes, node_index, depth + 1);
(child_l_index, child_l_aabb, child_r_index, child_r_aabb)
} else {
const NUM_BUCKETS: usize = 6;
let mut buckets = [Bucket::empty(); NUM_BUCKETS];
let mut bucket_assignments: [Vec<usize>; NUM_BUCKETS] = Default::default();
for idx in indices {
let shape = &shapes[*idx];
let shape_aabb = shape.aabb();
let shape_center = shape_aabb.center();
let bucket_num_relative =
(shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size;
let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f32 - 0.01)) as usize;
buckets[bucket_num].add_aabb(&shape_aabb);
bucket_assignments[bucket_num].push(*idx);
}
let mut min_bucket = 0;
let mut min_cost = f32::INFINITY;
let mut child_l_aabb = AABB::empty();
let mut child_r_aabb = AABB::empty();
for i in 0..(NUM_BUCKETS - 1) {
let (l_buckets, r_buckets) = buckets.split_at(i + 1);
let child_l = l_buckets.iter().fold(Bucket::empty(), Bucket::join_bucket);
let child_r = r_buckets.iter().fold(Bucket::empty(), Bucket::join_bucket);
let cost = (child_l.size as f32 * child_l.aabb.surface_area()
+ child_r.size as f32 * child_r.aabb.surface_area())
/ aabb_bounds.surface_area();
if cost < min_cost {
min_bucket = i;
min_cost = cost;
child_l_aabb = child_l.aabb;
child_r_aabb = child_r.aabb;
}
}
let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1);
let child_l_indices = concatenate_vectors(l_assignments);
let child_r_indices = concatenate_vectors(r_assignments);
let child_l_index =
BVHNode::build(shapes, &child_l_indices, nodes, node_index, depth + 1);
let child_r_index =
BVHNode::build(shapes, &child_r_indices, nodes, node_index, depth + 1);
(child_l_index, child_l_aabb, child_r_index, child_r_aabb)
};
assert!(!child_l_aabb.is_empty());
assert!(!child_r_aabb.is_empty());
nodes[node_index] = BVHNode::Node {
parent_index,
depth,
child_l_aabb,
child_l_index,
child_r_aabb,
child_r_index,
};
node_index
}
pub fn traverse_recursive(
nodes: &[BVHNode],
node_index: usize,
ray: &Ray,
indices: &mut Vec<usize>,
) {
match nodes[node_index] {
BVHNode::Node {
ref child_l_aabb,
child_l_index,
ref child_r_aabb,
child_r_index,
..
} => {
if ray.intersects_aabb(child_l_aabb) {
BVHNode::traverse_recursive(nodes, child_l_index, ray, indices);
}
if ray.intersects_aabb(child_r_aabb) {
BVHNode::traverse_recursive(nodes, child_r_index, ray, indices);
}
}
BVHNode::Leaf { shape_index, .. } => {
indices.push(shape_index);
}
}
}
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BVH {
pub nodes: Vec<BVHNode>,
}
impl BVH {
pub fn build<Shape: BHShape>(shapes: &mut [Shape]) -> BVH {
let indices = (0..shapes.len()).collect::<Vec<usize>>();
let expected_node_count = shapes.len() * 2;
let mut nodes = Vec::with_capacity(expected_node_count);
BVHNode::build(shapes, &indices, &mut nodes, 0, 0);
BVH { nodes }
}
pub fn traverse<'a, Shape: Bounded>(&'a self, ray: &Ray, shapes: &'a [Shape]) -> Vec<&Shape> {
let mut indices = Vec::new();
BVHNode::traverse_recursive(&self.nodes, 0, ray, &mut indices);
indices
.iter()
.map(|index| &shapes[*index])
.collect::<Vec<_>>()
}
pub fn traverse_iterator<'a, Shape: Bounded>(
&'a self,
ray: &'a Ray,
shapes: &'a [Shape],
) -> BVHTraverseIterator<Shape> {
BVHTraverseIterator::new(self, ray, shapes)
}
pub fn pretty_print(&self) {
let nodes = &self.nodes;
fn print_node(nodes: &[BVHNode], node_index: usize) {
match nodes[node_index] {
BVHNode::Node {
child_l_index,
child_r_index,
depth,
child_l_aabb,
child_r_aabb,
..
} => {
let padding: String = " ".repeat(depth as usize);
println!("{}child_l {}", padding, child_l_aabb);
print_node(nodes, child_l_index);
println!("{}child_r {}", padding, child_r_aabb);
print_node(nodes, child_r_index);
}
BVHNode::Leaf {
shape_index, depth, ..
} => {
let padding: String = " ".repeat(depth as usize);
println!("{}shape\t{:?}", padding, shape_index);
}
}
}
print_node(nodes, 0);
}
fn is_consistent_subtree<Shape: BHShape>(
&self,
node_index: usize,
expected_parent_index: usize,
expected_outer_aabb: &AABB,
expected_depth: u32,
node_count: &mut usize,
shapes: &[Shape],
) -> bool {
*node_count += 1;
match self.nodes[node_index] {
BVHNode::Node {
parent_index,
depth,
child_l_index,
child_l_aabb,
child_r_index,
child_r_aabb,
} => {
let correct_parent_index = expected_parent_index == parent_index;
let correct_depth = expected_depth == depth;
let left_aabb_in_parent =
expected_outer_aabb.approx_contains_aabb_eps(&child_l_aabb, EPSILON);
let right_aabb_in_parent =
expected_outer_aabb.approx_contains_aabb_eps(&child_r_aabb, EPSILON);
let left_subtree_consistent = self.is_consistent_subtree(
child_l_index,
node_index,
&child_l_aabb,
expected_depth + 1,
node_count,
shapes,
);
let right_subtree_consistent = self.is_consistent_subtree(
child_r_index,
node_index,
&child_r_aabb,
expected_depth + 1,
node_count,
shapes,
);
correct_parent_index
&& correct_depth
&& left_aabb_in_parent
&& right_aabb_in_parent
&& left_subtree_consistent
&& right_subtree_consistent
}
BVHNode::Leaf {
parent_index,
depth,
shape_index,
} => {
let correct_parent_index = expected_parent_index == parent_index;
let correct_depth = expected_depth == depth;
let shape_aabb = shapes[shape_index].aabb();
let shape_aabb_in_parent =
expected_outer_aabb.approx_contains_aabb_eps(&shape_aabb, EPSILON);
correct_parent_index && correct_depth && shape_aabb_in_parent
}
}
}
pub fn is_consistent<Shape: BHShape>(&self, shapes: &[Shape]) -> bool {
let space = AABB {
min: Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY),
max: Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY),
};
let mut node_count = 0;
let subtree_consistent =
self.is_consistent_subtree(0, 0, &space, 0, &mut node_count, shapes);
let is_connected = node_count == self.nodes.len();
subtree_consistent && is_connected
}
fn assert_consistent_subtree<Shape: BHShape>(
&self,
node_index: usize,
expected_parent_index: usize,
expected_outer_aabb: &AABB,
expected_depth: u32,
node_count: &mut usize,
shapes: &[Shape],
) {
*node_count += 1;
let node = &self.nodes[node_index];
let parent = node.parent();
assert_eq!(
expected_parent_index, parent,
"Wrong parent index. Expected: {}; Actual: {}",
expected_parent_index, parent
);
let depth = node.depth();
assert_eq!(
expected_depth, depth,
"Wrong depth. Expected: {}; Actual: {}",
expected_depth, depth
);
match *node {
BVHNode::Node {
child_l_index,
child_l_aabb,
child_r_index,
child_r_aabb,
..
} => {
assert!(
expected_outer_aabb.approx_contains_aabb_eps(&child_l_aabb, EPSILON),
"Left child lies outside the expected bounds.
\tBounds: {}
\tLeft child: {}",
expected_outer_aabb,
child_l_aabb
);
assert!(
expected_outer_aabb.approx_contains_aabb_eps(&child_r_aabb, EPSILON),
"Right child lies outside the expected bounds.
\tBounds: {}
\tRight child: {}",
expected_outer_aabb,
child_r_aabb
);
self.assert_consistent_subtree(
child_l_index,
node_index,
&child_l_aabb,
expected_depth + 1,
node_count,
shapes,
);
self.assert_consistent_subtree(
child_r_index,
node_index,
&child_r_aabb,
expected_depth + 1,
node_count,
shapes,
);
}
BVHNode::Leaf { shape_index, .. } => {
let shape_aabb = shapes[shape_index].aabb();
assert!(
expected_outer_aabb.approx_contains_aabb_eps(&shape_aabb, EPSILON),
"Shape's AABB lies outside the expected bounds.\n\tBounds: {}\n\tShape: {}",
expected_outer_aabb,
shape_aabb
);
}
}
}
pub fn assert_consistent<Shape: BHShape>(&self, shapes: &[Shape]) {
let space = AABB {
min: Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY),
max: Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY),
};
let mut node_count = 0;
self.assert_consistent_subtree(0, 0, &space, 0, &mut node_count, shapes);
assert_eq!(node_count, self.nodes.len(), "Detached subtree");
}
pub fn assert_tight_subtree(&self, node_index: usize, outer_aabb: &AABB) {
if let BVHNode::Node {
child_l_index,
child_l_aabb,
child_r_index,
child_r_aabb,
..
} = self.nodes[node_index]
{
let joint_aabb = child_l_aabb.join(&child_r_aabb);
assert!(joint_aabb.relative_eq(outer_aabb, EPSILON));
self.assert_tight_subtree(child_l_index, &child_l_aabb);
self.assert_tight_subtree(child_r_index, &child_r_aabb);
}
}
pub fn assert_tight(&self) {
if let BVHNode::Node {
child_l_aabb,
child_r_aabb,
..
} = self.nodes[0]
{
let joint_aabb = child_l_aabb.join(&child_r_aabb);
self.assert_tight_subtree(0, &joint_aabb);
}
}
}
impl BoundingHierarchy for BVH {
fn build<Shape: BHShape>(shapes: &mut [Shape]) -> BVH {
BVH::build(shapes)
}
fn traverse<'a, Shape: Bounded>(&'a self, ray: &Ray, shapes: &'a [Shape]) -> Vec<&Shape> {
self.traverse(ray, shapes)
}
fn pretty_print(&self) {
self.pretty_print();
}
}
#[cfg(test)]
mod tests {
use crate::bvh::{BVHNode, BVH};
use crate::testbase::{build_some_bh, traverse_some_bh};
#[test]
fn test_build_bvh() {
build_some_bh::<BVH>();
}
#[test]
fn test_traverse_bvh() {
traverse_some_bh::<BVH>();
}
#[test]
fn test_bvh_shape_indices() {
use std::collections::HashSet;
let (all_shapes, bh) = build_some_bh::<BVH>();
let expected_shapes: HashSet<_> = (0..all_shapes.len()).collect();
let mut found_shapes = HashSet::new();
for node in bh.nodes.iter() {
match *node {
BVHNode::Node { .. } => {
assert_eq!(node.shape_index(), None);
}
BVHNode::Leaf { .. } => {
found_shapes.insert(
node.shape_index()
.expect("getting a shape index from a leaf node"),
);
}
}
}
assert_eq!(expected_shapes, found_shapes);
}
}
#[cfg(all(feature = "bench", test))]
mod bench {
use crate::bvh::BVH;
use crate::testbase::{
build_1200_triangles_bh, build_120k_triangles_bh, build_12k_triangles_bh,
intersect_1200_triangles_bh, intersect_120k_triangles_bh, intersect_12k_triangles_bh,
intersect_bh, load_sponza_scene,
};
#[bench]
fn bench_build_1200_triangles_bvh(b: &mut ::test::Bencher) {
build_1200_triangles_bh::<BVH>(b);
}
#[bench]
fn bench_build_12k_triangles_bvh(b: &mut ::test::Bencher) {
build_12k_triangles_bh::<BVH>(b);
}
#[bench]
fn bench_build_120k_triangles_bvh(b: &mut ::test::Bencher) {
build_120k_triangles_bh::<BVH>(b);
}
#[bench]
fn bench_build_sponza_bvh(b: &mut ::test::Bencher) {
let (mut triangles, _) = load_sponza_scene();
b.iter(|| {
BVH::build(&mut triangles);
});
}
#[bench]
fn bench_intersect_1200_triangles_bvh(b: &mut ::test::Bencher) {
intersect_1200_triangles_bh::<BVH>(b);
}
#[bench]
fn bench_intersect_12k_triangles_bvh(b: &mut ::test::Bencher) {
intersect_12k_triangles_bh::<BVH>(b);
}
#[bench]
fn bench_intersect_120k_triangles_bvh(b: &mut ::test::Bencher) {
intersect_120k_triangles_bh::<BVH>(b);
}
#[bench]
fn bench_intersect_sponza_bvh(b: &mut ::test::Bencher) {
let (mut triangles, bounds) = load_sponza_scene();
let bvh = BVH::build(&mut triangles);
intersect_bh(&bvh, &triangles, &bounds, b)
}
}