Skip to content

Commit 6a3b059

Browse files
JondolfIQuick143
andauthored
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 <[email protected]>
1 parent df063ab commit 6a3b059

File tree

4 files changed

+379
-6
lines changed

4 files changed

+379
-6
lines changed

crates/bevy_math/src/bounding/bounded2d/mod.rs

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod primitive_impls;
22

33
use glam::Mat2;
44

5-
use super::BoundingVolume;
5+
use super::{BoundingVolume, IntersectsVolume};
66
use crate::prelude::Vec2;
77

88
/// Computes the geometric center of the given set of points.
@@ -80,6 +80,16 @@ impl Aabb2d {
8080
let radius = self.min.distance(self.max) / 2.0;
8181
BoundingCircle::new(self.center(), radius)
8282
}
83+
84+
/// Finds the point on the AABB that is closest to the given `point`.
85+
///
86+
/// If the point is outside the AABB, the returned point will be on the perimeter of the AABB.
87+
/// Otherwise, it will be inside the AABB and returned as is.
88+
#[inline(always)]
89+
pub fn closest_point(&self, point: Vec2) -> Vec2 {
90+
// Clamp point coordinates to the AABB
91+
point.clamp(self.min, self.max)
92+
}
8393
}
8494

8595
impl BoundingVolume for Aabb2d {
@@ -139,10 +149,32 @@ impl BoundingVolume for Aabb2d {
139149
}
140150
}
141151

152+
impl IntersectsVolume<Self> for Aabb2d {
153+
#[inline(always)]
154+
fn intersects(&self, other: &Self) -> bool {
155+
let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
156+
let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
157+
x_overlaps && y_overlaps
158+
}
159+
}
160+
161+
impl IntersectsVolume<BoundingCircle> for Aabb2d {
162+
#[inline(always)]
163+
fn intersects(&self, circle: &BoundingCircle) -> bool {
164+
let closest_point = self.closest_point(circle.center);
165+
let distance_squared = circle.center.distance_squared(closest_point);
166+
let radius_squared = circle.radius().powi(2);
167+
distance_squared <= radius_squared
168+
}
169+
}
170+
142171
#[cfg(test)]
143172
mod aabb2d_tests {
144173
use super::Aabb2d;
145-
use crate::{bounding::BoundingVolume, Vec2};
174+
use crate::{
175+
bounding::{BoundingCircle, BoundingVolume, IntersectsVolume},
176+
Vec2,
177+
};
146178

147179
#[test]
148180
fn center() {
@@ -244,6 +276,53 @@ mod aabb2d_tests {
244276
assert!(a.contains(&shrunk));
245277
assert!(!shrunk.contains(&a));
246278
}
279+
280+
#[test]
281+
fn closest_point() {
282+
let aabb = Aabb2d {
283+
min: Vec2::NEG_ONE,
284+
max: Vec2::ONE,
285+
};
286+
assert_eq!(aabb.closest_point(Vec2::X * 10.0), Vec2::X);
287+
assert_eq!(aabb.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
288+
assert_eq!(
289+
aabb.closest_point(Vec2::new(0.25, 0.1)),
290+
Vec2::new(0.25, 0.1)
291+
);
292+
}
293+
294+
#[test]
295+
fn intersect_aabb() {
296+
let aabb = Aabb2d {
297+
min: Vec2::NEG_ONE,
298+
max: Vec2::ONE,
299+
};
300+
assert!(aabb.intersects(&aabb));
301+
assert!(aabb.intersects(&Aabb2d {
302+
min: Vec2::new(0.5, 0.5),
303+
max: Vec2::new(2.0, 2.0),
304+
}));
305+
assert!(aabb.intersects(&Aabb2d {
306+
min: Vec2::new(-2.0, -2.0),
307+
max: Vec2::new(-0.5, -0.5),
308+
}));
309+
assert!(!aabb.intersects(&Aabb2d {
310+
min: Vec2::new(1.1, 0.0),
311+
max: Vec2::new(2.0, 0.5),
312+
}));
313+
}
314+
315+
#[test]
316+
fn intersect_bounding_circle() {
317+
let aabb = Aabb2d {
318+
min: Vec2::NEG_ONE,
319+
max: Vec2::ONE,
320+
};
321+
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
322+
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
323+
assert!(aabb.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.5, 1.0)));
324+
assert!(!aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.75, 1.0)));
325+
}
247326
}
248327

249328
use crate::primitives::Circle;
@@ -305,6 +384,15 @@ impl BoundingCircle {
305384
max: self.center + Vec2::splat(self.radius()),
306385
}
307386
}
387+
388+
/// Finds the point on the bounding circle that is closest to the given `point`.
389+
///
390+
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
391+
/// Otherwise, it will be inside the circle and returned as is.
392+
#[inline(always)]
393+
pub fn closest_point(&self, point: Vec2) -> Vec2 {
394+
self.circle.closest_point(point - self.center) + self.center
395+
}
308396
}
309397

310398
impl BoundingVolume for BoundingCircle {
@@ -363,10 +451,29 @@ impl BoundingVolume for BoundingCircle {
363451
}
364452
}
365453

454+
impl IntersectsVolume<Self> for BoundingCircle {
455+
#[inline(always)]
456+
fn intersects(&self, other: &Self) -> bool {
457+
let center_distance_squared = self.center.distance_squared(other.center);
458+
let radius_sum_squared = (self.radius() + other.radius()).powi(2);
459+
center_distance_squared <= radius_sum_squared
460+
}
461+
}
462+
463+
impl IntersectsVolume<Aabb2d> for BoundingCircle {
464+
#[inline(always)]
465+
fn intersects(&self, aabb: &Aabb2d) -> bool {
466+
aabb.intersects(self)
467+
}
468+
}
469+
366470
#[cfg(test)]
367471
mod bounding_circle_tests {
368472
use super::BoundingCircle;
369-
use crate::{bounding::BoundingVolume, Vec2};
473+
use crate::{
474+
bounding::{BoundingVolume, IntersectsVolume},
475+
Vec2,
476+
};
370477

371478
#[test]
372479
fn area() {
@@ -443,4 +550,27 @@ mod bounding_circle_tests {
443550
assert!(a.contains(&shrunk));
444551
assert!(!shrunk.contains(&a));
445552
}
553+
554+
#[test]
555+
fn closest_point() {
556+
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
557+
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
558+
assert_eq!(
559+
circle.closest_point(Vec2::NEG_ONE * 10.0),
560+
Vec2::NEG_ONE.normalize()
561+
);
562+
assert_eq!(
563+
circle.closest_point(Vec2::new(0.25, 0.1)),
564+
Vec2::new(0.25, 0.1)
565+
);
566+
}
567+
568+
#[test]
569+
fn intersect_bounding_circle() {
570+
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
571+
assert!(circle.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
572+
assert!(circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.25, 1.0)));
573+
assert!(circle.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.25, 1.0)));
574+
assert!(!circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
575+
}
446576
}

crates/bevy_math/src/bounding/bounded3d/mod.rs

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod primitive_impls;
22

3-
use super::BoundingVolume;
3+
use super::{BoundingVolume, IntersectsVolume};
44
use crate::prelude::{Quat, Vec3};
55

66
/// Computes the geometric center of the given set of points.
@@ -74,6 +74,16 @@ impl Aabb3d {
7474
let radius = self.min.distance(self.max) / 2.0;
7575
BoundingSphere::new(self.center(), radius)
7676
}
77+
78+
/// Finds the point on the AABB that is closest to the given `point`.
79+
///
80+
/// If the point is outside the AABB, the returned point will be on the surface of the AABB.
81+
/// Otherwise, it will be inside the AABB and returned as is.
82+
#[inline(always)]
83+
pub fn closest_point(&self, point: Vec3) -> Vec3 {
84+
// Clamp point coordinates to the AABB
85+
point.clamp(self.min, self.max)
86+
}
7787
}
7888

7989
impl BoundingVolume for Aabb3d {
@@ -135,10 +145,33 @@ impl BoundingVolume for Aabb3d {
135145
}
136146
}
137147

148+
impl IntersectsVolume<Self> for Aabb3d {
149+
#[inline(always)]
150+
fn intersects(&self, other: &Self) -> bool {
151+
let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
152+
let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
153+
let z_overlaps = self.min.z <= other.max.z && self.max.z >= other.min.z;
154+
x_overlaps && y_overlaps && z_overlaps
155+
}
156+
}
157+
158+
impl IntersectsVolume<BoundingSphere> for Aabb3d {
159+
#[inline(always)]
160+
fn intersects(&self, sphere: &BoundingSphere) -> bool {
161+
let closest_point = self.closest_point(sphere.center);
162+
let distance_squared = sphere.center.distance_squared(closest_point);
163+
let radius_squared = sphere.radius().powi(2);
164+
distance_squared <= radius_squared
165+
}
166+
}
167+
138168
#[cfg(test)]
139169
mod aabb3d_tests {
140170
use super::Aabb3d;
141-
use crate::{bounding::BoundingVolume, Vec3};
171+
use crate::{
172+
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
173+
Vec3,
174+
};
142175

143176
#[test]
144177
fn center() {
@@ -239,6 +272,53 @@ mod aabb3d_tests {
239272
assert!(a.contains(&shrunk));
240273
assert!(!shrunk.contains(&a));
241274
}
275+
276+
#[test]
277+
fn closest_point() {
278+
let aabb = Aabb3d {
279+
min: Vec3::NEG_ONE,
280+
max: Vec3::ONE,
281+
};
282+
assert_eq!(aabb.closest_point(Vec3::X * 10.0), Vec3::X);
283+
assert_eq!(aabb.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
284+
assert_eq!(
285+
aabb.closest_point(Vec3::new(0.25, 0.1, 0.3)),
286+
Vec3::new(0.25, 0.1, 0.3)
287+
);
288+
}
289+
290+
#[test]
291+
fn intersect_aabb() {
292+
let aabb = Aabb3d {
293+
min: Vec3::NEG_ONE,
294+
max: Vec3::ONE,
295+
};
296+
assert!(aabb.intersects(&aabb));
297+
assert!(aabb.intersects(&Aabb3d {
298+
min: Vec3::splat(0.5),
299+
max: Vec3::splat(2.0),
300+
}));
301+
assert!(aabb.intersects(&Aabb3d {
302+
min: Vec3::splat(-2.0),
303+
max: Vec3::splat(-0.5),
304+
}));
305+
assert!(!aabb.intersects(&Aabb3d {
306+
min: Vec3::new(1.1, 0.0, 0.0),
307+
max: Vec3::new(2.0, 0.5, 0.25),
308+
}));
309+
}
310+
311+
#[test]
312+
fn intersect_bounding_sphere() {
313+
let aabb = Aabb3d {
314+
min: Vec3::NEG_ONE,
315+
max: Vec3::ONE,
316+
};
317+
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
318+
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));
319+
assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));
320+
assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));
321+
}
242322
}
243323

244324
use crate::primitives::Sphere;
@@ -296,6 +376,15 @@ impl BoundingSphere {
296376
max: self.center + Vec3::splat(self.radius()),
297377
}
298378
}
379+
380+
/// Finds the point on the bounding sphere that is closest to the given `point`.
381+
///
382+
/// If the point is outside the sphere, the returned point will be on the surface of the sphere.
383+
/// Otherwise, it will be inside the sphere and returned as is.
384+
#[inline(always)]
385+
pub fn closest_point(&self, point: Vec3) -> Vec3 {
386+
self.sphere.closest_point(point - self.center) + self.center
387+
}
299388
}
300389

301390
impl BoundingVolume for BoundingSphere {
@@ -364,10 +453,29 @@ impl BoundingVolume for BoundingSphere {
364453
}
365454
}
366455

456+
impl IntersectsVolume<Self> for BoundingSphere {
457+
#[inline(always)]
458+
fn intersects(&self, other: &Self) -> bool {
459+
let center_distance_squared = self.center.distance_squared(other.center);
460+
let radius_sum_squared = (self.radius() + other.radius()).powi(2);
461+
center_distance_squared <= radius_sum_squared
462+
}
463+
}
464+
465+
impl IntersectsVolume<Aabb3d> for BoundingSphere {
466+
#[inline(always)]
467+
fn intersects(&self, aabb: &Aabb3d) -> bool {
468+
aabb.intersects(self)
469+
}
470+
}
471+
367472
#[cfg(test)]
368473
mod bounding_sphere_tests {
369474
use super::BoundingSphere;
370-
use crate::{bounding::BoundingVolume, Vec3};
475+
use crate::{
476+
bounding::{BoundingVolume, IntersectsVolume},
477+
Vec3,
478+
};
371479

372480
#[test]
373481
fn area() {
@@ -444,4 +552,27 @@ mod bounding_sphere_tests {
444552
assert!(a.contains(&shrunk));
445553
assert!(!shrunk.contains(&a));
446554
}
555+
556+
#[test]
557+
fn closest_point() {
558+
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
559+
assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
560+
assert_eq!(
561+
sphere.closest_point(Vec3::NEG_ONE * 10.0),
562+
Vec3::NEG_ONE.normalize()
563+
);
564+
assert_eq!(
565+
sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
566+
Vec3::new(0.25, 0.1, 0.3)
567+
);
568+
}
569+
570+
#[test]
571+
fn intersect_bounding_sphere() {
572+
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
573+
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
574+
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));
575+
assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));
576+
assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));
577+
}
447578
}

0 commit comments

Comments
 (0)