beevee: bvh: add intersection test to BVH
This commit is contained in:
parent
45fca6f3ed
commit
d8a4a2eaad
|
@ -1,4 +1,5 @@
|
|||
use crate::aabb::{Bounded, AABB};
|
||||
use crate::ray::Ray;
|
||||
use crate::Axis;
|
||||
|
||||
/// An enum representing either an internal or a leaf node of the [`BVH`]
|
||||
|
@ -151,6 +152,109 @@ impl BVH {
|
|||
};
|
||||
check_node(objects, &self.tree)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
/// [`Ray`]: ../ray/struct.Ray.html
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use beevee::{Point, Vector};
|
||||
/// # use beevee::aabb::{AABB, Bounded};
|
||||
/// # use beevee::bvh::BVH;
|
||||
/// use beevee::ray::Ray;
|
||||
/// #
|
||||
/// # #[derive(Clone, Debug, PartialEq)]
|
||||
/// # struct Sphere {
|
||||
/// # center: Point,
|
||||
/// # radius: f32,
|
||||
/// # }
|
||||
/// #
|
||||
/// # impl Bounded for Sphere {
|
||||
/// # fn aabb(&self) -> AABB {
|
||||
/// # let delt = Vector::new(self.radius, self.radius, self.radius);
|
||||
/// # AABB::with_bounds(self.center - delt, self.center + delt)
|
||||
/// # }
|
||||
/// # fn centroid(&self) -> Point {
|
||||
/// # self.center
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// // Using the same sphere definition than build
|
||||
/// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 0.5 }];
|
||||
/// let bvh = BVH::with_max_capacity(spheres, 32);
|
||||
///
|
||||
/// // This ray is directly looking at the spheres
|
||||
/// let ray = Ray::new(Point::new(-1., 0., 0.), Vector::x_axis());
|
||||
/// let res = bvh.walk(&ray, spheres);
|
||||
///
|
||||
/// assert!(res.is_some());
|
||||
/// let (dist, obj) = res.unwrap();
|
||||
/// assert_eq!(dist, 0.5);
|
||||
/// assert_eq!(obj, &spheres[0]);
|
||||
/// ```
|
||||
pub fn walk<'o, O: Bounded>(&self, ray: &Ray, objects: &'o [O]) -> Option<&'o O> {
|
||||
walk_rec_helper(ray, objects, &self.tree, std::f32::INFINITY).map(|(_, obj)| obj)
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_rec_helper<'o, O: Bounded>(
|
||||
ray: &Ray,
|
||||
objects: &'o [O],
|
||||
node: &Node,
|
||||
min: f32,
|
||||
) -> Option<(f32, &'o O)> {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
match &node.kind {
|
||||
// Return the smallest intersection distance on leaf nodes
|
||||
NodeEnum::Leaf => objects[node.begin..node.end]
|
||||
.iter()
|
||||
// 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)))
|
||||
// Discard values that are too far away
|
||||
.filter(|(dist, _)| dist < &min)
|
||||
// Only keep the minimum value, if there is one
|
||||
.min_by(|(lhs, _), (rhs, _)| lhs.partial_cmp(rhs).unwrap_or(Ordering::Equal)),
|
||||
|
||||
// Recursively find the best node otherwise
|
||||
NodeEnum::Internal { left, right } => {
|
||||
let left_dist = left.bounds.distance_to_point(ray.origin);
|
||||
let right_dist = right.bounds.distance_to_point(ray.origin);
|
||||
// Pick the short and far nodes
|
||||
let (near, far, short_dist, far_dist) = if left_dist < right_dist {
|
||||
(left, right, left_dist, right_dist)
|
||||
} else {
|
||||
(right, left, right_dist, left_dist)
|
||||
};
|
||||
// Don't recurse if we know we cannot possibly find a short-enough intersection
|
||||
if short_dist > min {
|
||||
return None;
|
||||
}
|
||||
// Recurse to the nearest Node first
|
||||
let nearest_res = walk_rec_helper(ray, objects, near.as_ref(), min);
|
||||
// Return immediately if there is no point going to the right at all
|
||||
if far_dist > min {
|
||||
return nearest_res;
|
||||
}
|
||||
match nearest_res {
|
||||
// Short-circuit if we know it is shorter than any point in the far node
|
||||
Some((t, obj)) if t <= far_dist => Some((t, obj)),
|
||||
// We have short_dist <= far_dist <= min in this scenario
|
||||
// With the eventual val.0 in the [short_dist, min) window
|
||||
val => {
|
||||
// Compute the new minimal distance encountered
|
||||
let min = val.map_or(min, |(t, _)| min.min(t));
|
||||
// Recursing with this new minimum can only return None or a better intersecion
|
||||
walk_rec_helper(ray, objects, far.as_ref(), min).or(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds_from_slice<O: Bounded>(objects: &[O]) -> AABB {
|
||||
|
|
Loading…
Reference in a new issue