Skip to content

Commit

Permalink
Better traversal agreement assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
finnbear committed Jan 7, 2025
1 parent b8d41fa commit 0387e00
Showing 1 changed file with 50 additions and 16 deletions.
66 changes: 50 additions & 16 deletions fuzz/fuzz_targets/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fuzz_target!(|workload: Workload<3>| {
#[derive(Clone, Arbitrary)]
struct ArbitraryPoint<const D: usize> {
coordinates: [NotNan<Float>; D],
mode: Mode,
}

impl<const D: usize> Debug for ArbitraryPoint<D> {
Expand All @@ -58,7 +59,14 @@ impl<const D: usize> Debug for ArbitraryPoint<D> {
impl<const D: usize> ArbitraryPoint<D> {
/// Produces the corresponding point from the input.
fn point(&self) -> Point<Float, D> {
Point::<_, D>::from_slice(&self.coordinates).map(|f| f.into_inner().clamp(-LIMIT, LIMIT))
Point::<_, D>::from_slice(&self.coordinates).map(|f| {
let ret = f.into_inner().clamp(-LIMIT, LIMIT);
if self.mode.is_grid() {
ret.round()
} else {
ret
}
})
}
}

Expand All @@ -68,7 +76,6 @@ impl<const D: usize> ArbitraryPoint<D> {
struct ArbitraryShape<const D: usize> {
a: ArbitraryPoint<D>,
b: ArbitraryPoint<D>,
mode: Mode,
/// This will end up being mutated, but initializing it arbitrarily could catch bugs.
bh_node_index: usize,
}
Expand All @@ -93,14 +100,17 @@ impl<const D: usize> Bounded<Float, D> for ArbitraryShape<D> {

let mut aabb = Aabb::with_bounds(a.simd_min(b), a.simd_max(b));

if self.mode.is_grid() {
if self.mode_is_grid() {
aabb.min.iter_mut().for_each(|f| {
*f = f.round();
// Bound integer coordinate with a margin described by `Mode::Grid`.
*f = f.floor() - 1.0 / 3.0;
});
let min = aabb.min;
aabb.max.iter_mut().enumerate().for_each(|(i, f)| {
// Positive volume is ensured `max`.
*f = f.round().max(min[i] + 1.0);
// Bound integer coordinate with a margin described by `Mode::Grid`.
//
// Use `max` to ensure volume.
*f = f.max(min[i]).ceil() + 1.0 / 3.0;
});
}

Expand All @@ -118,13 +128,18 @@ impl<const D: usize> BHShape<Float, D> for ArbitraryShape<D> {
}
}

impl<const D: usize> ArbitraryShape<D> {
fn mode_is_grid(&self) -> bool {
self.a.mode.is_grid() && self.b.mode.is_grid()
}
}

/// The input for arbitrary ray, starting at an `ArbitraryPoint` and having a precisely
/// normalized direction.
#[derive(Clone, Arbitrary)]
struct ArbitraryRay<const D: usize> {
origin: ArbitraryPoint<D>,
destination: ArbitraryPoint<D>,
mode: Mode,
}

impl<const D: usize> Debug for ArbitraryRay<D> {
Expand All @@ -134,6 +149,10 @@ impl<const D: usize> Debug for ArbitraryRay<D> {
}

impl<const D: usize> ArbitraryRay<D> {
fn mode_is_grid(&self) -> bool {
self.origin.mode.is_grid() && self.destination.mode.is_grid()
}

/// Produces the corresponding ray from the input.
fn ray(&self) -> Ray<Float, D> {
// Note that this eventually gets normalized in `Ray::new`. We don't expect precision issues
Expand All @@ -150,7 +169,7 @@ impl<const D: usize> ArbitraryRay<D> {

let mut ray = Ray::new(self.origin.point(), direction);

if self.mode.is_grid() {
if self.mode_is_grid() {
ray.origin.iter_mut().for_each(|f| *f = f.round());

// Algorithm to find the closest unit-vector parallel to one of the axes. For fuzzing purposes,
Expand Down Expand Up @@ -218,11 +237,14 @@ enum Mode {
/// AABB's may have mostly arbitrary bounds, and ray may have mostly arbitrary
/// origin and direction.
Chaos,
/// AABB's have integer coordinates. Ray must have an origin consisting of
/// integer coordinates and a direction that is parallel to one of the axes.
/// AABB's bound integer coordinates with a margin of 1/3. Twice the margin, 2/3,
/// is less than 1, so AABB's can either deeply intersect or will have a gap at
/// least 1/3 wide. Ray must have an origin consisting of integer coordinates and
/// a direction that is parallel to one of the axes. Point must have integer
/// coordinates.
///
/// In this mode, all types of traversal are expected to yield the same results,
/// except when bugs exist that have yet to be fixed.
/// In this mode, all types of ray, AABB, and point traversal are expected to
/// yield the same results, except when bugs exist that have yet to be fixed.
Grid,
}

Expand Down Expand Up @@ -311,21 +333,27 @@ impl<const D: usize> Workload<D> {

loop {
// `self.shapes` are all in grid mode.
let all_shapes_grid = self.shapes.iter().all(|s| s.mode.is_grid());
let all_shapes_grid = self.shapes.iter().all(|s| s.mode_is_grid());

// Under these circumstances, the ray either definitively hits an AABB or it definitively
// doesn't. The lack of near hits and near misses prevents rounding errors that could cause
// different traversal algorithms to disagree.
//
// This relates to the current state of the BVH. It may change after each mutation is applied
// e.g. we could add the first non-grid shape or remove the last non-grid shape.
let assert_ray_traversal_agreement = self.ray.mode.is_grid() && all_shapes_grid;
let assert_ray_traversal_agreement = self.ray.mode_is_grid() && all_shapes_grid;

// Under these circumstances, the `self.aabb` either definitively intersects with an AABB or
// it definitively doesn't.
//
// Similar meaning to `assert_ray_traversal_agreement`.
let assert_aabb_traversal_agreement = self.aabb.mode.is_grid() && all_shapes_grid;
let assert_aabb_traversal_agreement = self.aabb.mode_is_grid() && all_shapes_grid;

// Under these circumstances, the `self.point` is either definitively contained by an AABB or
// definitively not contained.
//
// Similar meaning to `assert_ray_traversal_agreement`.
let assert_point_traversal_agreement = self.point.mode.is_grid() && all_shapes_grid;

// Check that these don't panic.
bvh.assert_consistent(&self.shapes);
Expand All @@ -340,8 +368,14 @@ impl<const D: usize> Workload<D> {
&self.aabb.aabb(),
assert_aabb_traversal_agreement,
);
self.fuzz_traversal(
&bvh,
&flat_bvh,
&self.point.point(),
assert_point_traversal_agreement,
);
// Due to sphere geometry, `Mode::Grid` doesn't imply traversals will agree.
self.fuzz_traversal(&bvh, &flat_bvh, &self.ball.ball(), false);
self.fuzz_traversal(&bvh, &flat_bvh, &self.point.point(), false);

let nearest_traverse_iterator = bvh
.nearest_traverse_iterator(&ray, &self.shapes)
Expand Down

0 comments on commit 0387e00

Please sign in to comment.