Skip to content

Commit

Permalink
Add generic traversal
Browse files Browse the repository at this point in the history
  • Loading branch information
finnbear committed Jan 7, 2025
1 parent 51675dd commit 3b650f9
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 31 deletions.
64 changes: 64 additions & 0 deletions src/aabb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,70 @@ impl<T: BHValue + std::fmt::Display, const D: usize> fmt::Display for Aabb<T, D>
}
}

/// A trait implemented by things that may or may not intersect an AABB and, by extension,
/// things that can be used to traverse a BVH.
pub trait AabbIntersection<T: BHValue, const D: usize> {
/// Returns the geometric bounds of this object in the form of an [`Aabb`].
///
/// # Examples
/// ```
/// use bvh::aabb::{Aabb, AabbIntersection};
/// use nalgebra::Point3;
///
/// struct XyPlane;
///
/// impl AabbIntersection<f32,3> for XyPlane {
/// fn intersects_aabb(&self, aabb: &Aabb<f32,3>) -> bool {
/// aabb.min[2] <= 0.0 && aabb.max[2] >= 0.0
/// }
/// }
///
/// let xy_plane = XyPlane;
/// let aabb = Aabb::with_bounds(Point3::new(-1.0,-1.0,-1.0), Point3::new(1.0,1.0,1.0));
/// assert!(xy_plane.intersects_aabb(&aabb));
/// ```
///
/// [`Aabb`]: struct.Aabb.html
///
fn intersects_aabb(&self, aabb: &Aabb<T, D>) -> bool;
}

impl<T: BHValue, const D: usize> AabbIntersection<T, D> for Aabb<T, D> {
fn intersects_aabb(&self, aabb: &Aabb<T, D>) -> bool {
for i in 0..D {
if self.max[i] < aabb.min[i] || aabb.max[i] < self.min[i] {
return false;
}
}
true
}
}

/// In 2D, a circle. In 3D, a sphere. This can be used for traversing BVH's.
pub struct Ball<T: BHValue, const D: usize> {
/// The center of the ball.
pub center: Point<T, D>,
/// The radius of the ball.
pub radius: T,
}

impl<T: BHValue, const D: usize> AabbIntersection<T, D> for Ball<T, D> {
fn intersects_aabb(&self, aabb: &Aabb<T, D>) -> bool {
// https://gamemath.com/book/geomtests.html A.14
// Finding the point in/on the AABB that is closest to the ball's center or,
// more specifically, find the squared distance betwen that point and the
// ball's center.
let mut distance_squared = T::zero();
for i in 0..D {
let closest_on_aabb = self.center[i].clamp(aabb.min[i], aabb.max[i]);
distance_squared += closest_on_aabb.powi(2);
}

// Then test if that point is in/on the ball.
distance_squared <= self.radius
}
}

/// A trait implemented by things which can be bounded by an [`Aabb`].
///
/// [`Aabb`]: struct.Aabb.html
Expand Down
19 changes: 10 additions & 9 deletions src/bvh/bvh_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! [`BvhNode`]: struct.BvhNode.html
//!
use crate::aabb::{Aabb, Bounded};
use crate::aabb::{Aabb, AabbIntersection, Bounded};
use crate::bounding_hierarchy::{BHShape, BHValue, BoundingHierarchy};
use crate::bvh::iter::BvhTraverseIterator;
use crate::ray::Ray;
Expand Down Expand Up @@ -97,35 +97,36 @@ impl<T: BHValue, const D: usize> Bvh<T, D> {
/// [`Bvh`]: struct.Bvh.html
/// [`Aabb`]: ../aabb/struct.Aabb.html
///
pub fn traverse<'a, Shape: Bounded<T, D>>(
pub fn traverse<'a, Query: AabbIntersection<T, D>, Shape: Bounded<T, D>>(
&'a self,
ray: &Ray<T, D>,
query: &Query,
shapes: &'a [Shape],
) -> Vec<&'a Shape> {
if self.nodes.is_empty() {
// There won't be a 0th node_index.
return Vec::new();
}
let mut indices = Vec::new();
BvhNode::traverse_recursive(&self.nodes, 0, ray, &mut indices);
BvhNode::traverse_recursive(&self.nodes, 0, query, &mut indices);
indices
.iter()
.map(|index| &shapes[*index])
.collect::<Vec<_>>()
}

/// Creates a [`BvhTraverseIterator`] to traverse the [`Bvh`].
/// Returns a subset of `shapes`, in which the [`Aabb`]s of the elements were hit by [`Ray`].
/// Returns a subset of `shapes`, in which the [`Aabb`]s of the elements for which
/// [`AabbIntersection::intersects_aabb`] returns `true`.
///
/// [`Bvh`]: struct.Bvh.html
/// [`Aabb`]: ../aabb/struct.Aabb.html
///
pub fn traverse_iterator<'bvh, 'shape, Shape: Bounded<T, D>>(
pub fn traverse_iterator<'bvh, 'shape, Query: AabbIntersection<T, D>, Shape: Bounded<T, D>>(
&'bvh self,
ray: &'bvh Ray<T, D>,
query: &'bvh Query,
shapes: &'shape [Shape],
) -> BvhTraverseIterator<'bvh, 'shape, T, D, Shape> {
BvhTraverseIterator::new(self, ray, shapes)
) -> BvhTraverseIterator<'bvh, 'shape, T, D, Query, Shape> {
BvhTraverseIterator::new(self, query, shapes)
}

/// Creates a [`DistanceTraverseIterator`] to traverse the [`Bvh`].
Expand Down
16 changes: 7 additions & 9 deletions src/bvh/bvh_node.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::aabb::Aabb;
use crate::aabb::{Aabb, AabbIntersection};
use crate::bounding_hierarchy::{BHShape, BHValue};

use crate::ray::Ray;
use crate::utils::{joint_aabb_of_shapes, Bucket};
use std::cell::RefCell;
use std::marker::PhantomData;
Expand Down Expand Up @@ -293,10 +291,10 @@ impl<T: BHValue, const D: usize> BvhNode<T, D> {
/// [`Bvh`]: struct.Bvh.html
/// [`Ray`]: ../ray/struct.Ray.html
///
pub fn traverse_recursive(
pub fn traverse_recursive<Query: AabbIntersection<T, D>>(
nodes: &[BvhNode<T, D>],
node_index: usize,
ray: &Ray<T, D>,
query: &Query,
indices: &mut Vec<usize>,
) {
match nodes[node_index] {
Expand All @@ -307,11 +305,11 @@ impl<T: BHValue, const D: usize> BvhNode<T, D> {
child_r_index,
..
} => {
if ray.intersects_aabb(child_l_aabb) {
BvhNode::traverse_recursive(nodes, child_l_index, ray, indices);
if query.intersects_aabb(child_l_aabb) {
BvhNode::traverse_recursive(nodes, child_l_index, query, indices);
}
if ray.intersects_aabb(child_r_aabb) {
BvhNode::traverse_recursive(nodes, child_r_index, ray, indices);
if query.intersects_aabb(child_r_aabb) {
BvhNode::traverse_recursive(nodes, child_r_index, query, indices);
}
}
BvhNode::Leaf { shape_index, .. } => {
Expand Down
38 changes: 25 additions & 13 deletions src/bvh/iter.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
use crate::aabb::Bounded;
use crate::aabb::{AabbIntersection, Bounded};
use crate::bounding_hierarchy::BHValue;
use crate::bvh::{Bvh, BvhNode};
use crate::ray::Ray;

/// Iterator to traverse a [`Bvh`] without memory allocations
pub struct BvhTraverseIterator<'bvh, 'shape, T: BHValue, const D: usize, Shape: Bounded<T, D>> {
pub struct BvhTraverseIterator<
'bvh,
'shape,
T: BHValue,
const D: usize,
Query: AabbIntersection<T, D>,
Shape: Bounded<T, D>,
> {
/// Reference to the [`Bvh`] to traverse
bvh: &'bvh Bvh<T, D>,
/// Reference to the input ray
ray: &'bvh Ray<T, D>,
/// Reference to the input query
query: &'bvh Query,
/// Reference to the input shapes array
shapes: &'shape [Shape],
/// Traversal stack. 4 billion items seems enough?
Expand All @@ -21,14 +27,20 @@ pub struct BvhTraverseIterator<'bvh, 'shape, T: BHValue, const D: usize, Shape:
has_node: bool,
}

impl<'bvh, 'shape, T: BHValue, const D: usize, Shape: Bounded<T, D>>
BvhTraverseIterator<'bvh, 'shape, T, D, Shape>
impl<
'bvh,
'shape,
T: BHValue,
const D: usize,
Query: AabbIntersection<T, D>,
Shape: Bounded<T, D>,
> BvhTraverseIterator<'bvh, 'shape, T, D, Query, Shape>
{
/// Creates a new [`BvhTraverseIterator`]
pub fn new(bvh: &'bvh Bvh<T, D>, ray: &'bvh Ray<T, D>, shapes: &'shape [Shape]) -> Self {
pub fn new(bvh: &'bvh Bvh<T, D>, query: &'bvh Query, shapes: &'shape [Shape]) -> Self {
BvhTraverseIterator {
bvh,
ray,
query,
shapes,
stack: [0; 32],
node_index: 0,
Expand Down Expand Up @@ -71,7 +83,7 @@ impl<'bvh, 'shape, T: BHValue, const D: usize, Shape: Bounded<T, D>>
ref child_l_aabb,
..
} => {
if self.ray.intersects_aabb(child_l_aabb) {
if self.query.intersects_aabb(child_l_aabb) {
self.node_index = child_l_index;
self.has_node = true;
} else {
Expand All @@ -93,7 +105,7 @@ impl<'bvh, 'shape, T: BHValue, const D: usize, Shape: Bounded<T, D>>
ref child_r_aabb,
..
} => {
if self.ray.intersects_aabb(child_r_aabb) {
if self.query.intersects_aabb(child_r_aabb) {
self.node_index = child_r_index;
self.has_node = true;
} else {
Expand All @@ -107,8 +119,8 @@ impl<'bvh, 'shape, T: BHValue, const D: usize, Shape: Bounded<T, D>>
}
}

impl<'shape, T: BHValue, const D: usize, Shape: Bounded<T, D>> Iterator
for BvhTraverseIterator<'_, 'shape, T, D, Shape>
impl<'shape, T: BHValue, const D: usize, Query: AabbIntersection<T, D>, Shape: Bounded<T, D>>
Iterator for BvhTraverseIterator<'_, 'shape, T, D, Query, Shape>
{
type Item = &'shape Shape;

Expand Down
7 changes: 7 additions & 0 deletions src/ray/ray_impl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! This module defines a Ray structure and intersection algorithms
//! for axis aligned bounding boxes and triangles.
use crate::aabb::AabbIntersection;
use crate::utils::{fast_max, fast_min};
use crate::{aabb::Aabb, bounding_hierarchy::BHValue};
use nalgebra::{
Expand Down Expand Up @@ -353,6 +354,12 @@ mod tests {
}
}

impl<T: BHValue, const D: usize> AabbIntersection<T, D> for Ray<T, D> {
fn intersects_aabb(&self, aabb: &Aabb<T, D>) -> bool {
self.intersects_aabb(&aabb)
}
}

#[cfg(all(feature = "bench", test))]
mod bench {
use rand::rngs::StdRng;
Expand Down

0 comments on commit 3b650f9

Please sign in to comment.