From 38a2bd260a2a4a917a0fd0fcad74b05a9beb24b2 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Tue, 15 Jun 2021 17:12:54 -0400 Subject: [PATCH 01/37] conversion to 64bit, implemented add/remove of a node, add sphere, capsule and aabb intersection tests. Expose C FFI --- Cargo.toml | 4 + README.md | 6 +- src/aabb.rs | 88 ++++++-- src/axis.rs | 30 +-- src/bounding_hierarchy.rs | 12 +- src/bvh/bvh_impl.rs | 459 ++++++++++++++++++++++++++++++++++++-- src/bvh/iter.rs | 12 +- src/bvh/optimization.rs | 18 +- src/flat_bvh.rs | 11 +- src/lib.rs | 336 +++++++++++++++++++++++++++- src/main.rs | 50 +++++ src/ray.rs | 87 ++++---- src/shapes.rs | 294 ++++++++++++++++++++++++ src/testbase.rs | 32 +-- 14 files changed, 1292 insertions(+), 147 deletions(-) create mode 100644 src/main.rs create mode 100644 src/shapes.rs diff --git a/Cargo.toml b/Cargo.toml index d0c4299..e3ab6b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,10 @@ documentation = "https://docs.rs/crate/bvh" keywords = ["bvh", "bounding", "volume", "sah", "aabb"] license = "MIT" +[lib] +name="bvh_f64" +crate-type = ["rlib", "dylib"] + [dependencies] approx = "0.4" rand = "0.8" diff --git a/README.md b/README.md index 77db555..f9e376c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ let ray = Ray::new(origin, direction); struct Sphere { position: Point3, - radius: f32, + radius: f64, } impl Bounded for Sphere { @@ -47,8 +47,8 @@ impl Bounded for Sphere { let mut spheres = Vec::new(); for i in 0..1000u32 { - let position = Point3::new(i as f32, i as f32, i as f32); - let radius = (i % 10) as f32 + 1.0; + let position = Point3::new(i as f64, i as f64, i as f64); + let radius = (i % 10) as f64 + 1.0; spheres.push(Sphere { position: position, radius: radius, diff --git a/src/aabb.rs b/src/aabb.rs index f8f4cb2..028c417 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -1,8 +1,8 @@ //! Axis Aligned Bounding Boxes. -use std::f32; use std::fmt; use std::ops::Index; +use crate::bounding_hierarchy::{IntersectionTest}; use crate::{Point3, Vector3}; @@ -12,16 +12,16 @@ use crate::axis::Axis; #[derive(Debug, Copy, Clone)] #[allow(clippy::upper_case_acronyms)] pub struct AABB { - /// Minimum coordinates + /// minimum coordinates pub min: Point3, - /// Maximum coordinates + /// maximum coordinates pub max: Point3, } impl fmt::Display for AABB { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Min bound: {}; Max bound: {}", self.min, self.max) + write!(f, "min bound: {}; max bound: {}", self.min, self.max) } } @@ -106,8 +106,8 @@ impl AABB { /// pub fn empty() -> AABB { AABB { - min: Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY), - max: Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), + min: Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), + max: Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), } } @@ -158,7 +158,7 @@ impl AABB { /// [`AABB`]: struct.AABB.html /// [`Point3`]: glam::Vec3 /// - pub fn approx_contains_eps(&self, p: &Point3, epsilon: f32) -> bool { + pub fn approx_contains_eps(&self, p: &Point3, epsilon: f64) -> bool { (p.x - self.min.x) > -epsilon && (p.x - self.max.x) < epsilon && (p.y - self.min.y) > -epsilon @@ -185,7 +185,7 @@ impl AABB { /// ``` /// /// [`AABB`]: struct.AABB.html - pub fn approx_contains_aabb_eps(&self, other: &AABB, epsilon: f32) -> bool { + pub fn approx_contains_aabb_eps(&self, other: &AABB, epsilon: f64) -> bool { self.approx_contains_eps(&other.min, epsilon) && self.approx_contains_eps(&other.max, epsilon) } @@ -208,13 +208,13 @@ impl AABB { /// ``` /// /// [`AABB`]: struct.AABB.html - pub fn relative_eq(&self, other: &AABB, epsilon: f32) -> bool { - f32::abs(self.min.x - other.min.x) < epsilon - && f32::abs(self.min.y - other.min.y) < epsilon - && f32::abs(self.min.z - other.min.z) < epsilon - && f32::abs(self.max.x - other.max.x) < epsilon - && f32::abs(self.max.y - other.max.y) < epsilon - && f32::abs(self.max.z - other.max.z) < epsilon + pub fn relative_eq(&self, other: &AABB, epsilon: f64) -> bool { + f64::abs(self.min.x - other.min.x) < epsilon + && f64::abs(self.min.y - other.min.y) < epsilon + && f64::abs(self.min.z - other.min.z) < epsilon + && f64::abs(self.max.x - other.max.x) < epsilon + && f64::abs(self.max.y - other.max.y) < epsilon + && f64::abs(self.max.z - other.max.z) < epsilon } /// Returns a new minimal [`AABB`] which contains both this [`AABB`] and `other`. @@ -263,7 +263,7 @@ impl AABB { ) } - /// Mutable version of [`AABB::join`]. + /// mutable version of [`AABB::join`]. /// /// # Examples /// ``` @@ -352,7 +352,7 @@ impl AABB { ) } - /// Mutable version of [`AABB::grow`]. + /// mutable version of [`AABB::grow`]. /// /// # Examples /// ``` @@ -505,7 +505,7 @@ impl AABB { /// /// [`AABB`]: struct.AABB.html /// - pub fn surface_area(&self) -> f32 { + pub fn surface_area(&self) -> f64 { let size = self.size(); 2.0 * (size.x * size.y + size.x * size.z + size.y * size.z) } @@ -527,7 +527,7 @@ impl AABB { /// /// [`AABB`]: struct.AABB.html /// - pub fn volume(&self) -> f32 { + pub fn volume(&self) -> f64 { let size = self.size(); size.x * size.y * size.z } @@ -560,6 +560,16 @@ impl AABB { Axis::Z } } + + pub fn closest_point(&self, point: Point3) -> Point3 { + point.clamp(self.min, self.max) + } +} + +impl IntersectionTest for AABB { + fn intersects_aabb(&self, aabb: &AABB) -> bool { + !(self.max.x < aabb.min.x) && !(self.min.x > aabb.max.x) && (!(self.max.y < aabb.min.y) && !(self.min.y > aabb.max.y)) && (!(self.max.z < aabb.min.z) && !(self.min.z > aabb.max.z)) + } } /// Default instance for [`AABB`]s. Returns an [`AABB`] which is [`empty()`]. @@ -573,7 +583,7 @@ impl Default for AABB { } } -/// Make [`AABB`]s indexable. `aabb[0]` gives a reference to the minimum bound. +/// make [`AABB`]s indexable. `aabb[0]` gives a reference to the minimum bound. /// All other indices return a reference to the maximum bound. /// /// # Examples @@ -657,6 +667,7 @@ mod tests { use crate::testbase::{tuple_to_point, tuple_to_vector, tuplevec_large_strategy, TupleVec}; use crate::EPSILON; use crate::{Point3, Vector3}; + use crate::bounding_hierarchy::IntersectionTest; use float_eq::assert_float_eq; use proptest::prelude::*; @@ -702,6 +713,40 @@ mod tests { assert!(aabb.contains(&aabb.center())); } + // Test AABB intersection method. + #[test] + fn test_aabb_intersects(a: TupleVec, b: TupleVec) { + // Define two points which will be the corners of the `AABB` + let p1 = tuple_to_point(&a); + let p2 = tuple_to_point(&b); + + // Span the `AABB` + let aabb = AABB::empty().grow(&p1).join_bounded(&p2); + + // Get its size and center + let size = aabb.size(); + let size_half = size / 2.0; + let center = aabb.center(); + + // Compute the min and the max corners of the AABB by hand + let inside_ppp = center + size_half * 0.9; + let inside_mmm = center - size_half * 0.9; + + // Generate two points which are outside the AABB + let outside_ppp = inside_ppp + size_half * 1.1; + let outside_mmm = inside_mmm - size_half * 1.1; + let disjoint_mmm = outside_ppp + size_half * 0.1; + let disjoint_ppp = outside_ppp + size_half; + let small_aabb = AABB::empty().grow(&inside_ppp).grow(&inside_mmm); + let big_aabb = AABB::empty().grow(&outside_ppp).grow(&outside_mmm); + let dis_aabb = AABB::empty().grow(&disjoint_ppp).grow(&disjoint_mmm); + + assert!(aabb.intersects_aabb(&small_aabb) + && aabb.intersects_aabb(&big_aabb) + && big_aabb.intersects_aabb(&small_aabb) + && !dis_aabb.intersects_aabb(&big_aabb)); + } + // Test whether the joint of two point-sets contains all the points. #[test] fn test_join_two_aabbs(a: (TupleVec, TupleVec, TupleVec, TupleVec, TupleVec), @@ -776,7 +821,8 @@ mod tests { // Compute and compare the surface area of an AABB by hand. #[test] - fn test_surface_area_cube(pos: TupleVec, size in EPSILON..10e30_f32) { + fn test_surface_area_cube(pos: TupleVec, size in EPSILON..10e30_f64) { + // Generate some non-empty AABB let pos = tuple_to_point(&pos); let size_vec = Vector3::new(size, size, size); diff --git a/src/axis.rs b/src/axis.rs index f90f899..a63a518 100644 --- a/src/axis.rs +++ b/src/axis.rs @@ -63,19 +63,19 @@ impl Display for Axis { } /// Make slices indexable by `Axis`. -impl Index for [f32] { - type Output = f32; +impl Index for [f64] { + type Output = f64; - fn index(&self, axis: Axis) -> &f32 { + fn index(&self, axis: Axis) -> &f64 { &self[axis as usize] } } /// Make `Point3` indexable by `Axis`. impl Index for Point3 { - type Output = f32; + type Output = f64; - fn index(&self, axis: Axis) -> &f32 { + fn index(&self, axis: Axis) -> &f64 { match axis { Axis::X => &self.x, Axis::Y => &self.y, @@ -86,9 +86,9 @@ impl Index for Point3 { /// Make `Vector3` indexable by `Axis`. impl Index for MyType { - type Output = f32; + type Output = f64; - fn index(&self, axis: Axis) -> &f32 { + fn index(&self, axis: Axis) -> &f64 { match axis { Axis::X => &self.0.x, Axis::Y => &self.0.y, @@ -98,15 +98,15 @@ impl Index for MyType { } /// Make slices mutably accessible by `Axis`. -impl IndexMut for [f32] { - fn index_mut(&mut self, axis: Axis) -> &mut f32 { +impl IndexMut for [f64] { + fn index_mut(&mut self, axis: Axis) -> &mut f64 { &mut self[axis as usize] } } /// Make `Point3` mutably accessible by `Axis`. impl IndexMut for Point3 { - fn index_mut(&mut self, axis: Axis) -> &mut f32 { + fn index_mut(&mut self, axis: Axis) -> &mut f64 { match axis { Axis::X => &mut self.x, Axis::Y => &mut self.y, @@ -117,7 +117,7 @@ impl IndexMut for Point3 { /// Make `Vector3` mutably accessible by `Axis`. impl IndexMut for MyType { - fn index_mut(&mut self, axis: Axis) -> &mut f32 { + fn index_mut(&mut self, axis: Axis) -> &mut f64 { match axis { Axis::X => &mut self.0.x, Axis::Y => &mut self.0.y, @@ -134,22 +134,22 @@ mod test { proptest! { // Test whether accessing arrays by index is the same as accessing them by `Axis`. #[test] - fn test_index_by_axis(tpl: (f32, f32, f32)) { + fn test_index_by_axis(tpl: (f64, f64, f64)) { let a = [tpl.0, tpl.1, tpl.2]; - assert!((a[0] - a[Axis::X]).abs() < f32::EPSILON && (a[1] - a[Axis::Y]).abs() < f32::EPSILON && (a[2] - a[Axis::Z]).abs() < f32::EPSILON); + assert!((a[0] - a[Axis::X]).abs() < f64::EPSILON && (a[1] - a[Axis::Y]).abs() < f64::EPSILON && (a[2] - a[Axis::Z]).abs() < f64::EPSILON); } // Test whether arrays can be mutably set, by indexing via `Axis`. #[test] - fn test_set_by_axis(tpl: (f32, f32, f32)) { + fn test_set_by_axis(tpl: (f64, f64, f64)) { let mut a = [0.0, 0.0, 0.0]; a[Axis::X] = tpl.0; a[Axis::Y] = tpl.1; a[Axis::Z] = tpl.2; - assert!((a[0] - tpl.0).abs() < f32::EPSILON && (a[1] - tpl.1).abs() < f32::EPSILON && (a[2] - tpl.2).abs() < f32::EPSILON); + assert!((a[0] - tpl.0).abs() < f64::EPSILON && (a[1] - tpl.1).abs() < f64::EPSILON && (a[2] - tpl.2).abs() < f64::EPSILON); } } } diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index 6f26c3d..db6039e 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -1,7 +1,7 @@ //! This module defines the `BoundingHierarchy` trait. use crate::aabb::Bounded; -use crate::ray::Ray; +use crate::aabb::AABB; /// Describes a shape as referenced by a [`BoundingHierarchy`] leaf node. /// Knows the index of the node in the [`BoundingHierarchy`] it is in. @@ -72,7 +72,7 @@ pub trait BoundingHierarchy { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f32, i as f32, i as f32); + /// # let position = Point3::new(i as f64, i as f64, i as f64); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -145,7 +145,7 @@ pub trait BoundingHierarchy { /// # fn create_bvh() -> (BVH, Vec) { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f32, i as f32, i as f32); + /// # let position = Point3::new(i as f64, i as f64, i as f64); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # let bvh = BVH::build(&mut shapes); @@ -163,7 +163,7 @@ pub trait BoundingHierarchy { /// [`BoundingHierarchy`]: trait.BoundingHierarchy.html /// [`AABB`]: ../aabb/struct.AABB.html /// - fn traverse<'a, Shape: BHShape>(&'a self, ray: &Ray, shapes: &'a [Shape]) -> Vec<&Shape>; + fn traverse<'a, Shape: BHShape>(&'a self, test: &impl IntersectionTest, shapes: &'a [Shape]) -> Vec<&Shape>; /// Prints the [`BoundingHierarchy`] in a tree-like visualization. /// @@ -171,3 +171,7 @@ pub trait BoundingHierarchy { /// fn pretty_print(&self) {} } + +pub trait IntersectionTest { + fn intersects_aabb(&self, aabb: &AABB) -> bool; +} diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 54bdf70..04c0b7e 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -5,13 +5,11 @@ //! use crate::aabb::{Bounded, AABB}; -use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; +use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; 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; use std::iter::repeat; /// The [`BVHNode`] enum that describes a node in a [`BVH`]. @@ -134,6 +132,13 @@ impl BVHNode { _ => panic!("Tried to get the left child of a leaf node."), } } + /// Returns the index of the left child node. + pub fn child_l_mut(&mut self) -> &mut usize { + match *self { + BVHNode::Node { ref mut child_l_index, .. } => child_l_index, + _ => panic!("Tried to get the left child of a leaf node."), + } + } /// Returns the `AABB` of the right child node. pub fn child_l_aabb(&self) -> AABB { @@ -162,6 +167,14 @@ impl BVHNode { } } + /// Returns the index of the right child node. + pub fn child_r_mut(&mut self) -> &mut usize { + match *self { + BVHNode::Node { ref mut child_r_index, .. } => child_r_index, + _ => panic!("Tried to get the right child of a leaf node."), + } + } + /// Returns the `AABB` of the right child node. pub fn child_r_aabb(&self) -> AABB { match *self { @@ -188,6 +201,13 @@ impl BVHNode { } } + /// Returns the depth of the node. The root node has depth `0`. + pub fn depth_mut(&mut self) -> &mut u32 { + match *self { + BVHNode::Node { ref mut depth, .. } | BVHNode::Leaf { ref mut depth, .. } => depth, + } + } + /// Gets the `AABB` for a `BVHNode`. /// Returns the shape's `AABB` for leaves, and the joined `AABB` of /// the two children's `AABB`s for non-leaves. @@ -211,6 +231,15 @@ impl BVHNode { } } + /// Returns the index of the shape contained within the node if is a leaf, + /// or `None` if it is an interior node. + pub fn shape_index_mut(&mut self) -> Option<&mut usize> { + match *self { + BVHNode::Leaf { ref mut shape_index, .. } => Some(shape_index), + _ => None, + } + } + /// The build function sometimes needs to add nodes while their data is not available yet. /// A dummy cerated by this function serves the purpose of being changed later on. fn create_dummy() -> BVHNode { @@ -307,7 +336,7 @@ impl BVHNode { (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; // Convert that to the actual `Bucket` number. - let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f32 - 0.01)) as usize; + let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; // Extend the selected `Bucket` and add the index to the actual bucket. buckets[bucket_num].add_aabb(&shape_aabb); @@ -316,7 +345,7 @@ impl BVHNode { // Compute the costs for each configuration and select the best configuration. let mut min_bucket = 0; - let mut min_cost = f32::INFINITY; + let mut min_cost = f64::INFINITY; let mut child_l_aabb = AABB::empty(); let mut child_r_aabb = AABB::empty(); for i in 0..(NUM_BUCKETS - 1) { @@ -324,8 +353,8 @@ impl BVHNode { 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()) + let cost = (child_l.size as f64 * child_l.aabb.surface_area() + + child_r.size as f64 * child_r.aabb.surface_area()) / aabb_bounds.surface_area(); if cost < min_cost { min_bucket = i; @@ -363,6 +392,7 @@ impl BVHNode { node_index } + /// Traverses the [`BVH`] recursively and returns all shapes whose [`AABB`] is /// intersected by the given [`Ray`]. /// @@ -373,7 +403,7 @@ impl BVHNode { pub fn traverse_recursive( nodes: &[BVHNode], node_index: usize, - ray: &Ray, + ray: &impl IntersectionTest, indices: &mut Vec, ) { match nodes[node_index] { @@ -430,7 +460,7 @@ impl BVH { /// [`BVH`]: struct.BVH.html /// [`AABB`]: ../aabb/struct.AABB.html /// - pub fn traverse<'a, Shape: Bounded>(&'a self, ray: &Ray, shapes: &'a [Shape]) -> Vec<&Shape> { + pub fn traverse<'a, Shape: Bounded>(&'a self, ray: &impl IntersectionTest, shapes: &'a [Shape]) -> Vec<&Shape> { let mut indices = Vec::new(); BVHNode::traverse_recursive(&self.nodes, 0, ray, &mut indices); indices @@ -447,12 +477,286 @@ impl BVH { /// pub fn traverse_iterator<'a, Shape: Bounded>( &'a self, - ray: &'a Ray, + test: &'a impl IntersectionTest, shapes: &'a [Shape], ) -> BVHTraverseIterator { - BVHTraverseIterator::new(self, ray, shapes) + BVHTraverseIterator::new(self, test, shapes) + } + + pub fn add_node( + &mut self, + shapes: &mut [T], + new_shape_index: usize + ) { + let mut i = 0; + let new_shape = &shapes[new_shape_index]; + let shape_aabb = new_shape.aabb(); + let shape_sa = shape_aabb.surface_area(); + loop { + match self.nodes[i] { + BVHNode::Node { + child_l_aabb, + child_l_index, + child_r_aabb, + child_r_index, + depth, + parent_index + } => { + let left_expand = child_l_aabb.join(&shape_aabb); + + let right_expand = child_r_aabb.join(&shape_aabb); + + let send_left = child_r_aabb.surface_area() + left_expand.surface_area(); + let send_right = child_r_aabb.surface_area() + right_expand.surface_area(); + let merged_aabb = child_r_aabb.join(&child_l_aabb); + let merged = merged_aabb.surface_area() + shape_sa; + + // merge is more expensive only do when it's significantly better + let merge_discount = 0.3; + + // compared SA of the options + if merged < send_left.min(send_right) * merge_discount + { + // Merge left and right trees + let l_index = self.nodes.len(); + let new_left = BVHNode::Leaf { + depth: depth + 1, + parent_index: i, + shape_index: new_shape_index + }; + shapes[new_shape_index].set_bh_node_index(l_index); + self.nodes.push(new_left); + + let r_index = self.nodes.len(); + let new_right = BVHNode::Node { + child_l_aabb: child_l_aabb, + child_l_index, + child_r_aabb: child_r_aabb.clone(), + child_r_index, + depth: depth + 1, + parent_index: i + }; + self.nodes.push(new_right); + + self.nodes[i] = BVHNode::Node { + child_l_aabb: shape_aabb, + child_l_index: l_index, + child_r_aabb: merged_aabb, + child_r_index: r_index, + depth, + parent_index + }; + self.fix_depth(l_index, depth + 1); + self.fix_depth(r_index, depth + 1); + return; + } else if send_left < send_right { + // send new box down left side + if i == child_l_index + { + panic!("broken loop"); + } + let child_l_aabb = left_expand; + self.nodes[i] = BVHNode::Node { + child_l_aabb, + child_l_index, + child_r_aabb, + child_r_index, + depth, + parent_index + }; + i = child_l_index; + } else { + // send new box down right + if i == child_r_index + { + panic!("broken loop"); + } + let child_r_aabb = right_expand; + self.nodes[i] = BVHNode::Node { + child_l_aabb, + child_l_index, + child_r_aabb, + child_r_index, + depth, + parent_index + }; + i = child_r_index; + } + } + BVHNode::Leaf { shape_index, parent_index, depth} => { + // Split leaf into 2 nodes and insert the new box + let l_index = self.nodes.len(); + let new_left = BVHNode::Leaf { + depth: depth + 1, + parent_index: i, + shape_index: new_shape_index + }; + shapes[new_shape_index].set_bh_node_index(l_index); + self.nodes.push(new_left); + + let child_r_aabb = shapes[shape_index].aabb(); + let child_r_index = self.nodes.len(); + let new_right = BVHNode::Leaf { + depth: depth + 1, + parent_index: i, + shape_index: shape_index + }; + shapes[shape_index].set_bh_node_index(child_r_index); + self.nodes.push(new_right); + + let new_node = BVHNode::Node { + child_l_aabb: shape_aabb, + child_l_index: l_index, + child_r_aabb, + child_r_index, + depth, + parent_index + }; + self.nodes[i] = new_node; + return; + } + } + } + } + + pub fn remove_node( + &mut self, + shapes: &mut [T], + deleted_shape_index: usize + ) { + if shapes.len() < 2 + { + panic!("can't remove a node from a bvh with only one node"); + } + let bad_shape = &shapes[deleted_shape_index]; + + // to remove a node, delete it from the tree, remove the parent and replace it with the sibling + // swap the node being removed to the end of the slice and adjust the index of the node that was removed + // update the removed nodes index + // swap the shape to the end and update the node to still point at the right shape + let dead_node_index = bad_shape.bh_node_index(); + + println!("delete_i={}", dead_node_index); + + let dead_node = self.nodes[dead_node_index]; + + let parent_index = dead_node.parent(); + println!("parent_i={}", parent_index); + let gp_index = self.nodes[parent_index].parent(); + println!("{}->{}->{}", gp_index, parent_index, dead_node_index); + let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r() } else { self.nodes[parent_index].child_l() }; + let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r_aabb() } else { self.nodes[parent_index].child_l_aabb() }; + // TODO: fix potential issue leaving empty spot in self.nodes + // the node swapped to sibling_index should probably be swapped to the end + // of the vector and the vector truncated + if parent_index == gp_index { + println!("gp == parent {}", parent_index); + self.nodes.swap(parent_index, sibling_index); + match self.nodes[parent_index].shape_index() { + Some(index) => { + shapes[index].set_bh_node_index(parent_index); + }, + _ => {} + } + + } else { + let box_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_aabb_mut() } else { self.nodes[gp_index].child_r_aabb_mut() }; + println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); + *box_to_change = sibling_box; + let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_mut() } else { self.nodes[gp_index].child_r_mut() }; + println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); + *ref_to_change = sibling_index; + *self.nodes[sibling_index].parent_mut() = gp_index; + let new_depth = self.nodes[sibling_index].depth() - 1; + *self.nodes[sibling_index].depth_mut() = new_depth; + // remove node and parent + self.swap_and_remove_index(shapes, dead_node_index.max(parent_index)); + self.swap_and_remove_index(shapes, parent_index.min(dead_node_index)); + } + + + let end_shape = shapes.len() - 1; + if deleted_shape_index < end_shape { + shapes.swap(deleted_shape_index, end_shape); + let node_index = shapes[deleted_shape_index].bh_node_index(); + match self.nodes[node_index].shape_index_mut() { + Some(index) => { + *index = deleted_shape_index + }, + _ => {} + } + } + } + + fn swap_and_remove_index( + &mut self, + shapes: &mut [T], + node_index: usize + ) { + let end = self.nodes.len() - 1; + if node_index != end { + self.nodes[node_index] = self.nodes[end]; + let parent_index = self.nodes[node_index].parent(); + match self.nodes[parent_index] { + BVHNode::Leaf { ..} => { + println!("skip setting parent={}", parent_index); + self.nodes.truncate(end); + return; + } + _ => { } + } + let parent = self.nodes[parent_index]; + let moved_left = parent.child_l() == end; + let ref_to_change = if moved_left { self.nodes[parent_index].child_l_mut() } else { self.nodes[parent_index].child_r_mut() }; + println!("on {} changing {}=>{}", parent_index, ref_to_change, node_index); + *ref_to_change = node_index; + + match self.nodes[node_index] { + BVHNode::Leaf { shape_index, ..} => { + shapes[shape_index].set_bh_node_index(node_index); + } + BVHNode::Node { + child_l_index, + child_r_index, + .. + } => { + *self.nodes[child_l_index].parent_mut() = node_index; + *self.nodes[child_r_index].parent_mut() = node_index; + //let correct_depth + //self.fix_depth(child_l_index, ) + } + } + } + self.nodes.truncate(end); + } + + + pub fn fix_depth( + &mut self, + curr_node: usize, + correct_depth: u32 + ) { + match self.nodes[curr_node] { + BVHNode::Node { + ref mut depth, + child_l_index, + child_r_index, + .. + } => { + *depth = correct_depth; + self.fix_depth(child_l_index, correct_depth + 1); + self.fix_depth(child_r_index, correct_depth + 1); + } + BVHNode::Leaf { + ref mut depth, + .. + } => { + *depth = correct_depth; + } + } } + /// Prints the [`BVH`] in a tree-like visualization. /// /// [`BVH`]: struct.BVH.html @@ -470,9 +774,9 @@ impl BVH { .. } => { let padding: String = repeat(" ").take(depth as usize).collect(); - println!("{}child_l {}", padding, child_l_aabb); + println!("{}{} child_l {}", padding, child_l_index, child_l_aabb); print_node(nodes, child_l_index); - println!("{}child_r {}", padding, child_r_aabb); + println!("{}{} child_r {}", padding, child_r_index, child_r_aabb); print_node(nodes, child_r_index); } BVHNode::Leaf { @@ -559,8 +863,8 @@ impl BVH { pub fn is_consistent(&self, shapes: &[Shape]) -> bool { // The root node of the bvh is not bounded by anything. let space = AABB { - min: Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), - max: Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY), + min: Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), + max: Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), }; // The counter for all nodes. @@ -657,8 +961,8 @@ impl BVH { pub fn assert_consistent(&self, shapes: &[Shape]) { // The root node of the bvh is not bounded by anything. let space = AABB { - min: Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), - max: Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY), + min: Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), + max: Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), }; // The counter for all nodes. @@ -716,7 +1020,7 @@ impl BoundingHierarchy for BVH { BVH::build(shapes) } - fn traverse<'a, Shape: Bounded>(&'a self, ray: &Ray, shapes: &'a [Shape]) -> Vec<&Shape> { + fn traverse<'a, Shape: Bounded>(&'a self, ray: &impl IntersectionTest, shapes: &'a [Shape]) -> Vec<&Shape> { self.traverse(ray, shapes) } @@ -728,7 +1032,11 @@ impl BoundingHierarchy for BVH { #[cfg(test)] mod tests { use crate::bvh::{BVHNode, BVH}; - use crate::testbase::{build_some_bh, traverse_some_bh}; + use crate::aabb::AABB; + use crate::{Point3, Vector3}; + use crate::testbase::{build_some_bh, traverse_some_bh, UnitBox}; + use crate::Ray; + use crate::bounding_hierarchy::BHShape; #[test] /// Tests whether the building procedure succeeds in not failing. @@ -742,6 +1050,119 @@ mod tests { traverse_some_bh::(); } + #[test] + fn test_add_bvh() { + let mut shapes = Vec::new(); + + + for x in -1..2 { + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + } + + + let mut bvh = BVH::build(&mut shapes); + bvh.pretty_print(); + + let test = AABB::empty().grow(&Point3::new(1.6, 0.0, 0.0)).grow(&Point3::new(2.4, 1.0, 1.0)); + + let res = bvh.traverse(&test, &shapes); + assert_eq!(res.len(), 0); + + let x = 2; + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + let len = shapes.len() - 1; + bvh.add_node(&mut shapes, len); + + bvh.pretty_print(); + let res = bvh.traverse(&test, &shapes); + assert_eq!(res.len(), 1); + + + let x = 50; + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + let len = shapes.len() - 1; + bvh.add_node(&mut shapes, len); + bvh.pretty_print(); + let res = bvh.traverse(&test, &shapes); + assert_eq!(res.len(), 1); + + let test = AABB::empty().grow(&Point3::new(49.6, 0.0, 0.0)).grow(&Point3::new(52.4, 1.0, 1.0)); + let res = bvh.traverse(&test, &shapes); + assert_eq!(res.len(), 1); + } + + + #[test] + fn test_remove_bvh() { + let mut shapes = Vec::new(); + for x in -1..1 { + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + } + + let mut bvh = BVH::build(&mut shapes); + + bvh.pretty_print(); + println!("------"); + println!("{:?}", bvh.nodes.len()); + bvh.remove_node(&mut shapes, 0); + + println!("{:?}", bvh.nodes.len()); + println!("------"); + bvh.pretty_print(); + } + + #[test] + fn test_accuracy_after_bvh_remove() { + let mut shapes = Vec::new(); + for x in -25..25 { + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + } + + + + let mut bvh = BVH::build(&mut shapes); + + fn test_x(bvh: &BVH, x: f64, count: usize, shapes: &[UnitBox]) { + let dir = Vector3::new(0.0, -1.0, 0.0); + + let ray = Ray::new(Vector3::new(x, 2.0, 0.0), dir); + let res = bvh.traverse(&ray, shapes); + if count == 0 && res.len() > 0 + { + println!("hit={} node={}", res[0].pos, res[0].bh_node_index()); + } + assert_eq!(res.len(), count); + } + + test_x(&bvh, 2.0, 1, &shapes); + + for x in -23 .. 23 { + let point = Point3::new(x as f64, 0.0, 0.0); + let mut delete_i = 0; + for i in 0..shapes.len() { + if shapes[i].pos.distance_squared(point) < 0.01 { + delete_i = i; + break; + } + } + println!("Testing {}", x); + bvh.pretty_print(); + println!("Ensuring x={} shape[{}] is present", x, delete_i); + test_x(&bvh, x as f64, 1, &shapes); + println!("Deleting x={} shape[{}]", x, delete_i); + bvh.remove_node(&mut shapes, delete_i); + shapes.truncate(shapes.len() - 1); + bvh.pretty_print(); + println!("Ensuring {} [{}] is gone", x, delete_i); + test_x(&bvh, x as f64, 0, &shapes); + + } + } + + + + + #[test] /// Verify contents of the bounding hierarchy for a fixed scene structure fn test_bvh_shape_indices() { diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index 5166884..bf40203 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -1,6 +1,6 @@ use crate::aabb::Bounded; use crate::bvh::{BVHNode, BVH}; -use crate::ray::Ray; +use crate::bounding_hierarchy::{IntersectionTest}; /// Iterator to traverse a [`BVH`] without memory allocations #[allow(clippy::upper_case_acronyms)] @@ -8,7 +8,7 @@ pub struct BVHTraverseIterator<'a, Shape: Bounded> { /// Reference to the BVH to traverse bvh: &'a BVH, /// Reference to the input ray - ray: &'a Ray, + test: &'a dyn IntersectionTest, /// Reference to the input shapes array shapes: &'a [Shape], /// Traversal stack. 4 billion items seems enough? @@ -23,10 +23,10 @@ pub struct BVHTraverseIterator<'a, Shape: Bounded> { impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { /// Creates a new `BVHTraverseIterator` - pub fn new(bvh: &'a BVH, ray: &'a Ray, shapes: &'a [Shape]) -> Self { + pub fn new(bvh: &'a BVH, test: &'a impl IntersectionTest, shapes: &'a [Shape]) -> Self { BVHTraverseIterator { bvh, - ray, + test, shapes, stack: [0; 32], node_index: 0, @@ -69,7 +69,7 @@ impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { ref child_l_aabb, .. } => { - if self.ray.intersects_aabb(child_l_aabb) { + if self.test.intersects_aabb(child_l_aabb) { self.node_index = child_l_index; self.has_node = true; } else { @@ -91,7 +91,7 @@ impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { ref child_r_aabb, .. } => { - if self.ray.intersects_aabb(child_r_aabb) { + if self.test.intersects_aabb(child_r_aabb) { self.node_index = child_r_index; self.has_node = true; } else { diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 3b8d87a..157adde 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -235,7 +235,7 @@ impl BVH { // thus being the favored rotation that will be executed after considering all rotations. let mut best_rotation: Option<(usize, usize)> = None; { - let mut consider_rotation = |new_rotation: (usize, usize), surface_area: f32| { + let mut consider_rotation = |new_rotation: (usize, usize), surface_area: f64| { if surface_area < best_surface_area { best_surface_area = surface_area; best_rotation = Some(new_rotation); @@ -958,11 +958,11 @@ mod bench { /// Benchmark optimizing a `BVH` with 120,000 `Triangle`s, where `percent` /// `Triangles` have been randomly moved. - fn optimize_bvh_120k(percent: f32, b: &mut ::test::Bencher) { + fn optimize_bvh_120k(percent: f64, b: &mut ::test::Bencher) { let bounds = default_bounds(); let mut triangles = create_n_cubes(10_000, &bounds); let mut bvh = BVH::build(&mut triangles); - let num_move = (triangles.len() as f32 * percent) as usize; + let num_move = (triangles.len() as f64 * percent) as usize; let mut seed = 0; b.iter(|| { @@ -998,13 +998,13 @@ mod bench { fn intersect_scene_after_optimize( mut triangles: &mut Vec, bounds: &AABB, - percent: f32, + percent: f64, max_offset: Option, iterations: usize, b: &mut ::test::Bencher, ) { let mut bvh = BVH::build(&mut triangles); - let num_move = (triangles.len() as f32 * percent) as usize; + let num_move = (triangles.len() as f64 * percent) as usize; let mut seed = 0; for _ in 0..iterations { @@ -1051,12 +1051,12 @@ mod bench { fn intersect_scene_with_rebuild( mut triangles: &mut Vec, bounds: &AABB, - percent: f32, + percent: f64, max_offset: Option, iterations: usize, b: &mut ::test::Bencher, ) { - let num_move = (triangles.len() as f32 * percent) as usize; + let num_move = (triangles.len() as f64 * percent) as usize; let mut seed = 0; for _ in 0..iterations { randomly_transform_scene(&mut triangles, num_move, &bounds, max_offset, &mut seed); @@ -1096,7 +1096,7 @@ mod bench { /// Benchmark intersecting a `BVH` for Sponza after randomly moving one `Triangle` and /// optimizing. - fn intersect_sponza_after_optimize(percent: f32, b: &mut ::test::Bencher) { + fn intersect_sponza_after_optimize(percent: f64, b: &mut ::test::Bencher) { let (mut triangles, bounds) = load_sponza_scene(); intersect_scene_after_optimize(&mut triangles, &bounds, percent, Some(0.1), 10, b); } @@ -1123,7 +1123,7 @@ mod bench { /// Benchmark intersecting a `BVH` for Sponza after rebuilding. Used to compare optimizing /// with rebuilding. For reference see `intersect_sponza_after_optimize`. - fn intersect_sponza_with_rebuild(percent: f32, b: &mut ::test::Bencher) { + fn intersect_sponza_with_rebuild(percent: f64, b: &mut ::test::Bencher) { let (mut triangles, bounds) = load_sponza_scene(); intersect_scene_with_rebuild(&mut triangles, &bounds, percent, Some(0.1), 10, b); } diff --git a/src/flat_bvh.rs b/src/flat_bvh.rs index 4e43ece..9a05fbe 100644 --- a/src/flat_bvh.rs +++ b/src/flat_bvh.rs @@ -1,9 +1,8 @@ //! This module exports methods to flatten the `BVH` and traverse it iteratively. use crate::aabb::{Bounded, AABB}; -use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; +use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; use crate::bvh::{BVHNode, BVH}; -use crate::ray::Ray; /// A structure of a node of a flat [`BVH`]. The structure of the nodes allows for an /// iterative traversal approach without the necessity to maintain a stack or queue. @@ -200,7 +199,7 @@ impl BVH { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f32, i as f32, i as f32); + /// # let position = Point3::new(i as f64, i as f64, i as f64); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -284,7 +283,7 @@ impl BVH { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f32, i as f32, i as f32); + /// # let position = Point3::new(i as f64, i as f64, i as f64); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -365,7 +364,7 @@ impl BoundingHierarchy for FlatBVH { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f32, i as f32, i as f32); + /// # let position = Point3::new(i as f64, i as f64, i as f64); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -378,7 +377,7 @@ impl BoundingHierarchy for FlatBVH { /// let flat_bvh = FlatBVH::build(&mut shapes); /// let hit_shapes = flat_bvh.traverse(&ray, &shapes); /// ``` - fn traverse<'a, T: Bounded>(&'a self, ray: &Ray, shapes: &'a [T]) -> Vec<&T> { + fn traverse<'a, T: Bounded>(&'a self, ray: &impl IntersectionTest, shapes: &'a [T]) -> Vec<&T> { let mut hit_shapes = Vec::new(); let mut index = 0; diff --git a/src/lib.rs b/src/lib.rs index 55f6eca..b77950c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ //! //! struct Sphere { //! position: Point3, -//! radius: f32, +//! radius: f64, //! node_index: usize, //! } //! @@ -51,8 +51,8 @@ //! //! let mut spheres = Vec::new(); //! for i in 0..1000u32 { -//! let position = Point3::new(i as f32, i as f32, i as f32); -//! let radius = (i % 10) as f32 + 1.0; +//! let position = Point3::new(i as f64, i as f64, i as f64); +//! let radius = (i % 10) as f64 + 1.0; //! spheres.push(Sphere { //! position: position, //! radius: radius, @@ -65,7 +65,7 @@ //! ``` //! -#![deny(missing_docs)] +//#![deny(missing_docs)] #![cfg_attr(feature = "bench", feature(test))] #[cfg(all(feature = "bench", test))] @@ -73,13 +73,13 @@ extern crate test; /// A minimal floating value used as a lower bound. /// TODO: replace by/add ULPS/relative float comparison methods. -pub const EPSILON: f32 = 0.00001; +pub const EPSILON: f64 = 0.00001; /// Point math type used by this crate. Type alias for [`glam::Vec3`]. -pub type Point3 = glam::Vec3; +pub type Point3 = glam::DVec3; /// Vector math type used by this crate. Type alias for [`glam::Vec3`]. -pub type Vector3 = glam::Vec3; +pub type Vector3 = glam::DVec3; pub mod aabb; pub mod axis; @@ -87,7 +87,329 @@ pub mod bounding_hierarchy; pub mod bvh; pub mod flat_bvh; pub mod ray; +pub mod shapes; mod utils; +use aabb::{Bounded, AABB}; +use bounding_hierarchy::BHShape; +use bvh::BVHNode; +use ray::Ray; +use shapes::{Capsule, Sphere, OBB}; +use glam::DQuat; + #[cfg(test)] mod testbase; + +#[no_mangle] +pub extern fn add_numbers(number1: i32, number2: i32) -> i32 { + println!("Hello from rust!"); + number1 + number2 +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct Vector3D { + pub x: f64, + pub y: f64, + pub z: f64 +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct BoundsD { + pub center: Vector3D, + pub extents: Vector3D +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct BVHBounds { + pub bounds: BoundsD, + pub index: i32, + pub ptr: i32 +} + +#[repr(C)] +pub struct BVHRef { + pub ptr: *mut BVHNode, + pub len: i32, + pub cap: i32 +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct QuaternionD { + pub x: f64, + pub y: f64, + pub z: f64, + pub w: f64 +} + +impl Bounded for BVHBounds { + fn aabb(&self) -> AABB { + let half_size = to_vec(&self.bounds.extents); + let pos = to_vec(&self.bounds.center); + let min = pos - half_size; + let max = pos + half_size; + AABB::with_bounds(min, max) + } +} + +impl BHShape for BVHBounds { + fn set_bh_node_index(&mut self, index: usize) { + self.index = index as i32; + } + + fn bh_node_index(&self) -> usize { + self.index as usize + } +} + +pub fn to_vec(a: &Vector3D) -> Vector3 { + Vector3::new(a.x, a.y, a.z) +} + +pub fn to_vecd(a: &Vector3) -> Vector3D { + Vector3D { + x: a.x, + y: a.y, + z: a.z + } +} + +pub fn to_quat(a: &QuaternionD) -> DQuat { + DQuat::from_xyzw(a.x, a.y, a.z, a.w) +} + +#[no_mangle] +pub extern fn build_bvh(a: *mut BVHBounds, count: i32) -> BVHRef +{ + let mut s = unsafe { std::slice::from_raw_parts_mut(a, count as usize) }; + + let mut bvh = bvh::BVH::build(&mut s); + let len = bvh.nodes.len(); + let cap = bvh.nodes.capacity(); + let p = bvh.nodes.as_mut_ptr(); + std::mem::forget(bvh.nodes); + + BVHRef { ptr: p, len: len as i32, cap: cap as i32 } +} + +#[no_mangle] +pub extern fn query_ray(bvhRef: *const BVHRef, originVec: *const Vector3D, dirVec: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let bvh = bvh::BVH { + nodes: v + }; + + let ray = Ray::new(to_vec(& unsafe{*originVec}), to_vec(& unsafe{*dirVec})); + let mut i = 0; + + for x in bvh.traverse_iterator(&ray, &shapes) { + if i < max_res { + buffer[i as usize] = *x; + } + i += 1; + } + std::mem::forget(bvh.nodes); + + i as i32 +} + +#[no_mangle] +pub extern fn batch_query_rays(bvhRef: *const BVHRef, originVecs: *mut Vector3D, dirVecs: *mut Vector3D, hits: *mut i32, rayCount: i32, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + let origins = unsafe { std::slice::from_raw_parts_mut(originVecs, rayCount as usize) }; + let dirs = unsafe { std::slice::from_raw_parts_mut(dirVecs, rayCount as usize) }; + let hits = unsafe { std::slice::from_raw_parts_mut(hits, rayCount as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let bvh = bvh::BVH { + nodes: v + }; + let mut i = 0; + for r in 0..rayCount as usize { + let ray = Ray::new(to_vec(&origins[r]), to_vec(&dirs[r])); + let mut res = 0; + for x in bvh.traverse_iterator(&ray, &shapes) { + if i < max_res { + buffer[i as usize] = *x; + } + i += 1; + res += 1; + } + hits[r] = res; + } + + std::mem::forget(bvh.nodes); +} + + +#[no_mangle] +pub extern fn query_sphere(bvhRef: *const BVHRef, center: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let bvh = bvh::BVH { + nodes: v + }; + + let test_shape = Sphere::new(to_vec(&unsafe { *center }), radius); + let mut i = 0; + + for x in bvh.traverse_iterator(&test_shape, &shapes) { + if i < max_res { + buffer[i as usize] = *x; + } + i += 1; + } + std::mem::forget(bvh.nodes); + + i as i32 +} + +#[no_mangle] +pub extern fn query_capsule(bvhRef: *const BVHRef, start: *const Vector3D, end: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let bvh = bvh::BVH { + nodes: v + }; + + let test_shape = Capsule::new(to_vec(&unsafe { *start }), to_vec(&unsafe { *end }), radius); + let mut i = 0; + + for x in bvh.traverse_iterator(&test_shape, &shapes) { + if i < max_res { + buffer[i as usize] = *x; + } + i += 1; + } + std::mem::forget(bvh.nodes); + + i as i32 +} + +#[no_mangle] +pub extern fn query_aabb(bvhRef: *const BVHRef, bounds: *const BoundsD, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let bvh = bvh::BVH { + nodes: v + }; + + let half_size = to_vec(&unsafe { *bounds }.extents); + let pos = to_vec(&unsafe { *bounds }.center); + let min = pos - half_size; + let max = pos + half_size; + let test_shape = AABB::with_bounds(min, max); + let mut i = 0; + + for x in bvh.traverse_iterator(&test_shape, &shapes) { + if i < max_res { + buffer[i as usize] = *x; + } + i += 1; + } + std::mem::forget(bvh.nodes); + + i as i32 +} + +#[no_mangle] +pub extern fn query_obb(bvhRef: *const BVHRef, ori: *const QuaternionD, extents: *const Vector3D, center: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let bvh = bvh::BVH { + nodes: v + }; + let obb = OBB { + orientation: to_quat(&unsafe { *ori }), + extents: to_vec(&unsafe { *extents }), + center: to_vec(&unsafe { *center }) + }; + + let mut i = 0; + + for x in bvh.traverse_iterator(&obb, &shapes) { + if i < max_res { + buffer[i as usize] = *x; + } + i += 1; + } + std::mem::forget(bvh.nodes); + + i as i32 +} + +#[no_mangle] +pub extern fn free_bvh(bvhRef: *const BVHRef) +{ + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; +} + + +#[no_mangle] +pub extern fn add_node(bvhRef: *const BVHRef, new_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let mut bvh = bvh::BVH { + nodes: v + }; + + bvh.add_node(shapes, new_shape as usize); + let len = bvh.nodes.len(); + let cap = bvh.nodes.capacity(); + let p = bvh.nodes.as_mut_ptr(); + std::mem::forget(bvh.nodes); + + BVHRef { ptr: p, len: len as i32, cap: cap as i32 } +} + +#[no_mangle] +pub extern fn remove_node(bvhRef: *const BVHRef, remove_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + + let mut bvh = bvh::BVH { + nodes: v + }; + + bvh.remove_node(shapes, remove_shape as usize); + let len = bvh.nodes.len(); + let cap = bvh.nodes.capacity(); + let p = bvh.nodes.as_mut_ptr(); + std::mem::forget(bvh.nodes); + + BVHRef { ptr: p, len: len as i32, cap: cap as i32 } +} + + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0d8116c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,50 @@ +use bvh_f64::aabb::{Bounded, AABB}; +use bvh_f64::bounding_hierarchy::BHShape; +use bvh_f64::bvh::BVH; +use bvh_f64::ray::Ray; +use bvh_f64::{Point3, Vector3}; + +#[derive(Debug)] +struct Sphere { + position: Point3, + radius: f64, + node_index: usize, +} + +impl Bounded for Sphere { + fn aabb(&self) -> AABB { + let half_size = Vector3::new(self.radius, self.radius, self.radius); + let min = self.position - half_size; + let max = self.position + half_size; + AABB::with_bounds(min, max) + } +} + +impl BHShape for Sphere { + fn set_bh_node_index(&mut self, index: usize) { + self.node_index = index; + } + + fn bh_node_index(&self) -> usize { + self.node_index + } +} + +pub fn main() { + let mut spheres = Vec::new(); + for i in 0..1000000u32 { + let position = Point3::new(i as f64, i as f64, i as f64); + let radius = (i % 10) as f64 + 1.0; + spheres.push(Sphere { + position, + radius, + node_index: 0, + }); + } + let bvh = BVH::build(&mut spheres); + let origin = Point3::new(0.0, 0.0, 0.0); + let direction = Vector3::new(1.0, 0.0, 0.0); + let ray = Ray::new(origin, direction); + let hit_sphere_aabbs = bvh.traverse(&ray, &spheres); + dbg!(hit_sphere_aabbs); +} diff --git a/src/ray.rs b/src/ray.rs index 9984ac4..f10e24d 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -4,7 +4,8 @@ use crate::aabb::AABB; use crate::EPSILON; use crate::{Point3, Vector3}; -use std::f32::INFINITY; +use std::f64::INFINITY; +use crate::bounding_hierarchy::IntersectionTest; /// A struct which defines a ray and some of its cached values. #[derive(Debug)] @@ -46,54 +47,24 @@ pub struct Ray { /// A struct which is returned by the `intersects_triangle` method. pub struct Intersection { /// Distance from the ray origin to the intersection point. - pub distance: f32, + pub distance: f64, /// U coordinate of the intersection. - pub u: f32, + pub u: f64, /// V coordinate of the intersection. - pub v: f32, + pub v: f64, } impl Intersection { /// Constructs an `Intersection`. `distance` should be set to positive infinity, /// if the intersection does not occur. - pub fn new(distance: f32, u: f32, v: f32) -> Intersection { + pub fn new(distance: f64, u: f64, v: f64) -> Intersection { Intersection { distance, u, v } } } -impl Ray { - /// Creates a new [`Ray`] from an `origin` and a `direction`. - /// `direction` will be normalized. - /// - /// # Examples - /// ``` - /// use bvh::ray::Ray; - /// use bvh::{Point3,Vector3}; - /// - /// let origin = Point3::new(0.0,0.0,0.0); - /// let direction = Vector3::new(1.0,0.0,0.0); - /// let ray = Ray::new(origin, direction); - /// - /// assert_eq!(ray.origin, origin); - /// assert_eq!(ray.direction, direction); - /// ``` - /// - /// [`Ray`]: struct.Ray.html - /// - pub fn new(origin: Point3, direction: Vector3) -> Ray { - let direction = direction.normalize(); - Ray { - origin, - direction, - inv_direction: Vector3::new(1.0 / direction.x, 1.0 / direction.y, 1.0 / direction.z), - sign_x: (direction.x < 0.0) as usize, - sign_y: (direction.y < 0.0) as usize, - sign_z: (direction.z < 0.0) as usize, - } - } - +impl IntersectionTest for Ray { /// Tests the intersection of a [`Ray`] with an [`AABB`] using the optimized algorithm /// from [this paper](http://www.cs.utah.edu/~awilliam/box/box.pdf). /// @@ -117,7 +88,7 @@ impl Ray { /// [`Ray`]: struct.Ray.html /// [`AABB`]: struct.AABB.html /// - pub fn intersects_aabb(&self, aabb: &AABB) -> bool { + fn intersects_aabb(&self, aabb: &AABB) -> bool { let mut ray_min = (aabb[self.sign_x].x - self.origin.x) * self.inv_direction.x; let mut ray_max = (aabb[1 - self.sign_x].x - self.origin.x) * self.inv_direction.x; @@ -160,6 +131,40 @@ impl Ray { ray_max > 0.0 } +} + + +impl Ray { + /// Creates a new [`Ray`] from an `origin` and a `direction`. + /// `direction` will be normalized. + /// + /// # Examples + /// ``` + /// use bvh::ray::Ray; + /// use bvh::{Point3,Vector3}; + /// + /// let origin = Point3::new(0.0,0.0,0.0); + /// let direction = Vector3::new(1.0,0.0,0.0); + /// let ray = Ray::new(origin, direction); + /// + /// assert_eq!(ray.origin, origin); + /// assert_eq!(ray.direction, direction); + /// ``` + /// + /// [`Ray`]: struct.Ray.html + /// + pub fn new(origin: Point3, direction: Vector3) -> Ray { + let direction = direction.normalize(); + Ray { + origin, + direction, + inv_direction: Vector3::new(1.0 / direction.x, 1.0 / direction.y, 1.0 / direction.z), + sign_x: (direction.x < 0.0) as usize, + sign_y: (direction.y < 0.0) as usize, + sign_z: (direction.z < 0.0) as usize, + } + } + /// Naive implementation of a [`Ray`]/[`AABB`] intersection algorithm. /// @@ -315,13 +320,13 @@ impl Ray { #[cfg(test)] mod tests { use std::cmp; - use std::f32::INFINITY; + use std::f64::INFINITY; use crate::aabb::AABB; use crate::ray::Ray; use crate::testbase::{tuple_to_point, tuplevec_small_strategy, TupleVec}; use crate::EPSILON; - + use crate::bounding_hierarchy::IntersectionTest; use proptest::prelude::*; /// Generates a random `Ray` which points at at a random `AABB`. @@ -433,8 +438,8 @@ mod tests { // Get some u and v coordinates such that u+v <= 1 let u = u % 101; let v = cmp::min(100 - u, v % 101); - let u = u as f32 / 100.0; - let v = v as f32 / 100.0; + let u = u as f64 / 100.0; + let v = v as f64 / 100.0; // Define some point on the triangle let point_on_triangle = triangle.0 + u * u_vec + v * v_vec; diff --git a/src/shapes.rs b/src/shapes.rs new file mode 100644 index 0000000..03e9794 --- /dev/null +++ b/src/shapes.rs @@ -0,0 +1,294 @@ + +use crate::aabb::AABB; +use crate::{Point3, Vector3}; +use crate::bounding_hierarchy::IntersectionTest; +use glam::{DQuat, DMat4}; + + +pub struct Sphere { + center: Point3, + radius: f64 +} + +impl Sphere { + pub fn new(center: Point3, radius: f64) -> Sphere + { + Sphere { + center, + radius + } + } +} + +impl IntersectionTest for Sphere { + fn intersects_aabb(&self, aabb: &AABB) -> bool { + let vec = aabb.closest_point(self.center); + vec.distance_squared(self.center) < self.radius * self.radius + } +} + +pub struct Capsule { + start: Point3, + radius: f64, + + dir: Vector3, + len: f64 +} + +impl Capsule { + pub fn new(start: Point3, end: Point3, radius: f64) -> Capsule + { + let line = end - start; + let dir = line.normalize(); + let len = line.length(); + + Capsule { + start, + radius, + dir, + len + } + } +} + +impl IntersectionTest for Capsule { + fn intersects_aabb(&self, aabb: &AABB) -> bool { + /* + // Use Distance from closest point + let mut point: Vector3 = self.start; + let mut curr_d = 0.0; + let max_sq = (self.len + self.radius) * (self.len + self.radius) ; + let r_sq = self.radius * self.radius; + + loop { + let x = aabb.closest_point(point); + let d_sq = x.distance_squared(point); + println!("{:?} closest={:?} d={:?}", point, x, d_sq.sqrt()); + if d_sq <= r_sq + { + return true; + } + if d_sq > max_sq || curr_d >= self.len + { + return false; + } + curr_d = (curr_d + d_sq.sqrt()).min(self.len); + point = self.start + (curr_d * self.dir); + } + */ + let mut last = self.start; + loop { + let closest = &aabb.closest_point(last); + let center = nearest_point_on_line(&self.start, &self.dir, self.len, closest); + let sphere = Sphere{ + center, + radius: self.radius + }; + if sphere.intersects_aabb(aabb) { + return true; + } + if last.distance_squared(center) < 0.0001 + { + return false + } else { + last = center; + } + } + + + } +} + +pub struct OBB { + pub orientation: DQuat, + pub extents: Vector3, + pub center: Vector3, +} + +impl IntersectionTest for OBB { + + fn intersects_aabb(&self, aabb: &AABB) -> bool { + let half_a = self.extents; + let half_b = (aabb.max - aabb.min) * 0.5; + let value = (aabb.max + aabb.min) * 0.5; + let translation = self.orientation * (value - self.center); + let mat = DMat4::from_rotation_translation(self.orientation, translation); + + let vec_1 = Vector3::new(translation.x.abs(), translation.y.abs(), translation.z.abs()); + let right = right(mat); + let up = up(mat); + let backward = back(mat); + let vec_2 = right * half_b.x; + let vec_3 = up * half_b.y; + let vec_4 = backward * half_b.z; + let num = vec_2.x.abs() + vec_3.x.abs() + vec_4.x.abs(); + let num2 = vec_2.y.abs() + vec_3.y.abs() + vec_4.y.abs(); + let num3 = vec_2.z.abs() + vec_3.z.abs() + vec_4.z.abs(); + if vec_1.x + num <= half_a.x && vec_1.y + num2 <= half_a.y && vec_1.z + num3 <= half_a.z + { + // Contained + return true; + } + if vec_1.x > half_a.x + vec_2.x.abs() + vec_3.x.abs() + vec_4.x.abs() + { + return false; + } + if vec_1.y > half_a.y + vec_2.y.abs() + vec_3.y.abs() + vec_4.y.abs() + { + return false; + } + if vec_1.z > half_a.z + vec_2.z.abs() + vec_3.z.abs() + vec_4.z.abs() + { + return false; + } + if translation.dot(right.abs()) > half_a.x * right.x.abs() + half_a.y * right.y.abs() + half_a.z * right.z.abs() + half_b.x + { + return false; + } + if translation.dot(up.abs()) > half_a.x * up.x.abs() + half_a.y * up.y.abs() + half_a.z * up.z.abs() + half_b.y + { + return false; + } + if translation.dot(backward.abs()) > half_a.x * backward.x.abs() + half_a.y * backward.y.abs() + half_a.z * backward.z.abs() + half_b.z + { + return false; + } + let mut vec_5 = Vector3::new(0.0, -right.z, right.y); + if translation.dot(vec_5.abs()) > half_a.y * vec_5.y.abs() + half_a.z * vec_5.z.abs() + vec_5.dot(vec_3.abs()) + vec_5.dot(vec_4.abs()) + { + return false; + } + vec_5 = Vector3::new(0.0, -up.z, up.y); + if translation.dot(vec_5.abs()) > half_a.y * vec_5.y.abs() + half_a.z * vec_5.z.abs() + vec_5.dot(vec_4.abs()) + vec_5.dot(vec_2.abs()) + { + return false; + } + vec_5 = Vector3::new(0.0, -backward.z, backward.y); + if translation.dot(vec_5.abs()) > half_a.y * vec_5.y.abs() + half_a.z * vec_5.z.abs() + vec_5.dot(vec_2.abs()) + vec_5.dot(vec_3.abs()) + { + return false; + } + vec_5 = Vector3::new(right.z, 0.0, -right.x); + if translation.dot(vec_5.abs()) > half_a.z * vec_5.z.abs() + half_a.x * vec_5.x.abs() + vec_5.dot(vec_3.abs()) + vec_5.dot(vec_4.abs()) + { + return false; + } + vec_5 = Vector3::new(up.z, 0.0, -up.x); + if translation.dot(vec_5.abs()) > half_a.z * vec_5.z.abs() + half_a.x * vec_5.x.abs() + vec_5.dot(vec_4.abs()) + vec_5.dot(vec_2.abs()) + { + return false; + } + vec_5 = Vector3::new(backward.z, 0.0, -backward.x); + if translation.dot(vec_5.abs()) > half_a.z * vec_5.z.abs() + half_a.x * vec_5.x.abs() + vec_5.dot(vec_2.abs()) + vec_5.dot(vec_3.abs()) + { + return false; + } + vec_5 = Vector3::new(-right.y, right.x, 0.0); + if translation.dot(vec_5.abs()) > half_a.x * vec_5.x.abs() + half_a.y * vec_5.y.abs() + vec_5.dot(vec_3.abs()) + vec_5.dot(vec_4.abs()) + { + return false; + } + vec_5 = Vector3::new(-up.y, up.x, 0.0); + if translation.dot(vec_5.abs()) > half_a.x * vec_5.x.abs() + half_a.y * vec_5.y.abs() + vec_5.dot(vec_4.abs()) + vec_5.dot(vec_2.abs()) + { + return false; + } + vec_5 = Vector3::new(-backward.y, backward.x, 0.0); + if translation.dot(vec_5.abs()) > half_a.x * vec_5.x.abs() + half_a.y * vec_5.y.abs() + vec_5.dot(vec_2.abs()) + vec_5.dot(vec_3.abs()) + { + return false; + } + // Intersection + return true; + } +} + +fn right(matrix: DMat4) -> Vector3 +{ + matrix.row(0).truncate() +} + +fn up(matrix: DMat4) -> Vector3 +{ + matrix.row(1).truncate() +} + +fn back(matrix: DMat4) -> Vector3 +{ + matrix.row(2).truncate() +} + +fn translation(matrix: DMat4) -> Vector3 +{ + matrix.row(3).truncate() +} + +pub fn nearest_point_on_line(p1: &Point3, dir: &Vector3, len: f64, pnt: &Point3) -> Point3 { + let v = *pnt - *p1; + let d = v.dot(*dir); + *p1 + (*dir * d.clamp(0.0, len)) +} + + + +#[cfg(test)] +mod tests { + use crate::aabb::AABB; + use crate::{Point3, Vector3}; + use crate::bounding_hierarchy::IntersectionTest; + use glam::{DQuat, DMat4}; + use crate::shapes::{Capsule, OBB}; + + #[test] + fn basic_test_capsule() { + let min = Point3::new(0.0, 0.0, 0.0); + let max = Point3::new(1.0, 1.0, 1.0); + let aabb = AABB::empty().grow(&min).grow(&max); + let start = Point3::new(3.0, 0.0, 0.0); + let end = Point3::new(1.5, 0.0, 0.0); + let capsule = Capsule::new(start, end, 0.55); + assert!(capsule.intersects_aabb(&aabb)); + } + + #[test] + fn moving_test_capsule() { + let min = Point3::new(0.0, 0.0, 0.0); + let max = Point3::new(1.0, 1.0, 1.0); + let aabb = AABB::empty().grow(&min).grow(&max); + let start = Point3::new(0.5, 2.0, 2.0); + let end = Point3::new(0.5, 5.0, -1.0); + let capsule = Capsule::new(start, end, 1.45); + assert!(capsule.intersects_aabb(&aabb)); + + let dir = (start - end).normalize(); + let offset = 0.005; + println!("{}", dir); + for i in 0..800 { + println!("{}", i); + let pt = offset * dir * i as f64; + let cap = Capsule::new(start + pt, end + pt, 1.45); + assert!(cap.intersects_aabb(&aabb)); + } + } + + + #[test] + fn basic_obb() { + let min = Point3::new(0.0, 0.0, 0.0); + let max = Point3::new(1.0, 1.0, 1.0); + let aabb = AABB::empty().grow(&min).grow(&max); + + let ori = DQuat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0), 0.785398); + let extents = Vector3::new(0.5, 0.5, 0.5); + let pos = Vector3::new(0.5, 2.2, 0.5); + + let obb = OBB { + orientation: ori, + extents, + center: pos + }; + + assert!(obb.intersects_aabb(&aabb)); + + } +} \ No newline at end of file diff --git a/src/testbase.rs b/src/testbase.rs index cd67b41..871d6ed 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -2,7 +2,7 @@ #![cfg(test)] use std::collections::HashSet; -use std::f32; +use std::f64; use std::mem::transmute; use crate::{Point3, Vector3}; @@ -19,26 +19,26 @@ use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; use crate::ray::Ray; /// A vector represented as a tuple -pub type TupleVec = (f32, f32, f32); +pub type TupleVec = (f64, f64, f64); /// Generate a `TupleVec` for [`proptest::strategy::Strategy`] from -10e10 to 10e10 /// A small enough range to prevent most fp32 errors from breaking certain tests /// Tests which rely on this strategy should probably be rewritten pub fn tuplevec_small_strategy() -> impl Strategy { ( - -10e10_f32..10e10_f32, - -10e10_f32..10e10_f32, - -10e10_f32..10e10_f32, + -10e10_f64..10e10_f64, + -10e10_f64..10e10_f64, + -10e10_f64..10e10_f64, ) } /// Generate a `TupleVec` for [`proptest::strategy::Strategy`] from -10e30 to 10e30 -/// A small enough range to prevent `f32::MAX` ranges from breaking certain tests +/// A small enough range to prevent `f64::MAX` ranges from breaking certain tests pub fn tuplevec_large_strategy() -> impl Strategy { ( - -10e30_f32..10e30_f32, - -10e30_f32..10e30_f32, - -10e30_f32..10e30_f32, + -10e30_f64..10e30_f64, + -10e30_f64..10e30_f64, + -10e30_f64..10e30_f64, ) } @@ -94,7 +94,7 @@ pub fn generate_aligned_boxes() -> Vec { // Create 21 boxes along the x-axis let mut shapes = Vec::new(); for x in -10..11 { - shapes.push(UnitBox::new(x, Point3::new(x as f32, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); } shapes } @@ -214,7 +214,7 @@ impl FromRawVertex for Triangle { // Convert the vertices to `Point3`s. let points = vertices .into_iter() - .map(|v| Point3::new(v.0, v.1, v.2)) + .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) .collect::>(); // Estimate for the number of triangles, assuming that each polygon is a triangle. @@ -341,9 +341,9 @@ pub fn next_point3(seed: &mut u64, aabb: &AABB) -> Point3 { let (a, b, c) = next_point3_raw(seed); use std::i32; let float_vector = Vector3::new( - (a as f32 / i32::MAX as f32) + 1.0, - (b as f32 / i32::MAX as f32) + 1.0, - (c as f32 / i32::MAX as f32) + 1.0, + (a as f64 / i32::MAX as f64) + 1.0, + (b as f64 / i32::MAX as f64) + 1.0, + (c as f64 / i32::MAX as f64) + 1.0, ) * 0.5; assert!(float_vector.x >= 0.0 && float_vector.x <= 1.0); @@ -404,7 +404,7 @@ pub fn randomly_transform_scene( triangles: &mut Vec, amount: usize, bounds: &AABB, - max_offset_option: Option, + max_offset_option: Option, seed: &mut u64, ) -> HashSet { let mut indices: Vec = (0..triangles.len()).collect(); @@ -420,7 +420,7 @@ pub fn randomly_transform_scene( let max_offset = if let Some(value) = max_offset_option { value } else { - f32::INFINITY + f64::INFINITY }; for index in &indices { From a4691fd807bad636e609c5ca3e2b9f300632e61a Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Tue, 15 Jun 2021 17:21:24 -0400 Subject: [PATCH 02/37] fix lints --- examples/simple.rs | 16 ++++++++-------- src/lib.rs | 46 +++++++++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 180d973..9234994 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,13 +1,13 @@ -use bvh::aabb::{Bounded, AABB}; -use bvh::bounding_hierarchy::BHShape; -use bvh::bvh::BVH; -use bvh::ray::Ray; -use bvh::{Point3, Vector3}; +use bvh_f64::aabb::{Bounded, AABB}; +use bvh_f64::bounding_hierarchy::BHShape; +use bvh_f64::bvh::BVH; +use bvh_f64::ray::Ray; +use bvh_f64::{Point3, Vector3}; #[derive(Debug)] struct Sphere { position: Point3, - radius: f32, + radius: f64, node_index: usize, } @@ -33,8 +33,8 @@ impl BHShape for Sphere { pub fn main() { let mut spheres = Vec::new(); for i in 0..1000000u32 { - let position = Point3::new(i as f32, i as f32, i as f32); - let radius = (i % 10) as f32 + 1.0; + let position = Point3::new(i as f64, i as f64, i as f64); + let radius = (i % 10) as f64 + 1.0; spheres.push(Sphere { position, radius, diff --git a/src/lib.rs b/src/lib.rs index b77950c..c1b4a8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,18 +196,18 @@ pub extern fn build_bvh(a: *mut BVHBounds, count: i32) -> BVHRef } #[no_mangle] -pub extern fn query_ray(bvhRef: *const BVHRef, originVec: *const Vector3D, dirVec: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern fn query_ray(bvh_ref: *const BVHRef, origin_vec: *const Vector3D, dir_vec: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let bvh = bvh::BVH { nodes: v }; - let ray = Ray::new(to_vec(& unsafe{*originVec}), to_vec(& unsafe{*dirVec})); + let ray = Ray::new(to_vec(& unsafe{*origin_vec}), to_vec(& unsafe{*dir_vec})); let mut i = 0; for x in bvh.traverse_iterator(&ray, &shapes) { @@ -222,21 +222,21 @@ pub extern fn query_ray(bvhRef: *const BVHRef, originVec: *const Vector3D, dirVe } #[no_mangle] -pub extern fn batch_query_rays(bvhRef: *const BVHRef, originVecs: *mut Vector3D, dirVecs: *mut Vector3D, hits: *mut i32, rayCount: i32, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) +pub extern fn batch_query_rays(bvh_ref: *const BVHRef, origin_vecs: *mut Vector3D, dir_vecs: *mut Vector3D, hits: *mut i32, ray_count: i32, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let origins = unsafe { std::slice::from_raw_parts_mut(originVecs, rayCount as usize) }; - let dirs = unsafe { std::slice::from_raw_parts_mut(dirVecs, rayCount as usize) }; - let hits = unsafe { std::slice::from_raw_parts_mut(hits, rayCount as usize) }; + let origins = unsafe { std::slice::from_raw_parts_mut(origin_vecs, ray_count as usize) }; + let dirs = unsafe { std::slice::from_raw_parts_mut(dir_vecs, ray_count as usize) }; + let hits = unsafe { std::slice::from_raw_parts_mut(hits, ray_count as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let bvh = bvh::BVH { nodes: v }; let mut i = 0; - for r in 0..rayCount as usize { + for r in 0..ray_count as usize { let ray = Ray::new(to_vec(&origins[r]), to_vec(&dirs[r])); let mut res = 0; for x in bvh.traverse_iterator(&ray, &shapes) { @@ -254,12 +254,12 @@ pub extern fn batch_query_rays(bvhRef: *const BVHRef, originVecs: *mut Vector3D, #[no_mangle] -pub extern fn query_sphere(bvhRef: *const BVHRef, center: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern fn query_sphere(bvh_ref: *const BVHRef, center: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let bvh = bvh::BVH { nodes: v @@ -280,12 +280,12 @@ pub extern fn query_sphere(bvhRef: *const BVHRef, center: *const Vector3D, radiu } #[no_mangle] -pub extern fn query_capsule(bvhRef: *const BVHRef, start: *const Vector3D, end: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern fn query_capsule(bvh_ref: *const BVHRef, start: *const Vector3D, end: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let bvh = bvh::BVH { nodes: v @@ -306,12 +306,12 @@ pub extern fn query_capsule(bvhRef: *const BVHRef, start: *const Vector3D, end: } #[no_mangle] -pub extern fn query_aabb(bvhRef: *const BVHRef, bounds: *const BoundsD, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern fn query_aabb(bvh_ref: *const BVHRef, bounds: *const BoundsD, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let bvh = bvh::BVH { nodes: v @@ -336,12 +336,12 @@ pub extern fn query_aabb(bvhRef: *const BVHRef, bounds: *const BoundsD, boxes: * } #[no_mangle] -pub extern fn query_obb(bvhRef: *const BVHRef, ori: *const QuaternionD, extents: *const Vector3D, center: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern fn query_obb(bvh_ref: *const BVHRef, ori: *const QuaternionD, extents: *const Vector3D, center: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let bvh = bvh::BVH { nodes: v @@ -366,18 +366,18 @@ pub extern fn query_obb(bvhRef: *const BVHRef, ori: *const QuaternionD, extents: } #[no_mangle] -pub extern fn free_bvh(bvhRef: *const BVHRef) +pub extern fn free_bvh(bvh_ref: *const BVHRef) { - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let _v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; } #[no_mangle] -pub extern fn add_node(bvhRef: *const BVHRef, new_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef +pub extern fn add_node(bvh_ref: *const BVHRef, new_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let mut bvh = bvh::BVH { nodes: v @@ -393,11 +393,11 @@ pub extern fn add_node(bvhRef: *const BVHRef, new_shape: i32, boxes: *mut BVHBou } #[no_mangle] -pub extern fn remove_node(bvhRef: *const BVHRef, remove_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef +pub extern fn remove_node(bvh_ref: *const BVHRef, remove_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef { let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvhRef).ptr, (*bvhRef).len as usize, (*bvhRef).cap as usize)}; + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; let mut bvh = bvh::BVH { nodes: v From 196560cdb6736080a6f5b194cfceb8e24c2545b4 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 10 Jul 2021 22:11:29 -0400 Subject: [PATCH 03/37] parallelize bvh build using rayon --- Cargo.toml | 1 + src/bounding_hierarchy.rs | 2 +- src/bvh/bvh_impl.rs | 153 ++++++++++++++++++++++++++++++-------- src/bvh/optimization.rs | 4 +- src/flat_bvh.rs | 28 ++++--- src/lib.rs | 153 +++++++++++++++++++++++++++++++++++--- src/ray.rs | 1 + src/testbase.rs | 2 +- 8 files changed, 289 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e3ab6b4..b1e641e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ rand = "0.8" log = "0.4" num = "0.4" glam = "0.15" +rayon = "1.5.1" [dev-dependencies] proptest = "1.0" diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index db6039e..c87cdd9 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -9,7 +9,7 @@ use crate::aabb::AABB; /// [`BoundingHierarchy`]: struct.BoundingHierarchy.html /// #[allow(clippy::upper_case_acronyms)] -pub trait BHShape: Bounded { +pub trait BHShape: Bounded + Sync + Send { /// Sets the index of the referenced [`BoundingHierarchy`] node. /// /// [`BoundingHierarchy`]: struct.BoundingHierarchy.html diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 04c0b7e..40e0132 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -10,7 +10,13 @@ use crate::bvh::iter::BVHTraverseIterator; use crate::utils::{concatenate_vectors, joint_aabb_of_shapes, Bucket}; use crate::Point3; use crate::EPSILON; +use std::slice; +use rayon::prelude::*; use std::iter::repeat; +use std::sync::atomic::{AtomicUsize, Ordering}; + + +pub static BUILD_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0); /// The [`BVHNode`] enum that describes a node in a [`BVH`]. /// It's either a leaf node and references a shape (by holding its index) @@ -258,10 +264,14 @@ impl BVHNode { pub fn build( shapes: &mut [T], indices: &[usize], - nodes: &mut Vec, + nodes: &mut [BVHNode], parent_index: usize, depth: u32, + node_index: usize ) -> usize { + + //println!("Building node={}", node_index); + // Helper function to accumulate the AABB joint and the centroids AABB fn grow_convex_hull(convex_hull: (AABB, AABB), shape_aabb: &AABB) -> (AABB, AABB) { let center = &shape_aabb.center(); @@ -273,30 +283,54 @@ impl BVHNode { ) } - let mut convex_hull = Default::default(); - for index in indices { - convex_hull = grow_convex_hull(convex_hull, &shapes[*index].aabb()); - } + + let use_parallel_hull = false; + + let mut parallel_recurse = false; + if nodes.len() > 64 { + let avail_threads = BUILD_THREAD_COUNT.load(Ordering::Relaxed); + if avail_threads > 0 { + let exchange = BUILD_THREAD_COUNT.compare_exchange(avail_threads, avail_threads - 1, Ordering::Relaxed, Ordering::Relaxed); + match exchange { + Ok(_) => parallel_recurse = true, + Err(_) => () + }; + } + }; + + + + let convex_hull = if use_parallel_hull { + indices.par_iter().fold(|| (AABB::default(), AABB::default()), + |convex_hull, i| grow_convex_hull(convex_hull, &shapes[*i].aabb())).reduce(|| (AABB::default(), AABB::default()), |a , b| (a.0.join(&b.0), a.1.join(&b.1))) + } else { + let mut convex_hull = Default::default(); + + for index in indices { + convex_hull = grow_convex_hull(convex_hull, &shapes[*index].aabb()); + }; + convex_hull + }; + let (aabb_bounds, centroid_bounds) = convex_hull; // If there is only one element left, don't split anymore if indices.len() == 1 { let shape_index = indices[0]; - let node_index = nodes.len(); - nodes.push(BVHNode::Leaf { + nodes[0] = BVHNode::Leaf { parent_index, depth, shape_index, - }); + }; // Let the shape know the index of the node that represents it. shapes[shape_index].set_bh_node_index(node_index); + //println!("slice_i={} parent={}", node_index, parent_index); return node_index; } // From here on we handle the recursive case. This dummy is required, because the children // must know their parent, and it's easier to update one parent node than the child nodes. - let node_index = nodes.len(); - nodes.push(BVHNode::create_dummy()); + nodes[0] = BVHNode::create_dummy(); // Find the axis along which the shapes are spread the most. let split_axis = centroid_bounds.largest_axis(); @@ -312,11 +346,28 @@ impl BVHNode { 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 next_nodes = &mut nodes[1..]; + let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); + let child_l_index = node_index + 1; + let child_r_index = node_index + 1 + l_nodes.len(); // Proceed recursively. - 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); + if parallel_recurse { + // This is safe because the indices represent unique shapes and we'll never write to the same one + let (shapes_a, shapes_b) = unsafe { + let ptr = shapes.as_mut_ptr(); + let len = shapes.len(); + let shapes_a = slice::from_raw_parts_mut(ptr, len); + let shapes_b = slice::from_raw_parts_mut(ptr, len); + (shapes_a, shapes_b) + }; + rayon::join(|| BVHNode::build(shapes_a, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index), + || BVHNode::build(shapes_b, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); + BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); + } else { + BVHNode::build(shapes, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index); + BVHNode::build(shapes, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index); + } + //println!("{:?}", (parent_index, child_l_index, child_l_indices.len(), child_r_index, child_r_indices.len())); (child_l_index, child_l_aabb, child_r_index, child_r_aabb) } else { // Create six `Bucket`s, and six index assignment vector. @@ -369,18 +420,38 @@ impl BVHNode { let child_l_indices = concatenate_vectors(l_assignments); let child_r_indices = concatenate_vectors(r_assignments); + + let next_nodes = &mut nodes[1..]; + let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); + + let child_l_index = node_index + 1; + let child_r_index = node_index + 1 + l_nodes.len(); // Proceed recursively. - 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); + + if parallel_recurse { + let (shapes_a, shapes_b) = unsafe { + let ptr = shapes.as_mut_ptr(); + let len = shapes.len(); + let shapes_a = slice::from_raw_parts_mut(ptr, len); + let shapes_b = slice::from_raw_parts_mut(ptr, len); + (shapes_a, shapes_b) + }; + rayon::join(|| BVHNode::build(shapes_a, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index), + || BVHNode::build(shapes_b, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); + BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); + } else { + + BVHNode::build(shapes, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index); + BVHNode::build(shapes, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index); + } + //println!("{:?}", (parent_index, child_l_index, child_l_indices.len(), child_r_index, child_r_indices.len())); (child_l_index, child_l_aabb, child_r_index, child_r_aabb) }; // Construct the actual data structure and replace the dummy node. assert!(!child_l_aabb.is_empty()); assert!(!child_r_aabb.is_empty()); - nodes[node_index] = BVHNode::Node { + nodes[0] = BVHNode::Node { parent_index, depth, child_l_aabb, @@ -448,12 +519,31 @@ impl BVH { /// pub fn build(shapes: &mut [Shape]) -> BVH { let indices = (0..shapes.len()).collect::>(); - let expected_node_count = shapes.len() * 2; + let expected_node_count = shapes.len() * 2 - 1; let mut nodes = Vec::with_capacity(expected_node_count); - BVHNode::build(shapes, &indices, &mut nodes, 0, 0); + unsafe { + nodes.set_len(expected_node_count); + } + //println!("shapes={} nodes={}", shapes.len(), nodes.len()); + let n = nodes.as_mut_slice(); + BVHNode::build(shapes, &indices, n, 0, 0, 0); BVH { nodes } } + pub fn rebuild(&mut self, shapes: &mut [Shape]) { + let indices = (0..shapes.len()).collect::>(); + let expected_node_count = shapes.len() * 2 - 1; + let additional_nodes = self.nodes.capacity() as i32 - expected_node_count as i32; + if additional_nodes > 0 { + self.nodes.reserve(additional_nodes as usize); + } + unsafe { + self.nodes.set_len(expected_node_count); + } + let n = self.nodes.as_mut_slice(); + BVHNode::build(shapes, &indices, n, 0, 0, 0); + } + /// Traverses the [`BVH`]. /// Returns a subset of `shapes`, in which the [`AABB`]s of the elements were hit by `ray`. /// @@ -636,14 +726,14 @@ impl BVH { // swap the shape to the end and update the node to still point at the right shape let dead_node_index = bad_shape.bh_node_index(); - println!("delete_i={}", dead_node_index); + //println!("delete_i={}", dead_node_index); let dead_node = self.nodes[dead_node_index]; let parent_index = dead_node.parent(); - println!("parent_i={}", parent_index); + //println!("parent_i={}", parent_index); let gp_index = self.nodes[parent_index].parent(); - println!("{}->{}->{}", gp_index, parent_index, dead_node_index); + //println!("{}->{}->{}", gp_index, parent_index, dead_node_index); let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r() } else { self.nodes[parent_index].child_l() }; let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r_aabb() } else { self.nodes[parent_index].child_l_aabb() }; // TODO: fix potential issue leaving empty spot in self.nodes @@ -661,10 +751,10 @@ impl BVH { } else { let box_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_aabb_mut() } else { self.nodes[gp_index].child_r_aabb_mut() }; - println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); + //println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); *box_to_change = sibling_box; let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_mut() } else { self.nodes[gp_index].child_r_mut() }; - println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); + //println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); *ref_to_change = sibling_index; *self.nodes[sibling_index].parent_mut() = gp_index; let new_depth = self.nodes[sibling_index].depth() - 1; @@ -699,7 +789,7 @@ impl BVH { let parent_index = self.nodes[node_index].parent(); match self.nodes[parent_index] { BVHNode::Leaf { ..} => { - println!("skip setting parent={}", parent_index); + //println!("skip setting parent={}", parent_index); self.nodes.truncate(end); return; } @@ -708,7 +798,7 @@ impl BVH { let parent = self.nodes[parent_index]; let moved_left = parent.child_l() == end; let ref_to_change = if moved_left { self.nodes[parent_index].child_l_mut() } else { self.nodes[parent_index].child_r_mut() }; - println!("on {} changing {}=>{}", parent_index, ref_to_change, node_index); + //println!("on {} changing {}=>{}", parent_index, ref_to_change, node_index); *ref_to_change = node_index; match self.nodes[node_index] { @@ -1041,7 +1131,9 @@ mod tests { #[test] /// Tests whether the building procedure succeeds in not failing. fn test_build_bvh() { - build_some_bh::(); + let (shapes, bvh) = build_some_bh::(); + bvh.is_consistent(shapes.as_slice()); + bvh.pretty_print(); } #[test] @@ -1074,6 +1166,7 @@ mod tests { bvh.add_node(&mut shapes, len); bvh.pretty_print(); + bvh.rebuild(&mut shapes); let res = bvh.traverse(&test, &shapes); assert_eq!(res.len(), 1); @@ -1121,6 +1214,8 @@ mod tests { let mut bvh = BVH::build(&mut shapes); + bvh.pretty_print(); + bvh.assert_consistent(shapes.as_slice()); fn test_x(bvh: &BVH, x: f64, count: usize, shapes: &[UnitBox]) { let dir = Vector3::new(0.0, -1.0, 0.0); diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 157adde..2f86d99 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -999,7 +999,7 @@ mod bench { mut triangles: &mut Vec, bounds: &AABB, percent: f64, - max_offset: Option, + max_offset: Option, iterations: usize, b: &mut ::test::Bencher, ) { @@ -1052,7 +1052,7 @@ mod bench { mut triangles: &mut Vec, bounds: &AABB, percent: f64, - max_offset: Option, + max_offset: Option, iterations: usize, b: &mut ::test::Bencher, ) { diff --git a/src/flat_bvh.rs b/src/flat_bvh.rs index 9a05fbe..29d3978 100644 --- a/src/flat_bvh.rs +++ b/src/flat_bvh.rs @@ -44,11 +44,12 @@ impl BVHNode { /// Creates a flat node from a `BVH` inner node and its `AABB`. Returns the next free index. /// TODO: change the algorithm which pushes `FlatNode`s to a vector to not use indices this /// much. Implement an algorithm which writes directly to a writable slice. - fn create_flat_branch( + fn create_flat_branch( &self, nodes: &[BVHNode], this_aabb: &AABB, vec: &mut Vec, + shapes: &[T], next_free: usize, constructor: &F, ) -> usize @@ -61,7 +62,7 @@ impl BVHNode { assert_eq!(vec.len() - 1, next_free); // Create subtree. - let index_after_subtree = self.flatten_custom(nodes, vec, next_free + 1, constructor); + let index_after_subtree = self.flatten_custom(nodes, vec, shapes, next_free + 1, constructor); // Replace dummy node by actual node with the entry index pointing to the subtree // and the exit index pointing to the next node after the subtree. @@ -80,10 +81,11 @@ impl BVHNode { /// /// [`BVH`]: ../bvh/struct.BVH.html /// - pub fn flatten_custom( + pub fn flatten_custom( &self, nodes: &[BVHNode], vec: &mut Vec, + shapes: &[T], next_free: usize, constructor: &F, ) -> usize @@ -102,6 +104,7 @@ impl BVHNode { nodes, child_l_aabb, vec, + shapes, next_free, constructor, ); @@ -109,6 +112,7 @@ impl BVHNode { nodes, child_r_aabb, vec, + shapes, index_after_child_l, constructor, ) @@ -117,7 +121,7 @@ impl BVHNode { let mut next_shape = next_free; next_shape += 1; let leaf_node = constructor( - &AABB::empty(), + &shapes[shape_index].aabb(), u32::max_value(), next_shape as u32, shape_index as u32, @@ -225,12 +229,12 @@ impl BVH { /// let bvh = BVH::build(&mut shapes); /// let custom_flat_bvh = bvh.flatten_custom(&custom_constructor); /// ``` - pub fn flatten_custom(&self, constructor: &F) -> Vec + pub fn flatten_custom(&self, shapes: &[T], constructor: &F) -> Vec where F: Fn(&AABB, u32, u32, u32) -> FNodeType, { let mut vec = Vec::new(); - self.nodes[0].flatten_custom(&self.nodes, &mut vec, 0, constructor); + self.nodes[0].flatten_custom(&self.nodes, &mut vec, shapes, 0, constructor); vec } @@ -293,8 +297,8 @@ impl BVH { /// let bvh = BVH::build(&mut shapes); /// let flat_bvh = bvh.flatten(); /// ``` - pub fn flatten(&self) -> FlatBVH { - self.flatten_custom(&|aabb, entry, exit, shape| FlatNode { + pub fn flatten(&self, shapes: &[T]) -> FlatBVH { + self.flatten_custom(shapes, &|aabb, entry, exit, shape| FlatNode { aabb: *aabb, entry_index: entry, exit_index: exit, @@ -311,7 +315,7 @@ impl BoundingHierarchy for FlatBVH { /// fn build(shapes: &mut [T]) -> FlatBVH { let bvh = BVH::build(shapes); - bvh.flatten() + bvh.flatten(shapes) } /// Traverses a [`FlatBVH`] structure iteratively. @@ -390,8 +394,8 @@ impl BoundingHierarchy for FlatBVH { if node.entry_index == u32::max_value() { // If the entry_index is MAX_UINT32, then it's a leaf node. - let shape = &shapes[node.shape_index as usize]; - if ray.intersects_aabb(&shape.aabb()) { + if ray.intersects_aabb(&node.aabb) { + let shape = &shapes[node.shape_index as usize]; hit_shapes.push(shape); } @@ -463,7 +467,7 @@ mod bench { let bvh = BVH::build(&mut triangles); b.iter(|| { - bvh.flatten(); + bvh.flatten(&triangles); }); } #[bench] diff --git a/src/lib.rs b/src/lib.rs index c1b4a8a..8fcb2c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,10 +96,13 @@ use bvh::BVHNode; use ray::Ray; use shapes::{Capsule, Sphere, OBB}; use glam::DQuat; +use bvh::BUILD_THREAD_COUNT; +use std::sync::atomic::{AtomicUsize, Ordering}; #[cfg(test)] mod testbase; + #[no_mangle] pub extern fn add_numbers(number1: i32, number2: i32) -> i32 { println!("Hello from rust!"); @@ -114,11 +117,19 @@ pub struct Vector3D { pub z: f64 } +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct Float3 { + pub x: f32, + pub y: f32, + pub z: f32 +} + #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct BoundsD { - pub center: Vector3D, - pub extents: Vector3D + pub min: Vector3D, + pub max: Vector3D } #[repr(C)] @@ -145,12 +156,34 @@ pub struct QuaternionD { pub w: f64 } +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct Point32 { + pub x: f32, + pub y: f32, + pub z: f32 +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct AABB32 { + pub min: Point32, + pub max: Point32 +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct FlatNode32 { + pub aabb: AABB32, + pub entry_index: u32, + pub exit_index: u32, + pub shape_index: u32 +} + impl Bounded for BVHBounds { fn aabb(&self) -> AABB { - let half_size = to_vec(&self.bounds.extents); - let pos = to_vec(&self.bounds.center); - let min = pos - half_size; - let max = pos + half_size; + let min = to_vec(&self.bounds.min); + let max = to_vec(&self.bounds.max); AABB::with_bounds(min, max) } } @@ -181,6 +214,36 @@ pub fn to_quat(a: &QuaternionD) -> DQuat { DQuat::from_xyzw(a.x, a.y, a.z, a.w) } +#[no_mangle] +pub extern fn set_build_thread_count(count: i32) +{ + BUILD_THREAD_COUNT.store(count as usize, Ordering::Relaxed); +} + + +#[no_mangle] +pub extern fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) +{ + let a = unsafe {*a_ptr}; + + let a = glam::Vec3::new(a.x, a.y, a.z); + let b = unsafe {*b_ptr}; + let b = glam::Vec3::new(b.x, b.y, b.z); + let mut c = glam::Vec3::new(0.0, 0.0, 0.0); + + for i in 0 .. 100000 { + c = a + b + c; + } + + unsafe { + *out_ptr = Float3 { + x: c.x, + y: c.y, + z: c.z + }; + } +} + #[no_mangle] pub extern fn build_bvh(a: *mut BVHBounds, count: i32) -> BVHRef { @@ -195,6 +258,29 @@ pub extern fn build_bvh(a: *mut BVHBounds, count: i32) -> BVHRef BVHRef { ptr: p, len: len as i32, cap: cap as i32 } } + + +#[no_mangle] +pub extern fn rebuild_bvh(bvh_ref: *const BVHRef, a: *mut BVHBounds, count: i32) -> BVHRef +{ + let mut s = unsafe { std::slice::from_raw_parts_mut(a, count as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; + + let mut bvh = bvh::BVH { + nodes: v + }; + + bvh.rebuild(s); + + let len = bvh.nodes.len(); + let cap = bvh.nodes.capacity(); + let p = bvh.nodes.as_mut_ptr(); + std::mem::forget(bvh.nodes); + + BVHRef { ptr: p, len: len as i32, cap: cap as i32 } +} + #[no_mangle] pub extern fn query_ray(bvh_ref: *const BVHRef, origin_vec: *const Vector3D, dir_vec: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 { @@ -317,10 +403,8 @@ pub extern fn query_aabb(bvh_ref: *const BVHRef, bounds: *const BoundsD, boxes: nodes: v }; - let half_size = to_vec(&unsafe { *bounds }.extents); - let pos = to_vec(&unsafe { *bounds }.center); - let min = pos - half_size; - let max = pos + half_size; + let min = to_vec(&unsafe { *bounds }.min); + let max = to_vec(&unsafe { *bounds }.max); let test_shape = AABB::with_bounds(min, max); let mut i = 0; @@ -412,4 +496,53 @@ pub extern fn remove_node(bvh_ref: *const BVHRef, remove_shape: i32, boxes: *mut BVHRef { ptr: p, len: len as i32, cap: cap as i32 } } +#[no_mangle] +pub extern fn flatten_bvh(bvh_ref: *const BVHRef, boxes: *mut BVHBounds, count: i32, res: *mut FlatNode32, res_count: i32) -> i32 +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let results = unsafe { std::slice::from_raw_parts_mut(res, res_count as usize) }; + + let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; + + let bvh = bvh::BVH { + nodes: v + }; + + let flattened = bvh.flatten_custom(shapes, &node_32_constructor); + + for i in 0..flattened.len() { + results[i] = flattened[i]; + } + + std::mem::forget(bvh.nodes); + + flattened.len() as i32 +} + +pub fn node_32_constructor(aabb: &AABB, entry_index: u32, exit_index: u32, shape_index: u32) -> FlatNode32 +{ + let min = Point32 { + x: aabb.min.x as f32, + y: aabb.min.y as f32, + z: aabb.min.z as f32 + }; + let max = Point32 { + x: aabb.max.x as f32, + y: aabb.max.y as f32, + z: aabb.max.z as f32 + }; + let b = AABB32 { + min, + max + }; + FlatNode32 { + aabb: b, + entry_index, + exit_index, + shape_index + } +} + + + diff --git a/src/ray.rs b/src/ray.rs index f10e24d..fbde5df 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -487,6 +487,7 @@ mod tests { mod bench { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; + use crate::bounding_hierarchy::IntersectionTest; use crate::aabb::AABB; use crate::ray::Ray; diff --git a/src/testbase.rs b/src/testbase.rs index 871d6ed..b4b9709 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -15,7 +15,7 @@ use rand::seq::SliceRandom; use rand::SeedableRng; use crate::aabb::{Bounded, AABB}; -use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; +use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; use crate::ray::Ray; /// A vector represented as a tuple From 07c4c05f14c87e4ac97f5c00ee3d66afbefff8d0 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sun, 11 Jul 2021 13:04:16 -0400 Subject: [PATCH 04/37] testing rayon parallelization further --- src/bvh/bvh_impl.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 40e0132..6e48dfa 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -16,7 +16,7 @@ use std::iter::repeat; use std::sync::atomic::{AtomicUsize, Ordering}; -pub static BUILD_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0); +pub static BUILD_THREAD_COUNT: AtomicUsize = AtomicUsize::new(20); /// The [`BVHNode`] enum that describes a node in a [`BVH`]. /// It's either a leaf node and references a shape (by holding its index) @@ -284,10 +284,12 @@ impl BVHNode { } - let use_parallel_hull = false; + let mut use_parallel_hull = false; let mut parallel_recurse = false; - if nodes.len() > 64 { + if nodes.len() > 128 { + parallel_recurse = true; + /* let avail_threads = BUILD_THREAD_COUNT.load(Ordering::Relaxed); if avail_threads > 0 { let exchange = BUILD_THREAD_COUNT.compare_exchange(avail_threads, avail_threads - 1, Ordering::Relaxed, Ordering::Relaxed); @@ -295,7 +297,12 @@ impl BVHNode { Ok(_) => parallel_recurse = true, Err(_) => () }; + + if nodes.len() > 4096 { + //use_parallel_hull = true; + } } + */ }; @@ -362,7 +369,7 @@ impl BVHNode { }; rayon::join(|| BVHNode::build(shapes_a, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index), || BVHNode::build(shapes_b, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); - BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); + //BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); } else { BVHNode::build(shapes, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index); BVHNode::build(shapes, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index); @@ -438,7 +445,7 @@ impl BVHNode { }; rayon::join(|| BVHNode::build(shapes_a, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index), || BVHNode::build(shapes_b, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); - BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); + //BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); } else { BVHNode::build(shapes, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index); From 3deb2e33532be1c4e08db54157a0b3600276ddd3 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sun, 11 Jul 2021 15:26:16 -0400 Subject: [PATCH 05/37] bench test for flamegraph --- Cargo.toml | 3 +- src/bvh/bvh_impl.rs | 2 +- src/main.rs | 127 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 114 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1e641e..a96fdef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,10 @@ log = "0.4" num = "0.4" glam = "0.15" rayon = "1.5.1" +obj-rs = "0.6" [dev-dependencies] proptest = "1.0" -obj-rs = "0.6" float_eq = "0.6" criterion = "0.3" @@ -37,6 +37,7 @@ bench = [] [profile.release] lto = true codegen-units = 1 +debug = true [profile.bench] lto = true diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 6e48dfa..a1c087f 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -288,7 +288,7 @@ impl BVHNode { let mut parallel_recurse = false; if nodes.len() > 128 { - parallel_recurse = true; + //parallel_recurse = true; /* let avail_threads = BUILD_THREAD_COUNT.load(Ordering::Relaxed); if avail_threads > 0 { diff --git a/src/main.rs b/src/main.rs index 0d8116c..f06d763 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,9 @@ use bvh_f64::bounding_hierarchy::BHShape; use bvh_f64::bvh::BVH; use bvh_f64::ray::Ray; use bvh_f64::{Point3, Vector3}; +use obj::*; +use obj::raw::object::Polygon; +use num::{FromPrimitive, Integer}; #[derive(Debug)] struct Sphere { @@ -30,21 +33,113 @@ impl BHShape for Sphere { } } -pub fn main() { - let mut spheres = Vec::new(); - for i in 0..1000000u32 { - let position = Point3::new(i as f64, i as f64, i as f64); - let radius = (i % 10) as f64 + 1.0; - spheres.push(Sphere { - position, - radius, +/// A triangle struct. Instance of a more complex `Bounded` primitive. +#[derive(Debug)] +pub struct Triangle { + pub a: Point3, + pub b: Point3, + pub c: Point3, + aabb: AABB, + node_index: usize, +} + +impl Triangle { + pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { + Triangle { + a, + b, + c, + aabb: AABB::empty().grow(&a).grow(&b).grow(&c), node_index: 0, - }); - } - let bvh = BVH::build(&mut spheres); - let origin = Point3::new(0.0, 0.0, 0.0); - let direction = Vector3::new(1.0, 0.0, 0.0); - let ray = Ray::new(origin, direction); - let hit_sphere_aabbs = bvh.traverse(&ray, &spheres); - dbg!(hit_sphere_aabbs); + } + } +} + +impl Bounded for Triangle { + fn aabb(&self) -> AABB { + self.aabb + } +} + +impl BHShape for Triangle { + fn set_bh_node_index(&mut self, index: usize) { + self.node_index = index; + } + + fn bh_node_index(&self) -> usize { + self.node_index + } +} + +impl FromRawVertex for Triangle { + fn process( + vertices: Vec<(f32, f32, f32, f32)>, + _: Vec<(f32, f32, f32)>, + _: Vec<(f32, f32, f32)>, + polygons: Vec, + ) -> ObjResult<(Vec, Vec)> { + // Convert the vertices to `Point3`s. + let points = vertices + .into_iter() + .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) + .collect::>(); + + // Estimate for the number of triangles, assuming that each polygon is a triangle. + let mut triangles = Vec::with_capacity(polygons.len()); + { + let mut push_triangle = |indices: &Vec| { + let mut indices_iter = indices.iter(); + let anchor = points[*indices_iter.next().unwrap()]; + let mut second = points[*indices_iter.next().unwrap()]; + for third_index in indices_iter { + let third = points[*third_index]; + triangles.push(Triangle::new(anchor, second, third)); + second = third; + } + }; + + // Iterate over the polygons and populate the `Triangle`s vector. + for polygon in polygons.into_iter() { + match polygon { + Polygon::P(ref vec) => push_triangle(vec), + Polygon::PT(ref vec) | Polygon::PN(ref vec) => { + push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) + } + Polygon::PTN(ref vec) => { + push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) + } + } + } + } + Ok((triangles, Vec::new())) + } +} + +pub fn load_sponza_scene() -> (Vec, AABB) { + use std::fs::File; + use std::io::BufReader; + + let file_input = + BufReader::new(File::open("media/sponza.obj").expect("Failed to open .obj file.")); + let sponza_obj: Obj = load_obj(file_input).expect("Failed to decode .obj file data."); + let triangles = sponza_obj.vertices; + + let mut bounds = AABB::empty(); + for triangle in &triangles { + bounds.join_mut(&triangle.aabb()); + } + + (triangles, bounds) +} + + + + +pub fn main() { + let (mut triangles, bounds) = load_sponza_scene(); + let mut bvh = BVH::build(triangles.as_mut_slice()); + + for i in 0..50 { + bvh.rebuild(triangles.as_mut_slice()); + } } From e5cc3ec2d107233b4687d19bfcb63044c567f87c Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Wed, 14 Jul 2021 06:33:31 -0400 Subject: [PATCH 06/37] fixing bugs in add/remove, change optimize to just add then remove the nodes --- Cargo.toml | 3 + src/bvh/bvh_impl.rs | 414 +++++++++++++++++++++++++++++++--------- src/bvh/mod.rs | 1 + src/bvh/optimization.rs | 119 +++++++++--- src/bvh/qbvh.rs | 120 ++++++++++++ src/lib.rs | 2 +- src/main.rs | 2 +- src/testbase.rs | 4 +- 8 files changed, 540 insertions(+), 125 deletions(-) create mode 100644 src/bvh/qbvh.rs diff --git a/Cargo.toml b/Cargo.toml index a96fdef..7199ea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ obj-rs = "0.6" proptest = "1.0" float_eq = "0.6" criterion = "0.3" +itertools = "0.10.1" +parry3d-f64 = "0.6.0" [features] bench = [] @@ -42,3 +44,4 @@ debug = true [profile.bench] lto = true codegen-units = 1 +debug = true diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index a1c087f..dad7c99 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -35,9 +35,6 @@ pub enum BVHNode { /// The node's parent. parent_index: usize, - /// The node's depth. - depth: u32, - /// The shape contained in this leaf. shape_index: usize, }, @@ -46,9 +43,6 @@ pub enum BVHNode { /// The node's parent. parent_index: usize, - /// The node's depth. - depth: u32, - /// Index of the left subtree's root node. child_l_index: usize, @@ -70,38 +64,32 @@ impl PartialEq for BVHNode { ( &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, @@ -201,17 +189,14 @@ impl BVHNode { } /// Returns the depth of the node. The root node has depth `0`. - pub fn depth(&self) -> u32 { - match *self { - BVHNode::Node { depth, .. } | BVHNode::Leaf { depth, .. } => depth, - } - } - - /// Returns the depth of the node. The root node has depth `0`. - pub fn depth_mut(&mut self) -> &mut u32 { - match *self { - BVHNode::Node { ref mut depth, .. } | BVHNode::Leaf { ref mut depth, .. } => depth, + pub fn depth(&self, nodes: &[BVHNode]) -> u32 { + let parent_i = self.parent(); + if parent_i == 0 { + if nodes[parent_i].eq(&self) { + return 0; + } } + return 1 + nodes[parent_i].depth(nodes); } /// Gets the `AABB` for a `BVHNode`. @@ -251,7 +236,6 @@ impl BVHNode { fn create_dummy() -> BVHNode { BVHNode::Leaf { parent_index: 0, - depth: 0, shape_index: 0, } } @@ -326,7 +310,6 @@ impl BVHNode { let shape_index = indices[0]; nodes[0] = BVHNode::Leaf { parent_index, - depth, shape_index, }; // Let the shape know the index of the node that represents it. @@ -460,7 +443,6 @@ impl BVHNode { assert!(!child_r_aabb.is_empty()); nodes[0] = BVHNode::Node { parent_index, - depth, child_l_aabb, child_l_index, child_r_aabb, @@ -596,7 +578,6 @@ impl BVH { child_l_index, child_r_aabb, child_r_index, - depth, parent_index } => { let left_expand = child_l_aabb.join(&shape_aabb); @@ -614,10 +595,10 @@ impl BVH { // compared SA of the options if merged < send_left.min(send_right) * merge_discount { + //println!("Merging left and right trees"); // Merge left and right trees let l_index = self.nodes.len(); let new_left = BVHNode::Leaf { - depth: depth + 1, parent_index: i, shape_index: new_shape_index }; @@ -630,24 +611,25 @@ impl BVH { child_l_index, child_r_aabb: child_r_aabb.clone(), child_r_index, - depth: depth + 1, parent_index: i }; self.nodes.push(new_right); + *self.nodes[child_r_index].parent_mut() = r_index; + *self.nodes[child_l_index].parent_mut() = r_index; self.nodes[i] = BVHNode::Node { child_l_aabb: shape_aabb, child_l_index: l_index, child_r_aabb: merged_aabb, child_r_index: r_index, - depth, parent_index }; - self.fix_depth(l_index, depth + 1); - self.fix_depth(r_index, depth + 1); + //self.fix_depth(l_index, depth + 1); + //self.fix_depth(r_index, depth + 1); return; } else if send_left < send_right { // send new box down left side + //println!("Sending left"); if i == child_l_index { panic!("broken loop"); @@ -658,12 +640,12 @@ impl BVH { child_l_index, child_r_aabb, child_r_index, - depth, parent_index }; i = child_l_index; } else { // send new box down right + //println!("Sending right"); if i == child_r_index { panic!("broken loop"); @@ -674,17 +656,16 @@ impl BVH { child_l_index, child_r_aabb, child_r_index, - depth, parent_index }; i = child_r_index; } } - BVHNode::Leaf { shape_index, parent_index, depth} => { + BVHNode::Leaf { shape_index, parent_index} => { + //println!("Splitting leaf"); // Split leaf into 2 nodes and insert the new box let l_index = self.nodes.len(); let new_left = BVHNode::Leaf { - depth: depth + 1, parent_index: i, shape_index: new_shape_index }; @@ -694,7 +675,6 @@ impl BVH { let child_r_aabb = shapes[shape_index].aabb(); let child_r_index = self.nodes.len(); let new_right = BVHNode::Leaf { - depth: depth + 1, parent_index: i, shape_index: shape_index }; @@ -706,10 +686,10 @@ impl BVH { child_l_index: l_index, child_r_aabb, child_r_index, - depth, parent_index }; self.nodes[i] = new_node; + self.fix_aabbs_ascending(shapes, parent_index); return; } } @@ -719,9 +699,10 @@ impl BVH { pub fn remove_node( &mut self, shapes: &mut [T], - deleted_shape_index: usize + deleted_shape_index: usize, + swap_shape: bool, ) { - if shapes.len() < 2 + if self.nodes.len() < 2 { panic!("can't remove a node from a bvh with only one node"); } @@ -738,49 +719,130 @@ impl BVH { let dead_node = self.nodes[dead_node_index]; let parent_index = dead_node.parent(); - //println!("parent_i={}", parent_index); + println!("parent_i={}", parent_index); let gp_index = self.nodes[parent_index].parent(); - //println!("{}->{}->{}", gp_index, parent_index, dead_node_index); + println!("{}->{}->{}", gp_index, parent_index, dead_node_index); + let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r() } else { self.nodes[parent_index].child_l() }; let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r_aabb() } else { self.nodes[parent_index].child_l_aabb() }; // TODO: fix potential issue leaving empty spot in self.nodes // the node swapped to sibling_index should probably be swapped to the end // of the vector and the vector truncated if parent_index == gp_index { - println!("gp == parent {}", parent_index); + // We are removing one of the children of the root node + // The other child needs to become the root node + // The old root node and the dead child then have to be moved + + //println!("gp == parent {}", parent_index); + if parent_index != 0 { + panic!("Circular node that wasn't root parent={} node={}", parent_index, dead_node_index); + } self.nodes.swap(parent_index, sibling_index); + match self.nodes[parent_index].shape_index() { Some(index) => { + *self.nodes[parent_index].parent_mut() = parent_index; shapes[index].set_bh_node_index(parent_index); + self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); + self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); }, - _ => {} + _ => { + *self.nodes[parent_index].parent_mut() = parent_index; + let new_root = self.nodes[parent_index]; + *self.nodes[new_root.child_l()].parent_mut() = parent_index; + *self.nodes[new_root.child_r()].parent_mut() = parent_index; + //println!("set {}'s parent to {}", new_root.child_l(), parent_index); + //println!("set {}'s parent to {}", new_root.child_r(), parent_index); + self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); + self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); + } } - + //println!("nodes_len {}, sib_index {}", self.nodes.len(), sibling_index); + //println!("nodes_len {}", self.nodes.len()); } else { let box_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_aabb_mut() } else { self.nodes[gp_index].child_r_aabb_mut() }; //println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); *box_to_change = sibling_box; + //println!("{} {} {}", gp_index, self.nodes[gp_index].child_l_aabb(), self.nodes[gp_index].child_r_aabb()); let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_mut() } else { self.nodes[gp_index].child_r_mut() }; //println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); *ref_to_change = sibling_index; *self.nodes[sibling_index].parent_mut() = gp_index; - let new_depth = self.nodes[sibling_index].depth() - 1; - *self.nodes[sibling_index].depth_mut() = new_depth; + + self.fix_aabbs_ascending(shapes, gp_index); + //let new_depth = self.nodes[sibling_index].depth() - 1; + //*self.nodes[sibling_index].depth_mut() = new_depth; // remove node and parent + + //println!("---"); + //self.pretty_print(); + //println!("---"); self.swap_and_remove_index(shapes, dead_node_index.max(parent_index)); + + //println!("---"); + //self.pretty_print(); + //println!("---"); self.swap_and_remove_index(shapes, parent_index.min(dead_node_index)); + + //println!("---"); + //self.pretty_print(); + //println!("---"); } + if swap_shape { + let end_shape = shapes.len() - 1; + if deleted_shape_index < end_shape { + shapes.swap(deleted_shape_index, end_shape); + let node_index = shapes[deleted_shape_index].bh_node_index(); + match self.nodes[node_index].shape_index_mut() { + Some(index) => { + *index = deleted_shape_index + }, + _ => {} + } + } + } + } - let end_shape = shapes.len() - 1; - if deleted_shape_index < end_shape { - shapes.swap(deleted_shape_index, end_shape); - let node_index = shapes[deleted_shape_index].bh_node_index(); - match self.nodes[node_index].shape_index_mut() { - Some(index) => { - *index = deleted_shape_index - }, - _ => {} + fn fix_aabbs_ascending( + &mut self, + shapes: &mut [T], + node_index: usize + ) { + let mut index_to_fix = node_index; + while index_to_fix != 0 { + let parent = self.nodes[index_to_fix].parent(); + match self.nodes[parent] { + BVHNode::Node { + parent_index, + child_l_index, + child_r_index, + child_l_aabb, + child_r_aabb + } => { + //println!("checking {} l={} r={}", parent, child_l_index, child_r_index); + let l_aabb = self.nodes[child_l_index].get_node_aabb(shapes); + let r_aabb = self.nodes[child_r_index].get_node_aabb(shapes); + //println!("child_l_aabb {}", l_aabb); + //println!("child_r_aabb {}", r_aabb); + let mut stop = true; + if !l_aabb.relative_eq(&child_l_aabb, EPSILON) { + stop = false; + //println!("setting {} l = {}", parent, l_aabb); + *self.nodes[parent].child_l_aabb_mut() = l_aabb; + } + if !r_aabb.relative_eq(&child_r_aabb, EPSILON) { + stop = false; + //println!("setting {} r = {}", parent, r_aabb); + *self.nodes[parent].child_r_aabb_mut() = r_aabb; + } + if !stop { + index_to_fix = parent_index; + } else { + index_to_fix = 0; + } + } + _ => {index_to_fix = 0} } } } @@ -791,21 +853,22 @@ impl BVH { node_index: usize ) { let end = self.nodes.len() - 1; + //println!("removing node {}", node_index); if node_index != end { self.nodes[node_index] = self.nodes[end]; - let parent_index = self.nodes[node_index].parent(); - match self.nodes[parent_index] { - BVHNode::Leaf { ..} => { - //println!("skip setting parent={}", parent_index); + let node_parent = self.nodes[node_index].parent(); + match self.nodes[node_parent] { + BVHNode::Leaf {parent_index, shape_index} => { + println!("truncating early node_parent={} parent_index={} shape_index={}", node_parent, parent_index, shape_index); self.nodes.truncate(end); return; } _ => { } } - let parent = self.nodes[parent_index]; + let parent = self.nodes[node_parent]; let moved_left = parent.child_l() == end; - let ref_to_change = if moved_left { self.nodes[parent_index].child_l_mut() } else { self.nodes[parent_index].child_r_mut() }; - //println!("on {} changing {}=>{}", parent_index, ref_to_change, node_index); + let ref_to_change = if moved_left { self.nodes[node_parent].child_l_mut() } else { self.nodes[node_parent].child_r_mut() }; + //println!("on {} changing {}=>{}", node_parent, ref_to_change, node_index); *ref_to_change = node_index; match self.nodes[node_index] { @@ -819,6 +882,8 @@ impl BVH { } => { *self.nodes[child_l_index].parent_mut() = node_index; *self.nodes[child_r_index].parent_mut() = node_index; + + //println!("{} {} {}", node_index, self.nodes[node_index].child_l_aabb(), self.nodes[node_index].child_r_aabb()); //let correct_depth //self.fix_depth(child_l_index, ) } @@ -827,7 +892,7 @@ impl BVH { self.nodes.truncate(end); } - +/* pub fn fix_depth( &mut self, curr_node: usize, @@ -852,39 +917,44 @@ impl BVH { } } } - +*/ /// Prints the [`BVH`] in a tree-like visualization. /// /// [`BVH`]: struct.BVH.html /// pub fn pretty_print(&self) { + self.print_node(0); + } + + + pub fn print_node(&self, node_index: usize) { 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(" ").take(depth as usize).collect(); - println!("{}{} child_l {}", padding, child_l_index, child_l_aabb); - print_node(nodes, child_l_index); - println!("{}{} child_r {}", padding, child_r_index, child_r_aabb); - print_node(nodes, child_r_index); - } - BVHNode::Leaf { - shape_index, depth, .. - } => { - let padding: String = repeat(" ").take(depth as usize).collect(); - println!("{}shape\t{:?}", padding, shape_index); - } + match nodes[node_index] { + BVHNode::Node { + child_l_index, + child_r_index, + child_l_aabb, + child_r_aabb, + .. + } => { + let depth = nodes[node_index].depth(nodes); + let padding: String = repeat(" ").take(depth as usize).collect(); + println!("{} node={} parent={}", padding, node_index, nodes[node_index].parent()); + println!("{}{} child_l {}", padding, child_l_index, child_l_aabb); + self.print_node(child_l_index); + println!("{}{} child_r {}", padding, child_r_index, child_r_aabb); + self.print_node(child_r_index); + } + BVHNode::Leaf { + shape_index, .. + } => { + let depth = nodes[node_index].depth(nodes); + let padding: String = repeat(" ").take(depth as usize).collect(); + println!("{} node={} parent={}", padding, node_index, nodes[node_index].parent()); + println!("{}shape\t{:?}", padding, shape_index); } } - print_node(nodes, 0); } /// Verifies that the node at index `node_index` lies inside `expected_outer_aabb`, @@ -903,12 +973,12 @@ impl BVH { match self.nodes[node_index] { BVHNode::Node { parent_index, - depth, child_l_index, child_l_aabb, child_r_index, child_r_aabb, } => { + let depth = self.nodes[node_index].depth(self.nodes.as_slice()); let correct_parent_index = expected_parent_index == parent_index; let correct_depth = expected_depth == depth; let left_aabb_in_parent = @@ -941,9 +1011,9 @@ impl BVH { } BVHNode::Leaf { parent_index, - depth, shape_index, } => { + let depth = self.nodes[node_index].depth(self.nodes.as_slice()); let correct_parent_index = expected_parent_index == parent_index; let correct_depth = expected_depth == depth; let shape_aabb = shapes[shape_index].aabb(); @@ -994,7 +1064,7 @@ impl BVH { "Wrong parent index. Expected: {}; Actual: {}", expected_parent_index, parent ); - let depth = node.depth(); + let depth = node.depth(self.nodes.as_slice()); assert_eq!( expected_depth, depth, "Wrong depth. Expected: {}; Actual: {}", @@ -1045,7 +1115,7 @@ impl BVH { BVHNode::Leaf { shape_index, .. } => { let shape_aabb = shapes[shape_index].aabb(); assert!( - expected_outer_aabb.approx_contains_aabb_eps(&shape_aabb, EPSILON), + if parent != 0 { expected_outer_aabb.relative_eq(&shape_aabb, EPSILON) } else { true }, "Shape's AABB lies outside the expected bounds.\n\tBounds: {}\n\tShape: {}", expected_outer_aabb, shape_aabb @@ -1068,6 +1138,28 @@ impl BVH { // Check if all nodes have been counted from the root node. // If this is false, it means we have a detached subtree. + if (node_count != self.nodes.len()) { + for x in node_count..self.nodes.len() { + let node = self.nodes[x]; + match node { + BVHNode::Node { + parent_index, + child_l_index, + child_l_aabb, + child_r_index, + child_r_aabb, + } => { + println!("{}: parent_index={} child_l {} {} child_r {} {}", x, parent_index, child_l_index, child_l_aabb, child_r_index, child_r_aabb); + } + BVHNode::Leaf { + parent_index, + shape_index, + } => { + println!("{}: parent={} shape={}", x, parent_index, shape_index); + } + } + } + } assert_eq!(node_count, self.nodes.len(), "Detached subtree"); } @@ -1089,6 +1181,14 @@ impl BVH { } = self.nodes[node_index] { let joint_aabb = child_l_aabb.join(&child_r_aabb); + if !joint_aabb.relative_eq(outer_aabb, EPSILON) { + for i in 0..shapes.len() + { + //println!("s#{} {}", i, shapes[i].aabb()) + } + //self.pretty_print(); + println!("{} real_aabb={} stored_aabb={}", node_index, joint_aabb, outer_aabb); + } assert!(joint_aabb.relative_eq(outer_aabb, EPSILON)); self.assert_tight_subtree(child_l_index, &child_l_aabb, shapes); self.assert_tight_subtree(child_r_index, &child_r_aabb, shapes); @@ -1134,6 +1234,9 @@ mod tests { use crate::testbase::{build_some_bh, traverse_some_bh, UnitBox}; use crate::Ray; use crate::bounding_hierarchy::BHShape; + use rand::thread_rng; + use rand::prelude::SliceRandom; + use itertools::Itertools; #[test] /// Tests whether the building procedure succeeds in not failing. @@ -1204,7 +1307,7 @@ mod tests { bvh.pretty_print(); println!("------"); println!("{:?}", bvh.nodes.len()); - bvh.remove_node(&mut shapes, 0); + bvh.remove_node(&mut shapes, 0, true); println!("{:?}", bvh.nodes.len()); println!("------"); @@ -1252,7 +1355,7 @@ mod tests { println!("Ensuring x={} shape[{}] is present", x, delete_i); test_x(&bvh, x as f64, 1, &shapes); println!("Deleting x={} shape[{}]", x, delete_i); - bvh.remove_node(&mut shapes, delete_i); + bvh.remove_node(&mut shapes, delete_i, true); shapes.truncate(shapes.len() - 1); bvh.pretty_print(); println!("Ensuring {} [{}] is gone", x, delete_i); @@ -1261,6 +1364,65 @@ mod tests { } } + #[test] + fn test_random_deletions() { + let xs = -3..3; + let x_values = xs.clone().collect::>(); + for x_values in xs.clone().permutations(x_values.len() - 1) + { + let mut shapes = Vec::new(); + for x in xs.clone() { + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + } + let mut bvh = BVH::build(&mut shapes); + + //bvh.pretty_print(); + for x_i in 0..x_values.len() { + let x = x_values[x_i]; + + let point = Point3::new(x as f64, 0.0, 0.0); + let mut delete_i = 0; + for i in 0..shapes.len() { + if shapes[i].pos.distance_squared(point) < 0.01 { + delete_i = i; + break; + } + } + bvh.remove_node(&mut shapes, delete_i, true); + shapes.truncate(shapes.len() - 1); + assert_eq!(shapes.len(), x_values.len() - x_i); + //println!("--------------------------"); + //bvh.pretty_print(); + bvh.assert_consistent(shapes.as_slice()); + bvh.assert_tight(shapes.as_slice()); + + } + } + } + + + #[test] + fn test_add_consistency() { + let mut shapes = Vec::new(); + for x in -25..25 { + shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + } + + let (left, right) = shapes.split_at_mut(10); + let addable = 10..25; + + let mut bvh = BVH::build(left); + bvh.pretty_print(); + bvh.assert_consistent(left); + + for i in 0..right.len() { + let x = i + 10; + bvh.add_node(&mut shapes, x); + bvh.assert_tight(shapes.as_slice()); + bvh.assert_consistent(shapes.as_slice()); + } + } + @@ -1300,8 +1462,9 @@ mod bench { 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, + intersect_bh, load_sponza_scene,create_n_cubes, default_bounds }; + use crate::bounding_hierarchy::BHShape; #[bench] /// Benchmark the construction of a `BVH` with 1,200 triangles. @@ -1329,6 +1492,57 @@ mod bench { BVH::build(&mut triangles); }); } + + #[cfg(feature = "bench")] + fn add_triangles_bvh(n: usize, b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(n, &bounds); + b.iter(|| { + build_by_add(triangles.as_mut_slice()); + }); + } + + #[cfg(feature = "bench")] + fn build_by_add(shapes: &mut [T] ) -> BVH + { + let (first, rest) = shapes.split_at_mut(1); + let mut bvh = BVH::build(first); + for i in 1..shapes.len() { + bvh.add_node(shapes, i) + }; + bvh + } + + #[cfg(feature = "bench")] + pub fn intersect_n_triangles_add(n: usize, b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(n, &bounds); + let bh = build_by_add(&mut triangles); + intersect_bh(&bh, &triangles, &bounds, b) + } + + + #[bench] + /// Benchmark the construction of a `BVH` for the Sponza scene. + fn build_1200_triangles_add(b: &mut ::test::Bencher) { + add_triangles_bvh(1200, b) + } + + + #[bench] + /// Benchmark the construction of a `BVH` for the Sponza scene. + fn build_12k_triangles_add(b: &mut ::test::Bencher) { + add_triangles_bvh(12000, b) + } + + + /* + #[bench] + /// Benchmark the construction of a `BVH` for the Sponza scene. + fn build_120k_triangles_add(b: &mut ::test::Bencher) { + add_triangles_bvh(120000, b) + } + */ #[bench] /// Benchmark intersecting 1,200 triangles using the recursive `BVH`. @@ -1348,6 +1562,18 @@ mod bench { intersect_120k_triangles_bh::(&mut b); } + #[bench] + /// Benchmark intersecting 1,200 triangles using the recursive `BVH`. + fn bench_intersect_1200_triangles_bvh_add(mut b: &mut ::test::Bencher) { + intersect_n_triangles_add(1200, &mut b); + } + + #[bench] + /// Benchmark intersecting 1,200 triangles using the recursive `BVH`. + fn bench_intersect_12000_triangles_bvh_add(mut b: &mut ::test::Bencher) { + intersect_n_triangles_add(12000, &mut b); + } + #[bench] /// Benchmark the traversal of a `BVH` with the Sponza scene. fn bench_intersect_sponza_bvh(b: &mut ::test::Bencher) { diff --git a/src/bvh/mod.rs b/src/bvh/mod.rs index c3e56fa..d581313 100644 --- a/src/bvh/mod.rs +++ b/src/bvh/mod.rs @@ -6,6 +6,7 @@ mod bvh_impl; mod iter; mod optimization; +mod qbvh; pub use self::bvh_impl::*; pub use self::iter::*; diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 2f86d99..eb67ecf 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -74,8 +74,27 @@ impl BVH { pub fn optimize( &mut self, refit_shape_indices: &HashSet, - shapes: &[Shape], + shapes: &mut[Shape], ) { + + for i in refit_shape_indices { + self.remove_node(shapes, *i, false); + //self.assert_tight(shapes); + } + //println!("--------"); + //self.pretty_print(); + //println!("--------"); + for i in refit_shape_indices { + self.add_node(shapes, *i); + //self.assert_tight(shapes); + //println!("--------"); + //self.pretty_print(); + //println!("--------"); + //self.assert_consistent(shapes); + } + + return; + // `refit_node_indices` will contain the indices of the leaf nodes // that reference the given shapes, sorted by their depth // in increasing order. @@ -86,9 +105,10 @@ impl BVH { .collect::>(); // Sorts the Vector to have the greatest depth nodes last. + let depths = self.cache_depths(); raw_indices.sort_by(|a, b| { - let depth_a = self.nodes[*a].depth(); - let depth_b = self.nodes[*b].depth(); + let depth_a = depths[*a]; + let depth_b = depths[*b]; depth_a.cmp(&depth_b) }); @@ -104,12 +124,12 @@ impl BVH { let mut sweep_node_indices = Vec::new(); let max_depth = { let last_node_index = refit_node_indices.last().unwrap(); - self.nodes[last_node_index.index()].depth() + self.nodes[last_node_index.index()].depth(self.nodes.as_slice()) }; while !refit_node_indices.is_empty() { let last_node_depth = { let last_node_index = refit_node_indices.last().unwrap(); - self.nodes[last_node_index.index()].depth() + self.nodes[last_node_index.index()].depth(self.nodes.as_slice()) }; if last_node_depth == max_depth { sweep_node_indices.push(refit_node_indices.pop().unwrap()); @@ -141,7 +161,7 @@ impl BVH { // that we should check, so we add its index to the refit_node_indices. if let Some(index) = new_refit_node_index { assert!({ - let new_node_depth = self.nodes[index.index()].depth(); + let new_node_depth = self.nodes[index.index()].depth(self.nodes.as_slice()); new_node_depth == max_depth - 1 }); refit_node_indices.push(index); @@ -150,6 +170,34 @@ impl BVH { } } + fn cache_depths(&mut self) -> Vec { + let mut depths = Vec::with_capacity(self.nodes.len()); + unsafe { + depths.set_len(self.nodes.len()); + } + + let mut stack = Vec::new(); + stack.push((0 as usize, 0 as usize)); + while stack.len() > 0 { + let (i, depth) = stack.pop().unwrap(); + depths[i] = depth; + match self.nodes[i] { + BVHNode::Leaf { .. } => { + + } + BVHNode::Node { + child_l_index, + child_r_index, + .. + } => { + stack.push((child_r_index, depth + 1)); + stack.push((child_l_index, depth + 1)); + } + } + } + depths + } + /// This method is called for each node which has been modified and needs to be updated. /// If the specified node is a grandparent, then try to optimize the `BVH` by rotating its /// children. @@ -434,7 +482,7 @@ impl BVH { shapes, ); } - +/* /// Updates the depth of a node, and sets the depth of its descendants accordingly. fn update_depth_recursively(&mut self, node_index: usize, new_depth: u32) { let children = { @@ -460,6 +508,7 @@ impl BVH { self.update_depth_recursively(child_r_index, new_depth + 1); } } +*/ fn node_is_left_child(&self, node_index: usize) -> bool { // Get the index of the parent. @@ -479,14 +528,13 @@ impl BVH { let child_aabb = self.nodes[child_index].get_node_aabb(shapes); info!("\tConnecting: {} < {}.", child_index, parent_index); // Set parent's child and child_aabb; and get its depth. - let parent_depth = { + let _ = { match self.nodes[parent_index] { BVHNode::Node { ref mut child_l_index, ref mut child_r_index, ref mut child_l_aabb, ref mut child_r_aabb, - depth, .. } => { if left_child { @@ -497,7 +545,6 @@ impl BVH { *child_r_aabb = child_aabb; } info!("\t {}'s new {}", parent_index, child_aabb); - depth } // Assuming that our BVH is correct, the parent cannot be a leaf. _ => unreachable!(), @@ -506,9 +553,6 @@ impl BVH { // Set child's parent. *self.nodes[child_index].parent_mut() = parent_index; - - // Update the node's and the node's descendants' depth values. - self.update_depth_recursively(child_index, parent_depth + 1); } } @@ -527,12 +571,12 @@ mod tests { #[test] /// Tests if `optimize` does not modify a fresh `BVH`. fn test_optimizing_new_bvh() { - let (shapes, mut bvh) = build_some_bh::(); + let (mut shapes, mut bvh) = build_some_bh::(); let original_nodes = bvh.nodes.clone(); // Query an update for all nodes. let refit_shape_indices: HashSet = (0..shapes.len()).collect(); - bvh.optimize(&refit_shape_indices, &shapes); + bvh.optimize(&refit_shape_indices, &mut shapes); // Assert that all nodes are the same as before the update. for (optimized, original) in bvh.nodes.iter().zip(original_nodes.iter()) { @@ -552,7 +596,7 @@ mod tests { shapes[5].pos = Point3::new(11.0, 2.0, 2.0); let refit_shape_indices = (0..6).collect(); - bvh.optimize(&refit_shape_indices, &shapes); + bvh.optimize(&refit_shape_indices, &mut shapes); bvh.assert_consistent(&shapes); } @@ -596,7 +640,7 @@ mod tests { // Move the first shape so that it is closer to shape #2. shapes[1].pos = Point3::new(40.0, 0.0, 0.0); let refit_shape_indices: HashSet = (1..2).collect(); - bvh.optimize(&refit_shape_indices, &shapes); + bvh.optimize(&refit_shape_indices, &mut shapes); bvh.pretty_print(); bvh.assert_consistent(&shapes); @@ -639,7 +683,6 @@ mod tests { // Root node. BVHNode::Node { parent_index: 0, - depth: 0, child_l_aabb: shapes[0].aabb().join(&shapes[1].aabb()), child_l_index: 1, child_r_aabb: shapes[2].aabb().join(&shapes[3].aabb()), @@ -648,7 +691,6 @@ mod tests { // Depth 1 nodes. BVHNode::Node { parent_index: 0, - depth: 1, child_l_aabb: shapes[0].aabb(), child_l_index: 3, child_r_aabb: shapes[1].aabb(), @@ -656,7 +698,6 @@ mod tests { }, BVHNode::Node { parent_index: 0, - depth: 1, child_l_aabb: shapes[2].aabb(), child_l_index: 5, child_r_aabb: shapes[3].aabb(), @@ -665,22 +706,18 @@ mod tests { // Depth 2 nodes (leaves). BVHNode::Leaf { parent_index: 1, - depth: 2, shape_index: 0, }, BVHNode::Leaf { parent_index: 1, - depth: 2, shape_index: 1, }, BVHNode::Leaf { parent_index: 2, - depth: 2, shape_index: 2, }, BVHNode::Leaf { parent_index: 2, - depth: 2, shape_index: 3, }, ]; @@ -929,7 +966,35 @@ mod tests { assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); // After fixing the `AABB` consistency should be restored. - bvh.optimize(&updated, &triangles); + bvh.optimize(&updated, &mut triangles); + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + } + + #[test] + fn test_optimize_bvh_10_75p() { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(50, &bounds); + println!("triangles={}", triangles.len()); + + let mut bvh = BVH::build(&mut triangles); + + // The initial BVH should be consistent. + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + + // After moving triangles, the BVH should be inconsistent, because the shape `AABB`s do not + // match the tree entries. + let mut seed = 0; + + let updated = randomly_transform_scene(&mut triangles, 599, &bounds, None, &mut seed); + assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); + println!("triangles={}", triangles.len()); + //bvh.pretty_print(); + + // After fixing the `AABB` consistency should be restored. + bvh.optimize(&updated, &mut triangles); + //bvh.pretty_print(); bvh.assert_consistent(&triangles); bvh.assert_tight(&triangles); } @@ -968,7 +1033,7 @@ mod bench { b.iter(|| { let updated = randomly_transform_scene(&mut triangles, num_move, &bounds, Some(10.0), &mut seed); - bvh.optimize(&updated, &triangles); + bvh.optimize(&updated, &mut triangles); }); } @@ -1010,7 +1075,7 @@ mod bench { for _ in 0..iterations { let updated = randomly_transform_scene(&mut triangles, num_move, &bounds, max_offset, &mut seed); - bvh.optimize(&updated, &triangles); + bvh.optimize(&updated, &mut triangles); } intersect_bh(&bvh, &triangles, &bounds, b); diff --git a/src/bvh/qbvh.rs b/src/bvh/qbvh.rs new file mode 100644 index 0000000..10f8424 --- /dev/null +++ b/src/bvh/qbvh.rs @@ -0,0 +1,120 @@ +//#![cfg(test)] +#[cfg(all(feature = "bench", test))] +mod bench { + use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; + use parry3d_f64::bounding_volume::aabb::AABB; + use parry3d_f64::math::Point; + use std::rc::{Rc}; + use crate::testbase::{ + create_n_cubes, default_bounds, + Triangle, + }; + + #[derive(Clone, Debug)] + pub struct Triangles { + Tris: Rc> + } + + #[derive(Copy, Clone, Debug)] + pub struct IndexedTri { + tri: Triangle, + i: usize + } + + impl IndexedData for IndexedTri { + fn default() -> Self { + IndexedTri { + tri: Triangle::new(Default::default(), Default::default(), Default::default()), + i: 0 + } + } + + fn index(&self) -> usize { + self.i + } + } + + impl QBVHDataGenerator for Triangles { + fn size_hint(&self) -> usize { + return self.Tris.len(); + } + + fn for_each(&mut self, mut f: impl FnMut(IndexedTri, AABB)) { + for i in 0..self.Tris.len() + { + let tri = self.Tris[i]; + let mut min = unsafe { Point::new_uninitialized() }; + let mut max = unsafe { Point::new_uninitialized() }; + let a = tri.a; + let b = tri.b; + let c = tri.c; + for d in 0..3 { + min.coords[d] = a[d].min(b[d]).min(c[d]); + max.coords[d] = a[d].max(b[d]).max(c[d]); + } + let mut aabb = AABB::new(min, max); + let mut new_tri = IndexedTri { + tri: tri, + i: i + }; + f(new_tri, aabb); + } + + } + } + + //fn test() { + #[bench] + /// Benchmark building a qbvh + fn bench_build_1200_qbvh(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(100, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + b.iter(|| { + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 1.0); + }); + } + + #[bench] + /// Benchmark building a qbvh + fn bench_build_12000_qbvh(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(1000, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + b.iter(|| { + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 1.0); + }); + } + + #[bench] + /// Benchmark building a qbvh + fn bench_build_120k_qbvh(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(10000, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 1.0); + b.iter(|| { + bvh.clear_and_rebuild(generator.clone(), 1.0); + }); + } + + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 8fcb2c4..b2e2147 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -487,7 +487,7 @@ pub extern fn remove_node(bvh_ref: *const BVHRef, remove_shape: i32, boxes: *mut nodes: v }; - bvh.remove_node(shapes, remove_shape as usize); + bvh.remove_node(shapes, remove_shape as usize, true); let len = bvh.nodes.len(); let cap = bvh.nodes.capacity(); let p = bvh.nodes.as_mut_ptr(); diff --git a/src/main.rs b/src/main.rs index f06d763..1de7a36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -139,7 +139,7 @@ pub fn main() { let (mut triangles, bounds) = load_sponza_scene(); let mut bvh = BVH::build(triangles.as_mut_slice()); - for i in 0..50 { + for i in 0..10 { bvh.rebuild(triangles.as_mut_slice()); } } diff --git a/src/testbase.rs b/src/testbase.rs index b4b9709..001dc41 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -167,7 +167,7 @@ pub fn traverse_some_bh() { } /// A triangle struct. Instance of a more complex `Bounded` primitive. -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub struct Triangle { pub a: Point3, pub b: Point3, @@ -413,7 +413,7 @@ pub fn randomly_transform_scene( let bytes: [u8; 8] = unsafe { transmute(seed.to_be()) }; seed_array[i] = bytes[i % 8]; } - let mut rng: StdRng = SeedableRng::from_seed(seed_array); + let mut rng: StdRng = SeedableRng::from_seed(Default::default()); indices.shuffle(&mut rng); indices.truncate(amount); From 3b002e3f0e4ab7f81611b64594c961e20cd2d7e6 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Thu, 15 Jul 2021 17:40:54 -0400 Subject: [PATCH 07/37] more qbvh testing --- Cargo.toml | 4 +- flamegraph.svg | 412 ++++++++++++++++++++++++++++++++++++++++++++++++ src/bvh/mod.rs | 3 +- src/bvh/qbvh.rs | 319 ++++++++++++++++++++++++++++++++++--- src/lib.rs | 197 ++++++++++++++++++++++- 5 files changed, 912 insertions(+), 23 deletions(-) create mode 100644 flamegraph.svg diff --git a/Cargo.toml b/Cargo.toml index 7199ea8..76e6b75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,15 @@ num = "0.4" glam = "0.15" rayon = "1.5.1" obj-rs = "0.6" +parry3d-f64 = "0.6.0" +object-pool = "0.5.4" +lazy_static = "1.4.0" [dev-dependencies] proptest = "1.0" float_eq = "0.6" criterion = "0.3" itertools = "0.10.1" -parry3d-f64 = "0.6.0" [features] bench = [] diff --git a/flamegraph.svg b/flamegraph.svg new file mode 100644 index 0000000..387cc0a --- /dev/null +++ b/flamegraph.svg @@ -0,0 +1,412 @@ +Flame Graph Reset ZoomSearch KERNELBASE`ReadFile (2 samples, 5.00%)KERNEL..ntdll`NtReadFile (2 samples, 5.00%)ntdll`..bvh`main (1 samples, 2.50%)bv..bvh`std::sys_common::backtrace::__rust_begin_short_backtrace<fn(),tuple (1 samples, 2.50%)bv..bvh`bvh::main (1 samples, 2.50%)bv..bvh`bvh::{{impl}}::process::{{closure}} (1 samples, 2.50%)bv..ntdll`RtlCompareMemoryUlong (15 samples, 37.50%)ntdll`RtlCompareMemoryUlongntdll`NtAllocateVirtualMemory (1 samples, 2.50%)nt..ntdll`RtlAllocateHeap (19 samples, 47.50%)ntdll`RtlAllocateHeapntdll`RtlAllocateHeap (19 samples, 47.50%)ntdll`RtlAllocateHeapntdll`RtlProtectHeap (2 samples, 5.00%)ntdll`..ntdll`RtlProtectHeap (2 samples, 5.00%)ntdll`..ntdll`RtlProtectHeap (1 samples, 2.50%)nt..ntdll`RtlCompareMemoryUlong (13 samples, 32.50%)ntdll`RtlCompareMemoryUlongntdll`RtlRegisterSecureMemoryCacheCallback (34 samples, 85.00%)ntdll`RtlRegisterSecureMemoryCacheCallbackntdll`RtlFreeHeap (15 samples, 37.50%)ntdll`RtlFreeHeapntdll`RtlGetCurrentServiceSessionId (15 samples, 37.50%)ntdll`RtlGetCurrentServiceSessionIdntdll`RtlGetCurrentServiceSessionId (15 samples, 37.50%)ntdll`RtlGetCurrentServiceSessionIdntdll`TpWaitForWait (1 samples, 2.50%)nt..ntdll`NtFreeVirtualMemory (1 samples, 2.50%)nt..all (40 samples, 100%)ntdll`memset (3 samples, 7.50%)ntdll`mems.. \ No newline at end of file diff --git a/src/bvh/mod.rs b/src/bvh/mod.rs index d581313..d0e28d8 100644 --- a/src/bvh/mod.rs +++ b/src/bvh/mod.rs @@ -6,8 +6,9 @@ mod bvh_impl; mod iter; mod optimization; -mod qbvh; +pub mod qbvh; pub use self::bvh_impl::*; pub use self::iter::*; pub use self::optimization::*; + diff --git a/src/bvh/qbvh.rs b/src/bvh/qbvh.rs index 10f8424..e5c49ee 100644 --- a/src/bvh/qbvh.rs +++ b/src/bvh/qbvh.rs @@ -1,14 +1,106 @@ +use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; +use parry3d_f64::bounding_volume::aabb::{AABB}; +use parry3d_f64::math::{Real, SimdBool, SimdReal, SIMD_WIDTH}; +use parry3d_f64::query::{Ray, RayCast, RayIntersection, SimdRay}; +use parry3d_f64::partitioning::{SimdBestFirstVisitStatus, SimdBestFirstVisitor}; +use parry3d_f64::bounding_volume::SimdAABB; +use parry3d_f64::simba::simd::{SimdBool as _, SimdPartialOrd, SimdValue}; + +pub trait BoundableQ { + fn aabb(&self) -> AABB; +} + +/// A visitor for casting a ray on a composite shape. +pub struct RayBoundableToiBestFirstVisitor<'a> { + ray: &'a Ray, + simd_ray: SimdRay, + max_toi: Real, + solid: bool, +} + +impl<'a> RayBoundableToiBestFirstVisitor<'a> { + /// Initialize a visitor for casting a ray on a composite shape. + pub fn new(ray: &'a Ray, max_toi: Real, solid: bool) -> Self { + Self { + ray, + simd_ray: SimdRay::splat(*ray), + max_toi, + solid, + } + } +} + +impl<'a, T: BoundableQ + IndexedData> SimdBestFirstVisitor + for RayBoundableToiBestFirstVisitor<'a> +{ + type Result = (usize, Real); + + #[inline] + fn visit( + &mut self, + best: Real, + aabb: &SimdAABB, + data: Option<[Option<&T>; SIMD_WIDTH]>, + ) -> SimdBestFirstVisitStatus { + let (hit, toi) = aabb.cast_local_ray(&self.simd_ray, SimdReal::splat(self.max_toi)); + + if let Some(data) = data { + let mut weights = [0.0; SIMD_WIDTH]; + let mut mask = [false; SIMD_WIDTH]; + let mut results = [None; SIMD_WIDTH]; + + let better_toi = toi.simd_lt(SimdReal::splat(best)); + let bitmask = (hit & better_toi).bitmask(); + + for ii in 0..SIMD_WIDTH { + if (bitmask & (1 << ii)) != 0 && data[ii].is_some() { + let shape = data[ii].unwrap(); + let toi = shape.aabb().cast_local_ray(&self.ray, self.max_toi, self.solid); + if let Some(toi) = toi { + results[ii] = Some((shape.index(), toi)); + mask[ii] = true; + weights[ii] = toi; + } + } + } + + SimdBestFirstVisitStatus::MaybeContinue { + weights: SimdReal::from(weights), + mask: SimdBool::from(mask), + results, + } + } else { + SimdBestFirstVisitStatus::MaybeContinue { + weights: toi, + mask: hit, + results: [None; SIMD_WIDTH], + } + } + } +} + + + + + + + + + //#![cfg(test)] #[cfg(all(feature = "bench", test))] mod bench { use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; use parry3d_f64::bounding_volume::aabb::AABB; - use parry3d_f64::math::Point; + use parry3d_f64::math::{Point, Vector}; use std::rc::{Rc}; use crate::testbase::{ create_n_cubes, default_bounds, - Triangle, + Triangle, create_ray }; + use crate::bvh::qbvh::{RayBoundableToiBestFirstVisitor, BoundableQ}; + use parry3d_f64::query::{Ray, RayCast, RayIntersection, SimdRay}; + use parry3d_f64::query::visitors::RayIntersectionsVisitor; #[derive(Clone, Debug)] pub struct Triangles { @@ -34,6 +126,22 @@ mod bench { } } + impl BoundableQ for IndexedTri { + fn aabb(&self) -> AABB { + let tri = self.tri; + let mut min = unsafe { Point::new_uninitialized() }; + let mut max = unsafe { Point::new_uninitialized() }; + let a = tri.a; + let b = tri.b; + let c = tri.c; + for d in 0..3 { + min.coords[d] = a[d].min(b[d]).min(c[d]); + max.coords[d] = a[d].max(b[d]).max(c[d]); + } + AABB::new(min, max) + } + } + impl QBVHDataGenerator for Triangles { fn size_hint(&self) -> usize { return self.Tris.len(); @@ -43,27 +151,16 @@ mod bench { for i in 0..self.Tris.len() { let tri = self.Tris[i]; - let mut min = unsafe { Point::new_uninitialized() }; - let mut max = unsafe { Point::new_uninitialized() }; - let a = tri.a; - let b = tri.b; - let c = tri.c; - for d in 0..3 { - min.coords[d] = a[d].min(b[d]).min(c[d]); - max.coords[d] = a[d].max(b[d]).max(c[d]); - } - let mut aabb = AABB::new(min, max); let mut new_tri = IndexedTri { tri: tri, i: i }; - f(new_tri, aabb); + f(new_tri, new_tri.aabb()); } } } - //fn test() { #[bench] /// Benchmark building a qbvh fn bench_build_1200_qbvh(b: &mut ::test::Bencher) { @@ -75,9 +172,10 @@ mod bench { }; + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); b.iter(|| { - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 1.0); + bvh.clear_and_rebuild(generator.clone(), 0.0); }); } @@ -92,9 +190,10 @@ mod bench { }; + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); b.iter(|| { - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 1.0); + bvh.clear_and_rebuild(generator.clone(), 0.0); }); } @@ -110,11 +209,191 @@ mod bench { let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 1.0); + bvh.clear_and_rebuild(generator.clone(), 0.0); b.iter(|| { - bvh.clear_and_rebuild(generator.clone(), 1.0); + bvh.clear_and_rebuild(generator.clone(), 0.0); }); } + #[bench] + /// Benchmark building a qbvh + fn bench_1200_qbvh_ray_intersection(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(100, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); + let r = Ray::new(Point::new(0.0, 0.0, 0.0), Vector::new(1.0, 0.0, 0.0)); + let mut visitor = RayBoundableToiBestFirstVisitor::new(&r, 1000000000000.0, true); + b.iter(|| { + bvh.traverse_best_first(&mut visitor); + }); + } + + #[bench] + /// Benchmark building a qbvh + fn bench_12000_qbvh_ray_intersection(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(1000, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); + let r = Ray::new(Point::new(0.0, 0.0, 0.0), Vector::new(1.0, 0.0, 0.0)); + let mut visitor = RayBoundableToiBestFirstVisitor::new(&r, 1000000000000.0, true); + b.iter(|| { + bvh.traverse_best_first(&mut visitor); + }); + } + + #[bench] + /// Benchmark building a qbvh + fn bench_120k_qbvh_ray_intersection(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(10000, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); + let r = Ray::new(Point::new(0.0, 0.0, 0.0), Vector::new(1.0, 0.0, 0.0)); + let mut visitor = RayBoundableToiBestFirstVisitor::new(&r, 1000000000000.0, true); + b.iter(|| { + bvh.traverse_best_first(&mut visitor); + }); + } + +/* + fn visit_triangle(tri: &IndexedTri) -> bool { + true + } +*/ + #[bench] + /// Benchmark building a qbvh + fn bench_1200_qbvh_ray_intersection_stack(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(100, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); + let mut visit_triangle = |tri: &IndexedTri| { + true + }; + let mut stack = Vec::new(); + let mut seed = 0; + b.iter(|| { + stack.clear(); + let ray = create_ray(&mut seed, &bounds); + let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); + let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); + let r = Ray::new(o, d); + let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); + bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); + }); + } + + + #[bench] + /// Benchmark building a qbvh + fn bench_12000_qbvh_ray_intersection_stack(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(1000, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); + let mut visit_triangle = |tri: &IndexedTri| { + true + }; + let mut stack = Vec::new(); + let mut seed = 0; + b.iter(|| { + stack.clear(); + let ray = create_ray(&mut seed, &bounds); + let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); + let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); + let r = Ray::new(o, d); + let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); + bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); + }); + } + + #[bench] + /// Benchmark building a qbvh + fn bench_120k_qbvh_ray_intersection_stack(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(10000, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); + let mut visit_triangle = |tri: &IndexedTri| { + true + }; + let mut stack = Vec::new(); + let mut seed = 0; + b.iter(|| { + stack.clear(); + let ray = create_ray(&mut seed, &bounds); + let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); + let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); + let r = Ray::new(o, d); + let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); + bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); + }); + } + + + #[bench] + /// Benchmark building a qbvh + fn bench_120k_qbvh_ray_intersection_stackalloc(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(10000, &bounds); + let rc = Rc::new(triangles); + let mut generator = Triangles { + Tris: rc + }; + + + let mut bvh = QBVH::new(); + bvh.clear_and_rebuild(generator.clone(), 0.0); + let mut visit_triangle = |tri: &IndexedTri| { + true + }; + let mut seed = 0; + b.iter(|| { + let ray = create_ray(&mut seed, &bounds); + let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); + let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); + let r = Ray::new(o, d); + let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); + bvh.traverse_depth_first(&mut visitor); + }); + } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b2e2147..095a03f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,17 @@ use shapes::{Capsule, Sphere, OBB}; use glam::DQuat; use bvh::BUILD_THREAD_COUNT; use std::sync::atomic::{AtomicUsize, Ordering}; +use bvh::qbvh::BoundableQ; +use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; +use parry3d_f64::bounding_volume::aabb::AABB as QAABB; +use parry3d_f64::math::{Point, Vector}; +use parry3d_f64::query::Ray as RayQ; +use std::boxed; +use parry3d_f64::query::visitors::RayIntersectionsVisitor; + +#[macro_use] +extern crate lazy_static; + #[cfg(test)] mod testbase; @@ -137,7 +148,7 @@ pub struct BoundsD { pub struct BVHBounds { pub bounds: BoundsD, pub index: i32, - pub ptr: i32 + pub array_index: i32 } #[repr(C)] @@ -147,6 +158,23 @@ pub struct BVHRef { pub cap: i32 } +#[repr(C)] +pub struct QBVHRef { + pub ptr: *mut QBVH +} + +impl QBVHRef { + fn from_box(boxed: Box>) -> Self { + let ptr = Box::into_raw(boxed); + QBVHRef { + ptr + } + } + fn to_box(&self) -> Box> { + unsafe { Box::from_raw(self.ptr) } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct QuaternionD { @@ -188,6 +216,30 @@ impl Bounded for BVHBounds { } } +#[derive(Copy, Clone, Debug)] +pub struct RefNode { + pub index: usize +} + +impl IndexedData for RefNode { + fn index(&self) -> usize { + self.index + } + fn default() -> Self { + RefNode { + index: usize::MAX + } + } +} + +impl BVHBounds { + fn qaabb(&self) -> QAABB { + let min = Point::new(self.bounds.min.x, self.bounds.min.y, self.bounds.min.z); + let max = Point::new(self.bounds.max.x, self.bounds.max.y, self.bounds.max.z); + QAABB::new(min, max) + } +} + impl BHShape for BVHBounds { fn set_bh_node_index(&mut self, index: usize) { self.index = index as i32; @@ -214,6 +266,35 @@ pub fn to_quat(a: &QuaternionD) -> DQuat { DQuat::from_xyzw(a.x, a.y, a.z, a.w) } +struct BoundsData<'a> { + data: &'a mut [BVHBounds] +} + +impl <'a> BoundsData<'a> { + fn new(data: &'a mut [BVHBounds]) -> BoundsData { + BoundsData { + data + } + } +} + +impl <'a> QBVHDataGenerator for BoundsData<'a> { + fn size_hint(&self) -> usize { + self.data.len() + } + + fn for_each(&mut self, mut f: impl FnMut(RefNode, QAABB)) { + for i in 0..self.data.len() + { + let bounds = self.data[i]; + f(RefNode { + index: i + }, bounds.qaabb()); + } + + } +} + #[no_mangle] pub extern fn set_build_thread_count(count: i32) { @@ -259,6 +340,57 @@ pub extern fn build_bvh(a: *mut BVHBounds, count: i32) -> BVHRef } +#[no_mangle] +pub extern fn build_qbvh(a: *mut BVHBounds, count: i32) -> QBVHRef +{ + let mut s = unsafe { std::slice::from_raw_parts_mut(a, count as usize) }; + + let data = BoundsData::new(s); + let mut bvh = Box::new(QBVH::new()); + + bvh.clear_and_rebuild(data, 0.0); + + QBVHRef::from_box(bvh) +} + +#[no_mangle] +pub extern fn query_ray_q(bvh_ref: *const QBVHRef, origin_vec: *const Vector3D, dir_vec: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +{ + let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; + let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + + + let bvh = unsafe {(*bvh_ref).to_box()}; + let o_vec = unsafe {*origin_vec}; + let d_vec = unsafe {*dir_vec}; + let ray = RayQ::new(Point::new(o_vec.x, o_vec.y, o_vec.z), Vector::new(d_vec.x, d_vec.y, d_vec.z)); + let mut i = 0; + + let mut stack_arr: [u32; 32] = [0; 32]; + + let mut stack = unsafe { Vec::from_raw_parts(&mut stack_arr as *mut u32, 0, 32)}; + + let mut visit = |node: &RefNode| { + if i < max_res { + buffer[i as usize] = shapes[node.index]; + i += 1; + return false; + } + i += 1; + true + }; + + let mut visitor = RayIntersectionsVisitor::new(&ray, 1000000000000.0, &mut visit); + + bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); + + std::mem::forget(stack); + std::mem::forget(bvh); + + i as i32 +} + + #[no_mangle] pub extern fn rebuild_bvh(bvh_ref: *const BVHRef, a: *mut BVHBounds, count: i32) -> BVHRef @@ -544,5 +676,68 @@ pub fn node_32_constructor(aabb: &AABB, entry_index: u32, exit_index: u32, shape } +#[test] +fn test_building_and_querying() { + let min = Vector3D { + x: -1.0, + y: -1.0, + z: -1.0 + }; + let max = Vector3D { + x: 1.0, + y: 1.0, + z: 1.0 + }; + let bounds = BoundsD { + min, + max + }; + let mut b = BVHBounds { + bounds, + array_index: 0, + index: 0 + }; + let ptr = unsafe { + &mut b as *mut BVHBounds + }; + + + let mut out = BVHBounds { + bounds, + array_index: 0, + index: 0 + }; + let out_ptr = unsafe { + &mut out as *mut BVHBounds + }; + + let origin = Vector3D { + x: 0.0, + y: -5.0, + z: 0.0 + }; + let dir = Vector3D { + x: 0.0, + y: 1.0, + z: 0.0 + }; + let o_ptr = unsafe { + &origin as * const Vector3D + }; + let d_ptr = unsafe { + &dir as * const Vector3D + }; + + let mut bvhRef = build_qbvh(ptr, 1); + + let bvh_ptr = unsafe { + &bvhRef as * const QBVHRef + }; + + let x = query_ray_q(bvh_ptr, o_ptr, d_ptr, ptr, 1, out_ptr, 1); + assert_eq!(x, 1); +} + + From 192eed9d745573ad11880ec8022929298e9b6121 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 17 Jul 2021 21:07:21 -0400 Subject: [PATCH 08/37] reduced allocations in bvh build --- Cargo.toml | 1 + src/bvh/bvh_impl.rs | 62 +++++++++++++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76e6b75..c47dd13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ obj-rs = "0.6" parry3d-f64 = "0.6.0" object-pool = "0.5.4" lazy_static = "1.4.0" +smallvec = "1.6.1" [dev-dependencies] proptest = "1.0" diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index dad7c99..7a20956 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -14,6 +14,7 @@ use std::slice; use rayon::prelude::*; use std::iter::repeat; use std::sync::atomic::{AtomicUsize, Ordering}; +use smallvec::SmallVec; pub static BUILD_THREAD_COUNT: AtomicUsize = AtomicUsize::new(20); @@ -247,7 +248,7 @@ impl BVHNode { /// pub fn build( shapes: &mut [T], - indices: &[usize], + indices: &mut [usize], nodes: &mut [BVHNode], parent_index: usize, depth: u32, @@ -297,7 +298,7 @@ impl BVHNode { } else { let mut convex_hull = Default::default(); - for index in indices { + for index in indices.iter() { convex_hull = grow_convex_hull(convex_hull, &shapes[*index].aabb()); }; convex_hull @@ -332,7 +333,7 @@ impl BVHNode { { // In this branch the shapes lie too close together so that splitting them in a // sensible way is not possible. Instead we just split the list of shapes in half. - let (child_l_indices, child_r_indices) = indices.split_at(indices.len() / 2); + let (child_l_indices, child_r_indices) = indices.split_at_mut(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); @@ -350,12 +351,12 @@ impl BVHNode { let shapes_b = slice::from_raw_parts_mut(ptr, len); (shapes_a, shapes_b) }; - rayon::join(|| BVHNode::build(shapes_a, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index), - || BVHNode::build(shapes_b, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); + rayon::join(|| BVHNode::build(shapes_a, child_l_indices, l_nodes, node_index, depth + 1, child_l_index), + || BVHNode::build(shapes_b, child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); //BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); } else { - BVHNode::build(shapes, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index); - BVHNode::build(shapes, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index); + BVHNode::build(shapes, child_l_indices, l_nodes, node_index, depth + 1, child_l_index); + BVHNode::build(shapes, child_r_indices, r_nodes, node_index, depth + 1, child_r_index); } //println!("{:?}", (parent_index, child_l_index, child_l_indices.len(), child_r_index, child_r_indices.len())); (child_l_index, child_l_aabb, child_r_index, child_r_aabb) @@ -363,11 +364,11 @@ impl BVHNode { // Create six `Bucket`s, and six index assignment vector. const NUM_BUCKETS: usize = 6; let mut buckets = [Bucket::empty(); NUM_BUCKETS]; - let mut bucket_assignments: [Vec; NUM_BUCKETS] = Default::default(); + let mut bucket_assignments: [SmallVec<[usize; 128]>; NUM_BUCKETS] = Default::default(); // In this branch the `split_axis_size` is large enough to perform meaningful splits. // We start by assigning the shapes to `Bucket`s. - for idx in indices { + for idx in indices.iter() { let shape = &shapes[*idx]; let shape_aabb = shape.aabb(); let shape_center = shape_aabb.center(); @@ -407,8 +408,31 @@ impl BVHNode { // Join together all index buckets. 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); + // split input indices, loop over assignments and assign + let mut l_count = 0; + for group in l_assignments.iter() { + l_count += group.len(); + } + + let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); + let mut i = 0; + for group in l_assignments.iter() { + for x in group { + child_l_indices[i] = *x; + i += 1; + } + } + i = 0; + for group in r_assignments.iter() { + for x in group { + child_r_indices[i] = *x; + i += 1; + } + } + + + //let child_l_indices = concatenate_vectors(l_assignments); + //let child_r_indices = concatenate_vectors(r_assignments); let next_nodes = &mut nodes[1..]; @@ -426,13 +450,13 @@ impl BVHNode { let shapes_b = slice::from_raw_parts_mut(ptr, len); (shapes_a, shapes_b) }; - rayon::join(|| BVHNode::build(shapes_a, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index), - || BVHNode::build(shapes_b, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); + rayon::join(|| BVHNode::build(shapes_a, child_l_indices, l_nodes, node_index, depth + 1, child_l_index), + || BVHNode::build(shapes_b, child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); //BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); } else { - BVHNode::build(shapes, &child_l_indices, l_nodes, node_index, depth + 1, child_l_index); - BVHNode::build(shapes, &child_r_indices, r_nodes, node_index, depth + 1, child_r_index); + BVHNode::build(shapes, child_l_indices, l_nodes, node_index, depth + 1, child_l_index); + BVHNode::build(shapes, child_r_indices, r_nodes, node_index, depth + 1, child_r_index); } //println!("{:?}", (parent_index, child_l_index, child_l_indices.len(), child_r_index, child_r_indices.len())); (child_l_index, child_l_aabb, child_r_index, child_r_aabb) @@ -507,7 +531,7 @@ impl BVH { /// [`BVH`]: struct.BVH.html /// pub fn build(shapes: &mut [Shape]) -> BVH { - let indices = (0..shapes.len()).collect::>(); + let mut indices = (0..shapes.len()).collect::>(); let expected_node_count = shapes.len() * 2 - 1; let mut nodes = Vec::with_capacity(expected_node_count); unsafe { @@ -515,12 +539,12 @@ impl BVH { } //println!("shapes={} nodes={}", shapes.len(), nodes.len()); let n = nodes.as_mut_slice(); - BVHNode::build(shapes, &indices, n, 0, 0, 0); + BVHNode::build(shapes, &mut indices, n, 0, 0, 0); BVH { nodes } } pub fn rebuild(&mut self, shapes: &mut [Shape]) { - let indices = (0..shapes.len()).collect::>(); + let mut indices = (0..shapes.len()).collect::>(); let expected_node_count = shapes.len() * 2 - 1; let additional_nodes = self.nodes.capacity() as i32 - expected_node_count as i32; if additional_nodes > 0 { @@ -530,7 +554,7 @@ impl BVH { self.nodes.set_len(expected_node_count); } let n = self.nodes.as_mut_slice(); - BVHNode::build(shapes, &indices, n, 0, 0, 0); + BVHNode::build(shapes, &mut indices, n, 0, 0, 0); } /// Traverses the [`BVH`]. From 1a152b4e4a177d9fd552d79839b8bea36014cf24 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 17 Jul 2021 21:30:52 -0400 Subject: [PATCH 09/37] completely remove allocations, bit of incorrect logic tho --- src/aabb.rs | 4 +++ src/bvh/bvh_impl.rs | 63 ++++++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/aabb.rs b/src/aabb.rs index 028c417..05647b1 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -263,6 +263,10 @@ impl AABB { ) } + pub fn merge(a: AABB, b: &AABB) -> AABB { + a.join(b) + } + /// mutable version of [`AABB::join`]. /// /// # Examples diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 7a20956..e745358 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -364,7 +364,7 @@ impl BVHNode { // Create six `Bucket`s, and six index assignment vector. const NUM_BUCKETS: usize = 6; let mut buckets = [Bucket::empty(); NUM_BUCKETS]; - let mut bucket_assignments: [SmallVec<[usize; 128]>; NUM_BUCKETS] = Default::default(); + //let mut bucket_assignments: [SmallVec<[usize; 128]>; NUM_BUCKETS] = Default::default(); // In this branch the `split_axis_size` is large enough to perform meaningful splits. // We start by assigning the shapes to `Bucket`s. @@ -382,7 +382,7 @@ impl BVHNode { // Extend the selected `Bucket` and add the index to the actual bucket. buckets[bucket_num].add_aabb(&shape_aabb); - bucket_assignments[bucket_num].push(*idx); + //bucket_assignments[bucket_num].push(*idx); } // Compute the costs for each configuration and select the best configuration. @@ -390,6 +390,7 @@ impl BVHNode { let mut min_cost = f64::INFINITY; let mut child_l_aabb = AABB::empty(); let mut child_r_aabb = AABB::empty(); + let mut l_count = 0; 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); @@ -403,34 +404,50 @@ impl BVHNode { min_cost = cost; child_l_aabb = child_l.aabb; child_r_aabb = child_r.aabb; + l_count = child_l.size; } } - - // Join together all index buckets. - let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1); // split input indices, loop over assignments and assign - let mut l_count = 0; - for group in l_assignments.iter() { - l_count += group.len(); - } let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); - let mut i = 0; - for group in l_assignments.iter() { - for x in group { - child_l_indices[i] = *x; - i += 1; - } - } - i = 0; - for group in r_assignments.iter() { - for x in group { - child_r_indices[i] = *x; - i += 1; - } - } + let mut k = 0; + for j in 0..child_l_indices.len() { + let shape = &shapes[j]; + let shape_aabb = shape.aabb(); + let shape_center = shape_aabb.center(); + + // Get the relative position of the shape centroid `[0.0..1.0]`. + let bucket_num_relative = + (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; + + // Convert that to the actual `Bucket` number. + let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; + + if bucket_num > min_bucket { + while k < child_r_indices.len() { + let shape = &shapes[j]; + let shape_aabb = shape.aabb(); + let shape_center = shape_aabb.center(); + + // Get the relative position of the shape centroid `[0.0..1.0]`. + let bucket_num_relative = + (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; + + // Convert that to the actual `Bucket` number. + let r_bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; + if r_bucket_num <= min_bucket { + let temp = child_l_indices[j]; + child_l_indices[j] = child_r_indices[k]; + child_r_indices[k] = temp; + k += 1; + break; + } + k += 1; + } + } + } //let child_l_indices = concatenate_vectors(l_assignments); //let child_r_indices = concatenate_vectors(r_assignments); From 12e6ead000dfe24774811d4e0dc61ecee3f0fd1a Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 17 Jul 2021 21:31:02 -0400 Subject: [PATCH 10/37] Revert "completely remove allocations, bit of incorrect logic tho" This reverts commit 1a152b4e4a177d9fd552d79839b8bea36014cf24. --- src/aabb.rs | 4 --- src/bvh/bvh_impl.rs | 63 +++++++++++++++++---------------------------- 2 files changed, 23 insertions(+), 44 deletions(-) diff --git a/src/aabb.rs b/src/aabb.rs index 05647b1..028c417 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -263,10 +263,6 @@ impl AABB { ) } - pub fn merge(a: AABB, b: &AABB) -> AABB { - a.join(b) - } - /// mutable version of [`AABB::join`]. /// /// # Examples diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index e745358..7a20956 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -364,7 +364,7 @@ impl BVHNode { // Create six `Bucket`s, and six index assignment vector. const NUM_BUCKETS: usize = 6; let mut buckets = [Bucket::empty(); NUM_BUCKETS]; - //let mut bucket_assignments: [SmallVec<[usize; 128]>; NUM_BUCKETS] = Default::default(); + let mut bucket_assignments: [SmallVec<[usize; 128]>; NUM_BUCKETS] = Default::default(); // In this branch the `split_axis_size` is large enough to perform meaningful splits. // We start by assigning the shapes to `Bucket`s. @@ -382,7 +382,7 @@ impl BVHNode { // Extend the selected `Bucket` and add the index to the actual bucket. buckets[bucket_num].add_aabb(&shape_aabb); - //bucket_assignments[bucket_num].push(*idx); + bucket_assignments[bucket_num].push(*idx); } // Compute the costs for each configuration and select the best configuration. @@ -390,7 +390,6 @@ impl BVHNode { let mut min_cost = f64::INFINITY; let mut child_l_aabb = AABB::empty(); let mut child_r_aabb = AABB::empty(); - let mut l_count = 0; 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); @@ -404,50 +403,34 @@ impl BVHNode { min_cost = cost; child_l_aabb = child_l.aabb; child_r_aabb = child_r.aabb; - l_count = child_l.size; } } + + // Join together all index buckets. + let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1); // split input indices, loop over assignments and assign + let mut l_count = 0; + for group in l_assignments.iter() { + l_count += group.len(); + } let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); - - - let mut k = 0; - for j in 0..child_l_indices.len() { - let shape = &shapes[j]; - let shape_aabb = shape.aabb(); - let shape_center = shape_aabb.center(); - - // Get the relative position of the shape centroid `[0.0..1.0]`. - let bucket_num_relative = - (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; - - // Convert that to the actual `Bucket` number. - let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; - - if bucket_num > min_bucket { - while k < child_r_indices.len() { - let shape = &shapes[j]; - let shape_aabb = shape.aabb(); - let shape_center = shape_aabb.center(); - - // Get the relative position of the shape centroid `[0.0..1.0]`. - let bucket_num_relative = - (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; - - // Convert that to the actual `Bucket` number. - let r_bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; - if r_bucket_num <= min_bucket { - let temp = child_l_indices[j]; - child_l_indices[j] = child_r_indices[k]; - child_r_indices[k] = temp; - k += 1; - break; - } - k += 1; - } + let mut i = 0; + for group in l_assignments.iter() { + for x in group { + child_l_indices[i] = *x; + i += 1; + } + } + i = 0; + for group in r_assignments.iter() { + for x in group { + child_r_indices[i] = *x; + i += 1; } } + + //let child_l_indices = concatenate_vectors(l_assignments); //let child_r_indices = concatenate_vectors(r_assignments); From da98c79c904f23bd48ed30875346e5b643d82df3 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 17 Jul 2021 21:47:49 -0400 Subject: [PATCH 11/37] refactor to prevent stack overflows --- src/bvh/bvh_impl.rs | 150 +++++++++++++++++++++++++------------------- 1 file changed, 84 insertions(+), 66 deletions(-) diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 7a20956..b85b3c5 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -15,6 +15,7 @@ use rayon::prelude::*; use std::iter::repeat; use std::sync::atomic::{AtomicUsize, Ordering}; use smallvec::SmallVec; +use crate::axis::Axis; pub static BUILD_THREAD_COUNT: AtomicUsize = AtomicUsize::new(20); @@ -273,7 +274,7 @@ impl BVHNode { let mut parallel_recurse = false; if nodes.len() > 128 { - //parallel_recurse = true; + parallel_recurse = true; /* let avail_threads = BUILD_THREAD_COUNT.load(Ordering::Relaxed); if avail_threads > 0 { @@ -361,79 +362,17 @@ impl BVHNode { //println!("{:?}", (parent_index, child_l_index, child_l_indices.len(), child_r_index, child_r_indices.len())); (child_l_index, child_l_aabb, child_r_index, child_r_aabb) } else { - // Create six `Bucket`s, and six index assignment vector. - const NUM_BUCKETS: usize = 6; - let mut buckets = [Bucket::empty(); NUM_BUCKETS]; - let mut bucket_assignments: [SmallVec<[usize; 128]>; NUM_BUCKETS] = Default::default(); - - // In this branch the `split_axis_size` is large enough to perform meaningful splits. - // We start by assigning the shapes to `Bucket`s. - for idx in indices.iter() { - let shape = &shapes[*idx]; - let shape_aabb = shape.aabb(); - let shape_center = shape_aabb.center(); - - // Get the relative position of the shape centroid `[0.0..1.0]`. - let bucket_num_relative = - (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; - - // Convert that to the actual `Bucket` number. - let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; - - // Extend the selected `Bucket` and add the index to the actual bucket. - buckets[bucket_num].add_aabb(&shape_aabb); - bucket_assignments[bucket_num].push(*idx); - } - - // Compute the costs for each configuration and select the best configuration. - let mut min_bucket = 0; - let mut min_cost = f64::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 f64 * child_l.aabb.surface_area() - + child_r.size as f64 * 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; - } - } // Join together all index buckets. - let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1); - // split input indices, loop over assignments and assign - let mut l_count = 0; - for group in l_assignments.iter() { - l_count += group.len(); - } - let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); - let mut i = 0; - for group in l_assignments.iter() { - for x in group { - child_l_indices[i] = *x; - i += 1; - } - } - i = 0; - for group in r_assignments.iter() { - for x in group { - child_r_indices[i] = *x; - i += 1; - } - } + + // split input indices, loop over assignments and assign //let child_l_indices = concatenate_vectors(l_assignments); //let child_r_indices = concatenate_vectors(r_assignments); + let (child_l_aabb, child_r_aabb, child_l_indices, child_r_indices) = BVHNode::build_buckets(shapes, indices, split_axis, split_axis_size, ¢roid_bounds, &aabb_bounds); let next_nodes = &mut nodes[1..]; let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); @@ -477,6 +416,85 @@ impl BVHNode { } + fn build_buckets<'a, T: BHShape>( + shapes: &mut [T], + indices: &'a mut [usize], + split_axis: Axis, + split_axis_size: f64, + centroid_bounds: &AABB, + aabb_bounds: &AABB) -> (AABB, AABB, &'a mut [usize], &'a mut [usize]) { + // Create six `Bucket`s, and six index assignment vector. + const NUM_BUCKETS: usize = 6; + let mut buckets = [Bucket::empty(); NUM_BUCKETS]; + let mut bucket_assignments: [SmallVec<[usize; 1024]>; NUM_BUCKETS] = Default::default(); + + // In this branch the `split_axis_size` is large enough to perform meaningful splits. + // We start by assigning the shapes to `Bucket`s. + for idx in indices.iter() { + let shape = &shapes[*idx]; + let shape_aabb = shape.aabb(); + let shape_center = shape_aabb.center(); + + // Get the relative position of the shape centroid `[0.0..1.0]`. + let bucket_num_relative = + (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; + + // Convert that to the actual `Bucket` number. + let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; + + // Extend the selected `Bucket` and add the index to the actual bucket. + buckets[bucket_num].add_aabb(&shape_aabb); + bucket_assignments[bucket_num].push(*idx); + } + + // Compute the costs for each configuration and select the best configuration. + let mut min_bucket = 0; + let mut min_cost = f64::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 f64 * child_l.aabb.surface_area() + + child_r.size as f64 * 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 mut l_count = 0; + for group in l_assignments.iter() { + l_count += group.len(); + } + + let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); + let mut i = 0; + for group in l_assignments.iter() { + for x in group { + child_l_indices[i] = *x; + i += 1; + } + } + i = 0; + for group in r_assignments.iter() { + for x in group { + child_r_indices[i] = *x; + i += 1; + } + } + + (child_l_aabb, child_r_aabb, child_l_indices, child_r_indices) + } + /// Traverses the [`BVH`] recursively and returns all shapes whose [`AABB`] is /// intersected by the given [`Ray`]. /// From 5e15776ac421bd3bb740db3e486bdcb0bfc558a1 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Tue, 21 Sep 2021 18:38:14 -0400 Subject: [PATCH 12/37] rewrite to use interoptopus for the ffi bindings --- Cargo.toml | 8 +- src/bvh/bvh_impl.rs | 17 +- src/bvh/optimization.rs | 21 +- src/lib.rs | 434 +++++++++++++++++++--------------------- src/main.rs | 3 +- src/utils.rs | 5 +- 6 files changed, 232 insertions(+), 256 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c47dd13..133791b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,15 +20,19 @@ crate-type = ["rlib", "dylib"] [dependencies] approx = "0.4" rand = "0.8" -log = "0.4" +log = "0.4.14" num = "0.4" glam = "0.15" rayon = "1.5.1" obj-rs = "0.6" -parry3d-f64 = "0.6.0" +parry3d-f64 = { version = "0.6.0", features = [ ], path = "C:/Users/derek/OneDrive/jagexcache/Documents/GitHub/parry/build/parry3d-f64" } object-pool = "0.5.4" lazy_static = "1.4.0" smallvec = "1.6.1" +interoptopus = "0.10.3" +interoptopus_backend_csharp = "0.10.4" +flexi_logger = "0.19.3" +log-panics = "2.0.0" [dev-dependencies] proptest = "1.0" diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index b85b3c5..648bc17 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -7,13 +7,13 @@ use crate::aabb::{Bounded, AABB}; use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; use crate::bvh::iter::BVHTraverseIterator; -use crate::utils::{concatenate_vectors, joint_aabb_of_shapes, Bucket}; +use crate::utils::{joint_aabb_of_shapes, Bucket}; use crate::Point3; use crate::EPSILON; use std::slice; use rayon::prelude::*; use std::iter::repeat; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicUsize}; use smallvec::SmallVec; use crate::axis::Axis; @@ -270,11 +270,11 @@ impl BVHNode { } - let mut use_parallel_hull = false; + let use_parallel_hull = false; - let mut parallel_recurse = false; + let parallel_recurse = false; if nodes.len() > 128 { - parallel_recurse = true; + //parallel_recurse = true; /* let avail_threads = BUILD_THREAD_COUNT.load(Ordering::Relaxed); if avail_threads > 0 { @@ -1180,7 +1180,7 @@ impl BVH { // Check if all nodes have been counted from the root node. // If this is false, it means we have a detached subtree. - if (node_count != self.nodes.len()) { + if node_count != self.nodes.len() { for x in node_count..self.nodes.len() { let node = self.nodes[x]; match node { @@ -1224,7 +1224,7 @@ impl BVH { { let joint_aabb = child_l_aabb.join(&child_r_aabb); if !joint_aabb.relative_eq(outer_aabb, EPSILON) { - for i in 0..shapes.len() + for _i in 0..shapes.len() { //println!("s#{} {}", i, shapes[i].aabb()) } @@ -1276,8 +1276,6 @@ mod tests { use crate::testbase::{build_some_bh, traverse_some_bh, UnitBox}; use crate::Ray; use crate::bounding_hierarchy::BHShape; - use rand::thread_rng; - use rand::prelude::SliceRandom; use itertools::Itertools; #[test] @@ -1451,7 +1449,6 @@ mod tests { } let (left, right) = shapes.split_at_mut(10); - let addable = 10..25; let mut bvh = BVH::build(left); bvh.pretty_print(); diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index eb67ecf..10d1ce5 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -71,26 +71,28 @@ impl BVH { /// /// Needs all the scene's shapes, plus the indices of the shapes that were updated. /// - pub fn optimize( + pub fn optimize ( &mut self, - refit_shape_indices: &HashSet, + refit_shape_indices: &[usize], shapes: &mut[Shape], ) { + for i in refit_shape_indices { self.remove_node(shapes, *i, false); + //self.assert_tight(shapes); } //println!("--------"); //self.pretty_print(); //println!("--------"); for i in refit_shape_indices { - self.add_node(shapes, *i); //self.assert_tight(shapes); //println!("--------"); //self.pretty_print(); //println!("--------"); //self.assert_consistent(shapes); + self.add_node(shapes, *i); } return; @@ -99,8 +101,7 @@ impl BVH { // that reference the given shapes, sorted by their depth // in increasing order. let mut refit_node_indices: Vec<_> = { - let mut raw_indices = refit_shape_indices - .iter() + let mut raw_indices = refit_shape_indices.into_iter() .map(|x| shapes[*x].bh_node_index()) .collect::>(); @@ -575,7 +576,7 @@ mod tests { let original_nodes = bvh.nodes.clone(); // Query an update for all nodes. - let refit_shape_indices: HashSet = (0..shapes.len()).collect(); + let refit_shape_indices: Vec = (0..shapes.len()).collect(); bvh.optimize(&refit_shape_indices, &mut shapes); // Assert that all nodes are the same as before the update. @@ -595,7 +596,7 @@ mod tests { shapes[4].pos = Point3::new(11.0, 1.0, 2.0); shapes[5].pos = Point3::new(11.0, 2.0, 2.0); - let refit_shape_indices = (0..6).collect(); + let refit_shape_indices: Vec = (0..6).collect(); bvh.optimize(&refit_shape_indices, &mut shapes); bvh.assert_consistent(&shapes); } @@ -639,7 +640,7 @@ mod tests { // Move the first shape so that it is closer to shape #2. shapes[1].pos = Point3::new(40.0, 0.0, 0.0); - let refit_shape_indices: HashSet = (1..2).collect(); + let refit_shape_indices: Vec = (1..2).collect(); bvh.optimize(&refit_shape_indices, &mut shapes); bvh.pretty_print(); bvh.assert_consistent(&shapes); @@ -963,6 +964,7 @@ mod tests { let mut seed = 0; let updated = randomly_transform_scene(&mut triangles, 9_000, &bounds, None, &mut seed); + let updated: Vec = updated.into_iter().collect(); assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); // After fixing the `AABB` consistency should be restored. @@ -988,6 +990,7 @@ mod tests { let mut seed = 0; let updated = randomly_transform_scene(&mut triangles, 599, &bounds, None, &mut seed); + let updated: Vec = updated.into_iter().collect(); assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); println!("triangles={}", triangles.len()); //bvh.pretty_print(); @@ -1033,6 +1036,7 @@ mod bench { b.iter(|| { let updated = randomly_transform_scene(&mut triangles, num_move, &bounds, Some(10.0), &mut seed); + let updated: Vec = updated.into_iter().collect(); bvh.optimize(&updated, &mut triangles); }); } @@ -1075,6 +1079,7 @@ mod bench { for _ in 0..iterations { let updated = randomly_transform_scene(&mut triangles, num_move, &bounds, max_offset, &mut seed); + let updated: Vec = updated.into_iter().collect(); bvh.optimize(&updated, &mut triangles); } diff --git a/src/lib.rs b/src/lib.rs index 095a03f..fa04165 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,19 +92,24 @@ mod utils; use aabb::{Bounded, AABB}; use bounding_hierarchy::BHShape; -use bvh::BVHNode; +use bvh::{BVH, BVHNode}; use ray::Ray; use shapes::{Capsule, Sphere, OBB}; use glam::DQuat; use bvh::BUILD_THREAD_COUNT; use std::sync::atomic::{AtomicUsize, Ordering}; -use bvh::qbvh::BoundableQ; use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; use parry3d_f64::bounding_volume::aabb::AABB as QAABB; use parry3d_f64::math::{Point, Vector}; use parry3d_f64::query::Ray as RayQ; -use std::boxed; use parry3d_f64::query::visitors::RayIntersectionsVisitor; +use interoptopus::{ffi_function, ffi_type}; +use interoptopus::patterns::slice::{FFISliceMut}; +use interoptopus::patterns::string::AsciiPointer; +use interoptopus::lang::rust::CTypeInfo; +use interoptopus::lang::c::{CType, CompositeType, Documentation, Field, OpaqueType, PrimitiveType, Visibility, Meta}; +use flexi_logger::{FileSpec, Logger, detailed_format}; +use log::{info, warn, error}; #[macro_use] extern crate lazy_static; @@ -115,20 +120,22 @@ mod testbase; #[no_mangle] -pub extern fn add_numbers(number1: i32, number2: i32) -> i32 { +pub extern "C" fn add_numbers(number1: i32, number2: i32) -> i32 { println!("Hello from rust!"); number1 + number2 } #[repr(C)] +#[ffi_type] #[derive(Copy, Clone, Debug)] -pub struct Vector3D { +pub struct Double3 { pub x: f64, pub y: f64, pub z: f64 } #[repr(C)] +#[ffi_type] #[derive(Copy, Clone, Debug)] pub struct Float3 { pub x: f32, @@ -137,53 +144,54 @@ pub struct Float3 { } #[repr(C)] +#[ffi_type(name="BoundingBoxD")] #[derive(Copy, Clone, Debug)] pub struct BoundsD { - pub min: Vector3D, - pub max: Vector3D + pub min: Double3, + pub max: Double3 } #[repr(C)] +#[ffi_type(name="BvhNode")] #[derive(Copy, Clone, Debug)] pub struct BVHBounds { pub bounds: BoundsD, - pub index: i32, - pub array_index: i32 + pub internal_bvh_index: i32, + pub index: i32 } #[repr(C)] -pub struct BVHRef { - pub ptr: *mut BVHNode, - pub len: i32, - pub cap: i32 +pub struct BvhRef { + bvh: Box } -#[repr(C)] -pub struct QBVHRef { - pub ptr: *mut QBVH +unsafe impl CTypeInfo for BvhRef { + fn type_info() -> CType { + let fields: Vec = vec![ + Field::with_documentation("bvh".to_string(), CType::ReadPointer(Box::new(CType::Opaque(OpaqueType::new("BvhPtr".to_string(), Meta::new())))), Visibility::Private, Documentation::new()), + ]; + let composite = CompositeType::new("BvhRef".to_string(), fields); + CType::Composite(composite) + } } -impl QBVHRef { - fn from_box(boxed: Box>) -> Self { - let ptr = Box::into_raw(boxed); - QBVHRef { - ptr - } - } - fn to_box(&self) -> Box> { - unsafe { Box::from_raw(self.ptr) } - } +#[ffi_type(opaque)] +#[repr(C)] +pub struct QBVHRef { + bvh: Box> } +#[ffi_type] #[repr(C)] #[derive(Copy, Clone, Debug)] -pub struct QuaternionD { +pub struct QuatD { pub x: f64, pub y: f64, pub z: f64, pub w: f64 } +#[ffi_type(name="Float3")] #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct Point32 { @@ -192,6 +200,7 @@ pub struct Point32 { pub z: f32 } +#[ffi_type(name="BoundingBox")] #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct AABB32 { @@ -199,6 +208,7 @@ pub struct AABB32 { pub max: Point32 } +#[ffi_type(name="FlatNode")] #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct FlatNode32 { @@ -216,6 +226,7 @@ impl Bounded for BVHBounds { } } +#[ffi_type(opaque)] #[derive(Copy, Clone, Debug)] pub struct RefNode { pub index: usize @@ -242,27 +253,27 @@ impl BVHBounds { impl BHShape for BVHBounds { fn set_bh_node_index(&mut self, index: usize) { - self.index = index as i32; + self.internal_bvh_index = index as i32; } fn bh_node_index(&self) -> usize { - self.index as usize + self.internal_bvh_index as usize } } -pub fn to_vec(a: &Vector3D) -> Vector3 { +pub fn to_vec(a: &Double3) -> Vector3 { Vector3::new(a.x, a.y, a.z) } -pub fn to_vecd(a: &Vector3) -> Vector3D { - Vector3D { +pub fn to_vecd(a: &Vector3) -> Double3 { + Double3 { x: a.x, y: a.y, z: a.z } } -pub fn to_quat(a: &QuaternionD) -> DQuat { +pub fn to_quat(a: &QuatD) -> DQuat { DQuat::from_xyzw(a.x, a.y, a.z, a.w) } @@ -296,14 +307,37 @@ impl <'a> QBVHDataGenerator for BoundsData<'a> { } #[no_mangle] -pub extern fn set_build_thread_count(count: i32) +pub extern "C" fn set_build_thread_count(count: i32) { BUILD_THREAD_COUNT.store(count as usize, Ordering::Relaxed); } +static LOGGER_INITIALIZED: AtomicUsize = AtomicUsize::new(0); +#[ffi_function] #[no_mangle] -pub extern fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) +pub extern "C" fn init_logger(log_path: AsciiPointer) +{ + let init_count = LOGGER_INITIALIZED.fetch_add(1, Ordering::SeqCst); + if init_count == 0 { + let path = log_path.as_str().unwrap(); + let file = FileSpec::default() + .directory(path) + .basename("bvh_f64") + .suffix("log"); + Logger::try_with_str("info").unwrap() + .log_to_file(file) + .format_for_files(detailed_format) + .start().unwrap(); + log_panics::init(); + + info!("Log initialized in folder {}", path); + } +} + + +#[no_mangle] +pub extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) { let a = unsafe {*a_ptr}; @@ -312,7 +346,7 @@ pub extern fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Flo let b = glam::Vec3::new(b.x, b.y, b.z); let mut c = glam::Vec3::new(0.0, 0.0, 0.0); - for i in 0 .. 100000 { + for _i in 0 .. 100000 { c = a + b + c; } @@ -325,44 +359,35 @@ pub extern fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Flo } } +#[ffi_function] #[no_mangle] -pub extern fn build_bvh(a: *mut BVHBounds, count: i32) -> BVHRef +pub extern "C" fn build_bvh(shapes: &mut FFISliceMut) -> BvhRef { - let mut s = unsafe { std::slice::from_raw_parts_mut(a, count as usize) }; - - let mut bvh = bvh::BVH::build(&mut s); - let len = bvh.nodes.len(); - let cap = bvh.nodes.capacity(); - let p = bvh.nodes.as_mut_ptr(); - std::mem::forget(bvh.nodes); + let bvh = Box::new(bvh::BVH::build(shapes.as_slice_mut())); + info!("Building bvh"); - BVHRef { ptr: p, len: len as i32, cap: cap as i32 } + BvhRef { bvh } } +#[ffi_function] #[no_mangle] -pub extern fn build_qbvh(a: *mut BVHBounds, count: i32) -> QBVHRef +pub extern "C" fn build_qbvh(shapes: &mut FFISliceMut) -> QBVHRef { - let mut s = unsafe { std::slice::from_raw_parts_mut(a, count as usize) }; - let data = BoundsData::new(s); + let data = BoundsData::new(shapes.as_slice_mut()); let mut bvh = Box::new(QBVH::new()); bvh.clear_and_rebuild(data, 0.0); - QBVHRef::from_box(bvh) + QBVHRef { bvh } } +#[ffi_function] #[no_mangle] -pub extern fn query_ray_q(bvh_ref: *const QBVHRef, origin_vec: *const Vector3D, dir_vec: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern "C" fn query_ray_q(bvh_ref: &QBVHRef, o_vec: &Double3, d_vec: &Double3, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - - - let bvh = unsafe {(*bvh_ref).to_box()}; - let o_vec = unsafe {*origin_vec}; - let d_vec = unsafe {*dir_vec}; + let bvh = &bvh_ref.bvh; let ray = RayQ::new(Point::new(o_vec.x, o_vec.y, o_vec.z), Vector::new(d_vec.x, d_vec.y, d_vec.z)); let mut i = 0; @@ -371,7 +396,7 @@ pub extern fn query_ray_q(bvh_ref: *const QBVHRef, origin_vec: *const Vector3D, let mut stack = unsafe { Vec::from_raw_parts(&mut stack_arr as *mut u32, 0, 32)}; let mut visit = |node: &RefNode| { - if i < max_res { + if i < buffer.len() { buffer[i as usize] = shapes[node.index]; i += 1; return false; @@ -384,81 +409,52 @@ pub extern fn query_ray_q(bvh_ref: *const QBVHRef, origin_vec: *const Vector3D, bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); + // stack is pretending to be a heap allocated vector std::mem::forget(stack); - std::mem::forget(bvh); i as i32 } +#[ffi_function] #[no_mangle] -pub extern fn rebuild_bvh(bvh_ref: *const BVHRef, a: *mut BVHBounds, count: i32) -> BVHRef +pub extern "C" fn rebuild_bvh(bvh_ref: &mut BvhRef, shapes: &mut FFISliceMut) { - let mut s = unsafe { std::slice::from_raw_parts_mut(a, count as usize) }; - - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let mut bvh = bvh::BVH { - nodes: v - }; - - bvh.rebuild(s); - - let len = bvh.nodes.len(); - let cap = bvh.nodes.capacity(); - let p = bvh.nodes.as_mut_ptr(); - std::mem::forget(bvh.nodes); - - BVHRef { ptr: p, len: len as i32, cap: cap as i32 } + let bvh = &mut bvh_ref.bvh; + bvh.rebuild(shapes.as_slice_mut()); } +#[ffi_function] #[no_mangle] -pub extern fn query_ray(bvh_ref: *const BVHRef, origin_vec: *const Vector3D, dir_vec: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern "C" fn query_ray(bvh_ref: &BvhRef, origin_vec: &Double3, dir_vec: &Double3, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let bvh = bvh::BVH { - nodes: v - }; + let bvh = &bvh_ref.bvh; - let ray = Ray::new(to_vec(& unsafe{*origin_vec}), to_vec(& unsafe{*dir_vec})); + let ray = Ray::new(to_vec(origin_vec), to_vec(dir_vec)); let mut i = 0; for x in bvh.traverse_iterator(&ray, &shapes) { - if i < max_res { + if i < buffer.len() { buffer[i as usize] = *x; } i += 1; } - std::mem::forget(bvh.nodes); - i as i32 } +#[ffi_function] #[no_mangle] -pub extern fn batch_query_rays(bvh_ref: *const BVHRef, origin_vecs: *mut Vector3D, dir_vecs: *mut Vector3D, hits: *mut i32, ray_count: i32, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) +pub extern "C" fn batch_query_rays(bvh_ref: &BvhRef, origins: &FFISliceMut, dirs: &FFISliceMut, hits: &mut FFISliceMut, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let origins = unsafe { std::slice::from_raw_parts_mut(origin_vecs, ray_count as usize) }; - let dirs = unsafe { std::slice::from_raw_parts_mut(dir_vecs, ray_count as usize) }; - let hits = unsafe { std::slice::from_raw_parts_mut(hits, ray_count as usize) }; - - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let bvh = bvh::BVH { - nodes: v - }; + let bvh = &bvh_ref.bvh; let mut i = 0; + let ray_count = origins.len(); for r in 0..ray_count as usize { let ray = Ray::new(to_vec(&origins[r]), to_vec(&dirs[r])); let mut res = 0; for x in bvh.traverse_iterator(&ray, &shapes) { - if i < max_res { + if i < buffer.len() { buffer[i as usize] = *x; } i += 1; @@ -466,188 +462,132 @@ pub extern fn batch_query_rays(bvh_ref: *const BVHRef, origin_vecs: *mut Vector3 } hits[r] = res; } - - std::mem::forget(bvh.nodes); } +#[ffi_function] #[no_mangle] -pub extern fn query_sphere(bvh_ref: *const BVHRef, center: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern "C" fn query_sphere(bvh_ref: &BvhRef, center: &Double3, radius: f64, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let bvh = bvh::BVH { - nodes: v - }; + let bvh = &bvh_ref.bvh; - let test_shape = Sphere::new(to_vec(&unsafe { *center }), radius); + let test_shape = Sphere::new(to_vec(center), radius); let mut i = 0; for x in bvh.traverse_iterator(&test_shape, &shapes) { - if i < max_res { + if i < buffer.len() { buffer[i as usize] = *x; } i += 1; } - std::mem::forget(bvh.nodes); - i as i32 } +#[ffi_function] #[no_mangle] -pub extern fn query_capsule(bvh_ref: *const BVHRef, start: *const Vector3D, end: *const Vector3D, radius: f64, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern "C" fn query_capsule(bvh_ref: &BvhRef, start: &Double3, end: &Double3, radius: f64, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + let bvh = &bvh_ref.bvh; - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let bvh = bvh::BVH { - nodes: v - }; - - let test_shape = Capsule::new(to_vec(&unsafe { *start }), to_vec(&unsafe { *end }), radius); + let test_shape = Capsule::new(to_vec(start), to_vec(end), radius); let mut i = 0; for x in bvh.traverse_iterator(&test_shape, &shapes) { - if i < max_res { + if i < buffer.len() { buffer[i as usize] = *x; } i += 1; } - std::mem::forget(bvh.nodes); - i as i32 } +#[ffi_function] #[no_mangle] -pub extern fn query_aabb(bvh_ref: *const BVHRef, bounds: *const BoundsD, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern "C" fn query_aabb(bvh_ref: &BvhRef, bounds: &BoundsD, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; + let bvh = &bvh_ref.bvh; - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let bvh = bvh::BVH { - nodes: v - }; - - let min = to_vec(&unsafe { *bounds }.min); - let max = to_vec(&unsafe { *bounds }.max); + let min = to_vec(&bounds.min); + let max = to_vec(&bounds.max); let test_shape = AABB::with_bounds(min, max); let mut i = 0; for x in bvh.traverse_iterator(&test_shape, &shapes) { - if i < max_res { + if i < buffer.len() { buffer[i as usize] = *x; } i += 1; } - std::mem::forget(bvh.nodes); - i as i32 } +#[ffi_function] #[no_mangle] -pub extern fn query_obb(bvh_ref: *const BVHRef, ori: *const QuaternionD, extents: *const Vector3D, center: *const Vector3D, boxes: *mut BVHBounds, count: i32, res: *mut BVHBounds, max_res: i32) -> i32 +pub extern "C" fn query_obb(bvh_ref: &BvhRef, ori: &QuatD, extents: &Double3, center: &Double3, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let buffer = unsafe { std::slice::from_raw_parts_mut(res, max_res as usize) }; - - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let bvh = bvh::BVH { - nodes: v - }; + let bvh = &bvh_ref.bvh; let obb = OBB { - orientation: to_quat(&unsafe { *ori }), - extents: to_vec(&unsafe { *extents }), - center: to_vec(&unsafe { *center }) + orientation: to_quat(ori), + extents: to_vec(extents), + center: to_vec(center) }; let mut i = 0; for x in bvh.traverse_iterator(&obb, &shapes) { - if i < max_res { + if i < buffer.len() { buffer[i as usize] = *x; } i += 1; } - std::mem::forget(bvh.nodes); - i as i32 } +#[ffi_function] #[no_mangle] -pub extern fn free_bvh(bvh_ref: *const BVHRef) +pub extern "C" fn free_bvh(_bvh_ref: BvhRef) { - let _v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; } +#[ffi_function] #[no_mangle] -pub extern fn add_node(bvh_ref: *const BVHRef, new_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef +pub extern "C" fn add_node(bvh_ref: &mut BvhRef, new_shape: i32, shapes: &mut FFISliceMut) { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let mut bvh = bvh::BVH { - nodes: v - }; - - bvh.add_node(shapes, new_shape as usize); - let len = bvh.nodes.len(); - let cap = bvh.nodes.capacity(); - let p = bvh.nodes.as_mut_ptr(); - std::mem::forget(bvh.nodes); - - BVHRef { ptr: p, len: len as i32, cap: cap as i32 } + let bvh = &mut bvh_ref.bvh; + bvh.add_node(shapes.as_slice_mut(), new_shape as usize); } +#[ffi_function] #[no_mangle] -pub extern fn remove_node(bvh_ref: *const BVHRef, remove_shape: i32, boxes: *mut BVHBounds, count: i32) -> BVHRef +pub extern "C" fn remove_node(bvh_ref: &mut BvhRef, remove_shape: i32, shapes: &mut FFISliceMut) { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let mut bvh = bvh::BVH { - nodes: v - }; - - bvh.remove_node(shapes, remove_shape as usize, true); - let len = bvh.nodes.len(); - let cap = bvh.nodes.capacity(); - let p = bvh.nodes.as_mut_ptr(); - std::mem::forget(bvh.nodes); - - BVHRef { ptr: p, len: len as i32, cap: cap as i32 } + let bvh = &mut bvh_ref.bvh; + bvh.remove_node(shapes.as_slice_mut(), remove_shape as usize, true); } +#[ffi_function] #[no_mangle] -pub extern fn flatten_bvh(bvh_ref: *const BVHRef, boxes: *mut BVHBounds, count: i32, res: *mut FlatNode32, res_count: i32) -> i32 +pub extern "C" fn update_node(bvh_ref: &mut BvhRef, update_shape: i32, shapes: &mut FFISliceMut) { - let shapes = unsafe { std::slice::from_raw_parts_mut(boxes, count as usize) }; - let results = unsafe { std::slice::from_raw_parts_mut(res, res_count as usize) }; + let bvh = &mut bvh_ref.bvh; + bvh.remove_node(shapes.as_slice_mut(), update_shape as usize, false); + bvh.add_node(shapes, update_shape as usize); +} - let v = unsafe { Vec::from_raw_parts((*bvh_ref).ptr, (*bvh_ref).len as usize, (*bvh_ref).cap as usize)}; - - let bvh = bvh::BVH { - nodes: v - }; +#[ffi_function] +#[no_mangle] +pub extern "C" fn flatten_bvh(bvh_ref: &mut BvhRef, shapes: &mut FFISliceMut, results: &mut FFISliceMut) -> i32 +{ + let bvh = &bvh_ref.bvh; - let flattened = bvh.flatten_custom(shapes, &node_32_constructor); + let flattened = bvh.flatten_custom(shapes.as_slice_mut(), &node_32_constructor); for i in 0..flattened.len() { results[i] = flattened[i]; } - std::mem::forget(bvh.nodes); - flattened.len() as i32 } @@ -675,15 +615,58 @@ pub fn node_32_constructor(aabb: &AABB, entry_index: u32, exit_index: u32, shape } } +interoptopus::inventory!(my_inventory, [], [ + init_logger, + build_bvh, + build_qbvh, + query_ray_q, + rebuild_bvh, + query_ray, + batch_query_rays, + query_sphere, + query_capsule, + query_aabb, + query_obb, + free_bvh, + add_node, + remove_node, + update_node, + flatten_bvh, + ], [], []); + +use interoptopus::util::NamespaceMappings; +use interoptopus::{Error, Interop}; + +#[test] +fn bindings_csharp() -> Result<(), Error> { + use interoptopus_backend_csharp::{Config, Generator, Unsafe, overloads::{DotNet, Unity}}; + + Generator::new( + Config { + class: "NativeBvhInterop".to_string(), + dll_name: "bvh_f64".to_string(), + namespace_mappings: NamespaceMappings::new("Assets.Scripts.Native"), + use_unsafe: Unsafe::UnsafePlatformMemCpy, + ..Config::default() + }, + my_inventory(), + ) + .add_overload_writer(Unity::new()) + .add_overload_writer(DotNet::new()) + .write_file("bindings/csharp/Interop.cs")?; + + Ok(()) +} + #[test] fn test_building_and_querying() { - let min = Vector3D { + let min = Double3 { x: -1.0, y: -1.0, z: -1.0 }; - let max = Vector3D { + let max = Double3 { x: 1.0, y: 1.0, z: 1.0 @@ -692,49 +675,36 @@ fn test_building_and_querying() { min, max }; - let mut b = BVHBounds { + let b = BVHBounds { bounds, - array_index: 0, - index: 0 - }; - let ptr = unsafe { - &mut b as *mut BVHBounds + index: 0, + internal_bvh_index: 0 }; - - let mut out = BVHBounds { + let out = BVHBounds { bounds, - array_index: 0, - index: 0 - }; - let out_ptr = unsafe { - &mut out as *mut BVHBounds + index: 0, + internal_bvh_index: 0 }; - let origin = Vector3D { + let origin = Double3 { x: 0.0, y: -5.0, z: 0.0 }; - let dir = Vector3D { + let dir = Double3 { x: 0.0, y: 1.0, z: 0.0 }; - let o_ptr = unsafe { - &origin as * const Vector3D - }; - let d_ptr = unsafe { - &dir as * const Vector3D - }; + let in_slice = &mut [b]; + let mut input = FFISliceMut::::from_slice(in_slice); + let out_slice = &mut [b]; + let mut out = FFISliceMut::::from_slice(out_slice); - let mut bvhRef = build_qbvh(ptr, 1); - - let bvh_ptr = unsafe { - &bvhRef as * const QBVHRef - }; + let bvh_ref = build_qbvh(&mut input); - let x = query_ray_q(bvh_ptr, o_ptr, d_ptr, ptr, 1, out_ptr, 1); + let x = query_ray_q(&bvh_ref, &origin, &dir, &mut input, &mut out); assert_eq!(x, 1); } diff --git a/src/main.rs b/src/main.rs index 1de7a36..85494bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use bvh_f64::aabb::{Bounded, AABB}; use bvh_f64::bounding_hierarchy::BHShape; use bvh_f64::bvh::BVH; -use bvh_f64::ray::Ray; use bvh_f64::{Point3, Vector3}; use obj::*; use obj::raw::object::Polygon; @@ -139,7 +138,7 @@ pub fn main() { let (mut triangles, bounds) = load_sponza_scene(); let mut bvh = BVH::build(triangles.as_mut_slice()); - for i in 0..10 { + for _i in 0..10 { bvh.rebuild(triangles.as_mut_slice()); } } diff --git a/src/utils.rs b/src/utils.rs index 1fccfe2..4c982fd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -65,7 +65,7 @@ mod tests { /// Test if concatenating no `Vec`s yields an empty `Vec`. fn test_concatenate_empty() { let mut vectors: Vec> = vec![]; - let expected = vec![]; + let expected: Vec = vec![]; assert_eq!(concatenate_vectors(vectors.as_mut_slice()), expected); let expected_remainder: Vec> = vec![]; assert_eq!(vectors, expected_remainder); @@ -78,6 +78,7 @@ mod tests { let result = concatenate_vectors(vectors.as_mut_slice()); let expected = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; assert_eq!(result, expected); - assert_eq!(vectors, vec![vec![], vec![], vec![], vec![], vec![]]); + let expected_vecs: Vec> = vec![vec![], vec![], vec![], vec![], vec![]]; + assert_eq!(vectors, expected_vecs); } } From 8bd25ed49b77a80032231da0399e031817421b43 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Thu, 2 Dec 2021 06:54:09 -0500 Subject: [PATCH 13/37] use smallvec for traversal to avoid panics, handle 0 sized bvhs, fix bug with using the wrong surface_area --- src/bvh/bvh_impl.rs | 160 ++++++++++++++++++++++------------------ src/bvh/iter.rs | 17 ++--- src/bvh/optimization.rs | 24 ++++++ 3 files changed, 120 insertions(+), 81 deletions(-) diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 648bc17..d23291d 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -613,6 +613,16 @@ impl BVH { let new_shape = &shapes[new_shape_index]; let shape_aabb = new_shape.aabb(); let shape_sa = shape_aabb.surface_area(); + + if self.nodes.len() == 0 { + self.nodes.push(BVHNode::Leaf { + parent_index: 0, + shape_index: new_shape_index + }); + shapes[new_shape_index].set_bh_node_index(0); + return + } + loop { match self.nodes[i] { BVHNode::Node { @@ -627,7 +637,7 @@ impl BVH { let right_expand = child_r_aabb.join(&shape_aabb); let send_left = child_r_aabb.surface_area() + left_expand.surface_area(); - let send_right = child_r_aabb.surface_area() + right_expand.surface_area(); + let send_right = child_l_aabb.surface_area() + right_expand.surface_area(); let merged_aabb = child_r_aabb.join(&child_l_aabb); let merged = merged_aabb.surface_area() + shape_sa; @@ -744,9 +754,10 @@ impl BVH { deleted_shape_index: usize, swap_shape: bool, ) { - if self.nodes.len() < 2 + if self.nodes.len() == 0 { - panic!("can't remove a node from a bvh with only one node"); + return + //panic!("can't remove a node from a bvh with only one node"); } let bad_shape = &shapes[deleted_shape_index]; @@ -756,79 +767,86 @@ impl BVH { // swap the shape to the end and update the node to still point at the right shape let dead_node_index = bad_shape.bh_node_index(); - //println!("delete_i={}", dead_node_index); + if self.nodes.len() == 1 { + if dead_node_index == 0 { + self.nodes.clear(); + } + } else { - let dead_node = self.nodes[dead_node_index]; + //println!("delete_i={}", dead_node_index); - let parent_index = dead_node.parent(); - println!("parent_i={}", parent_index); - let gp_index = self.nodes[parent_index].parent(); - println!("{}->{}->{}", gp_index, parent_index, dead_node_index); + let dead_node = self.nodes[dead_node_index]; - let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r() } else { self.nodes[parent_index].child_l() }; - let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r_aabb() } else { self.nodes[parent_index].child_l_aabb() }; - // TODO: fix potential issue leaving empty spot in self.nodes - // the node swapped to sibling_index should probably be swapped to the end - // of the vector and the vector truncated - if parent_index == gp_index { - // We are removing one of the children of the root node - // The other child needs to become the root node - // The old root node and the dead child then have to be moved - - //println!("gp == parent {}", parent_index); - if parent_index != 0 { - panic!("Circular node that wasn't root parent={} node={}", parent_index, dead_node_index); - } - self.nodes.swap(parent_index, sibling_index); - - match self.nodes[parent_index].shape_index() { - Some(index) => { - *self.nodes[parent_index].parent_mut() = parent_index; - shapes[index].set_bh_node_index(parent_index); - self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); - self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); - }, - _ => { - *self.nodes[parent_index].parent_mut() = parent_index; - let new_root = self.nodes[parent_index]; - *self.nodes[new_root.child_l()].parent_mut() = parent_index; - *self.nodes[new_root.child_r()].parent_mut() = parent_index; - //println!("set {}'s parent to {}", new_root.child_l(), parent_index); - //println!("set {}'s parent to {}", new_root.child_r(), parent_index); - self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); - self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); + let parent_index = dead_node.parent(); + //println!("parent_i={}", parent_index); + let gp_index = self.nodes[parent_index].parent(); + //println!("{}->{}->{}", gp_index, parent_index, dead_node_index); + + let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r() } else { self.nodes[parent_index].child_l() }; + let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r_aabb() } else { self.nodes[parent_index].child_l_aabb() }; + // TODO: fix potential issue leaving empty spot in self.nodes + // the node swapped to sibling_index should probably be swapped to the end + // of the vector and the vector truncated + if parent_index == gp_index { + // We are removing one of the children of the root node + // The other child needs to become the root node + // The old root node and the dead child then have to be moved + + //println!("gp == parent {}", parent_index); + if parent_index != 0 { + panic!("Circular node that wasn't root parent={} node={}", parent_index, dead_node_index); + } + self.nodes.swap(parent_index, sibling_index); + + match self.nodes[parent_index].shape_index() { + Some(index) => { + *self.nodes[parent_index].parent_mut() = parent_index; + shapes[index].set_bh_node_index(parent_index); + self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); + self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); + }, + _ => { + *self.nodes[parent_index].parent_mut() = parent_index; + let new_root = self.nodes[parent_index]; + *self.nodes[new_root.child_l()].parent_mut() = parent_index; + *self.nodes[new_root.child_r()].parent_mut() = parent_index; + //println!("set {}'s parent to {}", new_root.child_l(), parent_index); + //println!("set {}'s parent to {}", new_root.child_r(), parent_index); + self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); + self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); + } } + //println!("nodes_len {}, sib_index {}", self.nodes.len(), sibling_index); + //println!("nodes_len {}", self.nodes.len()); + } else { + let box_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_aabb_mut() } else { self.nodes[gp_index].child_r_aabb_mut() }; + //println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); + *box_to_change = sibling_box; + //println!("{} {} {}", gp_index, self.nodes[gp_index].child_l_aabb(), self.nodes[gp_index].child_r_aabb()); + let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_mut() } else { self.nodes[gp_index].child_r_mut() }; + //println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); + *ref_to_change = sibling_index; + *self.nodes[sibling_index].parent_mut() = gp_index; + + self.fix_aabbs_ascending(shapes, gp_index); + //let new_depth = self.nodes[sibling_index].depth() - 1; + //*self.nodes[sibling_index].depth_mut() = new_depth; + // remove node and parent + + //println!("---"); + //self.pretty_print(); + //println!("---"); + self.swap_and_remove_index(shapes, dead_node_index.max(parent_index)); + + //println!("---"); + //self.pretty_print(); + //println!("---"); + self.swap_and_remove_index(shapes, parent_index.min(dead_node_index)); + + //println!("---"); + //self.pretty_print(); + //println!("---"); } - //println!("nodes_len {}, sib_index {}", self.nodes.len(), sibling_index); - //println!("nodes_len {}", self.nodes.len()); - } else { - let box_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_aabb_mut() } else { self.nodes[gp_index].child_r_aabb_mut() }; - //println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); - *box_to_change = sibling_box; - //println!("{} {} {}", gp_index, self.nodes[gp_index].child_l_aabb(), self.nodes[gp_index].child_r_aabb()); - let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_mut() } else { self.nodes[gp_index].child_r_mut() }; - //println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); - *ref_to_change = sibling_index; - *self.nodes[sibling_index].parent_mut() = gp_index; - - self.fix_aabbs_ascending(shapes, gp_index); - //let new_depth = self.nodes[sibling_index].depth() - 1; - //*self.nodes[sibling_index].depth_mut() = new_depth; - // remove node and parent - - //println!("---"); - //self.pretty_print(); - //println!("---"); - self.swap_and_remove_index(shapes, dead_node_index.max(parent_index)); - - //println!("---"); - //self.pretty_print(); - //println!("---"); - self.swap_and_remove_index(shapes, parent_index.min(dead_node_index)); - - //println!("---"); - //self.pretty_print(); - //println!("---"); } if swap_shape { diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index bf40203..3357f1b 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -1,3 +1,5 @@ +use smallvec::SmallVec; + use crate::aabb::Bounded; use crate::bvh::{BVHNode, BVH}; use crate::bounding_hierarchy::{IntersectionTest}; @@ -12,11 +14,9 @@ pub struct BVHTraverseIterator<'a, Shape: Bounded> { /// Reference to the input shapes array shapes: &'a [Shape], /// Traversal stack. 4 billion items seems enough? - stack: [usize; 32], + stack: SmallVec<[usize; 64]>, /// Position of the iterator in bvh.nodes node_index: usize, - /// Size of the traversal stack - stack_size: usize, /// Whether or not we have a valid node (or leaf) has_node: bool, } @@ -28,16 +28,15 @@ impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { bvh, test, shapes, - stack: [0; 32], + stack: SmallVec::new(), node_index: 0, - stack_size: 0, has_node: true, } } /// Test if stack is empty. fn is_stack_empty(&self) -> bool { - self.stack_size == 0 + self.stack.len() == 0 } /// Push node onto stack. @@ -46,8 +45,7 @@ impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { /// /// Panics if `stack[stack_size]` is out of bounds. fn stack_push(&mut self, node: usize) { - self.stack[self.stack_size] = node; - self.stack_size += 1; + self.stack.push(node); } /// Pop the stack and return the node. @@ -56,8 +54,7 @@ impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { /// /// Panics if `stack_size` underflows. fn stack_pop(&mut self) -> usize { - self.stack_size -= 1; - self.stack[self.stack_size] + self.stack.pop().unwrap() } /// Attempt to move to the left node child of the current node. diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 10d1ce5..15d2b1d 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -1061,6 +1061,11 @@ mod bench { optimize_bvh_120k(0.5, b); } + #[bench] + fn bench_optimize_bvh_120k_100p(b: &mut ::test::Bencher) { + optimize_bvh_120k(1.0, b); + } + /// Move `percent` `Triangle`s in the scene given by `triangles` and optimize the /// `BVH`. Iterate this procedure `iterations` times. Afterwards benchmark the performance /// of intersecting this scene/`BVH`. @@ -1114,6 +1119,13 @@ mod bench { intersect_scene_after_optimize(&mut triangles, &bounds, 0.5, None, 10, b); } + #[bench] + fn bench_intersect_120k_after_optimize_100p(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(10_000, &bounds); + intersect_scene_after_optimize(&mut triangles, &bounds, 1.0, None, 10, b); + } + /// Move `percent` `Triangle`s in the scene given by `triangles` `iterations` times. /// Afterwards optimize the `BVH` and benchmark the performance of intersecting this /// scene/`BVH`. Used to compare optimizing with rebuilding. For reference see @@ -1164,6 +1176,13 @@ mod bench { intersect_scene_with_rebuild(&mut triangles, &bounds, 0.5, None, 10, b); } + #[bench] + fn bench_intersect_120k_with_rebuild_100p(b: &mut ::test::Bencher) { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(10_000, &bounds); + intersect_scene_with_rebuild(&mut triangles, &bounds, 1.0, None, 10, b); + } + /// Benchmark intersecting a `BVH` for Sponza after randomly moving one `Triangle` and /// optimizing. fn intersect_sponza_after_optimize(percent: f64, b: &mut ::test::Bencher) { @@ -1191,6 +1210,11 @@ mod bench { intersect_sponza_after_optimize(0.5, b); } + #[bench] + fn bench_intersect_sponza_after_optimize_100p(b: &mut ::test::Bencher) { + intersect_sponza_after_optimize(1.0, b); + } + /// Benchmark intersecting a `BVH` for Sponza after rebuilding. Used to compare optimizing /// with rebuilding. For reference see `intersect_sponza_after_optimize`. fn intersect_sponza_with_rebuild(percent: f64, b: &mut ::test::Bencher) { From d2dc3bf95f67017033b82889c4aea7e135050e46 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 18 Dec 2021 23:22:23 -0500 Subject: [PATCH 14/37] start convex generation --- Cargo.toml | 1 + src/main.rs | 2 ++ src/mesh/convex_hull.rs | 5 +++++ src/mesh/mod.rs | 2 ++ 4 files changed, 10 insertions(+) create mode 100644 src/mesh/convex_hull.rs create mode 100644 src/mesh/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 133791b..66c5cfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ interoptopus = "0.10.3" interoptopus_backend_csharp = "0.10.4" flexi_logger = "0.19.3" log-panics = "2.0.0" +stl_io = "0.6.0" [dev-dependencies] proptest = "1.0" diff --git a/src/main.rs b/src/main.rs index 85494bd..78bc12a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,8 @@ struct Sphere { node_index: usize, } + + impl Bounded for Sphere { fn aabb(&self) -> AABB { let half_size = Vector3::new(self.radius, self.radius, self.radius); diff --git a/src/mesh/convex_hull.rs b/src/mesh/convex_hull.rs new file mode 100644 index 0000000..e9b0668 --- /dev/null +++ b/src/mesh/convex_hull.rs @@ -0,0 +1,5 @@ +use crate::Vector3; + + + + diff --git a/src/mesh/mod.rs b/src/mesh/mod.rs new file mode 100644 index 0000000..c4720fc --- /dev/null +++ b/src/mesh/mod.rs @@ -0,0 +1,2 @@ +pub mod convex_hull; + From 169dc3081a2e93d6fbdcc9d1ec407b3eb499c00b Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Thu, 6 Jan 2022 07:19:19 -0500 Subject: [PATCH 15/37] update interoptopus and prep for f32/f64 features --- Cargo.toml | 6 +++-- src/bvh/iter.rs | 2 +- src/bvh/optimization.rs | 4 +-- src/bvh/qbvh.rs | 4 +-- src/lib.rs | 54 ++++++++++++++++++++++++++++++++--------- src/main.rs | 2 +- src/shapes.rs | 2 +- src/testbase.rs | 2 +- 8 files changed, 55 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66c5cfa..4d117f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ parry3d-f64 = { version = "0.6.0", features = [ ], path = "C:/Users/derek/OneDri object-pool = "0.5.4" lazy_static = "1.4.0" smallvec = "1.6.1" -interoptopus = "0.10.3" -interoptopus_backend_csharp = "0.10.4" +interoptopus = "0.13.12" +interoptopus_backend_csharp = "0.13.12" flexi_logger = "0.19.3" log-panics = "2.0.0" stl_io = "0.6.0" @@ -42,7 +42,9 @@ criterion = "0.3" itertools = "0.10.1" [features] +default = [] bench = [] +f32 = [] [profile.release] lto = true diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index 3357f1b..19788e6 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -13,7 +13,7 @@ pub struct BVHTraverseIterator<'a, Shape: Bounded> { test: &'a dyn IntersectionTest, /// Reference to the input shapes array shapes: &'a [Shape], - /// Traversal stack. 4 billion items seems enough? + /// Traversal stack. Allocates if exceeds depth of 64 stack: SmallVec<[usize; 64]>, /// Position of the iterator in bvh.nodes node_index: usize, diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 15d2b1d..ec89d1f 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -12,7 +12,7 @@ use crate::bvh::*; use log::info; use rand::{thread_rng, Rng}; -use std::collections::HashSet; + // TODO Consider: Instead of getting the scene's shapes passed, let leaf nodes store an AABB // that is updated from the outside, perhaps by passing not only the indices of the changed @@ -567,7 +567,7 @@ mod tests { }; use crate::Point3; use crate::EPSILON; - use std::collections::HashSet; + #[test] /// Tests if `optimize` does not modify a fresh `BVH`. diff --git a/src/bvh/qbvh.rs b/src/bvh/qbvh.rs index e5c49ee..816c821 100644 --- a/src/bvh/qbvh.rs +++ b/src/bvh/qbvh.rs @@ -1,7 +1,7 @@ -use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; +use parry3d_f64::partitioning::{IndexedData}; use parry3d_f64::bounding_volume::aabb::{AABB}; use parry3d_f64::math::{Real, SimdBool, SimdReal, SIMD_WIDTH}; -use parry3d_f64::query::{Ray, RayCast, RayIntersection, SimdRay}; +use parry3d_f64::query::{Ray, RayCast, SimdRay}; use parry3d_f64::partitioning::{SimdBestFirstVisitStatus, SimdBestFirstVisitor}; use parry3d_f64::bounding_volume::SimdAABB; use parry3d_f64::simba::simd::{SimdBool as _, SimdPartialOrd, SimdValue}; diff --git a/src/lib.rs b/src/lib.rs index fa04165..be38511 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,15 +71,40 @@ #[cfg(all(feature = "bench", test))] extern crate test; + +/// Point math type used by this crate. Type alias for [`glam::DVec3`]. +#[cfg(not(feature = "f32"))] +pub type Point3 = glam::DVec3; + +/// Vector math type used by this crate. Type alias for [`glam::DVec3`]. +#[cfg(not(feature = "f32"))] +pub type Vector3 = glam::DVec3; + +#[cfg(not(feature = "f32"))] +/// Float type used by this crate +pub type Real = f64; + /// A minimal floating value used as a lower bound. /// TODO: replace by/add ULPS/relative float comparison methods. -pub const EPSILON: f64 = 0.00001; +#[cfg(feature = "f32")] +pub const EPSILON: f32 = 0.00001; /// Point math type used by this crate. Type alias for [`glam::Vec3`]. -pub type Point3 = glam::DVec3; +#[cfg(feature = "f32")] +pub type Point3 = glam::Vec3; /// Vector math type used by this crate. Type alias for [`glam::Vec3`]. -pub type Vector3 = glam::DVec3; +#[cfg(feature = "f32")] +pub type Vector3 = glam::Vec3; + + +#[cfg(feature = "f32")] +/// Float type used by this crate +pub type Real = f32; + +/// A minimal floating value used as a lower bound. +/// TODO: replace by/add ULPS/relative float comparison methods. +pub const EPSILON: Real = 0.00001; pub mod aabb; pub mod axis; @@ -92,7 +117,9 @@ mod utils; use aabb::{Bounded, AABB}; use bounding_hierarchy::BHShape; -use bvh::{BVH, BVHNode}; + + +use interoptopus::util::NamespaceMappings; use ray::Ray; use shapes::{Capsule, Sphere, OBB}; use glam::DQuat; @@ -103,13 +130,13 @@ use parry3d_f64::bounding_volume::aabb::AABB as QAABB; use parry3d_f64::math::{Point, Vector}; use parry3d_f64::query::Ray as RayQ; use parry3d_f64::query::visitors::RayIntersectionsVisitor; -use interoptopus::{ffi_function, ffi_type}; +use interoptopus::{ffi_function, ffi_type, Interop, Error}; use interoptopus::patterns::slice::{FFISliceMut}; use interoptopus::patterns::string::AsciiPointer; use interoptopus::lang::rust::CTypeInfo; -use interoptopus::lang::c::{CType, CompositeType, Documentation, Field, OpaqueType, PrimitiveType, Visibility, Meta}; +use interoptopus::lang::c::{CType, CompositeType, Documentation, Field, OpaqueType, Visibility, Meta}; use flexi_logger::{FileSpec, Logger, detailed_format}; -use log::{info, warn, error}; +use log::{info}; #[macro_use] extern crate lazy_static; @@ -615,6 +642,8 @@ pub fn node_32_constructor(aabb: &AABB, entry_index: u32, exit_index: u32, shape } } + + interoptopus::inventory!(my_inventory, [], [ init_logger, build_bvh, @@ -634,10 +663,9 @@ interoptopus::inventory!(my_inventory, [], [ flatten_bvh, ], [], []); -use interoptopus::util::NamespaceMappings; -use interoptopus::{Error, Interop}; -#[test] + + fn bindings_csharp() -> Result<(), Error> { use interoptopus_backend_csharp::{Config, Generator, Unsafe, overloads::{DotNet, Unity}}; @@ -658,6 +686,10 @@ fn bindings_csharp() -> Result<(), Error> { Ok(()) } +#[test] +fn gen_bindings() { + bindings_csharp().unwrap(); +} #[test] fn test_building_and_querying() { @@ -681,7 +713,7 @@ fn test_building_and_querying() { internal_bvh_index: 0 }; - let out = BVHBounds { + let _out = BVHBounds { bounds, index: 0, internal_bvh_index: 0 diff --git a/src/main.rs b/src/main.rs index 78bc12a..ffb6001 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,7 +137,7 @@ pub fn load_sponza_scene() -> (Vec, AABB) { pub fn main() { - let (mut triangles, bounds) = load_sponza_scene(); + let (mut triangles, _bounds) = load_sponza_scene(); let mut bvh = BVH::build(triangles.as_mut_slice()); for _i in 0..10 { diff --git a/src/shapes.rs b/src/shapes.rs index 03e9794..6dd955b 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -236,7 +236,7 @@ mod tests { use crate::aabb::AABB; use crate::{Point3, Vector3}; use crate::bounding_hierarchy::IntersectionTest; - use glam::{DQuat, DMat4}; + use glam::{DQuat}; use crate::shapes::{Capsule, OBB}; #[test] diff --git a/src/testbase.rs b/src/testbase.rs index 001dc41..d43fbf6 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -15,7 +15,7 @@ use rand::seq::SliceRandom; use rand::SeedableRng; use crate::aabb::{Bounded, AABB}; -use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; +use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; use crate::ray::Ray; /// A vector represented as a tuple From c0d0d998ec51d02c1959c84a347121476f00aa10 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Fri, 7 Jan 2022 08:02:09 -0500 Subject: [PATCH 16/37] refactoring of f64 into real type, move ffi bindings to subcrate --- Cargo.toml | 18 +- bvh-lib/Cargo.toml | 19 + bvh-lib/src/lib.rs | 540 ++++++++++++++++++++++++++++ examples/simple.rs | 18 +- src/aabb.rs | 103 +++--- src/axis.rs | 34 +- src/bounding_hierarchy.rs | 6 +- src/bvh/bvh_impl.rs | 639 ++++++++++++++++++--------------- src/bvh/iter.rs | 2 +- src/bvh/mod.rs | 2 - src/bvh/optimization.rs | 155 ++++---- src/bvh/qbvh.rs | 399 --------------------- src/flat_bvh.rs | 11 +- src/lib.rs | 720 +++++++------------------------------- src/main.rs | 146 -------- src/ray.rs | 37 +- src/shapes.rs | 158 +++++---- src/testbase.rs | 38 +- 18 files changed, 1357 insertions(+), 1688 deletions(-) create mode 100644 bvh-lib/Cargo.toml create mode 100644 bvh-lib/src/lib.rs delete mode 100644 src/bvh/qbvh.rs delete mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 4d117f8..459e459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,26 +13,18 @@ documentation = "https://docs.rs/crate/bvh" keywords = ["bvh", "bounding", "volume", "sah", "aabb"] license = "MIT" -[lib] -name="bvh_f64" -crate-type = ["rlib", "dylib"] +[workspace] +members = ["bvh-lib"] [dependencies] approx = "0.4" rand = "0.8" log = "0.4.14" num = "0.4" -glam = "0.15" +glam = "0.20" rayon = "1.5.1" -obj-rs = "0.6" -parry3d-f64 = { version = "0.6.0", features = [ ], path = "C:/Users/derek/OneDrive/jagexcache/Documents/GitHub/parry/build/parry3d-f64" } -object-pool = "0.5.4" -lazy_static = "1.4.0" smallvec = "1.6.1" -interoptopus = "0.13.12" -interoptopus_backend_csharp = "0.13.12" -flexi_logger = "0.19.3" -log-panics = "2.0.0" +obj-rs = "0.6" stl_io = "0.6.0" [dev-dependencies] @@ -44,7 +36,7 @@ itertools = "0.10.1" [features] default = [] bench = [] -f32 = [] +f64 = [] [profile.release] lto = true diff --git a/bvh-lib/Cargo.toml b/bvh-lib/Cargo.toml new file mode 100644 index 0000000..3e911ce --- /dev/null +++ b/bvh-lib/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bvh-lib" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bvh = { path = "../", features = ["f64"]} +interoptopus = "0.13.12" +interoptopus_backend_csharp = "0.13.12" +flexi_logger = "0.19.3" +log-panics = "2.0.0" +log = "0.4.14" +glam = "0.20" + +[lib] +name="bvh_f64" +crate-type = ["rlib", "dylib"] \ No newline at end of file diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs new file mode 100644 index 0000000..eae048c --- /dev/null +++ b/bvh-lib/src/lib.rs @@ -0,0 +1,540 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +use bvh::aabb::{Bounded, AABB}; +use bvh::bounding_hierarchy::BHShape; +use bvh::bvh::BVH; +use bvh::ray::Ray; +use bvh::shapes::{Capsule, Sphere, OBB}; +use bvh::Vector3; +use flexi_logger::{detailed_format, FileSpec, Logger}; +use glam::DQuat; +use interoptopus::lang::c::{ + CType, CompositeType, Documentation, Field, Meta, OpaqueType, Visibility, +}; +use interoptopus::lang::rust::CTypeInfo; +use interoptopus::patterns::slice::FFISliceMut; +use interoptopus::patterns::string::AsciiPointer; +use interoptopus::util::NamespaceMappings; +use interoptopus::{ffi_function, ffi_type, Error, Interop}; +use log::info; + +#[repr(C)] +#[ffi_type] +#[derive(Copy, Clone, Debug)] +pub struct Double3 { + pub x: f64, + pub y: f64, + pub z: f64, +} + +#[repr(C)] +#[ffi_type] +#[derive(Copy, Clone, Debug)] +pub struct Float3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[repr(C)] +#[ffi_type(name = "BoundingBoxD")] +#[derive(Copy, Clone, Debug)] +pub struct BoundsD { + pub min: Double3, + pub max: Double3, +} + +#[repr(C)] +#[ffi_type(name = "BvhNode")] +#[derive(Copy, Clone, Debug)] +pub struct BVHBounds { + pub bounds: BoundsD, + pub internal_bvh_index: i32, + pub index: i32, +} + +#[repr(C)] +pub struct BvhRef { + bvh: Box, +} + +unsafe impl CTypeInfo for BvhRef { + fn type_info() -> CType { + let fields: Vec = vec![Field::with_documentation( + "bvh".to_string(), + CType::ReadPointer(Box::new(CType::Opaque(OpaqueType::new( + "BvhPtr".to_string(), + Meta::new(), + )))), + Visibility::Private, + Documentation::new(), + )]; + let composite = CompositeType::new("BvhRef".to_string(), fields); + CType::Composite(composite) + } +} + +#[ffi_type] +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct QuatD { + pub x: f64, + pub y: f64, + pub z: f64, + pub w: f64, +} + +#[ffi_type(name = "Float3")] +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct Point32 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[ffi_type(name = "BoundingBox")] +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct AABB32 { + pub min: Point32, + pub max: Point32, +} + +#[ffi_type(name = "FlatNode")] +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct FlatNode32 { + pub aabb: AABB32, + pub entry_index: u32, + pub exit_index: u32, + pub shape_index: u32, +} + +impl Bounded for BVHBounds { + fn aabb(&self) -> AABB { + let min = to_vec(&self.bounds.min); + let max = to_vec(&self.bounds.max); + AABB::with_bounds(min, max) + } +} + +#[ffi_type(opaque)] +#[derive(Copy, Clone, Debug)] +pub struct RefNode { + pub index: usize, +} + +impl BHShape for BVHBounds { + fn set_bh_node_index(&mut self, index: usize) { + self.internal_bvh_index = index as i32; + } + + fn bh_node_index(&self) -> usize { + self.internal_bvh_index as usize + } +} + +pub fn to_vec(a: &Double3) -> Vector3 { + Vector3::new(a.x, a.y, a.z) +} + +pub fn to_vecd(a: &Vector3) -> Double3 { + Double3 { + x: a.x, + y: a.y, + z: a.z, + } +} + +pub fn to_quat(a: &QuatD) -> DQuat { + DQuat::from_xyzw(a.x, a.y, a.z, a.w) +} + +static LOGGER_INITIALIZED: AtomicUsize = AtomicUsize::new(0); + +#[ffi_function] +#[no_mangle] +pub extern "C" fn init_logger(log_path: AsciiPointer) { + let init_count = LOGGER_INITIALIZED.fetch_add(1, Ordering::SeqCst); + if init_count == 0 { + let path = log_path.as_str().unwrap(); + let file = FileSpec::default() + .directory(path) + .basename("bvh_f64") + .suffix("log"); + Logger::try_with_str("info") + .unwrap() + .log_to_file(file) + .format_for_files(detailed_format) + .start() + .unwrap(); + log_panics::init(); + + info!("Log initialized in folder {}", path); + } +} + +#[no_mangle] +pub extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) { + let a = unsafe { *a_ptr }; + + let a = glam::Vec3::new(a.x, a.y, a.z); + let b = unsafe { *b_ptr }; + let b = glam::Vec3::new(b.x, b.y, b.z); + let mut c = glam::Vec3::new(0.0, 0.0, 0.0); + + for _i in 0..100000 { + c = a + b + c; + } + + unsafe { + *out_ptr = Float3 { + x: c.x, + y: c.y, + z: c.z, + }; + } +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn build_bvh(shapes: &mut FFISliceMut) -> BvhRef { + let bvh = Box::new(BVH::build(shapes.as_slice_mut())); + info!("Building bvh"); + + BvhRef { bvh } +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn rebuild_bvh(bvh_ref: &mut BvhRef, shapes: &mut FFISliceMut) { + let bvh = &mut bvh_ref.bvh; + bvh.rebuild(shapes.as_slice_mut()); +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn query_ray( + bvh_ref: &BvhRef, + origin_vec: &Double3, + dir_vec: &Double3, + shapes: &mut FFISliceMut, + buffer: &mut FFISliceMut, +) -> i32 { + let bvh = &bvh_ref.bvh; + + let ray = Ray::new(to_vec(origin_vec), to_vec(dir_vec)); + let mut i = 0; + + for x in bvh.traverse_iterator(&ray, &shapes) { + if i < buffer.len() { + buffer[i as usize] = *x; + } + i += 1; + } + i as i32 +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn batch_query_rays( + bvh_ref: &BvhRef, + origins: &FFISliceMut, + dirs: &FFISliceMut, + hits: &mut FFISliceMut, + shapes: &mut FFISliceMut, + buffer: &mut FFISliceMut, +) { + let bvh = &bvh_ref.bvh; + let mut i = 0; + let ray_count = origins.len(); + for r in 0..ray_count as usize { + let ray = Ray::new(to_vec(&origins[r]), to_vec(&dirs[r])); + let mut res = 0; + for x in bvh.traverse_iterator(&ray, &shapes) { + if i < buffer.len() { + buffer[i as usize] = *x; + } + i += 1; + res += 1; + } + hits[r] = res; + } +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn query_sphere( + bvh_ref: &BvhRef, + center: &Double3, + radius: f64, + shapes: &mut FFISliceMut, + buffer: &mut FFISliceMut, +) -> i32 { + let bvh = &bvh_ref.bvh; + + let test_shape = Sphere::new(to_vec(center), radius); + let mut i = 0; + + for x in bvh.traverse_iterator(&test_shape, &shapes) { + if i < buffer.len() { + buffer[i as usize] = *x; + } + i += 1; + } + i as i32 +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn query_capsule( + bvh_ref: &BvhRef, + start: &Double3, + end: &Double3, + radius: f64, + shapes: &mut FFISliceMut, + buffer: &mut FFISliceMut, +) -> i32 { + let bvh = &bvh_ref.bvh; + + let test_shape = Capsule::new(to_vec(start), to_vec(end), radius); + let mut i = 0; + + for x in bvh.traverse_iterator(&test_shape, &shapes) { + if i < buffer.len() { + buffer[i as usize] = *x; + } + i += 1; + } + i as i32 +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn query_aabb( + bvh_ref: &BvhRef, + bounds: &BoundsD, + shapes: &mut FFISliceMut, + buffer: &mut FFISliceMut, +) -> i32 { + let bvh = &bvh_ref.bvh; + + let min = to_vec(&bounds.min); + let max = to_vec(&bounds.max); + let test_shape = AABB::with_bounds(min, max); + let mut i = 0; + + for x in bvh.traverse_iterator(&test_shape, &shapes) { + if i < buffer.len() { + buffer[i as usize] = *x; + } + i += 1; + } + i as i32 +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn query_obb( + bvh_ref: &BvhRef, + ori: &QuatD, + extents: &Double3, + center: &Double3, + shapes: &mut FFISliceMut, + buffer: &mut FFISliceMut, +) -> i32 { + let bvh = &bvh_ref.bvh; + let obb = OBB { + orientation: to_quat(ori), + extents: to_vec(extents), + center: to_vec(center), + }; + + let mut i = 0; + + for x in bvh.traverse_iterator(&obb, &shapes) { + if i < buffer.len() { + buffer[i as usize] = *x; + } + i += 1; + } + i as i32 +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn free_bvh(_bvh_ref: BvhRef) {} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn add_node( + bvh_ref: &mut BvhRef, + new_shape: i32, + shapes: &mut FFISliceMut, +) { + let bvh = &mut bvh_ref.bvh; + bvh.add_node(shapes.as_slice_mut(), new_shape as usize); +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn remove_node( + bvh_ref: &mut BvhRef, + remove_shape: i32, + shapes: &mut FFISliceMut, +) { + let bvh = &mut bvh_ref.bvh; + bvh.remove_node(shapes.as_slice_mut(), remove_shape as usize, true); +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn update_node( + bvh_ref: &mut BvhRef, + update_shape: i32, + shapes: &mut FFISliceMut, +) { + let bvh = &mut bvh_ref.bvh; + bvh.remove_node(shapes.as_slice_mut(), update_shape as usize, false); + bvh.add_node(shapes, update_shape as usize); +} + +#[ffi_function] +#[no_mangle] +pub extern "C" fn flatten_bvh( + bvh_ref: &mut BvhRef, + shapes: &mut FFISliceMut, + results: &mut FFISliceMut, +) -> i32 { + let bvh = &bvh_ref.bvh; + + let flattened = bvh.flatten_custom(shapes.as_slice_mut(), &node_32_constructor); + + for i in 0..flattened.len() { + results[i] = flattened[i]; + } + + flattened.len() as i32 +} + +pub fn node_32_constructor( + aabb: &AABB, + entry_index: u32, + exit_index: u32, + shape_index: u32, +) -> FlatNode32 { + let min = Point32 { + x: aabb.min.x as f32, + y: aabb.min.y as f32, + z: aabb.min.z as f32, + }; + let max = Point32 { + x: aabb.max.x as f32, + y: aabb.max.y as f32, + z: aabb.max.z as f32, + }; + let b = AABB32 { min, max }; + FlatNode32 { + aabb: b, + entry_index, + exit_index, + shape_index, + } +} + +interoptopus::inventory!( + my_inventory, + [], + [ + init_logger, + build_bvh, + rebuild_bvh, + query_ray, + batch_query_rays, + query_sphere, + query_capsule, + query_aabb, + query_obb, + free_bvh, + add_node, + remove_node, + update_node, + flatten_bvh, + ], + [], + [] +); + +fn bindings_csharp() -> Result<(), Error> { + use interoptopus_backend_csharp::{ + overloads::{DotNet, Unity}, + Config, Generator, Unsafe, + }; + + Generator::new( + Config { + class: "NativeBvhInterop".to_string(), + dll_name: "bvh_f64".to_string(), + namespace_mappings: NamespaceMappings::new("Assets.Scripts.Native"), + use_unsafe: Unsafe::UnsafePlatformMemCpy, + ..Config::default() + }, + my_inventory(), + ) + .add_overload_writer(Unity::new()) + .add_overload_writer(DotNet::new()) + .write_file("../bindings/csharp/Interop.cs")?; + + Ok(()) +} + +#[test] +fn gen_bindings() { + bindings_csharp().unwrap(); +} + +#[test] +fn test_building_and_querying() { + let min = Double3 { + x: -1.0, + y: -1.0, + z: -1.0, + }; + let max = Double3 { + x: 1.0, + y: 1.0, + z: 1.0, + }; + let bounds = BoundsD { min, max }; + let b = BVHBounds { + bounds, + index: 0, + internal_bvh_index: 0, + }; + + let _out = BVHBounds { + bounds, + index: 0, + internal_bvh_index: 0, + }; + + let origin = Double3 { + x: 0.0, + y: -5.0, + z: 0.0, + }; + let dir = Double3 { + x: 0.0, + y: 1.0, + z: 0.0, + }; + let in_slice = &mut [b]; + let mut input = FFISliceMut::::from_slice(in_slice); + let out_slice = &mut [b]; + let mut out = FFISliceMut::::from_slice(out_slice); + let bvh_ref = build_bvh(&mut input); + + let x = query_ray(&bvh_ref, &origin, &dir, &mut input, &mut out); + assert_eq!(x, 1); +} diff --git a/examples/simple.rs b/examples/simple.rs index 9234994..7057983 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,13 +1,15 @@ -use bvh_f64::aabb::{Bounded, AABB}; -use bvh_f64::bounding_hierarchy::BHShape; -use bvh_f64::bvh::BVH; -use bvh_f64::ray::Ray; -use bvh_f64::{Point3, Vector3}; +use bvh::{ + aabb::{Bounded, AABB}, + bounding_hierarchy::BHShape, + bvh::BVH, + ray::Ray, + Point3, Real, Vector3, +}; #[derive(Debug)] struct Sphere { position: Point3, - radius: f64, + radius: Real, node_index: usize, } @@ -33,8 +35,8 @@ impl BHShape for Sphere { pub fn main() { let mut spheres = Vec::new(); for i in 0..1000000u32 { - let position = Point3::new(i as f64, i as f64, i as f64); - let radius = (i % 10) as f64 + 1.0; + let position = Point3::new(i as Real, i as Real, i as Real); + let radius = (i % 10) as Real + 1.0; spheres.push(Sphere { position, radius, diff --git a/src/aabb.rs b/src/aabb.rs index 028c417..ab9629b 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -1,10 +1,10 @@ //! Axis Aligned Bounding Boxes. +use crate::bounding_hierarchy::IntersectionTest; use std::fmt; use std::ops::Index; -use crate::bounding_hierarchy::{IntersectionTest}; -use crate::{Point3, Vector3}; +use crate::{Point3, Real, Vector3}; use crate::axis::Axis; @@ -106,8 +106,8 @@ impl AABB { /// pub fn empty() -> AABB { AABB { - min: Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), - max: Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), + min: Point3::new(Real::INFINITY, Real::INFINITY, Real::INFINITY), + max: Point3::new(Real::NEG_INFINITY, Real::NEG_INFINITY, Real::NEG_INFINITY), } } @@ -130,12 +130,12 @@ impl AABB { /// [`Point3`]: glam::Vec3 /// pub fn contains(&self, p: &Point3) -> bool { - p.x >= self.min.x - && p.x <= self.max.x - && p.y >= self.min.y - && p.y <= self.max.y - && p.z >= self.min.z - && p.z <= self.max.z + !(p.x < self.min.x + || p.x > self.max.x + || p.y < self.min.y + || p.y > self.max.y + || p.z < self.min.z + || p.z > self.max.z) } /// Returns true if the [`Point3`] is approximately inside the [`AABB`] @@ -158,7 +158,7 @@ impl AABB { /// [`AABB`]: struct.AABB.html /// [`Point3`]: glam::Vec3 /// - pub fn approx_contains_eps(&self, p: &Point3, epsilon: f64) -> bool { + pub fn approx_contains_eps(&self, p: &Point3, epsilon: Real) -> bool { (p.x - self.min.x) > -epsilon && (p.x - self.max.x) < epsilon && (p.y - self.min.y) > -epsilon @@ -185,7 +185,7 @@ impl AABB { /// ``` /// /// [`AABB`]: struct.AABB.html - pub fn approx_contains_aabb_eps(&self, other: &AABB, epsilon: f64) -> bool { + pub fn approx_contains_aabb_eps(&self, other: &AABB, epsilon: Real) -> bool { self.approx_contains_eps(&other.min, epsilon) && self.approx_contains_eps(&other.max, epsilon) } @@ -208,13 +208,13 @@ impl AABB { /// ``` /// /// [`AABB`]: struct.AABB.html - pub fn relative_eq(&self, other: &AABB, epsilon: f64) -> bool { - f64::abs(self.min.x - other.min.x) < epsilon - && f64::abs(self.min.y - other.min.y) < epsilon - && f64::abs(self.min.z - other.min.z) < epsilon - && f64::abs(self.max.x - other.max.x) < epsilon - && f64::abs(self.max.y - other.max.y) < epsilon - && f64::abs(self.max.z - other.max.z) < epsilon + pub fn relative_eq(&self, other: &AABB, epsilon: Real) -> bool { + Real::abs(self.min.x - other.min.x) < epsilon + && Real::abs(self.min.y - other.min.y) < epsilon + && Real::abs(self.min.z - other.min.z) < epsilon + && Real::abs(self.max.x - other.max.x) < epsilon + && Real::abs(self.max.y - other.max.y) < epsilon + && Real::abs(self.max.z - other.max.z) < epsilon } /// Returns a new minimal [`AABB`] which contains both this [`AABB`] and `other`. @@ -505,7 +505,7 @@ impl AABB { /// /// [`AABB`]: struct.AABB.html /// - pub fn surface_area(&self) -> f64 { + pub fn surface_area(&self) -> Real { let size = self.size(); 2.0 * (size.x * size.y + size.x * size.z + size.y * size.z) } @@ -527,7 +527,7 @@ impl AABB { /// /// [`AABB`]: struct.AABB.html /// - pub fn volume(&self) -> f64 { + pub fn volume(&self) -> Real { let size = self.size(); size.x * size.y * size.z } @@ -568,7 +568,10 @@ impl AABB { impl IntersectionTest for AABB { fn intersects_aabb(&self, aabb: &AABB) -> bool { - !(self.max.x < aabb.min.x) && !(self.min.x > aabb.max.x) && (!(self.max.y < aabb.min.y) && !(self.min.y > aabb.max.y)) && (!(self.max.z < aabb.min.z) && !(self.min.z > aabb.max.z)) + !(self.max.x < aabb.min.x) + && !(self.min.x > aabb.max.x) + && (!(self.max.y < aabb.min.y) && !(self.min.y > aabb.max.y)) + && (!(self.max.z < aabb.min.z) && !(self.min.z > aabb.max.z)) } } @@ -664,10 +667,10 @@ impl Bounded for Point3 { #[cfg(test)] mod tests { use crate::aabb::{Bounded, AABB}; + use crate::bounding_hierarchy::IntersectionTest; use crate::testbase::{tuple_to_point, tuple_to_vector, tuplevec_large_strategy, TupleVec}; use crate::EPSILON; use crate::{Point3, Vector3}; - use crate::bounding_hierarchy::IntersectionTest; use float_eq::assert_float_eq; use proptest::prelude::*; @@ -710,6 +713,9 @@ mod tests { let aabb = AABB::empty().grow(&p1).join_bounded(&p2); // Its center should be inside the `AABB` + if !aabb.contains(&aabb.center()) { + dbg!(aabb.center()); + } assert!(aabb.contains(&aabb.center())); } @@ -722,29 +728,30 @@ mod tests { // Span the `AABB` let aabb = AABB::empty().grow(&p1).join_bounded(&p2); - - // Get its size and center - let size = aabb.size(); - let size_half = size / 2.0; - let center = aabb.center(); - - // Compute the min and the max corners of the AABB by hand - let inside_ppp = center + size_half * 0.9; - let inside_mmm = center - size_half * 0.9; - - // Generate two points which are outside the AABB - let outside_ppp = inside_ppp + size_half * 1.1; - let outside_mmm = inside_mmm - size_half * 1.1; - let disjoint_mmm = outside_ppp + size_half * 0.1; - let disjoint_ppp = outside_ppp + size_half; - let small_aabb = AABB::empty().grow(&inside_ppp).grow(&inside_mmm); - let big_aabb = AABB::empty().grow(&outside_ppp).grow(&outside_mmm); - let dis_aabb = AABB::empty().grow(&disjoint_ppp).grow(&disjoint_mmm); - - assert!(aabb.intersects_aabb(&small_aabb) - && aabb.intersects_aabb(&big_aabb) - && big_aabb.intersects_aabb(&small_aabb) - && !dis_aabb.intersects_aabb(&big_aabb)); + if aabb.size().abs().min_element() > EPSILON { + // Get its size and center + let size = aabb.size(); + let size_half = size / 2.0; + let center = aabb.center(); + + // Compute the min and the max corners of the AABB by hand + let inside_ppp = center + size_half * 0.9; + let inside_mmm = center - size_half * 0.9; + + // Generate two points which are outside the AABB + let outside_ppp = inside_ppp + (size_half * 1.1); + let outside_mmm = inside_mmm - size_half * 1.1; + let disjoint_mmm = outside_ppp + size_half * 0.1; + let disjoint_ppp = outside_ppp + size_half; + let small_aabb = AABB::empty().grow(&inside_ppp).grow(&inside_mmm); + let big_aabb = AABB::empty().grow(&outside_ppp).grow(&outside_mmm); + let dis_aabb = AABB::empty().grow(&disjoint_ppp).grow(&disjoint_mmm); + + assert!(aabb.intersects_aabb(&small_aabb)); + assert!(aabb.intersects_aabb(&big_aabb)); + assert!(big_aabb.intersects_aabb(&small_aabb)); + assert!(!dis_aabb.intersects_aabb(&big_aabb)); + } } // Test whether the joint of two point-sets contains all the points. @@ -821,8 +828,8 @@ mod tests { // Compute and compare the surface area of an AABB by hand. #[test] - fn test_surface_area_cube(pos: TupleVec, size in EPSILON..10e30_f64) { - + fn test_surface_area_cube(pos: TupleVec, size in EPSILON..10e30) { + // Generate some non-empty AABB let pos = tuple_to_point(&pos); let size_vec = Vector3::new(size, size, size); diff --git a/src/axis.rs b/src/axis.rs index a63a518..c390f96 100644 --- a/src/axis.rs +++ b/src/axis.rs @@ -1,7 +1,7 @@ //! Axis enum for indexing three-dimensional structures. #![allow(unused)] -use crate::{Point3, Vector3}; +use crate::{Point3, Real, Vector3}; use std::fmt::{Display, Formatter, Result}; use std::ops::{Index, IndexMut}; @@ -63,19 +63,19 @@ impl Display for Axis { } /// Make slices indexable by `Axis`. -impl Index for [f64] { - type Output = f64; +impl Index for [Real] { + type Output = Real; - fn index(&self, axis: Axis) -> &f64 { + fn index(&self, axis: Axis) -> &Real { &self[axis as usize] } } /// Make `Point3` indexable by `Axis`. impl Index for Point3 { - type Output = f64; + type Output = Real; - fn index(&self, axis: Axis) -> &f64 { + fn index(&self, axis: Axis) -> &Real { match axis { Axis::X => &self.x, Axis::Y => &self.y, @@ -86,9 +86,9 @@ impl Index for Point3 { /// Make `Vector3` indexable by `Axis`. impl Index for MyType { - type Output = f64; + type Output = Real; - fn index(&self, axis: Axis) -> &f64 { + fn index(&self, axis: Axis) -> &Real { match axis { Axis::X => &self.0.x, Axis::Y => &self.0.y, @@ -98,15 +98,15 @@ impl Index for MyType { } /// Make slices mutably accessible by `Axis`. -impl IndexMut for [f64] { - fn index_mut(&mut self, axis: Axis) -> &mut f64 { +impl IndexMut for [Real] { + fn index_mut(&mut self, axis: Axis) -> &mut Real { &mut self[axis as usize] } } /// Make `Point3` mutably accessible by `Axis`. impl IndexMut for Point3 { - fn index_mut(&mut self, axis: Axis) -> &mut f64 { + fn index_mut(&mut self, axis: Axis) -> &mut Real { match axis { Axis::X => &mut self.x, Axis::Y => &mut self.y, @@ -117,7 +117,7 @@ impl IndexMut for Point3 { /// Make `Vector3` mutably accessible by `Axis`. impl IndexMut for MyType { - fn index_mut(&mut self, axis: Axis) -> &mut f64 { + fn index_mut(&mut self, axis: Axis) -> &mut Real { match axis { Axis::X => &mut self.0.x, Axis::Y => &mut self.0.y, @@ -128,28 +128,28 @@ impl IndexMut for MyType { #[cfg(test)] mod test { - use crate::axis::Axis; + use crate::{axis::Axis, Real}; use proptest::prelude::*; proptest! { // Test whether accessing arrays by index is the same as accessing them by `Axis`. #[test] - fn test_index_by_axis(tpl: (f64, f64, f64)) { + fn test_index_by_axis(tpl: (Real, Real, Real)) { let a = [tpl.0, tpl.1, tpl.2]; - assert!((a[0] - a[Axis::X]).abs() < f64::EPSILON && (a[1] - a[Axis::Y]).abs() < f64::EPSILON && (a[2] - a[Axis::Z]).abs() < f64::EPSILON); + assert!((a[0] - a[Axis::X]).abs() < Real::EPSILON && (a[1] - a[Axis::Y]).abs() < Real::EPSILON && (a[2] - a[Axis::Z]).abs() < Real::EPSILON); } // Test whether arrays can be mutably set, by indexing via `Axis`. #[test] - fn test_set_by_axis(tpl: (f64, f64, f64)) { + fn test_set_by_axis(tpl: (Real, Real, Real)) { let mut a = [0.0, 0.0, 0.0]; a[Axis::X] = tpl.0; a[Axis::Y] = tpl.1; a[Axis::Z] = tpl.2; - assert!((a[0] - tpl.0).abs() < f64::EPSILON && (a[1] - tpl.1).abs() < f64::EPSILON && (a[2] - tpl.2).abs() < f64::EPSILON); + assert!((a[0] - tpl.0).abs() < Real::EPSILON && (a[1] - tpl.1).abs() < Real::EPSILON && (a[2] - tpl.2).abs() < Real::EPSILON); } } } diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index c87cdd9..8f08eb8 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -163,7 +163,11 @@ pub trait BoundingHierarchy { /// [`BoundingHierarchy`]: trait.BoundingHierarchy.html /// [`AABB`]: ../aabb/struct.AABB.html /// - fn traverse<'a, Shape: BHShape>(&'a self, test: &impl IntersectionTest, shapes: &'a [Shape]) -> Vec<&Shape>; + fn traverse<'a, Shape: BHShape>( + &'a self, + test: &impl IntersectionTest, + shapes: &'a [Shape], + ) -> Vec<&Shape>; /// Prints the [`BoundingHierarchy`] in a tree-like visualization. /// diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index d23291d..fd56be3 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -5,20 +5,16 @@ //! use crate::aabb::{Bounded, AABB}; +use crate::axis::Axis; use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; use crate::bvh::iter::BVHTraverseIterator; use crate::utils::{joint_aabb_of_shapes, Bucket}; -use crate::Point3; use crate::EPSILON; -use std::slice; +use crate::{Point3, Real}; use rayon::prelude::*; -use std::iter::repeat; -use std::sync::atomic::{AtomicUsize}; use smallvec::SmallVec; -use crate::axis::Axis; - - -pub static BUILD_THREAD_COUNT: AtomicUsize = AtomicUsize::new(20); +use std::iter::repeat; +use std::slice; /// The [`BVHNode`] enum that describes a node in a [`BVH`]. /// It's either a leaf node and references a shape (by holding its index) @@ -90,10 +86,7 @@ impl PartialEq for BVHNode { parent_index: other_parent_index, shape_index: other_shape_index, }, - ) => { - self_parent_index == other_parent_index - && self_shape_index == other_shape_index - } + ) => self_parent_index == other_parent_index && self_shape_index == other_shape_index, _ => false, } } @@ -131,7 +124,10 @@ impl BVHNode { /// Returns the index of the left child node. pub fn child_l_mut(&mut self) -> &mut usize { match *self { - BVHNode::Node { ref mut child_l_index, .. } => child_l_index, + BVHNode::Node { + ref mut child_l_index, + .. + } => child_l_index, _ => panic!("Tried to get the left child of a leaf node."), } } @@ -166,7 +162,10 @@ impl BVHNode { /// Returns the index of the right child node. pub fn child_r_mut(&mut self) -> &mut usize { match *self { - BVHNode::Node { ref mut child_r_index, .. } => child_r_index, + BVHNode::Node { + ref mut child_r_index, + .. + } => child_r_index, _ => panic!("Tried to get the right child of a leaf node."), } } @@ -228,7 +227,10 @@ impl BVHNode { /// or `None` if it is an interior node. pub fn shape_index_mut(&mut self) -> Option<&mut usize> { match *self { - BVHNode::Leaf { ref mut shape_index, .. } => Some(shape_index), + BVHNode::Leaf { + ref mut shape_index, + .. + } => Some(shape_index), _ => None, } } @@ -253,9 +255,8 @@ impl BVHNode { nodes: &mut [BVHNode], parent_index: usize, depth: u32, - node_index: usize + node_index: usize, ) -> usize { - //println!("Building node={}", node_index); // Helper function to accumulate the AABB joint and the centroids AABB @@ -269,39 +270,30 @@ impl BVHNode { ) } - let use_parallel_hull = false; - let parallel_recurse = false; - if nodes.len() > 128 { - //parallel_recurse = true; - /* - let avail_threads = BUILD_THREAD_COUNT.load(Ordering::Relaxed); - if avail_threads > 0 { - let exchange = BUILD_THREAD_COUNT.compare_exchange(avail_threads, avail_threads - 1, Ordering::Relaxed, Ordering::Relaxed); - match exchange { - Ok(_) => parallel_recurse = true, - Err(_) => () - }; - - if nodes.len() > 4096 { - //use_parallel_hull = true; - } - } - */ + let mut parallel_recurse = false; + if nodes.len() > 64 { + parallel_recurse = true; }; - - let convex_hull = if use_parallel_hull { - indices.par_iter().fold(|| (AABB::default(), AABB::default()), - |convex_hull, i| grow_convex_hull(convex_hull, &shapes[*i].aabb())).reduce(|| (AABB::default(), AABB::default()), |a , b| (a.0.join(&b.0), a.1.join(&b.1))) + indices + .par_iter() + .fold( + || (AABB::default(), AABB::default()), + |convex_hull, i| grow_convex_hull(convex_hull, &shapes[*i].aabb()), + ) + .reduce( + || (AABB::default(), AABB::default()), + |a, b| (a.0.join(&b.0), a.1.join(&b.1)), + ) } else { let mut convex_hull = Default::default(); - + for index in indices.iter() { convex_hull = grow_convex_hull(convex_hull, &shapes[*index].aabb()); - }; + } convex_hull }; @@ -316,7 +308,6 @@ impl BVHNode { }; // Let the shape know the index of the node that represents it. shapes[shape_index].set_bh_node_index(node_index); - //println!("slice_i={} parent={}", node_index, parent_index); return node_index; } @@ -329,77 +320,137 @@ impl BVHNode { let split_axis_size = centroid_bounds.max[split_axis] - centroid_bounds.min[split_axis]; // The following `if` partitions `indices` for recursively calling `BVH::build`. - let (child_l_index, child_l_aabb, child_r_index, child_r_aabb) = if split_axis_size - < EPSILON - { - // In this branch the shapes lie too close together so that splitting them in a - // sensible way is not possible. Instead we just split the list of shapes in half. - let (child_l_indices, child_r_indices) = indices.split_at_mut(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 next_nodes = &mut nodes[1..]; - let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); - let child_l_index = node_index + 1; - let child_r_index = node_index + 1 + l_nodes.len(); - // Proceed recursively. - if parallel_recurse { - // This is safe because the indices represent unique shapes and we'll never write to the same one - let (shapes_a, shapes_b) = unsafe { - let ptr = shapes.as_mut_ptr(); - let len = shapes.len(); - let shapes_a = slice::from_raw_parts_mut(ptr, len); - let shapes_b = slice::from_raw_parts_mut(ptr, len); - (shapes_a, shapes_b) - }; - rayon::join(|| BVHNode::build(shapes_a, child_l_indices, l_nodes, node_index, depth + 1, child_l_index), - || BVHNode::build(shapes_b, child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); - //BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); + let (child_l_index, child_l_aabb, child_r_index, child_r_aabb) = + if split_axis_size < EPSILON { + // In this branch the shapes lie too close together so that splitting them in a + // sensible way is not possible. Instead we just split the list of shapes in half. + let (child_l_indices, child_r_indices) = indices.split_at_mut(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 next_nodes = &mut nodes[1..]; + let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); + let child_l_index = node_index + 1; + let child_r_index = node_index + 1 + l_nodes.len(); + // Proceed recursively. + if parallel_recurse { + // This is safe because shapes is only accessed using the indices and each index is unique + let (shapes_a, shapes_b) = unsafe { + let ptr = shapes.as_mut_ptr(); + let len = shapes.len(); + let shapes_a = slice::from_raw_parts_mut(ptr, len); + let shapes_b = slice::from_raw_parts_mut(ptr, len); + (shapes_a, shapes_b) + }; + rayon::join( + || { + BVHNode::build( + shapes_a, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + ) + }, + || { + BVHNode::build( + shapes_b, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + ) + }, + ); + } else { + BVHNode::build( + shapes, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + ); + BVHNode::build( + shapes, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + ); + } + (child_l_index, child_l_aabb, child_r_index, child_r_aabb) } else { - BVHNode::build(shapes, child_l_indices, l_nodes, node_index, depth + 1, child_l_index); - BVHNode::build(shapes, child_r_indices, r_nodes, node_index, depth + 1, child_r_index); - } - //println!("{:?}", (parent_index, child_l_index, child_l_indices.len(), child_r_index, child_r_indices.len())); - (child_l_index, child_l_aabb, child_r_index, child_r_aabb) - } else { - - // Join together all index buckets. - - - // split input indices, loop over assignments and assign - - - //let child_l_indices = concatenate_vectors(l_assignments); - //let child_r_indices = concatenate_vectors(r_assignments); - - let (child_l_aabb, child_r_aabb, child_l_indices, child_r_indices) = BVHNode::build_buckets(shapes, indices, split_axis, split_axis_size, ¢roid_bounds, &aabb_bounds); + let (child_l_aabb, child_r_aabb, child_l_indices, child_r_indices) = + BVHNode::build_buckets( + shapes, + indices, + split_axis, + split_axis_size, + ¢roid_bounds, + &aabb_bounds, + ); - let next_nodes = &mut nodes[1..]; - let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); + let next_nodes = &mut nodes[1..]; + let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); - let child_l_index = node_index + 1; - let child_r_index = node_index + 1 + l_nodes.len(); - // Proceed recursively. + let child_l_index = node_index + 1; + let child_r_index = node_index + 1 + l_nodes.len(); + // Proceed recursively. - if parallel_recurse { - let (shapes_a, shapes_b) = unsafe { - let ptr = shapes.as_mut_ptr(); - let len = shapes.len(); - let shapes_a = slice::from_raw_parts_mut(ptr, len); - let shapes_b = slice::from_raw_parts_mut(ptr, len); - (shapes_a, shapes_b) - }; - rayon::join(|| BVHNode::build(shapes_a, child_l_indices, l_nodes, node_index, depth + 1, child_l_index), - || BVHNode::build(shapes_b, child_r_indices, r_nodes, node_index, depth + 1, child_r_index)); - //BUILD_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); - } else { - - BVHNode::build(shapes, child_l_indices, l_nodes, node_index, depth + 1, child_l_index); - BVHNode::build(shapes, child_r_indices, r_nodes, node_index, depth + 1, child_r_index); - } - //println!("{:?}", (parent_index, child_l_index, child_l_indices.len(), child_r_index, child_r_indices.len())); - (child_l_index, child_l_aabb, child_r_index, child_r_aabb) - }; + if parallel_recurse { + let (shapes_a, shapes_b) = unsafe { + let ptr = shapes.as_mut_ptr(); + let len = shapes.len(); + let shapes_a = slice::from_raw_parts_mut(ptr, len); + let shapes_b = slice::from_raw_parts_mut(ptr, len); + (shapes_a, shapes_b) + }; + rayon::join( + || { + BVHNode::build( + shapes_a, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + ) + }, + || { + BVHNode::build( + shapes_b, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + ) + }, + ); + } else { + BVHNode::build( + shapes, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + ); + BVHNode::build( + shapes, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + ); + } + (child_l_index, child_l_aabb, child_r_index, child_r_aabb) + }; // Construct the actual data structure and replace the dummy node. assert!(!child_l_aabb.is_empty()); @@ -415,14 +466,14 @@ impl BVHNode { node_index } - fn build_buckets<'a, T: BHShape>( shapes: &mut [T], indices: &'a mut [usize], split_axis: Axis, - split_axis_size: f64, + split_axis_size: Real, centroid_bounds: &AABB, - aabb_bounds: &AABB) -> (AABB, AABB, &'a mut [usize], &'a mut [usize]) { + aabb_bounds: &AABB, + ) -> (AABB, AABB, &'a mut [usize], &'a mut [usize]) { // Create six `Bucket`s, and six index assignment vector. const NUM_BUCKETS: usize = 6; let mut buckets = [Bucket::empty(); NUM_BUCKETS]; @@ -440,7 +491,7 @@ impl BVHNode { (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; // Convert that to the actual `Bucket` number. - let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f64 - 0.01)) as usize; + let bucket_num = (bucket_num_relative * (NUM_BUCKETS as Real - 0.01)) as usize; // Extend the selected `Bucket` and add the index to the actual bucket. buckets[bucket_num].add_aabb(&shape_aabb); @@ -449,7 +500,7 @@ impl BVHNode { // Compute the costs for each configuration and select the best configuration. let mut min_bucket = 0; - let mut min_cost = f64::INFINITY; + let mut min_cost = Real::INFINITY; let mut child_l_aabb = AABB::empty(); let mut child_r_aabb = AABB::empty(); for i in 0..(NUM_BUCKETS - 1) { @@ -457,8 +508,8 @@ impl BVHNode { 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 f64 * child_l.aabb.surface_area() - + child_r.size as f64 * child_r.aabb.surface_area()) + let cost = (child_l.size as Real * child_l.aabb.surface_area() + + child_r.size as Real * child_r.aabb.surface_area()) / aabb_bounds.surface_area(); if cost < min_cost { min_bucket = i; @@ -467,10 +518,10 @@ impl BVHNode { child_r_aabb = child_r.aabb; } } - + // Join together all index buckets. + // split input indices, loop over assignments and assign let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1); - let mut l_count = 0; for group in l_assignments.iter() { l_count += group.len(); @@ -581,7 +632,11 @@ impl BVH { /// [`BVH`]: struct.BVH.html /// [`AABB`]: ../aabb/struct.AABB.html /// - pub fn traverse<'a, Shape: Bounded>(&'a self, ray: &impl IntersectionTest, shapes: &'a [Shape]) -> Vec<&Shape> { + pub fn traverse<'a, Shape: Bounded>( + &'a self, + ray: &impl IntersectionTest, + shapes: &'a [Shape], + ) -> Vec<&Shape> { let mut indices = Vec::new(); BVHNode::traverse_recursive(&self.nodes, 0, ray, &mut indices); indices @@ -604,11 +659,7 @@ impl BVH { BVHTraverseIterator::new(self, test, shapes) } - pub fn add_node( - &mut self, - shapes: &mut [T], - new_shape_index: usize - ) { + pub fn add_node(&mut self, shapes: &mut [T], new_shape_index: usize) { let mut i = 0; let new_shape = &shapes[new_shape_index]; let shape_aabb = new_shape.aabb(); @@ -617,11 +668,12 @@ impl BVH { if self.nodes.len() == 0 { self.nodes.push(BVHNode::Leaf { parent_index: 0, - shape_index: new_shape_index + shape_index: new_shape_index, }); shapes[new_shape_index].set_bh_node_index(0); - return + return; } + let mut depth = 0; loop { match self.nodes[i] { @@ -630,7 +682,7 @@ impl BVH { child_l_index, child_r_aabb, child_r_index, - parent_index + parent_index, } => { let left_expand = child_l_aabb.join(&shape_aabb); @@ -643,16 +695,16 @@ impl BVH { // merge is more expensive only do when it's significantly better let merge_discount = 0.3; + //dbg!(depth); // compared SA of the options - if merged < send_left.min(send_right) * merge_discount - { + if merged < send_left.min(send_right) * merge_discount { //println!("Merging left and right trees"); // Merge left and right trees let l_index = self.nodes.len(); let new_left = BVHNode::Leaf { parent_index: i, - shape_index: new_shape_index + shape_index: new_shape_index, }; shapes[new_shape_index].set_bh_node_index(l_index); self.nodes.push(new_left); @@ -663,7 +715,7 @@ impl BVH { child_l_index, child_r_aabb: child_r_aabb.clone(), child_r_index, - parent_index: i + parent_index: i, }; self.nodes.push(new_right); *self.nodes[child_r_index].parent_mut() = r_index; @@ -674,7 +726,7 @@ impl BVH { child_l_index: l_index, child_r_aabb: merged_aabb, child_r_index: r_index, - parent_index + parent_index, }; //self.fix_depth(l_index, depth + 1); //self.fix_depth(r_index, depth + 1); @@ -682,8 +734,7 @@ impl BVH { } else if send_left < send_right { // send new box down left side //println!("Sending left"); - if i == child_l_index - { + if i == child_l_index { panic!("broken loop"); } let child_l_aabb = left_expand; @@ -692,14 +743,13 @@ impl BVH { child_l_index, child_r_aabb, child_r_index, - parent_index + parent_index, }; i = child_l_index; } else { // send new box down right //println!("Sending right"); - if i == child_r_index - { + if i == child_r_index { panic!("broken loop"); } let child_r_aabb = right_expand; @@ -708,18 +758,21 @@ impl BVH { child_l_index, child_r_aabb, child_r_index, - parent_index + parent_index, }; i = child_r_index; } } - BVHNode::Leaf { shape_index, parent_index} => { + BVHNode::Leaf { + shape_index, + parent_index, + } => { //println!("Splitting leaf"); // Split leaf into 2 nodes and insert the new box let l_index = self.nodes.len(); let new_left = BVHNode::Leaf { parent_index: i, - shape_index: new_shape_index + shape_index: new_shape_index, }; shapes[new_shape_index].set_bh_node_index(l_index); self.nodes.push(new_left); @@ -728,7 +781,7 @@ impl BVH { let child_r_index = self.nodes.len(); let new_right = BVHNode::Leaf { parent_index: i, - shape_index: shape_index + shape_index: shape_index, }; shapes[shape_index].set_bh_node_index(child_r_index); self.nodes.push(new_right); @@ -738,13 +791,14 @@ impl BVH { child_l_index: l_index, child_r_aabb, child_r_index, - parent_index + parent_index, }; self.nodes[i] = new_node; self.fix_aabbs_ascending(shapes, parent_index); return; } } + depth += 1; } } @@ -754,9 +808,8 @@ impl BVH { deleted_shape_index: usize, swap_shape: bool, ) { - if self.nodes.len() == 0 - { - return + if self.nodes.len() == 0 { + return; //panic!("can't remove a node from a bvh with only one node"); } let bad_shape = &shapes[deleted_shape_index]; @@ -772,7 +825,6 @@ impl BVH { self.nodes.clear(); } } else { - //println!("delete_i={}", dead_node_index); let dead_node = self.nodes[dead_node_index]; @@ -782,8 +834,16 @@ impl BVH { let gp_index = self.nodes[parent_index].parent(); //println!("{}->{}->{}", gp_index, parent_index, dead_node_index); - let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r() } else { self.nodes[parent_index].child_l() }; - let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { self.nodes[parent_index].child_r_aabb() } else { self.nodes[parent_index].child_l_aabb() }; + let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { + self.nodes[parent_index].child_r() + } else { + self.nodes[parent_index].child_l() + }; + let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { + self.nodes[parent_index].child_r_aabb() + } else { + self.nodes[parent_index].child_l_aabb() + }; // TODO: fix potential issue leaving empty spot in self.nodes // the node swapped to sibling_index should probably be swapped to the end // of the vector and the vector truncated @@ -791,10 +851,13 @@ impl BVH { // We are removing one of the children of the root node // The other child needs to become the root node // The old root node and the dead child then have to be moved - - //println!("gp == parent {}", parent_index); + + // println!("gp == parent {}", parent_index); if parent_index != 0 { - panic!("Circular node that wasn't root parent={} node={}", parent_index, dead_node_index); + panic!( + "Circular node that wasn't root parent={} node={}", + parent_index, dead_node_index + ); } self.nodes.swap(parent_index, sibling_index); @@ -804,7 +867,7 @@ impl BVH { shapes[index].set_bh_node_index(parent_index); self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); - }, + } _ => { *self.nodes[parent_index].parent_mut() = parent_index; let new_root = self.nodes[parent_index]; @@ -819,11 +882,19 @@ impl BVH { //println!("nodes_len {}, sib_index {}", self.nodes.len(), sibling_index); //println!("nodes_len {}", self.nodes.len()); } else { - let box_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_aabb_mut() } else { self.nodes[gp_index].child_r_aabb_mut() }; + let box_to_change = if self.nodes[gp_index].child_l() == parent_index { + self.nodes[gp_index].child_l_aabb_mut() + } else { + self.nodes[gp_index].child_r_aabb_mut() + }; //println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); *box_to_change = sibling_box; //println!("{} {} {}", gp_index, self.nodes[gp_index].child_l_aabb(), self.nodes[gp_index].child_r_aabb()); - let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { self.nodes[gp_index].child_l_mut() } else { self.nodes[gp_index].child_r_mut() }; + let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { + self.nodes[gp_index].child_l_mut() + } else { + self.nodes[gp_index].child_r_mut() + }; //println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); *ref_to_change = sibling_index; *self.nodes[sibling_index].parent_mut() = gp_index; @@ -832,17 +903,17 @@ impl BVH { //let new_depth = self.nodes[sibling_index].depth() - 1; //*self.nodes[sibling_index].depth_mut() = new_depth; // remove node and parent - + //println!("---"); //self.pretty_print(); //println!("---"); self.swap_and_remove_index(shapes, dead_node_index.max(parent_index)); - + //println!("---"); //self.pretty_print(); //println!("---"); self.swap_and_remove_index(shapes, parent_index.min(dead_node_index)); - + //println!("---"); //self.pretty_print(); //println!("---"); @@ -855,20 +926,14 @@ impl BVH { shapes.swap(deleted_shape_index, end_shape); let node_index = shapes[deleted_shape_index].bh_node_index(); match self.nodes[node_index].shape_index_mut() { - Some(index) => { - *index = deleted_shape_index - }, + Some(index) => *index = deleted_shape_index, _ => {} } } } } - fn fix_aabbs_ascending( - &mut self, - shapes: &mut [T], - node_index: usize - ) { + fn fix_aabbs_ascending(&mut self, shapes: &mut [T], node_index: usize) { let mut index_to_fix = node_index; while index_to_fix != 0 { let parent = self.nodes[index_to_fix].parent(); @@ -878,7 +943,7 @@ impl BVH { child_l_index, child_r_index, child_l_aabb, - child_r_aabb + child_r_aabb, } => { //println!("checking {} l={} r={}", parent, child_l_index, child_r_index); let l_aabb = self.nodes[child_l_index].get_node_aabb(shapes); @@ -898,41 +963,48 @@ impl BVH { } if !stop { index_to_fix = parent_index; + //dbg!(index_to_fix); } else { index_to_fix = 0; } } - _ => {index_to_fix = 0} + _ => index_to_fix = 0, } } } - fn swap_and_remove_index( - &mut self, - shapes: &mut [T], - node_index: usize - ) { + fn swap_and_remove_index(&mut self, shapes: &mut [T], node_index: usize) { let end = self.nodes.len() - 1; //println!("removing node {}", node_index); if node_index != end { self.nodes[node_index] = self.nodes[end]; let node_parent = self.nodes[node_index].parent(); match self.nodes[node_parent] { - BVHNode::Leaf {parent_index, shape_index} => { - println!("truncating early node_parent={} parent_index={} shape_index={}", node_parent, parent_index, shape_index); + BVHNode::Leaf { + parent_index, + shape_index, + } => { + println!( + "truncating early node_parent={} parent_index={} shape_index={}", + node_parent, parent_index, shape_index + ); self.nodes.truncate(end); return; } - _ => { } + _ => {} } let parent = self.nodes[node_parent]; let moved_left = parent.child_l() == end; - let ref_to_change = if moved_left { self.nodes[node_parent].child_l_mut() } else { self.nodes[node_parent].child_r_mut() }; + let ref_to_change = if moved_left { + self.nodes[node_parent].child_l_mut() + } else { + self.nodes[node_parent].child_r_mut() + }; //println!("on {} changing {}=>{}", node_parent, ref_to_change, node_index); *ref_to_change = node_index; match self.nodes[node_index] { - BVHNode::Leaf { shape_index, ..} => { + BVHNode::Leaf { shape_index, .. } => { shapes[shape_index].set_bh_node_index(node_index); } BVHNode::Node { @@ -942,9 +1014,9 @@ impl BVH { } => { *self.nodes[child_l_index].parent_mut() = node_index; *self.nodes[child_r_index].parent_mut() = node_index; - + //println!("{} {} {}", node_index, self.nodes[node_index].child_l_aabb(), self.nodes[node_index].child_r_aabb()); - //let correct_depth + //let correct_depth //self.fix_depth(child_l_index, ) } } @@ -952,32 +1024,32 @@ impl BVH { self.nodes.truncate(end); } -/* - pub fn fix_depth( - &mut self, - curr_node: usize, - correct_depth: u32 - ) { - match self.nodes[curr_node] { - BVHNode::Node { - ref mut depth, - child_l_index, - child_r_index, - .. - } => { - *depth = correct_depth; - self.fix_depth(child_l_index, correct_depth + 1); - self.fix_depth(child_r_index, correct_depth + 1); - } - BVHNode::Leaf { - ref mut depth, - .. - } => { - *depth = correct_depth; + /* + pub fn fix_depth( + &mut self, + curr_node: usize, + correct_depth: u32 + ) { + match self.nodes[curr_node] { + BVHNode::Node { + ref mut depth, + child_l_index, + child_r_index, + .. + } => { + *depth = correct_depth; + self.fix_depth(child_l_index, correct_depth + 1); + self.fix_depth(child_r_index, correct_depth + 1); + } + BVHNode::Leaf { + ref mut depth, + .. + } => { + *depth = correct_depth; + } } } - } -*/ + */ /// Prints the [`BVH`] in a tree-like visualization. /// @@ -987,7 +1059,6 @@ impl BVH { self.print_node(0); } - pub fn print_node(&self, node_index: usize) { let nodes = &self.nodes; match nodes[node_index] { @@ -1000,18 +1071,26 @@ impl BVH { } => { let depth = nodes[node_index].depth(nodes); let padding: String = repeat(" ").take(depth as usize).collect(); - println!("{} node={} parent={}", padding, node_index, nodes[node_index].parent()); + println!( + "{}node={} parent={}", + padding, + node_index, + nodes[node_index].parent() + ); println!("{}{} child_l {}", padding, child_l_index, child_l_aabb); self.print_node(child_l_index); println!("{}{} child_r {}", padding, child_r_index, child_r_aabb); self.print_node(child_r_index); } - BVHNode::Leaf { - shape_index, .. - } => { + BVHNode::Leaf { shape_index, .. } => { let depth = nodes[node_index].depth(nodes); let padding: String = repeat(" ").take(depth as usize).collect(); - println!("{} node={} parent={}", padding, node_index, nodes[node_index].parent()); + println!( + "{}node={} parent={}", + padding, + node_index, + nodes[node_index].parent() + ); println!("{}shape\t{:?}", padding, shape_index); } } @@ -1090,8 +1169,8 @@ impl BVH { pub fn is_consistent(&self, shapes: &[Shape]) -> bool { // The root node of the bvh is not bounded by anything. let space = AABB { - min: Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), - max: Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), + min: Point3::new(Real::NEG_INFINITY, Real::NEG_INFINITY, Real::NEG_INFINITY), + max: Point3::new(Real::INFINITY, Real::INFINITY, Real::INFINITY), }; // The counter for all nodes. @@ -1124,7 +1203,7 @@ impl BVH { "Wrong parent index. Expected: {}; Actual: {}", expected_parent_index, parent ); - let depth = node.depth(self.nodes.as_slice()); + let depth = node.depth(&self.nodes); assert_eq!( expected_depth, depth, "Wrong depth. Expected: {}; Actual: {}", @@ -1142,16 +1221,20 @@ impl BVH { assert!( expected_outer_aabb.approx_contains_aabb_eps(&child_l_aabb, EPSILON), "Left child lies outside the expected bounds. + \tDepth: {} \tBounds: {} \tLeft child: {}", + depth, 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. + \tDepth: {} \tBounds: {} \tRight child: {}", + depth, expected_outer_aabb, child_r_aabb ); @@ -1172,13 +1255,18 @@ impl BVH { shapes, ); } - BVHNode::Leaf { shape_index, .. } => { + BVHNode::Leaf { shape_index, parent_index, ..} => { let shape_aabb = shapes[shape_index].aabb(); assert!( - if parent != 0 { expected_outer_aabb.relative_eq(&shape_aabb, EPSILON) } else { true }, - "Shape's AABB lies outside the expected bounds.\n\tBounds: {}\n\tShape: {}", + if parent != 0 { + expected_outer_aabb.relative_eq(&shape_aabb, EPSILON) + } else { + true + }, + "Shape's AABB lies outside the expected bounds.\n\tBounds: {}\n\tShape: {}\n\tParent: {}", expected_outer_aabb, - shape_aabb + shape_aabb, + parent_index ); } } @@ -1188,8 +1276,8 @@ impl BVH { pub fn assert_consistent(&self, shapes: &[Shape]) { // The root node of the bvh is not bounded by anything. let space = AABB { - min: Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), - max: Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), + min: Point3::new(Real::NEG_INFINITY, Real::NEG_INFINITY, Real::NEG_INFINITY), + max: Point3::new(Real::INFINITY, Real::INFINITY, Real::INFINITY), }; // The counter for all nodes. @@ -1209,7 +1297,15 @@ impl BVH { child_r_index, child_r_aabb, } => { - println!("{}: parent_index={} child_l {} {} child_r {} {}", x, parent_index, child_l_index, child_l_aabb, child_r_index, child_r_aabb); + println!( + "{}: parent_index={} child_l {} {} child_r {} {}", + x, + parent_index, + child_l_index, + child_l_aabb, + child_r_index, + child_r_aabb + ); } BVHNode::Leaf { parent_index, @@ -1242,12 +1338,14 @@ impl BVH { { let joint_aabb = child_l_aabb.join(&child_r_aabb); if !joint_aabb.relative_eq(outer_aabb, EPSILON) { - for _i in 0..shapes.len() - { + for _i in 0..shapes.len() { //println!("s#{} {}", i, shapes[i].aabb()) } //self.pretty_print(); - println!("{} real_aabb={} stored_aabb={}", node_index, joint_aabb, outer_aabb); + println!( + "{} real_aabb={} stored_aabb={}", + node_index, joint_aabb, outer_aabb + ); } assert!(joint_aabb.relative_eq(outer_aabb, EPSILON)); self.assert_tight_subtree(child_l_index, &child_l_aabb, shapes); @@ -1277,7 +1375,11 @@ impl BoundingHierarchy for BVH { BVH::build(shapes) } - fn traverse<'a, Shape: Bounded>(&'a self, ray: &impl IntersectionTest, shapes: &'a [Shape]) -> Vec<&Shape> { + fn traverse<'a, Shape: Bounded>( + &'a self, + ray: &impl IntersectionTest, + shapes: &'a [Shape], + ) -> Vec<&Shape> { self.traverse(ray, shapes) } @@ -1288,12 +1390,12 @@ impl BoundingHierarchy for BVH { #[cfg(test)] mod tests { - use crate::bvh::{BVHNode, BVH}; use crate::aabb::AABB; - use crate::{Point3, Vector3}; - use crate::testbase::{build_some_bh, traverse_some_bh, UnitBox}; - use crate::Ray; use crate::bounding_hierarchy::BHShape; + use crate::bvh::{BVHNode, BVH}; + use crate::ray::Ray; + use crate::testbase::{build_some_bh, traverse_some_bh, UnitBox}; + use crate::{Point3, Real, Vector3}; use itertools::Itertools; #[test] @@ -1313,53 +1415,53 @@ mod tests { #[test] fn test_add_bvh() { let mut shapes = Vec::new(); - for x in -1..2 { - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); } - let mut bvh = BVH::build(&mut shapes); bvh.pretty_print(); - let test = AABB::empty().grow(&Point3::new(1.6, 0.0, 0.0)).grow(&Point3::new(2.4, 1.0, 1.0)); + let test = AABB::empty() + .grow(&Point3::new(1.6, 0.0, 0.0)) + .grow(&Point3::new(2.4, 1.0, 1.0)); let res = bvh.traverse(&test, &shapes); assert_eq!(res.len(), 0); let x = 2; - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); let len = shapes.len() - 1; bvh.add_node(&mut shapes, len); - + bvh.pretty_print(); bvh.rebuild(&mut shapes); let res = bvh.traverse(&test, &shapes); assert_eq!(res.len(), 1); - let x = 50; - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); let len = shapes.len() - 1; bvh.add_node(&mut shapes, len); bvh.pretty_print(); let res = bvh.traverse(&test, &shapes); assert_eq!(res.len(), 1); - - let test = AABB::empty().grow(&Point3::new(49.6, 0.0, 0.0)).grow(&Point3::new(52.4, 1.0, 1.0)); + + let test = AABB::empty() + .grow(&Point3::new(49.6, 0.0, 0.0)) + .grow(&Point3::new(52.4, 1.0, 1.0)); let res = bvh.traverse(&test, &shapes); assert_eq!(res.len(), 1); } - #[test] fn test_remove_bvh() { let mut shapes = Vec::new(); for x in -1..1 { - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); } - + let mut bvh = BVH::build(&mut shapes); bvh.pretty_print(); @@ -1376,22 +1478,19 @@ mod tests { fn test_accuracy_after_bvh_remove() { let mut shapes = Vec::new(); for x in -25..25 { - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); } - - let mut bvh = BVH::build(&mut shapes); bvh.pretty_print(); bvh.assert_consistent(shapes.as_slice()); - fn test_x(bvh: &BVH, x: f64, count: usize, shapes: &[UnitBox]) { + fn test_x(bvh: &BVH, x: Real, count: usize, shapes: &[UnitBox]) { let dir = Vector3::new(0.0, -1.0, 0.0); let ray = Ray::new(Vector3::new(x, 2.0, 0.0), dir); let res = bvh.traverse(&ray, shapes); - if count == 0 && res.len() > 0 - { + if count == 0 && res.len() > 0 { println!("hit={} node={}", res[0].pos, res[0].bh_node_index()); } assert_eq!(res.len(), count); @@ -1399,8 +1498,8 @@ mod tests { test_x(&bvh, 2.0, 1, &shapes); - for x in -23 .. 23 { - let point = Point3::new(x as f64, 0.0, 0.0); + for x in -23..23 { + let point = Point3::new(x as Real, 0.0, 0.0); let mut delete_i = 0; for i in 0..shapes.len() { if shapes[i].pos.distance_squared(point) < 0.01 { @@ -1411,14 +1510,13 @@ mod tests { println!("Testing {}", x); bvh.pretty_print(); println!("Ensuring x={} shape[{}] is present", x, delete_i); - test_x(&bvh, x as f64, 1, &shapes); + test_x(&bvh, x as Real, 1, &shapes); println!("Deleting x={} shape[{}]", x, delete_i); bvh.remove_node(&mut shapes, delete_i, true); shapes.truncate(shapes.len() - 1); bvh.pretty_print(); println!("Ensuring {} [{}] is gone", x, delete_i); - test_x(&bvh, x as f64, 0, &shapes); - + test_x(&bvh, x as Real, 0, &shapes); } } @@ -1426,19 +1524,18 @@ mod tests { fn test_random_deletions() { let xs = -3..3; let x_values = xs.clone().collect::>(); - for x_values in xs.clone().permutations(x_values.len() - 1) - { + for x_values in xs.clone().permutations(x_values.len() - 1) { let mut shapes = Vec::new(); for x in xs.clone() { - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); } let mut bvh = BVH::build(&mut shapes); - + //bvh.pretty_print(); for x_i in 0..x_values.len() { let x = x_values[x_i]; - - let point = Point3::new(x as f64, 0.0, 0.0); + + let point = Point3::new(x as Real, 0.0, 0.0); let mut delete_i = 0; for i in 0..shapes.len() { if shapes[i].pos.distance_squared(point) < 0.01 { @@ -1453,20 +1550,18 @@ mod tests { //bvh.pretty_print(); bvh.assert_consistent(shapes.as_slice()); bvh.assert_tight(shapes.as_slice()); - } } } - #[test] fn test_add_consistency() { let mut shapes = Vec::new(); for x in -25..25 { - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); } - let (left, right) = shapes.split_at_mut(10); + let (left, right) = shapes.split_at_mut(10); let mut bvh = BVH::build(left); bvh.pretty_print(); @@ -1480,10 +1575,6 @@ mod tests { } } - - - - #[test] /// Verify contents of the bounding hierarchy for a fixed scene structure fn test_bvh_shape_indices() { @@ -1515,13 +1606,13 @@ mod tests { #[cfg(all(feature = "bench", test))] mod bench { + use crate::bounding_hierarchy::BHShape; 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,create_n_cubes, default_bounds + build_1200_triangles_bh, build_120k_triangles_bh, build_12k_triangles_bh, create_n_cubes, + default_bounds, intersect_1200_triangles_bh, intersect_120k_triangles_bh, + intersect_12k_triangles_bh, intersect_bh, load_sponza_scene, }; - use crate::bounding_hierarchy::BHShape; #[bench] /// Benchmark the construction of a `BVH` with 1,200 triangles. @@ -1549,7 +1640,7 @@ mod bench { BVH::build(&mut triangles); }); } - + #[cfg(feature = "bench")] fn add_triangles_bvh(n: usize, b: &mut ::test::Bencher) { let bounds = default_bounds(); @@ -1560,13 +1651,12 @@ mod bench { } #[cfg(feature = "bench")] - fn build_by_add(shapes: &mut [T] ) -> BVH - { + fn build_by_add(shapes: &mut [T]) -> BVH { let (first, rest) = shapes.split_at_mut(1); let mut bvh = BVH::build(first); for i in 1..shapes.len() { bvh.add_node(shapes, i) - }; + } bvh } @@ -1578,21 +1668,18 @@ mod bench { intersect_bh(&bh, &triangles, &bounds, b) } - #[bench] /// Benchmark the construction of a `BVH` for the Sponza scene. fn build_1200_triangles_add(b: &mut ::test::Bencher) { add_triangles_bvh(1200, b) } - #[bench] /// Benchmark the construction of a `BVH` for the Sponza scene. fn build_12k_triangles_add(b: &mut ::test::Bencher) { add_triangles_bvh(12000, b) } - /* #[bench] /// Benchmark the construction of a `BVH` for the Sponza scene. diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index 19788e6..1536167 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -1,8 +1,8 @@ use smallvec::SmallVec; use crate::aabb::Bounded; +use crate::bounding_hierarchy::IntersectionTest; use crate::bvh::{BVHNode, BVH}; -use crate::bounding_hierarchy::{IntersectionTest}; /// Iterator to traverse a [`BVH`] without memory allocations #[allow(clippy::upper_case_acronyms)] diff --git a/src/bvh/mod.rs b/src/bvh/mod.rs index d0e28d8..c3e56fa 100644 --- a/src/bvh/mod.rs +++ b/src/bvh/mod.rs @@ -6,9 +6,7 @@ mod bvh_impl; mod iter; mod optimization; -pub mod qbvh; pub use self::bvh_impl::*; pub use self::iter::*; pub use self::optimization::*; - diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index ec89d1f..3040c4b 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -6,14 +6,14 @@ //! [`BVH`]: struct.BVH.html //! -use crate::aabb::AABB; use crate::bounding_hierarchy::BHShape; use crate::bvh::*; +use crate::{aabb::AABB, Real}; +use std::fmt::Debug; use log::info; use rand::{thread_rng, Rng}; - // TODO Consider: Instead of getting the scene's shapes passed, let leaf nodes store an AABB // that is updated from the outside, perhaps by passing not only the indices of the changed // shapes, but also their new AABBs into optimize(). @@ -71,18 +71,17 @@ impl BVH { /// /// Needs all the scene's shapes, plus the indices of the shapes that were updated. /// - pub fn optimize ( + pub fn optimize<'a, Shape: BHShape>( &mut self, - refit_shape_indices: &[usize], - shapes: &mut[Shape], + refit_shape_indices: impl IntoIterator + Copy, + shapes: &mut [Shape], ) { - - for i in refit_shape_indices { self.remove_node(shapes, *i, false); - //self.assert_tight(shapes); } + println!("removed"); + self.assert_consistent(shapes); //println!("--------"); //self.pretty_print(); //println!("--------"); @@ -93,6 +92,12 @@ impl BVH { //println!("--------"); //self.assert_consistent(shapes); self.add_node(shapes, *i); + if !self.is_consistent(shapes) { + dbg!(i); + dbg!(&shapes[*i].aabb()); + dbg!(&shapes[*i].bh_node_index()); + self.assert_consistent(shapes); + } } return; @@ -101,7 +106,8 @@ impl BVH { // that reference the given shapes, sorted by their depth // in increasing order. let mut refit_node_indices: Vec<_> = { - let mut raw_indices = refit_shape_indices.into_iter() + let mut raw_indices = refit_shape_indices + .into_iter() .map(|x| shapes[*x].bh_node_index()) .collect::>(); @@ -183,9 +189,7 @@ impl BVH { let (i, depth) = stack.pop().unwrap(); depths[i] = depth; match self.nodes[i] { - BVHNode::Leaf { .. } => { - - } + BVHNode::Leaf { .. } => {} BVHNode::Node { child_l_index, child_r_index, @@ -284,7 +288,7 @@ impl BVH { // thus being the favored rotation that will be executed after considering all rotations. let mut best_rotation: Option<(usize, usize)> = None; { - let mut consider_rotation = |new_rotation: (usize, usize), surface_area: f64| { + let mut consider_rotation = |new_rotation: (usize, usize), surface_area: Real| { if surface_area < best_surface_area { best_surface_area = surface_area; best_rotation = Some(new_rotation); @@ -483,33 +487,33 @@ impl BVH { shapes, ); } -/* - /// Updates the depth of a node, and sets the depth of its descendants accordingly. - fn update_depth_recursively(&mut self, node_index: usize, new_depth: u32) { - let children = { - let node = &mut self.nodes[node_index]; - match *node { - BVHNode::Node { - ref mut depth, - child_l_index, - child_r_index, - .. - } => { - *depth = new_depth; - Some((child_l_index, child_r_index)) - } - BVHNode::Leaf { ref mut depth, .. } => { - *depth = new_depth; - None + /* + /// Updates the depth of a node, and sets the depth of its descendants accordingly. + fn update_depth_recursively(&mut self, node_index: usize, new_depth: u32) { + let children = { + let node = &mut self.nodes[node_index]; + match *node { + BVHNode::Node { + ref mut depth, + child_l_index, + child_r_index, + .. + } => { + *depth = new_depth; + Some((child_l_index, child_r_index)) + } + BVHNode::Leaf { ref mut depth, .. } => { + *depth = new_depth; + None + } } + }; + if let Some((child_l_index, child_r_index)) = children { + self.update_depth_recursively(child_l_index, new_depth + 1); + self.update_depth_recursively(child_r_index, new_depth + 1); } - }; - if let Some((child_l_index, child_r_index)) = children { - self.update_depth_recursively(child_l_index, new_depth + 1); - self.update_depth_recursively(child_r_index, new_depth + 1); } - } -*/ + */ fn node_is_left_child(&self, node_index: usize) -> bool { // Get the index of the parent. @@ -567,23 +571,6 @@ mod tests { }; use crate::Point3; use crate::EPSILON; - - - #[test] - /// Tests if `optimize` does not modify a fresh `BVH`. - fn test_optimizing_new_bvh() { - let (mut shapes, mut bvh) = build_some_bh::(); - let original_nodes = bvh.nodes.clone(); - - // Query an update for all nodes. - let refit_shape_indices: Vec = (0..shapes.len()).collect(); - bvh.optimize(&refit_shape_indices, &mut shapes); - - // Assert that all nodes are the same as before the update. - for (optimized, original) in bvh.nodes.iter().zip(original_nodes.iter()) { - assert_eq!(optimized, original); - } - } #[test] /// Tests whether a BVH is still consistent after a few optimization calls. @@ -968,15 +955,16 @@ mod tests { assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); // After fixing the `AABB` consistency should be restored. + println!("optimize"); bvh.optimize(&updated, &mut triangles); bvh.assert_consistent(&triangles); bvh.assert_tight(&triangles); } #[test] - fn test_optimize_bvh_10_75p() { + fn test_optimize_bvh_12_75p() { let bounds = default_bounds(); - let mut triangles = create_n_cubes(50, &bounds); + let mut triangles = create_n_cubes(1, &bounds); println!("triangles={}", triangles.len()); let mut bvh = BVH::build(&mut triangles); @@ -989,8 +977,7 @@ mod tests { // match the tree entries. let mut seed = 0; - let updated = randomly_transform_scene(&mut triangles, 599, &bounds, None, &mut seed); - let updated: Vec = updated.into_iter().collect(); + let updated = randomly_transform_scene(&mut triangles, 9, &bounds, None, &mut seed); assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); println!("triangles={}", triangles.len()); //bvh.pretty_print(); @@ -1001,6 +988,37 @@ mod tests { bvh.assert_consistent(&triangles); bvh.assert_tight(&triangles); } + + + #[test] + fn test_optimizing_nodes() { + let bounds = default_bounds(); + let mut triangles = create_n_cubes(1, &bounds); + println!("triangles={}", triangles.len()); + + let mut bvh = BVH::build(&mut triangles); + + // The initial BVH should be consistent. + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + + // After moving triangles, the BVH should be inconsistent, because the shape `AABB`s do not + // match the tree entries. + let mut seed = 0; + + + for _ in 0..1000 { + let updated = randomly_transform_scene(&mut triangles, 1, &bounds, None, &mut seed); + assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); + //bvh.pretty_print(); + + // After fixing the `AABB` consistency should be restored. + bvh.optimize(&updated, &mut triangles); + //bvh.pretty_print(); + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + } + } } #[cfg(all(feature = "bench", test))] @@ -1011,6 +1029,7 @@ mod bench { create_n_cubes, default_bounds, intersect_bh, load_sponza_scene, randomly_transform_scene, Triangle, }; + use crate::Real; #[bench] /// Benchmark randomizing 50% of the shapes in a `BVH`. @@ -1026,11 +1045,11 @@ mod bench { /// Benchmark optimizing a `BVH` with 120,000 `Triangle`s, where `percent` /// `Triangles` have been randomly moved. - fn optimize_bvh_120k(percent: f64, b: &mut ::test::Bencher) { + fn optimize_bvh_120k(percent: Real, b: &mut ::test::Bencher) { let bounds = default_bounds(); let mut triangles = create_n_cubes(10_000, &bounds); let mut bvh = BVH::build(&mut triangles); - let num_move = (triangles.len() as f64 * percent) as usize; + let num_move = (triangles.len() as Real * percent) as usize; let mut seed = 0; b.iter(|| { @@ -1072,13 +1091,13 @@ mod bench { fn intersect_scene_after_optimize( mut triangles: &mut Vec, bounds: &AABB, - percent: f64, - max_offset: Option, + percent: Real, + max_offset: Option, iterations: usize, b: &mut ::test::Bencher, ) { let mut bvh = BVH::build(&mut triangles); - let num_move = (triangles.len() as f64 * percent) as usize; + let num_move = (triangles.len() as Real * percent) as usize; let mut seed = 0; for _ in 0..iterations { @@ -1133,12 +1152,12 @@ mod bench { fn intersect_scene_with_rebuild( mut triangles: &mut Vec, bounds: &AABB, - percent: f64, - max_offset: Option, + percent: Real, + max_offset: Option, iterations: usize, b: &mut ::test::Bencher, ) { - let num_move = (triangles.len() as f64 * percent) as usize; + let num_move = (triangles.len() as Real * percent) as usize; let mut seed = 0; for _ in 0..iterations { randomly_transform_scene(&mut triangles, num_move, &bounds, max_offset, &mut seed); @@ -1185,7 +1204,7 @@ mod bench { /// Benchmark intersecting a `BVH` for Sponza after randomly moving one `Triangle` and /// optimizing. - fn intersect_sponza_after_optimize(percent: f64, b: &mut ::test::Bencher) { + fn intersect_sponza_after_optimize(percent: Real, b: &mut ::test::Bencher) { let (mut triangles, bounds) = load_sponza_scene(); intersect_scene_after_optimize(&mut triangles, &bounds, percent, Some(0.1), 10, b); } @@ -1217,7 +1236,7 @@ mod bench { /// Benchmark intersecting a `BVH` for Sponza after rebuilding. Used to compare optimizing /// with rebuilding. For reference see `intersect_sponza_after_optimize`. - fn intersect_sponza_with_rebuild(percent: f64, b: &mut ::test::Bencher) { + fn intersect_sponza_with_rebuild(percent: Real, b: &mut ::test::Bencher) { let (mut triangles, bounds) = load_sponza_scene(); intersect_scene_with_rebuild(&mut triangles, &bounds, percent, Some(0.1), 10, b); } diff --git a/src/bvh/qbvh.rs b/src/bvh/qbvh.rs deleted file mode 100644 index 816c821..0000000 --- a/src/bvh/qbvh.rs +++ /dev/null @@ -1,399 +0,0 @@ -use parry3d_f64::partitioning::{IndexedData}; -use parry3d_f64::bounding_volume::aabb::{AABB}; -use parry3d_f64::math::{Real, SimdBool, SimdReal, SIMD_WIDTH}; -use parry3d_f64::query::{Ray, RayCast, SimdRay}; -use parry3d_f64::partitioning::{SimdBestFirstVisitStatus, SimdBestFirstVisitor}; -use parry3d_f64::bounding_volume::SimdAABB; -use parry3d_f64::simba::simd::{SimdBool as _, SimdPartialOrd, SimdValue}; - -pub trait BoundableQ { - fn aabb(&self) -> AABB; -} - -/// A visitor for casting a ray on a composite shape. -pub struct RayBoundableToiBestFirstVisitor<'a> { - ray: &'a Ray, - simd_ray: SimdRay, - max_toi: Real, - solid: bool, -} - -impl<'a> RayBoundableToiBestFirstVisitor<'a> { - /// Initialize a visitor for casting a ray on a composite shape. - pub fn new(ray: &'a Ray, max_toi: Real, solid: bool) -> Self { - Self { - ray, - simd_ray: SimdRay::splat(*ray), - max_toi, - solid, - } - } -} - -impl<'a, T: BoundableQ + IndexedData> SimdBestFirstVisitor - for RayBoundableToiBestFirstVisitor<'a> -{ - type Result = (usize, Real); - - #[inline] - fn visit( - &mut self, - best: Real, - aabb: &SimdAABB, - data: Option<[Option<&T>; SIMD_WIDTH]>, - ) -> SimdBestFirstVisitStatus { - let (hit, toi) = aabb.cast_local_ray(&self.simd_ray, SimdReal::splat(self.max_toi)); - - if let Some(data) = data { - let mut weights = [0.0; SIMD_WIDTH]; - let mut mask = [false; SIMD_WIDTH]; - let mut results = [None; SIMD_WIDTH]; - - let better_toi = toi.simd_lt(SimdReal::splat(best)); - let bitmask = (hit & better_toi).bitmask(); - - for ii in 0..SIMD_WIDTH { - if (bitmask & (1 << ii)) != 0 && data[ii].is_some() { - let shape = data[ii].unwrap(); - let toi = shape.aabb().cast_local_ray(&self.ray, self.max_toi, self.solid); - if let Some(toi) = toi { - results[ii] = Some((shape.index(), toi)); - mask[ii] = true; - weights[ii] = toi; - } - } - } - - SimdBestFirstVisitStatus::MaybeContinue { - weights: SimdReal::from(weights), - mask: SimdBool::from(mask), - results, - } - } else { - SimdBestFirstVisitStatus::MaybeContinue { - weights: toi, - mask: hit, - results: [None; SIMD_WIDTH], - } - } - } -} - - - - - - - - - -//#![cfg(test)] -#[cfg(all(feature = "bench", test))] -mod bench { - use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; - use parry3d_f64::bounding_volume::aabb::AABB; - use parry3d_f64::math::{Point, Vector}; - use std::rc::{Rc}; - use crate::testbase::{ - create_n_cubes, default_bounds, - Triangle, create_ray - }; - use crate::bvh::qbvh::{RayBoundableToiBestFirstVisitor, BoundableQ}; - use parry3d_f64::query::{Ray, RayCast, RayIntersection, SimdRay}; - use parry3d_f64::query::visitors::RayIntersectionsVisitor; - - #[derive(Clone, Debug)] - pub struct Triangles { - Tris: Rc> - } - - #[derive(Copy, Clone, Debug)] - pub struct IndexedTri { - tri: Triangle, - i: usize - } - - impl IndexedData for IndexedTri { - fn default() -> Self { - IndexedTri { - tri: Triangle::new(Default::default(), Default::default(), Default::default()), - i: 0 - } - } - - fn index(&self) -> usize { - self.i - } - } - - impl BoundableQ for IndexedTri { - fn aabb(&self) -> AABB { - let tri = self.tri; - let mut min = unsafe { Point::new_uninitialized() }; - let mut max = unsafe { Point::new_uninitialized() }; - let a = tri.a; - let b = tri.b; - let c = tri.c; - for d in 0..3 { - min.coords[d] = a[d].min(b[d]).min(c[d]); - max.coords[d] = a[d].max(b[d]).max(c[d]); - } - AABB::new(min, max) - } - } - - impl QBVHDataGenerator for Triangles { - fn size_hint(&self) -> usize { - return self.Tris.len(); - } - - fn for_each(&mut self, mut f: impl FnMut(IndexedTri, AABB)) { - for i in 0..self.Tris.len() - { - let tri = self.Tris[i]; - let mut new_tri = IndexedTri { - tri: tri, - i: i - }; - f(new_tri, new_tri.aabb()); - } - - } - } - - #[bench] - /// Benchmark building a qbvh - fn bench_build_1200_qbvh(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(100, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - b.iter(|| { - bvh.clear_and_rebuild(generator.clone(), 0.0); - }); - } - - #[bench] - /// Benchmark building a qbvh - fn bench_build_12000_qbvh(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(1000, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - b.iter(|| { - bvh.clear_and_rebuild(generator.clone(), 0.0); - }); - } - - #[bench] - /// Benchmark building a qbvh - fn bench_build_120k_qbvh(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(10000, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - b.iter(|| { - bvh.clear_and_rebuild(generator.clone(), 0.0); - }); - } - - #[bench] - /// Benchmark building a qbvh - fn bench_1200_qbvh_ray_intersection(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(100, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - let r = Ray::new(Point::new(0.0, 0.0, 0.0), Vector::new(1.0, 0.0, 0.0)); - let mut visitor = RayBoundableToiBestFirstVisitor::new(&r, 1000000000000.0, true); - b.iter(|| { - bvh.traverse_best_first(&mut visitor); - }); - } - - #[bench] - /// Benchmark building a qbvh - fn bench_12000_qbvh_ray_intersection(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(1000, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - let r = Ray::new(Point::new(0.0, 0.0, 0.0), Vector::new(1.0, 0.0, 0.0)); - let mut visitor = RayBoundableToiBestFirstVisitor::new(&r, 1000000000000.0, true); - b.iter(|| { - bvh.traverse_best_first(&mut visitor); - }); - } - - #[bench] - /// Benchmark building a qbvh - fn bench_120k_qbvh_ray_intersection(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(10000, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - let r = Ray::new(Point::new(0.0, 0.0, 0.0), Vector::new(1.0, 0.0, 0.0)); - let mut visitor = RayBoundableToiBestFirstVisitor::new(&r, 1000000000000.0, true); - b.iter(|| { - bvh.traverse_best_first(&mut visitor); - }); - } - -/* - fn visit_triangle(tri: &IndexedTri) -> bool { - true - } -*/ - #[bench] - /// Benchmark building a qbvh - fn bench_1200_qbvh_ray_intersection_stack(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(100, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - let mut visit_triangle = |tri: &IndexedTri| { - true - }; - let mut stack = Vec::new(); - let mut seed = 0; - b.iter(|| { - stack.clear(); - let ray = create_ray(&mut seed, &bounds); - let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); - let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); - let r = Ray::new(o, d); - let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); - bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); - }); - } - - - #[bench] - /// Benchmark building a qbvh - fn bench_12000_qbvh_ray_intersection_stack(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(1000, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - let mut visit_triangle = |tri: &IndexedTri| { - true - }; - let mut stack = Vec::new(); - let mut seed = 0; - b.iter(|| { - stack.clear(); - let ray = create_ray(&mut seed, &bounds); - let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); - let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); - let r = Ray::new(o, d); - let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); - bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); - }); - } - - #[bench] - /// Benchmark building a qbvh - fn bench_120k_qbvh_ray_intersection_stack(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(10000, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - let mut visit_triangle = |tri: &IndexedTri| { - true - }; - let mut stack = Vec::new(); - let mut seed = 0; - b.iter(|| { - stack.clear(); - let ray = create_ray(&mut seed, &bounds); - let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); - let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); - let r = Ray::new(o, d); - let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); - bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); - }); - } - - - #[bench] - /// Benchmark building a qbvh - fn bench_120k_qbvh_ray_intersection_stackalloc(b: &mut ::test::Bencher) { - let bounds = default_bounds(); - let mut triangles = create_n_cubes(10000, &bounds); - let rc = Rc::new(triangles); - let mut generator = Triangles { - Tris: rc - }; - - - let mut bvh = QBVH::new(); - bvh.clear_and_rebuild(generator.clone(), 0.0); - let mut visit_triangle = |tri: &IndexedTri| { - true - }; - let mut seed = 0; - b.iter(|| { - let ray = create_ray(&mut seed, &bounds); - let o = Point::new(ray.origin.x, ray.origin.y, ray.origin.z); - let d = Vector::new(ray.direction.x, ray.direction.y, ray.direction.z); - let r = Ray::new(o, d); - let mut visitor = RayIntersectionsVisitor::new(&r, 1000000000000.0, &mut visit_triangle); - bvh.traverse_depth_first(&mut visitor); - }); - } - -} \ No newline at end of file diff --git a/src/flat_bvh.rs b/src/flat_bvh.rs index 29d3978..dfe7cfe 100644 --- a/src/flat_bvh.rs +++ b/src/flat_bvh.rs @@ -44,7 +44,7 @@ impl BVHNode { /// Creates a flat node from a `BVH` inner node and its `AABB`. Returns the next free index. /// TODO: change the algorithm which pushes `FlatNode`s to a vector to not use indices this /// much. Implement an algorithm which writes directly to a writable slice. - fn create_flat_branch( + fn create_flat_branch( &self, nodes: &[BVHNode], this_aabb: &AABB, @@ -62,7 +62,8 @@ impl BVHNode { assert_eq!(vec.len() - 1, next_free); // Create subtree. - let index_after_subtree = self.flatten_custom(nodes, vec, shapes, next_free + 1, constructor); + let index_after_subtree = + self.flatten_custom(nodes, vec, shapes, next_free + 1, constructor); // Replace dummy node by actual node with the entry index pointing to the subtree // and the exit index pointing to the next node after the subtree. @@ -229,7 +230,11 @@ impl BVH { /// let bvh = BVH::build(&mut shapes); /// let custom_flat_bvh = bvh.flatten_custom(&custom_constructor); /// ``` - pub fn flatten_custom(&self, shapes: &[T], constructor: &F) -> Vec + pub fn flatten_custom( + &self, + shapes: &[T], + constructor: &F, + ) -> Vec where F: Fn(&AABB, u32, u32, u32) -> FNodeType, { diff --git a/src/lib.rs b/src/lib.rs index be38511..5407a67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(test)] //! A crate which exports rays, axis-aligned bounding boxes, and binary bounding //! volume hierarchies. //! @@ -65,40 +66,46 @@ //! ``` //! -//#![deny(missing_docs)] -#![cfg_attr(feature = "bench", feature(test))] - #[cfg(all(feature = "bench", test))] extern crate test; - /// Point math type used by this crate. Type alias for [`glam::DVec3`]. -#[cfg(not(feature = "f32"))] +#[cfg(feature = "f64")] pub type Point3 = glam::DVec3; /// Vector math type used by this crate. Type alias for [`glam::DVec3`]. -#[cfg(not(feature = "f32"))] +#[cfg(feature = "f64")] pub type Vector3 = glam::DVec3; -#[cfg(not(feature = "f32"))] +/// Matrix math type used by this crate. Type alias for [`glam::DMat4`]. +#[cfg(feature = "f64")] +pub type Mat4 = glam::DMat4; + +/// Matrix math type used by this crate. Type alias for [`glam::DQuat`]. +#[cfg(feature = "f64")] +pub type Quat = glam::DQuat; + +#[cfg(feature = "f64")] /// Float type used by this crate pub type Real = f64; -/// A minimal floating value used as a lower bound. -/// TODO: replace by/add ULPS/relative float comparison methods. -#[cfg(feature = "f32")] -pub const EPSILON: f32 = 0.00001; - /// Point math type used by this crate. Type alias for [`glam::Vec3`]. -#[cfg(feature = "f32")] +#[cfg(not(feature = "f64"))] pub type Point3 = glam::Vec3; /// Vector math type used by this crate. Type alias for [`glam::Vec3`]. -#[cfg(feature = "f32")] +#[cfg(not(feature = "f64"))] pub type Vector3 = glam::Vec3; +/// Matrix math type used by this crate. Type alias for [`glam::Mat4`]. +#[cfg(not(feature = "f64"))] +pub type Mat4 = glam::Mat4; -#[cfg(feature = "f32")] +/// Quat math type used by this crate. Type alias for [`glam::Quat`]. +#[cfg(not(feature = "f64"))] +pub type Quat = glam::Quat; + +#[cfg(not(feature = "f64"))] /// Float type used by this crate pub type Real = f32; @@ -115,631 +122,146 @@ pub mod ray; pub mod shapes; mod utils; -use aabb::{Bounded, AABB}; -use bounding_hierarchy::BHShape; - - -use interoptopus::util::NamespaceMappings; -use ray::Ray; -use shapes::{Capsule, Sphere, OBB}; -use glam::DQuat; -use bvh::BUILD_THREAD_COUNT; -use std::sync::atomic::{AtomicUsize, Ordering}; -use parry3d_f64::partitioning::{QBVH, QBVHDataGenerator, IndexedData}; -use parry3d_f64::bounding_volume::aabb::AABB as QAABB; -use parry3d_f64::math::{Point, Vector}; -use parry3d_f64::query::Ray as RayQ; -use parry3d_f64::query::visitors::RayIntersectionsVisitor; -use interoptopus::{ffi_function, ffi_type, Interop, Error}; -use interoptopus::patterns::slice::{FFISliceMut}; -use interoptopus::patterns::string::AsciiPointer; -use interoptopus::lang::rust::CTypeInfo; -use interoptopus::lang::c::{CType, CompositeType, Documentation, Field, OpaqueType, Visibility, Meta}; -use flexi_logger::{FileSpec, Logger, detailed_format}; -use log::{info}; - -#[macro_use] -extern crate lazy_static; - - #[cfg(test)] mod testbase; +use aabb::{Bounded, AABB}; +use bounding_hierarchy::BHShape; +use bvh::BVH; +use num::{FromPrimitive, Integer}; +use obj::raw::object::Polygon; +use obj::*; -#[no_mangle] -pub extern "C" fn add_numbers(number1: i32, number2: i32) -> i32 { - println!("Hello from rust!"); - number1 + number2 -} - -#[repr(C)] -#[ffi_type] -#[derive(Copy, Clone, Debug)] -pub struct Double3 { - pub x: f64, - pub y: f64, - pub z: f64 -} - -#[repr(C)] -#[ffi_type] -#[derive(Copy, Clone, Debug)] -pub struct Float3 { - pub x: f32, - pub y: f32, - pub z: f32 -} - -#[repr(C)] -#[ffi_type(name="BoundingBoxD")] -#[derive(Copy, Clone, Debug)] -pub struct BoundsD { - pub min: Double3, - pub max: Double3 -} - -#[repr(C)] -#[ffi_type(name="BvhNode")] -#[derive(Copy, Clone, Debug)] -pub struct BVHBounds { - pub bounds: BoundsD, - pub internal_bvh_index: i32, - pub index: i32 -} - -#[repr(C)] -pub struct BvhRef { - bvh: Box -} - -unsafe impl CTypeInfo for BvhRef { - fn type_info() -> CType { - let fields: Vec = vec![ - Field::with_documentation("bvh".to_string(), CType::ReadPointer(Box::new(CType::Opaque(OpaqueType::new("BvhPtr".to_string(), Meta::new())))), Visibility::Private, Documentation::new()), - ]; - let composite = CompositeType::new("BvhRef".to_string(), fields); - CType::Composite(composite) - } -} - -#[ffi_type(opaque)] -#[repr(C)] -pub struct QBVHRef { - bvh: Box> -} - -#[ffi_type] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct QuatD { - pub x: f64, - pub y: f64, - pub z: f64, - pub w: f64 -} - -#[ffi_type(name="Float3")] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct Point32 { - pub x: f32, - pub y: f32, - pub z: f32 -} - -#[ffi_type(name="BoundingBox")] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct AABB32 { - pub min: Point32, - pub max: Point32 -} - -#[ffi_type(name="FlatNode")] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct FlatNode32 { - pub aabb: AABB32, - pub entry_index: u32, - pub exit_index: u32, - pub shape_index: u32 +#[derive(Debug)] +struct Sphere { + position: Point3, + radius: Real, + node_index: usize, } -impl Bounded for BVHBounds { +impl Bounded for Sphere { fn aabb(&self) -> AABB { - let min = to_vec(&self.bounds.min); - let max = to_vec(&self.bounds.max); + let half_size = Vector3::new(self.radius, self.radius, self.radius); + let min = self.position - half_size; + let max = self.position + half_size; AABB::with_bounds(min, max) } } -#[ffi_type(opaque)] -#[derive(Copy, Clone, Debug)] -pub struct RefNode { - pub index: usize -} - -impl IndexedData for RefNode { - fn index(&self) -> usize { - self.index - } - fn default() -> Self { - RefNode { - index: usize::MAX - } - } -} - -impl BVHBounds { - fn qaabb(&self) -> QAABB { - let min = Point::new(self.bounds.min.x, self.bounds.min.y, self.bounds.min.z); - let max = Point::new(self.bounds.max.x, self.bounds.max.y, self.bounds.max.z); - QAABB::new(min, max) - } -} - -impl BHShape for BVHBounds { +impl BHShape for Sphere { fn set_bh_node_index(&mut self, index: usize) { - self.internal_bvh_index = index as i32; + self.node_index = index; } fn bh_node_index(&self) -> usize { - self.internal_bvh_index as usize - } -} - -pub fn to_vec(a: &Double3) -> Vector3 { - Vector3::new(a.x, a.y, a.z) -} - -pub fn to_vecd(a: &Vector3) -> Double3 { - Double3 { - x: a.x, - y: a.y, - z: a.z + self.node_index } } -pub fn to_quat(a: &QuatD) -> DQuat { - DQuat::from_xyzw(a.x, a.y, a.z, a.w) -} - -struct BoundsData<'a> { - data: &'a mut [BVHBounds] -} - -impl <'a> BoundsData<'a> { - fn new(data: &'a mut [BVHBounds]) -> BoundsData { - BoundsData { - data - } - } -} - -impl <'a> QBVHDataGenerator for BoundsData<'a> { - fn size_hint(&self) -> usize { - self.data.len() - } - - fn for_each(&mut self, mut f: impl FnMut(RefNode, QAABB)) { - for i in 0..self.data.len() - { - let bounds = self.data[i]; - f(RefNode { - index: i - }, bounds.qaabb()); +/// A triangle struct. Instance of a more complex `Bounded` primitive. +#[derive(Debug)] +pub struct Triangle { + pub a: Point3, + pub b: Point3, + pub c: Point3, + aabb: AABB, + node_index: usize, +} + +impl Triangle { + pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { + Triangle { + a, + b, + c, + aabb: AABB::empty().grow(&a).grow(&b).grow(&c), + node_index: 0, } - } } -#[no_mangle] -pub extern "C" fn set_build_thread_count(count: i32) -{ - BUILD_THREAD_COUNT.store(count as usize, Ordering::Relaxed); -} - -static LOGGER_INITIALIZED: AtomicUsize = AtomicUsize::new(0); - -#[ffi_function] -#[no_mangle] -pub extern "C" fn init_logger(log_path: AsciiPointer) -{ - let init_count = LOGGER_INITIALIZED.fetch_add(1, Ordering::SeqCst); - if init_count == 0 { - let path = log_path.as_str().unwrap(); - let file = FileSpec::default() - .directory(path) - .basename("bvh_f64") - .suffix("log"); - Logger::try_with_str("info").unwrap() - .log_to_file(file) - .format_for_files(detailed_format) - .start().unwrap(); - log_panics::init(); - - info!("Log initialized in folder {}", path); +impl Bounded for Triangle { + fn aabb(&self) -> AABB { + self.aabb } } - -#[no_mangle] -pub extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) -{ - let a = unsafe {*a_ptr}; - - let a = glam::Vec3::new(a.x, a.y, a.z); - let b = unsafe {*b_ptr}; - let b = glam::Vec3::new(b.x, b.y, b.z); - let mut c = glam::Vec3::new(0.0, 0.0, 0.0); - - for _i in 0 .. 100000 { - c = a + b + c; - } - - unsafe { - *out_ptr = Float3 { - x: c.x, - y: c.y, - z: c.z - }; +impl BHShape for Triangle { + fn set_bh_node_index(&mut self, index: usize) { + self.node_index = index; } -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn build_bvh(shapes: &mut FFISliceMut) -> BvhRef -{ - let bvh = Box::new(bvh::BVH::build(shapes.as_slice_mut())); - info!("Building bvh"); - - BvhRef { bvh } -} - - -#[ffi_function] -#[no_mangle] -pub extern "C" fn build_qbvh(shapes: &mut FFISliceMut) -> QBVHRef -{ - - let data = BoundsData::new(shapes.as_slice_mut()); - let mut bvh = Box::new(QBVH::new()); - - bvh.clear_and_rebuild(data, 0.0); - - QBVHRef { bvh } -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_ray_q(bvh_ref: &QBVHRef, o_vec: &Double3, d_vec: &Double3, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 -{ - let bvh = &bvh_ref.bvh; - let ray = RayQ::new(Point::new(o_vec.x, o_vec.y, o_vec.z), Vector::new(d_vec.x, d_vec.y, d_vec.z)); - let mut i = 0; - - let mut stack_arr: [u32; 32] = [0; 32]; - - let mut stack = unsafe { Vec::from_raw_parts(&mut stack_arr as *mut u32, 0, 32)}; - - let mut visit = |node: &RefNode| { - if i < buffer.len() { - buffer[i as usize] = shapes[node.index]; - i += 1; - return false; - } - i += 1; - true - }; - - let mut visitor = RayIntersectionsVisitor::new(&ray, 1000000000000.0, &mut visit); - - bvh.traverse_depth_first_with_stack(&mut visitor, &mut stack); - - // stack is pretending to be a heap allocated vector - std::mem::forget(stack); - - i as i32 -} - - - -#[ffi_function] -#[no_mangle] -pub extern "C" fn rebuild_bvh(bvh_ref: &mut BvhRef, shapes: &mut FFISliceMut) -{ - let bvh = &mut bvh_ref.bvh; - bvh.rebuild(shapes.as_slice_mut()); -} -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_ray(bvh_ref: &BvhRef, origin_vec: &Double3, dir_vec: &Double3, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 -{ - let bvh = &bvh_ref.bvh; - - let ray = Ray::new(to_vec(origin_vec), to_vec(dir_vec)); - let mut i = 0; - - for x in bvh.traverse_iterator(&ray, &shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; + fn bh_node_index(&self) -> usize { + self.node_index } - i as i32 } -#[ffi_function] -#[no_mangle] -pub extern "C" fn batch_query_rays(bvh_ref: &BvhRef, origins: &FFISliceMut, dirs: &FFISliceMut, hits: &mut FFISliceMut, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -{ - let bvh = &bvh_ref.bvh; - let mut i = 0; - let ray_count = origins.len(); - for r in 0..ray_count as usize { - let ray = Ray::new(to_vec(&origins[r]), to_vec(&dirs[r])); - let mut res = 0; - for x in bvh.traverse_iterator(&ray, &shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; +impl FromRawVertex for Triangle { + fn process( + vertices: Vec<(f32, f32, f32, f32)>, + _: Vec<(f32, f32, f32)>, + _: Vec<(f32, f32, f32)>, + polygons: Vec, + ) -> ObjResult<(Vec, Vec)> { + // Convert the vertices to `Point3`s. + let points = vertices + .into_iter() + .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) + .collect::>(); + + // Estimate for the number of triangles, assuming that each polygon is a triangle. + let mut triangles = Vec::with_capacity(polygons.len()); + { + let mut push_triangle = |indices: &Vec| { + let mut indices_iter = indices.iter(); + let anchor = points[*indices_iter.next().unwrap()]; + let mut second = points[*indices_iter.next().unwrap()]; + for third_index in indices_iter { + let third = points[*third_index]; + triangles.push(Triangle::new(anchor, second, third)); + second = third; + } + }; + + // Iterate over the polygons and populate the `Triangle`s vector. + for polygon in polygons.into_iter() { + match polygon { + Polygon::P(ref vec) => push_triangle(vec), + Polygon::PT(ref vec) | Polygon::PN(ref vec) => { + push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) + } + Polygon::PTN(ref vec) => { + push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) + } + } } - i += 1; - res += 1; - } - hits[r] = res; - } -} - - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_sphere(bvh_ref: &BvhRef, center: &Double3, radius: f64, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 -{ - - let bvh = &bvh_ref.bvh; - - let test_shape = Sphere::new(to_vec(center), radius); - let mut i = 0; - - for x in bvh.traverse_iterator(&test_shape, &shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; } - i += 1; + Ok((triangles, Vec::new())) } - i as i32 } -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_capsule(bvh_ref: &BvhRef, start: &Double3, end: &Double3, radius: f64, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 -{ - let bvh = &bvh_ref.bvh; +pub fn load_sponza_scene() -> (Vec, AABB) { + use std::fs::File; + use std::io::BufReader; - let test_shape = Capsule::new(to_vec(start), to_vec(end), radius); - let mut i = 0; + let file_input = + BufReader::new(File::open("media/sponza.obj").expect("Failed to open .obj file.")); + let sponza_obj: Obj = load_obj(file_input).expect("Failed to decode .obj file data."); + let triangles = sponza_obj.vertices; - for x in bvh.traverse_iterator(&test_shape, &shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_aabb(bvh_ref: &BvhRef, bounds: &BoundsD, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 -{ - let bvh = &bvh_ref.bvh; - - let min = to_vec(&bounds.min); - let max = to_vec(&bounds.max); - let test_shape = AABB::with_bounds(min, max); - let mut i = 0; - - for x in bvh.traverse_iterator(&test_shape, &shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_obb(bvh_ref: &BvhRef, ori: &QuatD, extents: &Double3, center: &Double3, shapes: &mut FFISliceMut, buffer: &mut FFISliceMut) -> i32 -{ - let bvh = &bvh_ref.bvh; - let obb = OBB { - orientation: to_quat(ori), - extents: to_vec(extents), - center: to_vec(center) - }; - - let mut i = 0; - - for x in bvh.traverse_iterator(&obb, &shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; + let mut bounds = AABB::empty(); + for triangle in &triangles { + bounds.join_mut(&triangle.aabb()); } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn free_bvh(_bvh_ref: BvhRef) -{ -} - - -#[ffi_function] -#[no_mangle] -pub extern "C" fn add_node(bvh_ref: &mut BvhRef, new_shape: i32, shapes: &mut FFISliceMut) -{ - let bvh = &mut bvh_ref.bvh; - bvh.add_node(shapes.as_slice_mut(), new_shape as usize); -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn remove_node(bvh_ref: &mut BvhRef, remove_shape: i32, shapes: &mut FFISliceMut) -{ - let bvh = &mut bvh_ref.bvh; - bvh.remove_node(shapes.as_slice_mut(), remove_shape as usize, true); -} -#[ffi_function] -#[no_mangle] -pub extern "C" fn update_node(bvh_ref: &mut BvhRef, update_shape: i32, shapes: &mut FFISliceMut) -{ - let bvh = &mut bvh_ref.bvh; - bvh.remove_node(shapes.as_slice_mut(), update_shape as usize, false); - bvh.add_node(shapes, update_shape as usize); + (triangles, bounds) } -#[ffi_function] -#[no_mangle] -pub extern "C" fn flatten_bvh(bvh_ref: &mut BvhRef, shapes: &mut FFISliceMut, results: &mut FFISliceMut) -> i32 -{ - let bvh = &bvh_ref.bvh; - - let flattened = bvh.flatten_custom(shapes.as_slice_mut(), &node_32_constructor); +pub fn main() { + let (mut triangles, _bounds) = load_sponza_scene(); + let mut bvh = BVH::build(triangles.as_mut_slice()); - for i in 0..flattened.len() { - results[i] = flattened[i]; + for _i in 0..10 { + bvh.rebuild(triangles.as_mut_slice()); } - - flattened.len() as i32 -} - -pub fn node_32_constructor(aabb: &AABB, entry_index: u32, exit_index: u32, shape_index: u32) -> FlatNode32 -{ - let min = Point32 { - x: aabb.min.x as f32, - y: aabb.min.y as f32, - z: aabb.min.z as f32 - }; - let max = Point32 { - x: aabb.max.x as f32, - y: aabb.max.y as f32, - z: aabb.max.z as f32 - }; - let b = AABB32 { - min, - max - }; - FlatNode32 { - aabb: b, - entry_index, - exit_index, - shape_index - } -} - - - -interoptopus::inventory!(my_inventory, [], [ - init_logger, - build_bvh, - build_qbvh, - query_ray_q, - rebuild_bvh, - query_ray, - batch_query_rays, - query_sphere, - query_capsule, - query_aabb, - query_obb, - free_bvh, - add_node, - remove_node, - update_node, - flatten_bvh, - ], [], []); - - - - -fn bindings_csharp() -> Result<(), Error> { - use interoptopus_backend_csharp::{Config, Generator, Unsafe, overloads::{DotNet, Unity}}; - - Generator::new( - Config { - class: "NativeBvhInterop".to_string(), - dll_name: "bvh_f64".to_string(), - namespace_mappings: NamespaceMappings::new("Assets.Scripts.Native"), - use_unsafe: Unsafe::UnsafePlatformMemCpy, - ..Config::default() - }, - my_inventory(), - ) - .add_overload_writer(Unity::new()) - .add_overload_writer(DotNet::new()) - .write_file("bindings/csharp/Interop.cs")?; - - Ok(()) -} - -#[test] -fn gen_bindings() { - bindings_csharp().unwrap(); -} - -#[test] -fn test_building_and_querying() { - let min = Double3 { - x: -1.0, - y: -1.0, - z: -1.0 - }; - let max = Double3 { - x: 1.0, - y: 1.0, - z: 1.0 - }; - let bounds = BoundsD { - min, - max - }; - let b = BVHBounds { - bounds, - index: 0, - internal_bvh_index: 0 - }; - - let _out = BVHBounds { - bounds, - index: 0, - internal_bvh_index: 0 - }; - - let origin = Double3 { - x: 0.0, - y: -5.0, - z: 0.0 - }; - let dir = Double3 { - x: 0.0, - y: 1.0, - z: 0.0 - }; - let in_slice = &mut [b]; - let mut input = FFISliceMut::::from_slice(in_slice); - let out_slice = &mut [b]; - let mut out = FFISliceMut::::from_slice(out_slice); - - let bvh_ref = build_qbvh(&mut input); - - let x = query_ray_q(&bvh_ref, &origin, &dir, &mut input, &mut out); - assert_eq!(x, 1); } - - - - diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index ffb6001..0000000 --- a/src/main.rs +++ /dev/null @@ -1,146 +0,0 @@ -use bvh_f64::aabb::{Bounded, AABB}; -use bvh_f64::bounding_hierarchy::BHShape; -use bvh_f64::bvh::BVH; -use bvh_f64::{Point3, Vector3}; -use obj::*; -use obj::raw::object::Polygon; -use num::{FromPrimitive, Integer}; - -#[derive(Debug)] -struct Sphere { - position: Point3, - radius: f64, - node_index: usize, -} - - - -impl Bounded for Sphere { - fn aabb(&self) -> AABB { - let half_size = Vector3::new(self.radius, self.radius, self.radius); - let min = self.position - half_size; - let max = self.position + half_size; - AABB::with_bounds(min, max) - } -} - -impl BHShape for Sphere { - fn set_bh_node_index(&mut self, index: usize) { - self.node_index = index; - } - - fn bh_node_index(&self) -> usize { - self.node_index - } -} - -/// A triangle struct. Instance of a more complex `Bounded` primitive. -#[derive(Debug)] -pub struct Triangle { - pub a: Point3, - pub b: Point3, - pub c: Point3, - aabb: AABB, - node_index: usize, -} - -impl Triangle { - pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { - Triangle { - a, - b, - c, - aabb: AABB::empty().grow(&a).grow(&b).grow(&c), - node_index: 0, - } - } -} - -impl Bounded for Triangle { - fn aabb(&self) -> AABB { - self.aabb - } -} - -impl BHShape for Triangle { - fn set_bh_node_index(&mut self, index: usize) { - self.node_index = index; - } - - fn bh_node_index(&self) -> usize { - self.node_index - } -} - -impl FromRawVertex for Triangle { - fn process( - vertices: Vec<(f32, f32, f32, f32)>, - _: Vec<(f32, f32, f32)>, - _: Vec<(f32, f32, f32)>, - polygons: Vec, - ) -> ObjResult<(Vec, Vec)> { - // Convert the vertices to `Point3`s. - let points = vertices - .into_iter() - .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) - .collect::>(); - - // Estimate for the number of triangles, assuming that each polygon is a triangle. - let mut triangles = Vec::with_capacity(polygons.len()); - { - let mut push_triangle = |indices: &Vec| { - let mut indices_iter = indices.iter(); - let anchor = points[*indices_iter.next().unwrap()]; - let mut second = points[*indices_iter.next().unwrap()]; - for third_index in indices_iter { - let third = points[*third_index]; - triangles.push(Triangle::new(anchor, second, third)); - second = third; - } - }; - - // Iterate over the polygons and populate the `Triangle`s vector. - for polygon in polygons.into_iter() { - match polygon { - Polygon::P(ref vec) => push_triangle(vec), - Polygon::PT(ref vec) | Polygon::PN(ref vec) => { - push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) - } - Polygon::PTN(ref vec) => { - push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) - } - } - } - } - Ok((triangles, Vec::new())) - } -} - -pub fn load_sponza_scene() -> (Vec, AABB) { - use std::fs::File; - use std::io::BufReader; - - let file_input = - BufReader::new(File::open("media/sponza.obj").expect("Failed to open .obj file.")); - let sponza_obj: Obj = load_obj(file_input).expect("Failed to decode .obj file data."); - let triangles = sponza_obj.vertices; - - let mut bounds = AABB::empty(); - for triangle in &triangles { - bounds.join_mut(&triangle.aabb()); - } - - (triangles, bounds) -} - - - - -pub fn main() { - let (mut triangles, _bounds) = load_sponza_scene(); - let mut bvh = BVH::build(triangles.as_mut_slice()); - - for _i in 0..10 { - bvh.rebuild(triangles.as_mut_slice()); - } -} diff --git a/src/ray.rs b/src/ray.rs index fbde5df..954c468 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -2,10 +2,9 @@ //! for axis aligned bounding boxes and triangles. use crate::aabb::AABB; -use crate::EPSILON; -use crate::{Point3, Vector3}; -use std::f64::INFINITY; use crate::bounding_hierarchy::IntersectionTest; +use crate::{Point3, Vector3}; +use crate::{Real, EPSILON}; /// A struct which defines a ray and some of its cached values. #[derive(Debug)] @@ -47,19 +46,19 @@ pub struct Ray { /// A struct which is returned by the `intersects_triangle` method. pub struct Intersection { /// Distance from the ray origin to the intersection point. - pub distance: f64, + pub distance: Real, /// U coordinate of the intersection. - pub u: f64, + pub u: Real, /// V coordinate of the intersection. - pub v: f64, + pub v: Real, } impl Intersection { /// Constructs an `Intersection`. `distance` should be set to positive infinity, /// if the intersection does not occur. - pub fn new(distance: f64, u: f64, v: f64) -> Intersection { + pub fn new(distance: Real, u: Real, v: Real) -> Intersection { Intersection { distance, u, v } } } @@ -133,7 +132,6 @@ impl IntersectionTest for Ray { } } - impl Ray { /// Creates a new [`Ray`] from an `origin` and a `direction`. /// `direction` will be normalized. @@ -165,7 +163,6 @@ impl Ray { } } - /// Naive implementation of a [`Ray`]/[`AABB`] intersection algorithm. /// /// # Examples @@ -281,7 +278,7 @@ impl Ray { // If backface culling is not desired write: // det < EPSILON && det > -EPSILON if det < EPSILON { - return Intersection::new(INFINITY, 0.0, 0.0); + return Intersection::new(Real::INFINITY, 0.0, 0.0); } let inv_det = 1.0 / det; @@ -294,7 +291,7 @@ impl Ray { // Test bounds: u < 0 || u > 1 => outside of triangle if !(0.0..=1.0).contains(&u) { - return Intersection::new(INFINITY, u, 0.0); + return Intersection::new(Real::INFINITY, u, 0.0); } // Prepare to test v parameter @@ -304,7 +301,7 @@ impl Ray { let v = self.direction.dot(v_vec) * inv_det; // The intersection lies outside of the triangle if v < 0.0 || u + v > 1.0 { - return Intersection::new(INFINITY, u, v); + return Intersection::new(Real::INFINITY, u, v); } let dist = a_to_c.dot(v_vec) * inv_det; @@ -312,21 +309,21 @@ impl Ray { if dist > EPSILON { Intersection::new(dist, u, v) } else { - Intersection::new(INFINITY, u, v) + Intersection::new(Real::INFINITY, u, v) } } } #[cfg(test)] mod tests { + use crate::Real; use std::cmp; - use std::f64::INFINITY; use crate::aabb::AABB; + use crate::bounding_hierarchy::IntersectionTest; use crate::ray::Ray; use crate::testbase::{tuple_to_point, tuplevec_small_strategy, TupleVec}; use crate::EPSILON; - use crate::bounding_hierarchy::IntersectionTest; use proptest::prelude::*; /// Generates a random `Ray` which points at at a random `AABB`. @@ -438,8 +435,8 @@ mod tests { // Get some u and v coordinates such that u+v <= 1 let u = u % 101; let v = cmp::min(100 - u, v % 101); - let u = u as f64 / 100.0; - let v = v as f64 / 100.0; + let u = u as Real / 100.0; + let v = v as Real / 100.0; // Define some point on the triangle let point_on_triangle = triangle.0 + u * u_vec + v * v_vec; @@ -456,12 +453,12 @@ mod tests { // Either the intersection is in the back side (including the triangle-plane) if on_back_side { // Intersection must be INFINITY, u and v are undefined - assert!(intersects.distance == INFINITY); + assert!(intersects.distance == Real::INFINITY); } else { // Or it is on the front side // Either the intersection is inside the triangle, which it should be // for all u, v such that u+v <= 1.0 - let intersection_inside = (0.0..=1.0).contains(&uv_sum) && intersects.distance < INFINITY; + let intersection_inside = (0.0..=1.0).contains(&uv_sum) && intersects.distance < Real::INFINITY; // Or the input data was close to the border let close_to_border = @@ -485,9 +482,9 @@ mod tests { #[cfg(all(feature = "bench", test))] mod bench { + use crate::bounding_hierarchy::IntersectionTest; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; - use crate::bounding_hierarchy::IntersectionTest; use crate::aabb::AABB; use crate::ray::Ray; diff --git a/src/shapes.rs b/src/shapes.rs index 6dd955b..4fc3f00 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -1,22 +1,15 @@ - use crate::aabb::AABB; -use crate::{Point3, Vector3}; use crate::bounding_hierarchy::IntersectionTest; -use glam::{DQuat, DMat4}; - +use crate::{Mat4, Point3, Quat, Real, Vector3}; pub struct Sphere { center: Point3, - radius: f64 + radius: Real, } impl Sphere { - pub fn new(center: Point3, radius: f64) -> Sphere - { - Sphere { - center, - radius - } + pub fn new(center: Point3, radius: Real) -> Sphere { + Sphere { center, radius } } } @@ -29,15 +22,14 @@ impl IntersectionTest for Sphere { pub struct Capsule { start: Point3, - radius: f64, + radius: Real, dir: Vector3, - len: f64 + len: Real, } impl Capsule { - pub fn new(start: Point3, end: Point3, radius: f64) -> Capsule - { + pub fn new(start: Point3, end: Point3, radius: Real) -> Capsule { let line = end - start; let dir = line.normalize(); let len = line.length(); @@ -46,7 +38,7 @@ impl Capsule { start, radius, dir, - len + len, } } } @@ -80,41 +72,41 @@ impl IntersectionTest for Capsule { loop { let closest = &aabb.closest_point(last); let center = nearest_point_on_line(&self.start, &self.dir, self.len, closest); - let sphere = Sphere{ + let sphere = Sphere { center, - radius: self.radius + radius: self.radius, }; if sphere.intersects_aabb(aabb) { return true; } - if last.distance_squared(center) < 0.0001 - { - return false + if last.distance_squared(center) < 0.0001 { + return false; } else { last = center; } } - - } } pub struct OBB { - pub orientation: DQuat, + pub orientation: Quat, pub extents: Vector3, pub center: Vector3, } impl IntersectionTest for OBB { - fn intersects_aabb(&self, aabb: &AABB) -> bool { let half_a = self.extents; let half_b = (aabb.max - aabb.min) * 0.5; let value = (aabb.max + aabb.min) * 0.5; let translation = self.orientation * (value - self.center); - let mat = DMat4::from_rotation_translation(self.orientation, translation); + let mat = Mat4::from_rotation_translation(self.orientation, translation); - let vec_1 = Vector3::new(translation.x.abs(), translation.y.abs(), translation.z.abs()); + let vec_1 = Vector3::new( + translation.x.abs(), + translation.y.abs(), + translation.z.abs(), + ); let right = right(mat); let up = up(mat); let backward = back(mat); @@ -124,77 +116,118 @@ impl IntersectionTest for OBB { let num = vec_2.x.abs() + vec_3.x.abs() + vec_4.x.abs(); let num2 = vec_2.y.abs() + vec_3.y.abs() + vec_4.y.abs(); let num3 = vec_2.z.abs() + vec_3.z.abs() + vec_4.z.abs(); - if vec_1.x + num <= half_a.x && vec_1.y + num2 <= half_a.y && vec_1.z + num3 <= half_a.z - { + if vec_1.x + num <= half_a.x && vec_1.y + num2 <= half_a.y && vec_1.z + num3 <= half_a.z { // Contained return true; } - if vec_1.x > half_a.x + vec_2.x.abs() + vec_3.x.abs() + vec_4.x.abs() - { + if vec_1.x > half_a.x + vec_2.x.abs() + vec_3.x.abs() + vec_4.x.abs() { return false; } - if vec_1.y > half_a.y + vec_2.y.abs() + vec_3.y.abs() + vec_4.y.abs() - { + if vec_1.y > half_a.y + vec_2.y.abs() + vec_3.y.abs() + vec_4.y.abs() { return false; } - if vec_1.z > half_a.z + vec_2.z.abs() + vec_3.z.abs() + vec_4.z.abs() - { + if vec_1.z > half_a.z + vec_2.z.abs() + vec_3.z.abs() + vec_4.z.abs() { return false; } - if translation.dot(right.abs()) > half_a.x * right.x.abs() + half_a.y * right.y.abs() + half_a.z * right.z.abs() + half_b.x + if translation.dot(right.abs()) + > half_a.x * right.x.abs() + + half_a.y * right.y.abs() + + half_a.z * right.z.abs() + + half_b.x { return false; } - if translation.dot(up.abs()) > half_a.x * up.x.abs() + half_a.y * up.y.abs() + half_a.z * up.z.abs() + half_b.y + if translation.dot(up.abs()) + > half_a.x * up.x.abs() + half_a.y * up.y.abs() + half_a.z * up.z.abs() + half_b.y { return false; } - if translation.dot(backward.abs()) > half_a.x * backward.x.abs() + half_a.y * backward.y.abs() + half_a.z * backward.z.abs() + half_b.z + if translation.dot(backward.abs()) + > half_a.x * backward.x.abs() + + half_a.y * backward.y.abs() + + half_a.z * backward.z.abs() + + half_b.z { return false; } let mut vec_5 = Vector3::new(0.0, -right.z, right.y); - if translation.dot(vec_5.abs()) > half_a.y * vec_5.y.abs() + half_a.z * vec_5.z.abs() + vec_5.dot(vec_3.abs()) + vec_5.dot(vec_4.abs()) + if translation.dot(vec_5.abs()) + > half_a.y * vec_5.y.abs() + + half_a.z * vec_5.z.abs() + + vec_5.dot(vec_3.abs()) + + vec_5.dot(vec_4.abs()) { return false; } vec_5 = Vector3::new(0.0, -up.z, up.y); - if translation.dot(vec_5.abs()) > half_a.y * vec_5.y.abs() + half_a.z * vec_5.z.abs() + vec_5.dot(vec_4.abs()) + vec_5.dot(vec_2.abs()) + if translation.dot(vec_5.abs()) + > half_a.y * vec_5.y.abs() + + half_a.z * vec_5.z.abs() + + vec_5.dot(vec_4.abs()) + + vec_5.dot(vec_2.abs()) { return false; } vec_5 = Vector3::new(0.0, -backward.z, backward.y); - if translation.dot(vec_5.abs()) > half_a.y * vec_5.y.abs() + half_a.z * vec_5.z.abs() + vec_5.dot(vec_2.abs()) + vec_5.dot(vec_3.abs()) + if translation.dot(vec_5.abs()) + > half_a.y * vec_5.y.abs() + + half_a.z * vec_5.z.abs() + + vec_5.dot(vec_2.abs()) + + vec_5.dot(vec_3.abs()) { return false; } vec_5 = Vector3::new(right.z, 0.0, -right.x); - if translation.dot(vec_5.abs()) > half_a.z * vec_5.z.abs() + half_a.x * vec_5.x.abs() + vec_5.dot(vec_3.abs()) + vec_5.dot(vec_4.abs()) + if translation.dot(vec_5.abs()) + > half_a.z * vec_5.z.abs() + + half_a.x * vec_5.x.abs() + + vec_5.dot(vec_3.abs()) + + vec_5.dot(vec_4.abs()) { return false; } vec_5 = Vector3::new(up.z, 0.0, -up.x); - if translation.dot(vec_5.abs()) > half_a.z * vec_5.z.abs() + half_a.x * vec_5.x.abs() + vec_5.dot(vec_4.abs()) + vec_5.dot(vec_2.abs()) + if translation.dot(vec_5.abs()) + > half_a.z * vec_5.z.abs() + + half_a.x * vec_5.x.abs() + + vec_5.dot(vec_4.abs()) + + vec_5.dot(vec_2.abs()) { return false; } vec_5 = Vector3::new(backward.z, 0.0, -backward.x); - if translation.dot(vec_5.abs()) > half_a.z * vec_5.z.abs() + half_a.x * vec_5.x.abs() + vec_5.dot(vec_2.abs()) + vec_5.dot(vec_3.abs()) + if translation.dot(vec_5.abs()) + > half_a.z * vec_5.z.abs() + + half_a.x * vec_5.x.abs() + + vec_5.dot(vec_2.abs()) + + vec_5.dot(vec_3.abs()) { return false; } vec_5 = Vector3::new(-right.y, right.x, 0.0); - if translation.dot(vec_5.abs()) > half_a.x * vec_5.x.abs() + half_a.y * vec_5.y.abs() + vec_5.dot(vec_3.abs()) + vec_5.dot(vec_4.abs()) + if translation.dot(vec_5.abs()) + > half_a.x * vec_5.x.abs() + + half_a.y * vec_5.y.abs() + + vec_5.dot(vec_3.abs()) + + vec_5.dot(vec_4.abs()) { return false; } vec_5 = Vector3::new(-up.y, up.x, 0.0); - if translation.dot(vec_5.abs()) > half_a.x * vec_5.x.abs() + half_a.y * vec_5.y.abs() + vec_5.dot(vec_4.abs()) + vec_5.dot(vec_2.abs()) + if translation.dot(vec_5.abs()) + > half_a.x * vec_5.x.abs() + + half_a.y * vec_5.y.abs() + + vec_5.dot(vec_4.abs()) + + vec_5.dot(vec_2.abs()) { return false; } vec_5 = Vector3::new(-backward.y, backward.x, 0.0); - if translation.dot(vec_5.abs()) > half_a.x * vec_5.x.abs() + half_a.y * vec_5.y.abs() + vec_5.dot(vec_2.abs()) + vec_5.dot(vec_3.abs()) + if translation.dot(vec_5.abs()) + > half_a.x * vec_5.x.abs() + + half_a.y * vec_5.y.abs() + + vec_5.dot(vec_2.abs()) + + vec_5.dot(vec_3.abs()) { return false; } @@ -203,41 +236,34 @@ impl IntersectionTest for OBB { } } -fn right(matrix: DMat4) -> Vector3 -{ +fn right(matrix: Mat4) -> Vector3 { matrix.row(0).truncate() } -fn up(matrix: DMat4) -> Vector3 -{ +fn up(matrix: Mat4) -> Vector3 { matrix.row(1).truncate() } -fn back(matrix: DMat4) -> Vector3 -{ +fn back(matrix: Mat4) -> Vector3 { matrix.row(2).truncate() } -fn translation(matrix: DMat4) -> Vector3 -{ +fn translation(matrix: Mat4) -> Vector3 { matrix.row(3).truncate() } -pub fn nearest_point_on_line(p1: &Point3, dir: &Vector3, len: f64, pnt: &Point3) -> Point3 { +pub fn nearest_point_on_line(p1: &Point3, dir: &Vector3, len: Real, pnt: &Point3) -> Point3 { let v = *pnt - *p1; let d = v.dot(*dir); *p1 + (*dir * d.clamp(0.0, len)) } - - #[cfg(test)] mod tests { use crate::aabb::AABB; - use crate::{Point3, Vector3}; use crate::bounding_hierarchy::IntersectionTest; - use glam::{DQuat}; use crate::shapes::{Capsule, OBB}; + use crate::{Point3, Quat, Real, Vector3}; #[test] fn basic_test_capsule() { @@ -261,34 +287,32 @@ mod tests { assert!(capsule.intersects_aabb(&aabb)); let dir = (start - end).normalize(); - let offset = 0.005; + let offset: Real = 0.005; println!("{}", dir); for i in 0..800 { println!("{}", i); - let pt = offset * dir * i as f64; + let pt = offset * dir * i as Real; let cap = Capsule::new(start + pt, end + pt, 1.45); assert!(cap.intersects_aabb(&aabb)); } } - #[test] fn basic_obb() { let min = Point3::new(0.0, 0.0, 0.0); let max = Point3::new(1.0, 1.0, 1.0); let aabb = AABB::empty().grow(&min).grow(&max); - let ori = DQuat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0), 0.785398); + let ori = Quat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0), 0.785398); let extents = Vector3::new(0.5, 0.5, 0.5); let pos = Vector3::new(0.5, 2.2, 0.5); let obb = OBB { orientation: ori, extents, - center: pos + center: pos, }; assert!(obb.intersects_aabb(&aabb)); - } -} \ No newline at end of file +} diff --git a/src/testbase.rs b/src/testbase.rs index d43fbf6..ade92d7 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -2,10 +2,9 @@ #![cfg(test)] use std::collections::HashSet; -use std::f64; use std::mem::transmute; -use crate::{Point3, Vector3}; +use crate::{Point3, Real, Vector3}; use num::{FromPrimitive, Integer}; use obj::raw::object::Polygon; use obj::*; @@ -19,27 +18,23 @@ use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; use crate::ray::Ray; /// A vector represented as a tuple -pub type TupleVec = (f64, f64, f64); +pub type TupleVec = (Real, Real, Real); /// Generate a `TupleVec` for [`proptest::strategy::Strategy`] from -10e10 to 10e10 /// A small enough range to prevent most fp32 errors from breaking certain tests /// Tests which rely on this strategy should probably be rewritten pub fn tuplevec_small_strategy() -> impl Strategy { - ( - -10e10_f64..10e10_f64, - -10e10_f64..10e10_f64, - -10e10_f64..10e10_f64, - ) + let min: Real = -10e10; + let max: Real = 10e10; + (min..max, min..max, min..max) } /// Generate a `TupleVec` for [`proptest::strategy::Strategy`] from -10e30 to 10e30 -/// A small enough range to prevent `f64::MAX` ranges from breaking certain tests +/// A small enough range to prevent `Real::MAX` ranges from breaking certain tests pub fn tuplevec_large_strategy() -> impl Strategy { - ( - -10e30_f64..10e30_f64, - -10e30_f64..10e30_f64, - -10e30_f64..10e30_f64, - ) + let min: Real = -10e30; + let max: Real = 10e30; + (min..max, min..max, min..max) } /// Convert a `TupleVec` to a [`Point3`]. @@ -53,6 +48,7 @@ pub fn tuple_to_vector(tpl: &TupleVec) -> Vector3 { } /// Define some `Bounded` structure. +#[derive(Debug, Clone, Copy)] pub struct UnitBox { pub id: i32, pub pos: Point3, @@ -94,7 +90,7 @@ pub fn generate_aligned_boxes() -> Vec { // Create 21 boxes along the x-axis let mut shapes = Vec::new(); for x in -10..11 { - shapes.push(UnitBox::new(x, Point3::new(x as f64, 0.0, 0.0))); + shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); } shapes } @@ -341,9 +337,9 @@ pub fn next_point3(seed: &mut u64, aabb: &AABB) -> Point3 { let (a, b, c) = next_point3_raw(seed); use std::i32; let float_vector = Vector3::new( - (a as f64 / i32::MAX as f64) + 1.0, - (b as f64 / i32::MAX as f64) + 1.0, - (c as f64 / i32::MAX as f64) + 1.0, + (a as Real / i32::MAX as Real) + 1.0, + (b as Real / i32::MAX as Real) + 1.0, + (c as Real / i32::MAX as Real) + 1.0, ) * 0.5; assert!(float_vector.x >= 0.0 && float_vector.x <= 1.0); @@ -404,7 +400,7 @@ pub fn randomly_transform_scene( triangles: &mut Vec, amount: usize, bounds: &AABB, - max_offset_option: Option, + max_offset_option: Option, seed: &mut u64, ) -> HashSet { let mut indices: Vec = (0..triangles.len()).collect(); @@ -420,7 +416,7 @@ pub fn randomly_transform_scene( let max_offset = if let Some(value) = max_offset_option { value } else { - f64::INFINITY + Real::INFINITY }; for index in &indices { @@ -520,6 +516,8 @@ fn bench_intersect_sponza_list(b: &mut ::test::Bencher) { /// structures. #[cfg(feature = "bench")] pub fn intersect_list_aabb(triangles: &[Triangle], bounds: &AABB, b: &mut ::test::Bencher) { + use crate::bounding_hierarchy::IntersectionTest; + let mut seed = 0; b.iter(|| { let ray = create_ray(&mut seed, &bounds); From 71a080f7e8741d0aa78c19c005f2e6a4b7039ec8 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 8 Jan 2022 09:39:54 -0500 Subject: [PATCH 17/37] merge with upstream, fix optimize bug --- Cargo.toml | 15 +- src/aabb.rs | 14 +- src/bounding_hierarchy.rs | 6 +- src/bvh/bvh_impl.rs | 429 +++----------------------- src/bvh/optimization.rs | 634 +++++++++++++++++++++++++++++++------- src/flat_bvh.rs | 37 ++- src/lib.rs | 153 ++++----- src/ray.rs | 1 + src/testbase.rs | 9 +- src/utils.rs | 4 +- 10 files changed, 707 insertions(+), 595 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 459e459..d694cff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bvh" description = "A fast BVH using SAH" -version = "0.5.0" +version = "0.6.0" edition = "2018" authors = [ "Sven-Hendrik Haase ", @@ -17,26 +17,33 @@ license = "MIT" members = ["bvh-lib"] [dependencies] -approx = "0.4" +approx = "0.5" rand = "0.8" log = "0.4.14" num = "0.4" glam = "0.20" rayon = "1.5.1" smallvec = "1.6.1" -obj-rs = "0.6" stl_io = "0.6.0" +serde = { optional = true, version = "1", features = ["derive"] } + [dev-dependencies] proptest = "1.0" -float_eq = "0.6" +obj-rs = "0.7" +float_eq = "0.7" criterion = "0.3" itertools = "0.10.1" +serde = { version = "1", features = ["derive"] } +glam = { version = "0.20", features = ["serde"] } +serde_json = "1" [features] default = [] bench = [] f64 = [] +# Unfortunately can't use "serde" as the feature name until https://github.com/rust-lang/cargo/issues/5565 lands +serde_impls = ["serde", "glam/serde"] [profile.release] lto = true diff --git a/src/aabb.rs b/src/aabb.rs index ab9629b..f9cc374 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -9,7 +9,8 @@ use crate::{Point3, Real, Vector3}; use crate::axis::Axis; /// AABB struct. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] #[allow(clippy::upper_case_acronyms)] pub struct AABB { /// minimum coordinates @@ -709,6 +710,7 @@ mod tests { let p1 = tuple_to_point(&a); let p2 = tuple_to_point(&b); + // Span the `AABB` let aabb = AABB::empty().grow(&p1).join_bounded(&p2); @@ -767,23 +769,23 @@ mod tests { // Create two `AABB`s. One spanned the first five points, // the other by the last five points - let aabb1 = points.iter().take(5).fold(AABB::empty(), |aabb, point| aabb.grow(&point)); - let aabb2 = points.iter().skip(5).fold(AABB::empty(), |aabb, point| aabb.grow(&point)); + let aabb1 = points.iter().take(5).fold(AABB::empty(), |aabb, point| aabb.grow(point)); + let aabb2 = points.iter().skip(5).fold(AABB::empty(), |aabb, point| aabb.grow(point)); // The `AABB`s should contain the points by which they are spanned let aabb1_contains_init_five = points.iter() .take(5) - .all(|point| aabb1.contains(&point)); + .all(|point| aabb1.contains(point)); let aabb2_contains_last_five = points.iter() .skip(5) - .all(|point| aabb2.contains(&point)); + .all(|point| aabb2.contains(point)); // Build the joint of the two `AABB`s let aabbu = aabb1.join(&aabb2); // The joint should contain all points let aabbu_contains_all = points.iter() - .all(|point| aabbu.contains(&point)); + .all(|point| aabbu.contains(point)); // Return the three properties assert!(aabb1_contains_init_five && aabb2_contains_last_five && aabbu_contains_all); diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index 8f08eb8..c9275ba 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -34,6 +34,7 @@ pub trait BoundingHierarchy { /// use bvh::aabb::{AABB, Bounded}; /// use bvh::bounding_hierarchy::BoundingHierarchy; /// use bvh::{Point3, Vector3}; + /// use bvh::Real; /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, @@ -72,7 +73,7 @@ pub trait BoundingHierarchy { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f64, i as f64, i as f64); + /// # let position = Point3::new(i as Real, i as Real, i as Real); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -107,6 +108,7 @@ pub trait BoundingHierarchy { /// use bvh::bvh::BVH; /// use bvh::{Point3, Vector3}; /// use bvh::ray::Ray; + /// use bvh::Real; /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, @@ -145,7 +147,7 @@ pub trait BoundingHierarchy { /// # fn create_bvh() -> (BVH, Vec) { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f64, i as f64, i as f64); + /// # let position = Point3::new(i as Real, i as Real, i as Real); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # let bvh = BVH::build(&mut shapes); diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index fd56be3..c066730 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -26,6 +26,7 @@ use std::slice; /// [`BVH`]: struct.BVHNode.html /// #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] #[allow(clippy::upper_case_acronyms)] pub enum BVHNode { /// Leaf node. @@ -586,6 +587,8 @@ impl BVHNode { /// [`BVH`]: struct.BVH.html /// #[allow(clippy::upper_case_acronyms)] +#[derive(Clone)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] pub struct BVH { /// The list of nodes of the [`BVH`]. /// @@ -659,371 +662,6 @@ impl BVH { BVHTraverseIterator::new(self, test, shapes) } - pub fn add_node(&mut self, shapes: &mut [T], new_shape_index: usize) { - let mut i = 0; - let new_shape = &shapes[new_shape_index]; - let shape_aabb = new_shape.aabb(); - let shape_sa = shape_aabb.surface_area(); - - if self.nodes.len() == 0 { - self.nodes.push(BVHNode::Leaf { - parent_index: 0, - shape_index: new_shape_index, - }); - shapes[new_shape_index].set_bh_node_index(0); - return; - } - let mut depth = 0; - - loop { - match self.nodes[i] { - BVHNode::Node { - child_l_aabb, - child_l_index, - child_r_aabb, - child_r_index, - parent_index, - } => { - let left_expand = child_l_aabb.join(&shape_aabb); - - let right_expand = child_r_aabb.join(&shape_aabb); - - let send_left = child_r_aabb.surface_area() + left_expand.surface_area(); - let send_right = child_l_aabb.surface_area() + right_expand.surface_area(); - let merged_aabb = child_r_aabb.join(&child_l_aabb); - let merged = merged_aabb.surface_area() + shape_sa; - - // merge is more expensive only do when it's significantly better - let merge_discount = 0.3; - //dbg!(depth); - - // compared SA of the options - if merged < send_left.min(send_right) * merge_discount { - //println!("Merging left and right trees"); - // Merge left and right trees - let l_index = self.nodes.len(); - let new_left = BVHNode::Leaf { - parent_index: i, - shape_index: new_shape_index, - }; - shapes[new_shape_index].set_bh_node_index(l_index); - self.nodes.push(new_left); - - let r_index = self.nodes.len(); - let new_right = BVHNode::Node { - child_l_aabb: child_l_aabb, - child_l_index, - child_r_aabb: child_r_aabb.clone(), - child_r_index, - parent_index: i, - }; - self.nodes.push(new_right); - *self.nodes[child_r_index].parent_mut() = r_index; - *self.nodes[child_l_index].parent_mut() = r_index; - - self.nodes[i] = BVHNode::Node { - child_l_aabb: shape_aabb, - child_l_index: l_index, - child_r_aabb: merged_aabb, - child_r_index: r_index, - parent_index, - }; - //self.fix_depth(l_index, depth + 1); - //self.fix_depth(r_index, depth + 1); - return; - } else if send_left < send_right { - // send new box down left side - //println!("Sending left"); - if i == child_l_index { - panic!("broken loop"); - } - let child_l_aabb = left_expand; - self.nodes[i] = BVHNode::Node { - child_l_aabb, - child_l_index, - child_r_aabb, - child_r_index, - parent_index, - }; - i = child_l_index; - } else { - // send new box down right - //println!("Sending right"); - if i == child_r_index { - panic!("broken loop"); - } - let child_r_aabb = right_expand; - self.nodes[i] = BVHNode::Node { - child_l_aabb, - child_l_index, - child_r_aabb, - child_r_index, - parent_index, - }; - i = child_r_index; - } - } - BVHNode::Leaf { - shape_index, - parent_index, - } => { - //println!("Splitting leaf"); - // Split leaf into 2 nodes and insert the new box - let l_index = self.nodes.len(); - let new_left = BVHNode::Leaf { - parent_index: i, - shape_index: new_shape_index, - }; - shapes[new_shape_index].set_bh_node_index(l_index); - self.nodes.push(new_left); - - let child_r_aabb = shapes[shape_index].aabb(); - let child_r_index = self.nodes.len(); - let new_right = BVHNode::Leaf { - parent_index: i, - shape_index: shape_index, - }; - shapes[shape_index].set_bh_node_index(child_r_index); - self.nodes.push(new_right); - - let new_node = BVHNode::Node { - child_l_aabb: shape_aabb, - child_l_index: l_index, - child_r_aabb, - child_r_index, - parent_index, - }; - self.nodes[i] = new_node; - self.fix_aabbs_ascending(shapes, parent_index); - return; - } - } - depth += 1; - } - } - - pub fn remove_node( - &mut self, - shapes: &mut [T], - deleted_shape_index: usize, - swap_shape: bool, - ) { - if self.nodes.len() == 0 { - return; - //panic!("can't remove a node from a bvh with only one node"); - } - let bad_shape = &shapes[deleted_shape_index]; - - // to remove a node, delete it from the tree, remove the parent and replace it with the sibling - // swap the node being removed to the end of the slice and adjust the index of the node that was removed - // update the removed nodes index - // swap the shape to the end and update the node to still point at the right shape - let dead_node_index = bad_shape.bh_node_index(); - - if self.nodes.len() == 1 { - if dead_node_index == 0 { - self.nodes.clear(); - } - } else { - //println!("delete_i={}", dead_node_index); - - let dead_node = self.nodes[dead_node_index]; - - let parent_index = dead_node.parent(); - //println!("parent_i={}", parent_index); - let gp_index = self.nodes[parent_index].parent(); - //println!("{}->{}->{}", gp_index, parent_index, dead_node_index); - - let sibling_index = if self.nodes[parent_index].child_l() == dead_node_index { - self.nodes[parent_index].child_r() - } else { - self.nodes[parent_index].child_l() - }; - let sibling_box = if self.nodes[parent_index].child_l() == dead_node_index { - self.nodes[parent_index].child_r_aabb() - } else { - self.nodes[parent_index].child_l_aabb() - }; - // TODO: fix potential issue leaving empty spot in self.nodes - // the node swapped to sibling_index should probably be swapped to the end - // of the vector and the vector truncated - if parent_index == gp_index { - // We are removing one of the children of the root node - // The other child needs to become the root node - // The old root node and the dead child then have to be moved - - // println!("gp == parent {}", parent_index); - if parent_index != 0 { - panic!( - "Circular node that wasn't root parent={} node={}", - parent_index, dead_node_index - ); - } - self.nodes.swap(parent_index, sibling_index); - - match self.nodes[parent_index].shape_index() { - Some(index) => { - *self.nodes[parent_index].parent_mut() = parent_index; - shapes[index].set_bh_node_index(parent_index); - self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); - self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); - } - _ => { - *self.nodes[parent_index].parent_mut() = parent_index; - let new_root = self.nodes[parent_index]; - *self.nodes[new_root.child_l()].parent_mut() = parent_index; - *self.nodes[new_root.child_r()].parent_mut() = parent_index; - //println!("set {}'s parent to {}", new_root.child_l(), parent_index); - //println!("set {}'s parent to {}", new_root.child_r(), parent_index); - self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); - self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); - } - } - //println!("nodes_len {}, sib_index {}", self.nodes.len(), sibling_index); - //println!("nodes_len {}", self.nodes.len()); - } else { - let box_to_change = if self.nodes[gp_index].child_l() == parent_index { - self.nodes[gp_index].child_l_aabb_mut() - } else { - self.nodes[gp_index].child_r_aabb_mut() - }; - //println!("on {} adjusting {} to {}", gp_index, box_to_change, sibling_box); - *box_to_change = sibling_box; - //println!("{} {} {}", gp_index, self.nodes[gp_index].child_l_aabb(), self.nodes[gp_index].child_r_aabb()); - let ref_to_change = if self.nodes[gp_index].child_l() == parent_index { - self.nodes[gp_index].child_l_mut() - } else { - self.nodes[gp_index].child_r_mut() - }; - //println!("on {} {}=>{}", gp_index, ref_to_change, sibling_index); - *ref_to_change = sibling_index; - *self.nodes[sibling_index].parent_mut() = gp_index; - - self.fix_aabbs_ascending(shapes, gp_index); - //let new_depth = self.nodes[sibling_index].depth() - 1; - //*self.nodes[sibling_index].depth_mut() = new_depth; - // remove node and parent - - //println!("---"); - //self.pretty_print(); - //println!("---"); - self.swap_and_remove_index(shapes, dead_node_index.max(parent_index)); - - //println!("---"); - //self.pretty_print(); - //println!("---"); - self.swap_and_remove_index(shapes, parent_index.min(dead_node_index)); - - //println!("---"); - //self.pretty_print(); - //println!("---"); - } - } - - if swap_shape { - let end_shape = shapes.len() - 1; - if deleted_shape_index < end_shape { - shapes.swap(deleted_shape_index, end_shape); - let node_index = shapes[deleted_shape_index].bh_node_index(); - match self.nodes[node_index].shape_index_mut() { - Some(index) => *index = deleted_shape_index, - _ => {} - } - } - } - } - - fn fix_aabbs_ascending(&mut self, shapes: &mut [T], node_index: usize) { - let mut index_to_fix = node_index; - while index_to_fix != 0 { - let parent = self.nodes[index_to_fix].parent(); - match self.nodes[parent] { - BVHNode::Node { - parent_index, - child_l_index, - child_r_index, - child_l_aabb, - child_r_aabb, - } => { - //println!("checking {} l={} r={}", parent, child_l_index, child_r_index); - let l_aabb = self.nodes[child_l_index].get_node_aabb(shapes); - let r_aabb = self.nodes[child_r_index].get_node_aabb(shapes); - //println!("child_l_aabb {}", l_aabb); - //println!("child_r_aabb {}", r_aabb); - let mut stop = true; - if !l_aabb.relative_eq(&child_l_aabb, EPSILON) { - stop = false; - //println!("setting {} l = {}", parent, l_aabb); - *self.nodes[parent].child_l_aabb_mut() = l_aabb; - } - if !r_aabb.relative_eq(&child_r_aabb, EPSILON) { - stop = false; - //println!("setting {} r = {}", parent, r_aabb); - *self.nodes[parent].child_r_aabb_mut() = r_aabb; - } - if !stop { - index_to_fix = parent_index; - //dbg!(index_to_fix); - } else { - index_to_fix = 0; - } - } - _ => index_to_fix = 0, - } - } - } - - fn swap_and_remove_index(&mut self, shapes: &mut [T], node_index: usize) { - let end = self.nodes.len() - 1; - //println!("removing node {}", node_index); - if node_index != end { - self.nodes[node_index] = self.nodes[end]; - let node_parent = self.nodes[node_index].parent(); - match self.nodes[node_parent] { - BVHNode::Leaf { - parent_index, - shape_index, - } => { - println!( - "truncating early node_parent={} parent_index={} shape_index={}", - node_parent, parent_index, shape_index - ); - self.nodes.truncate(end); - return; - } - _ => {} - } - let parent = self.nodes[node_parent]; - let moved_left = parent.child_l() == end; - let ref_to_change = if moved_left { - self.nodes[node_parent].child_l_mut() - } else { - self.nodes[node_parent].child_r_mut() - }; - //println!("on {} changing {}=>{}", node_parent, ref_to_change, node_index); - *ref_to_change = node_index; - - match self.nodes[node_index] { - BVHNode::Leaf { shape_index, .. } => { - shapes[shape_index].set_bh_node_index(node_index); - } - BVHNode::Node { - child_l_index, - child_r_index, - .. - } => { - *self.nodes[child_l_index].parent_mut() = node_index; - *self.nodes[child_r_index].parent_mut() = node_index; - - //println!("{} {} {}", node_index, self.nodes[node_index].child_l_aabb(), self.nodes[node_index].child_r_aabb()); - //let correct_depth - //self.fix_depth(child_l_index, ) - } - } - } - self.nodes.truncate(end); - } - /* pub fn fix_depth( &mut self, @@ -1194,6 +832,10 @@ impl BVH { node_count: &mut usize, shapes: &[Shape], ) { + if self.nodes.len() == 0 { + return; + } + *node_count += 1; let node = &self.nodes[node_index]; @@ -1220,23 +862,29 @@ impl BVH { } => { assert!( expected_outer_aabb.approx_contains_aabb_eps(&child_l_aabb, EPSILON), - "Left child lies outside the expected bounds. + "Left child {} lies outside the expected bounds. \tDepth: {} \tBounds: {} - \tLeft child: {}", + \tLeft child: {} + \tNode: {}", + child_l_index, depth, expected_outer_aabb, - child_l_aabb + child_l_aabb, + node_index ); assert!( expected_outer_aabb.approx_contains_aabb_eps(&child_r_aabb, EPSILON), - "Right child lies outside the expected bounds. + "Right child {} lies outside the expected bounds. \tDepth: {} \tBounds: {} - \tRight child: {}", + \tRight child: {} + \tNode: {}", + child_r_index, depth, expected_outer_aabb, - child_r_aabb + child_r_aabb, + node_index ); self.assert_consistent_subtree( child_l_index, @@ -1368,6 +1016,20 @@ impl BVH { self.assert_tight_subtree(0, &joint_aabb, shapes); } } + + /// Check that the `AABB`s in the `BVH` are tight, which means, that parent `AABB`s are not + /// larger than they should be. + pub fn assert_reachable(&self, shapes: &[Shape]) { + for shape in shapes { + let mut hit = false; + for s in self.traverse_iterator(&shape.aabb(), shapes) { + if s.bh_node_index() == shape.bh_node_index() { + hit = true; + } + } + assert!(hit); + } + } } impl BoundingHierarchy for BVH { @@ -1543,6 +1205,7 @@ mod tests { break; } } + //dbg!(delete_i, shapes.len()); bvh.remove_node(&mut shapes, delete_i, true); shapes.truncate(shapes.len() - 1); assert_eq!(shapes.len(), x_values.len() - x_i); @@ -1616,20 +1279,20 @@ mod bench { #[bench] /// Benchmark the construction of a `BVH` with 1,200 triangles. - fn bench_build_1200_triangles_bvh(mut b: &mut ::test::Bencher) { - build_1200_triangles_bh::(&mut b); + fn bench_build_1200_triangles_bvh(b: &mut ::test::Bencher) { + build_1200_triangles_bh::(b); } #[bench] /// Benchmark the construction of a `BVH` with 12,000 triangles. - fn bench_build_12k_triangles_bvh(mut b: &mut ::test::Bencher) { - build_12k_triangles_bh::(&mut b); + fn bench_build_12k_triangles_bvh(b: &mut ::test::Bencher) { + build_12k_triangles_bh::(b); } #[bench] /// Benchmark the construction of a `BVH` with 120,000 triangles. - fn bench_build_120k_triangles_bvh(mut b: &mut ::test::Bencher) { - build_120k_triangles_bh::(&mut b); + fn bench_build_120k_triangles_bvh(b: &mut ::test::Bencher) { + build_120k_triangles_bh::(b); } #[bench] @@ -1690,20 +1353,20 @@ mod bench { #[bench] /// Benchmark intersecting 1,200 triangles using the recursive `BVH`. - fn bench_intersect_1200_triangles_bvh(mut b: &mut ::test::Bencher) { - intersect_1200_triangles_bh::(&mut b); + fn bench_intersect_1200_triangles_bvh(b: &mut ::test::Bencher) { + intersect_1200_triangles_bh::(b); } #[bench] /// Benchmark intersecting 12,000 triangles using the recursive `BVH`. - fn bench_intersect_12k_triangles_bvh(mut b: &mut ::test::Bencher) { - intersect_12k_triangles_bh::(&mut b); + fn bench_intersect_12k_triangles_bvh(b: &mut ::test::Bencher) { + intersect_12k_triangles_bh::(b); } #[bench] /// Benchmark intersecting 120,000 triangles using the recursive `BVH`. - fn bench_intersect_120k_triangles_bvh(mut b: &mut ::test::Bencher) { - intersect_120k_triangles_bh::(&mut b); + fn bench_intersect_120k_triangles_bvh(b: &mut ::test::Bencher) { + intersect_120k_triangles_bh::(b); } #[bench] diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 3040c4b..8ddfce1 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -7,7 +7,7 @@ //! use crate::bounding_hierarchy::BHShape; -use crate::bvh::*; +use crate::{bvh::*, EPSILON}; use crate::{aabb::AABB, Real}; use std::fmt::Debug; @@ -71,16 +71,57 @@ impl BVH { /// /// Needs all the scene's shapes, plus the indices of the shapes that were updated. /// - pub fn optimize<'a, Shape: BHShape>( + #[cfg(not(feature = "serde_impls"))] + pub fn optimize<'a, Shape: BHShape + Clone>( &mut self, refit_shape_indices: impl IntoIterator + Copy, shapes: &mut [Shape], ) { for i in refit_shape_indices { self.remove_node(shapes, *i, false); + } + for i in refit_shape_indices { + self.add_node(shapes, *i); + } + } + + + /// Optimizes the `BVH` by batch-reorganizing updated nodes. + /// Based on https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH.cs + /// + /// Needs all the scene's shapes, plus the indices of the shapes that were updated. + /// + #[cfg(feature = "serde_impls")] + pub fn optimize<'a, Shape: BHShape + Clone + serde::Serialize>( + &mut self, + refit_shape_indices: impl IntoIterator + Copy, + shapes: &mut [Shape], + ) { + //let mut prev_aabb_one = self.nodes[1].child_l_aabb(); + // let prev = self.clone(); + // let prev_shapes = shapes.to_owned(); + // let refit_shapes: Vec<_> = refit_shape_indices.into_iter().map(|&x| x).collect(); + for i in refit_shape_indices { + //println!("removing shape {}", i); + self.remove_node(shapes, *i, false); + // if self.nodes[1].child_l_aabb() != prev_aabb_one { + // dbg!(&self.nodes[1].child_l_aabb()); + // prev_aabb_one = self.nodes[1].child_l_aabb(); + // dbg!(i); + // } //self.assert_tight(shapes); } - println!("removed"); + + // if !self.is_consistent(shapes) { + // let bvh = serde_json::to_string_pretty(&prev).expect("bvh to serialize"); + // let shapes_str = serde_json::to_string_pretty(&prev_shapes).expect("shapes to serialize"); + // let refits_str = serde_json::to_string_pretty(&refit_shapes).expect("shapes to serialize"); + // dbg!(bvh.len()); + // std::fs::write("badbvh.json", &bvh).expect("unable to write to file"); + // std::fs::write("badshapes.json", &shapes_str).expect("unable to write to file"); + // std::fs::write("badrefits.json", &refits_str).expect("unable to write to file"); + // } + self.assert_consistent(shapes); //println!("--------"); //self.pretty_print(); @@ -91,89 +132,22 @@ impl BVH { //self.pretty_print(); //println!("--------"); //self.assert_consistent(shapes); + // let prev = self.clone(); + // let prev_shapes = shapes.to_owned(); self.add_node(shapes, *i); - if !self.is_consistent(shapes) { - dbg!(i); - dbg!(&shapes[*i].aabb()); - dbg!(&shapes[*i].bh_node_index()); - self.assert_consistent(shapes); - } - } - - return; - - // `refit_node_indices` will contain the indices of the leaf nodes - // that reference the given shapes, sorted by their depth - // in increasing order. - let mut refit_node_indices: Vec<_> = { - let mut raw_indices = refit_shape_indices - .into_iter() - .map(|x| shapes[*x].bh_node_index()) - .collect::>(); - - // Sorts the Vector to have the greatest depth nodes last. - let depths = self.cache_depths(); - raw_indices.sort_by(|a, b| { - let depth_a = depths[*a]; - let depth_b = depths[*b]; - depth_a.cmp(&depth_b) - }); - - raw_indices - .iter() - .map(|x| OptimizationIndex::Refit(*x)) - .collect() - }; - - // As long as we have refit nodes left, take the list of refit nodes - // with the greatest depth (sweep nodes) and try to rotate them all. - while !refit_node_indices.is_empty() { - let mut sweep_node_indices = Vec::new(); - let max_depth = { - let last_node_index = refit_node_indices.last().unwrap(); - self.nodes[last_node_index.index()].depth(self.nodes.as_slice()) - }; - while !refit_node_indices.is_empty() { - let last_node_depth = { - let last_node_index = refit_node_indices.last().unwrap(); - self.nodes[last_node_index.index()].depth(self.nodes.as_slice()) - }; - if last_node_depth == max_depth { - sweep_node_indices.push(refit_node_indices.pop().unwrap()); - } else { - break; - } - } - - info!( - "{} sweep nodes, depth {}.", - sweep_node_indices.len(), - max_depth - ); - - // Try to find a useful tree rotation with all previously found nodes. - for sweep_node_index in sweep_node_indices { - // TODO There might be multithreading potential here - // In order to have threads working seperately without having to write on - // the nodes vector (which would lock other threads), - // write the results of a thread into a small data structure. - // Apply the changes to the nodes vector represented by the data structure - // in a quick, sequential loop after all threads finished their work. - let new_refit_node_index = match sweep_node_index { - OptimizationIndex::Refit(index) => self.update(index, shapes), - OptimizationIndex::FixAABBs(index) => self.fix_aabbs(index, shapes), - }; - - // Instead of finding a useful tree rotation, we found another node - // that we should check, so we add its index to the refit_node_indices. - if let Some(index) = new_refit_node_index { - assert!({ - let new_node_depth = self.nodes[index.index()].depth(self.nodes.as_slice()); - new_node_depth == max_depth - 1 - }); - refit_node_indices.push(index); - } - } + // if !self.is_consistent(shapes) { + // let bvh = serde_json::to_string_pretty(&prev).expect("bvh to serialize"); + // let shapes_str = serde_json::to_string_pretty(&prev_shapes).expect("shapes to serialize"); + // dbg!(bvh.len()); + // std::fs::write("badbvh.json", &bvh).expect("unable to write to file"); + // std::fs::write("badshapes.json", &shapes_str).expect("unable to write to file"); + + + // dbg!(i); + // dbg!(&shapes[*i].aabb()); + // dbg!(&shapes[*i].bh_node_index()); + // self.assert_consistent(shapes); + // } } } @@ -203,6 +177,372 @@ impl BVH { depths } + + pub fn add_node(&mut self, shapes: &mut [T], new_shape_index: usize) { + let mut i = 0; + let new_shape = &shapes[new_shape_index]; + let shape_aabb = new_shape.aabb(); + let shape_sa = shape_aabb.surface_area(); + + if self.nodes.len() == 0 { + self.nodes.push(BVHNode::Leaf { + parent_index: 0, + shape_index: new_shape_index, + }); + shapes[new_shape_index].set_bh_node_index(0); + return; + } + let mut depth = 0; + + loop { + match self.nodes[i] { + BVHNode::Node { + child_l_aabb, + child_l_index, + child_r_aabb, + child_r_index, + parent_index, + } => { + let left_expand = child_l_aabb.join(&shape_aabb); + + let right_expand = child_r_aabb.join(&shape_aabb); + + let send_left = child_r_aabb.surface_area() + left_expand.surface_area(); + let send_right = child_l_aabb.surface_area() + right_expand.surface_area(); + let merged_aabb = child_r_aabb.join(&child_l_aabb); + let merged = merged_aabb.surface_area() + shape_sa; + + // merge is more expensive only do when it's significantly better + let merge_discount = 0.3; + //dbg!(depth); + + // compared SA of the options + if merged < send_left.min(send_right) * merge_discount { + //println!("Merging left and right trees"); + // Merge left and right trees + let l_index = self.nodes.len(); + let new_left = BVHNode::Leaf { + parent_index: i, + shape_index: new_shape_index, + }; + shapes[new_shape_index].set_bh_node_index(l_index); + self.nodes.push(new_left); + + let r_index = self.nodes.len(); + let new_right = BVHNode::Node { + child_l_aabb: child_l_aabb, + child_l_index, + child_r_aabb: child_r_aabb.clone(), + child_r_index, + parent_index: i, + }; + self.nodes.push(new_right); + *self.nodes[child_r_index].parent_mut() = r_index; + *self.nodes[child_l_index].parent_mut() = r_index; + + self.nodes[i] = BVHNode::Node { + child_l_aabb: shape_aabb, + child_l_index: l_index, + child_r_aabb: merged_aabb, + child_r_index: r_index, + parent_index, + }; + //self.fix_depth(l_index, depth + 1); + //self.fix_depth(r_index, depth + 1); + return; + } else if send_left < send_right { + // send new box down left side + //println!("Sending left"); + if i == child_l_index { + panic!("broken loop"); + } + let child_l_aabb = left_expand; + self.nodes[i] = BVHNode::Node { + child_l_aabb, + child_l_index, + child_r_aabb, + child_r_index, + parent_index, + }; + i = child_l_index; + } else { + // send new box down right + //println!("Sending right"); + if i == child_r_index { + panic!("broken loop"); + } + let child_r_aabb = right_expand; + self.nodes[i] = BVHNode::Node { + child_l_aabb, + child_l_index, + child_r_aabb, + child_r_index, + parent_index, + }; + i = child_r_index; + } + } + BVHNode::Leaf { + shape_index, + parent_index, + } => { + //println!("Splitting leaf"); + // Split leaf into 2 nodes and insert the new box + let l_index = self.nodes.len(); + let new_left = BVHNode::Leaf { + parent_index: i, + shape_index: new_shape_index, + }; + shapes[new_shape_index].set_bh_node_index(l_index); + self.nodes.push(new_left); + + let child_r_aabb = shapes[shape_index].aabb(); + let child_r_index = self.nodes.len(); + let new_right = BVHNode::Leaf { + parent_index: i, + shape_index: shape_index, + }; + shapes[shape_index].set_bh_node_index(child_r_index); + self.nodes.push(new_right); + + let new_node = BVHNode::Node { + child_l_aabb: shape_aabb, + child_l_index: l_index, + child_r_aabb, + child_r_index, + parent_index, + }; + self.nodes[i] = new_node; + self.fix_aabbs_ascending(shapes, parent_index); + return; + } + } + depth += 1; + } + } + + pub fn remove_node( + &mut self, + shapes: &mut [T], + deleted_shape_index: usize, + swap_shape: bool, + ) { + if self.nodes.len() == 0 { + return; + //panic!("can't remove a node from a bvh with only one node"); + } + let bad_shape = &shapes[deleted_shape_index]; + + // to remove a node, delete it from the tree, remove the parent and replace it with the sibling + // swap the node being removed to the end of the slice and adjust the index of the node that was removed + // update the removed nodes index + // swap the shape to the end and update the node to still point at the right shape + let dead_node_index = bad_shape.bh_node_index(); + + if self.nodes.len() == 1 { + if dead_node_index == 0 { + self.nodes.clear(); + } + } else { + //println!("delete_i={}", dead_node_index); + + let dead_node = self.nodes[dead_node_index]; + + let parent_index = dead_node.parent(); + //println!("parent_i={}", parent_index); + let gp_index = self.nodes[parent_index].parent(); + //println!("{}->{}->{}", gp_index, parent_index, dead_node_index); + + let sibling_index = if self.node_is_left_child(dead_node_index) { + self.nodes[parent_index].child_r() + } else { + self.nodes[parent_index].child_l() + }; + //dbg!((gp_index, parent_index, dead_node_index, sibling_index)); + + // TODO: fix potential issue leaving empty spot in self.nodes + // the node swapped to sibling_index should probably be swapped to the end + // of the vector and the vector truncated + if parent_index == gp_index { + // We are removing one of the children of the root node + // The other child needs to become the root node + // The old root node and the dead child then have to be moved + + // println!("gp == parent {}", parent_index); + if parent_index != 0 { + panic!( + "Circular node that wasn't root parent={} node={}", + parent_index, dead_node_index + ); + } + + match self.nodes[sibling_index] { + BVHNode::Node { + child_l_index, + child_r_index, + .. + } => { + self.connect_nodes(child_l_index, parent_index, true, shapes); + self.connect_nodes(child_r_index, parent_index, false, shapes); + } + _ => { + self.nodes[0] = self.nodes[sibling_index]; + *self.nodes[0].parent_mut() = 0; + shapes[self.nodes[0].shape_index().unwrap()].set_bh_node_index(0); + } + } + + self.swap_and_remove_index(shapes, sibling_index.max(dead_node_index)); + self.swap_and_remove_index(shapes, sibling_index.min(dead_node_index)); + //println!("nodes_len {}, sib_index {}", self.nodes.len(), sibling_index); + //println!("nodes_len {}", self.nodes.len()); + } else { + + let parent_is_left = self.node_is_left_child(parent_index); + + self.connect_nodes(sibling_index, gp_index, parent_is_left, shapes); + + self.fix_aabbs_ascending(shapes, gp_index); + //let new_depth = self.nodes[sibling_index].depth() - 1; + //*self.nodes[sibling_index].depth_mut() = new_depth; + // remove node and parent + + //println!("---"); + //self.pretty_print(); + //println!("---"); + self.swap_and_remove_index(shapes, dead_node_index.max(parent_index)); + + //println!("---"); + //self.pretty_print(); + //println!("---"); + self.swap_and_remove_index(shapes, parent_index.min(dead_node_index)); + + //println!("---"); + //self.pretty_print(); + //println!("---"); + } + } + + if swap_shape { + let end_shape = shapes.len() - 1; + if deleted_shape_index < end_shape { + shapes.swap(deleted_shape_index, end_shape); + let node_index = shapes[deleted_shape_index].bh_node_index(); + match self.nodes[node_index].shape_index_mut() { + Some(index) => *index = deleted_shape_index, + _ => {} + } + } + } + } + + fn fix_aabbs_ascending(&mut self, shapes: &mut [T], node_index: usize) { + let mut index_to_fix = node_index; + while index_to_fix != 0 { + let parent = self.nodes[index_to_fix].parent(); + match self.nodes[parent] { + BVHNode::Node { + child_l_index, + child_r_index, + child_l_aabb, + child_r_aabb, + .. + } => { + //println!("checking {} l={} r={}", parent, child_l_index, child_r_index); + let l_aabb = self.nodes[child_l_index].get_node_aabb(shapes); + let r_aabb = self.nodes[child_r_index].get_node_aabb(shapes); + //println!("child_l_aabb {}", l_aabb); + //println!("child_r_aabb {}", r_aabb); + let mut stop = true; + if !l_aabb.relative_eq(&child_l_aabb, EPSILON) { + stop = false; + //println!("setting {} l = {}", parent, l_aabb); + *self.nodes[parent].child_l_aabb_mut() = l_aabb; + } + if !r_aabb.relative_eq(&child_r_aabb, EPSILON) { + stop = false; + //println!("setting {} r = {}", parent, r_aabb); + *self.nodes[parent].child_r_aabb_mut() = r_aabb; + } + if !stop { + index_to_fix = parent; + //dbg!(parent); + } else { + //dbg!(index_to_fix); + index_to_fix = 0; + } + } + _ => index_to_fix = 0, + } + } + } + + fn swap_and_remove_index(&mut self, shapes: &mut [T], node_index: usize) { + let end = self.nodes.len() - 1; + //println!("removing node {}", node_index); + if node_index != end { + self.nodes[node_index] = self.nodes[end]; + let parent_index = self.nodes[node_index].parent(); + match self.nodes[parent_index] { + BVHNode::Leaf { + parent_index, + shape_index, + } => { + // println!( + // "truncating early node_parent={} parent_index={} shape_index={}", + // node_parent, parent_index, shape_index + // ); + self.nodes.truncate(end); + return; + } + _ => {} + } + let parent = self.nodes[parent_index]; + let moved_left = parent.child_l() == end; + if !moved_left && parent.child_r() != end { + panic!("test"); + self.nodes.truncate(end); + return; + } + let ref_to_change = if moved_left { + self.nodes[parent_index].child_l_mut() + } else { + self.nodes[parent_index].child_r_mut() + }; + //println!("on {} changing {}=>{}", node_parent, ref_to_change, node_index); + *ref_to_change = node_index; + + match self.nodes[node_index] { + BVHNode::Leaf { shape_index, .. } => { + shapes[shape_index].set_bh_node_index(node_index); + } + BVHNode::Node { + child_l_index, + child_r_index, + .. + } => { + *self.nodes[child_l_index].parent_mut() = node_index; + *self.nodes[child_r_index].parent_mut() = node_index; + + //println!("{} {} {}", node_index, self.nodes[node_index].child_l_aabb(), self.nodes[node_index].child_r_aabb()); + //let correct_depth + //self.fix_depth(child_l_index, ) + } + } + } + self.nodes.truncate(end); + } + + + + + + + + + + + /// This method is called for each node which has been modified and needs to be updated. /// If the specified node is a grandparent, then try to optimize the `BVH` by rotating its /// children. @@ -487,6 +827,7 @@ impl BVH { shapes, ); } + /* /// Updates the depth of a node, and sets the depth of its descendants accordingly. fn update_depth_recursively(&mut self, node_index: usize, new_depth: u32) { @@ -518,7 +859,7 @@ impl BVH { fn node_is_left_child(&self, node_index: usize) -> bool { // Get the index of the parent. let node_parent_index = self.nodes[node_index].parent(); - // Get the index of te left child of the parent. + // Get the index of the left child of the parent. let child_l_index = self.nodes[node_parent_index].child_l(); child_l_index == node_index } @@ -530,8 +871,11 @@ impl BVH { left_child: bool, shapes: &[Shape], ) { + if child_index == parent_index { + return + } let child_aabb = self.nodes[child_index].get_node_aabb(shapes); - info!("\tConnecting: {} < {}.", child_index, parent_index); + //info!("\tConnecting: {} < {}.", child_index, parent_index); // Set parent's child and child_aabb; and get its depth. let _ = { match self.nodes[parent_index] { @@ -567,7 +911,7 @@ mod tests { use crate::bounding_hierarchy::BHShape; use crate::bvh::{BVHNode, BVH}; use crate::testbase::{ - build_some_bh, create_n_cubes, default_bounds, randomly_transform_scene, UnitBox, + build_some_bh, create_n_cubes, default_bounds, randomly_transform_scene, UnitBox, Triangle, }; use crate::Point3; use crate::EPSILON; @@ -921,7 +1265,7 @@ mod tests { .join(&shapes[3].aabb()); assert!(nodes[0] .child_r_aabb() - .relative_eq(&right_subtree_aabb, EPSILON)); + .relative_eq(right_subtree_aabb, EPSILON)); assert!(nodes[2] .child_r_aabb() @@ -937,8 +1281,9 @@ mod tests { #[test] /// Test optimizing `BVH` after randomizing 50% of the shapes. fn test_optimize_bvh_12k_75p() { + let c = 1000; let bounds = default_bounds(); - let mut triangles = create_n_cubes(1_000, &bounds); + let mut triangles = create_n_cubes(c, &bounds); let mut bvh = BVH::build(&mut triangles); @@ -950,8 +1295,7 @@ mod tests { // match the tree entries. let mut seed = 0; - let updated = randomly_transform_scene(&mut triangles, 9_000, &bounds, None, &mut seed); - let updated: Vec = updated.into_iter().collect(); + let updated = randomly_transform_scene(&mut triangles, c * 9, &bounds, None, &mut seed); assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); // After fixing the `AABB` consistency should be restored. @@ -961,10 +1305,94 @@ mod tests { bvh.assert_tight(&triangles); } + #[test] + /// Test optimizing `BVH` after randomizing 100% of the shapes. + fn test_optimize_bvh_12k_100p() { + let c = 1000; + let bounds = default_bounds(); + let mut triangles = create_n_cubes(c, &bounds); + + let mut bvh = BVH::build(&mut triangles); + + // The initial BVH should be consistent. + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + + // After moving triangles, the BVH should be inconsistent, because the shape `AABB`s do not + // match the tree entries. + let mut seed = 0; + + let updated = randomly_transform_scene(&mut triangles, c * 12, &bounds, None, &mut seed); + assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); + + // After fixing the `AABB` consistency should be restored. + println!("optimize"); + bvh.optimize(&updated, &mut triangles); + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + } + + #[test] + /// Test optimizing `BVH` after randomizing 100% of the shapes. + fn test_shapes_reachable_optimize_bvh_1200_100p() { + let c = 1000; + let bounds = default_bounds(); + let mut triangles = create_n_cubes(c, &bounds); + + let mut bvh = BVH::build(&mut triangles); + + // The initial BVH should be consistent. + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + + // After moving triangles, the BVH should be inconsistent, because the shape `AABB`s do not + // match the tree entries. + let mut seed = 0; + + let updated = randomly_transform_scene(&mut triangles, c * 12, &bounds, None, &mut seed); + assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); + + // After fixing the `AABB` consistency should be restored. + println!("optimize"); + bvh.optimize(&updated, &mut triangles); + bvh.assert_consistent(&triangles); + bvh.assert_tight(&triangles); + bvh.assert_reachable(&triangles); + } + + #[test] + #[cfg(feature = "serde_impls")] + fn test_bad_bvh() { + let bvh_str = std::fs::read_to_string("bvh_2.json").expect("unable to read file"); + let refit_str = std::fs::read_to_string("refitshapes_2.json").expect("unable to read file"); + let shapes_str = std::fs::read_to_string("shapes_2.json").expect("unable to read file"); + + let mut bvh: BVH = serde_json::from_str(&bvh_str).expect("to parse"); + dbg!(&bvh.nodes[1]); + let mut shapes: Vec = serde_json::from_str(&shapes_str).expect("to parse"); + let refit_shapes: Vec = serde_json::from_str(&refit_str).expect("to parse"); + for (i, shape) in shapes.iter().enumerate() { + let bh_index = shape.bh_node_index(); + let node = bvh.nodes[bh_index]; + let parent = bvh.nodes[node.parent()]; + let bh_aabb = if bvh.node_is_left_child(bh_index) { + parent.child_l_aabb() + } else { + parent.child_r_aabb() + }; + if refit_shapes.contains(&i) { + assert!(!bh_aabb.relative_eq(&shape.aabb(), EPSILON)) + } else { + assert!(bh_aabb.relative_eq(&shape.aabb(), EPSILON)) + } + } + bvh.optimize(&refit_shapes, &mut shapes); + } + #[test] fn test_optimize_bvh_12_75p() { let bounds = default_bounds(); - let mut triangles = create_n_cubes(1, &bounds); + let mut triangles = create_n_cubes(10, &bounds); println!("triangles={}", triangles.len()); let mut bvh = BVH::build(&mut triangles); @@ -977,7 +1405,7 @@ mod tests { // match the tree entries. let mut seed = 0; - let updated = randomly_transform_scene(&mut triangles, 9, &bounds, None, &mut seed); + let updated = randomly_transform_scene(&mut triangles, 90, &bounds, None, &mut seed); assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); println!("triangles={}", triangles.len()); //bvh.pretty_print(); @@ -1089,25 +1517,25 @@ mod bench { /// `BVH`. Iterate this procedure `iterations` times. Afterwards benchmark the performance /// of intersecting this scene/`BVH`. fn intersect_scene_after_optimize( - mut triangles: &mut Vec, + triangles: &mut Vec, bounds: &AABB, percent: Real, max_offset: Option, iterations: usize, b: &mut ::test::Bencher, ) { - let mut bvh = BVH::build(&mut triangles); + let mut bvh = BVH::build(triangles); let num_move = (triangles.len() as Real * percent) as usize; let mut seed = 0; for _ in 0..iterations { let updated = - randomly_transform_scene(&mut triangles, num_move, &bounds, max_offset, &mut seed); + randomly_transform_scene(triangles, num_move, &bounds, max_offset, &mut seed); let updated: Vec = updated.into_iter().collect(); - bvh.optimize(&updated, &mut triangles); + bvh.optimize(&updated, triangles); } - intersect_bh(&bvh, &triangles, &bounds, b); + intersect_bh(&bvh, triangles, bounds, b); } #[bench] @@ -1150,7 +1578,7 @@ mod bench { /// scene/`BVH`. Used to compare optimizing with rebuilding. For reference see /// `intersect_scene_after_optimize`. fn intersect_scene_with_rebuild( - mut triangles: &mut Vec, + triangles: &mut Vec, bounds: &AABB, percent: Real, max_offset: Option, @@ -1160,11 +1588,11 @@ mod bench { let num_move = (triangles.len() as Real * percent) as usize; let mut seed = 0; for _ in 0..iterations { - randomly_transform_scene(&mut triangles, num_move, &bounds, max_offset, &mut seed); + randomly_transform_scene(triangles, num_move, bounds, max_offset, &mut seed); } - let bvh = BVH::build(&mut triangles); - intersect_bh(&bvh, &triangles, &bounds, b); + let bvh = BVH::build(triangles); + intersect_bh(&bvh, triangles, bounds, b); } #[bench] diff --git a/src/flat_bvh.rs b/src/flat_bvh.rs index dfe7cfe..c1b9340 100644 --- a/src/flat_bvh.rs +++ b/src/flat_bvh.rs @@ -166,6 +166,7 @@ impl BVH { /// use bvh::bvh::BVH; /// use bvh::{Point3, Vector3}; /// use bvh::ray::Ray; + /// use bvh::Real; /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, @@ -204,7 +205,7 @@ impl BVH { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f64, i as f64, i as f64); + /// # let position = Point3::new(i as Real, i as Real, i as Real); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -228,7 +229,7 @@ impl BVH { /// /// let mut shapes = create_bhshapes(); /// let bvh = BVH::build(&mut shapes); - /// let custom_flat_bvh = bvh.flatten_custom(&custom_constructor); + /// let custom_flat_bvh = bvh.flatten_custom(&shapes, &custom_constructor); /// ``` pub fn flatten_custom( &self, @@ -254,6 +255,7 @@ impl BVH { /// use bvh::bvh::BVH; /// use bvh::{Point3, Vector3}; /// use bvh::ray::Ray; + /// use bvh::Real; /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, @@ -292,7 +294,7 @@ impl BVH { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f64, i as f64, i as f64); + /// # let position = Point3::new(i as Real, i as Real, i as Real); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -300,7 +302,7 @@ impl BVH { /// /// let mut shapes = create_bhshapes(); /// let bvh = BVH::build(&mut shapes); - /// let flat_bvh = bvh.flatten(); + /// let flat_bvh = bvh.flatten(&shapes); /// ``` pub fn flatten(&self, shapes: &[T]) -> FlatBVH { self.flatten_custom(shapes, &|aabb, entry, exit, shape| FlatNode { @@ -335,6 +337,7 @@ impl BoundingHierarchy for FlatBVH { /// use bvh::flat_bvh::FlatBVH; /// use bvh::{Point3, Vector3}; /// use bvh::ray::Ray; + /// use bvh::Real; /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, @@ -373,7 +376,7 @@ impl BoundingHierarchy for FlatBVH { /// # fn create_bhshapes() -> Vec { /// # let mut shapes = Vec::new(); /// # for i in 0..1000 { - /// # let position = Point3::new(i as f64, i as f64, i as f64); + /// # let position = Point3::new(i as Real, i as Real, i as Real); /// # shapes.push(UnitBox::new(i, position)); /// # } /// # shapes @@ -477,37 +480,37 @@ mod bench { } #[bench] /// Benchmark the construction of a `FlatBVH` with 1,200 triangles. - fn bench_build_1200_triangles_flat_bvh(mut b: &mut ::test::Bencher) { - build_1200_triangles_bh::(&mut b); + fn bench_build_1200_triangles_flat_bvh(b: &mut ::test::Bencher) { + build_1200_triangles_bh::(b); } #[bench] /// Benchmark the construction of a `FlatBVH` with 12,000 triangles. - fn bench_build_12k_triangles_flat_bvh(mut b: &mut ::test::Bencher) { - build_12k_triangles_bh::(&mut b); + fn bench_build_12k_triangles_flat_bvh(b: &mut ::test::Bencher) { + build_12k_triangles_bh::(b); } #[bench] /// Benchmark the construction of a `FlatBVH` with 120,000 triangles. - fn bench_build_120k_triangles_flat_bvh(mut b: &mut ::test::Bencher) { - build_120k_triangles_bh::(&mut b); + fn bench_build_120k_triangles_flat_bvh(b: &mut ::test::Bencher) { + build_120k_triangles_bh::(b); } #[bench] /// Benchmark intersecting 1,200 triangles using the recursive `FlatBVH`. - fn bench_intersect_1200_triangles_flat_bvh(mut b: &mut ::test::Bencher) { - intersect_1200_triangles_bh::(&mut b); + fn bench_intersect_1200_triangles_flat_bvh(b: &mut ::test::Bencher) { + intersect_1200_triangles_bh::(b); } #[bench] /// Benchmark intersecting 12,000 triangles using the recursive `FlatBVH`. - fn bench_intersect_12k_triangles_flat_bvh(mut b: &mut ::test::Bencher) { - intersect_12k_triangles_bh::(&mut b); + fn bench_intersect_12k_triangles_flat_bvh(b: &mut ::test::Bencher) { + intersect_12k_triangles_bh::(b); } #[bench] /// Benchmark intersecting 120,000 triangles using the recursive `FlatBVH`. - fn bench_intersect_120k_triangles_flat_bvh(mut b: &mut ::test::Bencher) { - intersect_120k_triangles_bh::(&mut b); + fn bench_intersect_120k_triangles_flat_bvh(b: &mut ::test::Bencher) { + intersect_120k_triangles_bh::(b); } } diff --git a/src/lib.rs b/src/lib.rs index 5407a67..0d64cac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ //! use bvh::bvh::BVH; //! use bvh::{Point3, Vector3}; //! use bvh::ray::Ray; +//! use bvh::Real; //! //! let origin = Point3::new(0.0,0.0,0.0); //! let direction = Vector3::new(1.0,0.0,0.0); @@ -27,7 +28,7 @@ //! //! struct Sphere { //! position: Point3, -//! radius: f64, +//! radius: Real, //! node_index: usize, //! } //! @@ -52,8 +53,8 @@ //! //! let mut spheres = Vec::new(); //! for i in 0..1000u32 { -//! let position = Point3::new(i as f64, i as f64, i as f64); -//! let radius = (i % 10) as f64 + 1.0; +//! let position = Point3::new(i as Real, i as Real, i as Real); +//! let radius = (i % 10) as Real + 1.0; //! spheres.push(Sphere { //! position: position, //! radius: radius, @@ -65,6 +66,10 @@ //! let hit_sphere_aabbs = bvh.traverse(&ray, &spheres); //! ``` //! +//! ## Features +//! +//! - `serde_impls` (default **disabled**) - adds `Serialize` and `Deserialize` implementations for some types +//! #[cfg(all(feature = "bench", test))] extern crate test; @@ -127,10 +132,8 @@ mod testbase; use aabb::{Bounded, AABB}; use bounding_hierarchy::BHShape; -use bvh::BVH; +use crate::bvh::BVH; use num::{FromPrimitive, Integer}; -use obj::raw::object::Polygon; -use obj::*; #[derive(Debug)] struct Sphere { @@ -196,72 +199,72 @@ impl BHShape for Triangle { } } -impl FromRawVertex for Triangle { - fn process( - vertices: Vec<(f32, f32, f32, f32)>, - _: Vec<(f32, f32, f32)>, - _: Vec<(f32, f32, f32)>, - polygons: Vec, - ) -> ObjResult<(Vec, Vec)> { - // Convert the vertices to `Point3`s. - let points = vertices - .into_iter() - .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) - .collect::>(); - - // Estimate for the number of triangles, assuming that each polygon is a triangle. - let mut triangles = Vec::with_capacity(polygons.len()); - { - let mut push_triangle = |indices: &Vec| { - let mut indices_iter = indices.iter(); - let anchor = points[*indices_iter.next().unwrap()]; - let mut second = points[*indices_iter.next().unwrap()]; - for third_index in indices_iter { - let third = points[*third_index]; - triangles.push(Triangle::new(anchor, second, third)); - second = third; - } - }; - - // Iterate over the polygons and populate the `Triangle`s vector. - for polygon in polygons.into_iter() { - match polygon { - Polygon::P(ref vec) => push_triangle(vec), - Polygon::PT(ref vec) | Polygon::PN(ref vec) => { - push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) - } - Polygon::PTN(ref vec) => { - push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) - } - } - } - } - Ok((triangles, Vec::new())) - } -} - -pub fn load_sponza_scene() -> (Vec, AABB) { - use std::fs::File; - use std::io::BufReader; - - let file_input = - BufReader::new(File::open("media/sponza.obj").expect("Failed to open .obj file.")); - let sponza_obj: Obj = load_obj(file_input).expect("Failed to decode .obj file data."); - let triangles = sponza_obj.vertices; - - let mut bounds = AABB::empty(); - for triangle in &triangles { - bounds.join_mut(&triangle.aabb()); - } - - (triangles, bounds) -} - -pub fn main() { - let (mut triangles, _bounds) = load_sponza_scene(); - let mut bvh = BVH::build(triangles.as_mut_slice()); - - for _i in 0..10 { - bvh.rebuild(triangles.as_mut_slice()); - } -} +// impl FromRawVertex for Triangle { +// fn process( +// vertices: Vec<(f32, f32, f32, f32)>, +// _: Vec<(f32, f32, f32)>, +// _: Vec<(f32, f32, f32)>, +// polygons: Vec, +// ) -> ObjResult<(Vec, Vec)> { +// // Convert the vertices to `Point3`s. +// let points = vertices +// .into_iter() +// .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) +// .collect::>(); + +// // Estimate for the number of triangles, assuming that each polygon is a triangle. +// let mut triangles = Vec::with_capacity(polygons.len()); +// { +// let mut push_triangle = |indices: &Vec| { +// let mut indices_iter = indices.iter(); +// let anchor = points[*indices_iter.next().unwrap()]; +// let mut second = points[*indices_iter.next().unwrap()]; +// for third_index in indices_iter { +// let third = points[*third_index]; +// triangles.push(Triangle::new(anchor, second, third)); +// second = third; +// } +// }; + +// // Iterate over the polygons and populate the `Triangle`s vector. +// for polygon in polygons.into_iter() { +// match polygon { +// Polygon::P(ref vec) => push_triangle(vec), +// Polygon::PT(ref vec) | Polygon::PN(ref vec) => { +// push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) +// } +// Polygon::PTN(ref vec) => { +// push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) +// } +// } +// } +// } +// Ok((triangles, Vec::new())) +// } +// } + +// pub fn load_sponza_scene() -> (Vec, AABB) { +// use std::fs::File; +// use std::io::BufReader; + +// let file_input = +// BufReader::new(File::open("media/sponza.obj").expect("Failed to open .obj file.")); +// let sponza_obj: Obj = load_obj(file_input).expect("Failed to decode .obj file data."); +// let triangles = sponza_obj.vertices; + +// let mut bounds = AABB::empty(); +// for triangle in &triangles { +// bounds.join_mut(&triangle.aabb()); +// } + +// (triangles, bounds) +// } + +// pub fn main() { +// let (mut triangles, _bounds) = load_sponza_scene(); +// let mut bvh = BVH::build(triangles.as_mut_slice()); + +// for _i in 0..10 { +// bvh.rebuild(triangles.as_mut_slice()); +// } +// } diff --git a/src/ray.rs b/src/ray.rs index 954c468..3906263 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -72,6 +72,7 @@ impl IntersectionTest for Ray { /// use bvh::aabb::AABB; /// use bvh::ray::Ray; /// use bvh::{Point3,Vector3}; + /// use bvh::bounding_hierarchy::IntersectionTest; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); diff --git a/src/testbase.rs b/src/testbase.rs index ade92d7..ca34a58 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -12,6 +12,7 @@ use proptest::prelude::*; use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::SeedableRng; +use serde::{Serialize, Deserialize}; use crate::aabb::{Bounded, AABB}; use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; @@ -49,6 +50,7 @@ pub fn tuple_to_vector(tpl: &TupleVec) -> Vector3 { /// Define some `Bounded` structure. #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] pub struct UnitBox { pub id: i32, pub pos: Point3, @@ -164,6 +166,7 @@ pub fn traverse_some_bh() { /// A triangle struct. Instance of a more complex `Bounded` primitive. #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] pub struct Triangle { pub a: Point3, pub b: Point3, @@ -406,7 +409,7 @@ pub fn randomly_transform_scene( let mut indices: Vec = (0..triangles.len()).collect(); let mut seed_array = [0u8; 32]; for i in 0..seed_array.len() { - let bytes: [u8; 8] = unsafe { transmute(seed.to_be()) }; + let bytes: [u8; 8] = seed.to_be_bytes(); seed_array[i] = bytes[i % 8]; } let mut rng: StdRng = SeedableRng::from_seed(Default::default()); @@ -486,7 +489,7 @@ pub fn build_120k_triangles_bh(b: &mut ::test::Bencher) { pub fn intersect_list(triangles: &[Triangle], bounds: &AABB, b: &mut ::test::Bencher) { let mut seed = 0; b.iter(|| { - let ray = create_ray(&mut seed, &bounds); + let ray = create_ray(&mut seed, bounds); // Iterate over the list of triangles. for triangle in triangles { @@ -520,7 +523,7 @@ pub fn intersect_list_aabb(triangles: &[Triangle], bounds: &AABB, b: &mut ::test let mut seed = 0; b.iter(|| { - let ray = create_ray(&mut seed, &bounds); + let ray = create_ray(&mut seed, bounds); // Iterate over the list of triangles. for triangle in triangles { diff --git a/src/utils.rs b/src/utils.rs index 4c982fd..2441526 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,8 +7,8 @@ use crate::bounding_hierarchy::BHShape; /// Drains the elements from the source `vectors`. pub fn concatenate_vectors(vectors: &mut [Vec]) -> Vec { let mut result = Vec::new(); - for mut vector in vectors.iter_mut() { - result.append(&mut vector); + for vector in vectors.iter_mut() { + result.append(vector); } result } From d72ff4eb364b92d96c963cb4e30c5fcfdb65f18d Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 8 Jan 2022 17:54:12 -0500 Subject: [PATCH 18/37] fix docs --- src/shapes.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shapes.rs b/src/shapes.rs index 4fc3f00..19b4eb6 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -100,7 +100,7 @@ impl IntersectionTest for OBB { let half_b = (aabb.max - aabb.min) * 0.5; let value = (aabb.max + aabb.min) * 0.5; let translation = self.orientation * (value - self.center); - let mat = Mat4::from_rotation_translation(self.orientation, translation); + let mat = Mat4::from_rotation_translation(self.orientation, translation.into()); let vec_1 = Vector3::new( translation.x.abs(), @@ -237,19 +237,19 @@ impl IntersectionTest for OBB { } fn right(matrix: Mat4) -> Vector3 { - matrix.row(0).truncate() + matrix.row(0).truncate().into() } fn up(matrix: Mat4) -> Vector3 { - matrix.row(1).truncate() + matrix.row(1).truncate().into() } fn back(matrix: Mat4) -> Vector3 { - matrix.row(2).truncate() + matrix.row(2).truncate().into() } fn translation(matrix: Mat4) -> Vector3 { - matrix.row(3).truncate() + matrix.row(3).truncate().into() } pub fn nearest_point_on_line(p1: &Point3, dir: &Vector3, len: Real, pnt: &Point3) -> Point3 { @@ -303,7 +303,7 @@ mod tests { let max = Point3::new(1.0, 1.0, 1.0); let aabb = AABB::empty().grow(&min).grow(&max); - let ori = Quat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0), 0.785398); + let ori = Quat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0).into(), 0.785398); let extents = Vector3::new(0.5, 0.5, 0.5); let pos = Vector3::new(0.5, 2.2, 0.5); From 2d23f3914e7f0f8c84c93c548c842e3245dcc483 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sun, 9 Jan 2022 08:06:09 -0500 Subject: [PATCH 19/37] add 100p sponza intersection bench --- src/bvh/optimization.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 8ddfce1..bba8a8b 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -1688,4 +1688,9 @@ mod bench { fn bench_intersect_sponza_with_rebuild_50p(b: &mut ::test::Bencher) { intersect_sponza_with_rebuild(0.5, b); } + + #[bench] + fn bench_intersect_sponza_with_rebuild_100p(b: &mut ::test::Bencher) { + intersect_sponza_with_rebuild(1.0, b); + } } From fbc607cdd2936a36a1f5f221e7572d7115155274 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Fri, 14 Jan 2022 07:22:43 -0500 Subject: [PATCH 20/37] refactor into 32 and 64bit sub crates, optimize build using thread_locals and by computing centroid bounds during the bucket phase --- Cargo.toml | 56 +--- bvh-lib/Cargo.toml | 3 +- bvh-lib/src/lib.rs | 14 +- dynbvh-f32/Cargo.toml | 58 ++++ dynbvh-f64/Cargo.toml | 60 ++++ src/aabb.rs | 125 ++++----- src/axis.rs | 8 +- src/bounding_hierarchy.rs | 32 +-- src/bvh/bvh_impl.rs | 502 +++++++++++++++++---------------- src/bvh/iter.rs | 6 +- src/bvh/optimization.rs | 569 +------------------------------------- src/flat_bvh.rs | 42 +-- src/lib.rs | 14 +- src/ray.rs | 37 +-- src/shapes.rs | 10 +- src/testbase.rs | 6 +- src/utils.rs | 14 +- 17 files changed, 549 insertions(+), 1007 deletions(-) create mode 100644 dynbvh-f32/Cargo.toml create mode 100644 dynbvh-f64/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index d694cff..73a53ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,56 +1,2 @@ -[package] -name = "bvh" -description = "A fast BVH using SAH" -version = "0.6.0" -edition = "2018" -authors = [ - "Sven-Hendrik Haase ", - "Alexander Dmitriev " -] -readme = "README.md" -repository = "https://github.com/svenstaro/bvh" -documentation = "https://docs.rs/crate/bvh" -keywords = ["bvh", "bounding", "volume", "sah", "aabb"] -license = "MIT" - [workspace] -members = ["bvh-lib"] - -[dependencies] -approx = "0.5" -rand = "0.8" -log = "0.4.14" -num = "0.4" -glam = "0.20" -rayon = "1.5.1" -smallvec = "1.6.1" -stl_io = "0.6.0" -serde = { optional = true, version = "1", features = ["derive"] } - - -[dev-dependencies] -proptest = "1.0" -obj-rs = "0.7" -float_eq = "0.7" -criterion = "0.3" -itertools = "0.10.1" -serde = { version = "1", features = ["derive"] } -glam = { version = "0.20", features = ["serde"] } -serde_json = "1" - -[features] -default = [] -bench = [] -f64 = [] -# Unfortunately can't use "serde" as the feature name until https://github.com/rust-lang/cargo/issues/5565 lands -serde_impls = ["serde", "glam/serde"] - -[profile.release] -lto = true -codegen-units = 1 -debug = true - -[profile.bench] -lto = true -codegen-units = 1 -debug = true +members = ["bvh-lib", "dynbvh-f32", "dynbvh-f64"] diff --git a/bvh-lib/Cargo.toml b/bvh-lib/Cargo.toml index 3e911ce..5f4c156 100644 --- a/bvh-lib/Cargo.toml +++ b/bvh-lib/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bvh = { path = "../", features = ["f64"]} +dynbvh-f32 = { path = "../dynbvh-f32" } +dynbvh-f64 = { path = "../dynbvh-f64" } interoptopus = "0.13.12" interoptopus_backend_csharp = "0.13.12" flexi_logger = "0.19.3" diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index eae048c..dbc91d1 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -1,11 +1,11 @@ use std::sync::atomic::{AtomicUsize, Ordering}; -use bvh::aabb::{Bounded, AABB}; -use bvh::bounding_hierarchy::BHShape; -use bvh::bvh::BVH; -use bvh::ray::Ray; -use bvh::shapes::{Capsule, Sphere, OBB}; -use bvh::Vector3; +use dynbvh_f64::aabb::{Bounded, AABB}; +use dynbvh_f64::bounding_hierarchy::BHShape; +use dynbvh_f64::bvh::BVH; +use dynbvh_f64::ray::Ray; +use dynbvh_f64::shapes::{Capsule, Sphere, OBB}; +use dynbvh_f64::Vector3; use flexi_logger::{detailed_format, FileSpec, Logger}; use glam::DQuat; use interoptopus::lang::c::{ @@ -489,7 +489,7 @@ fn bindings_csharp() -> Result<(), Error> { Ok(()) } -#[test] +//#[test] fn gen_bindings() { bindings_csharp().unwrap(); } diff --git a/dynbvh-f32/Cargo.toml b/dynbvh-f32/Cargo.toml new file mode 100644 index 0000000..f5c71af --- /dev/null +++ b/dynbvh-f32/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "dynbvh-f32" +description = "A fast dynamic BVH using SAH" +version = "0.6.0" +edition = "2018" +authors = [ + "Sven-Hendrik Haase ", + "Alexander Dmitriev " +] +readme = "README.md" +repository = "https://github.com/svenstaro/bvh" +documentation = "https://docs.rs/crate/bvh" +keywords = ["bvh", "bounding", "volume", "sah", "aabb"] +license = "MIT" + + +[lib] +name = "dynbvh_f32" +path = "../src/lib.rs" +required-features = [] + +[dependencies] +approx = "0.5" +rand = "0.8" +log = "0.4.14" +num = "0.4" +glam = "0.20" +rayon = "1.5.1" +smallvec = "1.6.1" +stl_io = "0.6.0" +serde = { optional = true, version = "1", features = ["derive"] } + + +[dev-dependencies] +proptest = "1.0" +obj-rs = "0.7" +float_eq = "0.7" +criterion = "0.3" +itertools = "0.10.1" +serde = { version = "1", features = ["derive"] } +glam = { version = "0.20", features = ["serde"] } +serde_json = "1" + +[features] +default = [] +bench = [] +# Unfortunately can't use "serde" as the feature name until https://github.com/rust-lang/cargo/issues/5565 lands +serde_impls = ["serde", "glam/serde"] + +[profile.release] +lto = true +codegen-units = 1 +debug = true + +[profile.bench] +lto = true +codegen-units = 1 +debug = true diff --git a/dynbvh-f64/Cargo.toml b/dynbvh-f64/Cargo.toml new file mode 100644 index 0000000..59ef2c9 --- /dev/null +++ b/dynbvh-f64/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "dynbvh-f64" +description = "A fast dynamic BVH using SAH" +version = "0.6.0" +edition = "2018" +authors = [ + "Sven-Hendrik Haase ", + "Alexander Dmitriev " +] +readme = "README.md" +repository = "https://github.com/svenstaro/bvh" +documentation = "https://docs.rs/crate/bvh" +keywords = ["bvh", "bounding", "volume", "sah", "aabb"] +license = "MIT" + + +[lib] +name = "dynbvh_f64" +path = "../src/lib.rs" +required-features = ["f64"] +doctest = false + +[dependencies] +approx = "0.5" +rand = "0.8" +log = "0.4.14" +num = "0.4" +glam = "0.20" +rayon = "1.5.1" +smallvec = "1.6.1" +stl_io = "0.6.0" +serde = { optional = true, version = "1", features = ["derive"] } + + +[dev-dependencies] +proptest = "1.0" +obj-rs = "0.7" +float_eq = "0.7" +criterion = "0.3" +itertools = "0.10.1" +serde = { version = "1", features = ["derive"] } +glam = { version = "0.20", features = ["serde"] } +serde_json = "1" + +[features] +default = ["f64"] +bench = [] +f64 = [] +# Unfortunately can't use "serde" as the feature name until https://github.com/rust-lang/cargo/issues/5565 lands +serde_impls = ["serde", "glam/serde"] + +[profile.release] +lto = true +codegen-units = 1 +debug = true + +[profile.bench] +lto = true +codegen-units = 1 +debug = true diff --git a/src/aabb.rs b/src/aabb.rs index f9cc374..28d82b4 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -1,6 +1,6 @@ //! Axis Aligned Bounding Boxes. -use crate::bounding_hierarchy::IntersectionTest; +use crate::bounding_hierarchy::IntersectionAABB; use std::fmt; use std::ops::Index; @@ -35,8 +35,8 @@ pub trait Bounded { /// /// # Examples /// ``` - /// use bvh::aabb::{AABB, Bounded}; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::{AABB, Bounded}; + /// use dynbvh_f32::Point3; /// /// struct Something; /// @@ -65,8 +65,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0,-1.0,-1.0), Point3::new(1.0,1.0,1.0)); /// assert_eq!(aabb.min.x, -1.0); @@ -83,9 +83,8 @@ impl AABB { /// /// # Examples /// ``` - /// # extern crate bvh; /// # extern crate rand; - /// use bvh::aabb::AABB; + /// use dynbvh_f32::aabb::AABB; /// /// # fn main() { /// let aabb = AABB::empty(); @@ -116,8 +115,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_inside = Point3::new(0.125, -0.25, 0.5); @@ -144,9 +143,9 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::EPSILON; - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::EPSILON; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_barely_outside = Point3::new(1.000_000_1, -1.000_000_1, 1.000_000_001); @@ -173,9 +172,9 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::EPSILON; - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::EPSILON; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_barely_outside = Point3::new(1.000_000_1, 1.000_000_1, 1.000_000_1); @@ -196,9 +195,9 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::EPSILON; - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::EPSILON; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_barely_outside_min = Point3::new(-1.000_000_1, -1.000_000_1, -1.000_000_1); @@ -223,8 +222,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let aabb1 = AABB::with_bounds(Point3::new(-101.0, 0.0, 0.0), Point3::new(-100.0, 1.0, 1.0)); /// let aabb2 = AABB::with_bounds(Point3::new(100.0, 0.0, 0.0), Point3::new(101.0, 1.0, 1.0)); @@ -268,8 +267,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::{Point3, Vector3}; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::{Point3, Vector3}; /// /// let size = Vector3::new(1.0, 1.0, 1.0); /// let aabb_pos = Point3::new(-101.0, 0.0, 0.0); @@ -317,8 +316,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let point1 = Point3::new(0.0, 0.0, 0.0); /// let point2 = Point3::new(1.0, 1.0, 1.0); @@ -357,8 +356,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let point1 = Point3::new(0.0, 0.0, 0.0); /// let point2 = Point3::new(1.0, 1.0, 1.0); @@ -397,8 +396,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::{AABB, Bounded}; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::{AABB, Bounded}; + /// use dynbvh_f32::Point3; /// /// struct Something; /// @@ -429,8 +428,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0,-1.0,-1.0), Point3::new(1.0,1.0,1.0)); /// let size = aabb.size(); @@ -447,8 +446,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let min = Point3::new(41.0,41.0,41.0); /// let max = Point3::new(43.0,43.0,43.0); @@ -470,8 +469,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let empty_aabb = AABB::empty(); /// assert!(empty_aabb.is_empty()); @@ -493,8 +492,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let min = Point3::new(41.0,41.0,41.0); /// let max = Point3::new(43.0,43.0,43.0); @@ -515,8 +514,8 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::Point3; /// /// let min = Point3::new(41.0,41.0,41.0); /// let max = Point3::new(43.0,43.0,43.0); @@ -537,9 +536,9 @@ impl AABB { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::axis::Axis; - /// use bvh::Point3; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::axis::Axis; + /// use dynbvh_f32::Point3; /// /// let min = Point3::new(-100.0,0.0,0.0); /// let max = Point3::new(100.0,0.0,0.0); @@ -567,7 +566,7 @@ impl AABB { } } -impl IntersectionTest for AABB { +impl IntersectionAABB for AABB { fn intersects_aabb(&self, aabb: &AABB) -> bool { !(self.max.x < aabb.min.x) && !(self.min.x > aabb.max.x) @@ -592,8 +591,8 @@ impl Default for AABB { /// /// # Examples /// ``` -/// use bvh::aabb::AABB; -/// use bvh::Point3; +/// use dynbvh_f32::aabb::AABB; +/// use dynbvh_f32::Point3; /// /// let min = Point3::new(3.0,4.0,5.0); /// let max = Point3::new(123.0,123.0,123.0); @@ -621,8 +620,8 @@ impl Index for AABB { /// /// # Examples /// ``` -/// use bvh::aabb::{AABB, Bounded}; -/// use bvh::Point3; +/// use dynbvh_f32::aabb::{AABB, Bounded}; +/// use dynbvh_f32::Point3; /// /// let point_a = Point3::new(3.0,4.0,5.0); /// let point_b = Point3::new(17.0,18.0,19.0); @@ -647,8 +646,8 @@ impl Bounded for AABB { /// /// # Examples /// ``` -/// use bvh::aabb::{AABB, Bounded}; -/// use bvh::Point3; +/// use dynbvh_f32::aabb::{AABB, Bounded}; +/// use dynbvh_f32::Point3; /// /// let point = Point3::new(3.0,4.0,5.0); /// @@ -668,14 +667,15 @@ impl Bounded for Point3 { #[cfg(test)] mod tests { use crate::aabb::{Bounded, AABB}; - use crate::bounding_hierarchy::IntersectionTest; + use crate::bounding_hierarchy::IntersectionAABB; use crate::testbase::{tuple_to_point, tuple_to_vector, tuplevec_large_strategy, TupleVec}; - use crate::EPSILON; use crate::{Point3, Vector3}; + use crate::{Real, EPSILON}; use float_eq::assert_float_eq; use proptest::prelude::*; + #[cfg(not(miri))] proptest! { // Test whether an empty `AABB` does not contains anything. #[test] @@ -709,16 +709,16 @@ mod tests { // Define two points which will be the corners of the `AABB` let p1 = tuple_to_point(&a); let p2 = tuple_to_point(&b); - - // Span the `AABB` let aabb = AABB::empty().grow(&p1).join_bounded(&p2); - // Its center should be inside the `AABB` - if !aabb.contains(&aabb.center()) { - dbg!(aabb.center()); + if aabb.center().abs().max_element() != Real::INFINITY { + // Its center should be inside the `AABB` + if !aabb.contains(&aabb.center()) { + dbg!(aabb.center()); + } + assert!(aabb.contains(&aabb.center())); } - assert!(aabb.contains(&aabb.center())); } // Test AABB intersection method. @@ -730,7 +730,7 @@ mod tests { // Span the `AABB` let aabb = AABB::empty().grow(&p1).join_bounded(&p2); - if aabb.size().abs().min_element() > EPSILON { + if aabb.size().abs().min_element() > EPSILON && aabb.size().abs().max_element() != Real::INFINITY { // Get its size and center let size = aabb.size(); let size_half = size / 2.0; @@ -836,11 +836,12 @@ mod tests { let pos = tuple_to_point(&pos); let size_vec = Vector3::new(size, size, size); let aabb = AABB::with_bounds(pos, pos + size_vec); - - // Check its surface area - let area_a = aabb.surface_area(); - let area_b = 6.0 * size * size; - assert_float_eq!(area_a, area_b, rmax <= EPSILON); + if aabb.max.abs().max_element() - size < 10e20 { + // Check its surface area + let area_a = aabb.surface_area(); + let area_b = 6.0 * size * size; + assert_float_eq!(area_a, area_b, rmax <= EPSILON); + } } // Test whether the volume of a nonempty AABB is always positive. diff --git a/src/axis.rs b/src/axis.rs index c390f96..c5efb56 100644 --- a/src/axis.rs +++ b/src/axis.rs @@ -12,7 +12,7 @@ struct MyType(T); /// /// # Examples /// ``` -/// use bvh::axis::Axis; +/// use dynbvh_f32::axis::Axis; /// /// let mut position = [1.0, 0.5, 42.0]; /// position[Axis::Y] *= 4.0; @@ -23,10 +23,9 @@ struct MyType(T); /// [`Point3`] and [`Vector3`] are also indexable using `Axis`. /// /// ``` -/// extern crate bvh; /// -/// use bvh::axis::Axis; -/// use bvh::Point3; +/// use dynbvh_f32::axis::Axis; +/// use dynbvh_f32::Point3; /// /// # fn main() { /// let mut position: Point3 = Point3::new(1.0, 2.0, 3.0); @@ -131,6 +130,7 @@ mod test { use crate::{axis::Axis, Real}; use proptest::prelude::*; + #[cfg(not(miri))] proptest! { // Test whether accessing arrays by index is the same as accessing them by `Axis`. #[test] diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index c9275ba..e21cbf7 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -31,11 +31,11 @@ pub trait BoundingHierarchy { /// # Examples /// /// ``` - /// use bvh::aabb::{AABB, Bounded}; - /// use bvh::bounding_hierarchy::BoundingHierarchy; - /// use bvh::{Point3, Vector3}; - /// use bvh::Real; - /// # use bvh::bounding_hierarchy::BHShape; + /// use dynbvh_f32::aabb::{AABB, Bounded}; + /// use dynbvh_f32::bounding_hierarchy::BoundingHierarchy; + /// use dynbvh_f32::{Point3, Vector3}; + /// use dynbvh_f32::Real; + /// # use dynbvh_f32::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -82,13 +82,13 @@ pub trait BoundingHierarchy { /// let mut shapes = create_bhshapes(); /// // Construct a normal `BVH`. /// { - /// use bvh::bvh::BVH; + /// use dynbvh_f32::bvh::BVH; /// let bvh = BVH::build(&mut shapes); /// } /// /// // Or construct a `FlatBVH`. /// { - /// use bvh::flat_bvh::FlatBVH; + /// use dynbvh_f32::flat_bvh::FlatBVH; /// let bvh = FlatBVH::build(&mut shapes); /// } /// ``` @@ -103,13 +103,13 @@ pub trait BoundingHierarchy { /// # Examples /// /// ``` - /// use bvh::aabb::{AABB, Bounded}; - /// use bvh::bounding_hierarchy::BoundingHierarchy; - /// use bvh::bvh::BVH; - /// use bvh::{Point3, Vector3}; - /// use bvh::ray::Ray; - /// use bvh::Real; - /// # use bvh::bounding_hierarchy::BHShape; + /// use dynbvh_f32::aabb::{AABB, Bounded}; + /// use dynbvh_f32::bounding_hierarchy::BoundingHierarchy; + /// use dynbvh_f32::bvh::BVH; + /// use dynbvh_f32::{Point3, Vector3}; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::Real; + /// # use dynbvh_f32::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -167,7 +167,7 @@ pub trait BoundingHierarchy { /// fn traverse<'a, Shape: BHShape>( &'a self, - test: &impl IntersectionTest, + test: &impl IntersectionAABB, shapes: &'a [Shape], ) -> Vec<&Shape>; @@ -178,6 +178,6 @@ pub trait BoundingHierarchy { fn pretty_print(&self) {} } -pub trait IntersectionTest { +pub trait IntersectionAABB { fn intersects_aabb(&self, aabb: &AABB) -> bool; } diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index c066730..7320d11 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -6,16 +6,24 @@ use crate::aabb::{Bounded, AABB}; use crate::axis::Axis; -use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; +use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionAABB}; use crate::bvh::iter::BVHTraverseIterator; use crate::utils::{joint_aabb_of_shapes, Bucket}; use crate::EPSILON; use crate::{Point3, Real}; use rayon::prelude::*; use smallvec::SmallVec; +use std::borrow::BorrowMut; +use std::cell::RefCell; use std::iter::repeat; use std::slice; +const NUM_BUCKETS: usize = 6; + +thread_local! { + pub static BUCKETS: RefCell<[Vec; NUM_BUCKETS]> = RefCell::new(Default::default()); +} + /// The [`BVHNode`] enum that describes a node in a [`BVH`]. /// It's either a leaf node and references a shape (by holding its index) /// or a regular node that has two child nodes. @@ -257,49 +265,9 @@ impl BVHNode { parent_index: usize, depth: u32, node_index: usize, + aabb_bounds: AABB, + centroid_bounds: AABB, ) -> usize { - //println!("Building node={}", node_index); - - // Helper function to accumulate the AABB joint and the centroids AABB - 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 use_parallel_hull = false; - - let mut parallel_recurse = false; - if nodes.len() > 64 { - parallel_recurse = true; - }; - - let convex_hull = if use_parallel_hull { - indices - .par_iter() - .fold( - || (AABB::default(), AABB::default()), - |convex_hull, i| grow_convex_hull(convex_hull, &shapes[*i].aabb()), - ) - .reduce( - || (AABB::default(), AABB::default()), - |a, b| (a.0.join(&b.0), a.1.join(&b.1)), - ) - } else { - let mut convex_hull = Default::default(); - - for index in indices.iter() { - convex_hull = grow_convex_hull(convex_hull, &shapes[*index].aabb()); - } - convex_hull - }; - - let (aabb_bounds, centroid_bounds) = convex_hull; - // If there is only one element left, don't split anymore if indices.len() == 1 { let shape_index = indices[0]; @@ -311,6 +279,10 @@ impl BVHNode { shapes[shape_index].set_bh_node_index(node_index); return node_index; } + let mut parallel_recurse = false; + if indices.len() > 64 { + parallel_recurse = true; + } // From here on we handle the recursive case. This dummy is required, because the children // must know their parent, and it's easier to update one parent node than the child nodes. @@ -321,141 +293,160 @@ impl BVHNode { let split_axis_size = centroid_bounds.max[split_axis] - centroid_bounds.min[split_axis]; // The following `if` partitions `indices` for recursively calling `BVH::build`. - let (child_l_index, child_l_aabb, child_r_index, child_r_aabb) = - if split_axis_size < EPSILON { - // In this branch the shapes lie too close together so that splitting them in a - // sensible way is not possible. Instead we just split the list of shapes in half. - let (child_l_indices, child_r_indices) = indices.split_at_mut(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 next_nodes = &mut nodes[1..]; - let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); - let child_l_index = node_index + 1; - let child_r_index = node_index + 1 + l_nodes.len(); - // Proceed recursively. - if parallel_recurse { - // This is safe because shapes is only accessed using the indices and each index is unique - let (shapes_a, shapes_b) = unsafe { - let ptr = shapes.as_mut_ptr(); - let len = shapes.len(); - let shapes_a = slice::from_raw_parts_mut(ptr, len); - let shapes_b = slice::from_raw_parts_mut(ptr, len); - (shapes_a, shapes_b) - }; - rayon::join( - || { - BVHNode::build( - shapes_a, - child_l_indices, - l_nodes, - node_index, - depth + 1, - child_l_index, - ) - }, - || { - BVHNode::build( - shapes_b, - child_r_indices, - r_nodes, - node_index, - depth + 1, - child_r_index, - ) - }, - ); - } else { - BVHNode::build( - shapes, - child_l_indices, - l_nodes, - node_index, - depth + 1, - child_l_index, - ); - BVHNode::build( - shapes, - child_r_indices, - r_nodes, - node_index, - depth + 1, - child_r_index, - ); - } - (child_l_index, child_l_aabb, child_r_index, child_r_aabb) + let (child_l_index, child_l_aabb, child_r_index, child_r_aabb) = if split_axis_size + < EPSILON + { + // In this branch the shapes lie too close together so that splitting them in a + // sensible way is not possible. Instead we just split the list of shapes in half. + let (child_l_indices, child_r_indices) = indices.split_at_mut(indices.len() / 2); + let (child_l_aabb, child_l_centroid) = joint_aabb_of_shapes(child_l_indices, shapes); + let (child_r_aabb, child_r_centroid) = joint_aabb_of_shapes(child_r_indices, shapes); + + let next_nodes = &mut nodes[1..]; + let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); + let child_l_index = node_index + 1; + let child_r_index = node_index + 1 + l_nodes.len(); + // Proceed recursively. + if parallel_recurse { + // This is safe because shapes is only accessed using the indices and each index is unique + let (shapes_a, shapes_b) = unsafe { + let ptr = shapes.as_mut_ptr(); + let len = shapes.len(); + let shapes_a = slice::from_raw_parts_mut(ptr, len); + let shapes_b = slice::from_raw_parts_mut(ptr, len); + (shapes_a, shapes_b) + }; + rayon::join( + || { + BVHNode::build( + shapes_a, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + child_l_aabb, + child_l_centroid, + ) + }, + || { + BVHNode::build( + shapes_b, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + child_r_aabb, + child_r_centroid, + ) + }, + ); } else { - let (child_l_aabb, child_r_aabb, child_l_indices, child_r_indices) = - BVHNode::build_buckets( - shapes, - indices, - split_axis, - split_axis_size, - ¢roid_bounds, - &aabb_bounds, - ); - - let next_nodes = &mut nodes[1..]; - let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); - - let child_l_index = node_index + 1; - let child_r_index = node_index + 1 + l_nodes.len(); - // Proceed recursively. - - if parallel_recurse { - let (shapes_a, shapes_b) = unsafe { - let ptr = shapes.as_mut_ptr(); - let len = shapes.len(); - let shapes_a = slice::from_raw_parts_mut(ptr, len); - let shapes_b = slice::from_raw_parts_mut(ptr, len); - (shapes_a, shapes_b) - }; - rayon::join( - || { - BVHNode::build( - shapes_a, - child_l_indices, - l_nodes, - node_index, - depth + 1, - child_l_index, - ) - }, - || { - BVHNode::build( - shapes_b, - child_r_indices, - r_nodes, - node_index, - depth + 1, - child_r_index, - ) - }, - ); - } else { - BVHNode::build( - shapes, - child_l_indices, - l_nodes, - node_index, - depth + 1, - child_l_index, - ); - BVHNode::build( - shapes, - child_r_indices, - r_nodes, - node_index, - depth + 1, - child_r_index, - ); - } - (child_l_index, child_l_aabb, child_r_index, child_r_aabb) - }; + BVHNode::build( + shapes, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + child_l_aabb, + child_l_centroid, + ); + BVHNode::build( + shapes, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + child_r_aabb, + child_r_centroid, + ); + } + (child_l_index, child_l_aabb, child_r_index, child_r_aabb) + } else { + let ( + (child_l_aabb, child_l_centroid, child_l_indices), + (child_r_aabb, child_r_centroid, child_r_indices), + ) = BVHNode::build_buckets( + shapes, + indices, + split_axis, + split_axis_size, + ¢roid_bounds, + &aabb_bounds, + ); + + let next_nodes = &mut nodes[1..]; + let (l_nodes, r_nodes) = next_nodes.split_at_mut(child_l_indices.len() * 2 - 1); + + let child_l_index = node_index + 1; + let child_r_index = node_index + 1 + l_nodes.len(); + // Proceed recursively. + + if parallel_recurse { + let (shapes_a, shapes_b) = unsafe { + let ptr = shapes.as_mut_ptr(); + let len = shapes.len(); + let shapes_a = slice::from_raw_parts_mut(ptr, len); + let shapes_b = slice::from_raw_parts_mut(ptr, len); + (shapes_a, shapes_b) + }; + rayon::join( + || { + BVHNode::build( + shapes_a, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + child_l_aabb, + child_l_centroid, + ) + }, + || { + BVHNode::build( + shapes_b, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + child_r_aabb, + child_r_centroid, + ) + }, + ); + } else { + BVHNode::build( + shapes, + child_l_indices, + l_nodes, + node_index, + depth + 1, + child_l_index, + child_l_aabb, + child_l_centroid, + ); + BVHNode::build( + shapes, + child_r_indices, + r_nodes, + node_index, + depth + 1, + child_r_index, + child_r_aabb, + child_r_centroid, + ); + } + (child_l_index, child_l_aabb, child_r_index, child_r_aabb) + }; // Construct the actual data structure and replace the dummy node. - assert!(!child_l_aabb.is_empty()); - assert!(!child_r_aabb.is_empty()); + //assert!(!child_l_aabb.is_empty()); + //assert!(!child_r_aabb.is_empty()); nodes[0] = BVHNode::Node { parent_index, child_l_aabb, @@ -474,77 +465,91 @@ impl BVHNode { split_axis_size: Real, centroid_bounds: &AABB, aabb_bounds: &AABB, - ) -> (AABB, AABB, &'a mut [usize], &'a mut [usize]) { + ) -> ((AABB, AABB, &'a mut [usize]), (AABB, AABB, &'a mut [usize])) { // Create six `Bucket`s, and six index assignment vector. - const NUM_BUCKETS: usize = 6; - let mut buckets = [Bucket::empty(); NUM_BUCKETS]; - let mut bucket_assignments: [SmallVec<[usize; 1024]>; NUM_BUCKETS] = Default::default(); - - // In this branch the `split_axis_size` is large enough to perform meaningful splits. - // We start by assigning the shapes to `Bucket`s. - for idx in indices.iter() { - let shape = &shapes[*idx]; - let shape_aabb = shape.aabb(); - let shape_center = shape_aabb.center(); - - // Get the relative position of the shape centroid `[0.0..1.0]`. - let bucket_num_relative = - (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; - - // Convert that to the actual `Bucket` number. - let bucket_num = (bucket_num_relative * (NUM_BUCKETS as Real - 0.01)) as usize; - - // Extend the selected `Bucket` and add the index to the actual bucket. - buckets[bucket_num].add_aabb(&shape_aabb); - bucket_assignments[bucket_num].push(*idx); - } + // let mut buckets = [Bucket::empty(); NUM_BUCKETS]; + // let mut bucket_assignments: [SmallVec<[usize; 1024]>; NUM_BUCKETS] = Default::default(); + BUCKETS.with(move |buckets| { + let bucket_assignments = &mut *buckets.borrow_mut(); + let mut buckets = [Bucket::empty(); NUM_BUCKETS]; + buckets.fill(Bucket::empty()); + for b in bucket_assignments.iter_mut() { + b.clear(); + } + + // In this branch the `split_axis_size` is large enough to perform meaningful splits. + // We start by assigning the shapes to `Bucket`s. + for idx in indices.iter() { + let shape = &shapes[*idx]; + let shape_aabb = shape.aabb(); + let shape_center = shape_aabb.center(); + + // Get the relative position of the shape centroid `[0.0..1.0]`. + let bucket_num_relative = + (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; + + // Convert that to the actual `Bucket` number. + let bucket_num = (bucket_num_relative * (NUM_BUCKETS as Real - 0.01)) as usize; - // Compute the costs for each configuration and select the best configuration. - let mut min_bucket = 0; - let mut min_cost = Real::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 Real * child_l.aabb.surface_area() - + child_r.size as Real * 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; + // Extend the selected `Bucket` and add the index to the actual bucket. + buckets[bucket_num].add_aabb(&shape_aabb); + bucket_assignments[bucket_num].push(*idx); } - } - // Join together all index buckets. - // split input indices, loop over assignments and assign - let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1); - let mut l_count = 0; - for group in l_assignments.iter() { - l_count += group.len(); - } + // Compute the costs for each configuration and select the best configuration. + let mut min_bucket = 0; + let mut min_cost = Real::INFINITY; + let mut child_l_aabb = AABB::empty(); + let mut child_l_centroid = AABB::empty(); + let mut child_r_aabb = AABB::empty(); + let mut child_r_centroid = 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 Real * child_l.aabb.surface_area() + + child_r.size as Real * 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_l_centroid = child_l.centroid; + child_r_aabb = child_r.aabb; + child_r_centroid = child_r.centroid; + } + } + // Join together all index buckets. + // split input indices, loop over assignments and assign + let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1); - let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); - let mut i = 0; - for group in l_assignments.iter() { - for x in group { - child_l_indices[i] = *x; - i += 1; + let mut l_count = 0; + for group in l_assignments.iter() { + l_count += group.len(); } - } - i = 0; - for group in r_assignments.iter() { - for x in group { - child_r_indices[i] = *x; - i += 1; + + let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); + let mut i = 0; + for group in l_assignments.iter() { + for x in group { + child_l_indices[i] = *x; + i += 1; + } + } + i = 0; + for group in r_assignments.iter() { + for x in group { + child_r_indices[i] = *x; + i += 1; + } } - } - (child_l_aabb, child_r_aabb, child_l_indices, child_r_indices) + ( + (child_l_aabb, child_l_centroid, child_l_indices), + (child_r_aabb, child_r_centroid, child_r_indices), + ) + }) } /// Traverses the [`BVH`] recursively and returns all shapes whose [`AABB`] is @@ -557,7 +562,7 @@ impl BVHNode { pub fn traverse_recursive( nodes: &[BVHNode], node_index: usize, - ray: &impl IntersectionTest, + ray: &impl IntersectionAABB, indices: &mut Vec, ) { match nodes[node_index] { @@ -611,7 +616,9 @@ impl BVH { } //println!("shapes={} nodes={}", shapes.len(), nodes.len()); let n = nodes.as_mut_slice(); - BVHNode::build(shapes, &mut indices, n, 0, 0, 0); + + let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); + BVHNode::build(shapes, &mut indices, n, 0, 0, 0, aabb, centroid); BVH { nodes } } @@ -626,7 +633,8 @@ impl BVH { self.nodes.set_len(expected_node_count); } let n = self.nodes.as_mut_slice(); - BVHNode::build(shapes, &mut indices, n, 0, 0, 0); + let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); + BVHNode::build(shapes, &mut indices, n, 0, 0, 0, aabb, centroid); } /// Traverses the [`BVH`]. @@ -637,7 +645,7 @@ impl BVH { /// pub fn traverse<'a, Shape: Bounded>( &'a self, - ray: &impl IntersectionTest, + ray: &impl IntersectionAABB, shapes: &'a [Shape], ) -> Vec<&Shape> { let mut indices = Vec::new(); @@ -656,7 +664,7 @@ impl BVH { /// pub fn traverse_iterator<'a, Shape: Bounded>( &'a self, - test: &'a impl IntersectionTest, + test: &'a impl IntersectionAABB, shapes: &'a [Shape], ) -> BVHTraverseIterator { BVHTraverseIterator::new(self, test, shapes) @@ -903,7 +911,11 @@ impl BVH { shapes, ); } - BVHNode::Leaf { shape_index, parent_index, ..} => { + BVHNode::Leaf { + shape_index, + parent_index, + .. + } => { let shape_aabb = shapes[shape_index].aabb(); assert!( if parent != 0 { @@ -1039,7 +1051,7 @@ impl BoundingHierarchy for BVH { fn traverse<'a, Shape: Bounded>( &'a self, - ray: &impl IntersectionTest, + ray: &impl IntersectionAABB, shapes: &'a [Shape], ) -> Vec<&Shape> { self.traverse(ray, shapes) diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index 1536167..be42d18 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -1,7 +1,7 @@ use smallvec::SmallVec; use crate::aabb::Bounded; -use crate::bounding_hierarchy::IntersectionTest; +use crate::bounding_hierarchy::IntersectionAABB; use crate::bvh::{BVHNode, BVH}; /// Iterator to traverse a [`BVH`] without memory allocations @@ -10,7 +10,7 @@ pub struct BVHTraverseIterator<'a, Shape: Bounded> { /// Reference to the BVH to traverse bvh: &'a BVH, /// Reference to the input ray - test: &'a dyn IntersectionTest, + test: &'a dyn IntersectionAABB, /// Reference to the input shapes array shapes: &'a [Shape], /// Traversal stack. Allocates if exceeds depth of 64 @@ -23,7 +23,7 @@ pub struct BVHTraverseIterator<'a, Shape: Bounded> { impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { /// Creates a new `BVHTraverseIterator` - pub fn new(bvh: &'a BVH, test: &'a impl IntersectionTest, shapes: &'a [Shape]) -> Self { + pub fn new(bvh: &'a BVH, test: &'a impl IntersectionAABB, shapes: &'a [Shape]) -> Self { BVHTraverseIterator { bvh, test, diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index bba8a8b..66c1177 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -7,64 +7,13 @@ //! use crate::bounding_hierarchy::BHShape; -use crate::{bvh::*, EPSILON}; use crate::{aabb::AABB, Real}; +use crate::{bvh::*, EPSILON}; use std::fmt::Debug; use log::info; use rand::{thread_rng, Rng}; -// TODO Consider: Instead of getting the scene's shapes passed, let leaf nodes store an AABB -// that is updated from the outside, perhaps by passing not only the indices of the changed -// shapes, but also their new AABBs into optimize(). -// TODO Consider: Stop updating AABBs upwards the tree once an AABB didn't get changed. - -#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] -#[allow(clippy::upper_case_acronyms)] -enum OptimizationIndex { - Refit(usize), - FixAABBs(usize), -} - -impl OptimizationIndex { - fn index(&self) -> usize { - match *self { - OptimizationIndex::Refit(index) | OptimizationIndex::FixAABBs(index) => index, - } - } -} - -#[derive(Debug, Copy, Clone)] -struct NodeData { - index: usize, - aabb: AABB, -} - -impl BVHNode { - // Get the grandchildren's NodeData. - fn get_children_node_data(&self) -> Option<(NodeData, NodeData)> { - match *self { - BVHNode::Node { - child_l_index, - child_l_aabb, - child_r_index, - child_r_aabb, - .. - } => Some(( - NodeData { - index: child_l_index, - aabb: child_l_aabb, - }, - NodeData { - index: child_r_index, - aabb: child_r_aabb, - }, - )), - BVHNode::Leaf { .. } => None, - } - } -} - impl BVH { /// Optimizes the `BVH` by batch-reorganizing updated nodes. /// Based on https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH.cs @@ -85,7 +34,6 @@ impl BVH { } } - /// Optimizes the `BVH` by batch-reorganizing updated nodes. /// Based on https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH.cs /// @@ -111,7 +59,7 @@ impl BVH { // } //self.assert_tight(shapes); } - + // if !self.is_consistent(shapes) { // let bvh = serde_json::to_string_pretty(&prev).expect("bvh to serialize"); // let shapes_str = serde_json::to_string_pretty(&prev_shapes).expect("shapes to serialize"); @@ -142,42 +90,14 @@ impl BVH { // std::fs::write("badbvh.json", &bvh).expect("unable to write to file"); // std::fs::write("badshapes.json", &shapes_str).expect("unable to write to file"); - // dbg!(i); // dbg!(&shapes[*i].aabb()); // dbg!(&shapes[*i].bh_node_index()); - // self.assert_consistent(shapes); + // self.assert_consistent(shapes); // } } } - fn cache_depths(&mut self) -> Vec { - let mut depths = Vec::with_capacity(self.nodes.len()); - unsafe { - depths.set_len(self.nodes.len()); - } - - let mut stack = Vec::new(); - stack.push((0 as usize, 0 as usize)); - while stack.len() > 0 { - let (i, depth) = stack.pop().unwrap(); - depths[i] = depth; - match self.nodes[i] { - BVHNode::Leaf { .. } => {} - BVHNode::Node { - child_l_index, - child_r_index, - .. - } => { - stack.push((child_r_index, depth + 1)); - stack.push((child_l_index, depth + 1)); - } - } - } - depths - } - - pub fn add_node(&mut self, shapes: &mut [T], new_shape_index: usize) { let mut i = 0; let new_shape = &shapes[new_shape_index]; @@ -192,7 +112,6 @@ impl BVH { shapes[new_shape_index].set_bh_node_index(0); return; } - let mut depth = 0; loop { match self.nodes[i] { @@ -317,7 +236,6 @@ impl BVH { return; } } - depth += 1; } } @@ -368,14 +286,14 @@ impl BVH { // The other child needs to become the root node // The old root node and the dead child then have to be moved - // println!("gp == parent {}", parent_index); + // println!("gp == parent {}", parent_index); if parent_index != 0 { panic!( "Circular node that wasn't root parent={} node={}", parent_index, dead_node_index ); } - + match self.nodes[sibling_index] { BVHNode::Node { child_l_index, @@ -397,11 +315,10 @@ impl BVH { //println!("nodes_len {}, sib_index {}", self.nodes.len(), sibling_index); //println!("nodes_len {}", self.nodes.len()); } else { - let parent_is_left = self.node_is_left_child(parent_index); self.connect_nodes(sibling_index, gp_index, parent_is_left, shapes); - + self.fix_aabbs_ascending(shapes, gp_index); //let new_depth = self.nodes[sibling_index].depth() - 1; //*self.nodes[sibling_index].depth_mut() = new_depth; @@ -484,10 +401,7 @@ impl BVH { self.nodes[node_index] = self.nodes[end]; let parent_index = self.nodes[node_index].parent(); match self.nodes[parent_index] { - BVHNode::Leaf { - parent_index, - shape_index, - } => { + BVHNode::Leaf { .. } => { // println!( // "truncating early node_parent={} parent_index={} shape_index={}", // node_parent, parent_index, shape_index @@ -500,7 +414,6 @@ impl BVH { let parent = self.nodes[parent_index]; let moved_left = parent.child_l() == end; if !moved_left && parent.child_r() != end { - panic!("test"); self.nodes.truncate(end); return; } @@ -533,329 +446,6 @@ impl BVH { self.nodes.truncate(end); } - - - - - - - - - - - /// This method is called for each node which has been modified and needs to be updated. - /// If the specified node is a grandparent, then try to optimize the `BVH` by rotating its - /// children. - fn update( - &mut self, - node_index: usize, - shapes: &[Shape], - ) -> Option { - info!(" [{}]\t", node_index); - - match self.nodes[node_index] { - BVHNode::Leaf { - parent_index, - shape_index, - .. - } => { - // The current node is a leaf. - info!( - "Leaf node. Queueing parent ({}). {}.", - parent_index, - shapes[shape_index].aabb() - ); - Some(OptimizationIndex::Refit(parent_index)) - } - BVHNode::Node { - parent_index, - child_l_index, - child_r_index, - .. - } => { - // The current node is a parent. - if let ( - &BVHNode::Leaf { - shape_index: shape_l_index, - .. - }, - &BVHNode::Leaf { - shape_index: shape_r_index, - .. - }, - ) = (&self.nodes[child_l_index], &self.nodes[child_r_index]) - { - // The current node is a final parent. Update its `AABB`s, because at least - // one of its children was updated and queue its parent for refitting. - if let BVHNode::Node { - ref mut child_l_aabb, - ref mut child_r_aabb, - .. - } = self.nodes[node_index] - { - *child_l_aabb = shapes[shape_l_index].aabb(); - *child_r_aabb = shapes[shape_r_index].aabb(); - info!("Setting {} from {}", child_l_aabb, child_l_index); - info!("\tand {} from {}.", child_r_aabb, child_r_index); - return Some(OptimizationIndex::Refit(parent_index)); - } - unreachable!(); - } - - // The current node is a grandparent and can be optimized by rotating. - self.try_rotate(node_index, shapes) - } - } - } - - fn find_better_rotation( - &self, - child_l_index: usize, - child_l_aabb: &AABB, - child_r_index: usize, - child_r_aabb: &AABB, - ) -> Option<(usize, usize)> { - // Get indices and `AABB`s of all grandchildren. - let left_children_nodes = self.nodes[child_l_index].get_children_node_data(); - let right_children_nodes = self.nodes[child_r_index].get_children_node_data(); - - // Contains the surface area that would result from applying the currently favored rotation. - // The rotation with the smallest surface area will be applied in the end. - // The value is calculated by `child_l_aabb.surface_area() + child_r_aabb.surface_area()`. - let mut best_surface_area = child_l_aabb.surface_area() + child_r_aabb.surface_area(); - - // Stores the rotation that would result in the surface area `best_surface_area`, - // thus being the favored rotation that will be executed after considering all rotations. - let mut best_rotation: Option<(usize, usize)> = None; - { - let mut consider_rotation = |new_rotation: (usize, usize), surface_area: Real| { - if surface_area < best_surface_area { - best_surface_area = surface_area; - best_rotation = Some(new_rotation); - } - }; - - // Child to grandchild rotations - if let Some((child_rl, child_rr)) = right_children_nodes { - let surface_area_l_rl = - child_rl.aabb.surface_area() + child_l_aabb.join(&child_rr.aabb).surface_area(); - consider_rotation((child_l_index, child_rl.index), surface_area_l_rl); - let surface_area_l_rr = - child_rr.aabb.surface_area() + child_l_aabb.join(&child_rl.aabb).surface_area(); - consider_rotation((child_l_index, child_rr.index), surface_area_l_rr); - } - if let Some((child_ll, child_lr)) = left_children_nodes { - let surface_area_r_ll = - child_ll.aabb.surface_area() + child_r_aabb.join(&child_lr.aabb).surface_area(); - consider_rotation((child_r_index, child_ll.index), surface_area_r_ll); - let surface_area_r_lr = - child_lr.aabb.surface_area() + child_r_aabb.join(&child_ll.aabb).surface_area(); - consider_rotation((child_r_index, child_lr.index), surface_area_r_lr); - - // Grandchild to grandchild rotations - if let Some((child_rl, child_rr)) = right_children_nodes { - let surface_area_ll_rl = child_rl.aabb.join(&child_lr.aabb).surface_area() - + child_ll.aabb.join(&child_rr.aabb).surface_area(); - consider_rotation((child_ll.index, child_rl.index), surface_area_ll_rl); - let surface_area_ll_rr = child_ll.aabb.join(&child_rl.aabb).surface_area() - + child_lr.aabb.join(&child_rr.aabb).surface_area(); - consider_rotation((child_ll.index, child_rr.index), surface_area_ll_rr); - } - } - } - best_rotation - } - - /// Checks if there is a way to rotate a child and a grandchild (or two grandchildren) of - /// the given node (specified by `node_index`) that would improve the `BVH`. - /// If there is, the best rotation found is performed. - /// - /// # Preconditions - /// - /// This function requires that the subtree at `node_index` has correct `AABB`s. - /// - /// # Returns - /// - /// `Some(index_of_node)` if a new node was found that should be used for optimization. - /// - fn try_rotate( - &mut self, - node_index: usize, - shapes: &[Shape], - ) -> Option { - let (parent_index, child_l_index, child_r_index) = if let BVHNode::Node { - parent_index, - child_l_index, - child_r_index, - .. - } = self.nodes[node_index] - { - (parent_index, child_l_index, child_r_index) - } else { - unreachable!() - }; - - // Recalculate `AABB`s for the children since at least one of them changed. Don't update - // the `AABB`s in the node yet because they're still subject to change during potential - // upcoming rotations. - let child_l_aabb = self.nodes[child_l_index].get_node_aabb(shapes); - let child_r_aabb = self.nodes[child_r_index].get_node_aabb(shapes); - - let best_rotation = - self.find_better_rotation(child_l_index, &child_l_aabb, child_r_index, &child_r_aabb); - - if let Some((rotation_node_a, rotation_node_b)) = best_rotation { - self.rotate(rotation_node_a, rotation_node_b, shapes); - - // Update the children's children `AABB`s and the children `AABB`s of node. - self.fix_children_and_own_aabbs(node_index, shapes); - - // Return parent node's index for upcoming refitting, - // since this node just changed its `AABB`. - if node_index != 0 { - Some(OptimizationIndex::Refit(parent_index)) - } else { - None - } - } else { - info!(" No useful rotation."); - // Set the correct children `AABB`s, which have been computed earlier. - *self.nodes[node_index].child_l_aabb_mut() = child_l_aabb; - *self.nodes[node_index].child_r_aabb_mut() = child_r_aabb; - - // Only execute the following block, if `node_index` does not reference the root node. - if node_index != 0 { - // Even with no rotation being useful for this node, a parent node's rotation - // could be beneficial, so queue the parent *sometimes*. For reference see: - // https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH_Node.cs#L307 - // TODO Evaluate whether this is a smart thing to do. - let mut rng = thread_rng(); - if rng.gen_bool(0.01) { - Some(OptimizationIndex::Refit(parent_index)) - } else { - // Otherwise, we still have to fix the parent's AABBs - Some(OptimizationIndex::FixAABBs(parent_index)) - } - } else { - None - } - } - } - - /// Sets child_l_aabb and child_r_aabb of a BVHNode::Node to match its children, - /// right after updating the children themselves. Not recursive. - fn fix_children_and_own_aabbs(&mut self, node_index: usize, shapes: &[Shape]) { - let (child_l_index, child_r_index) = if let BVHNode::Node { - child_l_index, - child_r_index, - .. - } = self.nodes[node_index] - { - (child_l_index, child_r_index) - } else { - unreachable!() - }; - - self.fix_aabbs(child_l_index, shapes); - self.fix_aabbs(child_r_index, shapes); - - *self.nodes[node_index].child_l_aabb_mut() = - self.nodes[child_l_index].get_node_aabb(shapes); - *self.nodes[node_index].child_r_aabb_mut() = - self.nodes[child_r_index].get_node_aabb(shapes); - } - - /// Updates `child_l_aabb` and `child_r_aabb` of the `BVHNode::Node` - /// with the index `node_index` from its children. - fn fix_aabbs( - &mut self, - node_index: usize, - shapes: &[Shape], - ) -> Option { - match self.nodes[node_index] { - BVHNode::Node { - parent_index, - child_l_index, - child_r_index, - .. - } => { - *self.nodes[node_index].child_l_aabb_mut() = - self.nodes[child_l_index].get_node_aabb(shapes); - *self.nodes[node_index].child_r_aabb_mut() = - self.nodes[child_r_index].get_node_aabb(shapes); - - if node_index > 0 { - Some(OptimizationIndex::FixAABBs(parent_index)) - } else { - None - } - } - // Don't do anything if the node is a leaf. - _ => None, - } - } - - /// Switch two nodes by rewiring the involved indices (not by moving them in the nodes slice). - /// Also updates the AABBs of the parents. - fn rotate( - &mut self, - node_a_index: usize, - node_b_index: usize, - shapes: &[Shape], - ) { - info!(" ROTATING {} and {}", node_a_index, node_b_index); - - // Get parent indices - let node_a_parent_index = self.nodes[node_a_index].parent(); - let node_b_parent_index = self.nodes[node_b_index].parent(); - - // Get info about the nodes being a left or right child - let node_a_is_left_child = self.node_is_left_child(node_a_index); - let node_b_is_left_child = self.node_is_left_child(node_b_index); - - // Perform the switch - self.connect_nodes( - node_a_index, - node_b_parent_index, - node_b_is_left_child, - shapes, - ); - self.connect_nodes( - node_b_index, - node_a_parent_index, - node_a_is_left_child, - shapes, - ); - } - - /* - /// Updates the depth of a node, and sets the depth of its descendants accordingly. - fn update_depth_recursively(&mut self, node_index: usize, new_depth: u32) { - let children = { - let node = &mut self.nodes[node_index]; - match *node { - BVHNode::Node { - ref mut depth, - child_l_index, - child_r_index, - .. - } => { - *depth = new_depth; - Some((child_l_index, child_r_index)) - } - BVHNode::Leaf { ref mut depth, .. } => { - *depth = new_depth; - None - } - } - }; - if let Some((child_l_index, child_r_index)) = children { - self.update_depth_recursively(child_l_index, new_depth + 1); - self.update_depth_recursively(child_r_index, new_depth + 1); - } - } - */ - fn node_is_left_child(&self, node_index: usize) -> bool { // Get the index of the parent. let node_parent_index = self.nodes[node_index].parent(); @@ -872,7 +462,7 @@ impl BVH { shapes: &[Shape], ) { if child_index == parent_index { - return + return; } let child_aabb = self.nodes[child_index].get_node_aabb(shapes); //info!("\tConnecting: {} < {}.", child_index, parent_index); @@ -911,7 +501,7 @@ mod tests { use crate::bounding_hierarchy::BHShape; use crate::bvh::{BVHNode, BVH}; use crate::testbase::{ - build_some_bh, create_n_cubes, default_bounds, randomly_transform_scene, UnitBox, Triangle, + build_some_bh, create_n_cubes, default_bounds, randomly_transform_scene, Triangle, UnitBox, }; use crate::Point3; use crate::EPSILON; @@ -1141,143 +731,6 @@ mod tests { .relative_eq(&shapes[1].aabb(), EPSILON)); } - #[test] - fn test_rotate_grandchildren() { - let (shapes, mut bvh) = create_predictable_bvh(); - - // Switch two nodes. - bvh.rotate(3, 5, &shapes); - - // Check if the resulting tree is as expected. - let BVH { nodes } = bvh; - - assert_eq!(nodes[0].parent(), 0); - assert_eq!(nodes[0].child_l(), 1); - assert_eq!(nodes[0].child_r(), 2); - - assert_eq!(nodes[1].parent(), 0); - assert_eq!(nodes[1].child_l(), 5); - assert_eq!(nodes[1].child_r(), 4); - - assert_eq!(nodes[2].parent(), 0); - assert_eq!(nodes[2].child_l(), 3); - assert_eq!(nodes[2].child_r(), 6); - - assert_eq!(nodes[3].parent(), 2); - assert_eq!(nodes[4].parent(), 1); - assert_eq!(nodes[5].parent(), 1); - assert_eq!(nodes[6].parent(), 2); - - assert!(nodes[1] - .child_l_aabb() - .relative_eq(&shapes[2].aabb(), EPSILON)); - assert!(nodes[1] - .child_r_aabb() - .relative_eq(&shapes[1].aabb(), EPSILON)); - assert!(nodes[2] - .child_l_aabb() - .relative_eq(&shapes[0].aabb(), EPSILON)); - assert!(nodes[2] - .child_r_aabb() - .relative_eq(&shapes[3].aabb(), EPSILON)); - } - - #[test] - fn test_rotate_child_grandchild() { - let (shapes, mut bvh) = create_predictable_bvh(); - - // Switch two nodes. - bvh.rotate(1, 5, &shapes); - - // Check if the resulting tree is as expected. - let BVH { nodes } = bvh; - - assert_eq!(nodes[0].parent(), 0); - assert_eq!(nodes[0].child_l(), 5); - assert_eq!(nodes[0].child_r(), 2); - - assert_eq!(nodes[1].parent(), 2); - assert_eq!(nodes[1].child_l(), 3); - assert_eq!(nodes[1].child_r(), 4); - - assert_eq!(nodes[2].parent(), 0); - assert_eq!(nodes[2].child_l(), 1); - assert_eq!(nodes[2].child_r(), 6); - - assert_eq!(nodes[3].parent(), 1); - assert_eq!(nodes[4].parent(), 1); - assert_eq!(nodes[5].parent(), 0); - assert_eq!(nodes[6].parent(), 2); - - assert!(nodes[0] - .child_l_aabb() - .relative_eq(&shapes[2].aabb(), EPSILON)); - assert!(nodes[2] - .child_r_aabb() - .relative_eq(&shapes[3].aabb(), EPSILON)); - assert!(nodes[1] - .child_l_aabb() - .relative_eq(&shapes[0].aabb(), EPSILON)); - assert!(nodes[1] - .child_r_aabb() - .relative_eq(&shapes[1].aabb(), EPSILON)); - } - - #[test] - fn test_try_rotate_child_grandchild() { - let (mut shapes, mut bvh) = create_predictable_bvh(); - - // Move the second shape. - shapes[2].pos = Point3::new(-40.0, 0.0, 0.0); - - // Try to rotate node 2 because node 5 changed. - bvh.try_rotate(2, &shapes); - - // Try to rotate node 0 because rotating node 2 should not have yielded a result. - bvh.try_rotate(0, &shapes); - - // Check if the resulting tree is as expected. - let BVH { nodes } = bvh; - - assert_eq!(nodes[0].parent(), 0); - assert_eq!(nodes[0].child_l(), 5); - assert_eq!(nodes[0].child_r(), 2); - - assert_eq!(nodes[1].parent(), 2); - assert_eq!(nodes[1].child_l(), 3); - assert_eq!(nodes[1].child_r(), 4); - - assert_eq!(nodes[2].parent(), 0); - assert_eq!(nodes[2].child_l(), 1); - assert_eq!(nodes[2].child_r(), 6); - - assert_eq!(nodes[3].parent(), 1); - assert_eq!(nodes[4].parent(), 1); - assert_eq!(nodes[5].parent(), 0); - assert_eq!(nodes[6].parent(), 2); - - assert!(nodes[0] - .child_l_aabb() - .relative_eq(&shapes[2].aabb(), EPSILON)); - let right_subtree_aabb = &shapes[0] - .aabb() - .join(&shapes[1].aabb()) - .join(&shapes[3].aabb()); - assert!(nodes[0] - .child_r_aabb() - .relative_eq(right_subtree_aabb, EPSILON)); - - assert!(nodes[2] - .child_r_aabb() - .relative_eq(&shapes[3].aabb(), EPSILON)); - assert!(nodes[1] - .child_l_aabb() - .relative_eq(&shapes[0].aabb(), EPSILON)); - assert!(nodes[1] - .child_r_aabb() - .relative_eq(&shapes[1].aabb(), EPSILON)); - } - #[test] /// Test optimizing `BVH` after randomizing 50% of the shapes. fn test_optimize_bvh_12k_75p() { @@ -1417,7 +870,6 @@ mod tests { bvh.assert_tight(&triangles); } - #[test] fn test_optimizing_nodes() { let bounds = default_bounds(); @@ -1434,12 +886,11 @@ mod tests { // match the tree entries. let mut seed = 0; - for _ in 0..1000 { let updated = randomly_transform_scene(&mut triangles, 1, &bounds, None, &mut seed); assert!(!bvh.is_consistent(&triangles), "BVH is consistent."); //bvh.pretty_print(); - + // After fixing the `AABB` consistency should be restored. bvh.optimize(&updated, &mut triangles); //bvh.pretty_print(); diff --git a/src/flat_bvh.rs b/src/flat_bvh.rs index c1b9340..8bbacf7 100644 --- a/src/flat_bvh.rs +++ b/src/flat_bvh.rs @@ -1,7 +1,7 @@ //! This module exports methods to flatten the `BVH` and traverse it iteratively. use crate::aabb::{Bounded, AABB}; -use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionTest}; +use crate::bounding_hierarchy::{BHShape, BoundingHierarchy, IntersectionAABB}; use crate::bvh::{BVHNode, BVH}; /// A structure of a node of a flat [`BVH`]. The structure of the nodes allows for an @@ -162,12 +162,12 @@ impl BVH { /// # Example /// /// ``` - /// use bvh::aabb::{AABB, Bounded}; - /// use bvh::bvh::BVH; - /// use bvh::{Point3, Vector3}; - /// use bvh::ray::Ray; - /// use bvh::Real; - /// # use bvh::bounding_hierarchy::BHShape; + /// use dynbvh_f32::aabb::{AABB, Bounded}; + /// use dynbvh_f32::bvh::BVH; + /// use dynbvh_f32::{Point3, Vector3}; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::Real; + /// # use dynbvh_f32::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -251,12 +251,12 @@ impl BVH { /// # Example /// /// ``` - /// use bvh::aabb::{AABB, Bounded}; - /// use bvh::bvh::BVH; - /// use bvh::{Point3, Vector3}; - /// use bvh::ray::Ray; - /// use bvh::Real; - /// # use bvh::bounding_hierarchy::BHShape; + /// use dynbvh_f32::aabb::{AABB, Bounded}; + /// use dynbvh_f32::bvh::BVH; + /// use dynbvh_f32::{Point3, Vector3}; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::Real; + /// # use dynbvh_f32::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -332,13 +332,13 @@ impl BoundingHierarchy for FlatBVH { /// # Examples /// /// ``` - /// use bvh::aabb::{AABB, Bounded}; - /// use bvh::bounding_hierarchy::BoundingHierarchy; - /// use bvh::flat_bvh::FlatBVH; - /// use bvh::{Point3, Vector3}; - /// use bvh::ray::Ray; - /// use bvh::Real; - /// # use bvh::bounding_hierarchy::BHShape; + /// use dynbvh_f32::aabb::{AABB, Bounded}; + /// use dynbvh_f32::bounding_hierarchy::BoundingHierarchy; + /// use dynbvh_f32::flat_bvh::FlatBVH; + /// use dynbvh_f32::{Point3, Vector3}; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::Real; + /// # use dynbvh_f32::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -389,7 +389,7 @@ impl BoundingHierarchy for FlatBVH { /// let flat_bvh = FlatBVH::build(&mut shapes); /// let hit_shapes = flat_bvh.traverse(&ray, &shapes); /// ``` - fn traverse<'a, T: Bounded>(&'a self, ray: &impl IntersectionTest, shapes: &'a [T]) -> Vec<&T> { + fn traverse<'a, T: Bounded>(&'a self, ray: &impl IntersectionAABB, shapes: &'a [T]) -> Vec<&T> { let mut hit_shapes = Vec::new(); let mut index = 0; diff --git a/src/lib.rs b/src/lib.rs index 0d64cac..e386003 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,12 +15,12 @@ //! ## Example //! //! ``` -//! use bvh::aabb::{AABB, Bounded}; -//! use bvh::bounding_hierarchy::{BoundingHierarchy, BHShape}; -//! use bvh::bvh::BVH; -//! use bvh::{Point3, Vector3}; -//! use bvh::ray::Ray; -//! use bvh::Real; +//! use dynbvh_f32::aabb::{AABB, Bounded}; +//! use dynbvh_f32::bounding_hierarchy::{BoundingHierarchy, BHShape}; +//! use dynbvh_f32::bvh::BVH; +//! use dynbvh_f32::{Point3, Vector3}; +//! use dynbvh_f32::ray::Ray; +//! use dynbvh_f32::Real; //! //! let origin = Point3::new(0.0,0.0,0.0); //! let direction = Vector3::new(1.0,0.0,0.0); @@ -130,9 +130,9 @@ mod utils; #[cfg(test)] mod testbase; +use crate::bvh::BVH; use aabb::{Bounded, AABB}; use bounding_hierarchy::BHShape; -use crate::bvh::BVH; use num::{FromPrimitive, Integer}; #[derive(Debug)] diff --git a/src/ray.rs b/src/ray.rs index 3906263..c2b39b8 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -2,7 +2,7 @@ //! for axis aligned bounding boxes and triangles. use crate::aabb::AABB; -use crate::bounding_hierarchy::IntersectionTest; +use crate::bounding_hierarchy::IntersectionAABB; use crate::{Point3, Vector3}; use crate::{Real, EPSILON}; @@ -63,16 +63,16 @@ impl Intersection { } } -impl IntersectionTest for Ray { +impl IntersectionAABB for Ray { /// Tests the intersection of a [`Ray`] with an [`AABB`] using the optimized algorithm /// from [this paper](http://www.cs.utah.edu/~awilliam/box/box.pdf). /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::ray::Ray; - /// use bvh::{Point3,Vector3}; - /// use bvh::bounding_hierarchy::IntersectionTest; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::{Point3,Vector3}; + /// use dynbvh_f32::bounding_hierarchy::IntersectionAABB; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); @@ -139,8 +139,8 @@ impl Ray { /// /// # Examples /// ``` - /// use bvh::ray::Ray; - /// use bvh::{Point3,Vector3}; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::{Point3,Vector3}; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); @@ -168,9 +168,9 @@ impl Ray { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::ray::Ray; - /// use bvh::{Point3,Vector3}; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::{Point3,Vector3}; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); @@ -214,9 +214,9 @@ impl Ray { /// /// # Examples /// ``` - /// use bvh::aabb::AABB; - /// use bvh::ray::Ray; - /// use bvh::{Point3,Vector3}; + /// use dynbvh_f32::aabb::AABB; + /// use dynbvh_f32::ray::Ray; + /// use dynbvh_f32::{Point3,Vector3}; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); @@ -313,6 +313,10 @@ impl Ray { Intersection::new(Real::INFINITY, u, v) } } + + pub fn at(&self, dist: Real) -> Vector3 { + self.origin + (self.direction * dist) + } } #[cfg(test)] @@ -321,7 +325,7 @@ mod tests { use std::cmp; use crate::aabb::AABB; - use crate::bounding_hierarchy::IntersectionTest; + use crate::bounding_hierarchy::IntersectionAABB; use crate::ray::Ray; use crate::testbase::{tuple_to_point, tuplevec_small_strategy, TupleVec}; use crate::EPSILON; @@ -343,6 +347,7 @@ mod tests { (ray, aabb) } + #[cfg(not(miri))] proptest! { // Test whether a `Ray` which points at the center of an `AABB` intersects it. // Uses the optimized algorithm. @@ -483,7 +488,7 @@ mod tests { #[cfg(all(feature = "bench", test))] mod bench { - use crate::bounding_hierarchy::IntersectionTest; + use crate::bounding_hierarchy::IntersectionAABB; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; diff --git a/src/shapes.rs b/src/shapes.rs index 19b4eb6..f09d966 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -1,5 +1,5 @@ use crate::aabb::AABB; -use crate::bounding_hierarchy::IntersectionTest; +use crate::bounding_hierarchy::IntersectionAABB; use crate::{Mat4, Point3, Quat, Real, Vector3}; pub struct Sphere { @@ -13,7 +13,7 @@ impl Sphere { } } -impl IntersectionTest for Sphere { +impl IntersectionAABB for Sphere { fn intersects_aabb(&self, aabb: &AABB) -> bool { let vec = aabb.closest_point(self.center); vec.distance_squared(self.center) < self.radius * self.radius @@ -43,7 +43,7 @@ impl Capsule { } } -impl IntersectionTest for Capsule { +impl IntersectionAABB for Capsule { fn intersects_aabb(&self, aabb: &AABB) -> bool { /* // Use Distance from closest point @@ -94,7 +94,7 @@ pub struct OBB { pub center: Vector3, } -impl IntersectionTest for OBB { +impl IntersectionAABB for OBB { fn intersects_aabb(&self, aabb: &AABB) -> bool { let half_a = self.extents; let half_b = (aabb.max - aabb.min) * 0.5; @@ -261,7 +261,7 @@ pub fn nearest_point_on_line(p1: &Point3, dir: &Vector3, len: Real, pnt: &Point3 #[cfg(test)] mod tests { use crate::aabb::AABB; - use crate::bounding_hierarchy::IntersectionTest; + use crate::bounding_hierarchy::IntersectionAABB; use crate::shapes::{Capsule, OBB}; use crate::{Point3, Quat, Real, Vector3}; diff --git a/src/testbase.rs b/src/testbase.rs index ca34a58..14be75d 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -12,7 +12,7 @@ use proptest::prelude::*; use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::SeedableRng; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use crate::aabb::{Bounded, AABB}; use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; @@ -383,7 +383,7 @@ pub fn load_sponza_scene() -> (Vec, AABB) { use std::io::BufReader; let file_input = - BufReader::new(File::open("media/sponza.obj").expect("Failed to open .obj file.")); + BufReader::new(File::open("../media/sponza.obj").expect("Failed to open .obj file.")); let sponza_obj: Obj = load_obj(file_input).expect("Failed to decode .obj file data."); let triangles = sponza_obj.vertices; @@ -519,7 +519,7 @@ fn bench_intersect_sponza_list(b: &mut ::test::Bencher) { /// structures. #[cfg(feature = "bench")] pub fn intersect_list_aabb(triangles: &[Triangle], bounds: &AABB, b: &mut ::test::Bencher) { - use crate::bounding_hierarchy::IntersectionTest; + use crate::bounding_hierarchy::IntersectionAABB; let mut seed = 0; b.iter(|| { diff --git a/src/utils.rs b/src/utils.rs index 2441526..a2be63f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -22,6 +22,9 @@ pub struct Bucket { /// The joint `AABB` of the shapes in this `Bucket`. pub aabb: AABB, + + /// the `AABB` of the centroid of the centers of the shapes in this `Bucket` + pub centroid: AABB, } impl Bucket { @@ -30,13 +33,15 @@ impl Bucket { Bucket { size: 0, aabb: AABB::empty(), + centroid: AABB::empty(), } } /// Extend this `Bucket` by a shape with the given `AABB`. pub fn add_aabb(&mut self, aabb: &AABB) { self.size += 1; - self.aabb = self.aabb.join(aabb); + self.aabb.join_mut(aabb); + self.centroid.grow_mut(&aabb.center()); } /// Join the contents of two `Bucket`s. @@ -44,17 +49,20 @@ impl Bucket { Bucket { size: a.size + b.size, aabb: a.aabb.join(&b.aabb), + centroid: a.centroid.join(&b.centroid), } } } -pub fn joint_aabb_of_shapes(indices: &[usize], shapes: &[Shape]) -> AABB { +pub fn joint_aabb_of_shapes(indices: &[usize], shapes: &[Shape]) -> (AABB, AABB) { let mut aabb = AABB::empty(); + let mut centroid = AABB::empty(); for index in indices { let shape = &shapes[*index]; aabb.join_mut(&shape.aabb()); + centroid.grow_mut(&shape.aabb().center()); } - aabb + (aabb, centroid) } #[cfg(test)] From c52c1ca6301829e0045c7423826c7ee0957eb728 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Fri, 14 Jan 2022 07:25:41 -0500 Subject: [PATCH 21/37] running cargo-fix --- src/bvh/bvh_impl.rs | 2 +- src/bvh/optimization.rs | 8 ++++---- src/lib.rs | 4 ++-- src/testbase.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 7320d11..efe35b5 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -12,7 +12,7 @@ use crate::utils::{joint_aabb_of_shapes, Bucket}; use crate::EPSILON; use crate::{Point3, Real}; use rayon::prelude::*; -use smallvec::SmallVec; + use std::borrow::BorrowMut; use std::cell::RefCell; use std::iter::repeat; diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 66c1177..a3fab30 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -7,12 +7,12 @@ //! use crate::bounding_hierarchy::BHShape; -use crate::{aabb::AABB, Real}; + use crate::{bvh::*, EPSILON}; -use std::fmt::Debug; + use log::info; -use rand::{thread_rng, Rng}; + impl BVH { /// Optimizes the `BVH` by batch-reorganizing updated nodes. @@ -501,7 +501,7 @@ mod tests { use crate::bounding_hierarchy::BHShape; use crate::bvh::{BVHNode, BVH}; use crate::testbase::{ - build_some_bh, create_n_cubes, default_bounds, randomly_transform_scene, Triangle, UnitBox, + build_some_bh, create_n_cubes, default_bounds, randomly_transform_scene, UnitBox, }; use crate::Point3; use crate::EPSILON; diff --git a/src/lib.rs b/src/lib.rs index e386003..fb75f1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,10 +130,10 @@ mod utils; #[cfg(test)] mod testbase; -use crate::bvh::BVH; + use aabb::{Bounded, AABB}; use bounding_hierarchy::BHShape; -use num::{FromPrimitive, Integer}; + #[derive(Debug)] struct Sphere { diff --git a/src/testbase.rs b/src/testbase.rs index 14be75d..3a54bc4 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -2,7 +2,7 @@ #![cfg(test)] use std::collections::HashSet; -use std::mem::transmute; + use crate::{Point3, Real, Vector3}; use num::{FromPrimitive, Integer}; @@ -12,7 +12,7 @@ use proptest::prelude::*; use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::SeedableRng; -use serde::{Deserialize, Serialize}; + use crate::aabb::{Bounded, AABB}; use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; From f8479a6996e73d02156c9d7fd48a668cdaeeaf69 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Fri, 14 Jan 2022 07:27:13 -0500 Subject: [PATCH 22/37] remove unneeded clone on optimize --- src/bvh/optimization.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index a3fab30..8ceb5eb 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -21,7 +21,7 @@ impl BVH { /// Needs all the scene's shapes, plus the indices of the shapes that were updated. /// #[cfg(not(feature = "serde_impls"))] - pub fn optimize<'a, Shape: BHShape + Clone>( + pub fn optimize<'a, Shape: BHShape>( &mut self, refit_shape_indices: impl IntoIterator + Copy, shapes: &mut [Shape], From 468190e38d4ec4f3bd63db7062de14553f564ff9 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Fri, 14 Jan 2022 07:48:25 -0500 Subject: [PATCH 23/37] finish merging with upstream, add missing documentation --- dynbvh-f32/Cargo.toml | 3 +-- dynbvh-f64/Cargo.toml | 3 +-- src/aabb.rs | 4 ++++ src/bounding_hierarchy.rs | 8 ++++++++ src/bvh/bvh_impl.rs | 10 ++++++++-- src/bvh/optimization.rs | 6 ++++++ src/lib.rs | 4 ++++ src/ray.rs | 1 + src/shapes.rs | 35 +++++++++++++++++++++++++---------- 9 files changed, 58 insertions(+), 16 deletions(-) diff --git a/dynbvh-f32/Cargo.toml b/dynbvh-f32/Cargo.toml index f5c71af..d10ae02 100644 --- a/dynbvh-f32/Cargo.toml +++ b/dynbvh-f32/Cargo.toml @@ -22,12 +22,11 @@ required-features = [] [dependencies] approx = "0.5" rand = "0.8" -log = "0.4.14" +log = "0.4" num = "0.4" glam = "0.20" rayon = "1.5.1" smallvec = "1.6.1" -stl_io = "0.6.0" serde = { optional = true, version = "1", features = ["derive"] } diff --git a/dynbvh-f64/Cargo.toml b/dynbvh-f64/Cargo.toml index 59ef2c9..e61d5be 100644 --- a/dynbvh-f64/Cargo.toml +++ b/dynbvh-f64/Cargo.toml @@ -23,12 +23,11 @@ doctest = false [dependencies] approx = "0.5" rand = "0.8" -log = "0.4.14" +log = "0.4" num = "0.4" glam = "0.20" rayon = "1.5.1" smallvec = "1.6.1" -stl_io = "0.6.0" serde = { optional = true, version = "1", features = ["derive"] } diff --git a/src/aabb.rs b/src/aabb.rs index 28d82b4..7cf5492 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -561,6 +561,10 @@ impl AABB { } } + /// Returns the closest point inside the `AABB` to a target point + /// + /// [`AABB`]: struct.AABB.html + /// pub fn closest_point(&self, point: Point3) -> Point3 { point.clamp(self.min, self.max) } diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index e21cbf7..feca1c1 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -178,6 +178,14 @@ pub trait BoundingHierarchy { fn pretty_print(&self) {} } +/// This trait can be implemented on anything that can intersect with an `AABB` +/// Used to traverse the `BVH` +/// +/// [`AABB`]: ../aabb/struct.AABB.html +/// +/// [`BVH`]: ../bvh/struct.BVH.html +/// pub trait IntersectionAABB { + /// Returns true if there is an intersection with the given AABB fn intersects_aabb(&self, aabb: &AABB) -> bool; } diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 1963b50..afd44bf 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -21,7 +21,8 @@ use std::slice; const NUM_BUCKETS: usize = 6; thread_local! { - pub static BUCKETS: RefCell<[Vec; NUM_BUCKETS]> = RefCell::new(Default::default()); + /// Thread local for the buckets used while building to reduce allocations during build + static BUCKETS: RefCell<[Vec; NUM_BUCKETS]> = RefCell::new(Default::default()); } /// The [`BVHNode`] enum that describes a node in a [`BVH`]. @@ -622,6 +623,11 @@ impl BVH { BVH { nodes } } + + /// Rebuilds a [`BVH`] from the `shapes` slice. Reuses the existing allocated space + /// + /// [`BVH`]: struct.BVH.html + /// pub fn rebuild(&mut self, shapes: &mut [Shape]) { let mut indices = (0..shapes.len()).collect::>(); let expected_node_count = shapes.len() * 2 - 1; @@ -678,7 +684,7 @@ impl BVH { self.print_node(0); } - pub fn print_node(&self, node_index: usize) { + fn print_node(&self, node_index: usize) { let nodes = &self.nodes; match nodes[node_index] { BVHNode::Node { diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 8ceb5eb..0434179 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -98,6 +98,9 @@ impl BVH { } } + /// Adds a shape with the given index to the `BVH` + /// Significantly slower at building a `BVH` than the full build or rebuild option + /// Useful for moving a small subset of nodes around in a large `BVH` pub fn add_node(&mut self, shapes: &mut [T], new_shape_index: usize) { let mut i = 0; let new_shape = &shapes[new_shape_index]; @@ -239,6 +242,9 @@ impl BVH { } } + /// Removes a shape from the `BVH` + /// if swap_shape is true, it swaps the shape you are removing with the last shape in the shape slice + /// truncation of the data structure backing the shapes slice must be performed by the user pub fn remove_node( &mut self, shapes: &mut [T], diff --git a/src/lib.rs b/src/lib.rs index 3f23913..9e729da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,14 +167,18 @@ impl BHShape for Sphere { /// A triangle struct. Instance of a more complex `Bounded` primitive. #[derive(Debug)] pub struct Triangle { + /// First point on the triangle pub a: Point3, + /// Second point on the triangle pub b: Point3, + /// Third point on the triangle pub c: Point3, aabb: AABB, node_index: usize, } impl Triangle { + /// Creates a new triangle given a clockwise set of points pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { Triangle { a, diff --git a/src/ray.rs b/src/ray.rs index c2b39b8..0161e39 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -314,6 +314,7 @@ impl Ray { } } + /// Returns the position the front of the `Ray` is after traveling dist pub fn at(&self, dist: Real) -> Vector3 { self.origin + (self.direction * dist) } diff --git a/src/shapes.rs b/src/shapes.rs index f09d966..e2338de 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -1,13 +1,18 @@ +//! This module exports various shapes that can be used to intersect with use crate::aabb::AABB; use crate::bounding_hierarchy::IntersectionAABB; use crate::{Mat4, Point3, Quat, Real, Vector3}; +/// A representation of a Sphere pub struct Sphere { - center: Point3, - radius: Real, + /// Center of the sphere + pub center: Point3, + /// Radius of the sphere + pub radius: Real, } impl Sphere { + /// Creates a sphere centered on a given point with a radius pub fn new(center: Point3, radius: Real) -> Sphere { Sphere { center, radius } } @@ -20,15 +25,20 @@ impl IntersectionAABB for Sphere { } } +/// Representation of a capsule pub struct Capsule { - start: Point3, - radius: Real, - - dir: Vector3, - len: Real, + /// Start point of the line segment for the capsule + pub start: Point3, + /// Radius of the capsule + pub radius: Real, + /// Direction of the capsule's line segment + pub dir: Vector3, + /// Length of the capsules line segment + pub len: Real, } impl Capsule { + /// Creates a capsule given a start and end point for the center line and the radius around it pub fn new(start: Point3, end: Point3, radius: Real) -> Capsule { let line = end - start; let dir = line.normalize(); @@ -88,9 +98,13 @@ impl IntersectionAABB for Capsule { } } +/// Represents a box that can be rotated in any direction pub struct OBB { + /// Orientation of the OBB pub orientation: Quat, + /// Extents of the box before being transformed by the orientation pub extents: Vector3, + /// Center of the box pub center: Vector3, } @@ -252,10 +266,11 @@ fn translation(matrix: Mat4) -> Vector3 { matrix.row(3).truncate().into() } -pub fn nearest_point_on_line(p1: &Point3, dir: &Vector3, len: Real, pnt: &Point3) -> Point3 { - let v = *pnt - *p1; +/// Helper function that given a line segment and a target point, finds the closest point on the line segment to target +pub fn nearest_point_on_line(start: &Point3, dir: &Vector3, len: Real, target: &Point3) -> Point3 { + let v = *target - *start; let d = v.dot(*dir); - *p1 + (*dir * d.clamp(0.0, len)) + *start + (*dir * d.clamp(0.0, len)) } #[cfg(test)] From 902b0c678ee1c7cf2e611c1c111211f3221db083 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Fri, 14 Jan 2022 07:55:19 -0500 Subject: [PATCH 24/37] rename packages to prepare for upstreaming --- Cargo.toml | 2 +- README.md | 6 +- {dynbvh-f64 => bvh-f64}/Cargo.toml | 4 +- bvh-lib/Cargo.toml | 6 +- bvh-lib/src/lib.rs | 12 ++-- {dynbvh-f32 => bvh}/Cargo.toml | 4 +- src/aabb.rs | 90 +++++++++++++++--------------- src/axis.rs | 6 +- src/bounding_hierarchy.rs | 28 +++++----- src/flat_bvh.rs | 38 ++++++------- src/lib.rs | 13 ++--- src/ray.rs | 24 ++++---- 12 files changed, 116 insertions(+), 117 deletions(-) rename {dynbvh-f64 => bvh-f64}/Cargo.toml (96%) rename {dynbvh-f32 => bvh}/Cargo.toml (96%) diff --git a/Cargo.toml b/Cargo.toml index 73a53ad..aa3d2b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["bvh-lib", "dynbvh-f32", "dynbvh-f64"] +members = ["bvh-lib", "bvh", "bvh-f64"] diff --git a/README.md b/README.md index f9e376c..77db555 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ let ray = Ray::new(origin, direction); struct Sphere { position: Point3, - radius: f64, + radius: f32, } impl Bounded for Sphere { @@ -47,8 +47,8 @@ impl Bounded for Sphere { let mut spheres = Vec::new(); for i in 0..1000u32 { - let position = Point3::new(i as f64, i as f64, i as f64); - let radius = (i % 10) as f64 + 1.0; + let position = Point3::new(i as f32, i as f32, i as f32); + let radius = (i % 10) as f32 + 1.0; spheres.push(Sphere { position: position, radius: radius, diff --git a/dynbvh-f64/Cargo.toml b/bvh-f64/Cargo.toml similarity index 96% rename from dynbvh-f64/Cargo.toml rename to bvh-f64/Cargo.toml index e61d5be..fbcc679 100644 --- a/dynbvh-f64/Cargo.toml +++ b/bvh-f64/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dynbvh-f64" +name = "bvh-f64" description = "A fast dynamic BVH using SAH" version = "0.6.0" edition = "2018" @@ -15,7 +15,7 @@ license = "MIT" [lib] -name = "dynbvh_f64" +name = "bvh_f64" path = "../src/lib.rs" required-features = ["f64"] doctest = false diff --git a/bvh-lib/Cargo.toml b/bvh-lib/Cargo.toml index 5f4c156..ed583d7 100644 --- a/bvh-lib/Cargo.toml +++ b/bvh-lib/Cargo.toml @@ -6,8 +6,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dynbvh-f32 = { path = "../dynbvh-f32" } -dynbvh-f64 = { path = "../dynbvh-f64" } +bvh = { path = "../bvh" } +bvh-f64 = { path = "../bvh-f64" } interoptopus = "0.13.12" interoptopus_backend_csharp = "0.13.12" flexi_logger = "0.19.3" @@ -16,5 +16,5 @@ log = "0.4.14" glam = "0.20" [lib] -name="bvh_f64" +name="bvh_lib" crate-type = ["rlib", "dylib"] \ No newline at end of file diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index dbc91d1..5c06946 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -1,11 +1,11 @@ use std::sync::atomic::{AtomicUsize, Ordering}; -use dynbvh_f64::aabb::{Bounded, AABB}; -use dynbvh_f64::bounding_hierarchy::BHShape; -use dynbvh_f64::bvh::BVH; -use dynbvh_f64::ray::Ray; -use dynbvh_f64::shapes::{Capsule, Sphere, OBB}; -use dynbvh_f64::Vector3; +use bvh_f64::aabb::{Bounded, AABB}; +use bvh_f64::bounding_hierarchy::BHShape; +use bvh_f64::bvh::BVH; +use bvh_f64::ray::Ray; +use bvh_f64::shapes::{Capsule, Sphere, OBB}; +use bvh_f64::Vector3; use flexi_logger::{detailed_format, FileSpec, Logger}; use glam::DQuat; use interoptopus::lang::c::{ diff --git a/dynbvh-f32/Cargo.toml b/bvh/Cargo.toml similarity index 96% rename from dynbvh-f32/Cargo.toml rename to bvh/Cargo.toml index d10ae02..81c99e2 100644 --- a/dynbvh-f32/Cargo.toml +++ b/bvh/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dynbvh-f32" +name = "bvh" description = "A fast dynamic BVH using SAH" version = "0.6.0" edition = "2018" @@ -15,7 +15,7 @@ license = "MIT" [lib] -name = "dynbvh_f32" +name = "bvh" path = "../src/lib.rs" required-features = [] diff --git a/src/aabb.rs b/src/aabb.rs index 7cf5492..de71121 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -35,8 +35,8 @@ pub trait Bounded { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::{AABB, Bounded}; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::{AABB, Bounded}; + /// use bvh::Point3; /// /// struct Something; /// @@ -65,8 +65,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0,-1.0,-1.0), Point3::new(1.0,1.0,1.0)); /// assert_eq!(aabb.min.x, -1.0); @@ -84,7 +84,7 @@ impl AABB { /// # Examples /// ``` /// # extern crate rand; - /// use dynbvh_f32::aabb::AABB; + /// use bvh::aabb::AABB; /// /// # fn main() { /// let aabb = AABB::empty(); @@ -115,8 +115,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_inside = Point3::new(0.125, -0.25, 0.5); @@ -143,9 +143,9 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::EPSILON; - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::EPSILON; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_barely_outside = Point3::new(1.000_000_1, -1.000_000_1, 1.000_000_001); @@ -172,9 +172,9 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::EPSILON; - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::EPSILON; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_barely_outside = Point3::new(1.000_000_1, 1.000_000_1, 1.000_000_1); @@ -195,9 +195,9 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::EPSILON; - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::EPSILON; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0)); /// let point_barely_outside_min = Point3::new(-1.000_000_1, -1.000_000_1, -1.000_000_1); @@ -222,8 +222,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let aabb1 = AABB::with_bounds(Point3::new(-101.0, 0.0, 0.0), Point3::new(-100.0, 1.0, 1.0)); /// let aabb2 = AABB::with_bounds(Point3::new(100.0, 0.0, 0.0), Point3::new(101.0, 1.0, 1.0)); @@ -267,8 +267,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::{Point3, Vector3}; + /// use bvh::aabb::AABB; + /// use bvh::{Point3, Vector3}; /// /// let size = Vector3::new(1.0, 1.0, 1.0); /// let aabb_pos = Point3::new(-101.0, 0.0, 0.0); @@ -316,8 +316,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let point1 = Point3::new(0.0, 0.0, 0.0); /// let point2 = Point3::new(1.0, 1.0, 1.0); @@ -356,8 +356,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let point1 = Point3::new(0.0, 0.0, 0.0); /// let point2 = Point3::new(1.0, 1.0, 1.0); @@ -396,8 +396,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::{AABB, Bounded}; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::{AABB, Bounded}; + /// use bvh::Point3; /// /// struct Something; /// @@ -428,8 +428,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let aabb = AABB::with_bounds(Point3::new(-1.0,-1.0,-1.0), Point3::new(1.0,1.0,1.0)); /// let size = aabb.size(); @@ -446,8 +446,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let min = Point3::new(41.0,41.0,41.0); /// let max = Point3::new(43.0,43.0,43.0); @@ -469,8 +469,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let empty_aabb = AABB::empty(); /// assert!(empty_aabb.is_empty()); @@ -492,8 +492,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let min = Point3::new(41.0,41.0,41.0); /// let max = Point3::new(43.0,43.0,43.0); @@ -514,8 +514,8 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::Point3; /// /// let min = Point3::new(41.0,41.0,41.0); /// let max = Point3::new(43.0,43.0,43.0); @@ -536,9 +536,9 @@ impl AABB { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::axis::Axis; - /// use dynbvh_f32::Point3; + /// use bvh::aabb::AABB; + /// use bvh::axis::Axis; + /// use bvh::Point3; /// /// let min = Point3::new(-100.0,0.0,0.0); /// let max = Point3::new(100.0,0.0,0.0); @@ -595,8 +595,8 @@ impl Default for AABB { /// /// # Examples /// ``` -/// use dynbvh_f32::aabb::AABB; -/// use dynbvh_f32::Point3; +/// use bvh::aabb::AABB; +/// use bvh::Point3; /// /// let min = Point3::new(3.0,4.0,5.0); /// let max = Point3::new(123.0,123.0,123.0); @@ -624,8 +624,8 @@ impl Index for AABB { /// /// # Examples /// ``` -/// use dynbvh_f32::aabb::{AABB, Bounded}; -/// use dynbvh_f32::Point3; +/// use bvh::aabb::{AABB, Bounded}; +/// use bvh::Point3; /// /// let point_a = Point3::new(3.0,4.0,5.0); /// let point_b = Point3::new(17.0,18.0,19.0); @@ -650,8 +650,8 @@ impl Bounded for AABB { /// /// # Examples /// ``` -/// use dynbvh_f32::aabb::{AABB, Bounded}; -/// use dynbvh_f32::Point3; +/// use bvh::aabb::{AABB, Bounded}; +/// use bvh::Point3; /// /// let point = Point3::new(3.0,4.0,5.0); /// diff --git a/src/axis.rs b/src/axis.rs index c5efb56..a19b905 100644 --- a/src/axis.rs +++ b/src/axis.rs @@ -12,7 +12,7 @@ struct MyType(T); /// /// # Examples /// ``` -/// use dynbvh_f32::axis::Axis; +/// use bvh::axis::Axis; /// /// let mut position = [1.0, 0.5, 42.0]; /// position[Axis::Y] *= 4.0; @@ -24,8 +24,8 @@ struct MyType(T); /// /// ``` /// -/// use dynbvh_f32::axis::Axis; -/// use dynbvh_f32::Point3; +/// use bvh::axis::Axis; +/// use bvh::Point3; /// /// # fn main() { /// let mut position: Point3 = Point3::new(1.0, 2.0, 3.0); diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index feca1c1..82e7f5f 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -31,11 +31,11 @@ pub trait BoundingHierarchy { /// # Examples /// /// ``` - /// use dynbvh_f32::aabb::{AABB, Bounded}; - /// use dynbvh_f32::bounding_hierarchy::BoundingHierarchy; - /// use dynbvh_f32::{Point3, Vector3}; - /// use dynbvh_f32::Real; - /// # use dynbvh_f32::bounding_hierarchy::BHShape; + /// use bvh::aabb::{AABB, Bounded}; + /// use bvh::bounding_hierarchy::BoundingHierarchy; + /// use bvh::{Point3, Vector3}; + /// use bvh::Real; + /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -82,13 +82,13 @@ pub trait BoundingHierarchy { /// let mut shapes = create_bhshapes(); /// // Construct a normal `BVH`. /// { - /// use dynbvh_f32::bvh::BVH; + /// use bvh::bvh::BVH; /// let bvh = BVH::build(&mut shapes); /// } /// /// // Or construct a `FlatBVH`. /// { - /// use dynbvh_f32::flat_bvh::FlatBVH; + /// use bvh::flat_bvh::FlatBVH; /// let bvh = FlatBVH::build(&mut shapes); /// } /// ``` @@ -103,13 +103,13 @@ pub trait BoundingHierarchy { /// # Examples /// /// ``` - /// use dynbvh_f32::aabb::{AABB, Bounded}; - /// use dynbvh_f32::bounding_hierarchy::BoundingHierarchy; - /// use dynbvh_f32::bvh::BVH; - /// use dynbvh_f32::{Point3, Vector3}; - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::Real; - /// # use dynbvh_f32::bounding_hierarchy::BHShape; + /// use bvh::aabb::{AABB, Bounded}; + /// use bvh::bounding_hierarchy::BoundingHierarchy; + /// use bvh::bvh::BVH; + /// use bvh::{Point3, Vector3}; + /// use bvh::ray::Ray; + /// use bvh::Real; + /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, diff --git a/src/flat_bvh.rs b/src/flat_bvh.rs index 8bbacf7..070329d 100644 --- a/src/flat_bvh.rs +++ b/src/flat_bvh.rs @@ -162,12 +162,12 @@ impl BVH { /// # Example /// /// ``` - /// use dynbvh_f32::aabb::{AABB, Bounded}; - /// use dynbvh_f32::bvh::BVH; - /// use dynbvh_f32::{Point3, Vector3}; - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::Real; - /// # use dynbvh_f32::bounding_hierarchy::BHShape; + /// use bvh::aabb::{AABB, Bounded}; + /// use bvh::bvh::BVH; + /// use bvh::{Point3, Vector3}; + /// use bvh::ray::Ray; + /// use bvh::Real; + /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -251,12 +251,12 @@ impl BVH { /// # Example /// /// ``` - /// use dynbvh_f32::aabb::{AABB, Bounded}; - /// use dynbvh_f32::bvh::BVH; - /// use dynbvh_f32::{Point3, Vector3}; - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::Real; - /// # use dynbvh_f32::bounding_hierarchy::BHShape; + /// use bvh::aabb::{AABB, Bounded}; + /// use bvh::bvh::BVH; + /// use bvh::{Point3, Vector3}; + /// use bvh::ray::Ray; + /// use bvh::Real; + /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, @@ -332,13 +332,13 @@ impl BoundingHierarchy for FlatBVH { /// # Examples /// /// ``` - /// use dynbvh_f32::aabb::{AABB, Bounded}; - /// use dynbvh_f32::bounding_hierarchy::BoundingHierarchy; - /// use dynbvh_f32::flat_bvh::FlatBVH; - /// use dynbvh_f32::{Point3, Vector3}; - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::Real; - /// # use dynbvh_f32::bounding_hierarchy::BHShape; + /// use bvh::aabb::{AABB, Bounded}; + /// use bvh::bounding_hierarchy::BoundingHierarchy; + /// use bvh::flat_bvh::FlatBVH; + /// use bvh::{Point3, Vector3}; + /// use bvh::ray::Ray; + /// use bvh::Real; + /// # use bvh::bounding_hierarchy::BHShape; /// # pub struct UnitBox { /// # pub id: i32, /// # pub pos: Point3, diff --git a/src/lib.rs b/src/lib.rs index 9e729da..d10702f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(test)] //! A crate which exports rays, axis-aligned bounding boxes, and binary bounding //! volume hierarchies. //! @@ -15,12 +14,12 @@ //! ## Example //! //! ``` -//! use dynbvh_f32::aabb::{AABB, Bounded}; -//! use dynbvh_f32::bounding_hierarchy::{BoundingHierarchy, BHShape}; -//! use dynbvh_f32::bvh::BVH; -//! use dynbvh_f32::{Point3, Vector3}; -//! use dynbvh_f32::ray::Ray; -//! use dynbvh_f32::Real; +//! use bvh::aabb::{AABB, Bounded}; +//! use bvh::bounding_hierarchy::{BoundingHierarchy, BHShape}; +//! use bvh::bvh::BVH; +//! use bvh::{Point3, Vector3}; +//! use bvh::ray::Ray; +//! use bvh::Real; //! //! let origin = Point3::new(0.0,0.0,0.0); //! let direction = Vector3::new(1.0,0.0,0.0); diff --git a/src/ray.rs b/src/ray.rs index 0161e39..6f5bb46 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -69,10 +69,10 @@ impl IntersectionAABB for Ray { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::{Point3,Vector3}; - /// use dynbvh_f32::bounding_hierarchy::IntersectionAABB; + /// use bvh::aabb::AABB; + /// use bvh::ray::Ray; + /// use bvh::{Point3,Vector3}; + /// use bvh::bounding_hierarchy::IntersectionAABB; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); @@ -139,8 +139,8 @@ impl Ray { /// /// # Examples /// ``` - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::{Point3,Vector3}; + /// use bvh::ray::Ray; + /// use bvh::{Point3,Vector3}; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); @@ -168,9 +168,9 @@ impl Ray { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::{Point3,Vector3}; + /// use bvh::aabb::AABB; + /// use bvh::ray::Ray; + /// use bvh::{Point3,Vector3}; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); @@ -214,9 +214,9 @@ impl Ray { /// /// # Examples /// ``` - /// use dynbvh_f32::aabb::AABB; - /// use dynbvh_f32::ray::Ray; - /// use dynbvh_f32::{Point3,Vector3}; + /// use bvh::aabb::AABB; + /// use bvh::ray::Ray; + /// use bvh::{Point3,Vector3}; /// /// let origin = Point3::new(0.0,0.0,0.0); /// let direction = Vector3::new(1.0,0.0,0.0); From fe22c91d9cafbeac740ca9a943735c9deab228ed Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Fri, 14 Jan 2022 22:36:29 -0500 Subject: [PATCH 25/37] changes to support ray tracing --- bvh-lib/Cargo.toml | 12 ++- bvh-lib/src/lib.rs | 4 +- src/lib.rs | 87 +--------------- src/{ => shapes}/aabb.rs | 0 src/shapes/capsule.rs | 76 ++++++++++++++ src/shapes/mod.rs | 66 ++++++++++++ src/{shapes.rs => shapes/obb.rs} | 168 +------------------------------ src/{ => shapes}/ray.rs | 41 ++++++-- src/shapes/sphere.rs | 53 ++++++++++ src/utils.rs | 8 ++ 10 files changed, 257 insertions(+), 258 deletions(-) rename src/{ => shapes}/aabb.rs (100%) create mode 100644 src/shapes/capsule.rs create mode 100644 src/shapes/mod.rs rename src/{shapes.rs => shapes/obb.rs} (52%) rename src/{ => shapes}/ray.rs (93%) create mode 100644 src/shapes/sphere.rs diff --git a/bvh-lib/Cargo.toml b/bvh-lib/Cargo.toml index ed583d7..ed87a4c 100644 --- a/bvh-lib/Cargo.toml +++ b/bvh-lib/Cargo.toml @@ -17,4 +17,14 @@ glam = "0.20" [lib] name="bvh_lib" -crate-type = ["rlib", "dylib"] \ No newline at end of file +crate-type = ["rlib", "dylib"] + +[profile.release] +lto = true +codegen-units = 1 +debug = true + +[profile.bench] +lto = true +codegen-units = 1 +debug = true \ No newline at end of file diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index 5c06946..bc6d4e7 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -3,9 +3,11 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use bvh_f64::aabb::{Bounded, AABB}; use bvh_f64::bounding_hierarchy::BHShape; use bvh_f64::bvh::BVH; +use bvh_f64::capsule::Capsule; +use bvh_f64::obb::OBB; use bvh_f64::ray::Ray; -use bvh_f64::shapes::{Capsule, Sphere, OBB}; use bvh_f64::Vector3; +use bvh_f64::sphere::Sphere; use flexi_logger::{detailed_format, FileSpec, Logger}; use glam::DQuat; use interoptopus::lang::c::{ diff --git a/src/lib.rs b/src/lib.rs index d10702f..8e2bb88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,19 +120,18 @@ pub type Real = f32; /// TODO: replace by/add ULPS/relative float comparison methods. pub const EPSILON: Real = 0.00001; -pub mod aabb; pub mod axis; pub mod bounding_hierarchy; pub mod bvh; pub mod flat_bvh; -pub mod ray; -pub mod shapes; +mod shapes; mod utils; #[cfg(test)] mod testbase; - +pub use shapes::*; +//pub use shapes::{Ray, AABB, OBB, Capsule, Sphere}; use aabb::{Bounded, AABB}; use bounding_hierarchy::BHShape; @@ -194,83 +193,3 @@ impl Bounded for Triangle { self.aabb } } - -impl BHShape for Triangle { - fn set_bh_node_index(&mut self, index: usize) { - self.node_index = index; - } - - fn bh_node_index(&self) -> usize { - self.node_index - } -} - -// impl FromRawVertex for Triangle { -// fn process( -// vertices: Vec<(f32, f32, f32, f32)>, -// _: Vec<(f32, f32, f32)>, -// _: Vec<(f32, f32, f32)>, -// polygons: Vec, -// ) -> ObjResult<(Vec, Vec)> { -// // Convert the vertices to `Point3`s. -// let points = vertices -// .into_iter() -// .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) -// .collect::>(); - -// // Estimate for the number of triangles, assuming that each polygon is a triangle. -// let mut triangles = Vec::with_capacity(polygons.len()); -// { -// let mut push_triangle = |indices: &Vec| { -// let mut indices_iter = indices.iter(); -// let anchor = points[*indices_iter.next().unwrap()]; -// let mut second = points[*indices_iter.next().unwrap()]; -// for third_index in indices_iter { -// let third = points[*third_index]; -// triangles.push(Triangle::new(anchor, second, third)); -// second = third; -// } -// }; - -// // Iterate over the polygons and populate the `Triangle`s vector. -// for polygon in polygons.into_iter() { -// match polygon { -// Polygon::P(ref vec) => push_triangle(vec), -// Polygon::PT(ref vec) | Polygon::PN(ref vec) => { -// push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) -// } -// Polygon::PTN(ref vec) => { -// push_triangle(&vec.iter().map(|vertex| vertex.0).collect()) -// } -// } -// } -// } -// Ok((triangles, Vec::new())) -// } -// } - -// pub fn load_sponza_scene() -> (Vec, AABB) { -// use std::fs::File; -// use std::io::BufReader; - -// let file_input = -// BufReader::new(File::open("media/sponza.obj").expect("Failed to open .obj file.")); -// let sponza_obj: Obj = load_obj(file_input).expect("Failed to decode .obj file data."); -// let triangles = sponza_obj.vertices; - -// let mut bounds = AABB::empty(); -// for triangle in &triangles { -// bounds.join_mut(&triangle.aabb()); -// } - -// (triangles, bounds) -// } - -// pub fn main() { -// let (mut triangles, _bounds) = load_sponza_scene(); -// let mut bvh = BVH::build(triangles.as_mut_slice()); - -// for _i in 0..10 { -// bvh.rebuild(triangles.as_mut_slice()); -// } -// } diff --git a/src/aabb.rs b/src/shapes/aabb.rs similarity index 100% rename from src/aabb.rs rename to src/shapes/aabb.rs diff --git a/src/shapes/capsule.rs b/src/shapes/capsule.rs new file mode 100644 index 0000000..3933426 --- /dev/null +++ b/src/shapes/capsule.rs @@ -0,0 +1,76 @@ +//! This module defines Capsules and their intersection algorithms +use crate::{Point3, Vector3, Real, bounding_hierarchy::IntersectionAABB, aabb::AABB, utils::nearest_point_on_line}; + + +/// Representation of a capsule +pub struct Capsule { + /// Start point of the line segment for the capsule + pub start: Point3, + /// Radius of the capsule + pub radius: Real, + /// Direction of the capsule's line segment + pub dir: Vector3, + /// Length of the capsules line segment + pub len: Real, +} + +impl Capsule { + /// Creates a capsule given a start and end point for the center line and the radius around it + pub fn new(start: Point3, end: Point3, radius: Real) -> Capsule { + let line = end - start; + let dir = line.normalize(); + let len = line.length(); + + Capsule { + start, + radius, + dir, + len, + } + } +} + +impl IntersectionAABB for Capsule { + fn intersects_aabb(&self, aabb: &AABB) -> bool { + /* + // Use Distance from closest point + let mut point: Vector3 = self.start; + let mut curr_d = 0.0; + let max_sq = (self.len + self.radius) * (self.len + self.radius) ; + let r_sq = self.radius * self.radius; + + loop { + let x = aabb.closest_point(point); + let d_sq = x.distance_squared(point); + println!("{:?} closest={:?} d={:?}", point, x, d_sq.sqrt()); + if d_sq <= r_sq + { + return true; + } + if d_sq > max_sq || curr_d >= self.len + { + return false; + } + curr_d = (curr_d + d_sq.sqrt()).min(self.len); + point = self.start + (curr_d * self.dir); + } + */ + let mut last = self.start; + loop { + let closest = &aabb.closest_point(last); + let center = nearest_point_on_line(&self.start, &self.dir, self.len, closest); + let sphere = crate::sphere::Sphere { + center, + radius: self.radius, + }; + if sphere.intersects_aabb(aabb) { + return true; + } + if last.distance_squared(center) < 0.0001 { + return false; + } else { + last = center; + } + } + } +} \ No newline at end of file diff --git a/src/shapes/mod.rs b/src/shapes/mod.rs new file mode 100644 index 0000000..8edcfec --- /dev/null +++ b/src/shapes/mod.rs @@ -0,0 +1,66 @@ +pub mod aabb; +pub mod capsule; +pub mod obb; +pub mod ray; +pub mod sphere; + + +#[cfg(test)] +mod tests { + use crate::aabb::AABB; + use crate::bounding_hierarchy::IntersectionAABB; + use crate::capsule::Capsule; + use crate::obb::OBB; + use crate::{Point3, Quat, Real, Vector3}; + + #[test] + fn basic_test_capsule() { + let min = Point3::new(0.0, 0.0, 0.0); + let max = Point3::new(1.0, 1.0, 1.0); + let aabb = AABB::empty().grow(&min).grow(&max); + let start = Point3::new(3.0, 0.0, 0.0); + let end = Point3::new(1.5, 0.0, 0.0); + let capsule = Capsule::new(start, end, 0.55); + assert!(capsule.intersects_aabb(&aabb)); + } + + #[test] + fn moving_test_capsule() { + let min = Point3::new(0.0, 0.0, 0.0); + let max = Point3::new(1.0, 1.0, 1.0); + let aabb = AABB::empty().grow(&min).grow(&max); + let start = Point3::new(0.5, 2.0, 2.0); + let end = Point3::new(0.5, 5.0, -1.0); + let capsule = Capsule::new(start, end, 1.45); + assert!(capsule.intersects_aabb(&aabb)); + + let dir = (start - end).normalize(); + let offset: Real = 0.005; + println!("{}", dir); + for i in 0..800 { + println!("{}", i); + let pt = offset * dir * i as Real; + let cap = Capsule::new(start + pt, end + pt, 1.45); + assert!(cap.intersects_aabb(&aabb)); + } + } + + #[test] + fn basic_obb() { + let min = Point3::new(0.0, 0.0, 0.0); + let max = Point3::new(1.0, 1.0, 1.0); + let aabb = AABB::empty().grow(&min).grow(&max); + + let ori = Quat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0).into(), 0.785398); + let extents = Vector3::new(0.5, 0.5, 0.5); + let pos = Vector3::new(0.5, 2.2, 0.5); + + let obb = OBB { + orientation: ori, + extents, + center: pos, + }; + + assert!(obb.intersects_aabb(&aabb)); + } +} \ No newline at end of file diff --git a/src/shapes.rs b/src/shapes/obb.rs similarity index 52% rename from src/shapes.rs rename to src/shapes/obb.rs index e2338de..f31669d 100644 --- a/src/shapes.rs +++ b/src/shapes/obb.rs @@ -1,102 +1,6 @@ -//! This module exports various shapes that can be used to intersect with -use crate::aabb::AABB; -use crate::bounding_hierarchy::IntersectionAABB; -use crate::{Mat4, Point3, Quat, Real, Vector3}; +//! This module defines an Oriented Bounding Box and its intersection properties +use crate::{Quat, Vector3, aabb::AABB, bounding_hierarchy::IntersectionAABB, Mat4}; -/// A representation of a Sphere -pub struct Sphere { - /// Center of the sphere - pub center: Point3, - /// Radius of the sphere - pub radius: Real, -} - -impl Sphere { - /// Creates a sphere centered on a given point with a radius - pub fn new(center: Point3, radius: Real) -> Sphere { - Sphere { center, radius } - } -} - -impl IntersectionAABB for Sphere { - fn intersects_aabb(&self, aabb: &AABB) -> bool { - let vec = aabb.closest_point(self.center); - vec.distance_squared(self.center) < self.radius * self.radius - } -} - -/// Representation of a capsule -pub struct Capsule { - /// Start point of the line segment for the capsule - pub start: Point3, - /// Radius of the capsule - pub radius: Real, - /// Direction of the capsule's line segment - pub dir: Vector3, - /// Length of the capsules line segment - pub len: Real, -} - -impl Capsule { - /// Creates a capsule given a start and end point for the center line and the radius around it - pub fn new(start: Point3, end: Point3, radius: Real) -> Capsule { - let line = end - start; - let dir = line.normalize(); - let len = line.length(); - - Capsule { - start, - radius, - dir, - len, - } - } -} - -impl IntersectionAABB for Capsule { - fn intersects_aabb(&self, aabb: &AABB) -> bool { - /* - // Use Distance from closest point - let mut point: Vector3 = self.start; - let mut curr_d = 0.0; - let max_sq = (self.len + self.radius) * (self.len + self.radius) ; - let r_sq = self.radius * self.radius; - - loop { - let x = aabb.closest_point(point); - let d_sq = x.distance_squared(point); - println!("{:?} closest={:?} d={:?}", point, x, d_sq.sqrt()); - if d_sq <= r_sq - { - return true; - } - if d_sq > max_sq || curr_d >= self.len - { - return false; - } - curr_d = (curr_d + d_sq.sqrt()).min(self.len); - point = self.start + (curr_d * self.dir); - } - */ - let mut last = self.start; - loop { - let closest = &aabb.closest_point(last); - let center = nearest_point_on_line(&self.start, &self.dir, self.len, closest); - let sphere = Sphere { - center, - radius: self.radius, - }; - if sphere.intersects_aabb(aabb) { - return true; - } - if last.distance_squared(center) < 0.0001 { - return false; - } else { - last = center; - } - } - } -} /// Represents a box that can be rotated in any direction pub struct OBB { @@ -264,70 +168,4 @@ fn back(matrix: Mat4) -> Vector3 { fn translation(matrix: Mat4) -> Vector3 { matrix.row(3).truncate().into() -} - -/// Helper function that given a line segment and a target point, finds the closest point on the line segment to target -pub fn nearest_point_on_line(start: &Point3, dir: &Vector3, len: Real, target: &Point3) -> Point3 { - let v = *target - *start; - let d = v.dot(*dir); - *start + (*dir * d.clamp(0.0, len)) -} - -#[cfg(test)] -mod tests { - use crate::aabb::AABB; - use crate::bounding_hierarchy::IntersectionAABB; - use crate::shapes::{Capsule, OBB}; - use crate::{Point3, Quat, Real, Vector3}; - - #[test] - fn basic_test_capsule() { - let min = Point3::new(0.0, 0.0, 0.0); - let max = Point3::new(1.0, 1.0, 1.0); - let aabb = AABB::empty().grow(&min).grow(&max); - let start = Point3::new(3.0, 0.0, 0.0); - let end = Point3::new(1.5, 0.0, 0.0); - let capsule = Capsule::new(start, end, 0.55); - assert!(capsule.intersects_aabb(&aabb)); - } - - #[test] - fn moving_test_capsule() { - let min = Point3::new(0.0, 0.0, 0.0); - let max = Point3::new(1.0, 1.0, 1.0); - let aabb = AABB::empty().grow(&min).grow(&max); - let start = Point3::new(0.5, 2.0, 2.0); - let end = Point3::new(0.5, 5.0, -1.0); - let capsule = Capsule::new(start, end, 1.45); - assert!(capsule.intersects_aabb(&aabb)); - - let dir = (start - end).normalize(); - let offset: Real = 0.005; - println!("{}", dir); - for i in 0..800 { - println!("{}", i); - let pt = offset * dir * i as Real; - let cap = Capsule::new(start + pt, end + pt, 1.45); - assert!(cap.intersects_aabb(&aabb)); - } - } - - #[test] - fn basic_obb() { - let min = Point3::new(0.0, 0.0, 0.0); - let max = Point3::new(1.0, 1.0, 1.0); - let aabb = AABB::empty().grow(&min).grow(&max); - - let ori = Quat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0).into(), 0.785398); - let extents = Vector3::new(0.5, 0.5, 0.5); - let pos = Vector3::new(0.5, 2.2, 0.5); - - let obb = OBB { - orientation: ori, - extents, - center: pos, - }; - - assert!(obb.intersects_aabb(&aabb)); - } -} +} \ No newline at end of file diff --git a/src/ray.rs b/src/shapes/ray.rs similarity index 93% rename from src/ray.rs rename to src/shapes/ray.rs index 6f5bb46..b45f12a 100644 --- a/src/ray.rs +++ b/src/shapes/ray.rs @@ -43,6 +43,9 @@ pub struct Ray { sign_z: usize, } + + + /// A struct which is returned by the `intersects_triangle` method. pub struct Intersection { /// Distance from the ray origin to the intersection point. @@ -53,16 +56,28 @@ pub struct Intersection { /// V coordinate of the intersection. pub v: Real, + + /// Normal of the intersection + pub norm: Vector3, + + /// Whether the intersect was a backface + pub back_face: bool } impl Intersection { /// Constructs an `Intersection`. `distance` should be set to positive infinity, /// if the intersection does not occur. - pub fn new(distance: Real, u: Real, v: Real) -> Intersection { - Intersection { distance, u, v } + pub fn new(distance: Real, u: Real, v: Real, norm: Vector3, back_face: bool) -> Intersection { + Intersection { distance, u, v, norm, back_face } } } +/// This trait can be implemented on anything that can intersect with a `Ray` +pub trait IntersectionRay { + /// Returns true if there is an intersection with the given `Ray` + fn intersects_ray(&self, ray: &Ray, t_min: Real, t_max: Real) -> Option; +} + impl IntersectionAABB for Ray { /// Tests the intersection of a [`Ray`] with an [`AABB`] using the optimized algorithm /// from [this paper](http://www.cs.utah.edu/~awilliam/box/box.pdf). @@ -279,7 +294,7 @@ impl Ray { // If backface culling is not desired write: // det < EPSILON && det > -EPSILON if det < EPSILON { - return Intersection::new(Real::INFINITY, 0.0, 0.0); + return Intersection::new(Real::INFINITY, 0.0, 0.0, Vector3::ZERO, false); } let inv_det = 1.0 / det; @@ -292,7 +307,7 @@ impl Ray { // Test bounds: u < 0 || u > 1 => outside of triangle if !(0.0..=1.0).contains(&u) { - return Intersection::new(Real::INFINITY, u, 0.0); + return Intersection::new(Real::INFINITY, u, 0.0, Vector3::ZERO, false); } // Prepare to test v parameter @@ -302,15 +317,15 @@ impl Ray { let v = self.direction.dot(v_vec) * inv_det; // The intersection lies outside of the triangle if v < 0.0 || u + v > 1.0 { - return Intersection::new(Real::INFINITY, u, v); + return Intersection::new(Real::INFINITY, u, v, Vector3::ZERO, false); } let dist = a_to_c.dot(v_vec) * inv_det; if dist > EPSILON { - Intersection::new(dist, u, v) + Intersection::new(dist, u, v, Vector3::ZERO, false) } else { - Intersection::new(Real::INFINITY, u, v) + Intersection::new(Real::INFINITY, u, v, Vector3::ZERO, false) } } @@ -318,8 +333,20 @@ impl Ray { pub fn at(&self, dist: Real) -> Vector3 { self.origin + (self.direction * dist) } + + /// Given an outward normal returns whether it hit a back_face and adjusts the normal + pub fn face_normal(&self, out_norm: Vector3) -> (Vector3, bool) { + let back_face = self.direction.dot(out_norm) >= 0.; + let norm = if back_face { + -out_norm + } else { + out_norm + }; + (norm, back_face) + } } + #[cfg(test)] mod tests { use crate::Real; diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs new file mode 100644 index 0000000..5e9ff43 --- /dev/null +++ b/src/shapes/sphere.rs @@ -0,0 +1,53 @@ +//! This module defines a Sphere and it's intersection algorithms +use crate::{Point3, Real, bounding_hierarchy::IntersectionAABB, aabb::AABB, ray::{IntersectionRay, Ray, Intersection}, Vector3}; + + +/// A representation of a Sphere +pub struct Sphere { + /// Center of the sphere + pub center: Point3, + /// Radius of the sphere + pub radius: Real, +} + +impl Sphere { + /// Creates a sphere centered on a given point with a radius + pub fn new(center: Point3, radius: Real) -> Sphere { + Sphere { center, radius } + } +} + +impl IntersectionAABB for Sphere { + fn intersects_aabb(&self, aabb: &AABB) -> bool { + let vec = aabb.closest_point(self.center); + vec.distance_squared(self.center) < self.radius * self.radius + } +} + +impl IntersectionRay for Sphere { + fn intersects_ray(&self, ray: &Ray, t_min: Real, t_max: Real) -> Option { + let oc = ray.origin - self.center; + let a = ray.direction.length_squared(); + let half_b = oc.dot(ray.direction); + let c = oc.length_squared() - self.radius * self.radius; + let discriminant = half_b*half_b - a*c; + + if discriminant < 0. { + return None; + } + + let sqrtd = discriminant.sqrt(); + + let mut toi = (-half_b - sqrtd) / a; + if toi < t_min || t_max < toi { + toi = (-half_b + sqrtd) / a; + if toi < t_min || t_max < toi { + return None + } + } + + let out_norm = (ray.at(toi) - self.center).normalize(); + let (norm, back_face) = ray.face_normal(out_norm); + Some(Intersection::new(toi, 0., 0., norm, back_face)) + } +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index a2be63f..3d53dea 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ //! Utilities module. +use crate::{Point3, Vector3, Real}; use crate::aabb::AABB; use crate::bounding_hierarchy::BHShape; @@ -65,6 +66,13 @@ pub fn joint_aabb_of_shapes(indices: &[usize], shapes: &[Shape]) (aabb, centroid) } +/// Helper function that given a line segment and a target point, finds the closest point on the line segment to target +pub fn nearest_point_on_line(start: &Point3, dir: &Vector3, len: Real, target: &Point3) -> Point3 { + let v = *target - *start; + let d = v.dot(*dir); + *start + (*dir * d.clamp(0.0, len)) +} + #[cfg(test)] mod tests { use crate::utils::concatenate_vectors; From 93cd4e6b54c7643416f8130924272b7e190cc178 Mon Sep 17 00:00:00 2001 From: Derek Benson Date: Sun, 16 Jan 2022 18:21:49 -0500 Subject: [PATCH 26/37] adjust trait lifetimes, add sphere and ray normals, fix building empty bvh --- src/bvh/bvh_impl.rs | 22 +++++++++++++--------- src/bvh/iter.rs | 18 +++++++++--------- src/lib.rs | 43 +++++++++++++------------------------------ src/shapes/ray.rs | 9 +++++++-- src/shapes/sphere.rs | 25 ++++++++++++++++++++++--- 5 files changed, 64 insertions(+), 53 deletions(-) diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index afd44bf..d115076 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -609,6 +609,12 @@ impl BVH { /// [`BVH`]: struct.BVH.html /// pub fn build(shapes: &mut [Shape]) -> BVH { + if shapes.len() == 0 { + return BVH { + nodes: Vec::new() + } + } + let mut indices = (0..shapes.len()).collect::>(); let expected_node_count = shapes.len() * 2 - 1; let mut nodes = Vec::with_capacity(expected_node_count); @@ -631,10 +637,8 @@ impl BVH { pub fn rebuild(&mut self, shapes: &mut [Shape]) { let mut indices = (0..shapes.len()).collect::>(); let expected_node_count = shapes.len() * 2 - 1; - let additional_nodes = self.nodes.capacity() as i32 - expected_node_count as i32; - if additional_nodes > 0 { - self.nodes.reserve(additional_nodes as usize); - } + self.nodes.clear(); + self.nodes.reserve(expected_node_count); unsafe { self.nodes.set_len(expected_node_count); } @@ -668,11 +672,11 @@ impl BVH { /// [`BVH`]: struct.BVH.html /// [`AABB`]: ../aabb/struct.AABB.html /// - pub fn traverse_iterator<'a, Shape: Bounded>( - &'a self, - test: &'a impl IntersectionAABB, - shapes: &'a [Shape], - ) -> BVHTraverseIterator { + pub fn traverse_iterator<'bvh, 'test, 'shapes, Shape: Bounded>( + &'bvh self, + test: &'test impl IntersectionAABB, + shapes: &'shapes [Shape], + ) -> BVHTraverseIterator<'bvh, 'test, 'shapes, Shape> { BVHTraverseIterator::new(self, test, shapes) } diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index be42d18..1fbb259 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -6,13 +6,13 @@ use crate::bvh::{BVHNode, BVH}; /// Iterator to traverse a [`BVH`] without memory allocations #[allow(clippy::upper_case_acronyms)] -pub struct BVHTraverseIterator<'a, Shape: Bounded> { +pub struct BVHTraverseIterator<'bvh, 'test, 'shapes, Shape: Bounded> { /// Reference to the BVH to traverse - bvh: &'a BVH, + bvh: &'bvh BVH, /// Reference to the input ray - test: &'a dyn IntersectionAABB, + test: &'test dyn IntersectionAABB, /// Reference to the input shapes array - shapes: &'a [Shape], + shapes: &'shapes [Shape], /// Traversal stack. Allocates if exceeds depth of 64 stack: SmallVec<[usize; 64]>, /// Position of the iterator in bvh.nodes @@ -21,9 +21,9 @@ pub struct BVHTraverseIterator<'a, Shape: Bounded> { has_node: bool, } -impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { +impl<'bvh, 'test, 'shapes, Shape: Bounded> BVHTraverseIterator<'bvh, 'test, 'shapes, Shape> { /// Creates a new `BVHTraverseIterator` - pub fn new(bvh: &'a BVH, test: &'a impl IntersectionAABB, shapes: &'a [Shape]) -> Self { + pub fn new(bvh: &'bvh BVH, test: &'test impl IntersectionAABB, shapes: &'shapes [Shape]) -> Self { BVHTraverseIterator { bvh, test, @@ -102,10 +102,10 @@ impl<'a, Shape: Bounded> BVHTraverseIterator<'a, Shape> { } } -impl<'a, Shape: Bounded> Iterator for BVHTraverseIterator<'a, Shape> { - type Item = &'a Shape; +impl<'bvh, 'test, 'shapes, Shape: Bounded> Iterator for BVHTraverseIterator<'bvh, 'test, 'shapes, Shape> { + type Item = &'shapes Shape; - fn next(&mut self) -> Option<&'a Shape> { + fn next(&mut self) -> Option<&'shapes Shape> { loop { if self.is_stack_empty() && !self.has_node { // Completed traversal. diff --git a/src/lib.rs b/src/lib.rs index 8e2bb88..748a410 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,33 +134,7 @@ pub use shapes::*; //pub use shapes::{Ray, AABB, OBB, Capsule, Sphere}; use aabb::{Bounded, AABB}; use bounding_hierarchy::BHShape; - - -#[derive(Debug)] -struct Sphere { - position: Point3, - radius: Real, - node_index: usize, -} - -impl Bounded for Sphere { - fn aabb(&self) -> AABB { - let half_size = Vector3::new(self.radius, self.radius, self.radius); - let min = self.position - half_size; - let max = self.position + half_size; - AABB::with_bounds(min, max) - } -} - -impl BHShape for Sphere { - fn set_bh_node_index(&mut self, index: usize) { - self.node_index = index; - } - - fn bh_node_index(&self) -> usize { - self.node_index - } -} +use shapes::ray::IntersectionRay; /// A triangle struct. Instance of a more complex `Bounded` primitive. #[derive(Debug)] @@ -172,18 +146,16 @@ pub struct Triangle { /// Third point on the triangle pub c: Point3, aabb: AABB, - node_index: usize, } impl Triangle { - /// Creates a new triangle given a clockwise set of points + /// Creates a new triangle given a counter clockwise set of points pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { Triangle { a, b, c, aabb: AABB::empty().grow(&a).grow(&b).grow(&c), - node_index: 0, } } } @@ -193,3 +165,14 @@ impl Bounded for Triangle { self.aabb } } + +impl IntersectionRay for Triangle { + fn intersects_ray(&self, ray: &ray::Ray, t_min: Real, t_max: Real) -> Option { + let inter = ray.intersects_triangle(&self.a, &self.b, &self.c); + if inter.distance < Real::INFINITY { + Some(inter) + } else { + None + } + } +} diff --git a/src/shapes/ray.rs b/src/shapes/ray.rs index b45f12a..dead143 100644 --- a/src/shapes/ray.rs +++ b/src/shapes/ray.rs @@ -7,7 +7,7 @@ use crate::{Point3, Vector3}; use crate::{Real, EPSILON}; /// A struct which defines a ray and some of its cached values. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Ray { /// The ray origin. pub origin: Point3, @@ -47,6 +47,7 @@ pub struct Ray { /// A struct which is returned by the `intersects_triangle` method. +#[derive(Debug, Clone, Copy)] pub struct Intersection { /// Distance from the ray origin to the intersection point. pub distance: Real, @@ -323,7 +324,11 @@ impl Ray { let dist = a_to_c.dot(v_vec) * inv_det; if dist > EPSILON { - Intersection::new(dist, u, v, Vector3::ZERO, false) + let mut normal = Vector3::ZERO; + normal.x = (a_to_b.y * a_to_c.z) - (a_to_b.z * a_to_c.y); + normal.y = (a_to_b.z * a_to_c.x) - (a_to_b.x * a_to_c.z); + normal.z = (a_to_b.x * a_to_c.y) - (a_to_b.y * a_to_c.x); + Intersection::new(dist, u, v, normal.normalize(), false) } else { Intersection::new(Real::INFINITY, u, v, Vector3::ZERO, false) } diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 5e9ff43..a9ce585 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -1,8 +1,11 @@ //! This module defines a Sphere and it's intersection algorithms -use crate::{Point3, Real, bounding_hierarchy::IntersectionAABB, aabb::AABB, ray::{IntersectionRay, Ray, Intersection}, Vector3}; +use std::f32::consts::PI; + +use crate::{Point3, Real, bounding_hierarchy::IntersectionAABB, aabb::{AABB, Bounded}, ray::{IntersectionRay, Ray, Intersection}, Vector3}; /// A representation of a Sphere +#[derive(Debug, Clone, Copy)] pub struct Sphere { /// Center of the sphere pub center: Point3, @@ -46,8 +49,24 @@ impl IntersectionRay for Sphere { } } - let out_norm = (ray.at(toi) - self.center).normalize(); + let hit = ray.at(toi); + + let out_norm = (hit - self.center) / self.radius; + + let theta = (-out_norm.y).acos(); + let phi = (-out_norm.z).atan2(out_norm.x) + PI; + let u = phi / (2. * PI); + let v = theta / PI; + let (norm, back_face) = ray.face_normal(out_norm); - Some(Intersection::new(toi, 0., 0., norm, back_face)) + Some(Intersection::new(toi, u, v, norm, back_face)) + } +} + +impl Bounded for Sphere { + fn aabb(&self) -> AABB { + let min = self.center - Vector3::splat(self.radius); + let max = self.center + Vector3::splat(self.radius); + AABB::with_bounds(min, max) } } \ No newline at end of file From b137f65b4523d56b42ecf3c86abfc8d1d18e93f8 Mon Sep 17 00:00:00 2001 From: Derek Benson Date: Wed, 19 Jan 2022 08:16:32 -0500 Subject: [PATCH 27/37] remove dyn dispatch from iterator, fix sphere compilation error --- src/bvh/bvh_impl.rs | 6 +++--- src/bvh/iter.rs | 10 +++++----- src/lib.rs | 2 ++ src/shapes/ray.rs | 10 +++++----- src/shapes/sphere.rs | 4 +--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index d115076..269cf8b 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -672,11 +672,11 @@ impl BVH { /// [`BVH`]: struct.BVH.html /// [`AABB`]: ../aabb/struct.AABB.html /// - pub fn traverse_iterator<'bvh, 'test, 'shapes, Shape: Bounded>( + pub fn traverse_iterator<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB>( &'bvh self, - test: &'test impl IntersectionAABB, + test: &'test Test, shapes: &'shapes [Shape], - ) -> BVHTraverseIterator<'bvh, 'test, 'shapes, Shape> { + ) -> BVHTraverseIterator<'bvh, 'test, 'shapes, Shape, Test> { BVHTraverseIterator::new(self, test, shapes) } diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index 1fbb259..e6ef5f2 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -6,11 +6,11 @@ use crate::bvh::{BVHNode, BVH}; /// Iterator to traverse a [`BVH`] without memory allocations #[allow(clippy::upper_case_acronyms)] -pub struct BVHTraverseIterator<'bvh, 'test, 'shapes, Shape: Bounded> { +pub struct BVHTraverseIterator<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> { /// Reference to the BVH to traverse bvh: &'bvh BVH, /// Reference to the input ray - test: &'test dyn IntersectionAABB, + test: &'test Test, /// Reference to the input shapes array shapes: &'shapes [Shape], /// Traversal stack. Allocates if exceeds depth of 64 @@ -21,9 +21,9 @@ pub struct BVHTraverseIterator<'bvh, 'test, 'shapes, Shape: Bounded> { has_node: bool, } -impl<'bvh, 'test, 'shapes, Shape: Bounded> BVHTraverseIterator<'bvh, 'test, 'shapes, Shape> { +impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> BVHTraverseIterator<'bvh, 'test, 'shapes, Shape, Test> { /// Creates a new `BVHTraverseIterator` - pub fn new(bvh: &'bvh BVH, test: &'test impl IntersectionAABB, shapes: &'shapes [Shape]) -> Self { + pub fn new(bvh: &'bvh BVH, test: &'test Test, shapes: &'shapes [Shape]) -> Self { BVHTraverseIterator { bvh, test, @@ -102,7 +102,7 @@ impl<'bvh, 'test, 'shapes, Shape: Bounded> BVHTraverseIterator<'bvh, 'test, 'sha } } -impl<'bvh, 'test, 'shapes, Shape: Bounded> Iterator for BVHTraverseIterator<'bvh, 'test, 'shapes, Shape> { +impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> Iterator for BVHTraverseIterator<'bvh, 'test, 'shapes, Shape, Test> { type Item = &'shapes Shape; fn next(&mut self) -> Option<&'shapes Shape> { diff --git a/src/lib.rs b/src/lib.rs index 748a410..47834d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,8 @@ pub type Real = f32; /// A minimal floating value used as a lower bound. /// TODO: replace by/add ULPS/relative float comparison methods. pub const EPSILON: Real = 0.00001; +/// Const for PI +pub const PI: Real = std::f64::consts::PI as Real; pub mod axis; pub mod bounding_hierarchy; diff --git a/src/shapes/ray.rs b/src/shapes/ray.rs index dead143..5987bb7 100644 --- a/src/shapes/ray.rs +++ b/src/shapes/ray.rs @@ -324,11 +324,11 @@ impl Ray { let dist = a_to_c.dot(v_vec) * inv_det; if dist > EPSILON { - let mut normal = Vector3::ZERO; - normal.x = (a_to_b.y * a_to_c.z) - (a_to_b.z * a_to_c.y); - normal.y = (a_to_b.z * a_to_c.x) - (a_to_b.x * a_to_c.z); - normal.z = (a_to_b.x * a_to_c.y) - (a_to_b.y * a_to_c.x); - Intersection::new(dist, u, v, normal.normalize(), false) + let mut normal = Vector3::X; + // normal.x = (a_to_b.y * a_to_c.z) - (a_to_b.z * a_to_c.y); + // normal.y = (a_to_b.z * a_to_c.x) - (a_to_b.x * a_to_c.z); + // normal.z = (a_to_b.x * a_to_c.y) - (a_to_b.y * a_to_c.x); + Intersection::new(dist, u, v, normal, false) } else { Intersection::new(Real::INFINITY, u, v, Vector3::ZERO, false) } diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index a9ce585..22a2db3 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -1,8 +1,6 @@ //! This module defines a Sphere and it's intersection algorithms -use std::f32::consts::PI; - -use crate::{Point3, Real, bounding_hierarchy::IntersectionAABB, aabb::{AABB, Bounded}, ray::{IntersectionRay, Ray, Intersection}, Vector3}; +use crate::{Point3, Real, bounding_hierarchy::IntersectionAABB, aabb::{AABB, Bounded}, ray::{IntersectionRay, Ray, Intersection}, Vector3, PI}; /// A representation of a Sphere #[derive(Debug, Clone, Copy)] From 9e405230aec151d4c1a068abb57bf65c91cf725d Mon Sep 17 00:00:00 2001 From: Derek Benson Date: Wed, 19 Jan 2022 16:24:01 -0500 Subject: [PATCH 28/37] add best_first traversal to bvh, run cargo fmt --- bvh-lib/src/lib.rs | 2 +- src/bounding_hierarchy.rs | 6 +- src/bvh/best_first.rs | 129 ++++++++++++++++++++++++++++++++++++++ src/bvh/bvh_impl.rs | 5 +- src/bvh/iter.rs | 8 ++- src/bvh/mod.rs | 2 + src/bvh/optimization.rs | 2 - src/lib.rs | 7 ++- src/shapes/aabb.rs | 2 +- src/shapes/capsule.rs | 8 ++- src/shapes/mod.rs | 3 +- src/shapes/obb.rs | 5 +- src/shapes/ray.rs | 72 +++++++++++++++++---- src/shapes/sphere.rs | 13 ++-- src/testbase.rs | 2 - src/utils.rs | 2 +- 16 files changed, 228 insertions(+), 40 deletions(-) create mode 100644 src/bvh/best_first.rs diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index bc6d4e7..69a5005 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -6,8 +6,8 @@ use bvh_f64::bvh::BVH; use bvh_f64::capsule::Capsule; use bvh_f64::obb::OBB; use bvh_f64::ray::Ray; -use bvh_f64::Vector3; use bvh_f64::sphere::Sphere; +use bvh_f64::Vector3; use flexi_logger::{detailed_format, FileSpec, Logger}; use glam::DQuat; use interoptopus::lang::c::{ diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index 82e7f5f..c3ac6be 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -180,11 +180,11 @@ pub trait BoundingHierarchy { /// This trait can be implemented on anything that can intersect with an `AABB` /// Used to traverse the `BVH` -/// +/// /// [`AABB`]: ../aabb/struct.AABB.html -/// +/// /// [`BVH`]: ../bvh/struct.BVH.html -/// +/// pub trait IntersectionAABB { /// Returns true if there is an intersection with the given AABB fn intersects_aabb(&self, aabb: &AABB) -> bool; diff --git a/src/bvh/best_first.rs b/src/bvh/best_first.rs new file mode 100644 index 0000000..3beb946 --- /dev/null +++ b/src/bvh/best_first.rs @@ -0,0 +1,129 @@ +use std::borrow::BorrowMut; +use std::cell::RefCell; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +use crate::bvh::BVH; +use crate::{aabb::AABB, bounding_hierarchy::BHShape, Real}; + +use super::BVHNode; + +thread_local! { + /// Thread local for doing a best first traversal of the bvh + static HEAP: RefCell>> = RefCell::new(Default::default()); +} + +/// Used to traverse the results from intersections +#[derive(Debug, Clone, Copy)] +pub struct BvhTraversalRes { + /// Squared distance to min intersection + pub t_min_squared: Real, + /// bvh node to test next + pub node_index: usize, +} + +impl BvhTraversalRes { + /// Create new instance of BvhTraversalRes + pub fn new(node_index: usize, t_min: Real) -> Self { + Self { + node_index, + t_min_squared: t_min, + } + } +} + +impl Ord for BvhTraversalRes { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.t_min_squared + .partial_cmp(&other.t_min_squared) + .unwrap_or(Ordering::Equal) + .reverse() + } +} +impl PartialOrd for BvhTraversalRes { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(&other)) + } +} + +impl PartialEq for BvhTraversalRes { + fn eq(&self, other: &Self) -> bool { + self.node_index == other.node_index + } +} + +impl Eq for BvhTraversalRes {} + +impl BVH { + /// Walk BVH with the closest nodes first + pub fn traverse_best_first_with_heap( + &self, + t_min: Real, + t_max: Real, + test_aabb: impl Fn(&AABB) -> Option, + test_shape: impl Fn(usize) -> Option<(Real, Res)>, + heap: &mut BinaryHeap, + ) -> Option { + heap.clear(); + heap.push(BvhTraversalRes::new(0, 0.)); + + let mut result = None; + let mut curr_min = Real::INFINITY; + + while let Some(next) = heap.pop() { + if curr_min < next.t_min_squared { + break; + } + + let node = self.nodes[next.node_index]; + match node { + BVHNode::Leaf { shape_index, .. } => { + if let Some((dist, res)) = test_shape(shape_index) { + let dist_squared = dist * dist; + if dist_squared < curr_min && dist > t_min && dist < t_max { + curr_min = dist_squared; + result = Some(res); + } + } + } + BVHNode::Node { + child_l_index, + child_l_aabb, + child_r_index, + child_r_aabb, + .. + } => { + if let Some(l_min) = test_aabb(&child_l_aabb) { + heap.push(BvhTraversalRes::new(child_l_index, l_min)) + } + if let Some(r_min) = test_aabb(&child_r_aabb) { + heap.push(BvhTraversalRes::new(child_r_index, r_min)) + } + } + } + } + result + } + + /// Traverses best first using a thread local heap + pub fn traverse_best_first( + &self, + t_min: Real, + t_max: Real, + test_aabb: impl Fn(&AABB) -> Option, + test_shape: impl Fn(usize) -> Option<(Real, Res)>, + ) -> Option { + let mut heap = HEAP.with(|h| { + if let Some(x) = h.borrow_mut().pop() { + x + } else { + Default::default() + } + }); + let res = + self.traverse_best_first_with_heap(t_min, t_max, test_aabb, test_shape, &mut heap); + + HEAP.with(|h| h.borrow_mut().push(heap)); + res + } +} diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 269cf8b..c924d6a 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -610,9 +610,7 @@ impl BVH { /// pub fn build(shapes: &mut [Shape]) -> BVH { if shapes.len() == 0 { - return BVH { - nodes: Vec::new() - } + return BVH { nodes: Vec::new() }; } let mut indices = (0..shapes.len()).collect::>(); @@ -629,7 +627,6 @@ impl BVH { BVH { nodes } } - /// Rebuilds a [`BVH`] from the `shapes` slice. Reuses the existing allocated space /// /// [`BVH`]: struct.BVH.html diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index e6ef5f2..3222df9 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -21,7 +21,9 @@ pub struct BVHTraverseIterator<'bvh, 'test, 'shapes, Shape: Bounded, Test: Inter has_node: bool, } -impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> BVHTraverseIterator<'bvh, 'test, 'shapes, Shape, Test> { +impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> + BVHTraverseIterator<'bvh, 'test, 'shapes, Shape, Test> +{ /// Creates a new `BVHTraverseIterator` pub fn new(bvh: &'bvh BVH, test: &'test Test, shapes: &'shapes [Shape]) -> Self { BVHTraverseIterator { @@ -102,7 +104,9 @@ impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> BVHTraverseIt } } -impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> Iterator for BVHTraverseIterator<'bvh, 'test, 'shapes, Shape, Test> { +impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> Iterator + for BVHTraverseIterator<'bvh, 'test, 'shapes, Shape, Test> +{ type Item = &'shapes Shape; fn next(&mut self) -> Option<&'shapes Shape> { diff --git a/src/bvh/mod.rs b/src/bvh/mod.rs index c3e56fa..50829f7 100644 --- a/src/bvh/mod.rs +++ b/src/bvh/mod.rs @@ -3,10 +3,12 @@ //! [`BVH`]: struct.BVH.html //! +mod best_first; mod bvh_impl; mod iter; mod optimization; +pub use self::best_first::*; pub use self::bvh_impl::*; pub use self::iter::*; pub use self::optimization::*; diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 0434179..9d4d2c5 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -10,10 +10,8 @@ use crate::bounding_hierarchy::BHShape; use crate::{bvh::*, EPSILON}; - use log::info; - impl BVH { /// Optimizes the `BVH` by batch-reorganizing updated nodes. /// Based on https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/ssBVH.cs diff --git a/src/lib.rs b/src/lib.rs index 47834d4..1b21181 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,12 @@ impl Bounded for Triangle { } impl IntersectionRay for Triangle { - fn intersects_ray(&self, ray: &ray::Ray, t_min: Real, t_max: Real) -> Option { + fn intersects_ray( + &self, + ray: &ray::Ray, + t_min: Real, + t_max: Real, + ) -> Option { let inter = ray.intersects_triangle(&self.a, &self.b, &self.c); if inter.distance < Real::INFINITY { Some(inter) diff --git a/src/shapes/aabb.rs b/src/shapes/aabb.rs index de71121..0415c5a 100644 --- a/src/shapes/aabb.rs +++ b/src/shapes/aabb.rs @@ -562,7 +562,7 @@ impl AABB { } /// Returns the closest point inside the `AABB` to a target point - /// + /// /// [`AABB`]: struct.AABB.html /// pub fn closest_point(&self, point: Point3) -> Point3 { diff --git a/src/shapes/capsule.rs b/src/shapes/capsule.rs index 3933426..3a07f45 100644 --- a/src/shapes/capsule.rs +++ b/src/shapes/capsule.rs @@ -1,6 +1,8 @@ //! This module defines Capsules and their intersection algorithms -use crate::{Point3, Vector3, Real, bounding_hierarchy::IntersectionAABB, aabb::AABB, utils::nearest_point_on_line}; - +use crate::{ + aabb::AABB, bounding_hierarchy::IntersectionAABB, utils::nearest_point_on_line, Point3, Real, + Vector3, +}; /// Representation of a capsule pub struct Capsule { @@ -73,4 +75,4 @@ impl IntersectionAABB for Capsule { } } } -} \ No newline at end of file +} diff --git a/src/shapes/mod.rs b/src/shapes/mod.rs index 8edcfec..1565b52 100644 --- a/src/shapes/mod.rs +++ b/src/shapes/mod.rs @@ -4,7 +4,6 @@ pub mod obb; pub mod ray; pub mod sphere; - #[cfg(test)] mod tests { use crate::aabb::AABB; @@ -63,4 +62,4 @@ mod tests { assert!(obb.intersects_aabb(&aabb)); } -} \ No newline at end of file +} diff --git a/src/shapes/obb.rs b/src/shapes/obb.rs index f31669d..1526156 100644 --- a/src/shapes/obb.rs +++ b/src/shapes/obb.rs @@ -1,6 +1,5 @@ //! This module defines an Oriented Bounding Box and its intersection properties -use crate::{Quat, Vector3, aabb::AABB, bounding_hierarchy::IntersectionAABB, Mat4}; - +use crate::{aabb::AABB, bounding_hierarchy::IntersectionAABB, Mat4, Quat, Vector3}; /// Represents a box that can be rotated in any direction pub struct OBB { @@ -168,4 +167,4 @@ fn back(matrix: Mat4) -> Vector3 { fn translation(matrix: Mat4) -> Vector3 { matrix.row(3).truncate().into() -} \ No newline at end of file +} diff --git a/src/shapes/ray.rs b/src/shapes/ray.rs index 5987bb7..b024f5a 100644 --- a/src/shapes/ray.rs +++ b/src/shapes/ray.rs @@ -43,9 +43,6 @@ pub struct Ray { sign_z: usize, } - - - /// A struct which is returned by the `intersects_triangle` method. #[derive(Debug, Clone, Copy)] pub struct Intersection { @@ -62,14 +59,20 @@ pub struct Intersection { pub norm: Vector3, /// Whether the intersect was a backface - pub back_face: bool + pub back_face: bool, } impl Intersection { /// Constructs an `Intersection`. `distance` should be set to positive infinity, /// if the intersection does not occur. pub fn new(distance: Real, u: Real, v: Real, norm: Vector3, back_face: bool) -> Intersection { - Intersection { distance, u, v, norm, back_face } + Intersection { + distance, + u, + v, + norm, + back_face, + } } } @@ -334,6 +337,58 @@ impl Ray { } } + /// Returns the t_min of the aabb intersection + pub fn intersects_aabb_dist(&self, aabb: &AABB) -> Option { + let x_min = (aabb[self.sign_x].x - self.origin.x) * self.inv_direction.x; + let x_max = (aabb[1 - self.sign_x].x - self.origin.x) * self.inv_direction.x; + let mut ray_min = x_min; + let mut ray_max = x_max; + + let y_min = (aabb[self.sign_y].y - self.origin.y) * self.inv_direction.y; + let y_max = (aabb[1 - self.sign_y].y - self.origin.y) * self.inv_direction.y; + + if (ray_min > y_max) || (y_min > ray_max) { + return None; + } + + if y_min > ray_min { + ray_min = y_min; + } + // Using the following solution significantly decreases the performance + // ray_min = ray_min.max(y_min); + + if y_max < ray_max { + ray_max = y_max; + } + // Using the following solution significantly decreases the performance + // ray_max = ray_max.min(y_max); + + let z_min = (aabb[self.sign_z].z - self.origin.z) * self.inv_direction.z; + let z_max = (aabb[1 - self.sign_z].z - self.origin.z) * self.inv_direction.z; + + if (ray_min > z_max) || (z_min > ray_max) { + return None; + } + + // Only required for bounded intersection intervals. + // if z_min > ray_min { + // ray_min = z_min; + // } + + if z_max < ray_max { + ray_max = z_max; + } + // Using the following solution significantly decreases the performance + // ray_max = ray_max.min(y_max); + + if ray_max < 0.0 { + return None; + } else { + let min = Vector3::new(x_min, y_min, z_min).length_squared(); + Some(min) + } + } + /// Returns the position the front of the `Ray` is after traveling dist pub fn at(&self, dist: Real) -> Vector3 { self.origin + (self.direction * dist) @@ -342,16 +397,11 @@ impl Ray { /// Given an outward normal returns whether it hit a back_face and adjusts the normal pub fn face_normal(&self, out_norm: Vector3) -> (Vector3, bool) { let back_face = self.direction.dot(out_norm) >= 0.; - let norm = if back_face { - -out_norm - } else { - out_norm - }; + let norm = if back_face { -out_norm } else { out_norm }; (norm, back_face) } } - #[cfg(test)] mod tests { use crate::Real; diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 22a2db3..7cd21ae 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -1,6 +1,11 @@ //! This module defines a Sphere and it's intersection algorithms -use crate::{Point3, Real, bounding_hierarchy::IntersectionAABB, aabb::{AABB, Bounded}, ray::{IntersectionRay, Ray, Intersection}, Vector3, PI}; +use crate::{ + aabb::{Bounded, AABB}, + bounding_hierarchy::IntersectionAABB, + ray::{Intersection, IntersectionRay, Ray}, + Point3, Real, Vector3, PI, +}; /// A representation of a Sphere #[derive(Debug, Clone, Copy)] @@ -31,7 +36,7 @@ impl IntersectionRay for Sphere { let a = ray.direction.length_squared(); let half_b = oc.dot(ray.direction); let c = oc.length_squared() - self.radius * self.radius; - let discriminant = half_b*half_b - a*c; + let discriminant = half_b * half_b - a * c; if discriminant < 0. { return None; @@ -43,7 +48,7 @@ impl IntersectionRay for Sphere { if toi < t_min || t_max < toi { toi = (-half_b + sqrtd) / a; if toi < t_min || t_max < toi { - return None + return None; } } @@ -67,4 +72,4 @@ impl Bounded for Sphere { let max = self.center + Vector3::splat(self.radius); AABB::with_bounds(min, max) } -} \ No newline at end of file +} diff --git a/src/testbase.rs b/src/testbase.rs index 3a54bc4..42e31d6 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -3,7 +3,6 @@ use std::collections::HashSet; - use crate::{Point3, Real, Vector3}; use num::{FromPrimitive, Integer}; use obj::raw::object::Polygon; @@ -13,7 +12,6 @@ use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::SeedableRng; - use crate::aabb::{Bounded, AABB}; use crate::bounding_hierarchy::{BHShape, BoundingHierarchy}; use crate::ray::Ray; diff --git a/src/utils.rs b/src/utils.rs index 3d53dea..7803d20 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,8 @@ //! Utilities module. -use crate::{Point3, Vector3, Real}; use crate::aabb::AABB; use crate::bounding_hierarchy::BHShape; +use crate::{Point3, Real, Vector3}; /// Concatenates the list of vectors into a single vector. /// Drains the elements from the source `vectors`. From f80bf0a3870eeed404c1569464cc42cbe2081591 Mon Sep 17 00:00:00 2001 From: Derek Benson Date: Thu, 20 Jan 2022 07:09:09 -0500 Subject: [PATCH 29/37] put ray normal calc back in --- src/shapes/ray.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shapes/ray.rs b/src/shapes/ray.rs index b024f5a..7dc2fc1 100644 --- a/src/shapes/ray.rs +++ b/src/shapes/ray.rs @@ -327,10 +327,10 @@ impl Ray { let dist = a_to_c.dot(v_vec) * inv_det; if dist > EPSILON { - let mut normal = Vector3::X; - // normal.x = (a_to_b.y * a_to_c.z) - (a_to_b.z * a_to_c.y); - // normal.y = (a_to_b.z * a_to_c.x) - (a_to_b.x * a_to_c.z); - // normal.z = (a_to_b.x * a_to_c.y) - (a_to_b.y * a_to_c.x); + let mut normal = Vector3::ZERO; + normal.x = (a_to_b.y * a_to_c.z) - (a_to_b.z * a_to_c.y); + normal.y = (a_to_b.z * a_to_c.x) - (a_to_b.x * a_to_c.z); + normal.z = (a_to_b.x * a_to_c.y) - (a_to_b.y * a_to_c.x); Intersection::new(dist, u, v, normal, false) } else { Intersection::new(Real::INFINITY, u, v, Vector3::ZERO, false) From e8fe6de7e375a385b1015cc72b8302223f6332e4 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 12 Feb 2022 08:08:04 -0500 Subject: [PATCH 30/37] fix clippy errors, delete svg file --- bvh-lib/src/lib.rs | 24 +-- flamegraph.svg | 412 -------------------------------------- src/bounding_hierarchy.rs | 8 +- src/bvh/bvh_impl.rs | 23 +-- src/shapes/aabb.rs | 6 + src/shapes/obb.rs | 12 +- src/shapes/ray.rs | 9 +- 7 files changed, 45 insertions(+), 449 deletions(-) delete mode 100644 flamegraph.svg diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index 69a5005..3cf4676 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -163,7 +163,7 @@ pub extern "C" fn init_logger(log_path: AsciiPointer) { let path = log_path.as_str().unwrap(); let file = FileSpec::default() .directory(path) - .basename("bvh_f64") + .basename("bvh_lib") .suffix("log"); Logger::try_with_str("info") .unwrap() @@ -178,11 +178,11 @@ pub extern "C" fn init_logger(log_path: AsciiPointer) { } #[no_mangle] -pub extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) { - let a = unsafe { *a_ptr }; +pub unsafe extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) { + let a = *a_ptr; let a = glam::Vec3::new(a.x, a.y, a.z); - let b = unsafe { *b_ptr }; + let b = *b_ptr; let b = glam::Vec3::new(b.x, b.y, b.z); let mut c = glam::Vec3::new(0.0, 0.0, 0.0); @@ -190,13 +190,11 @@ pub extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut c = a + b + c; } - unsafe { - *out_ptr = Float3 { - x: c.x, - y: c.y, - z: c.z, - }; - } + *out_ptr = Float3 { + x: c.x, + y: c.y, + z: c.z, + }; } #[ffi_function] @@ -477,7 +475,7 @@ fn bindings_csharp() -> Result<(), Error> { Generator::new( Config { class: "NativeBvhInterop".to_string(), - dll_name: "bvh_f64".to_string(), + dll_name: "bvh_lib".to_string(), namespace_mappings: NamespaceMappings::new("Assets.Scripts.Native"), use_unsafe: Unsafe::UnsafePlatformMemCpy, ..Config::default() @@ -491,7 +489,7 @@ fn bindings_csharp() -> Result<(), Error> { Ok(()) } -//#[test] +#[test] fn gen_bindings() { bindings_csharp().unwrap(); } diff --git a/flamegraph.svg b/flamegraph.svg deleted file mode 100644 index 387cc0a..0000000 --- a/flamegraph.svg +++ /dev/null @@ -1,412 +0,0 @@ -Flame Graph Reset ZoomSearch KERNELBASE`ReadFile (2 samples, 5.00%)KERNEL..ntdll`NtReadFile (2 samples, 5.00%)ntdll`..bvh`main (1 samples, 2.50%)bv..bvh`std::sys_common::backtrace::__rust_begin_short_backtrace<fn(),tuple (1 samples, 2.50%)bv..bvh`bvh::main (1 samples, 2.50%)bv..bvh`bvh::{{impl}}::process::{{closure}} (1 samples, 2.50%)bv..ntdll`RtlCompareMemoryUlong (15 samples, 37.50%)ntdll`RtlCompareMemoryUlongntdll`NtAllocateVirtualMemory (1 samples, 2.50%)nt..ntdll`RtlAllocateHeap (19 samples, 47.50%)ntdll`RtlAllocateHeapntdll`RtlAllocateHeap (19 samples, 47.50%)ntdll`RtlAllocateHeapntdll`RtlProtectHeap (2 samples, 5.00%)ntdll`..ntdll`RtlProtectHeap (2 samples, 5.00%)ntdll`..ntdll`RtlProtectHeap (1 samples, 2.50%)nt..ntdll`RtlCompareMemoryUlong (13 samples, 32.50%)ntdll`RtlCompareMemoryUlongntdll`RtlRegisterSecureMemoryCacheCallback (34 samples, 85.00%)ntdll`RtlRegisterSecureMemoryCacheCallbackntdll`RtlFreeHeap (15 samples, 37.50%)ntdll`RtlFreeHeapntdll`RtlGetCurrentServiceSessionId (15 samples, 37.50%)ntdll`RtlGetCurrentServiceSessionIdntdll`RtlGetCurrentServiceSessionId (15 samples, 37.50%)ntdll`RtlGetCurrentServiceSessionIdntdll`TpWaitForWait (1 samples, 2.50%)nt..ntdll`NtFreeVirtualMemory (1 samples, 2.50%)nt..all (40 samples, 100%)ntdll`memset (3 samples, 7.50%)ntdll`mems.. \ No newline at end of file diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index c3ac6be..fe5cf2c 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -180,12 +180,12 @@ pub trait BoundingHierarchy { /// This trait can be implemented on anything that can intersect with an `AABB` /// Used to traverse the `BVH` -/// +/// /// [`AABB`]: ../aabb/struct.AABB.html -/// +/// /// [`BVH`]: ../bvh/struct.BVH.html -/// +/// pub trait IntersectionAABB { - /// Returns true if there is an intersection with the given AABB + /// Returns true if there is an intersection with the given `AABB` fn intersects_aabb(&self, aabb: &AABB) -> bool; } diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index c924d6a..040ed62 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -16,6 +16,7 @@ use rayon::prelude::*; use std::borrow::BorrowMut; use std::cell::RefCell; use std::iter::repeat; +use std::mem::MaybeUninit; use std::slice; const NUM_BUCKETS: usize = 6; @@ -262,7 +263,7 @@ impl BVHNode { pub fn build( shapes: &mut [T], indices: &mut [usize], - nodes: &mut [BVHNode], + nodes: &mut [MaybeUninit], parent_index: usize, depth: u32, node_index: usize, @@ -272,10 +273,10 @@ impl BVHNode { // If there is only one element left, don't split anymore if indices.len() == 1 { let shape_index = indices[0]; - nodes[0] = BVHNode::Leaf { + nodes[0].write(BVHNode::Leaf { parent_index, shape_index, - }; + }); // Let the shape know the index of the node that represents it. shapes[shape_index].set_bh_node_index(node_index); return node_index; @@ -285,10 +286,6 @@ impl BVHNode { parallel_recurse = true; } - // From here on we handle the recursive case. This dummy is required, because the children - // must know their parent, and it's easier to update one parent node than the child nodes. - nodes[0] = BVHNode::create_dummy(); - // Find the axis along which the shapes are spread the most. let split_axis = centroid_bounds.largest_axis(); let split_axis_size = centroid_bounds.max[split_axis] - centroid_bounds.min[split_axis]; @@ -448,14 +445,13 @@ impl BVHNode { // Construct the actual data structure and replace the dummy node. //assert!(!child_l_aabb.is_empty()); //assert!(!child_r_aabb.is_empty()); - nodes[0] = BVHNode::Node { + nodes[0].write(BVHNode::Node { parent_index, child_l_aabb, child_l_index, child_r_aabb, child_r_index, - }; - + }); node_index } @@ -624,6 +620,7 @@ impl BVH { let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); BVHNode::build(shapes, &mut indices, n, 0, 0, 0, aabb, centroid); + let nodes = unsafe { std::mem::transmute(nodes) }; BVH { nodes } } @@ -636,12 +633,12 @@ impl BVH { let expected_node_count = shapes.len() * 2 - 1; self.nodes.clear(); self.nodes.reserve(expected_node_count); + let n = unsafe { std::mem::transmute(self.nodes.as_mut_slice()) }; + let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); + BVHNode::build(shapes, &mut indices, n, 0, 0, 0, aabb, centroid); unsafe { self.nodes.set_len(expected_node_count); } - let n = self.nodes.as_mut_slice(); - let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); - BVHNode::build(shapes, &mut indices, n, 0, 0, 0, aabb, centroid); } /// Traverses the [`BVH`]. diff --git a/src/shapes/aabb.rs b/src/shapes/aabb.rs index 0415c5a..318d73d 100644 --- a/src/shapes/aabb.rs +++ b/src/shapes/aabb.rs @@ -60,6 +60,12 @@ pub trait Bounded { fn aabb(&self) -> AABB; } +impl Bounded for &T where T: Bounded { + fn aabb(&self) -> AABB { + (*self).aabb() + } +} + impl AABB { /// Creates a new [`AABB`] with the given bounds. /// diff --git a/src/shapes/obb.rs b/src/shapes/obb.rs index 1526156..2c40b86 100644 --- a/src/shapes/obb.rs +++ b/src/shapes/obb.rs @@ -17,7 +17,7 @@ impl IntersectionAABB for OBB { let half_b = (aabb.max - aabb.min) * 0.5; let value = (aabb.max + aabb.min) * 0.5; let translation = self.orientation * (value - self.center); - let mat = Mat4::from_rotation_translation(self.orientation, translation.into()); + let mat = Mat4::from_rotation_translation(self.orientation, translation); let vec_1 = Vector3::new( translation.x.abs(), @@ -149,22 +149,22 @@ impl IntersectionAABB for OBB { return false; } // Intersection - return true; + true } } fn right(matrix: Mat4) -> Vector3 { - matrix.row(0).truncate().into() + matrix.row(0).truncate() } fn up(matrix: Mat4) -> Vector3 { - matrix.row(1).truncate().into() + matrix.row(1).truncate() } fn back(matrix: Mat4) -> Vector3 { - matrix.row(2).truncate().into() + matrix.row(2).truncate() } fn translation(matrix: Mat4) -> Vector3 { - matrix.row(3).truncate().into() + matrix.row(3).truncate() } diff --git a/src/shapes/ray.rs b/src/shapes/ray.rs index 7dc2fc1..b50c948 100644 --- a/src/shapes/ray.rs +++ b/src/shapes/ray.rs @@ -82,6 +82,13 @@ pub trait IntersectionRay { fn intersects_ray(&self, ray: &Ray, t_min: Real, t_max: Real) -> Option; } + +impl IntersectionRay for &T where T: IntersectionRay { + fn intersects_ray(&self, ray: &Ray, t_min: Real, t_max: Real) -> Option { + (*self).intersects_ray(ray, t_min, t_max) + } +} + impl IntersectionAABB for Ray { /// Tests the intersection of a [`Ray`] with an [`AABB`] using the optimized algorithm /// from [this paper](http://www.cs.utah.edu/~awilliam/box/box.pdf). @@ -382,7 +389,7 @@ impl Ray { // ray_max = ray_max.min(y_max); if ray_max < 0.0 { - return None; + None } else { let min = Vector3::new(x_min, y_min, z_min).length_squared(); Some(min) From fd0b05e4ee49efa127f46f609b5fca312d7b925b Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 12 Feb 2022 08:37:50 -0500 Subject: [PATCH 31/37] get rid of transmute using a safer way --- bvh-lib/src/lib.rs | 17 ++++++++++------- src/bvh/bvh_impl.rs | 19 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index 3cf4676..f77a410 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -177,6 +177,10 @@ pub extern "C" fn init_logger(log_path: AsciiPointer) { } } + +/// # Safety +/// +/// This function should not be called with invalid pointers #[no_mangle] pub unsafe extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) { let a = *a_ptr; @@ -227,7 +231,7 @@ pub extern "C" fn query_ray( let ray = Ray::new(to_vec(origin_vec), to_vec(dir_vec)); let mut i = 0; - for x in bvh.traverse_iterator(&ray, &shapes) { + for x in bvh.traverse_iterator(&ray, shapes) { if i < buffer.len() { buffer[i as usize] = *x; } @@ -252,7 +256,7 @@ pub extern "C" fn batch_query_rays( for r in 0..ray_count as usize { let ray = Ray::new(to_vec(&origins[r]), to_vec(&dirs[r])); let mut res = 0; - for x in bvh.traverse_iterator(&ray, &shapes) { + for x in bvh.traverse_iterator(&ray, shapes) { if i < buffer.len() { buffer[i as usize] = *x; } @@ -277,7 +281,7 @@ pub extern "C" fn query_sphere( let test_shape = Sphere::new(to_vec(center), radius); let mut i = 0; - for x in bvh.traverse_iterator(&test_shape, &shapes) { + for x in bvh.traverse_iterator(&test_shape, shapes) { if i < buffer.len() { buffer[i as usize] = *x; } @@ -301,7 +305,7 @@ pub extern "C" fn query_capsule( let test_shape = Capsule::new(to_vec(start), to_vec(end), radius); let mut i = 0; - for x in bvh.traverse_iterator(&test_shape, &shapes) { + for x in bvh.traverse_iterator(&test_shape, shapes) { if i < buffer.len() { buffer[i as usize] = *x; } @@ -325,7 +329,7 @@ pub extern "C" fn query_aabb( let test_shape = AABB::with_bounds(min, max); let mut i = 0; - for x in bvh.traverse_iterator(&test_shape, &shapes) { + for x in bvh.traverse_iterator(&test_shape, shapes) { if i < buffer.len() { buffer[i as usize] = *x; } @@ -353,7 +357,7 @@ pub extern "C" fn query_obb( let mut i = 0; - for x in bvh.traverse_iterator(&obb, &shapes) { + for x in bvh.traverse_iterator(&obb, shapes) { if i < buffer.len() { buffer[i as usize] = *x; } @@ -489,7 +493,6 @@ fn bindings_csharp() -> Result<(), Error> { Ok(()) } -#[test] fn gen_bindings() { bindings_csharp().unwrap(); } diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 040ed62..eccf214 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -17,6 +17,7 @@ use std::borrow::BorrowMut; use std::cell::RefCell; use std::iter::repeat; use std::mem::MaybeUninit; +use std::ptr::slice_from_raw_parts; use std::slice; const NUM_BUCKETS: usize = 6; @@ -612,15 +613,15 @@ impl BVH { let mut indices = (0..shapes.len()).collect::>(); let expected_node_count = shapes.len() * 2 - 1; let mut nodes = Vec::with_capacity(expected_node_count); + + + let uninit_slice = unsafe { slice::from_raw_parts_mut(nodes.as_mut_ptr() as *mut MaybeUninit, expected_node_count) }; + let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); + BVHNode::build(shapes, &mut indices, uninit_slice, 0, 0, 0, aabb, centroid); + unsafe { nodes.set_len(expected_node_count); } - //println!("shapes={} nodes={}", shapes.len(), nodes.len()); - let n = nodes.as_mut_slice(); - - let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); - BVHNode::build(shapes, &mut indices, n, 0, 0, 0, aabb, centroid); - let nodes = unsafe { std::mem::transmute(nodes) }; BVH { nodes } } @@ -633,9 +634,11 @@ impl BVH { let expected_node_count = shapes.len() * 2 - 1; self.nodes.clear(); self.nodes.reserve(expected_node_count); - let n = unsafe { std::mem::transmute(self.nodes.as_mut_slice()) }; + let ptr = self.nodes.as_mut_ptr(); + + let uninit_slice = unsafe { slice::from_raw_parts_mut(ptr as *mut MaybeUninit, expected_node_count) }; let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); - BVHNode::build(shapes, &mut indices, n, 0, 0, 0, aabb, centroid); + BVHNode::build(shapes, &mut indices, uninit_slice, 0, 0, 0, aabb, centroid); unsafe { self.nodes.set_len(expected_node_count); } From 88a9155ed5c83e36271b1a992e669053458d8aca Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 12 Feb 2022 08:56:20 -0500 Subject: [PATCH 32/37] run cargo fmt --- bvh-lib/src/lib.rs | 1 - src/bounding_hierarchy.rs | 6 +++--- src/bvh/bvh_impl.rs | 14 ++++++++++---- src/shapes/aabb.rs | 5 ++++- src/shapes/ray.rs | 6 ++++-- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index f77a410..efa0607 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -177,7 +177,6 @@ pub extern "C" fn init_logger(log_path: AsciiPointer) { } } - /// # Safety /// /// This function should not be called with invalid pointers diff --git a/src/bounding_hierarchy.rs b/src/bounding_hierarchy.rs index fe5cf2c..104bb77 100644 --- a/src/bounding_hierarchy.rs +++ b/src/bounding_hierarchy.rs @@ -180,11 +180,11 @@ pub trait BoundingHierarchy { /// This trait can be implemented on anything that can intersect with an `AABB` /// Used to traverse the `BVH` -/// +/// /// [`AABB`]: ../aabb/struct.AABB.html -/// +/// /// [`BVH`]: ../bvh/struct.BVH.html -/// +/// pub trait IntersectionAABB { /// Returns true if there is an intersection with the given `AABB` fn intersects_aabb(&self, aabb: &AABB) -> bool; diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index eccf214..2af8468 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -614,11 +614,15 @@ impl BVH { let expected_node_count = shapes.len() * 2 - 1; let mut nodes = Vec::with_capacity(expected_node_count); - - let uninit_slice = unsafe { slice::from_raw_parts_mut(nodes.as_mut_ptr() as *mut MaybeUninit, expected_node_count) }; + let uninit_slice = unsafe { + slice::from_raw_parts_mut( + nodes.as_mut_ptr() as *mut MaybeUninit, + expected_node_count, + ) + }; let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); BVHNode::build(shapes, &mut indices, uninit_slice, 0, 0, 0, aabb, centroid); - + unsafe { nodes.set_len(expected_node_count); } @@ -636,7 +640,9 @@ impl BVH { self.nodes.reserve(expected_node_count); let ptr = self.nodes.as_mut_ptr(); - let uninit_slice = unsafe { slice::from_raw_parts_mut(ptr as *mut MaybeUninit, expected_node_count) }; + let uninit_slice = unsafe { + slice::from_raw_parts_mut(ptr as *mut MaybeUninit, expected_node_count) + }; let (aabb, centroid) = joint_aabb_of_shapes(&indices, shapes); BVHNode::build(shapes, &mut indices, uninit_slice, 0, 0, 0, aabb, centroid); unsafe { diff --git a/src/shapes/aabb.rs b/src/shapes/aabb.rs index 318d73d..ca41262 100644 --- a/src/shapes/aabb.rs +++ b/src/shapes/aabb.rs @@ -60,7 +60,10 @@ pub trait Bounded { fn aabb(&self) -> AABB; } -impl Bounded for &T where T: Bounded { +impl Bounded for &T +where + T: Bounded, +{ fn aabb(&self) -> AABB { (*self).aabb() } diff --git a/src/shapes/ray.rs b/src/shapes/ray.rs index b50c948..9c8d043 100644 --- a/src/shapes/ray.rs +++ b/src/shapes/ray.rs @@ -82,8 +82,10 @@ pub trait IntersectionRay { fn intersects_ray(&self, ray: &Ray, t_min: Real, t_max: Real) -> Option; } - -impl IntersectionRay for &T where T: IntersectionRay { +impl IntersectionRay for &T +where + T: IntersectionRay, +{ fn intersects_ray(&self, ray: &Ray, t_min: Real, t_max: Real) -> Option { (*self).intersects_ray(ray, t_min, t_max) } From c55c37dbe3e39035e0767bce6dae397d1478cdb0 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 12 Feb 2022 11:36:27 -0500 Subject: [PATCH 33/37] fix all clippy warnings --- bvh-lib/src/lib.rs | 1 + src/bvh/best_first.rs | 5 ++--- src/bvh/bvh_impl.rs | 32 ++++++++++---------------------- src/bvh/iter.rs | 2 +- src/bvh/optimization.rs | 29 +++++++++++------------------ src/lib.rs | 3 +-- src/shapes/aabb.rs | 25 +++++++++++++++---------- src/shapes/obb.rs | 1 + src/utils.rs | 1 + 9 files changed, 43 insertions(+), 56 deletions(-) diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs index efa0607..e268df6 100644 --- a/bvh-lib/src/lib.rs +++ b/bvh-lib/src/lib.rs @@ -492,6 +492,7 @@ fn bindings_csharp() -> Result<(), Error> { Ok(()) } +#[allow(dead_code)] fn gen_bindings() { bindings_csharp().unwrap(); } diff --git a/src/bvh/best_first.rs b/src/bvh/best_first.rs index 3beb946..2d8ba7a 100644 --- a/src/bvh/best_first.rs +++ b/src/bvh/best_first.rs @@ -1,10 +1,9 @@ -use std::borrow::BorrowMut; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::BinaryHeap; use crate::bvh::BVH; -use crate::{aabb::AABB, bounding_hierarchy::BHShape, Real}; +use crate::{aabb::AABB, Real}; use super::BVHNode; @@ -42,7 +41,7 @@ impl Ord for BvhTraversalRes { } impl PartialOrd for BvhTraversalRes { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) + Some(self.cmp(other)) } } diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index 2af8468..cf3d58d 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -11,13 +11,9 @@ use crate::bvh::iter::BVHTraverseIterator; use crate::utils::{joint_aabb_of_shapes, Bucket}; use crate::EPSILON; use crate::{Point3, Real}; -use rayon::prelude::*; -use std::borrow::BorrowMut; use std::cell::RefCell; -use std::iter::repeat; use std::mem::MaybeUninit; -use std::ptr::slice_from_raw_parts; use std::slice; const NUM_BUCKETS: usize = 6; @@ -204,12 +200,11 @@ impl BVHNode { /// Returns the depth of the node. The root node has depth `0`. pub fn depth(&self, nodes: &[BVHNode]) -> u32 { let parent_i = self.parent(); - if parent_i == 0 { - if nodes[parent_i].eq(&self) { - return 0; - } + if parent_i == 0 && nodes[parent_i].eq(self) { + 0 + } else { + 1 + nodes[parent_i].depth(nodes) } - return 1 + nodes[parent_i].depth(nodes); } /// Gets the `AABB` for a `BVHNode`. @@ -247,20 +242,12 @@ impl BVHNode { } } - /// The build function sometimes needs to add nodes while their data is not available yet. - /// A dummy cerated by this function serves the purpose of being changed later on. - fn create_dummy() -> BVHNode { - BVHNode::Leaf { - parent_index: 0, - shape_index: 0, - } - } - /// Builds a [`BVHNode`] recursively using SAH partitioning. /// Returns the index of the new node in the nodes vector. /// /// [`BVHNode`]: enum.BVHNode.html /// + #[allow(clippy::too_many_arguments)] pub fn build( shapes: &mut [T], indices: &mut [usize], @@ -456,6 +443,7 @@ impl BVHNode { node_index } + #[allow(clippy::type_complexity)] fn build_buckets<'a, T: BHShape>( shapes: &mut [T], indices: &'a mut [usize], @@ -606,7 +594,7 @@ impl BVH { /// [`BVH`]: struct.BVH.html /// pub fn build(shapes: &mut [Shape]) -> BVH { - if shapes.len() == 0 { + if shapes.is_empty() { return BVH { nodes: Vec::new() }; } @@ -702,7 +690,7 @@ impl BVH { .. } => { let depth = nodes[node_index].depth(nodes); - let padding: String = repeat(" ").take(depth as usize).collect(); + let padding: String = " ".repeat(depth as usize); println!( "{}node={} parent={}", padding, @@ -716,7 +704,7 @@ impl BVH { } BVHNode::Leaf { shape_index, .. } => { let depth = nodes[node_index].depth(nodes); - let padding: String = repeat(" ").take(depth as usize).collect(); + let padding: String = " ".repeat(depth as usize); println!( "{}node={} parent={}", padding, @@ -826,7 +814,7 @@ impl BVH { node_count: &mut usize, shapes: &[Shape], ) { - if self.nodes.len() == 0 { + if self.nodes.is_empty() { return; } diff --git a/src/bvh/iter.rs b/src/bvh/iter.rs index 3222df9..b725c7b 100644 --- a/src/bvh/iter.rs +++ b/src/bvh/iter.rs @@ -38,7 +38,7 @@ impl<'bvh, 'test, 'shapes, Shape: Bounded, Test: IntersectionAABB> /// Test if stack is empty. fn is_stack_empty(&self) -> bool { - self.stack.len() == 0 + self.stack.is_empty() } /// Push node onto stack. diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 9d4d2c5..74f1749 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -105,7 +105,7 @@ impl BVH { let shape_aabb = new_shape.aabb(); let shape_sa = shape_aabb.surface_area(); - if self.nodes.len() == 0 { + if self.nodes.is_empty() { self.nodes.push(BVHNode::Leaf { parent_index: 0, shape_index: new_shape_index, @@ -150,9 +150,9 @@ impl BVH { let r_index = self.nodes.len(); let new_right = BVHNode::Node { - child_l_aabb: child_l_aabb, + child_l_aabb, child_l_index, - child_r_aabb: child_r_aabb.clone(), + child_r_aabb, child_r_index, parent_index: i, }; @@ -220,7 +220,7 @@ impl BVH { let child_r_index = self.nodes.len(); let new_right = BVHNode::Leaf { parent_index: i, - shape_index: shape_index, + shape_index, }; shapes[shape_index].set_bh_node_index(child_r_index); self.nodes.push(new_right); @@ -249,7 +249,7 @@ impl BVH { deleted_shape_index: usize, swap_shape: bool, ) { - if self.nodes.len() == 0 { + if self.nodes.is_empty() { return; //panic!("can't remove a node from a bvh with only one node"); } @@ -349,9 +349,8 @@ impl BVH { if deleted_shape_index < end_shape { shapes.swap(deleted_shape_index, end_shape); let node_index = shapes[deleted_shape_index].bh_node_index(); - match self.nodes[node_index].shape_index_mut() { - Some(index) => *index = deleted_shape_index, - _ => {} + if let Some(index) = self.nodes[node_index].shape_index_mut() { + *index = deleted_shape_index } } } @@ -404,16 +403,10 @@ impl BVH { if node_index != end { self.nodes[node_index] = self.nodes[end]; let parent_index = self.nodes[node_index].parent(); - match self.nodes[parent_index] { - BVHNode::Leaf { .. } => { - // println!( - // "truncating early node_parent={} parent_index={} shape_index={}", - // node_parent, parent_index, shape_index - // ); - self.nodes.truncate(end); - return; - } - _ => {} + + if let BVHNode::Leaf { .. } = self.nodes[parent_index] { + self.nodes.truncate(end); + return; } let parent = self.nodes[parent_index]; let moved_left = parent.child_l() == end; diff --git a/src/lib.rs b/src/lib.rs index 1b21181..c4fab23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,7 +135,6 @@ mod testbase; pub use shapes::*; //pub use shapes::{Ray, AABB, OBB, Capsule, Sphere}; use aabb::{Bounded, AABB}; -use bounding_hierarchy::BHShape; use shapes::ray::IntersectionRay; /// A triangle struct. Instance of a more complex `Bounded` primitive. @@ -176,7 +175,7 @@ impl IntersectionRay for Triangle { t_max: Real, ) -> Option { let inter = ray.intersects_triangle(&self.a, &self.b, &self.c); - if inter.distance < Real::INFINITY { + if inter.distance <= t_max && inter.distance >= t_min { Some(inter) } else { None diff --git a/src/shapes/aabb.rs b/src/shapes/aabb.rs index ca41262..b8ac609 100644 --- a/src/shapes/aabb.rs +++ b/src/shapes/aabb.rs @@ -139,12 +139,12 @@ impl AABB { /// [`Point3`]: glam::Vec3 /// pub fn contains(&self, p: &Point3) -> bool { - !(p.x < self.min.x - || p.x > self.max.x - || p.y < self.min.y - || p.y > self.max.y - || p.z < self.min.z - || p.z > self.max.z) + p.x >= self.min.x + && p.x <= self.max.x + && p.y >= self.min.y + && p.y <= self.max.y + && p.z >= self.min.z + && p.z <= self.max.z } /// Returns true if the [`Point3`] is approximately inside the [`AABB`] @@ -257,6 +257,7 @@ impl AABB { /// /// [`AABB`]: struct.AABB.html /// + #[must_use] pub fn join(&self, other: &AABB) -> AABB { AABB::with_bounds( Point3::new( @@ -346,6 +347,7 @@ impl AABB { /// [`AABB`]: struct.AABB.html /// [`Point3`]: glam::Vec3 /// + #[must_use] pub fn grow(&self, other: &Point3) -> AABB { AABB::with_bounds( Point3::new( @@ -429,6 +431,7 @@ impl AABB { /// [`AABB`]: struct.AABB.html /// [`Bounded`]: trait.Bounded.html /// + #[must_use] pub fn join_bounded(&self, other: &T) -> AABB { self.join(&other.aabb()) } @@ -581,10 +584,12 @@ impl AABB { impl IntersectionAABB for AABB { fn intersects_aabb(&self, aabb: &AABB) -> bool { - !(self.max.x < aabb.min.x) - && !(self.min.x > aabb.max.x) - && (!(self.max.y < aabb.min.y) && !(self.min.y > aabb.max.y)) - && (!(self.max.z < aabb.min.z) && !(self.min.z > aabb.max.z)) + self.max.x >= aabb.min.x + && self.min.x <= aabb.max.x + && self.max.y >= aabb.min.y + && self.min.y <= aabb.max.y + && self.max.z >= aabb.min.z + && self.min.z <= aabb.max.z } } diff --git a/src/shapes/obb.rs b/src/shapes/obb.rs index 2c40b86..b182a3e 100644 --- a/src/shapes/obb.rs +++ b/src/shapes/obb.rs @@ -165,6 +165,7 @@ fn back(matrix: Mat4) -> Vector3 { matrix.row(2).truncate() } +#[allow(dead_code)] fn translation(matrix: Mat4) -> Vector3 { matrix.row(3).truncate() } diff --git a/src/utils.rs b/src/utils.rs index 7803d20..6ca028a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,6 +6,7 @@ use crate::{Point3, Real, Vector3}; /// Concatenates the list of vectors into a single vector. /// Drains the elements from the source `vectors`. +#[allow(dead_code)] pub fn concatenate_vectors(vectors: &mut [Vec]) -> Vec { let mut result = Vec::new(); for vector in vectors.iter_mut() { From 016ac8d336472f64cb8100313286a31be93eaacc Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Sat, 12 Feb 2022 16:51:55 -0500 Subject: [PATCH 34/37] move triangle impl, fix test failure --- src/bvh/optimization.rs | 29 ----------- src/shapes/capsule.rs | 2 + src/shapes/mod.rs | 1 + src/shapes/obb.rs | 2 + src/shapes/ray.rs | 3 +- src/shapes/sphere.rs | 5 +- src/shapes/triangle.rs | 113 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 32 deletions(-) create mode 100644 src/shapes/triangle.rs diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index 74f1749..f8c243c 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -810,35 +810,6 @@ mod tests { bvh.assert_reachable(&triangles); } - #[test] - #[cfg(feature = "serde_impls")] - fn test_bad_bvh() { - let bvh_str = std::fs::read_to_string("bvh_2.json").expect("unable to read file"); - let refit_str = std::fs::read_to_string("refitshapes_2.json").expect("unable to read file"); - let shapes_str = std::fs::read_to_string("shapes_2.json").expect("unable to read file"); - - let mut bvh: BVH = serde_json::from_str(&bvh_str).expect("to parse"); - dbg!(&bvh.nodes[1]); - let mut shapes: Vec = serde_json::from_str(&shapes_str).expect("to parse"); - let refit_shapes: Vec = serde_json::from_str(&refit_str).expect("to parse"); - for (i, shape) in shapes.iter().enumerate() { - let bh_index = shape.bh_node_index(); - let node = bvh.nodes[bh_index]; - let parent = bvh.nodes[node.parent()]; - let bh_aabb = if bvh.node_is_left_child(bh_index) { - parent.child_l_aabb() - } else { - parent.child_r_aabb() - }; - if refit_shapes.contains(&i) { - assert!(!bh_aabb.relative_eq(&shape.aabb(), EPSILON)) - } else { - assert!(bh_aabb.relative_eq(&shape.aabb(), EPSILON)) - } - } - bvh.optimize(&refit_shapes, &mut shapes); - } - #[test] fn test_optimize_bvh_12_75p() { let bounds = default_bounds(); diff --git a/src/shapes/capsule.rs b/src/shapes/capsule.rs index 3a07f45..dc99e77 100644 --- a/src/shapes/capsule.rs +++ b/src/shapes/capsule.rs @@ -5,6 +5,8 @@ use crate::{ }; /// Representation of a capsule +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] pub struct Capsule { /// Start point of the line segment for the capsule pub start: Point3, diff --git a/src/shapes/mod.rs b/src/shapes/mod.rs index 1565b52..f0ab150 100644 --- a/src/shapes/mod.rs +++ b/src/shapes/mod.rs @@ -3,6 +3,7 @@ pub mod capsule; pub mod obb; pub mod ray; pub mod sphere; +pub mod triangle; #[cfg(test)] mod tests { diff --git a/src/shapes/obb.rs b/src/shapes/obb.rs index b182a3e..3e8ea43 100644 --- a/src/shapes/obb.rs +++ b/src/shapes/obb.rs @@ -2,6 +2,8 @@ use crate::{aabb::AABB, bounding_hierarchy::IntersectionAABB, Mat4, Quat, Vector3}; /// Represents a box that can be rotated in any direction +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] pub struct OBB { /// Orientation of the OBB pub orientation: Quat, diff --git a/src/shapes/ray.rs b/src/shapes/ray.rs index 9c8d043..f9592d3 100644 --- a/src/shapes/ray.rs +++ b/src/shapes/ray.rs @@ -7,7 +7,8 @@ use crate::{Point3, Vector3}; use crate::{Real, EPSILON}; /// A struct which defines a ray and some of its cached values. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] pub struct Ray { /// The ray origin. pub origin: Point3, diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 7cd21ae..252b861 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -1,4 +1,4 @@ -//! This module defines a Sphere and it's intersection algorithms +//! This module defines a Sphere and its intersection algorithms use crate::{ aabb::{Bounded, AABB}, @@ -8,7 +8,8 @@ use crate::{ }; /// A representation of a Sphere -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] pub struct Sphere { /// Center of the sphere pub center: Point3, diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs new file mode 100644 index 0000000..6040875 --- /dev/null +++ b/src/shapes/triangle.rs @@ -0,0 +1,113 @@ +//! This module defines a Triangle and its intersection algorithms + +use crate::aabb::{Bounded, AABB}; +use crate::bounding_hierarchy::IntersectionAABB; +use crate::shapes::ray::{Intersection, IntersectionRay, Ray}; +use crate::{Point3, Real, Vector3}; + +/// A triangle struct. Instance of a more complex `Bounded` primitive. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde_impls", derive(serde::Serialize, serde::Deserialize))] +pub struct Triangle { + /// First point on the triangle + pub a: Point3, + /// Second point on the triangle + pub b: Point3, + /// Third point on the triangle + pub c: Point3, + aabb: AABB, +} + +impl Triangle { + /// Creates a new triangle given a counter clockwise set of points + pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { + Triangle { + a, + b, + c, + aabb: AABB::empty().grow(&a).grow(&b).grow(&c), + } + } +} + +impl Bounded for Triangle { + fn aabb(&self) -> AABB { + self.aabb + } +} + +impl IntersectionRay for Triangle { + fn intersects_ray(&self, ray: &Ray, t_min: Real, t_max: Real) -> Option { + let inter = ray.intersects_triangle(&self.a, &self.b, &self.c); + if inter.distance <= t_max && inter.distance >= t_min { + Some(inter) + } else { + None + } + } +} + +impl IntersectionAABB for Triangle { + fn intersects_aabb(&self, aabb: &AABB) -> bool { + let c = aabb.center(); + let extents = ((aabb.max - aabb.min) / 2.).to_array(); + + let verts = [self.a - c, self.b - c, self.c - c]; + + let lines = [ + verts[1] - verts[0], + verts[2] - verts[1], + verts[0] - verts[2], + ]; + + let normals = [Vector3::X, Vector3::Y, Vector3::Z]; + + let mut axis: [[Vector3; 3]; 3] = Default::default(); + + for (u, u_axis) in normals.iter().zip(axis.iter_mut()) { + for (f, curr_axis) in lines.iter().zip(u_axis.iter_mut()) { + *curr_axis = u.cross(*f); + } + } + + for u_axis in axis { + for a in u_axis { + if !test_axis(&verts, &normals, &extents, a) { + return false; + } + } + } + + for a in normals { + if !test_axis(&verts, &normals, &extents, a) { + return false; + } + } + + let triangle_normal = lines[0].cross(lines[1]); + if !test_axis(&verts, &normals, &extents, triangle_normal) { + return false; + } + + true + } +} + +fn test_axis( + verts: &[Vector3; 3], + normals: &[Vector3; 3], + extents: &[Real; 3], + axis: Vector3, +) -> bool { + let projection = Vector3::new(verts[0].dot(axis), verts[1].dot(axis), verts[2].dot(axis)); + + let r: Real = extents + .iter() + .zip(normals.iter()) + .map(|(&e, u)| e * (u.dot(axis))) + .sum(); + + let max = projection.max_element(); + let min = projection.min_element(); + (-max).max(min) <= r +} From 0b822d49527acf2888d13a1714dbdcd557dde9c0 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Mon, 14 Feb 2022 18:05:12 -0500 Subject: [PATCH 35/37] fix subcrate profiles, small tweaks --- bvh-f64/Cargo.toml | 10 --------- bvh-lib/Cargo.toml | 10 --------- bvh/Cargo.toml | 12 +---------- src/bvh/bvh_impl.rs | 8 +++---- src/lib.rs | 48 ------------------------------------------ src/shapes/sphere.rs | 4 ++-- src/shapes/triangle.rs | 31 ++++++++------------------- 7 files changed, 15 insertions(+), 108 deletions(-) diff --git a/bvh-f64/Cargo.toml b/bvh-f64/Cargo.toml index fbcc679..dfdf112 100644 --- a/bvh-f64/Cargo.toml +++ b/bvh-f64/Cargo.toml @@ -47,13 +47,3 @@ bench = [] f64 = [] # Unfortunately can't use "serde" as the feature name until https://github.com/rust-lang/cargo/issues/5565 lands serde_impls = ["serde", "glam/serde"] - -[profile.release] -lto = true -codegen-units = 1 -debug = true - -[profile.bench] -lto = true -codegen-units = 1 -debug = true diff --git a/bvh-lib/Cargo.toml b/bvh-lib/Cargo.toml index ed87a4c..e7cdaa9 100644 --- a/bvh-lib/Cargo.toml +++ b/bvh-lib/Cargo.toml @@ -18,13 +18,3 @@ glam = "0.20" [lib] name="bvh_lib" crate-type = ["rlib", "dylib"] - -[profile.release] -lto = true -codegen-units = 1 -debug = true - -[profile.bench] -lto = true -codegen-units = 1 -debug = true \ No newline at end of file diff --git a/bvh/Cargo.toml b/bvh/Cargo.toml index 81c99e2..049edc2 100644 --- a/bvh/Cargo.toml +++ b/bvh/Cargo.toml @@ -44,14 +44,4 @@ serde_json = "1" default = [] bench = [] # Unfortunately can't use "serde" as the feature name until https://github.com/rust-lang/cargo/issues/5565 lands -serde_impls = ["serde", "glam/serde"] - -[profile.release] -lto = true -codegen-units = 1 -debug = true - -[profile.bench] -lto = true -codegen-units = 1 -debug = true +serde_impls = ["serde", "glam/serde"] \ No newline at end of file diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index cf3d58d..e6d9075 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -243,7 +243,6 @@ impl BVHNode { } /// Builds a [`BVHNode`] recursively using SAH partitioning. - /// Returns the index of the new node in the nodes vector. /// /// [`BVHNode`]: enum.BVHNode.html /// @@ -257,7 +256,7 @@ impl BVHNode { node_index: usize, aabb_bounds: AABB, centroid_bounds: AABB, - ) -> usize { + ) { // If there is only one element left, don't split anymore if indices.len() == 1 { let shape_index = indices[0]; @@ -267,7 +266,7 @@ impl BVHNode { }); // Let the shape know the index of the node that represents it. shapes[shape_index].set_bh_node_index(node_index); - return node_index; + return; } let mut parallel_recurse = false; if indices.len() > 64 { @@ -440,7 +439,6 @@ impl BVHNode { child_r_aabb, child_r_index, }); - node_index } #[allow(clippy::type_complexity)] @@ -1301,7 +1299,7 @@ mod bench { #[cfg(feature = "bench")] fn build_by_add(shapes: &mut [T]) -> BVH { - let (first, rest) = shapes.split_at_mut(1); + let (first, _rest) = shapes.split_at_mut(1); let mut bvh = BVH::build(first); for i in 1..shapes.len() { bvh.add_node(shapes, i) diff --git a/src/lib.rs b/src/lib.rs index c4fab23..ef1dafd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,51 +134,3 @@ mod testbase; pub use shapes::*; //pub use shapes::{Ray, AABB, OBB, Capsule, Sphere}; -use aabb::{Bounded, AABB}; -use shapes::ray::IntersectionRay; - -/// A triangle struct. Instance of a more complex `Bounded` primitive. -#[derive(Debug)] -pub struct Triangle { - /// First point on the triangle - pub a: Point3, - /// Second point on the triangle - pub b: Point3, - /// Third point on the triangle - pub c: Point3, - aabb: AABB, -} - -impl Triangle { - /// Creates a new triangle given a counter clockwise set of points - pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { - Triangle { - a, - b, - c, - aabb: AABB::empty().grow(&a).grow(&b).grow(&c), - } - } -} - -impl Bounded for Triangle { - fn aabb(&self) -> AABB { - self.aabb - } -} - -impl IntersectionRay for Triangle { - fn intersects_ray( - &self, - ray: &ray::Ray, - t_min: Real, - t_max: Real, - ) -> Option { - let inter = ray.intersects_triangle(&self.a, &self.b, &self.c); - if inter.distance <= t_max && inter.distance >= t_min { - Some(inter) - } else { - None - } - } -} diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 252b861..3b00f3a 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -26,8 +26,8 @@ impl Sphere { impl IntersectionAABB for Sphere { fn intersects_aabb(&self, aabb: &AABB) -> bool { - let vec = aabb.closest_point(self.center); - vec.distance_squared(self.center) < self.radius * self.radius + let closest = aabb.closest_point(self.center); + closest.distance_squared(self.center) <= self.radius * self.radius } } diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs index 6040875..48494bf 100644 --- a/src/shapes/triangle.rs +++ b/src/shapes/triangle.rs @@ -15,24 +15,18 @@ pub struct Triangle { pub b: Point3, /// Third point on the triangle pub c: Point3, - aabb: AABB, } impl Triangle { /// Creates a new triangle given a counter clockwise set of points pub fn new(a: Point3, b: Point3, c: Point3) -> Triangle { - Triangle { - a, - b, - c, - aabb: AABB::empty().grow(&a).grow(&b).grow(&c), - } + Triangle { a, b, c } } } impl Bounded for Triangle { fn aabb(&self) -> AABB { - self.aabb + AABB::empty().grow(&self.a).grow(&self.b).grow(&self.c) } } @@ -62,30 +56,23 @@ impl IntersectionAABB for Triangle { let normals = [Vector3::X, Vector3::Y, Vector3::Z]; - let mut axis: [[Vector3; 3]; 3] = Default::default(); - - for (u, u_axis) in normals.iter().zip(axis.iter_mut()) { - for (f, curr_axis) in lines.iter().zip(u_axis.iter_mut()) { - *curr_axis = u.cross(*f); - } - } - - for u_axis in axis { - for a in u_axis { - if !test_axis(&verts, &normals, &extents, a) { + for u in normals.iter() { + for f in lines.iter() { + let axis = u.cross(*f); + if !separating_axis_test(&verts, &normals, &extents, axis) { return false; } } } for a in normals { - if !test_axis(&verts, &normals, &extents, a) { + if !separating_axis_test(&verts, &normals, &extents, a) { return false; } } let triangle_normal = lines[0].cross(lines[1]); - if !test_axis(&verts, &normals, &extents, triangle_normal) { + if !separating_axis_test(&verts, &normals, &extents, triangle_normal) { return false; } @@ -93,7 +80,7 @@ impl IntersectionAABB for Triangle { } } -fn test_axis( +fn separating_axis_test( verts: &[Vector3; 3], normals: &[Vector3; 3], extents: &[Real; 3], From 64263c9c3b26b304e6c136df6f3be92da0b159a4 Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Tue, 15 Feb 2022 07:26:47 -0500 Subject: [PATCH 36/37] fix all clippy errors in the benchmarks --- src/bvh/bvh_impl.rs | 21 ++++++++++----------- src/bvh/optimization.rs | 2 +- src/shapes/mod.rs | 4 ++-- src/testbase.rs | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/bvh/bvh_impl.rs b/src/bvh/bvh_impl.rs index e6d9075..145e475 100644 --- a/src/bvh/bvh_impl.rs +++ b/src/bvh/bvh_impl.rs @@ -1136,7 +1136,7 @@ mod tests { let ray = Ray::new(Vector3::new(x, 2.0, 0.0), dir); let res = bvh.traverse(&ray, shapes); - if count == 0 && res.len() > 0 { + if count == 0 && !res.is_empty() { println!("hit={} node={}", res[0].pos, res[0].bh_node_index()); } assert_eq!(res.len(), count); @@ -1147,8 +1147,8 @@ mod tests { for x in -23..23 { let point = Point3::new(x as Real, 0.0, 0.0); let mut delete_i = 0; - for i in 0..shapes.len() { - if shapes[i].pos.distance_squared(point) < 0.01 { + for (i, shape) in shapes.iter().enumerate() { + if shape.pos.distance_squared(point) < 0.01 { delete_i = i; break; } @@ -1169,8 +1169,7 @@ mod tests { #[test] fn test_random_deletions() { let xs = -3..3; - let x_values = xs.clone().collect::>(); - for x_values in xs.clone().permutations(x_values.len() - 1) { + for x_values in xs.clone().permutations(xs.clone().count() - 1) { let mut shapes = Vec::new(); for x in xs.clone() { shapes.push(UnitBox::new(x, Point3::new(x as Real, 0.0, 0.0))); @@ -1183,8 +1182,8 @@ mod tests { let point = Point3::new(x as Real, 0.0, 0.0); let mut delete_i = 0; - for i in 0..shapes.len() { - if shapes[i].pos.distance_squared(point) < 0.01 { + for (i, shape) in shapes.iter().enumerate() { + if shape.pos.distance_squared(point) < 0.01 { delete_i = i; break; } @@ -1355,14 +1354,14 @@ mod bench { #[bench] /// Benchmark intersecting 1,200 triangles using the recursive `BVH`. - fn bench_intersect_1200_triangles_bvh_add(mut b: &mut ::test::Bencher) { - intersect_n_triangles_add(1200, &mut b); + fn bench_intersect_1200_triangles_bvh_add(b: &mut ::test::Bencher) { + intersect_n_triangles_add(1200, b); } #[bench] /// Benchmark intersecting 1,200 triangles using the recursive `BVH`. - fn bench_intersect_12000_triangles_bvh_add(mut b: &mut ::test::Bencher) { - intersect_n_triangles_add(12000, &mut b); + fn bench_intersect_12000_triangles_bvh_add(b: &mut ::test::Bencher) { + intersect_n_triangles_add(12000, b); } #[bench] diff --git a/src/bvh/optimization.rs b/src/bvh/optimization.rs index f8c243c..12fc01d 100644 --- a/src/bvh/optimization.rs +++ b/src/bvh/optimization.rs @@ -949,7 +949,7 @@ mod bench { for _ in 0..iterations { let updated = - randomly_transform_scene(triangles, num_move, &bounds, max_offset, &mut seed); + randomly_transform_scene(triangles, num_move, bounds, max_offset, &mut seed); let updated: Vec = updated.into_iter().collect(); bvh.optimize(&updated, triangles); } diff --git a/src/shapes/mod.rs b/src/shapes/mod.rs index f0ab150..2a76e67 100644 --- a/src/shapes/mod.rs +++ b/src/shapes/mod.rs @@ -11,7 +11,7 @@ mod tests { use crate::bounding_hierarchy::IntersectionAABB; use crate::capsule::Capsule; use crate::obb::OBB; - use crate::{Point3, Quat, Real, Vector3}; + use crate::{Point3, Quat, Real, Vector3, PI}; #[test] fn basic_test_capsule() { @@ -51,7 +51,7 @@ mod tests { let max = Point3::new(1.0, 1.0, 1.0); let aabb = AABB::empty().grow(&min).grow(&max); - let ori = Quat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0).into(), 0.785398); + let ori = Quat::from_axis_angle(Vector3::new(1.0, 0.0, 0.0), PI / 4.); let extents = Vector3::new(0.5, 0.5, 0.5); let pos = Vector3::new(0.5, 2.2, 0.5); diff --git a/src/testbase.rs b/src/testbase.rs index 42e31d6..f7de808 100644 --- a/src/testbase.rs +++ b/src/testbase.rs @@ -211,7 +211,7 @@ impl FromRawVertex for Triangle { // Convert the vertices to `Point3`s. let points = vertices .into_iter() - .map(|v| Point3::new(v.0.into(), v.1.into(), v.2.into())) + .map(|v| Point3::new(v.0 as Real, v.1 as Real, v.2 as Real)) .collect::>(); // Estimate for the number of triangles, assuming that each polygon is a triangle. From 904b86e95d19ee48e0e7012ec4242a9eec240f7c Mon Sep 17 00:00:00 2001 From: dbenson24 Date: Wed, 16 Mar 2022 07:32:39 -0400 Subject: [PATCH 37/37] move ffi to a standalone repo + crate --- Cargo.toml | 12 +- bvh-lib/Cargo.toml | 20 -- bvh-lib/src/lib.rs | 543 ------------------------------------------- src/shapes/sphere.rs | 6 +- 4 files changed, 14 insertions(+), 567 deletions(-) delete mode 100644 bvh-lib/Cargo.toml delete mode 100644 bvh-lib/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index aa3d2b2..a39b407 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,12 @@ [workspace] -members = ["bvh-lib", "bvh", "bvh-f64"] +members = ["bvh", "bvh-f64"] + +[profile.release] +lto = true +codegen-units = 1 +debug = true + +[profile.bench] +lto = true +codegen-units = 1 +debug = true diff --git a/bvh-lib/Cargo.toml b/bvh-lib/Cargo.toml deleted file mode 100644 index e7cdaa9..0000000 --- a/bvh-lib/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "bvh-lib" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bvh = { path = "../bvh" } -bvh-f64 = { path = "../bvh-f64" } -interoptopus = "0.13.12" -interoptopus_backend_csharp = "0.13.12" -flexi_logger = "0.19.3" -log-panics = "2.0.0" -log = "0.4.14" -glam = "0.20" - -[lib] -name="bvh_lib" -crate-type = ["rlib", "dylib"] diff --git a/bvh-lib/src/lib.rs b/bvh-lib/src/lib.rs deleted file mode 100644 index e268df6..0000000 --- a/bvh-lib/src/lib.rs +++ /dev/null @@ -1,543 +0,0 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; - -use bvh_f64::aabb::{Bounded, AABB}; -use bvh_f64::bounding_hierarchy::BHShape; -use bvh_f64::bvh::BVH; -use bvh_f64::capsule::Capsule; -use bvh_f64::obb::OBB; -use bvh_f64::ray::Ray; -use bvh_f64::sphere::Sphere; -use bvh_f64::Vector3; -use flexi_logger::{detailed_format, FileSpec, Logger}; -use glam::DQuat; -use interoptopus::lang::c::{ - CType, CompositeType, Documentation, Field, Meta, OpaqueType, Visibility, -}; -use interoptopus::lang::rust::CTypeInfo; -use interoptopus::patterns::slice::FFISliceMut; -use interoptopus::patterns::string::AsciiPointer; -use interoptopus::util::NamespaceMappings; -use interoptopus::{ffi_function, ffi_type, Error, Interop}; -use log::info; - -#[repr(C)] -#[ffi_type] -#[derive(Copy, Clone, Debug)] -pub struct Double3 { - pub x: f64, - pub y: f64, - pub z: f64, -} - -#[repr(C)] -#[ffi_type] -#[derive(Copy, Clone, Debug)] -pub struct Float3 { - pub x: f32, - pub y: f32, - pub z: f32, -} - -#[repr(C)] -#[ffi_type(name = "BoundingBoxD")] -#[derive(Copy, Clone, Debug)] -pub struct BoundsD { - pub min: Double3, - pub max: Double3, -} - -#[repr(C)] -#[ffi_type(name = "BvhNode")] -#[derive(Copy, Clone, Debug)] -pub struct BVHBounds { - pub bounds: BoundsD, - pub internal_bvh_index: i32, - pub index: i32, -} - -#[repr(C)] -pub struct BvhRef { - bvh: Box, -} - -unsafe impl CTypeInfo for BvhRef { - fn type_info() -> CType { - let fields: Vec = vec![Field::with_documentation( - "bvh".to_string(), - CType::ReadPointer(Box::new(CType::Opaque(OpaqueType::new( - "BvhPtr".to_string(), - Meta::new(), - )))), - Visibility::Private, - Documentation::new(), - )]; - let composite = CompositeType::new("BvhRef".to_string(), fields); - CType::Composite(composite) - } -} - -#[ffi_type] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct QuatD { - pub x: f64, - pub y: f64, - pub z: f64, - pub w: f64, -} - -#[ffi_type(name = "Float3")] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct Point32 { - pub x: f32, - pub y: f32, - pub z: f32, -} - -#[ffi_type(name = "BoundingBox")] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct AABB32 { - pub min: Point32, - pub max: Point32, -} - -#[ffi_type(name = "FlatNode")] -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct FlatNode32 { - pub aabb: AABB32, - pub entry_index: u32, - pub exit_index: u32, - pub shape_index: u32, -} - -impl Bounded for BVHBounds { - fn aabb(&self) -> AABB { - let min = to_vec(&self.bounds.min); - let max = to_vec(&self.bounds.max); - AABB::with_bounds(min, max) - } -} - -#[ffi_type(opaque)] -#[derive(Copy, Clone, Debug)] -pub struct RefNode { - pub index: usize, -} - -impl BHShape for BVHBounds { - fn set_bh_node_index(&mut self, index: usize) { - self.internal_bvh_index = index as i32; - } - - fn bh_node_index(&self) -> usize { - self.internal_bvh_index as usize - } -} - -pub fn to_vec(a: &Double3) -> Vector3 { - Vector3::new(a.x, a.y, a.z) -} - -pub fn to_vecd(a: &Vector3) -> Double3 { - Double3 { - x: a.x, - y: a.y, - z: a.z, - } -} - -pub fn to_quat(a: &QuatD) -> DQuat { - DQuat::from_xyzw(a.x, a.y, a.z, a.w) -} - -static LOGGER_INITIALIZED: AtomicUsize = AtomicUsize::new(0); - -#[ffi_function] -#[no_mangle] -pub extern "C" fn init_logger(log_path: AsciiPointer) { - let init_count = LOGGER_INITIALIZED.fetch_add(1, Ordering::SeqCst); - if init_count == 0 { - let path = log_path.as_str().unwrap(); - let file = FileSpec::default() - .directory(path) - .basename("bvh_lib") - .suffix("log"); - Logger::try_with_str("info") - .unwrap() - .log_to_file(file) - .format_for_files(detailed_format) - .start() - .unwrap(); - log_panics::init(); - - info!("Log initialized in folder {}", path); - } -} - -/// # Safety -/// -/// This function should not be called with invalid pointers -#[no_mangle] -pub unsafe extern "C" fn add_vecs(a_ptr: *mut Float3, b_ptr: *mut Float3, out_ptr: *mut Float3) { - let a = *a_ptr; - - let a = glam::Vec3::new(a.x, a.y, a.z); - let b = *b_ptr; - let b = glam::Vec3::new(b.x, b.y, b.z); - let mut c = glam::Vec3::new(0.0, 0.0, 0.0); - - for _i in 0..100000 { - c = a + b + c; - } - - *out_ptr = Float3 { - x: c.x, - y: c.y, - z: c.z, - }; -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn build_bvh(shapes: &mut FFISliceMut) -> BvhRef { - let bvh = Box::new(BVH::build(shapes.as_slice_mut())); - info!("Building bvh"); - - BvhRef { bvh } -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn rebuild_bvh(bvh_ref: &mut BvhRef, shapes: &mut FFISliceMut) { - let bvh = &mut bvh_ref.bvh; - bvh.rebuild(shapes.as_slice_mut()); -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_ray( - bvh_ref: &BvhRef, - origin_vec: &Double3, - dir_vec: &Double3, - shapes: &mut FFISliceMut, - buffer: &mut FFISliceMut, -) -> i32 { - let bvh = &bvh_ref.bvh; - - let ray = Ray::new(to_vec(origin_vec), to_vec(dir_vec)); - let mut i = 0; - - for x in bvh.traverse_iterator(&ray, shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn batch_query_rays( - bvh_ref: &BvhRef, - origins: &FFISliceMut, - dirs: &FFISliceMut, - hits: &mut FFISliceMut, - shapes: &mut FFISliceMut, - buffer: &mut FFISliceMut, -) { - let bvh = &bvh_ref.bvh; - let mut i = 0; - let ray_count = origins.len(); - for r in 0..ray_count as usize { - let ray = Ray::new(to_vec(&origins[r]), to_vec(&dirs[r])); - let mut res = 0; - for x in bvh.traverse_iterator(&ray, shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - res += 1; - } - hits[r] = res; - } -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_sphere( - bvh_ref: &BvhRef, - center: &Double3, - radius: f64, - shapes: &mut FFISliceMut, - buffer: &mut FFISliceMut, -) -> i32 { - let bvh = &bvh_ref.bvh; - - let test_shape = Sphere::new(to_vec(center), radius); - let mut i = 0; - - for x in bvh.traverse_iterator(&test_shape, shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_capsule( - bvh_ref: &BvhRef, - start: &Double3, - end: &Double3, - radius: f64, - shapes: &mut FFISliceMut, - buffer: &mut FFISliceMut, -) -> i32 { - let bvh = &bvh_ref.bvh; - - let test_shape = Capsule::new(to_vec(start), to_vec(end), radius); - let mut i = 0; - - for x in bvh.traverse_iterator(&test_shape, shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_aabb( - bvh_ref: &BvhRef, - bounds: &BoundsD, - shapes: &mut FFISliceMut, - buffer: &mut FFISliceMut, -) -> i32 { - let bvh = &bvh_ref.bvh; - - let min = to_vec(&bounds.min); - let max = to_vec(&bounds.max); - let test_shape = AABB::with_bounds(min, max); - let mut i = 0; - - for x in bvh.traverse_iterator(&test_shape, shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn query_obb( - bvh_ref: &BvhRef, - ori: &QuatD, - extents: &Double3, - center: &Double3, - shapes: &mut FFISliceMut, - buffer: &mut FFISliceMut, -) -> i32 { - let bvh = &bvh_ref.bvh; - let obb = OBB { - orientation: to_quat(ori), - extents: to_vec(extents), - center: to_vec(center), - }; - - let mut i = 0; - - for x in bvh.traverse_iterator(&obb, shapes) { - if i < buffer.len() { - buffer[i as usize] = *x; - } - i += 1; - } - i as i32 -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn free_bvh(_bvh_ref: BvhRef) {} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn add_node( - bvh_ref: &mut BvhRef, - new_shape: i32, - shapes: &mut FFISliceMut, -) { - let bvh = &mut bvh_ref.bvh; - bvh.add_node(shapes.as_slice_mut(), new_shape as usize); -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn remove_node( - bvh_ref: &mut BvhRef, - remove_shape: i32, - shapes: &mut FFISliceMut, -) { - let bvh = &mut bvh_ref.bvh; - bvh.remove_node(shapes.as_slice_mut(), remove_shape as usize, true); -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn update_node( - bvh_ref: &mut BvhRef, - update_shape: i32, - shapes: &mut FFISliceMut, -) { - let bvh = &mut bvh_ref.bvh; - bvh.remove_node(shapes.as_slice_mut(), update_shape as usize, false); - bvh.add_node(shapes, update_shape as usize); -} - -#[ffi_function] -#[no_mangle] -pub extern "C" fn flatten_bvh( - bvh_ref: &mut BvhRef, - shapes: &mut FFISliceMut, - results: &mut FFISliceMut, -) -> i32 { - let bvh = &bvh_ref.bvh; - - let flattened = bvh.flatten_custom(shapes.as_slice_mut(), &node_32_constructor); - - for i in 0..flattened.len() { - results[i] = flattened[i]; - } - - flattened.len() as i32 -} - -pub fn node_32_constructor( - aabb: &AABB, - entry_index: u32, - exit_index: u32, - shape_index: u32, -) -> FlatNode32 { - let min = Point32 { - x: aabb.min.x as f32, - y: aabb.min.y as f32, - z: aabb.min.z as f32, - }; - let max = Point32 { - x: aabb.max.x as f32, - y: aabb.max.y as f32, - z: aabb.max.z as f32, - }; - let b = AABB32 { min, max }; - FlatNode32 { - aabb: b, - entry_index, - exit_index, - shape_index, - } -} - -interoptopus::inventory!( - my_inventory, - [], - [ - init_logger, - build_bvh, - rebuild_bvh, - query_ray, - batch_query_rays, - query_sphere, - query_capsule, - query_aabb, - query_obb, - free_bvh, - add_node, - remove_node, - update_node, - flatten_bvh, - ], - [], - [] -); - -fn bindings_csharp() -> Result<(), Error> { - use interoptopus_backend_csharp::{ - overloads::{DotNet, Unity}, - Config, Generator, Unsafe, - }; - - Generator::new( - Config { - class: "NativeBvhInterop".to_string(), - dll_name: "bvh_lib".to_string(), - namespace_mappings: NamespaceMappings::new("Assets.Scripts.Native"), - use_unsafe: Unsafe::UnsafePlatformMemCpy, - ..Config::default() - }, - my_inventory(), - ) - .add_overload_writer(Unity::new()) - .add_overload_writer(DotNet::new()) - .write_file("../bindings/csharp/Interop.cs")?; - - Ok(()) -} - -#[allow(dead_code)] -fn gen_bindings() { - bindings_csharp().unwrap(); -} - -#[test] -fn test_building_and_querying() { - let min = Double3 { - x: -1.0, - y: -1.0, - z: -1.0, - }; - let max = Double3 { - x: 1.0, - y: 1.0, - z: 1.0, - }; - let bounds = BoundsD { min, max }; - let b = BVHBounds { - bounds, - index: 0, - internal_bvh_index: 0, - }; - - let _out = BVHBounds { - bounds, - index: 0, - internal_bvh_index: 0, - }; - - let origin = Double3 { - x: 0.0, - y: -5.0, - z: 0.0, - }; - let dir = Double3 { - x: 0.0, - y: 1.0, - z: 0.0, - }; - let in_slice = &mut [b]; - let mut input = FFISliceMut::::from_slice(in_slice); - let out_slice = &mut [b]; - let mut out = FFISliceMut::::from_slice(out_slice); - let bvh_ref = build_bvh(&mut input); - - let x = query_ray(&bvh_ref, &origin, &dir, &mut input, &mut out); - assert_eq!(x, 1); -} diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 3b00f3a..bda9039 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -43,11 +43,11 @@ impl IntersectionRay for Sphere { return None; } - let sqrtd = discriminant.sqrt(); + let d_sqrt = discriminant.sqrt(); - let mut toi = (-half_b - sqrtd) / a; + let mut toi = (-half_b - d_sqrt) / a; if toi < t_min || t_max < toi { - toi = (-half_b + sqrtd) / a; + toi = (-half_b + d_sqrt) / a; if toi < t_min || t_max < toi { return None; }