Add volume cast intersection tests (#11586)
# Objective - Add a basic form of shapecasting for bounding volumes ## Solution - Implement AabbCast2d, AabbCast3d, BoundingCircleCast, and BoundingSphereCast - These are really just raycasts, but they modify the volumes the ray is casting against - The tests are slightly simpler, since they just use the raycast code for the heavy lifting
This commit is contained in:
parent
76d32c9d5a
commit
1b98de68fe
@ -29,7 +29,7 @@ pub trait Bounded2d {
|
|||||||
|
|
||||||
/// A 2D axis-aligned bounding box, or bounding rectangle
|
/// A 2D axis-aligned bounding box, or bounding rectangle
|
||||||
#[doc(alias = "BoundingRectangle")]
|
#[doc(alias = "BoundingRectangle")]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Aabb2d {
|
pub struct Aabb2d {
|
||||||
/// The minimum, conventionally bottom-left, point of the box
|
/// The minimum, conventionally bottom-left, point of the box
|
||||||
pub min: Vec2,
|
pub min: Vec2,
|
||||||
@ -328,7 +328,7 @@ mod aabb2d_tests {
|
|||||||
use crate::primitives::Circle;
|
use crate::primitives::Circle;
|
||||||
|
|
||||||
/// A bounding circle
|
/// A bounding circle
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct BoundingCircle {
|
pub struct BoundingCircle {
|
||||||
/// The center of the bounding circle
|
/// The center of the bounding circle
|
||||||
pub center: Vec2,
|
pub center: Vec2,
|
||||||
@ -425,10 +425,10 @@ impl BoundingVolume for BoundingCircle {
|
|||||||
let diff = other.center - self.center;
|
let diff = other.center - self.center;
|
||||||
let length = diff.length();
|
let length = diff.length();
|
||||||
if self.radius() >= length + other.radius() {
|
if self.radius() >= length + other.radius() {
|
||||||
return self.clone();
|
return *self;
|
||||||
}
|
}
|
||||||
if other.radius() >= length + self.radius() {
|
if other.radius() >= length + self.radius() {
|
||||||
return other.clone();
|
return *other;
|
||||||
}
|
}
|
||||||
let dir = diff / length;
|
let dir = diff / length;
|
||||||
Self::new(
|
Self::new(
|
||||||
|
|||||||
@ -24,7 +24,7 @@ pub trait Bounded3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A 3D axis-aligned bounding box
|
/// A 3D axis-aligned bounding box
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Aabb3d {
|
pub struct Aabb3d {
|
||||||
/// The minimum point of the box
|
/// The minimum point of the box
|
||||||
pub min: Vec3,
|
pub min: Vec3,
|
||||||
@ -324,7 +324,7 @@ mod aabb3d_tests {
|
|||||||
use crate::primitives::Sphere;
|
use crate::primitives::Sphere;
|
||||||
|
|
||||||
/// A bounding sphere
|
/// A bounding sphere
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct BoundingSphere {
|
pub struct BoundingSphere {
|
||||||
/// The center of the bounding sphere
|
/// The center of the bounding sphere
|
||||||
pub center: Vec3,
|
pub center: Vec3,
|
||||||
@ -417,10 +417,10 @@ impl BoundingVolume for BoundingSphere {
|
|||||||
let diff = other.center - self.center;
|
let diff = other.center - self.center;
|
||||||
let length = diff.length();
|
let length = diff.length();
|
||||||
if self.radius() >= length + other.radius() {
|
if self.radius() >= length + other.radius() {
|
||||||
return self.clone();
|
return *self;
|
||||||
}
|
}
|
||||||
if other.radius() >= length + self.radius() {
|
if other.radius() >= length + self.radius() {
|
||||||
return other.clone();
|
return *other;
|
||||||
}
|
}
|
||||||
let dir = diff / length;
|
let dir = diff / length;
|
||||||
Self::new(
|
Self::new(
|
||||||
|
|||||||
@ -13,7 +13,7 @@ pub struct RayTest2d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RayTest2d {
|
impl RayTest2d {
|
||||||
/// Construct a [`RayTest2d`] from an origin, [`Direction2d`] and max distance.
|
/// Construct a [`RayTest2d`] from an origin, [`Direction2d`], and max distance.
|
||||||
pub fn new(origin: Vec2, direction: Direction2d, max: f32) -> Self {
|
pub fn new(origin: Vec2, direction: Direction2d, max: f32) -> Self {
|
||||||
Self::from_ray(Ray2d { origin, direction }, max)
|
Self::from_ray(Ray2d { origin, direction }, max)
|
||||||
}
|
}
|
||||||
@ -98,6 +98,80 @@ impl IntersectsVolume<BoundingCircle> for RayTest2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An intersection test that casts an [`Aabb2d`] along a ray.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AabbCast2d {
|
||||||
|
/// The ray along which to cast the bounding volume
|
||||||
|
pub ray: RayTest2d,
|
||||||
|
/// The aabb that is being cast
|
||||||
|
pub aabb: Aabb2d,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AabbCast2d {
|
||||||
|
/// Construct an [`AabbCast2d`] from an [`Aabb2d`], origin, [`Direction2d`], and max distance.
|
||||||
|
pub fn new(aabb: Aabb2d, origin: Vec2, direction: Direction2d, max: f32) -> Self {
|
||||||
|
Self::from_ray(aabb, Ray2d { origin, direction }, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct an [`AabbCast2d`] from an [`Aabb2d`], [`Ray2d`], and max distance.
|
||||||
|
pub fn from_ray(aabb: Aabb2d, ray: Ray2d, max: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
ray: RayTest2d::from_ray(ray, max),
|
||||||
|
aabb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the distance at which the [`Aabb2d`]s collide, if at all.
|
||||||
|
pub fn aabb_collision_at(&self, mut aabb: Aabb2d) -> Option<f32> {
|
||||||
|
aabb.min -= self.aabb.max;
|
||||||
|
aabb.max -= self.aabb.min;
|
||||||
|
self.ray.aabb_intersection_at(&aabb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntersectsVolume<Aabb2d> for AabbCast2d {
|
||||||
|
fn intersects(&self, volume: &Aabb2d) -> bool {
|
||||||
|
self.aabb_collision_at(*volume).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An intersection test that casts a [`BoundingCircle`] along a ray.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BoundingCircleCast {
|
||||||
|
/// The ray along which to cast the bounding volume
|
||||||
|
pub ray: RayTest2d,
|
||||||
|
/// The circle that is being cast
|
||||||
|
pub circle: BoundingCircle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoundingCircleCast {
|
||||||
|
/// Construct a [`BoundingCircleCast`] from a [`BoundingCircle`], origin, [`Direction2d`], and max distance.
|
||||||
|
pub fn new(circle: BoundingCircle, origin: Vec2, direction: Direction2d, max: f32) -> Self {
|
||||||
|
Self::from_ray(circle, Ray2d { origin, direction }, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`BoundingCircleCast`] from a [`BoundingCircle`], [`Ray2d`], and max distance.
|
||||||
|
pub fn from_ray(circle: BoundingCircle, ray: Ray2d, max: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
ray: RayTest2d::from_ray(ray, max),
|
||||||
|
circle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the distance at which the [`BoundingCircle`]s collide, if at all.
|
||||||
|
pub fn circle_collision_at(&self, mut circle: BoundingCircle) -> Option<f32> {
|
||||||
|
circle.center -= self.circle.center;
|
||||||
|
circle.circle.radius += self.circle.radius();
|
||||||
|
self.ray.circle_intersection_at(&circle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntersectsVolume<BoundingCircle> for BoundingCircleCast {
|
||||||
|
fn intersects(&self, volume: &BoundingCircle) -> bool {
|
||||||
|
self.circle_collision_at(*volume).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -327,4 +401,138 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aabb_cast_hits() {
|
||||||
|
for (test, volume, expected_distance) in &[
|
||||||
|
(
|
||||||
|
// Hit the center of the aabb, that a ray would've also hit
|
||||||
|
AabbCast2d::new(
|
||||||
|
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
|
||||||
|
Vec2::ZERO,
|
||||||
|
Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the center of the aabb, but from the other side
|
||||||
|
AabbCast2d::new(
|
||||||
|
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
|
||||||
|
Vec2::Y * 10.,
|
||||||
|
-Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the edge of the aabb, that a ray would've missed
|
||||||
|
AabbCast2d::new(
|
||||||
|
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
|
||||||
|
Vec2::X * 1.5,
|
||||||
|
Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the edge of the aabb, by casting an off-center AABB
|
||||||
|
AabbCast2d::new(
|
||||||
|
Aabb2d::new(Vec2::X * -2., Vec2::ONE),
|
||||||
|
Vec2::X * 3.,
|
||||||
|
Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let case = format!(
|
||||||
|
"Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}",
|
||||||
|
test, volume, expected_distance
|
||||||
|
);
|
||||||
|
assert!(test.intersects(volume), "{}", case);
|
||||||
|
let actual_distance = test.aabb_collision_at(*volume).unwrap();
|
||||||
|
assert!(
|
||||||
|
(actual_distance - expected_distance).abs() < EPSILON,
|
||||||
|
"{}\n Actual distance: {}",
|
||||||
|
case,
|
||||||
|
actual_distance
|
||||||
|
);
|
||||||
|
|
||||||
|
let inverted_ray =
|
||||||
|
RayTest2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
|
||||||
|
assert!(!inverted_ray.intersects(volume), "{}", case);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circle_cast_hits() {
|
||||||
|
for (test, volume, expected_distance) in &[
|
||||||
|
(
|
||||||
|
// Hit the center of the bounding circle, that a ray would've also hit
|
||||||
|
BoundingCircleCast::new(
|
||||||
|
BoundingCircle::new(Vec2::ZERO, 1.),
|
||||||
|
Vec2::ZERO,
|
||||||
|
Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingCircle::new(Vec2::Y * 5., 1.),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the center of the bounding circle, but from the other side
|
||||||
|
BoundingCircleCast::new(
|
||||||
|
BoundingCircle::new(Vec2::ZERO, 1.),
|
||||||
|
Vec2::Y * 10.,
|
||||||
|
-Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingCircle::new(Vec2::Y * 5., 1.),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the bounding circle off-center, that a ray would've missed
|
||||||
|
BoundingCircleCast::new(
|
||||||
|
BoundingCircle::new(Vec2::ZERO, 1.),
|
||||||
|
Vec2::X * 1.5,
|
||||||
|
Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingCircle::new(Vec2::Y * 5., 1.),
|
||||||
|
3.677,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the bounding circle off-center, by casting a circle that is off-center
|
||||||
|
BoundingCircleCast::new(
|
||||||
|
BoundingCircle::new(Vec2::X * -1.5, 1.),
|
||||||
|
Vec2::X * 3.,
|
||||||
|
Direction2d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingCircle::new(Vec2::Y * 5., 1.),
|
||||||
|
3.677,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let case = format!(
|
||||||
|
"Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}",
|
||||||
|
test, volume, expected_distance
|
||||||
|
);
|
||||||
|
assert!(test.intersects(volume), "{}", case);
|
||||||
|
let actual_distance = test.circle_collision_at(*volume).unwrap();
|
||||||
|
assert!(
|
||||||
|
(actual_distance - expected_distance).abs() < EPSILON,
|
||||||
|
"{}\n Actual distance: {}",
|
||||||
|
case,
|
||||||
|
actual_distance
|
||||||
|
);
|
||||||
|
|
||||||
|
let inverted_ray =
|
||||||
|
RayTest2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
|
||||||
|
assert!(!inverted_ray.intersects(volume), "{}", case);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ pub struct RayTest3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RayTest3d {
|
impl RayTest3d {
|
||||||
/// Construct a [`RayTest3d`] from an origin, [`Direction3d`] and max distance.
|
/// Construct a [`RayTest3d`] from an origin, [`Direction3d`], and max distance.
|
||||||
pub fn new(origin: Vec3, direction: Direction3d, max: f32) -> Self {
|
pub fn new(origin: Vec3, direction: Direction3d, max: f32) -> Self {
|
||||||
Self::from_ray(Ray3d { origin, direction }, max)
|
Self::from_ray(Ray3d { origin, direction }, max)
|
||||||
}
|
}
|
||||||
@ -105,6 +105,80 @@ impl IntersectsVolume<BoundingSphere> for RayTest3d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An intersection test that casts an [`Aabb3d`] along a ray.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AabbCast3d {
|
||||||
|
/// The ray along which to cast the bounding volume
|
||||||
|
pub ray: RayTest3d,
|
||||||
|
/// The aabb that is being cast
|
||||||
|
pub aabb: Aabb3d,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AabbCast3d {
|
||||||
|
/// Construct an [`AabbCast3d`] from an [`Aabb3d`], origin, [`Direction3d`], and max distance.
|
||||||
|
pub fn new(aabb: Aabb3d, origin: Vec3, direction: Direction3d, max: f32) -> Self {
|
||||||
|
Self::from_ray(aabb, Ray3d { origin, direction }, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct an [`AabbCast3d`] from an [`Aabb3d`], [`Ray3d`], and max distance.
|
||||||
|
pub fn from_ray(aabb: Aabb3d, ray: Ray3d, max: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
ray: RayTest3d::from_ray(ray, max),
|
||||||
|
aabb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the distance at which the [`Aabb3d`]s collide, if at all.
|
||||||
|
pub fn aabb_collision_at(&self, mut aabb: Aabb3d) -> Option<f32> {
|
||||||
|
aabb.min -= self.aabb.max;
|
||||||
|
aabb.max -= self.aabb.min;
|
||||||
|
self.ray.aabb_intersection_at(&aabb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntersectsVolume<Aabb3d> for AabbCast3d {
|
||||||
|
fn intersects(&self, volume: &Aabb3d) -> bool {
|
||||||
|
self.aabb_collision_at(*volume).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An intersection test that casts a [`BoundingSphere`] along a ray.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BoundingSphereCast {
|
||||||
|
/// The ray along which to cast the bounding volume
|
||||||
|
pub ray: RayTest3d,
|
||||||
|
/// The sphere that is being cast
|
||||||
|
pub sphere: BoundingSphere,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoundingSphereCast {
|
||||||
|
/// Construct a [`BoundingSphereCast`] from a [`BoundingSphere`], origin, [`Direction3d`], and max distance.
|
||||||
|
pub fn new(sphere: BoundingSphere, origin: Vec3, direction: Direction3d, max: f32) -> Self {
|
||||||
|
Self::from_ray(sphere, Ray3d { origin, direction }, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`BoundingSphereCast`] from a [`BoundingSphere`], [`Ray3d`], and max distance.
|
||||||
|
pub fn from_ray(sphere: BoundingSphere, ray: Ray3d, max: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
ray: RayTest3d::from_ray(ray, max),
|
||||||
|
sphere,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the distance at which the [`BoundingSphere`]s collide, if at all.
|
||||||
|
pub fn sphere_collision_at(&self, mut sphere: BoundingSphere) -> Option<f32> {
|
||||||
|
sphere.center -= self.sphere.center;
|
||||||
|
sphere.sphere.radius += self.sphere.radius();
|
||||||
|
self.ray.sphere_intersection_at(&sphere)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntersectsVolume<BoundingSphere> for BoundingSphereCast {
|
||||||
|
fn intersects(&self, volume: &BoundingSphere) -> bool {
|
||||||
|
self.sphere_collision_at(*volume).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -346,4 +420,138 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aabb_cast_hits() {
|
||||||
|
for (test, volume, expected_distance) in &[
|
||||||
|
(
|
||||||
|
// Hit the center of the aabb, that a ray would've also hit
|
||||||
|
AabbCast3d::new(
|
||||||
|
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
|
||||||
|
Vec3::ZERO,
|
||||||
|
Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the center of the aabb, but from the other side
|
||||||
|
AabbCast3d::new(
|
||||||
|
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
|
||||||
|
Vec3::Y * 10.,
|
||||||
|
-Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the edge of the aabb, that a ray would've missed
|
||||||
|
AabbCast3d::new(
|
||||||
|
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
|
||||||
|
Vec3::X * 1.5,
|
||||||
|
Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the edge of the aabb, by casting an off-center AABB
|
||||||
|
AabbCast3d::new(
|
||||||
|
Aabb3d::new(Vec3::X * -2., Vec3::ONE),
|
||||||
|
Vec3::X * 3.,
|
||||||
|
Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let case = format!(
|
||||||
|
"Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}",
|
||||||
|
test, volume, expected_distance
|
||||||
|
);
|
||||||
|
assert!(test.intersects(volume), "{}", case);
|
||||||
|
let actual_distance = test.aabb_collision_at(*volume).unwrap();
|
||||||
|
assert!(
|
||||||
|
(actual_distance - expected_distance).abs() < EPSILON,
|
||||||
|
"{}\n Actual distance: {}",
|
||||||
|
case,
|
||||||
|
actual_distance
|
||||||
|
);
|
||||||
|
|
||||||
|
let inverted_ray =
|
||||||
|
RayTest3d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
|
||||||
|
assert!(!inverted_ray.intersects(volume), "{}", case);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sphere_cast_hits() {
|
||||||
|
for (test, volume, expected_distance) in &[
|
||||||
|
(
|
||||||
|
// Hit the center of the bounding sphere, that a ray would've also hit
|
||||||
|
BoundingSphereCast::new(
|
||||||
|
BoundingSphere::new(Vec3::ZERO, 1.),
|
||||||
|
Vec3::ZERO,
|
||||||
|
Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingSphere::new(Vec3::Y * 5., 1.),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the center of the bounding sphere, but from the other side
|
||||||
|
BoundingSphereCast::new(
|
||||||
|
BoundingSphere::new(Vec3::ZERO, 1.),
|
||||||
|
Vec3::Y * 10.,
|
||||||
|
-Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingSphere::new(Vec3::Y * 5., 1.),
|
||||||
|
3.,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the bounding sphere off-center, that a ray would've missed
|
||||||
|
BoundingSphereCast::new(
|
||||||
|
BoundingSphere::new(Vec3::ZERO, 1.),
|
||||||
|
Vec3::X * 1.5,
|
||||||
|
Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingSphere::new(Vec3::Y * 5., 1.),
|
||||||
|
3.677,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
// Hit the bounding sphere off-center, by casting a sphere that is off-center
|
||||||
|
BoundingSphereCast::new(
|
||||||
|
BoundingSphere::new(Vec3::X * -1.5, 1.),
|
||||||
|
Vec3::X * 3.,
|
||||||
|
Direction3d::Y,
|
||||||
|
90.,
|
||||||
|
),
|
||||||
|
BoundingSphere::new(Vec3::Y * 5., 1.),
|
||||||
|
3.677,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let case = format!(
|
||||||
|
"Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}",
|
||||||
|
test, volume, expected_distance
|
||||||
|
);
|
||||||
|
assert!(test.intersects(volume), "{}", case);
|
||||||
|
let actual_distance = test.sphere_collision_at(*volume).unwrap();
|
||||||
|
assert!(
|
||||||
|
(actual_distance - expected_distance).abs() < EPSILON,
|
||||||
|
"{}\n Actual distance: {}",
|
||||||
|
case,
|
||||||
|
actual_distance
|
||||||
|
);
|
||||||
|
|
||||||
|
let inverted_ray =
|
||||||
|
RayTest3d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
|
||||||
|
assert!(!inverted_ray.intersects(volume), "{}", case);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user