Skip to content

Commit

Permalink
Fuzz.
Browse files Browse the repository at this point in the history
  • Loading branch information
finnbear committed Jan 7, 2025
1 parent 15b4b0a commit b8d41fa
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 37 deletions.
145 changes: 109 additions & 36 deletions fuzz/fuzz_targets/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};

use arbitrary::Arbitrary;
use bvh::aabb::{Aabb, Bounded};
use bvh::aabb::{Aabb, Bounded, IntersectsAabb};
use bvh::ball::Ball;
use bvh::bounding_hierarchy::{BHShape, BoundingHierarchy};
use bvh::bvh::Bvh;
use bvh::flat_bvh::FlatBvh;
use bvh::ray::Ray;
use libfuzzer_sys::fuzz_target;
use nalgebra::{Point, SimdPartialOrd};
Expand All @@ -47,6 +49,12 @@ struct ArbitraryPoint<const D: usize> {
coordinates: [NotNan<Float>; D],
}

impl<const D: usize> Debug for ArbitraryPoint<D> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.point(), f)
}
}

impl<const D: usize> ArbitraryPoint<D> {
/// Produces the corresponding point from the input.
fn point(&self) -> Point<Float, D> {
Expand All @@ -61,6 +69,7 @@ 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 @@ -85,14 +94,13 @@ 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() {
let mut center = aabb.center();
center.iter_mut().for_each(|f| *f = f.round());
// Unit AABB around center.
aabb.min.iter_mut().enumerate().for_each(|(i, f)| {
*f = center[i] - 0.5;
aabb.min.iter_mut().for_each(|f| {
*f = f.round();
});
let min = aabb.min;
aabb.max.iter_mut().enumerate().for_each(|(i, f)| {
*f = center[i] + 0.5;
// Positive volume is ensured `max`.
*f = f.round().max(min[i] + 1.0);
});
}

Expand Down Expand Up @@ -174,6 +182,29 @@ impl<const D: usize> ArbitraryRay<D> {
}
}

/// The input for arbitrary ray, starting at an `ArbitraryPoint` and having a precisely
/// normalized direction.
#[derive(Clone, Arbitrary)]
struct ArbitraryBall<const D: usize> {
center: ArbitraryPoint<D>,
radius: NotNan<f32>,
}

impl<const D: usize> Debug for ArbitraryBall<D> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.ball(), f)
}
}

impl<const D: usize> ArbitraryBall<D> {
fn ball(&self) -> Ball<Float, D> {
Ball {
center: self.center.point(),
radius: self.radius.into_inner().max(0.0),
}
}
}

/// An arbitrary mutation to apply to the BVH to fuzz BVH optimization.
#[derive(Debug, Arbitrary)]
enum ArbitraryMutation<const D: usize> {
Expand All @@ -187,9 +218,8 @@ enum Mode {
/// AABB's may have mostly arbitrary bounds, and ray may have mostly arbitrary
/// origin and direction.
Chaos,
/// AABB's are unit cubes, and must 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 have integer coordinates. Ray must have an origin consisting of
/// integer coordinates and a direction that is parallel to one of the axes.
///
/// In this mode, all types of traversal are expected to yield the same results,
/// except when bugs exist that have yet to be fixed.
Expand All @@ -206,11 +236,58 @@ impl Mode {
#[derive(Debug, Arbitrary)]
struct Workload<const D: usize> {
shapes: Vec<ArbitraryShape<D>>,
/// Traverse by ray.
ray: ArbitraryRay<D>,
/// Traverse by point.
point: ArbitraryPoint<D>,
/// Traverse by AABB.
aabb: ArbitraryShape<D>,
/// Traverse by ball.
ball: ArbitraryBall<D>,
mutations: Vec<ArbitraryMutation<D>>,
}

impl<const D: usize> Workload<D> {
/// Compares normal, iterative, and flat-BVH traversal of the same query.
///
/// Returns the result of normal traversal.
///
/// # Panics
/// If `assert_agreement` is true, panics if the results differ in an
/// unexpected way.
fn fuzz_traversal<'a>(
&'a self,
bvh: &'a Bvh<Float, D>,
flat_bvh: &'a FlatBvh<Float, D>,
query: &impl IntersectsAabb<Float, D>,
assert_agreement: bool,
) -> HashSet<ByPtr<'a, ArbitraryShape<D>>> {
let traverse = bvh
.traverse(query, &self.shapes)
.into_iter()
.map(ByPtr)
.collect::<HashSet<_>>();
let traverse_iterator = bvh
.traverse_iterator(query, &self.shapes)
.map(ByPtr)
.collect::<HashSet<_>>();
let _traverse_flat = flat_bvh
.traverse(query, &self.shapes)
.into_iter()
.map(ByPtr)
.collect::<HashSet<_>>();

if assert_agreement {
assert_eq!(traverse, traverse_iterator);
// TODO: Fails, due to bug(s) e.g. https://github.com/svenstaro/bvh/issues/120.
// assert_eq!(traverse, traverse_flat);
} else {
// Fails, probably due to normal rounding errors.
}

traverse
}

/// Called directly from the `cargo fuzz` entry point. Code in this function is
/// easier for `rust-analyzer`` than code in that macro.
///
Expand All @@ -233,42 +310,38 @@ 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());

// 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_traversal_agreement =
self.ray.mode.is_grid() && self.shapes.iter().all(|s| s.mode.is_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;

// Check that these don't panic.
bvh.assert_consistent(&self.shapes);
bvh.assert_tight();
let flat_bvh = bvh.flatten();

let traverse = bvh
.traverse(&ray, &self.shapes)
.into_iter()
.map(ByPtr)
.collect::<HashSet<_>>();
let traverse_iterator = bvh
.traverse_iterator(&ray, &self.shapes)
.map(ByPtr)
.collect::<HashSet<_>>();
let _traverse_flat = flat_bvh
.traverse(&ray, &self.shapes)
.into_iter()
.map(ByPtr)
.collect::<HashSet<_>>();

if assert_traversal_agreement {
assert_eq!(traverse, traverse_iterator);
// TODO: Fails, due to bug(s) e.g. https://github.com/svenstaro/bvh/issues/120.
// assert_eq!(traverse, traverse_flat);
} else {
// Fails, probably due to normal rounding errors.
}
let _traverse_ray =
self.fuzz_traversal(&bvh, &flat_bvh, &ray, assert_ray_traversal_agreement);
self.fuzz_traversal(
&bvh,
&flat_bvh,
&self.aabb.aabb(),
assert_aabb_traversal_agreement,
);
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 All @@ -279,9 +352,9 @@ impl<const D: usize> Workload<D> {
.map(ByPtr)
.collect::<HashSet<_>>();

if assert_traversal_agreement {
if assert_ray_traversal_agreement {
// TODO: Fails, due to bug(s) e.g. https://github.com/svenstaro/bvh/issues/119
//assert_eq!(traverse_iterator, nearest_traverse_iterator);
//assert_eq!(_traverse_ray, nearest_traverse_iterator);
} else {
// Fails, probably due to normal rounding errors.
}
Expand Down
4 changes: 3 additions & 1 deletion src/aabb/intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use crate::{aabb::Aabb, bounding_hierarchy::BHValue};
/// 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 IntersectsAabb<T: BHValue, const D: usize> {
/// Returns whether this object intersects an [`Aabb`].
/// Returns whether this object intersects an [`Aabb`]. For the purpose of intersection,
/// the [`Aabb`] is a solid with area/volume. As a result, intersecting an [`Aabb`]
/// implies intersecting any [`Aabb`] that contains that [`Aabb`].
///
/// # Examples
/// ```
Expand Down
7 changes: 7 additions & 0 deletions src/ball.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ use crate::{
};
use nalgebra::Point;

/// A 2d circle.
pub type Circle<T> = Ball<T, 2>;

/// A 3d sphere.
pub type Sphere<T> = Ball<T, 3>;

/// In 2D, a circle. In 3D, a sphere. This can be used for traversing BVH's.
#[derive(Debug, Clone, Copy)]
pub struct Ball<T: BHValue, const D: usize> {
/// The center of the ball.
pub center: Point<T, D>,
Expand Down

0 comments on commit b8d41fa

Please sign in to comment.