beevee: bvh: use Intersected trait for objects

This avoids having black squares around the objects because we computed
that it was the closest but the ray didn't actually hit it...
This commit is contained in:
Bruno BELANYI 2020-03-24 21:35:21 +01:00
parent 48bb3550cb
commit 21c7aea1c0
3 changed files with 165 additions and 22 deletions

View file

@ -0,0 +1,11 @@
use crate::aabb::Bounded;
use crate::ray::Ray;
/// The trait for any object to be used in the [`BVH`].
///
/// [`BVH`]: struct.BVH.html
pub trait Intersected: Bounded {
/// Return None if there is no intersection, or the distance along the ray to the closest
/// intersection
fn intersect(&self, ray: &Ray) -> Option<f32>;
}

View file

@ -1,4 +1,7 @@
//! The Boudning Volume Hiearchy //! The Boudning Volume Hiearchy
mod intersected;
pub use intersected::*;
mod tree; mod tree;
pub use tree::*; pub use tree::*;

View file

@ -1,4 +1,5 @@
use crate::aabb::{Bounded, AABB}; use super::Intersected;
use crate::aabb::AABB;
use crate::ray::Ray; use crate::ray::Ray;
use crate::Axis; use crate::Axis;
@ -22,9 +23,9 @@ struct Node {
} }
/// The BVH containing all the objects of type O. /// The BVH containing all the objects of type O.
/// This type must implement [`Bounded`]. /// This type must implement [`Intersected`].
/// ///
/// [`Bounded`]: ../aabb/trait.Bounded.html /// [`Intersected`]: trait.Intersected.html
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct BVH { pub struct BVH {
tree: Node, tree: Node,
@ -38,7 +39,8 @@ impl BVH {
/// ``` /// ```
/// use beevee::{Point, Vector}; /// use beevee::{Point, Vector};
/// use beevee::aabb::{AABB, Bounded}; /// use beevee::aabb::{AABB, Bounded};
/// use beevee::bvh::BVH; /// use beevee::bvh::{BVH, Intersected};
/// use beevee::ray::Ray;
/// ///
/// #[derive(Clone, Debug, PartialEq)] /// #[derive(Clone, Debug, PartialEq)]
/// struct Sphere { /// struct Sphere {
@ -56,10 +58,41 @@ impl BVH {
/// } /// }
/// } /// }
/// ///
/// impl Intersected for Sphere {
/// fn intersect(&self, ray: &Ray) -> Option<f32> {
/// use std::mem;
///
/// let delt = self.center - ray.origin;
/// let tca = ray.direction.dot(&delt);
/// let d2 = delt.norm_squared() - tca * tca;
/// let r_2 = self.radius * self.radius;
///
/// if d2 > r_2 {
/// return None;
/// }
///
/// let thc = (r_2 - d2).sqrt();
/// let mut t_0 = tca - thc;
/// let mut t_1 = tca + thc;
///
/// if t_0 > t_1 {
/// mem::swap(&mut t_0, &mut t_1)
/// }
/// if t_0 < 0. {
/// t_0 = t_1
/// }
/// if t_0 < 0. {
/// None
/// } else {
/// Some(t_0)
/// }
/// }
/// }
///
/// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }];
/// let bvh = BVH::build(spheres); /// let bvh = BVH::build(spheres);
/// ``` /// ```
pub fn build<O: Bounded>(objects: &mut [O]) -> Self { pub fn build<O: Intersected>(objects: &mut [O]) -> Self {
Self::with_max_capacity(objects, 32) Self::with_max_capacity(objects, 32)
} }
@ -71,7 +104,8 @@ impl BVH {
/// ``` /// ```
/// use beevee::{Point, Vector}; /// use beevee::{Point, Vector};
/// use beevee::aabb::{AABB, Bounded}; /// use beevee::aabb::{AABB, Bounded};
/// use beevee::bvh::BVH; /// use beevee::bvh::{BVH, Intersected};
/// use beevee::ray::Ray;
/// ///
/// #[derive(Clone, Debug, PartialEq)] /// #[derive(Clone, Debug, PartialEq)]
/// struct Sphere { /// struct Sphere {
@ -89,10 +123,41 @@ impl BVH {
/// } /// }
/// } /// }
/// ///
/// impl Intersected for Sphere {
/// fn intersect(&self, ray: &Ray) -> Option<f32> {
/// use std::mem;
///
/// let delt = self.center - ray.origin;
/// let tca = ray.direction.dot(&delt);
/// let d2 = delt.norm_squared() - tca * tca;
/// let r_2 = self.radius * self.radius;
///
/// if d2 > r_2 {
/// return None;
/// }
///
/// let thc = (r_2 - d2).sqrt();
/// let mut t_0 = tca - thc;
/// let mut t_1 = tca + thc;
///
/// if t_0 > t_1 {
/// mem::swap(&mut t_0, &mut t_1)
/// }
/// if t_0 < 0. {
/// t_0 = t_1
/// }
/// if t_0 < 0. {
/// None
/// } else {
/// Some(t_0)
/// }
/// }
/// }
///
/// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }];
/// let bvh = BVH::with_max_capacity(spheres, 32); /// let bvh = BVH::with_max_capacity(spheres, 32);
/// ``` /// ```
pub fn with_max_capacity<O: Bounded>(objects: &mut [O], max_cap: usize) -> Self { pub fn with_max_capacity<O: Intersected>(objects: &mut [O], max_cap: usize) -> Self {
let tree = build_node(objects, 0, objects.len(), max_cap); let tree = build_node(objects, 0, objects.len(), max_cap);
Self { tree } Self { tree }
} }
@ -106,7 +171,8 @@ impl BVH {
/// ``` /// ```
/// # use beevee::{Point, Vector}; /// # use beevee::{Point, Vector};
/// # use beevee::aabb::{AABB, Bounded}; /// # use beevee::aabb::{AABB, Bounded};
/// # use beevee::bvh::BVH; /// # use beevee::bvh::{BVH, Intersected};
/// # use beevee::ray::Ray;
/// # /// #
/// # #[derive(Clone, Debug, PartialEq)] /// # #[derive(Clone, Debug, PartialEq)]
/// # struct Sphere { /// # struct Sphere {
@ -124,14 +190,44 @@ impl BVH {
/// # } /// # }
/// # } /// # }
/// # /// #
/// # impl Intersected for Sphere {
/// # fn intersect(&self, ray: &Ray) -> Option<f32> {
/// # use std::mem;
/// #
/// # let delt = self.center - ray.origin;
/// # let tca = ray.direction.dot(&delt);
/// # let d2 = delt.norm_squared() - tca * tca;
/// # let r_2 = self.radius * self.radius;
/// #
/// # if d2 > r_2 {
/// # return None;
/// # }
/// #
/// # let thc = (r_2 - d2).sqrt();
/// # let mut t_0 = tca - thc;
/// # let mut t_1 = tca + thc;
/// #
/// # if t_0 > t_1 {
/// # mem::swap(&mut t_0, &mut t_1)
/// # }
/// # if t_0 < 0. {
/// # t_0 = t_1
/// # }
/// # if t_0 < 0. {
/// # None
/// # } else {
/// # Some(t_0)
/// # }
/// # }
/// # }
/// #
/// // Using the same sphere definition than build /// // Using the same sphere definition than build
/// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }];
/// let bvh = BVH::with_max_capacity(spheres, 32); /// let bvh = BVH::with_max_capacity(spheres, 32);
/// assert!(bvh.is_sound(spheres)); /// assert!(bvh.is_sound(spheres));
/// ``` /// ```
pub fn is_sound<O: Intersected>(&self, objects: &[O]) -> bool {
pub fn is_sound<O: Bounded>(&self, objects: &[O]) -> bool { fn check_node<O: Intersected>(objects: &[O], node: &Node) -> bool {
fn check_node<O: Bounded>(objects: &[O], node: &Node) -> bool {
if node.begin > node.end { if node.begin > node.end {
return false; return false;
} }
@ -156,8 +252,6 @@ impl BVH {
/// Iterate recursively over the [`BVH`] to find an intersection point with the given [`Ray`]. /// Iterate recursively over the [`BVH`] to find an intersection point with the given [`Ray`].
/// This algorithm tries to only iterate over Nodes that are abolutely necessary, and skip /// This algorithm tries to only iterate over Nodes that are abolutely necessary, and skip
/// visiting nodes that are too far away. /// visiting nodes that are too far away.
/// You still need to make sure if the object is actually intersected by the [`Ray`]
/// afterwards.
/// ///
/// [`BVH`]: struct.BVH.html /// [`BVH`]: struct.BVH.html
/// [`Ray`]: ../ray/struct.Ray.html /// [`Ray`]: ../ray/struct.Ray.html
@ -165,8 +259,8 @@ impl BVH {
/// ``` /// ```
/// # use beevee::{Point, Vector}; /// # use beevee::{Point, Vector};
/// # use beevee::aabb::{AABB, Bounded}; /// # use beevee::aabb::{AABB, Bounded};
/// # use beevee::bvh::BVH; /// # use beevee::bvh::{BVH, Intersected};
/// use beevee::ray::Ray; /// # use beevee::ray::Ray;
/// # /// #
/// # #[derive(Clone, Debug, PartialEq)] /// # #[derive(Clone, Debug, PartialEq)]
/// # struct Sphere { /// # struct Sphere {
@ -184,6 +278,37 @@ impl BVH {
/// # } /// # }
/// # } /// # }
/// # /// #
/// # impl Intersected for Sphere {
/// # fn intersect(&self, ray: &Ray) -> Option<f32> {
/// # use std::mem;
/// #
/// # let delt = self.center - ray.origin;
/// # let tca = ray.direction.dot(&delt);
/// # let d2 = delt.norm_squared() - tca * tca;
/// # let r_2 = self.radius * self.radius;
/// #
/// # if d2 > r_2 {
/// # return None;
/// # }
/// #
/// # let thc = (r_2 - d2).sqrt();
/// # let mut t_0 = tca - thc;
/// # let mut t_1 = tca + thc;
/// #
/// # if t_0 > t_1 {
/// # mem::swap(&mut t_0, &mut t_1)
/// # }
/// # if t_0 < 0. {
/// # t_0 = t_1
/// # }
/// # if t_0 < 0. {
/// # None
/// # } else {
/// # Some(t_0)
/// # }
/// # }
/// # }
/// #
/// // Using the same sphere definition than build /// // Using the same sphere definition than build
/// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 0.5 }]; /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 0.5 }];
/// let bvh = BVH::with_max_capacity(spheres, 32); /// let bvh = BVH::with_max_capacity(spheres, 32);
@ -197,12 +322,12 @@ impl BVH {
/// assert_eq!(dist, 0.5); /// assert_eq!(dist, 0.5);
/// assert_eq!(obj, &spheres[0]); /// assert_eq!(obj, &spheres[0]);
/// ``` /// ```
pub fn walk<'o, O: Bounded>(&self, ray: &Ray, objects: &'o [O]) -> Option<&'o O> { pub fn walk<'o, O: Intersected>(&self, ray: &Ray, objects: &'o [O]) -> Option<(f32, &'o O)> {
walk_rec_helper(ray, objects, &self.tree, std::f32::INFINITY).map(|(_, obj)| obj) walk_rec_helper(ray, objects, &self.tree, std::f32::INFINITY)
} }
} }
fn walk_rec_helper<'o, O: Bounded>( fn walk_rec_helper<'o, O: Intersected>(
ray: &Ray, ray: &Ray,
objects: &'o [O], objects: &'o [O],
node: &Node, node: &Node,
@ -215,7 +340,7 @@ fn walk_rec_helper<'o, O: Bounded>(
NodeEnum::Leaf => objects[node.begin..node.end] NodeEnum::Leaf => objects[node.begin..node.end]
.iter() .iter()
// This turns the Option<f32> of an intersection into an Option<(f32, &O)> // This turns the Option<f32> of an intersection into an Option<(f32, &O)>
.filter_map(|o| ray.aabb_intersection(&o.aabb()).map(|d| (d, o))) .filter_map(|o| o.intersect(ray).map(|d| (d, o)))
// Discard values that are too far away // Discard values that are too far away
.filter(|(dist, _)| dist < &min) .filter(|(dist, _)| dist < &min)
// Only keep the minimum value, if there is one // Only keep the minimum value, if there is one
@ -257,14 +382,14 @@ fn walk_rec_helper<'o, O: Bounded>(
} }
} }
fn bounds_from_slice<O: Bounded>(objects: &[O]) -> AABB { fn bounds_from_slice<O: Intersected>(objects: &[O]) -> AABB {
objects objects
.iter() .iter()
.map(|o| o.aabb()) .map(|o| o.aabb())
.fold(AABB::empty(), |acc, other| acc.union(&other)) .fold(AABB::empty(), |acc, other| acc.union(&other))
} }
fn build_node<O: Bounded>(objects: &mut [O], begin: usize, end: usize, max_cap: usize) -> Node { fn build_node<O: Intersected>(objects: &mut [O], begin: usize, end: usize, max_cap: usize) -> Node {
let aabb = bounds_from_slice(objects); let aabb = bounds_from_slice(objects);
// Don't split nodes under capacity // Don't split nodes under capacity
if objects.len() <= max_cap { if objects.len() <= max_cap {
@ -312,7 +437,11 @@ fn build_node<O: Bounded>(objects: &mut [O], begin: usize, end: usize, max_cap:
/// Returns the index at which to split for SAH, the Axis along which to split, and the calculated /// Returns the index at which to split for SAH, the Axis along which to split, and the calculated
/// cost. /// cost.
fn compute_sah<O: Bounded>(objects: &mut [O], surface: f32, max_cap: usize) -> (usize, Axis, f32) { fn compute_sah<O: Intersected>(
objects: &mut [O],
surface: f32,
max_cap: usize,
) -> (usize, Axis, f32) {
// FIXME(Bruno): too imperative to my taste... // FIXME(Bruno): too imperative to my taste...
let mut mid = objects.len() / 2; let mut mid = objects.len() / 2;
let mut dim = Axis::X; // Arbitrary split let mut dim = Axis::X; // Arbitrary split