From 5e8e6814a3643702a02f1b1a959993731cae3e1b Mon Sep 17 00:00:00 2001 From: Antonio Caggiano Date: Mon, 18 Nov 2024 21:20:30 +0100 Subject: [PATCH] bvh: Store primitives in BVH Move BVH primitives in structure, while using ranges in nodes. --- src/bvh/structure.rs | 211 +++++++++++++++++++++++++++++++------------ src/bvh/triangle.rs | 11 ++- src/mesh.rs | 22 +++++ src/scene.rs | 2 +- src/www/context.rs | 8 +- 5 files changed, 187 insertions(+), 67 deletions(-) diff --git a/src/bvh/structure.rs b/src/bvh/structure.rs index 63b624b..93ec361 100644 --- a/src/bvh/structure.rs +++ b/src/bvh/structure.rs @@ -2,6 +2,8 @@ // Author: Antonio Caggiano // SPDX-License-Identifier: MIT +use std::{marker::PhantomData, ops::Range}; + use crate::*; #[derive(Default)] @@ -74,6 +76,62 @@ impl AABB { } } +#[derive(Clone, Copy)] +pub struct BvhRange { + offset: u32, + count: u32, + phantom: PhantomData, +} + +impl Default for BvhRange { + fn default() -> Self { + Self::new(0, 0) + } +} + +impl BvhRange { + pub fn new(offset: u32, count: u32) -> Self { + Self { + offset, + count, + phantom: PhantomData, + } + } + + pub fn is_empty(&self) -> bool { + self.count == 0 + } + + pub fn len(&self) -> usize { + self.count as usize + } + + pub fn split_off(&mut self, offset: usize) -> Self { + let o = offset as u32; + let right_count = self.count - o; + self.count = o; + Self::new(self.offset + o, right_count) + } +} + +impl IntoIterator for BvhRange { + type Item = usize; + type IntoIter = Range; + + fn into_iter(self) -> Self::IntoIter { + self.offset as usize..(self.offset as usize + self.count as usize) + } +} + +impl IntoIterator for &BvhRange { + type Item = usize; + type IntoIter = Range; + + fn into_iter(self) -> Self::IntoIter { + self.offset as usize..(self.offset as usize + self.count as usize) + } +} + #[derive(Default)] pub struct BvhNode { bounds: AABB, @@ -81,17 +139,12 @@ pub struct BvhNode { left: Handle, right: Handle, - primitives: Vec, + primitives: BvhRange, } impl BvhNode { pub fn new() -> Self { - Self { - bounds: Default::default(), - left: Handle::NONE, - right: Handle::NONE, - primitives: vec![], - } + Self::default() } pub fn is_leaf(&self) -> bool { @@ -101,26 +154,34 @@ impl BvhNode { pub fn set_primitives( &mut self, model: &Model, - primitives: Vec, + primitives_range: BvhRange, + primitives: &mut [BvhPrimitive], max_depth: usize, nodes: &mut Pack, ) { let mut timer = Timer::new(); - self.set_primitives_recursive(model, primitives, max_depth, 0, nodes); + self.set_primitives_recursive(model, primitives_range, primitives, max_depth, 0, nodes); print_success!("BVH", "built in {:.2}ms", timer.get_delta().as_millis()); } /// Surface Area Heuristics: /// The cost of a split is proportional to the summed cost of intersecting the two /// resulting boxes, including the triangles they store. - fn evaluate_sah(&self, model: &Model, axis: Axis3, pos: f32) -> f32 { + fn evaluate_sah( + &self, + model: &Model, + axis: Axis3, + pos: f32, + primitives: &[BvhPrimitive], + ) -> f32 { // determine triangle counts and bounds for this split candidate let mut left_box = AABB::default(); let mut right_box = AABB::default(); let mut left_count = 0; let mut right_count = 0; - for pri in &self.primitives { + for pri_index in &self.primitives { + let pri = &primitives[pri_index]; let centroid = pri.centroid(model); if centroid[axis] < pos { left_count += 1; @@ -141,7 +202,11 @@ impl BvhNode { /// Finds the optimal split plane position and axis /// - Returns (split axis, split pos, split cost) - fn find_best_split_plane(&self, model: &Model) -> (Axis3, f32, f32) { + fn find_best_split_plane( + &self, + model: &Model, + primitives: &[BvhPrimitive], + ) -> (Axis3, f32, f32) { const ALL_AXIS: [Axis3; 3] = [Axis3::X, Axis3::Y, Axis3::Z]; let mut best_cost = f32::MAX; @@ -161,7 +226,7 @@ impl BvhNode { for i in 1..AREA_COUNT { let candidate_pos = bounds_min + i as f32 * scale; - let cost = self.evaluate_sah(model, axis, candidate_pos); + let cost = self.evaluate_sah(model, axis, candidate_pos, primitives); if cost < best_cost { best_cost = cost; best_axis = axis; @@ -180,19 +245,21 @@ impl BvhNode { fn set_primitives_recursive( &mut self, model: &Model, - primitives: Vec, + primitives_range: BvhRange, + primitives: &mut [BvhPrimitive], max_depth: usize, level: usize, nodes: &mut Pack, ) { assert!(!primitives.is_empty()); - self.primitives = primitives; + self.primitives = primitives_range; self.bounds.a = Point3::new(f32::MAX, f32::MAX, f32::MAX); self.bounds.b = Point3::new(f32::MIN, f32::MIN, f32::MIN); // Visit each vertex of the primitives to find the lowest and highest x, y, and z - for pri in self.primitives.iter() { + for pri_index in &self.primitives { + let pri = &primitives[pri_index]; self.bounds.a = self.bounds.a.min(&pri.min(model)); self.bounds.b = self.bounds.b.max(&pri.max(model)); } @@ -202,7 +269,7 @@ impl BvhNode { } // Surface Area Heuristics - let (split_axis, split_pos, split_cost) = self.find_best_split_plane(model); + let (split_axis, split_pos, split_cost) = self.find_best_split_plane(model, primitives); let no_split_cost = self.calculate_cost(); if split_cost > no_split_cost { @@ -213,11 +280,14 @@ impl BvhNode { let mut i = 0; let mut j = self.primitives.len(); while i < j { - let centroid = self.primitives[i].centroid(model); + let index = self.primitives.offset as usize + i; + + let centroid = primitives[index].centroid(model); if centroid[split_axis] < split_pos { i += 1; } else { - self.primitives.swap(i, j - 1); + let jndex = self.primitives.offset as usize + j; + primitives.swap(index, jndex - 1); j -= 1; } } @@ -226,17 +296,25 @@ impl BvhNode { let left_count = i; let right_count = self.primitives.len() - left_count; if left_count > 0 && right_count > 0 { - let right_triangles = self.primitives.split_off(left_count); - let left_triangles = self.primitives.split_off(0); + let right_primitives = self.primitives.split_off(left_count); + let left_primitives = self.primitives.split_off(0); // Create two nodes let mut left_child = BvhNode::new(); - left_child.set_primitives_recursive(model, left_triangles, max_depth, level + 1, nodes); + left_child.set_primitives_recursive( + model, + left_primitives, + primitives, + max_depth, + level + 1, + nodes, + ); let mut right_child = BvhNode::new(); right_child.set_primitives_recursive( model, - right_triangles, + right_primitives, + primitives, max_depth, level + 1, nodes, @@ -251,7 +329,7 @@ impl BvhNode { &'b self, model: &Model, ray: &Ray, - nodes: &'b Pack, + bvh: &'b Bvh, triangle_count: &mut usize, ) -> Option<(Hit, &'b BvhPrimitive)> { let d = self.bounds.intersects(ray); @@ -266,28 +344,29 @@ impl BvhNode { if self.is_leaf() { *triangle_count += self.primitives.len(); - for tri in &self.primitives { - if let Some(hit) = tri.intersects(model, ray) { + for pri_index in &self.primitives { + let pri = &bvh.primitives[pri_index]; + if let Some(hit) = pri.intersects(model, ray) { if hit.depth < depth { depth = hit.depth; - ret = Some((hit, tri)); + ret = Some((hit, pri)); } } } } else { - if let Some(left_node) = nodes.get(self.left) { - if let Some((hit, tri)) = left_node.intersects(model, ray, nodes, triangle_count) { + if let Some(left_node) = bvh.nodes.get(self.left) { + if let Some((hit, pri)) = left_node.intersects(model, ray, bvh, triangle_count) { if hit.depth < depth { depth = hit.depth; - ret = Some((hit, tri)); + ret = Some((hit, pri)); } } } - if let Some(right_node) = nodes.get(self.right) { - if let Some((hit, tri)) = right_node.intersects(model, ray, nodes, triangle_count) { + if let Some(right_node) = bvh.nodes.get(self.right) { + if let Some((hit, pri)) = right_node.intersects(model, ray, bvh, triangle_count) { if hit.depth < depth { - ret = Some((hit, tri)); + ret = Some((hit, pri)); } } } @@ -335,6 +414,8 @@ pub struct Bvh { pub root: BvhNode, pub nodes: Pack, pub triangle_count: usize, + + pub primitives: Vec, } impl Bvh { @@ -342,7 +423,7 @@ impl Bvh { BvhBuilder::new() } - pub fn new(model: &Model, primitives: Vec, max_depth: usize) -> Self { + pub fn new(model: &Model, mut primitives: Vec, max_depth: usize) -> Self { let mut nodes = Pack::new(); let mut root = BvhNode::new(); @@ -350,12 +431,14 @@ impl Bvh { Point3::new(f32::MAX, f32::MAX, f32::MAX), Point3::new(f32::MIN, f32::MIN, f32::MIN), ); - root.set_primitives(model, primitives, max_depth, &mut nodes); + let range = BvhRange::new(0, primitives.len() as u32); + root.set_primitives(model, range, &mut primitives, max_depth, &mut nodes); Self { root, nodes, triangle_count: 0, + primitives, } } @@ -368,11 +451,12 @@ impl Bvh { loop { if node.is_leaf() { - for tri in &node.primitives { - if let Some(hit) = tri.intersects(model, ray) { + for pri_index in &node.primitives { + let pri = &self.primitives[pri_index]; + if let Some(hit) = pri.intersects(model, ray) { if hit.depth < max_depth { max_depth = hit.depth; - ret_hit = Some((hit, tri)); + ret_hit = Some((hit, pri)); } } } @@ -413,8 +497,7 @@ impl Bvh { pub fn intersects(&self, model: &Model, ray: &Ray) -> Option<(Hit, &BvhPrimitive)> { let mut triangle_count = 0; - self.root - .intersects(model, ray, &self.nodes, &mut triangle_count) + self.root.intersects(model, ray, self, &mut triangle_count) } pub fn intersects_stats( @@ -423,8 +506,7 @@ impl Bvh { ray: &Ray, triangle_count: &mut usize, ) -> Option<(Hit, &BvhPrimitive)> { - self.root - .intersects(model, ray, &self.nodes, triangle_count) + self.root.intersects(model, ray, self, triangle_count) } } @@ -436,10 +518,16 @@ mod test { fn simple() { let mut model = Model::new(); let triangle_prim = Primitive::unit_triangle(); - let node = model.nodes.push(Node::default()); - let triangles = triangle_prim.primitives(node, Handle::none(), &model); + let mesh = Mesh::builder() + .primitives(vec![model.primitives.push(triangle_prim)]) + .build(); + let node = model + .nodes + .push(Node::builder().mesh(model.meshes.push(mesh)).build()); + model.root.children.push(node); + let primitives = model.collect(); - let bvh = Bvh::builder().primitives(triangles).build(&model); + let bvh = Bvh::builder().primitives(primitives).build(&model); assert!(bvh.nodes.is_empty()); assert!(!bvh.root.left.valid()); assert!(!bvh.root.right.valid()); @@ -450,21 +538,26 @@ mod test { fn two_children() { let mut model = Model::new(); - let left_triangle_prim = Primitive::builder() - .vertices(vec![ - Vertex::new(-4.0, 0.0, 0.0), - Vertex::new(-2.0, 0.0, 0.0), - Vertex::new(-3.0, 0.3, 0.0), - ]) - .indices(vec![0, 1, 2]) - .build(); - let right_triangle_prim = Primitive::unit_triangle(); + let left_triangle_prim = model.primitives.push( + Primitive::builder() + .vertices(vec![ + Vertex::new(-4.0, 0.0, 0.0), + Vertex::new(-2.0, 0.0, 0.0), + Vertex::new(-3.0, 0.3, 0.0), + ]) + .indices(vec![0, 1, 2]) + .build(), + ); + let right_triangle_prim = model.primitives.push(Primitive::unit_triangle()); - let node = model.nodes.push(Node::default()); - let mut left_primitives = left_triangle_prim.primitives(node, Handle::none(), &model); - let mut right_primitives = right_triangle_prim.primitives(node, Handle::none(), &model); - left_primitives.append(&mut right_primitives); - let primitives = left_primitives; + let mesh = Mesh::builder() + .primitives(vec![left_triangle_prim, right_triangle_prim]) + .build(); + let node = model + .nodes + .push(Node::builder().mesh(model.meshes.push(mesh)).build()); + model.root.children.push(node); + let primitives = model.collect(); let bvh = Bvh::builder().primitives(primitives).build(&model); assert!(!bvh.nodes.is_empty()); diff --git a/src/bvh/triangle.rs b/src/bvh/triangle.rs index d29c943..bbfe7e2 100644 --- a/src/bvh/triangle.rs +++ b/src/bvh/triangle.rs @@ -156,10 +156,13 @@ mod test { #[test] fn intersect() { let mut model = Model::new(); - let material = model.materials.push(Material::new()); - let triangle_prim = Primitive::unit_triangle(); - let node = model.nodes.push(Node::default()); - let triangles = triangle_prim.primitives(node, material, &model); + let triangle_prim = model.primitives.push(Primitive::unit_triangle()); + let mesh = model + .meshes + .push(Mesh::builder().primitives(vec![triangle_prim]).build()); + let node = model.nodes.push(Node::builder().mesh(mesh).build()); + model.root.children.push(node); + let triangles = model.collect(); let triangle_ref = &triangles[0]; let ray = Ray::new(Point3::new(0.0, 0.0, 1.0), Vec3::new(0.0, 0.0, -1.0)); diff --git a/src/mesh.rs b/src/mesh.rs index 8044ebe..0ba9a99 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -125,12 +125,34 @@ impl Primitive { } } +#[derive(Default)] +pub struct MeshBuilder { + pub primitives: Vec>, +} + +impl MeshBuilder { + pub fn primitives(mut self, primitives: Vec>) -> Self { + self.primitives = primitives; + self + } + + pub fn build(self) -> Mesh { + Mesh { + primitives: self.primitives, + } + } +} + #[derive(Default)] pub struct Mesh { pub primitives: Vec>, } impl Mesh { + pub fn builder() -> MeshBuilder { + MeshBuilder::default() + } + pub fn new(primitives: Vec>) -> Self { Self { primitives } } diff --git a/src/scene.rs b/src/scene.rs index eaacd2b..6ab2d8b 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -26,7 +26,7 @@ impl Default for Scene { impl Scene { /// This can be used for default values which are not defined in any other model in the scene - fn create_default_model() -> Model { + pub fn create_default_model() -> Model { let mut model = Model::new(); // Add 1 camera diff --git a/src/www/context.rs b/src/www/context.rs index 9926f29..ab32681 100644 --- a/src/www/context.rs +++ b/src/www/context.rs @@ -141,7 +141,8 @@ impl Context { tweak_box_scene(&mut model); let mut scene = Scene::new(); - scene.models.push(model); + scene.push(model); + scene.push(Scene::create_default_model()); let data = Clamped(image.bytes()); @@ -160,9 +161,10 @@ impl Context { pub fn rotate_box(&mut self) { let delta = self.timer.get_delta().as_secs_f32(); let angle = FRAC_PI_8 * delta; - self.scene.models[0] + self.scene + .model .nodes - .get_mut(1.into()) + .get_mut(2.into()) .unwrap() .trs .rotation *= Quat::new(0.0, angle.sin(), 0.0, angle.cos());