use gizmo_math::{Aabb, Vec3, Vec3A};
#[derive(Debug, Clone, PartialEq)]
pub struct BvhNode {
pub aabb: Aabb,
pub left_child: i32,
pub right_child: i32,
pub first_tri_index: u32,
pub tri_count: u32,
}
impl BvhNode {
pub fn is_leaf(&self) -> bool {
self.left_child == -1
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct BvhTree {
pub nodes: Vec<BvhNode>,
}
impl BvhTree {
pub fn build(vertices: &[Vec3], indices: &mut [u32]) -> Result<Self, crate::error::GizmoError> {
if indices.is_empty() {
return Ok(Self::default());
}
for &idx in indices.iter() {
if idx as usize >= vertices.len() {
return Err(crate::error::GizmoError::BvhBuildFailed);
}
}
let tri_count = (indices.len() / 3) as u32;
let mut nodes = Vec::with_capacity((tri_count * 2) as usize);
nodes.push(BvhNode {
aabb: Aabb::empty(),
left_child: -1,
right_child: -1,
first_tri_index: 0,
tri_count,
});
let mut tree = Self { nodes };
tree.update_node_bounds(0, vertices, indices);
tree.subdivide(0, vertices, indices, 0);
Ok(tree)
}
fn update_node_bounds(&mut self, node_idx: usize, vertices: &[Vec3], indices: &[u32]) {
let node = &mut self.nodes[node_idx];
let mut aabb = Aabb::empty();
let start = (node.first_tri_index * 3) as usize;
let end = start + (node.tri_count * 3) as usize;
for i in start..end {
aabb.extend(Vec3A::from(vertices[indices[i] as usize]));
}
node.aabb = aabb;
}
fn subdivide(&mut self, node_idx: usize, vertices: &[Vec3], indices: &mut [u32], depth: u32) {
let first_tri_index;
let tri_count;
let aabb;
{
let node = &self.nodes[node_idx];
first_tri_index = node.first_tri_index;
tri_count = node.tri_count;
aabb = node.aabb;
}
if tri_count <= 2 || depth > 64 {
return;
}
let mut best_axis = -1;
let mut best_split_pos = 0.0;
let mut best_cost = f32::MAX;
const BINS: usize = 8;
let start = (first_tri_index * 3) as usize;
let end = start + (tri_count * 3) as usize;
let parent_area = aabb.surface_area();
const C_TRAV: f32 = 1.0;
const C_ISECT: f32 = 1.0;
let current_cost = (tri_count as f32) * parent_area * C_ISECT;
for axis in 0..3 {
let mut bin_count = [0; BINS];
let mut bin_bounds = [Aabb::empty(); BINS];
let mut min_centroid = f32::MAX;
let mut max_centroid = f32::MIN;
for i in (start..end).step_by(3) {
let v0 = vertices[indices[i] as usize];
let v1 = vertices[indices[i + 1] as usize];
let v2 = vertices[indices[i + 2] as usize];
let centroid = (v0[axis] + v1[axis] + v2[axis]) / 3.0;
min_centroid = min_centroid.min(centroid);
max_centroid = max_centroid.max(centroid);
}
if min_centroid == max_centroid {
continue;
}
let scale = BINS as f32 / (max_centroid - min_centroid);
for i in (start..end).step_by(3) {
let v0 = vertices[indices[i] as usize];
let v1 = vertices[indices[i + 1] as usize];
let v2 = vertices[indices[i + 2] as usize];
let centroid = (v0[axis] + v1[axis] + v2[axis]) / 3.0;
let mut bin_idx = ((centroid - min_centroid) * scale) as usize;
if bin_idx == BINS {
bin_idx = BINS - 1;
}
bin_count[bin_idx] += 1;
bin_bounds[bin_idx].extend(Vec3A::from(v0));
bin_bounds[bin_idx].extend(Vec3A::from(v1));
bin_bounds[bin_idx].extend(Vec3A::from(v2));
}
let mut left_area = [0.0; BINS - 1];
let mut left_count = [0; BINS - 1];
let mut left_box = Aabb::empty();
let mut left_sum = 0;
for i in 0..BINS - 1 {
left_sum += bin_count[i];
left_count[i] = left_sum;
left_box = left_box.merge(bin_bounds[i]);
left_area[i] = left_box.surface_area();
}
let mut right_box = Aabb::empty();
let mut right_sum = 0;
for i in (1..BINS).rev() {
right_sum += bin_count[i];
right_box = right_box.merge(bin_bounds[i]);
let right_area = right_box.surface_area();
let cost = C_TRAV * parent_area
+ (left_count[i - 1] as f32 * left_area[i - 1] + right_sum as f32 * right_area)
* C_ISECT;
if cost < best_cost {
best_cost = cost;
best_axis = axis as i32;
best_split_pos =
min_centroid + (i as f32) * (max_centroid - min_centroid) / (BINS as f32);
}
}
}
if best_axis == -1 || best_cost >= current_cost {
return;
}
let best_axis = best_axis as usize;
let mut i = first_tri_index as usize;
let mut j = (first_tri_index + tri_count - 1) as usize;
while i <= j {
let i_idx = i * 3;
let v0 = vertices[indices[i_idx] as usize];
let v1 = vertices[indices[i_idx + 1] as usize];
let v2 = vertices[indices[i_idx + 2] as usize];
let centroid = (v0[best_axis] + v1[best_axis] + v2[best_axis]) / 3.0;
if centroid < best_split_pos {
i += 1;
} else {
let j_idx = j * 3;
indices.swap(i_idx, j_idx);
indices.swap(i_idx + 1, j_idx + 1);
indices.swap(i_idx + 2, j_idx + 2);
if j == 0 {
break;
}
j -= 1;
}
}
let left_count = (i as u32) - first_tri_index;
if left_count == 0 || left_count == tri_count {
return;
}
let left_child_idx = self.nodes.len();
let right_child_idx = left_child_idx + 1;
self.nodes.push(BvhNode {
aabb: Aabb::empty(),
left_child: -1,
right_child: -1,
first_tri_index,
tri_count: left_count,
});
self.nodes.push(BvhNode {
aabb: Aabb::empty(),
left_child: -1,
right_child: -1,
first_tri_index: i as u32,
tri_count: tri_count - left_count,
});
self.nodes[node_idx].left_child = left_child_idx as i32;
self.nodes[node_idx].right_child = right_child_idx as i32;
self.nodes[node_idx].tri_count = 0;
self.update_node_bounds(left_child_idx, vertices, indices);
self.update_node_bounds(right_child_idx, vertices, indices);
self.subdivide(left_child_idx, vertices, indices, depth + 1);
self.subdivide(right_child_idx, vertices, indices, depth + 1);
}
}