mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
Implement bounding volume types (#10946)
# Objective Implement bounding volume trait and the 4 types from https://github.com/bevyengine/bevy/issues/10570. I will add intersection tests in a future PR. ## Solution Implement mostly everything as written in the issue, except: - Intersection is no longer a method on the bounding volumes, but a separate trait. - I implemented a `visible_area` since it's the most common usecase to care about the surface that could collide with cast rays. - Maybe we want both? --- ## Changelog - Added bounding volume types to bevy_math --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
d4ffd4ff28
commit
c4e479a2d4
4 changed files with 775 additions and 0 deletions
353
crates/bevy_math/src/bounding/bounded2d.rs
Normal file
353
crates/bevy_math/src/bounding/bounded2d.rs
Normal file
|
@ -0,0 +1,353 @@
|
|||
use super::BoundingVolume;
|
||||
use crate::prelude::Vec2;
|
||||
|
||||
/// A trait with methods that return 2D bounded volumes for a shape
|
||||
pub trait Bounded2d {
|
||||
/// Get an axis-aligned bounding box for the shape with the given translation and rotation.
|
||||
/// The rotation is in radians, counterclockwise, with 0 meaning no rotation.
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: f32) -> Aabb2d;
|
||||
/// Get a bounding circle for the shape
|
||||
/// The rotation is in radians, counterclockwise, with 0 meaning no rotation.
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: f32) -> BoundingCircle;
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// The maximum, conventionally top-right, point of the box
|
||||
pub max: Vec2,
|
||||
}
|
||||
|
||||
impl BoundingVolume for Aabb2d {
|
||||
type Position = Vec2;
|
||||
type HalfSize = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn center(&self) -> Self::Position {
|
||||
(self.min + self.max) / 2.
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn half_size(&self) -> Self::HalfSize {
|
||||
(self.max - self.min) / 2.
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visible_area(&self) -> f32 {
|
||||
let b = self.max - self.min;
|
||||
b.x * b.y
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contains(&self, other: &Self) -> bool {
|
||||
other.min.x >= self.min.x
|
||||
&& other.min.y >= self.min.y
|
||||
&& other.max.x <= self.max.x
|
||||
&& other.max.y <= self.max.y
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn merge(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
min: self.min.min(other.min),
|
||||
max: self.max.max(other.max),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn grow(&self, amount: Self::HalfSize) -> Self {
|
||||
let b = Self {
|
||||
min: self.min - amount,
|
||||
max: self.max + amount,
|
||||
};
|
||||
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
|
||||
b
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn shrink(&self, amount: Self::HalfSize) -> Self {
|
||||
let b = Self {
|
||||
min: self.min + amount,
|
||||
max: self.max - amount,
|
||||
};
|
||||
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod aabb2d_tests {
|
||||
use super::Aabb2d;
|
||||
use crate::{bounding::BoundingVolume, Vec2};
|
||||
|
||||
#[test]
|
||||
fn center() {
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::new(-0.5, -1.),
|
||||
max: Vec2::new(1., 1.),
|
||||
};
|
||||
assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < std::f32::EPSILON);
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::new(5., -10.),
|
||||
max: Vec2::new(10., -5.),
|
||||
};
|
||||
assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < std::f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn half_size() {
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::new(-0.5, -1.),
|
||||
max: Vec2::new(1., 1.),
|
||||
};
|
||||
let half_size = aabb.half_size();
|
||||
assert!((half_size - Vec2::new(0.75, 1.)).length() < std::f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn area() {
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::new(-1., -1.),
|
||||
max: Vec2::new(1., 1.),
|
||||
};
|
||||
assert!((aabb.visible_area() - 4.).abs() < std::f32::EPSILON);
|
||||
let aabb = Aabb2d {
|
||||
min: Vec2::new(0., 0.),
|
||||
max: Vec2::new(1., 0.5),
|
||||
};
|
||||
assert!((aabb.visible_area() - 0.5).abs() < std::f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let a = Aabb2d {
|
||||
min: Vec2::new(-1., -1.),
|
||||
max: Vec2::new(1., 1.),
|
||||
};
|
||||
let b = Aabb2d {
|
||||
min: Vec2::new(-2., -1.),
|
||||
max: Vec2::new(1., 1.),
|
||||
};
|
||||
assert!(!a.contains(&b));
|
||||
let b = Aabb2d {
|
||||
min: Vec2::new(-0.25, -0.8),
|
||||
max: Vec2::new(1., 1.),
|
||||
};
|
||||
assert!(a.contains(&b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
let a = Aabb2d {
|
||||
min: Vec2::new(-1., -1.),
|
||||
max: Vec2::new(1., 0.5),
|
||||
};
|
||||
let b = Aabb2d {
|
||||
min: Vec2::new(-2., -0.5),
|
||||
max: Vec2::new(0.75, 1.),
|
||||
};
|
||||
let merged = a.merge(&b);
|
||||
assert!((merged.min - Vec2::new(-2., -1.)).length() < std::f32::EPSILON);
|
||||
assert!((merged.max - Vec2::new(1., 1.)).length() < std::f32::EPSILON);
|
||||
assert!(merged.contains(&a));
|
||||
assert!(merged.contains(&b));
|
||||
assert!(!a.contains(&merged));
|
||||
assert!(!b.contains(&merged));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow() {
|
||||
let a = Aabb2d {
|
||||
min: Vec2::new(-1., -1.),
|
||||
max: Vec2::new(1., 1.),
|
||||
};
|
||||
let padded = a.grow(Vec2::ONE);
|
||||
assert!((padded.min - Vec2::new(-2., -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 shrink() {
|
||||
let a = Aabb2d {
|
||||
min: Vec2::new(-2., -2.),
|
||||
max: Vec2::new(2., 2.),
|
||||
};
|
||||
let shrunk = a.shrink(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));
|
||||
}
|
||||
}
|
||||
|
||||
use crate::primitives::Circle;
|
||||
|
||||
/// A bounding circle
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoundingCircle {
|
||||
/// The center of the bounding circle
|
||||
pub center: Vec2,
|
||||
/// The circle
|
||||
pub circle: Circle,
|
||||
}
|
||||
|
||||
impl BoundingCircle {
|
||||
/// Construct a bounding circle from its center and radius
|
||||
#[inline(always)]
|
||||
pub fn new(center: Vec2, radius: f32) -> Self {
|
||||
debug_assert!(radius >= 0.);
|
||||
Self {
|
||||
center,
|
||||
circle: Circle { radius },
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the radius of the bounding circle
|
||||
#[inline(always)]
|
||||
pub fn radius(&self) -> f32 {
|
||||
self.circle.radius
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingVolume for BoundingCircle {
|
||||
type Position = Vec2;
|
||||
type HalfSize = f32;
|
||||
|
||||
#[inline(always)]
|
||||
fn center(&self) -> Self::Position {
|
||||
self.center
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn half_size(&self) -> Self::HalfSize {
|
||||
self.radius()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visible_area(&self) -> f32 {
|
||||
std::f32::consts::PI * self.radius() * self.radius()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contains(&self, other: &Self) -> bool {
|
||||
let diff = self.radius() - other.radius();
|
||||
self.center.distance_squared(other.center) <= diff.powi(2).copysign(diff)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn merge(&self, other: &Self) -> Self {
|
||||
let diff = other.center - self.center;
|
||||
let length = diff.length();
|
||||
if self.radius() >= length + other.radius() {
|
||||
return self.clone();
|
||||
}
|
||||
if other.radius() >= length + self.radius() {
|
||||
return other.clone();
|
||||
}
|
||||
let dir = diff / length;
|
||||
Self::new(
|
||||
(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
|
||||
(length + self.radius() + other.radius()) / 2.,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn grow(&self, amount: Self::HalfSize) -> Self {
|
||||
debug_assert!(amount >= 0.);
|
||||
Self::new(self.center, self.radius() + amount)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn shrink(&self, amount: Self::HalfSize) -> Self {
|
||||
debug_assert!(amount >= 0.);
|
||||
debug_assert!(self.radius() >= amount);
|
||||
Self::new(self.center, self.radius() - amount)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod bounding_circle_tests {
|
||||
use super::BoundingCircle;
|
||||
use crate::{bounding::BoundingVolume, Vec2};
|
||||
|
||||
#[test]
|
||||
fn area() {
|
||||
let circle = BoundingCircle::new(Vec2::ONE, 5.);
|
||||
// Since this number is messy we check it with a higher threshold
|
||||
assert!((circle.visible_area() - 78.5398).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let a = BoundingCircle::new(Vec2::ONE, 5.);
|
||||
let b = BoundingCircle::new(Vec2::new(5.5, 1.), 1.);
|
||||
assert!(!a.contains(&b));
|
||||
let b = BoundingCircle::new(Vec2::new(1., -3.5), 0.5);
|
||||
assert!(a.contains(&b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_identical() {
|
||||
let a = BoundingCircle::new(Vec2::ONE, 5.);
|
||||
assert!(a.contains(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
// 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.);
|
||||
let merged = a.merge(&b);
|
||||
assert!((merged.center - Vec2::new(1., 0.5)).length() < std::f32::EPSILON);
|
||||
assert!((merged.radius() - 5.5).abs() < std::f32::EPSILON);
|
||||
assert!(merged.contains(&a));
|
||||
assert!(merged.contains(&b));
|
||||
assert!(!a.contains(&merged));
|
||||
assert!(!b.contains(&merged));
|
||||
|
||||
// When one circle contains the other circle, we use the bigger circle
|
||||
let b = BoundingCircle::new(Vec2::ZERO, 3.);
|
||||
assert!(a.contains(&b));
|
||||
let merged = a.merge(&b);
|
||||
assert_eq!(merged.center, a.center);
|
||||
assert_eq!(merged.radius(), a.radius());
|
||||
|
||||
// When two circles are at the same point, we use the bigger radius
|
||||
let b = BoundingCircle::new(Vec2::ONE, 6.);
|
||||
let merged = a.merge(&b);
|
||||
assert_eq!(merged.center, a.center);
|
||||
assert_eq!(merged.radius(), b.radius());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_identical() {
|
||||
let a = BoundingCircle::new(Vec2::ONE, 5.);
|
||||
let merged = a.merge(&a);
|
||||
assert_eq!(merged.center, a.center);
|
||||
assert_eq!(merged.radius(), a.radius());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow() {
|
||||
let a = BoundingCircle::new(Vec2::ONE, 5.);
|
||||
let padded = a.grow(1.25);
|
||||
assert!((padded.radius() - 6.25).abs() < std::f32::EPSILON);
|
||||
assert!(padded.contains(&a));
|
||||
assert!(!a.contains(&padded));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink() {
|
||||
let a = BoundingCircle::new(Vec2::ONE, 5.);
|
||||
let shrunk = a.shrink(0.5);
|
||||
assert!((shrunk.radius() - 4.5).abs() < std::f32::EPSILON);
|
||||
assert!(a.contains(&shrunk));
|
||||
assert!(!shrunk.contains(&a));
|
||||
}
|
||||
}
|
360
crates/bevy_math/src/bounding/bounded3d.rs
Normal file
360
crates/bevy_math/src/bounding/bounded3d.rs
Normal file
|
@ -0,0 +1,360 @@
|
|||
use super::BoundingVolume;
|
||||
use crate::prelude::{Quat, Vec3};
|
||||
|
||||
/// A trait with methods that return 3D bounded volumes for a shape
|
||||
pub trait Bounded3d {
|
||||
/// Get an axis-aligned bounding box for the shape with the given translation and rotation
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d;
|
||||
/// Get a bounding sphere for the shape with the given translation and rotation
|
||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere;
|
||||
}
|
||||
|
||||
/// A 3D axis-aligned bounding box
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Aabb3d {
|
||||
/// The minimum point of the box
|
||||
pub min: Vec3,
|
||||
/// The maximum point of the box
|
||||
pub max: Vec3,
|
||||
}
|
||||
|
||||
impl BoundingVolume for Aabb3d {
|
||||
type Position = Vec3;
|
||||
type HalfSize = Vec3;
|
||||
|
||||
#[inline(always)]
|
||||
fn center(&self) -> Self::Position {
|
||||
(self.min + self.max) / 2.
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn half_size(&self) -> Self::HalfSize {
|
||||
(self.max - self.min) / 2.
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visible_area(&self) -> f32 {
|
||||
let b = self.max - self.min;
|
||||
b.x * (b.y + b.z) + b.y * b.z
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contains(&self, other: &Self) -> bool {
|
||||
other.min.x >= self.min.x
|
||||
&& other.min.y >= self.min.y
|
||||
&& other.min.z >= self.min.z
|
||||
&& other.max.x <= self.max.x
|
||||
&& other.max.y <= self.max.y
|
||||
&& other.max.z <= self.max.z
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn merge(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
min: self.min.min(other.min),
|
||||
max: self.max.max(other.max),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn grow(&self, amount: Self::HalfSize) -> Self {
|
||||
let b = Self {
|
||||
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
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn shrink(&self, amount: Self::HalfSize) -> Self {
|
||||
let b = Self {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod aabb3d_tests {
|
||||
use super::Aabb3d;
|
||||
use crate::{bounding::BoundingVolume, Vec3};
|
||||
|
||||
#[test]
|
||||
fn center() {
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::new(-0.5, -1., -0.5),
|
||||
max: Vec3::new(1., 1., 2.),
|
||||
};
|
||||
assert!((aabb.center() - Vec3::new(0.25, 0., 0.75)).length() < std::f32::EPSILON);
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::new(5., 5., -10.),
|
||||
max: Vec3::new(10., 10., -5.),
|
||||
};
|
||||
assert!((aabb.center() - Vec3::new(7.5, 7.5, -7.5)).length() < std::f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn half_size() {
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::new(-0.5, -1., -0.5),
|
||||
max: Vec3::new(1., 1., 2.),
|
||||
};
|
||||
assert!((aabb.half_size() - Vec3::new(0.75, 1., 1.25)).length() < std::f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn area() {
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::new(-1., -1., -1.),
|
||||
max: Vec3::new(1., 1., 1.),
|
||||
};
|
||||
assert!((aabb.visible_area() - 12.).abs() < std::f32::EPSILON);
|
||||
let aabb = Aabb3d {
|
||||
min: Vec3::new(0., 0., 0.),
|
||||
max: Vec3::new(1., 0.5, 0.25),
|
||||
};
|
||||
assert!((aabb.visible_area() - 0.875).abs() < std::f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let a = Aabb3d {
|
||||
min: Vec3::new(-1., -1., -1.),
|
||||
max: Vec3::new(1., 1., 1.),
|
||||
};
|
||||
let b = Aabb3d {
|
||||
min: Vec3::new(-2., -1., -1.),
|
||||
max: Vec3::new(1., 1., 1.),
|
||||
};
|
||||
assert!(!a.contains(&b));
|
||||
let b = Aabb3d {
|
||||
min: Vec3::new(-0.25, -0.8, -0.9),
|
||||
max: Vec3::new(1., 1., 0.9),
|
||||
};
|
||||
assert!(a.contains(&b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
let a = Aabb3d {
|
||||
min: Vec3::new(-1., -1., -1.),
|
||||
max: Vec3::new(1., 0.5, 1.),
|
||||
};
|
||||
let b = Aabb3d {
|
||||
min: Vec3::new(-2., -0.5, -0.),
|
||||
max: Vec3::new(0.75, 1., 2.),
|
||||
};
|
||||
let merged = a.merge(&b);
|
||||
assert!((merged.min - Vec3::new(-2., -1., -1.)).length() < std::f32::EPSILON);
|
||||
assert!((merged.max - Vec3::new(1., 1., 2.)).length() < std::f32::EPSILON);
|
||||
assert!(merged.contains(&a));
|
||||
assert!(merged.contains(&b));
|
||||
assert!(!a.contains(&merged));
|
||||
assert!(!b.contains(&merged));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow() {
|
||||
let a = Aabb3d {
|
||||
min: Vec3::new(-1., -1., -1.),
|
||||
max: Vec3::new(1., 1., 1.),
|
||||
};
|
||||
let padded = a.grow(Vec3::ONE);
|
||||
assert!((padded.min - Vec3::new(-2., -2., -2.)).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 shrink() {
|
||||
let a = Aabb3d {
|
||||
min: Vec3::new(-2., -2., -2.),
|
||||
max: Vec3::new(2., 2., 2.),
|
||||
};
|
||||
let shrunk = a.shrink(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));
|
||||
}
|
||||
}
|
||||
|
||||
use crate::primitives::Sphere;
|
||||
|
||||
/// A bounding sphere
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoundingSphere {
|
||||
/// The center of the bounding sphere
|
||||
pub center: Vec3,
|
||||
/// The sphere
|
||||
pub sphere: Sphere,
|
||||
}
|
||||
|
||||
impl BoundingSphere {
|
||||
/// Construct a bounding sphere from its center and radius.
|
||||
pub fn new(center: Vec3, radius: f32) -> Self {
|
||||
debug_assert!(radius >= 0.);
|
||||
Self {
|
||||
center,
|
||||
sphere: Sphere { radius },
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the radius of the bounding sphere
|
||||
#[inline(always)]
|
||||
pub fn radius(&self) -> f32 {
|
||||
self.sphere.radius
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingVolume for BoundingSphere {
|
||||
type Position = Vec3;
|
||||
type HalfSize = f32;
|
||||
|
||||
#[inline(always)]
|
||||
fn center(&self) -> Self::Position {
|
||||
self.center
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn half_size(&self) -> Self::HalfSize {
|
||||
self.radius()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visible_area(&self) -> f32 {
|
||||
2. * std::f32::consts::PI * self.radius() * self.radius()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contains(&self, other: &Self) -> bool {
|
||||
let diff = self.radius() - other.radius();
|
||||
self.center.distance_squared(other.center) <= diff.powi(2).copysign(diff)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn merge(&self, other: &Self) -> Self {
|
||||
let diff = other.center - self.center;
|
||||
let length = diff.length();
|
||||
if self.radius() >= length + other.radius() {
|
||||
return self.clone();
|
||||
}
|
||||
if other.radius() >= length + self.radius() {
|
||||
return other.clone();
|
||||
}
|
||||
let dir = diff / length;
|
||||
Self::new(
|
||||
(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
|
||||
(length + self.radius() + other.radius()) / 2.,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn grow(&self, amount: Self::HalfSize) -> Self {
|
||||
debug_assert!(amount >= 0.);
|
||||
Self {
|
||||
center: self.center,
|
||||
sphere: Sphere {
|
||||
radius: self.radius() + amount,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn shrink(&self, amount: Self::HalfSize) -> Self {
|
||||
debug_assert!(amount >= 0.);
|
||||
debug_assert!(self.radius() >= amount);
|
||||
Self {
|
||||
center: self.center,
|
||||
sphere: Sphere {
|
||||
radius: self.radius() - amount,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod bounding_sphere_tests {
|
||||
use super::BoundingSphere;
|
||||
use crate::{bounding::BoundingVolume, Vec3};
|
||||
|
||||
#[test]
|
||||
fn area() {
|
||||
let sphere = BoundingSphere::new(Vec3::ONE, 5.);
|
||||
// Since this number is messy we check it with a higher threshold
|
||||
assert!((sphere.visible_area() - 157.0796).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let a = BoundingSphere::new(Vec3::ONE, 5.);
|
||||
let b = BoundingSphere::new(Vec3::new(5.5, 1., 1.), 1.);
|
||||
assert!(!a.contains(&b));
|
||||
let b = BoundingSphere::new(Vec3::new(1., -3.5, 1.), 0.5);
|
||||
assert!(a.contains(&b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_identical() {
|
||||
let a = BoundingSphere::new(Vec3::ONE, 5.);
|
||||
assert!(a.contains(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
// 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.);
|
||||
let merged = a.merge(&b);
|
||||
assert!((merged.center - Vec3::new(1., 1., 0.5)).length() < std::f32::EPSILON);
|
||||
assert!((merged.radius() - 5.5).abs() < std::f32::EPSILON);
|
||||
assert!(merged.contains(&a));
|
||||
assert!(merged.contains(&b));
|
||||
assert!(!a.contains(&merged));
|
||||
assert!(!b.contains(&merged));
|
||||
|
||||
// When one circle contains the other circle, we use the bigger circle
|
||||
let b = BoundingSphere::new(Vec3::ZERO, 3.);
|
||||
assert!(a.contains(&b));
|
||||
let merged = a.merge(&b);
|
||||
assert_eq!(merged.center, a.center);
|
||||
assert_eq!(merged.radius(), a.radius());
|
||||
|
||||
// When two circles are at the same point, we use the bigger radius
|
||||
let b = BoundingSphere::new(Vec3::ONE, 6.);
|
||||
let merged = a.merge(&b);
|
||||
assert_eq!(merged.center, a.center);
|
||||
assert_eq!(merged.radius(), b.radius());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_identical() {
|
||||
let a = BoundingSphere::new(Vec3::ONE, 5.);
|
||||
let merged = a.merge(&a);
|
||||
assert_eq!(merged.center, a.center);
|
||||
assert_eq!(merged.radius(), a.radius());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow() {
|
||||
let a = BoundingSphere::new(Vec3::ONE, 5.);
|
||||
let padded = a.grow(1.25);
|
||||
assert!((padded.radius() - 6.25).abs() < std::f32::EPSILON);
|
||||
assert!(padded.contains(&a));
|
||||
assert!(!a.contains(&padded));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink() {
|
||||
let a = BoundingSphere::new(Vec3::ONE, 5.);
|
||||
let shrunk = a.shrink(0.5);
|
||||
assert!((shrunk.radius() - 4.5).abs() < std::f32::EPSILON);
|
||||
assert!(a.contains(&shrunk));
|
||||
assert!(!shrunk.contains(&a));
|
||||
}
|
||||
}
|
61
crates/bevy_math/src/bounding/mod.rs
Normal file
61
crates/bevy_math/src/bounding/mod.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
//! This module contains traits and implements for working with bounding shapes
|
||||
//!
|
||||
//! There are four traits used:
|
||||
//! - [`BoundingVolume`] is a generic abstraction for any bounding volume
|
||||
//! - [`IntersectsVolume`] abstracts intersection tests against a [`BoundingVolume`]
|
||||
//! - [`Bounded2d`]/[`Bounded3d`] are abstractions for shapes to generate [`BoundingVolume`]s
|
||||
|
||||
/// A trait that generalizes different bounding volumes.
|
||||
/// 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 size of the bounding volume. Usually a half size. For example an
|
||||
/// `f32` radius for a circle, or a `Vec3` with half sizes for x, y and z for a 3D axis-aligned
|
||||
/// bounding box
|
||||
type HalfSize;
|
||||
|
||||
/// Returns the center of the bounding volume.
|
||||
fn center(&self) -> Self::Position;
|
||||
|
||||
/// Returns the half size of the bounding volume.
|
||||
fn half_size(&self) -> Self::HalfSize;
|
||||
|
||||
/// 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 Heuristic.
|
||||
///
|
||||
/// 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.
|
||||
fn contains(&self, other: &Self) -> bool;
|
||||
|
||||
/// Computes the smallest bounding volume that contains both `self` and `other`.
|
||||
fn merge(&self, other: &Self) -> Self;
|
||||
|
||||
/// Increase the size of the bounding volume in each direction by the given amount
|
||||
fn grow(&self, amount: Self::HalfSize) -> Self;
|
||||
|
||||
/// Decrease the size of the bounding volume in each direction by the given amount
|
||||
fn shrink(&self, amount: Self::HalfSize) -> Self;
|
||||
}
|
||||
|
||||
/// A trait that generalizes intersection tests against a volume.
|
||||
/// Intersection tests can be used for a variety of tasks, for example:
|
||||
/// - 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;
|
||||
}
|
||||
|
||||
mod bounded2d;
|
||||
pub use bounded2d::*;
|
||||
mod bounded3d;
|
||||
pub use bounded3d::*;
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
mod affine3;
|
||||
mod aspect_ratio;
|
||||
pub mod bounding;
|
||||
pub mod cubic_splines;
|
||||
pub mod primitives;
|
||||
mod ray;
|
||||
|
|
Loading…
Add table
Reference in a new issue