Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bounding volume types #10946

Merged
merged 10 commits into from
Jan 10, 2024
35 changes: 18 additions & 17 deletions crates/bevy_math/src/bounding/bounded2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use super::BoundingVolume;
use crate::prelude::Vec2;

/// A trait with methods that return 2D bounded volumes for a shape
#[doc(alias = "BoundingRectangle")]
pub trait Bounded2d {
/// Get an axis-aligned bounding box for the shape with the given translation and rotation.
/// The rotation is in radians, clockwise, with 0 meaning no rotation.
NiseVoid marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -12,6 +11,8 @@ pub trait Bounded2d {
}

/// A 2D axis-aligned bounding box, or bounding rectangle
#[doc(alias = "BoundingRectangle")]
#[derive(Clone, Debug)]
pub struct Aabb2d {
/// The minimum, conventionally bottom-left, point of the box
pub min: Vec2,
Expand All @@ -21,7 +22,7 @@ pub struct Aabb2d {

impl BoundingVolume for Aabb2d {
type Position = Vec2;
type Padding = (Vec2, Vec2);
type Padding = Vec2;

#[inline(always)]
fn center(&self) -> Self::Position {
Expand Down Expand Up @@ -53,8 +54,8 @@ impl BoundingVolume for Aabb2d {
#[inline(always)]
fn padded(&self, amount: Self::Padding) -> Self {
let b = Self {
min: self.min - amount.0,
max: self.max + amount.1,
min: self.min - amount,
max: self.max + amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
Expand All @@ -63,8 +64,8 @@ impl BoundingVolume for Aabb2d {
#[inline(always)]
fn shrunk(&self, amount: Self::Padding) -> Self {
let b = Self {
min: self.min + amount.0,
max: self.max - amount.1,
min: self.min + amount,
max: self.max - amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
Expand Down Expand Up @@ -147,22 +148,22 @@ mod aabb2d_tests {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
};
let padded = a.padded((Vec2::ONE, Vec2::Y));
let padded = a.padded(Vec2::ONE);
assert!((padded.min - Vec2::new(-2., -2.)).length() < std::f32::EPSILON);
assert!((padded.max - Vec2::new(1., 2.)).length() < std::f32::EPSILON);
assert!((padded.max - Vec2::new(2., 2.)).length() < std::f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}

#[test]
fn shrunk() {
let a = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
min: Vec2::new(-2., -2.),
max: Vec2::new(2., 2.),
};
let shrunk = a.shrunk((Vec2::ONE, Vec2::Y));
assert!((shrunk.min - Vec2::new(-0., -0.)).length() < std::f32::EPSILON);
assert!((shrunk.max - Vec2::new(1., 0.)).length() < std::f32::EPSILON);
let shrunk = a.shrunk(Vec2::ONE);
assert!((shrunk.min - Vec2::new(-1., -1.)).length() < std::f32::EPSILON);
assert!((shrunk.max - Vec2::new(1., 1.)).length() < std::f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
Expand All @@ -171,7 +172,7 @@ mod aabb2d_tests {
use crate::primitives::Circle;

/// A bounding circle
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct BoundingCircle {
/// The center of the bounding circle
pub center: Vec2,
Expand Down Expand Up @@ -213,8 +214,8 @@ impl BoundingVolume for BoundingCircle {

#[inline(always)]
fn contains(&self, other: &Self) -> bool {
let furthest_point = (other.center - self.center).length() + other.radius();
furthest_point <= self.radius()
let diff = self.radius() - other.radius();
self.center.distance_squared(other.center) <= diff.powi(2).copysign(diff)
}

#[inline(always)]
Expand Down Expand Up @@ -277,7 +278,7 @@ mod bounding_circle_tests {

#[test]
fn merged() {
// When merging two circles that don't contain eachother, we find a center position that
// When merging two circles that don't contain each other, we find a center position that
// contains both
let a = BoundingCircle::new(Vec2::ONE, 5.);
let b = BoundingCircle::new(Vec2::new(1., -4.), 1.);
Expand Down
33 changes: 17 additions & 16 deletions crates/bevy_math/src/bounding/bounded3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub trait Bounded3d {
}

/// A 3D axis-aligned bounding box
#[derive(Clone, Debug)]
pub struct Aabb3d {
/// The minimum point of the box
min: Vec3,
Expand All @@ -19,7 +20,7 @@ pub struct Aabb3d {

impl BoundingVolume for Aabb3d {
type Position = Vec3;
type Padding = (Vec3, Vec3);
type Padding = Vec3;

#[inline(always)]
fn center(&self) -> Self::Position {
Expand Down Expand Up @@ -53,8 +54,8 @@ impl BoundingVolume for Aabb3d {
#[inline(always)]
fn padded(&self, amount: Self::Padding) -> Self {
let b = Self {
min: self.min - amount.0,
max: self.max + amount.1,
min: self.min - amount,
max: self.max + amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z);
b
Expand All @@ -63,8 +64,8 @@ impl BoundingVolume for Aabb3d {
#[inline(always)]
fn shrunk(&self, amount: Self::Padding) -> Self {
let b = Self {
min: self.min + amount.0,
max: self.max - amount.1,
min: self.min + amount,
max: self.max - amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z);
b
Expand Down Expand Up @@ -147,22 +148,22 @@ mod aabb3d_tests {
min: Vec3::new(-1., -1., -1.),
max: Vec3::new(1., 1., 1.),
};
let padded = a.padded((Vec3::ONE, Vec3::Y));
let padded = a.padded(Vec3::ONE);
assert!((padded.min - Vec3::new(-2., -2., -2.)).length() < std::f32::EPSILON);
assert!((padded.max - Vec3::new(1., 2., 1.)).length() < std::f32::EPSILON);
assert!((padded.max - Vec3::new(2., 2., 2.)).length() < std::f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}

#[test]
fn shrunk() {
let a = Aabb3d {
min: Vec3::new(-1., -1., -1.),
max: Vec3::new(1., 1., 1.),
min: Vec3::new(-2., -2., -2.),
max: Vec3::new(2., 2., 2.),
};
let shrunk = a.shrunk((Vec3::ONE, Vec3::Y));
assert!((shrunk.min - Vec3::new(-0., -0., -0.)).length() < std::f32::EPSILON);
assert!((shrunk.max - Vec3::new(1., 0., 1.)).length() < std::f32::EPSILON);
let shrunk = a.shrunk(Vec3::ONE);
assert!((shrunk.min - Vec3::new(-1., -1., -1.)).length() < std::f32::EPSILON);
assert!((shrunk.max - Vec3::new(1., 1., 1.)).length() < std::f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
Expand All @@ -171,7 +172,7 @@ mod aabb3d_tests {
use crate::primitives::Sphere;

/// A bounding sphere
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct BoundingSphere {
/// The center of the bounding sphere
center: Vec3,
Expand Down Expand Up @@ -212,8 +213,8 @@ impl BoundingVolume for BoundingSphere {

#[inline(always)]
fn contains(&self, other: &Self) -> bool {
let furthest_point = (other.center - self.center).length() + other.radius();
furthest_point <= self.radius()
let diff = self.radius() - other.radius();
self.center.distance_squared(other.center) <= diff.powi(2).copysign(diff)
}

#[inline(always)]
Expand Down Expand Up @@ -286,7 +287,7 @@ mod bounding_sphere_tests {

#[test]
fn merged() {
// When merging two circles that don't contain eachother, we find a center position that
// When merging two circles that don't contain each other, we find a center position that
// contains both
let a = BoundingSphere::new(Vec3::ONE, 5.);
let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.);
Expand Down
24 changes: 18 additions & 6 deletions crates/bevy_math/src/bounding/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@
//! - [`Bounded2d`]/[`Bounded3d`] are abstractions for shapes to generate [`BoundingVolume`]s

/// A trait that generalizes different bounding volumes.
/// It supports both 2D and 3D bounding shapes.
/// Bounding volumes are simplified shapes that are used to get simpler ways to check for
/// overlapping elements or finding intersections.
///
/// This trait supports both 2D and 3D bounding shapes.
pub trait BoundingVolume {
/// The position type used for the volume. This should be `Vec2` for 2D and `Vec3` for 3D.
type Position: Clone + Copy + PartialEq;
/// The type used for the `padded` and `shrunk` methods. For example, a `f32` radius for
/// The type used for the `padded` and `shrunk` methods. For example, an `f32` radius for
/// circles and spheres.
type Padding;
NiseVoid marked this conversation as resolved.
Show resolved Hide resolved

/// Returns the center of the bounding volume.
fn center(&self) -> Self::Position;

/// Computes the maximum surface area of the bounding volume that is visible from any angle.
/// Computes the visible surface area of the bounding volume.
/// This method can be useful to make decisions about merging bounding volumes,
/// using a Surface Area Heuristics.
NiseVoid marked this conversation as resolved.
Show resolved Hide resolved
///
/// For 2D shapes this would simply be the area of the shape.
/// For 3D shapes this would usually be half the area of the shape.
fn visible_area(&self) -> f32;

/// Checks if this bounding volume contains another one.
Expand All @@ -26,14 +34,18 @@ pub trait BoundingVolume {
/// Computes the smallest bounding volume that contains both `self` and `other`.
fn merged(&self, other: &Self) -> Self;

/// Increases the size of this bounding volume by the given amount.
/// Expand the bounding volume in each direction by the given amount
fn padded(&self, amount: Self::Padding) -> Self;

/// Decreases the size of this bounding volume by the given amount.
/// Shrink the bounding volume in each direction by the given amount
fn shrunk(&self, amount: Self::Padding) -> Self;
}

/// A trait that generalizes intersection tests against a volume
/// A trait that generalizes intersection tests against a volume.
/// Intersection tests can take many shapes, for example:
NiseVoid marked this conversation as resolved.
Show resolved Hide resolved
/// - Raycasting
/// - Testing for overlap
/// - Checking if an object is within the view frustum of a camera
pub trait IntersectsVolume<Volume: BoundingVolume> {
/// Check if a volume intersects with this intersection test
fn intersects(&self, volume: &Volume) -> bool;
Expand Down