Skip to content

Commit

Permalink
Test generic traversal.
Browse files Browse the repository at this point in the history
  • Loading branch information
finnbear committed Jan 9, 2025
1 parent 5d302fa commit aad1f99
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 15 deletions.
50 changes: 50 additions & 0 deletions src/aabb/aabb_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,30 @@ impl<T: BHValue, const D: usize> Aabb<T, D> {
&& self.approx_contains_eps(&other.max, epsilon)
}

/// Returns true if this [`Aabb`] touches the `other` [`Aabb`].
///
/// # Examples
/// ```
/// use bvh::aabb::Aabb;
/// use nalgebra::Point3;
///
/// let aabb1 = Aabb::with_bounds(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0));
/// let aabb2 = Aabb::with_bounds(Point3::new(0.5, -0.1, -0.1), Point3::new(1.5, 0.1, 0.1));
///
/// assert!(aabb1.intersects_aabb(&aabb2));
/// ```
///
/// [`Aabb`]: struct.Aabb.html
pub fn intersects_aabb(&self, aabb: &Aabb<T, D>) -> bool {
// TODO: Try adding a SIMD specialization.
for i in 0..D {
if self.max[i] < aabb.min[i] || aabb.max[i] < self.min[i] {
return false;
}
}
true
}

/// Returns true if the `other` [`Aabb`] is approximately equal to this [`Aabb`]
/// with respect to some `epsilon`.
///
Expand Down Expand Up @@ -688,6 +712,32 @@ mod tests {
}

proptest! {
// Test properties of `Aabb` intersection.
#[test]
fn test_intersecting_aabbs(a: TupleVec, b: TupleVec, c: TupleVec, d: TupleVec, p: TupleVec) {
let a = tuple_to_point(&a);
let b = tuple_to_point(&b);
let c = tuple_to_point(&c);
let d = tuple_to_point(&d);
let aabb1 = TAabb3::empty().grow(&a).join_bounded(&b);
let aabb2 = TAabb3::empty().grow(&c).join_bounded(&d);
if aabb1.intersects_aabb(&aabb2) {
// For intersecting Aabb's, at least one point is shared.
let mut closest = aabb1.center();
for i in 0..3 {
closest[i] = closest[i].clamp(aabb2.min[i], aabb2.max[i]);
}
assert!(aabb1.contains(&closest), "closest={closest:?}");
assert!(aabb2.contains(&closest), "closest={closest:?}");
} else {
// For non-intersecting Aabb's, no point can't be in both Aabb's.
let p = tuple_to_point(&p);
for point in [a, b, c, d, p] {
assert!(!aabb1.contains(&point) || !aabb2.contains(&point));
}
}
}

// Test whether an empty `Aabb` does not contains anything.
#[test]
fn test_empty_contains_nothing(tpl: TupleVec) {
Expand Down
7 changes: 1 addition & 6 deletions src/aabb/intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,7 @@ pub trait IntersectsAabb<T: BHValue, const D: usize> {

impl<T: BHValue, const D: usize> IntersectsAabb<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
self.intersects_aabb(aabb)
}
}

Expand Down
79 changes: 70 additions & 9 deletions src/testbase.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Common utilities shared by unit tests.
use crate::aabb::Bounded;
use crate::aabb::{Aabb, Bounded, IntersectsAabb};
use crate::ball::Sphere;
use crate::bounding_hierarchy::{BHShape, BoundingHierarchy};

use num::{FromPrimitive, Integer};
Expand Down Expand Up @@ -129,18 +130,20 @@ pub fn build_empty_bh<BH: BoundingHierarchy<f32, 3>>() -> (Vec<UnitBox>, BH) {
/// Given a ray, a bounding hierarchy, the complete list of shapes in the scene and a list of
/// expected hits, verifies, whether the ray hits only the expected shapes.
fn traverse_and_verify<BH: BoundingHierarchy<f32, 3>>(
ray_origin: TPoint3,
ray_direction: TVector3,
query: &impl IntersectsAabb<f32, 3>,
all_shapes: &[UnitBox],
bh: &BH,
expected_shapes: &HashSet<i32>,
) {
let ray = TRay3::new(ray_origin, ray_direction);
let hit_shapes = bh.traverse(&ray, all_shapes);
let hit_shapes = bh.traverse(query, all_shapes);

assert_eq!(expected_shapes.len(), hit_shapes.len());
for shape in hit_shapes {
assert!(expected_shapes.contains(&shape.id));
assert!(
expected_shapes.contains(&shape.id),
"unexpected shape {}",
shape.id
);
}
}

Expand Down Expand Up @@ -169,7 +172,12 @@ fn traverse_some_built_bh<BH: BoundingHierarchy<f32, 3>>(all_shapes: &[UnitBox],
for id in -10..11 {
expected_shapes.insert(id);
}
traverse_and_verify(origin, direction, all_shapes, &bh, &expected_shapes);
traverse_and_verify(
&TRay3::new(origin, direction),
all_shapes,
&bh,
&expected_shapes,
);
}

{
Expand All @@ -180,7 +188,12 @@ fn traverse_some_built_bh<BH: BoundingHierarchy<f32, 3>>(all_shapes: &[UnitBox],
// It should hit only one box.
let mut expected_shapes = HashSet::new();
expected_shapes.insert(0);
traverse_and_verify(origin, direction, all_shapes, &bh, &expected_shapes);
traverse_and_verify(
&TRay3::new(origin, direction),
all_shapes,
&bh,
&expected_shapes,
);
}

{
Expand All @@ -193,7 +206,55 @@ fn traverse_some_built_bh<BH: BoundingHierarchy<f32, 3>>(all_shapes: &[UnitBox],
expected_shapes.insert(4);
expected_shapes.insert(5);
expected_shapes.insert(6);
traverse_and_verify(origin, direction, all_shapes, &bh, &expected_shapes);
traverse_and_verify(
&TRay3::new(origin, direction),
all_shapes,
&bh,
&expected_shapes,
);
}

{
// Define a point at the origin.
let point = TPoint3::new(0.0, 0.0, 0.0);

// It should be contained by the middle box.
let mut expected_shapes = HashSet::new();
expected_shapes.insert(0);
traverse_and_verify(&point, all_shapes, &bh, &expected_shapes);
}

{
// Define a point far away.
let point = TPoint3::new(0.0, 1000.0, 0.0);

// It shouldn't be contained by any boxes.
let expected_shapes = HashSet::new();
traverse_and_verify(&point, all_shapes, &bh, &expected_shapes);
}

{
// Define an AABB intersecting with some boxes.
let aabb = Aabb::with_bounds(TPoint3::new(5.1, -1.0, -1.0), TPoint3::new(9.9, 1.0, 1.0));

// It shouldn't be contained by any boxes.
let mut expected_shapes = HashSet::new();
for x in 5..=10 {
expected_shapes.insert(x);
}
traverse_and_verify(&aabb, all_shapes, &bh, &expected_shapes);
}

{
// Define a sphere intersecting with some boxes.
let aabb = Sphere::new(TPoint3::new(5.0, -1.0, -1.0), 1.4);

// It shouldn't be contained by any boxes.
let mut expected_shapes = HashSet::new();
for x in 4..=6 {
expected_shapes.insert(x);
}
traverse_and_verify(&aabb, all_shapes, &bh, &expected_shapes);
}
}

Expand Down

0 comments on commit aad1f99

Please sign in to comment.