Skip to content

Commit

Permalink
Bugfix #119 - distance traversal no non-hit shapes
Browse files Browse the repository at this point in the history
  • Loading branch information
finnbear committed Jan 10, 2025
1 parent c16b990 commit 7898579
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 107 deletions.
5 changes: 2 additions & 3 deletions fuzz/fuzz_targets/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ impl<const D: usize> Workload<D> {
bvh.assert_tight();
let flat_bvh = bvh.flatten();

let _traverse_ray = self.fuzz_traversal(
let traverse_ray = self.fuzz_traversal(
&bvh,
&flat_bvh,
&self.ray.ray(),
Expand Down Expand Up @@ -398,8 +398,7 @@ impl<const D: usize> Workload<D> {
.collect::<HashSet<_>>();

if assert_ray_traversal_agreement {
// TODO: Fails, due to bug(s) e.g. https://github.com/svenstaro/bvh/issues/119
//assert_eq!(_traverse_ray, nearest_traverse_iterator);
assert_eq!(traverse_ray, nearest_traverse_iterator);
} else {
// Fails, probably due to normal rounding errors.
}
Expand Down
120 changes: 38 additions & 82 deletions src/bvh/distance_traverse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ where
self.stack[self.stack_size]
}

/// Attempt to move to the child node that is closest to the ray, relative to the current node.
/// Attempt to move to the child node that is closest/furthest to the ray, depending on `ASCENDING`,
/// relative to the current node.
/// If it is a leaf, or the [`Ray`] does not intersect the any node [`Aabb`], `has_node` will become `false`.
fn move_nearest(&mut self) -> (usize, RestChild) {
fn move_first_priority(&mut self) -> (usize, RestChild) {
let current_node_index = self.node_index;
match self.bvh.nodes[current_node_index] {
BvhNode::Node {
Expand All @@ -91,93 +92,50 @@ where
ref child_r_aabb,
..
} => {
let (left_dist, _) = self.ray.intersection_slice_for_aabb(child_l_aabb);
let (right_dist, _) = self.ray.intersection_slice_for_aabb(child_r_aabb);
let left_dist = self
.ray
.intersection_slice_for_aabb(child_l_aabb)
.map(|(d, _)| d);
let right_dist = self
.ray
.intersection_slice_for_aabb(child_r_aabb)
.map(|(d, _)| d);

if left_dist < T::zero() {
if right_dist < T::zero() {
match (left_dist, right_dist) {
(None, None) => {
// no intersections with any children
self.has_node = false;
(current_node_index, RestChild::None)
} else {
// has intersection only with right child
}
(Some(_), None) => {
// has intersection only with left child
self.has_node = true;
self.node_index = child_r_index;
self.node_index = child_l_index;
let rest_child = RestChild::None;
(current_node_index, rest_child)
}
} else if right_dist < T::zero() {
// has intersection only with left child
self.has_node = true;
self.node_index = child_l_index;
let rest_child = RestChild::None;
(current_node_index, rest_child)
} else if left_dist > right_dist {
// right is closer than left
self.has_node = true;
self.node_index = child_r_index;
let rest_child = RestChild::Left;
return (current_node_index, rest_child);
} else {
// left is closer than right
self.has_node = true;
self.node_index = child_l_index;
let rest_child = RestChild::Right;
return (current_node_index, rest_child);
}
}
BvhNode::Leaf { .. } => {
self.has_node = false;
(current_node_index, RestChild::None)
}
}
}

/// Attempt to move to the child node that is the farthest from the ray, relative to the current node.
/// If it is a leaf, or the [`Ray`] does not intersect the node [`Aabb`], `has_node` will become `false`.
fn move_furthest(&mut self) -> (usize, RestChild) {
let current_node_index = self.node_index;
match self.bvh.nodes[current_node_index] {
BvhNode::Node {
child_l_index,
ref child_l_aabb,
child_r_index,
ref child_r_aabb,
..
} => {
let (_, left_dist) = self.ray.intersection_slice_for_aabb(child_l_aabb);
let (_, right_dist) = self.ray.intersection_slice_for_aabb(child_r_aabb);

if left_dist < T::zero() {
if right_dist < T::zero() {
// no intersections with any children
self.has_node = false;
(current_node_index, RestChild::None)
} else {
(None, Some(_)) => {
// has intersection only with right child
self.has_node = true;
self.node_index = child_r_index;
let rest_child = RestChild::None;
(current_node_index, rest_child)
}
} else if right_dist < T::zero() {
// has intersection only with left child
self.has_node = true;
self.node_index = child_l_index;
let rest_child = RestChild::None;
(current_node_index, rest_child)
} else if left_dist < right_dist {
// right is farther than left
self.has_node = true;
self.node_index = child_r_index;
let rest_child = RestChild::Left;
return (current_node_index, rest_child);
} else {
// left is farther than right
self.has_node = true;
self.node_index = child_l_index;
let rest_child = RestChild::Right;
return (current_node_index, rest_child);
(Some(left_dist), Some(right_dist)) => {
if (left_dist > right_dist) ^ !ASCENDING {
// right is higher priority than left
self.has_node = true;
self.node_index = child_r_index;
let rest_child = RestChild::Left;
(current_node_index, rest_child)
} else {
// left is higher priority than right
self.has_node = true;
self.node_index = child_l_index;
let rest_child = RestChild::Right;
(current_node_index, rest_child)
}
}
}
}
BvhNode::Leaf { .. } => {
Expand Down Expand Up @@ -231,11 +189,7 @@ where

if self.has_node {
// If we have any node, attempt to move to its nearest child.
let stack_info = if ASCENDING {
self.move_nearest()
} else {
self.move_furthest()
};
let stack_info = self.move_first_priority();
// Save current node and farthest child
self.stack_push(stack_info)
} else {
Expand Down Expand Up @@ -297,8 +251,10 @@ mod tests {
let mut prev_far_dist = f32::INFINITY;

for (near_shape, far_shape) in near_it.zip(far_it) {
let (intersect_near_dist, _) = ray.intersection_slice_for_aabb(&near_shape.aabb());
let (intersect_far_dist, _) = ray.intersection_slice_for_aabb(&far_shape.aabb());
let (intersect_near_dist, _) =
ray.intersection_slice_for_aabb(&near_shape.aabb()).unwrap();
let (intersect_far_dist, _) =
ray.intersection_slice_for_aabb(&far_shape.aabb()).unwrap();

assert!(expected_shapes.contains(&near_shape.id));
assert!(expected_shapes.contains(&far_shape.id));
Expand Down
73 changes: 51 additions & 22 deletions src/ray/ray_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! for axis aligned bounding boxes and triangles.
use crate::aabb::IntersectsAabb;
use crate::utils::{fast_max, fast_min};
use crate::utils::fast_max;
use crate::{aabb::Aabb, bounding_hierarchy::BHValue};
use nalgebra::{
ClosedAddAssign, ClosedMulAssign, ClosedSubAssign, ComplexField, Point, SVector, SimdPartialOrd,
Expand Down Expand Up @@ -113,31 +113,26 @@ impl<T: BHValue, const D: usize> Ray<T, D> {
/// where the first number is the distance from [`Ray`] to the nearest intersection point
/// and the second number is the distance from [`Ray`] to the farthest intersection point
///
/// If there are no intersections, it returns negative one for both distances
pub fn intersection_slice_for_aabb(&self, aabb: &Aabb<T, D>) -> (T, T)
/// If there are no intersections, it returns `None`.
pub fn intersection_slice_for_aabb(&self, aabb: &Aabb<T, D>) -> Option<(T, T)>
where
T: BHValue,
{
// https://iquilezles.org/articles/intersectors/
let mut n = self.origin.coords - aabb.center().coords;
n.component_mul_assign(&self.inv_direction);
// Copied from the default `RayIntersection` implementation. TODO: abstract and add SIMD.
let lbr = (aabb[0].coords - self.origin.coords).component_mul(&self.inv_direction);
let rtr = (aabb[1].coords - self.origin.coords).component_mul(&self.inv_direction);

let k = self.inv_direction.abs().component_mul(&aabb.half_size());
let t1 = -n - k;
let t2 = -n + k;
let (inf, sup) = lbr.inf_sup(&rtr);

let entry_distance = t1
.iter()
.skip(1)
.fold(t1[0], |acc, x| -> T { fast_max(*x, acc) });
let exit_distance = t2.iter().skip(1).fold(t2[0], |acc, x| fast_min(*x, acc));
let tmin = inf.max();
let tmax = sup.min();

// no intersection
if entry_distance > exit_distance || exit_distance < T::zero() {
return (T::from_f32(-1.0).unwrap(), T::from_f32(-1.0).unwrap());
if tmin > tmax || tmax < T::zero() {
return None;
}

(fast_max(entry_distance, T::zero()), exit_distance)
Some((fast_max(tmin, T::zero()), tmax))
}

/// Implementation of the
Expand Down Expand Up @@ -213,8 +208,12 @@ impl<T: BHValue, const D: usize> Ray<T, D> {
mod tests {
use std::cmp;

use crate::testbase::{
tuple_to_point, tuplevec_small_strategy, TAabb3, TPoint3, TRay3, TVector3, TupleVec,
use crate::{
aabb::Bounded,
testbase::{
tuple_to_point, tuplevec_small_strategy, TAabb3, TPoint3, TRay3, TVector3, TupleVec,
UnitBox,
},
};

use proptest::prelude::*;
Expand Down Expand Up @@ -247,6 +246,35 @@ mod tests {
assert!(ray.intersects_aabb(&aabb));
}

/// Ensure slice has correct min and max distance for a particular case.
#[test]
fn test_ray_slice_distance_accuracy() {
let aabb = TAabb3::empty()
.grow(&TPoint3::new(-3.0, -4.0, -5.0))
.grow(&TPoint3::new(-6.0, -8.0, 5.0));
let ray = TRay3::new(
TPoint3::new(2.0, 2.0, 2.0),
TVector3::new(-5.0, -8.66666, -3.666666),
);
let expected_min = 10.6562;
let expected_max = 12.3034;
let (min, max) = ray.intersection_slice_for_aabb(&aabb.aabb()).unwrap();
assert!((min - expected_min).abs() < 0.01);
assert!((max - expected_max).abs() < 0.01);
}

/// Ensure no slice is returned when the ray is parallel to AABB faces but
/// doesn't hit, which is a special case due to infinities in the computation.
#[test]
fn test_parallel_ray_slice() {
let aabb = UnitBox::new(0, TPoint3::new(-50.0, -50.0, -25.0));
let ray = TRay3::new(
TPoint3::new(-50.0, -50.0, -50.0),
TVector3::new(1.0, 0.0, 0.0),
);
assert!(ray.intersection_slice_for_aabb(&aabb.aabb()).is_none());
}

proptest! {
// Test whether a `Ray` which points at the center of an `Aabb` intersects it.
#[test]
Expand Down Expand Up @@ -277,7 +305,7 @@ mod tests {
tuplevec_small_strategy(),
tuplevec_small_strategy())) {
let (ray, aabb) = gen_ray_to_aabb(data);
let (start_dist, end_dist) = ray.intersection_slice_for_aabb(&aabb);
let (start_dist, end_dist) = ray.intersection_slice_for_aabb(&aabb).unwrap();
assert!(start_dist < end_dist);
assert!(start_dist >= 0.0);
}
Expand All @@ -294,14 +322,15 @@ mod tests {
ray.direction = -ray.direction;
ray.inv_direction = -ray.inv_direction;

let (start_dist, end_dist) = ray.intersection_slice_for_aabb(&aabb);
let slice = ray.intersection_slice_for_aabb(&aabb);
if aabb.contains(&ray.origin) {
let (start_dist, end_dist) = slice.unwrap();
// ray inside of aabb
assert!(start_dist < end_dist);
assert!(start_dist >= 0.0);
} else {
// ray outside of aabb and doesn't intersect it
assert!((start_dist, end_dist) == (-1.0, -1.0));
assert!(slice.is_none());
}
}

Expand Down

0 comments on commit 7898579

Please sign in to comment.