Implement bounding volume intersections (#11439)
# Objective #10946 added bounding volume types and an `IntersectsVolume` trait, but didn't actually implement intersections between bounding volumes. This PR implements AABB-AABB, circle-circle / sphere-sphere, and AABB-circle / AABB-sphere intersections. ## Solution Implement `IntersectsVolume` for bounding volume pairs. I also added `closest_point` methods to return the closest point on the surface / inside of bounding volumes. This is used for AABB-circle / AABB-sphere intersections. --------- Co-authored-by: IQuick 143 <IQuick143cz@gmail.com>
This commit is contained in:
parent
df063ab1ef
commit
6a3b059db9
@ -2,7 +2,7 @@ mod primitive_impls;
|
||||
|
||||
use glam::Mat2;
|
||||
|
||||
use super::BoundingVolume;
|
||||
use super::{BoundingVolume, IntersectsVolume};
|
||||
use crate::prelude::Vec2;
|
||||
|
||||
/// Computes the geometric center of the given set of points.
|
||||
@ -80,6 +80,16 @@ impl Aabb2d {
|
||||
let radius = self.min.distance(self.max) / 2.0;
|
||||
BoundingCircle::new(self.center(), radius)
|
||||
}
|
||||
|
||||
/// Finds the point on the AABB that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the AABB, the returned point will be on the perimeter of the AABB.
|
||||
/// Otherwise, it will be inside the AABB and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec2) -> Vec2 {
|
||||
// Clamp point coordinates to the AABB
|
||||
point.clamp(self.min, self.max)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingVolume for Aabb2d {
|
||||
@ -139,10 +149,32 @@ impl BoundingVolume for Aabb2d {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<Self> for Aabb2d {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, other: &Self) -> bool {
|
||||
let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
|
||||
let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
|
||||
x_overlaps && y_overlaps
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<BoundingCircle> for Aabb2d {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, circle: &BoundingCircle) -> bool {
|
||||
let closest_point = self.closest_point(circle.center);
|
||||
let distance_squared = circle.center.distance_squared(closest_point);
|
||||
let radius_squared = circle.radius().powi(2);
|
||||
distance_squared <= radius_squared
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod aabb2d_tests {
|
||||
use super::Aabb2d;
|
||||
use crate::{bounding::BoundingVolume, Vec2};
|
||||
use crate::{
|
||||
bounding::{BoundingCircle, BoundingVolume, IntersectsVolume},
|
||||
Vec2,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn center() {
|
||||
@ -244,6 +276,53 @@ mod aabb2d_tests {
|
||||
assert!(a.contains(&shrunk));
|
||||
assert!(!shrunk.contains(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closest_point() {
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::NEG_ONE,
|
||||
max: Vec2::ONE,
|
||||
};
|
||||
assert_eq!(aabb.closest_point(Vec2::X * 10.0), Vec2::X);
|
||||
assert_eq!(aabb.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
|
||||
assert_eq!(
|
||||
aabb.closest_point(Vec2::new(0.25, 0.1)),
|
||||
Vec2::new(0.25, 0.1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_aabb() {
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::NEG_ONE,
|
||||
max: Vec2::ONE,
|
||||
};
|
||||
assert!(aabb.intersects(&aabb));
|
||||
assert!(aabb.intersects(&Aabb2d {
|
||||
min: Vec2::new(0.5, 0.5),
|
||||
max: Vec2::new(2.0, 2.0),
|
||||
}));
|
||||
assert!(aabb.intersects(&Aabb2d {
|
||||
min: Vec2::new(-2.0, -2.0),
|
||||
max: Vec2::new(-0.5, -0.5),
|
||||
}));
|
||||
assert!(!aabb.intersects(&Aabb2d {
|
||||
min: Vec2::new(1.1, 0.0),
|
||||
max: Vec2::new(2.0, 0.5),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_bounding_circle() {
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::NEG_ONE,
|
||||
max: Vec2::ONE,
|
||||
};
|
||||
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
|
||||
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
|
||||
assert!(aabb.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.5, 1.0)));
|
||||
assert!(!aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.75, 1.0)));
|
||||
}
|
||||
}
|
||||
|
||||
use crate::primitives::Circle;
|
||||
@ -305,6 +384,15 @@ impl BoundingCircle {
|
||||
max: self.center + Vec2::splat(self.radius()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the point on the bounding circle that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
|
||||
/// Otherwise, it will be inside the circle and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec2) -> Vec2 {
|
||||
self.circle.closest_point(point - self.center) + self.center
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingVolume for BoundingCircle {
|
||||
@ -363,10 +451,29 @@ impl BoundingVolume for BoundingCircle {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<Self> for BoundingCircle {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, other: &Self) -> bool {
|
||||
let center_distance_squared = self.center.distance_squared(other.center);
|
||||
let radius_sum_squared = (self.radius() + other.radius()).powi(2);
|
||||
center_distance_squared <= radius_sum_squared
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<Aabb2d> for BoundingCircle {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, aabb: &Aabb2d) -> bool {
|
||||
aabb.intersects(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod bounding_circle_tests {
|
||||
use super::BoundingCircle;
|
||||
use crate::{bounding::BoundingVolume, Vec2};
|
||||
use crate::{
|
||||
bounding::{BoundingVolume, IntersectsVolume},
|
||||
Vec2,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn area() {
|
||||
@ -443,4 +550,27 @@ mod bounding_circle_tests {
|
||||
assert!(a.contains(&shrunk));
|
||||
assert!(!shrunk.contains(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closest_point() {
|
||||
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
|
||||
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
|
||||
assert_eq!(
|
||||
circle.closest_point(Vec2::NEG_ONE * 10.0),
|
||||
Vec2::NEG_ONE.normalize()
|
||||
);
|
||||
assert_eq!(
|
||||
circle.closest_point(Vec2::new(0.25, 0.1)),
|
||||
Vec2::new(0.25, 0.1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_bounding_circle() {
|
||||
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
|
||||
assert!(circle.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
|
||||
assert!(circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.25, 1.0)));
|
||||
assert!(circle.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.25, 1.0)));
|
||||
assert!(!circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
mod primitive_impls;
|
||||
|
||||
use super::BoundingVolume;
|
||||
use super::{BoundingVolume, IntersectsVolume};
|
||||
use crate::prelude::{Quat, Vec3};
|
||||
|
||||
/// Computes the geometric center of the given set of points.
|
||||
@ -74,6 +74,16 @@ impl Aabb3d {
|
||||
let radius = self.min.distance(self.max) / 2.0;
|
||||
BoundingSphere::new(self.center(), radius)
|
||||
}
|
||||
|
||||
/// Finds the point on the AABB that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the AABB, the returned point will be on the surface of the AABB.
|
||||
/// Otherwise, it will be inside the AABB and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec3) -> Vec3 {
|
||||
// Clamp point coordinates to the AABB
|
||||
point.clamp(self.min, self.max)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingVolume for Aabb3d {
|
||||
@ -135,10 +145,33 @@ impl BoundingVolume for Aabb3d {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<Self> for Aabb3d {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, other: &Self) -> bool {
|
||||
let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
|
||||
let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
|
||||
let z_overlaps = self.min.z <= other.max.z && self.max.z >= other.min.z;
|
||||
x_overlaps && y_overlaps && z_overlaps
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<BoundingSphere> for Aabb3d {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, sphere: &BoundingSphere) -> bool {
|
||||
let closest_point = self.closest_point(sphere.center);
|
||||
let distance_squared = sphere.center.distance_squared(closest_point);
|
||||
let radius_squared = sphere.radius().powi(2);
|
||||
distance_squared <= radius_squared
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod aabb3d_tests {
|
||||
use super::Aabb3d;
|
||||
use crate::{bounding::BoundingVolume, Vec3};
|
||||
use crate::{
|
||||
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
|
||||
Vec3,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn center() {
|
||||
@ -239,6 +272,53 @@ mod aabb3d_tests {
|
||||
assert!(a.contains(&shrunk));
|
||||
assert!(!shrunk.contains(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closest_point() {
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::NEG_ONE,
|
||||
max: Vec3::ONE,
|
||||
};
|
||||
assert_eq!(aabb.closest_point(Vec3::X * 10.0), Vec3::X);
|
||||
assert_eq!(aabb.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
|
||||
assert_eq!(
|
||||
aabb.closest_point(Vec3::new(0.25, 0.1, 0.3)),
|
||||
Vec3::new(0.25, 0.1, 0.3)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_aabb() {
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::NEG_ONE,
|
||||
max: Vec3::ONE,
|
||||
};
|
||||
assert!(aabb.intersects(&aabb));
|
||||
assert!(aabb.intersects(&Aabb3d {
|
||||
min: Vec3::splat(0.5),
|
||||
max: Vec3::splat(2.0),
|
||||
}));
|
||||
assert!(aabb.intersects(&Aabb3d {
|
||||
min: Vec3::splat(-2.0),
|
||||
max: Vec3::splat(-0.5),
|
||||
}));
|
||||
assert!(!aabb.intersects(&Aabb3d {
|
||||
min: Vec3::new(1.1, 0.0, 0.0),
|
||||
max: Vec3::new(2.0, 0.5, 0.25),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_bounding_sphere() {
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::NEG_ONE,
|
||||
max: Vec3::ONE,
|
||||
};
|
||||
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
|
||||
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));
|
||||
assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));
|
||||
assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));
|
||||
}
|
||||
}
|
||||
|
||||
use crate::primitives::Sphere;
|
||||
@ -296,6 +376,15 @@ impl BoundingSphere {
|
||||
max: self.center + Vec3::splat(self.radius()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the point on the bounding sphere that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the sphere, the returned point will be on the surface of the sphere.
|
||||
/// Otherwise, it will be inside the sphere and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec3) -> Vec3 {
|
||||
self.sphere.closest_point(point - self.center) + self.center
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingVolume for BoundingSphere {
|
||||
@ -364,10 +453,29 @@ impl BoundingVolume for BoundingSphere {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<Self> for BoundingSphere {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, other: &Self) -> bool {
|
||||
let center_distance_squared = self.center.distance_squared(other.center);
|
||||
let radius_sum_squared = (self.radius() + other.radius()).powi(2);
|
||||
center_distance_squared <= radius_sum_squared
|
||||
}
|
||||
}
|
||||
|
||||
impl IntersectsVolume<Aabb3d> for BoundingSphere {
|
||||
#[inline(always)]
|
||||
fn intersects(&self, aabb: &Aabb3d) -> bool {
|
||||
aabb.intersects(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod bounding_sphere_tests {
|
||||
use super::BoundingSphere;
|
||||
use crate::{bounding::BoundingVolume, Vec3};
|
||||
use crate::{
|
||||
bounding::{BoundingVolume, IntersectsVolume},
|
||||
Vec3,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn area() {
|
||||
@ -444,4 +552,27 @@ mod bounding_sphere_tests {
|
||||
assert!(a.contains(&shrunk));
|
||||
assert!(!shrunk.contains(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closest_point() {
|
||||
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
|
||||
assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
|
||||
assert_eq!(
|
||||
sphere.closest_point(Vec3::NEG_ONE * 10.0),
|
||||
Vec3::NEG_ONE.normalize()
|
||||
);
|
||||
assert_eq!(
|
||||
sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
|
||||
Vec3::new(0.25, 0.1, 0.3)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_bounding_sphere() {
|
||||
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
|
||||
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
|
||||
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));
|
||||
assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));
|
||||
assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,27 @@ pub struct Circle {
|
||||
}
|
||||
impl Primitive2d for Circle {}
|
||||
|
||||
impl Circle {
|
||||
/// Finds the point on the circle that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
|
||||
/// Otherwise, it will be inside the circle and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec2) -> Vec2 {
|
||||
let distance_squared = point.length_squared();
|
||||
|
||||
if distance_squared <= self.radius.powi(2) {
|
||||
// The point is inside the circle.
|
||||
point
|
||||
} else {
|
||||
// The point is outside the circle.
|
||||
// Find the closest point on the perimeter of the circle.
|
||||
let dir_to_point = point / distance_squared.sqrt();
|
||||
self.radius * dir_to_point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An ellipse primitive
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Ellipse {
|
||||
@ -358,6 +379,16 @@ impl Rectangle {
|
||||
half_size: size / 2.,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the point on the rectangle that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the rectangle, the returned point will be on the perimeter of the rectangle.
|
||||
/// Otherwise, it will be inside the rectangle and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec2) -> Vec2 {
|
||||
// Clamp point coordinates to the rectangle
|
||||
point.clamp(-self.half_size, self.half_size)
|
||||
}
|
||||
}
|
||||
|
||||
/// A polygon with N vertices.
|
||||
@ -549,4 +580,29 @@ mod tests {
|
||||
< 1e-7,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangle_closest_point() {
|
||||
let rectangle = Rectangle::new(2.0, 2.0);
|
||||
assert_eq!(rectangle.closest_point(Vec2::X * 10.0), Vec2::X);
|
||||
assert_eq!(rectangle.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
|
||||
assert_eq!(
|
||||
rectangle.closest_point(Vec2::new(0.25, 0.1)),
|
||||
Vec2::new(0.25, 0.1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn circle_closest_point() {
|
||||
let circle = Circle { radius: 1.0 };
|
||||
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
|
||||
assert_eq!(
|
||||
circle.closest_point(Vec2::NEG_ONE * 10.0),
|
||||
Vec2::NEG_ONE.normalize()
|
||||
);
|
||||
assert_eq!(
|
||||
circle.closest_point(Vec2::new(0.25, 0.1)),
|
||||
Vec2::new(0.25, 0.1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,27 @@ pub struct Sphere {
|
||||
}
|
||||
impl Primitive3d for Sphere {}
|
||||
|
||||
impl Sphere {
|
||||
/// Finds the point on the sphere that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the sphere, the returned point will be on the surface of the sphere.
|
||||
/// Otherwise, it will be inside the sphere and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec3) -> Vec3 {
|
||||
let distance_squared = point.length_squared();
|
||||
|
||||
if distance_squared <= self.radius.powi(2) {
|
||||
// The point is inside the sphere.
|
||||
point
|
||||
} else {
|
||||
// The point is outside the sphere.
|
||||
// Find the closest point on the surface of the sphere.
|
||||
let dir_to_point = point / distance_squared.sqrt();
|
||||
self.radius * dir_to_point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An unbounded plane in 3D space. It forms a separating surface through the origin,
|
||||
/// stretching infinitely far
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -245,6 +266,16 @@ impl Cuboid {
|
||||
half_size: size / 2.,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the point on the cuboid that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the cuboid, the returned point will be on the surface of the cuboid.
|
||||
/// Otherwise, it will be inside the cuboid and returned as is.
|
||||
#[inline(always)]
|
||||
pub fn closest_point(&self, point: Vec3) -> Vec3 {
|
||||
// Clamp point coordinates to the cuboid
|
||||
point.clamp(-self.half_size, self.half_size)
|
||||
}
|
||||
}
|
||||
|
||||
/// A cylinder primitive
|
||||
@ -433,4 +464,29 @@ mod test {
|
||||
Ok((Direction3d::X, 6.5))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cuboid_closest_point() {
|
||||
let cuboid = Cuboid::new(2.0, 2.0, 2.0);
|
||||
assert_eq!(cuboid.closest_point(Vec3::X * 10.0), Vec3::X);
|
||||
assert_eq!(cuboid.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
|
||||
assert_eq!(
|
||||
cuboid.closest_point(Vec3::new(0.25, 0.1, 0.3)),
|
||||
Vec3::new(0.25, 0.1, 0.3)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sphere_closest_point() {
|
||||
let sphere = Sphere { radius: 1.0 };
|
||||
assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
|
||||
assert_eq!(
|
||||
sphere.closest_point(Vec3::NEG_ONE * 10.0),
|
||||
Vec3::NEG_ONE.normalize()
|
||||
);
|
||||
assert_eq!(
|
||||
sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
|
||||
Vec3::new(0.25, 0.1, 0.3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user