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
	 NiseVoid
						NiseVoid