From 0a986bf1621d785e4c28f581cc3836a0196aabe8 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Tue, 12 Dec 2023 10:30:53 +0100 Subject: [PATCH 1/9] Implement bounding volume types --- crates/bevy_math/src/bounding/bounded2d.rs | 284 ++++++++++++++++++++ crates/bevy_math/src/bounding/bounded3d.rs | 286 +++++++++++++++++++++ crates/bevy_math/src/bounding/mod.rs | 39 +++ crates/bevy_math/src/lib.rs | 1 + 4 files changed, 610 insertions(+) create mode 100644 crates/bevy_math/src/bounding/bounded2d.rs create mode 100644 crates/bevy_math/src/bounding/bounded3d.rs create mode 100644 crates/bevy_math/src/bounding/mod.rs diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs new file mode 100644 index 0000000000000..a1283c11a170c --- /dev/null +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -0,0 +1,284 @@ +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 + fn aabb_2d(&self, translation: Vec2, rotation: f32) -> Aabb2d; + /// Get a bounding circle for the shape + fn bounding_circle(&self, translation: Vec2) -> BoundingCircle; +} + +/// A 2D axis-aligned bounding box, or bounding rectangle +pub struct Aabb2d { + /// The minimum point of the box + pub min: Vec2, + /// The maximum point of the box + pub max: Vec2, +} + +impl BoundingVolume for Aabb2d { + type Position = Vec2; + type Padding = (Vec2, Vec2); + + #[inline(always)] + fn center(&self) -> Self::Position { + (self.min + self.max) / 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 merged(&self, other: &Self) -> Self { + Self { + min: self.min.min(other.min), + max: self.max.max(other.max), + } + } + + #[inline(always)] + fn padded(&self, amount: Self::Padding) -> Self { + let b = Self { + min: self.min - amount.0, + max: self.max + amount.1, + }; + debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y); + b + } + + #[inline(always)] + fn shrunk(&self, amount: Self::Padding) -> Self { + let b = Self { + min: self.min + amount.0, + max: self.max - amount.1, + }; + debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y); + b + } +} + +#[test] +fn test_aabb2d_center() { + let aabb = Aabb2d { + min: Vec2::new(-0.5, -1.), + max: Vec2::new(1., 1.), + }; + assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < 0.0001); + let aabb = Aabb2d { + min: Vec2::new(5., -10.), + max: Vec2::new(10., -5.), + }; + assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < 0.0001); +} + +#[test] +fn test_aabb2d_area() { + let aabb = Aabb2d { + min: Vec2::new(-1., -1.), + max: Vec2::new(1., 1.), + }; + assert!((aabb.visible_area() - 4.).abs() < 0.0001); + let aabb = Aabb2d { + min: Vec2::new(0., 0.), + max: Vec2::new(1., 0.5), + }; + assert!((aabb.visible_area() - 0.5).abs() < 0.0001); +} + +#[test] +fn test_aabb2d_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 test_aabb2d_merged() { + 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.merged(&b); + assert!((merged.min - Vec2::new(-2., -1.)).length() < 0.0001); + assert!((merged.max - Vec2::new(1., 1.)).length() < 0.0001); +} + +#[test] +fn test_aabb2d_padded() { + let a = Aabb2d { + min: Vec2::new(-1., -1.), + max: Vec2::new(1., 1.), + }; + let padded = a.padded((Vec2::ONE, Vec2::Y)); + assert!((padded.min - Vec2::new(-2., -2.)).length() < 0.0001); + assert!((padded.max - Vec2::new(1., 2.)).length() < 0.0001); +} + +#[test] +fn test_aabb2d_shrunk() { + let a = Aabb2d { + min: Vec2::new(-1., -1.), + max: Vec2::new(1., 1.), + }; + let shrunk = a.shrunk((Vec2::ONE, Vec2::Y)); + assert!((shrunk.min - Vec2::new(-0., -0.)).length() < 0.0001); + assert!((shrunk.max - Vec2::new(1., 0.)).length() < 0.0001); +} + +/// A bounding circle +pub struct BoundingCircle { + /// The center of the bounding circle + pub center: Vec2, + /// The radius of the bounding circle + pub radius: f32, +} + +impl BoundingVolume for BoundingCircle { + type Position = Vec2; + type Padding = f32; + + #[inline(always)] + fn center(&self) -> Self::Position { + self.center + } + + #[inline(always)] + fn visible_area(&self) -> f32 { + std::f32::consts::PI * self.radius * self.radius + } + + #[inline(always)] + fn contains(&self, other: &Self) -> bool { + if other.center == self.center { + other.radius <= self.radius + } else { + let furthest_point = (other.center - self.center).length() + other.radius; + furthest_point <= self.radius + } + } + + #[inline(always)] + fn merged(&self, other: &Self) -> Self { + if other.center == self.center { + Self { + center: self.center, + radius: self.radius.max(other.radius), + } + } else { + let diff = other.center - self.center; + let length = diff.length(); + let dir = diff / length; + + Self { + center: self.center + dir * ((length + other.radius - self.radius) / 2.), + radius: (length + self.radius + other.radius) / 2., + } + } + } + + #[inline(always)] + fn padded(&self, amount: Self::Padding) -> Self { + Self { + center: self.center, + radius: self.radius + amount, + } + } + + #[inline(always)] + fn shrunk(&self, amount: Self::Padding) -> Self { + debug_assert!(self.radius >= amount); + Self { + center: self.center, + radius: self.radius - amount, + } + } +} + +#[test] +fn test_bounding_circle_area() { + let circle = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + assert!((circle.visible_area() - 78.5398).abs() < 0.001); +} + +#[test] +fn test_bounding_circle_contains() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let b = BoundingCircle { + center: Vec2::new(5.5, 1.), + radius: 1., + }; + assert!(!a.contains(&b)); + let b = BoundingCircle { + center: Vec2::new(1., -3.5), + radius: 0.5, + }; + assert!(a.contains(&b)); +} + +#[test] +fn test_bounding_circle_merged() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let b = BoundingCircle { + center: Vec2::new(1., -4.), + radius: 1., + }; + let merged = a.merged(&b); + assert!((merged.center - Vec2::new(1., 0.5)).length() < 0.0001); + assert!((merged.radius - 5.5).abs() < 0.0001); +} + +#[test] +fn test_bounding_circle_padded() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let padded = a.padded(1.25); + assert!((padded.radius - 6.25).abs() < 0.0001); +} + +#[test] +fn test_bounding_circle_shrunk() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let shrunk = a.shrunk(0.5); + assert!((shrunk.radius - 4.5).abs() < 0.0001); +} diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs new file mode 100644 index 0000000000000..35b2ea6e19cf2 --- /dev/null +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -0,0 +1,286 @@ +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 + fn bounding_sphere(&self, translation: Vec3) -> BoundingSphere; +} + +/// A 3D axis-aligned bounding box +pub struct Aabb3d { + /// The minimum point of the box + min: Vec3, + /// The maximum point of the box + max: Vec3, +} + +impl BoundingVolume for Aabb3d { + type Position = Vec3; + type Padding = (Vec3, Vec3); + + #[inline(always)] + fn center(&self) -> Self::Position { + (self.min + self.max) / 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 merged(&self, other: &Self) -> Self { + Self { + min: self.min.min(other.min), + max: self.max.max(other.max), + } + } + + #[inline(always)] + fn padded(&self, amount: Self::Padding) -> Self { + let b = Self { + min: self.min - amount.0, + max: self.max + amount.1, + }; + debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z); + b + } + + #[inline(always)] + fn shrunk(&self, amount: Self::Padding) -> Self { + let b = Self { + min: self.min + amount.0, + max: self.max - amount.1, + }; + debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z); + b + } +} + +#[test] +fn test_aabb3d_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() < 0.0001); + 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() < 0.0001); +} + +#[test] +fn test_aabb3d_area() { + let aabb = Aabb3d { + min: Vec3::new(-1., -1., -1.), + max: Vec3::new(1., 1., 1.), + }; + assert!((aabb.visible_area() - 12.).abs() < 0.0001); + let aabb = Aabb3d { + min: Vec3::new(0., 0., 0.), + max: Vec3::new(1., 0.5, 0.25), + }; + assert!((aabb.visible_area() - 0.875).abs() < 0.0001); +} + +#[test] +fn test_aabb3d_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 test_aabb3d_merged() { + 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.merged(&b); + assert!((merged.min - Vec3::new(-2., -1., -1.)).length() < 0.0001); + assert!((merged.max - Vec3::new(1., 1., 2.)).length() < 0.0001); +} + +#[test] +fn test_aabb3d_padded() { + let a = Aabb3d { + min: Vec3::new(-1., -1., -1.), + max: Vec3::new(1., 1., 1.), + }; + let padded = a.padded((Vec3::ONE, Vec3::Y)); + assert!((padded.min - Vec3::new(-2., -2., -2.)).length() < 0.0001); + assert!((padded.max - Vec3::new(1., 2., 1.)).length() < 0.0001); +} + +#[test] +fn test_aabb3d_shrunk() { + let a = Aabb3d { + min: Vec3::new(-1., -1., -1.), + max: Vec3::new(1., 1., 1.), + }; + let shrunk = a.shrunk((Vec3::ONE, Vec3::Y)); + assert!((shrunk.min - Vec3::new(-0., -0., -0.)).length() < 0.0001); + assert!((shrunk.max - Vec3::new(1., 0., 1.)).length() < 0.0001); +} + +/// A bounding sphere +pub struct BoundingSphere { + /// The center of the bounding sphere + center: Vec3, + /// The radius of the bounding sphere + radius: f32, +} + +impl BoundingVolume for BoundingSphere { + type Position = Vec3; + type Padding = f32; + + #[inline(always)] + fn center(&self) -> Self::Position { + self.center + } + + #[inline(always)] + fn visible_area(&self) -> f32 { + 2. * std::f32::consts::PI * self.radius * self.radius + } + + #[inline(always)] + fn contains(&self, other: &Self) -> bool { + if other.center == self.center { + other.radius <= self.radius + } else { + let furthest_point = (other.center - self.center).length() + other.radius; + furthest_point <= self.radius + } + } + + #[inline(always)] + fn merged(&self, other: &Self) -> Self { + if other.center == self.center { + Self { + center: self.center, + radius: self.radius.max(other.radius), + } + } else { + let diff = other.center - self.center; + let length = diff.length(); + let dir = diff / length; + + Self { + center: self.center + dir * ((length + other.radius - self.radius) / 2.), + radius: (length + self.radius + other.radius) / 2., + } + } + } + + #[inline(always)] + fn padded(&self, amount: Self::Padding) -> Self { + Self { + center: self.center, + radius: self.radius + amount, + } + } + + #[inline(always)] + fn shrunk(&self, amount: Self::Padding) -> Self { + debug_assert!(self.radius >= amount); + Self { + center: self.center, + radius: self.radius - amount, + } + } +} + +#[test] +fn test_bounding_sphere_area() { + let sphere = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + assert!((sphere.visible_area() - 157.0796).abs() < 0.001); +} + +#[test] +fn test_bounding_sphere_contains() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let b = BoundingSphere { + center: Vec3::new(5.5, 1., 1.), + radius: 1., + }; + assert!(!a.contains(&b)); + let b = BoundingSphere { + center: Vec3::new(1., -3.5, 1.), + radius: 0.5, + }; + assert!(a.contains(&b)); +} + +#[test] +fn test_bounding_sphere_merged() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let b = BoundingSphere { + center: Vec3::new(1., 1., -4.), + radius: 1., + }; + let merged = a.merged(&b); + assert!((merged.center - Vec3::new(1., 1., 0.5)).length() < 0.0001); + assert!((merged.radius - 5.5).abs() < 0.0001); +} + +#[test] +fn test_bounding_sphere_padded() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let padded = a.padded(1.25); + assert!((padded.radius - 6.25).abs() < 0.0001); +} + +#[test] +fn test_bounding_sphere_shrunk() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let shrunk = a.shrunk(0.5); + assert!((shrunk.radius - 4.5).abs() < 0.0001); +} diff --git a/crates/bevy_math/src/bounding/mod.rs b/crates/bevy_math/src/bounding/mod.rs new file mode 100644 index 0000000000000..f8a886c9bb144 --- /dev/null +++ b/crates/bevy_math/src/bounding/mod.rs @@ -0,0 +1,39 @@ +//! This module contains traits and implements for working with bounding shapes + +/// A trait that generalizes different bounding volumes. +/// It 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. + type Padding; + + /// 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. + 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 merged(&self, other: &Self) -> Self; + + /// Increases the size of this bounding volume by the given amount. + fn padded(&self, amount: Self::Padding) -> Self; + + /// Decreases the size of this bounding volume by the given amount. + fn shrunk(&self, amount: Self::Padding) -> Self; +} + +/// A trait that generalizes intersection tests against a volume +pub trait IntersectsVolume { + /// 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::*; diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 4ad9dcc3731ba..d1748016f33ba 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -7,6 +7,7 @@ #![warn(missing_docs)] mod affine3; +pub mod bounding; pub mod cubic_splines; pub mod primitives; mod ray; From d4f018e0299b82b1e2b521dbead48f97e6f12b14 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Thu, 14 Dec 2023 06:44:55 +0100 Subject: [PATCH 2/9] Move tests into submodules --- crates/bevy_math/src/bounding/bounded2d.rs | 277 +++++++++++---------- crates/bevy_math/src/bounding/bounded3d.rs | 277 +++++++++++---------- 2 files changed, 290 insertions(+), 264 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index a1283c11a170c..f41b2ddf27521 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -69,87 +69,93 @@ impl BoundingVolume for Aabb2d { } } -#[test] -fn test_aabb2d_center() { - let aabb = Aabb2d { - min: Vec2::new(-0.5, -1.), - max: Vec2::new(1., 1.), - }; - assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < 0.0001); - let aabb = Aabb2d { - min: Vec2::new(5., -10.), - max: Vec2::new(10., -5.), - }; - assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < 0.0001); -} +#[cfg(test)] +mod aabb2d_tests { + use super::Aabb2d; + use crate::{bounding::BoundingVolume, Vec2}; -#[test] -fn test_aabb2d_area() { - let aabb = Aabb2d { - min: Vec2::new(-1., -1.), - max: Vec2::new(1., 1.), - }; - assert!((aabb.visible_area() - 4.).abs() < 0.0001); - let aabb = Aabb2d { - min: Vec2::new(0., 0.), - max: Vec2::new(1., 0.5), - }; - assert!((aabb.visible_area() - 0.5).abs() < 0.0001); -} + #[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 test_aabb2d_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 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 test_aabb2d_merged() { - 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.merged(&b); - assert!((merged.min - Vec2::new(-2., -1.)).length() < 0.0001); - assert!((merged.max - Vec2::new(1., 1.)).length() < 0.0001); -} + #[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 test_aabb2d_padded() { - let a = Aabb2d { - min: Vec2::new(-1., -1.), - max: Vec2::new(1., 1.), - }; - let padded = a.padded((Vec2::ONE, Vec2::Y)); - assert!((padded.min - Vec2::new(-2., -2.)).length() < 0.0001); - assert!((padded.max - Vec2::new(1., 2.)).length() < 0.0001); -} + #[test] + fn merged() { + 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.merged(&b); + assert!((merged.min - Vec2::new(-2., -1.)).length() < std::f32::EPSILON); + assert!((merged.max - Vec2::new(1., 1.)).length() < std::f32::EPSILON); + } + + #[test] + fn padded() { + let a = Aabb2d { + min: Vec2::new(-1., -1.), + max: Vec2::new(1., 1.), + }; + let padded = a.padded((Vec2::ONE, Vec2::Y)); + assert!((padded.min - Vec2::new(-2., -2.)).length() < std::f32::EPSILON); + assert!((padded.max - Vec2::new(1., 2.)).length() < std::f32::EPSILON); + } -#[test] -fn test_aabb2d_shrunk() { - let a = Aabb2d { - min: Vec2::new(-1., -1.), - max: Vec2::new(1., 1.), - }; - let shrunk = a.shrunk((Vec2::ONE, Vec2::Y)); - assert!((shrunk.min - Vec2::new(-0., -0.)).length() < 0.0001); - assert!((shrunk.max - Vec2::new(1., 0.)).length() < 0.0001); + #[test] + fn shrunk() { + let a = Aabb2d { + min: Vec2::new(-1., -1.), + max: Vec2::new(1., 1.), + }; + 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); + } } /// A bounding circle @@ -221,64 +227,71 @@ impl BoundingVolume for BoundingCircle { } } -#[test] -fn test_bounding_circle_area() { - let circle = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; - assert!((circle.visible_area() - 78.5398).abs() < 0.001); -} +#[cfg(test)] +mod bounding_circle_tests { + use super::BoundingCircle; + use crate::{bounding::BoundingVolume, Vec2}; -#[test] -fn test_bounding_circle_contains() { - let a = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; - let b = BoundingCircle { - center: Vec2::new(5.5, 1.), - radius: 1., - }; - assert!(!a.contains(&b)); - let b = BoundingCircle { - center: Vec2::new(1., -3.5), - radius: 0.5, - }; - assert!(a.contains(&b)); -} + #[test] + fn area() { + let circle = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + // Since this number is messy we check it with a higher threshold + assert!((circle.visible_area() - 78.5398).abs() < 0.001); + } -#[test] -fn test_bounding_circle_merged() { - let a = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; - let b = BoundingCircle { - center: Vec2::new(1., -4.), - radius: 1., - }; - let merged = a.merged(&b); - assert!((merged.center - Vec2::new(1., 0.5)).length() < 0.0001); - assert!((merged.radius - 5.5).abs() < 0.0001); -} + #[test] + fn contains() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let b = BoundingCircle { + center: Vec2::new(5.5, 1.), + radius: 1., + }; + assert!(!a.contains(&b)); + let b = BoundingCircle { + center: Vec2::new(1., -3.5), + radius: 0.5, + }; + assert!(a.contains(&b)); + } -#[test] -fn test_bounding_circle_padded() { - let a = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; - let padded = a.padded(1.25); - assert!((padded.radius - 6.25).abs() < 0.0001); -} + #[test] + fn merged() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let b = BoundingCircle { + center: Vec2::new(1., -4.), + radius: 1., + }; + let merged = a.merged(&b); + assert!((merged.center - Vec2::new(1., 0.5)).length() < std::f32::EPSILON); + assert!((merged.radius - 5.5).abs() < std::f32::EPSILON); + } -#[test] -fn test_bounding_circle_shrunk() { - let a = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; - let shrunk = a.shrunk(0.5); - assert!((shrunk.radius - 4.5).abs() < 0.0001); + #[test] + fn padded() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let padded = a.padded(1.25); + assert!((padded.radius - 6.25).abs() < std::f32::EPSILON); + } + + #[test] + fn shrunk() { + let a = BoundingCircle { + center: Vec2::ONE, + radius: 5., + }; + let shrunk = a.shrunk(0.5); + assert!((shrunk.radius - 4.5).abs() < std::f32::EPSILON); + } } diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs index 35b2ea6e19cf2..dc3b733a1ea64 100644 --- a/crates/bevy_math/src/bounding/bounded3d.rs +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -71,87 +71,93 @@ impl BoundingVolume for Aabb3d { } } -#[test] -fn test_aabb3d_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() < 0.0001); - 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() < 0.0001); -} +#[cfg(test)] +mod aabb3d_tests { + use super::Aabb3d; + use crate::{bounding::BoundingVolume, Vec3}; -#[test] -fn test_aabb3d_area() { - let aabb = Aabb3d { - min: Vec3::new(-1., -1., -1.), - max: Vec3::new(1., 1., 1.), - }; - assert!((aabb.visible_area() - 12.).abs() < 0.0001); - let aabb = Aabb3d { - min: Vec3::new(0., 0., 0.), - max: Vec3::new(1., 0.5, 0.25), - }; - assert!((aabb.visible_area() - 0.875).abs() < 0.0001); -} + #[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 test_aabb3d_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 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 test_aabb3d_merged() { - 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.merged(&b); - assert!((merged.min - Vec3::new(-2., -1., -1.)).length() < 0.0001); - assert!((merged.max - Vec3::new(1., 1., 2.)).length() < 0.0001); -} + #[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 test_aabb3d_padded() { - let a = Aabb3d { - min: Vec3::new(-1., -1., -1.), - max: Vec3::new(1., 1., 1.), - }; - let padded = a.padded((Vec3::ONE, Vec3::Y)); - assert!((padded.min - Vec3::new(-2., -2., -2.)).length() < 0.0001); - assert!((padded.max - Vec3::new(1., 2., 1.)).length() < 0.0001); -} + #[test] + fn merged() { + 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.merged(&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); + } + + #[test] + fn padded() { + let a = Aabb3d { + min: Vec3::new(-1., -1., -1.), + max: Vec3::new(1., 1., 1.), + }; + let padded = a.padded((Vec3::ONE, Vec3::Y)); + assert!((padded.min - Vec3::new(-2., -2., -2.)).length() < std::f32::EPSILON); + assert!((padded.max - Vec3::new(1., 2., 1.)).length() < std::f32::EPSILON); + } -#[test] -fn test_aabb3d_shrunk() { - let a = Aabb3d { - min: Vec3::new(-1., -1., -1.), - max: Vec3::new(1., 1., 1.), - }; - let shrunk = a.shrunk((Vec3::ONE, Vec3::Y)); - assert!((shrunk.min - Vec3::new(-0., -0., -0.)).length() < 0.0001); - assert!((shrunk.max - Vec3::new(1., 0., 1.)).length() < 0.0001); + #[test] + fn shrunk() { + let a = Aabb3d { + min: Vec3::new(-1., -1., -1.), + max: Vec3::new(1., 1., 1.), + }; + 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); + } } /// A bounding sphere @@ -223,64 +229,71 @@ impl BoundingVolume for BoundingSphere { } } -#[test] -fn test_bounding_sphere_area() { - let sphere = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; - assert!((sphere.visible_area() - 157.0796).abs() < 0.001); -} +#[cfg(test)] +mod bounding_sphere_tests { + use super::BoundingSphere; + use crate::{bounding::BoundingVolume, Vec3}; -#[test] -fn test_bounding_sphere_contains() { - let a = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; - let b = BoundingSphere { - center: Vec3::new(5.5, 1., 1.), - radius: 1., - }; - assert!(!a.contains(&b)); - let b = BoundingSphere { - center: Vec3::new(1., -3.5, 1.), - radius: 0.5, - }; - assert!(a.contains(&b)); -} + #[test] + fn area() { + let sphere = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + // Since this number is messy we check it with a higher threshold + assert!((sphere.visible_area() - 157.0796).abs() < 0.001); + } -#[test] -fn test_bounding_sphere_merged() { - let a = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; - let b = BoundingSphere { - center: Vec3::new(1., 1., -4.), - radius: 1., - }; - let merged = a.merged(&b); - assert!((merged.center - Vec3::new(1., 1., 0.5)).length() < 0.0001); - assert!((merged.radius - 5.5).abs() < 0.0001); -} + #[test] + fn contains() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let b = BoundingSphere { + center: Vec3::new(5.5, 1., 1.), + radius: 1., + }; + assert!(!a.contains(&b)); + let b = BoundingSphere { + center: Vec3::new(1., -3.5, 1.), + radius: 0.5, + }; + assert!(a.contains(&b)); + } -#[test] -fn test_bounding_sphere_padded() { - let a = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; - let padded = a.padded(1.25); - assert!((padded.radius - 6.25).abs() < 0.0001); -} + #[test] + fn merged() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let b = BoundingSphere { + center: Vec3::new(1., 1., -4.), + radius: 1., + }; + let merged = a.merged(&b); + assert!((merged.center - Vec3::new(1., 1., 0.5)).length() < std::f32::EPSILON); + assert!((merged.radius - 5.5).abs() < std::f32::EPSILON); + } -#[test] -fn test_bounding_sphere_shrunk() { - let a = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; - let shrunk = a.shrunk(0.5); - assert!((shrunk.radius - 4.5).abs() < 0.0001); + #[test] + fn padded() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let padded = a.padded(1.25); + assert!((padded.radius - 6.25).abs() < std::f32::EPSILON); + } + + #[test] + fn shrunk() { + let a = BoundingSphere { + center: Vec3::ONE, + radius: 5., + }; + let shrunk = a.shrunk(0.5); + assert!((shrunk.radius - 4.5).abs() < std::f32::EPSILON); + } } From b73d18605da80c0e37eff0bd8aac110e710a20c6 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Thu, 14 Dec 2023 08:00:25 +0100 Subject: [PATCH 3/9] Improve docs; Improve tests; Fix bugs --- crates/bevy_math/src/bounding/bounded2d.rs | 176 ++++++++++++--------- crates/bevy_math/src/bounding/bounded3d.rs | 165 +++++++++++-------- crates/bevy_math/src/bounding/mod.rs | 10 +- 3 files changed, 216 insertions(+), 135 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index f41b2ddf27521..ecd004e23f4f5 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -1,9 +1,11 @@ use super::BoundingVolume; use crate::prelude::Vec2; -/// A trait with methods that return 2d bounded volumes for a shape +/// 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 + /// 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. fn aabb_2d(&self, translation: Vec2, rotation: f32) -> Aabb2d; /// Get a bounding circle for the shape fn bounding_circle(&self, translation: Vec2) -> BoundingCircle; @@ -11,9 +13,9 @@ pub trait Bounded2d { /// A 2D axis-aligned bounding box, or bounding rectangle pub struct Aabb2d { - /// The minimum point of the box + /// The minimum, conventionally bottom-left, point of the box pub min: Vec2, - /// The maximum point of the box + /// The maximum, conventionally top-right, point of the box pub max: Vec2, } @@ -133,6 +135,10 @@ mod aabb2d_tests { let merged = a.merged(&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] @@ -144,6 +150,8 @@ mod aabb2d_tests { let padded = a.padded((Vec2::ONE, Vec2::Y)); assert!((padded.min - Vec2::new(-2., -2.)).length() < std::f32::EPSILON); assert!((padded.max - Vec2::new(1., 2.)).length() < std::f32::EPSILON); + assert!(padded.contains(&a)); + assert!(!a.contains(&padded)); } #[test] @@ -155,15 +163,38 @@ mod aabb2d_tests { 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); + assert!(a.contains(&shrunk)); + assert!(!shrunk.contains(&a)); } } +use crate::primitives::Circle; + /// A bounding circle +#[derive(Clone)] pub struct BoundingCircle { /// The center of the bounding circle pub center: Vec2, - /// The radius of the bounding circle - pub radius: f32, + /// 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 { @@ -177,53 +208,43 @@ impl BoundingVolume for BoundingCircle { #[inline(always)] fn visible_area(&self) -> f32 { - std::f32::consts::PI * self.radius * self.radius + std::f32::consts::PI * self.radius() * self.radius() } #[inline(always)] fn contains(&self, other: &Self) -> bool { - if other.center == self.center { - other.radius <= self.radius - } else { - let furthest_point = (other.center - self.center).length() + other.radius; - furthest_point <= self.radius - } + let furthest_point = (other.center - self.center).length() + other.radius(); + furthest_point <= self.radius() } #[inline(always)] fn merged(&self, other: &Self) -> Self { - if other.center == self.center { - Self { - center: self.center, - radius: self.radius.max(other.radius), - } - } else { - let diff = other.center - self.center; - let length = diff.length(); - let dir = diff / length; - - Self { - center: self.center + dir * ((length + other.radius - self.radius) / 2.), - radius: (length + self.radius + other.radius) / 2., - } + 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 padded(&self, amount: Self::Padding) -> Self { - Self { - center: self.center, - radius: self.radius + amount, - } + debug_assert!(amount >= 0.); + Self::new(self.center, self.radius() + amount) } #[inline(always)] fn shrunk(&self, amount: Self::Padding) -> Self { - debug_assert!(self.radius >= amount); - Self { - center: self.center, - radius: self.radius - amount, - } + debug_assert!(amount >= 0.); + debug_assert!(self.radius() >= amount); + Self::new(self.center, self.radius() - amount) } } @@ -234,64 +255,77 @@ mod bounding_circle_tests { #[test] fn area() { - let circle = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; + 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 { - center: Vec2::ONE, - radius: 5., - }; - let b = BoundingCircle { - center: Vec2::new(5.5, 1.), - radius: 1., - }; + let a = BoundingCircle::new(Vec2::ONE, 5.); + let b = BoundingCircle::new(Vec2::new(5.5, 1.), 1.); assert!(!a.contains(&b)); - let b = BoundingCircle { - center: Vec2::new(1., -3.5), - radius: 0.5, - }; + 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 merged() { - let a = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; - let b = BoundingCircle { - center: Vec2::new(1., -4.), - radius: 1., - }; + // When merging two circles that don't contain eachother, 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.merged(&b); assert!((merged.center - Vec2::new(1., 0.5)).length() < std::f32::EPSILON); - assert!((merged.radius - 5.5).abs() < 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.merged(&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.merged(&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.merged(&a); + assert_eq!(merged.center, a.center); + assert_eq!(merged.radius(), a.radius()); } #[test] fn padded() { - let a = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; + let a = BoundingCircle::new(Vec2::ONE, 5.); let padded = a.padded(1.25); - assert!((padded.radius - 6.25).abs() < std::f32::EPSILON); + assert!((padded.radius() - 6.25).abs() < std::f32::EPSILON); + assert!(padded.contains(&a)); + assert!(!a.contains(&padded)); } #[test] fn shrunk() { - let a = BoundingCircle { - center: Vec2::ONE, - radius: 5., - }; + let a = BoundingCircle::new(Vec2::ONE, 5.); let shrunk = a.shrunk(0.5); - assert!((shrunk.radius - 4.5).abs() < std::f32::EPSILON); + assert!((shrunk.radius() - 4.5).abs() < std::f32::EPSILON); + assert!(a.contains(&shrunk)); + assert!(!shrunk.contains(&a)); } } diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs index dc3b733a1ea64..13b4badae2ac5 100644 --- a/crates/bevy_math/src/bounding/bounded3d.rs +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -1,7 +1,7 @@ use super::BoundingVolume; use crate::prelude::{Quat, Vec3}; -/// A trait with methods that return 3d bounded volumes for a shape +/// 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; @@ -135,6 +135,10 @@ mod aabb3d_tests { let merged = a.merged(&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] @@ -146,6 +150,8 @@ mod aabb3d_tests { let padded = a.padded((Vec3::ONE, Vec3::Y)); 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.contains(&a)); + assert!(!a.contains(&padded)); } #[test] @@ -157,15 +163,37 @@ mod aabb3d_tests { 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); + assert!(a.contains(&shrunk)); + assert!(!shrunk.contains(&a)); } } +use crate::primitives::Sphere; + /// A bounding sphere +#[derive(Clone)] pub struct BoundingSphere { /// The center of the bounding sphere center: Vec3, - /// The radius of the bounding sphere - radius: f32, + /// The sphere + 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 { @@ -179,52 +207,52 @@ impl BoundingVolume for BoundingSphere { #[inline(always)] fn visible_area(&self) -> f32 { - 2. * std::f32::consts::PI * self.radius * self.radius + 2. * std::f32::consts::PI * self.radius() * self.radius() } #[inline(always)] fn contains(&self, other: &Self) -> bool { - if other.center == self.center { - other.radius <= self.radius - } else { - let furthest_point = (other.center - self.center).length() + other.radius; - furthest_point <= self.radius - } + let furthest_point = (other.center - self.center).length() + other.radius(); + furthest_point <= self.radius() } #[inline(always)] fn merged(&self, other: &Self) -> Self { - if other.center == self.center { - Self { - center: self.center, - radius: self.radius.max(other.radius), - } - } else { - let diff = other.center - self.center; - let length = diff.length(); - let dir = diff / length; - - Self { - center: self.center + dir * ((length + other.radius - self.radius) / 2.), - radius: (length + self.radius + other.radius) / 2., - } + 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 padded(&self, amount: Self::Padding) -> Self { + debug_assert!(amount >= 0.); Self { center: self.center, - radius: self.radius + amount, + sphere: Sphere { + radius: self.radius() + amount, + }, } } #[inline(always)] fn shrunk(&self, amount: Self::Padding) -> Self { - debug_assert!(self.radius >= amount); + debug_assert!(amount >= 0.); + debug_assert!(self.radius() >= amount); Self { center: self.center, - radius: self.radius - amount, + sphere: Sphere { + radius: self.radius() - amount, + }, } } } @@ -236,64 +264,77 @@ mod bounding_sphere_tests { #[test] fn area() { - let sphere = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; + 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 { - center: Vec3::ONE, - radius: 5., - }; - let b = BoundingSphere { - center: Vec3::new(5.5, 1., 1.), - radius: 1., - }; + 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 { - center: Vec3::new(1., -3.5, 1.), - radius: 0.5, - }; + 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 merged() { - let a = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; - let b = BoundingSphere { - center: Vec3::new(1., 1., -4.), - radius: 1., - }; + // When merging two circles that don't contain eachother, 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.merged(&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.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.merged(&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.merged(&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.merged(&a); + assert_eq!(merged.center, a.center); + assert_eq!(merged.radius(), a.radius()); } #[test] fn padded() { - let a = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; + let a = BoundingSphere::new(Vec3::ONE, 5.); let padded = a.padded(1.25); - assert!((padded.radius - 6.25).abs() < std::f32::EPSILON); + assert!((padded.radius() - 6.25).abs() < std::f32::EPSILON); + assert!(padded.contains(&a)); + assert!(!a.contains(&padded)); } #[test] fn shrunk() { - let a = BoundingSphere { - center: Vec3::ONE, - radius: 5., - }; + let a = BoundingSphere::new(Vec3::ONE, 5.); let shrunk = a.shrunk(0.5); - assert!((shrunk.radius - 4.5).abs() < std::f32::EPSILON); + assert!((shrunk.radius() - 4.5).abs() < std::f32::EPSILON); + assert!(a.contains(&shrunk)); + assert!(!shrunk.contains(&a)); } } diff --git a/crates/bevy_math/src/bounding/mod.rs b/crates/bevy_math/src/bounding/mod.rs index f8a886c9bb144..6bf9e87cea58c 100644 --- a/crates/bevy_math/src/bounding/mod.rs +++ b/crates/bevy_math/src/bounding/mod.rs @@ -1,11 +1,17 @@ //! 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. /// It 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. + /// 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. + /// The type used for the `padded` and `shrunk` methods. For example, a `f32` radius for + /// circles and spheres. type Padding; /// Returns the center of the bounding volume. From e42e1be03d0eec5e754110947a463c8fe9c21f2a Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Wed, 20 Dec 2023 18:12:37 +0100 Subject: [PATCH 4/9] Review comments and derives --- crates/bevy_math/src/bounding/bounded2d.rs | 31 +++++++++++----------- crates/bevy_math/src/bounding/bounded3d.rs | 29 ++++++++++---------- crates/bevy_math/src/bounding/mod.rs | 24 ++++++++++++----- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index ecd004e23f4f5..692dc21c232d4 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -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. @@ -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, @@ -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 { @@ -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 @@ -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 @@ -147,9 +148,9 @@ 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)); } @@ -157,12 +158,12 @@ mod aabb2d_tests { #[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)); } @@ -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, @@ -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.); diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs index 13b4badae2ac5..002f6469ae1c4 100644 --- a/crates/bevy_math/src/bounding/bounded3d.rs +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -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, @@ -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 { @@ -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 @@ -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 @@ -147,9 +148,9 @@ 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)); } @@ -157,12 +158,12 @@ mod aabb3d_tests { #[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)); } @@ -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, @@ -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.); diff --git a/crates/bevy_math/src/bounding/mod.rs b/crates/bevy_math/src/bounding/mod.rs index 6bf9e87cea58c..d5128a49b5170 100644 --- a/crates/bevy_math/src/bounding/mod.rs +++ b/crates/bevy_math/src/bounding/mod.rs @@ -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; /// 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. + /// + /// 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. @@ -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: +/// - Raycasting +/// - Testing for overlap +/// - Checking if an object is within the view frustum of a camera pub trait IntersectsVolume { /// Check if a volume intersects with this intersection test fn intersects(&self, volume: &Volume) -> bool; From 460677a8c9f6a6b515f8931456b72318a39d3f8c Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Wed, 20 Dec 2023 18:55:06 +0100 Subject: [PATCH 5/9] Optimize sqrt out of BoundingCircle/Sphere contains method --- crates/bevy_math/src/bounding/bounded2d.rs | 4 ++-- crates/bevy_math/src/bounding/bounded3d.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index 692dc21c232d4..3135a2fc3becd 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -214,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)] diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs index 002f6469ae1c4..f8f5b57b06efd 100644 --- a/crates/bevy_math/src/bounding/bounded3d.rs +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -213,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)] From 6dd0a6c85f5aca15aff60cbdc78d28f75a8acfe2 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Fri, 29 Dec 2023 19:54:06 +0100 Subject: [PATCH 6/9] Minor doc fixes --- crates/bevy_math/src/bounding/bounded2d.rs | 2 +- crates/bevy_math/src/bounding/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index 3135a2fc3becd..03c12c06ef201 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -4,7 +4,7 @@ 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, clockwise, with 0 meaning no 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 fn bounding_circle(&self, translation: Vec2) -> BoundingCircle; diff --git a/crates/bevy_math/src/bounding/mod.rs b/crates/bevy_math/src/bounding/mod.rs index d5128a49b5170..a720e669f3654 100644 --- a/crates/bevy_math/src/bounding/mod.rs +++ b/crates/bevy_math/src/bounding/mod.rs @@ -22,7 +22,7 @@ pub trait BoundingVolume { /// 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. + /// 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. @@ -42,7 +42,7 @@ pub trait BoundingVolume { } /// A trait that generalizes intersection tests against a volume. -/// Intersection tests can take many shapes, for example: +/// 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 From c2a7483b33298f244048fbf2a291dd929dc9072d Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Mon, 1 Jan 2024 19:33:27 +0100 Subject: [PATCH 7/9] Rename merged/padded/shrunk/Padding; Add half_size method --- crates/bevy_math/src/bounding/bounded2d.rs | 66 +++++++++----- crates/bevy_math/src/bounding/bounded3d.rs | 101 ++++++++++++--------- crates/bevy_math/src/bounding/mod.rs | 20 ++-- 3 files changed, 112 insertions(+), 75 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index 03c12c06ef201..9b9a6bb0b8c37 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -22,13 +22,18 @@ pub struct Aabb2d { impl BoundingVolume for Aabb2d { type Position = Vec2; - type Padding = 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; @@ -44,7 +49,7 @@ impl BoundingVolume for Aabb2d { } #[inline(always)] - fn merged(&self, other: &Self) -> Self { + fn merge(&self, other: &Self) -> Self { Self { min: self.min.min(other.min), max: self.max.max(other.max), @@ -52,7 +57,7 @@ impl BoundingVolume for Aabb2d { } #[inline(always)] - fn padded(&self, amount: Self::Padding) -> Self { + fn grow(&self, amount: Self::HalfSize) -> Self { let b = Self { min: self.min - amount, max: self.max + amount, @@ -62,7 +67,7 @@ impl BoundingVolume for Aabb2d { } #[inline(always)] - fn shrunk(&self, amount: Self::Padding) -> Self { + fn shrink(&self, amount: Self::HalfSize) -> Self { let b = Self { min: self.min + amount, max: self.max - amount, @@ -91,6 +96,16 @@ mod aabb2d_tests { 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 { @@ -124,7 +139,7 @@ mod aabb2d_tests { } #[test] - fn merged() { + fn merge() { let a = Aabb2d { min: Vec2::new(-1., -1.), max: Vec2::new(1., 0.5), @@ -133,7 +148,7 @@ mod aabb2d_tests { min: Vec2::new(-2., -0.5), max: Vec2::new(0.75, 1.), }; - let merged = a.merged(&b); + 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)); @@ -143,12 +158,12 @@ mod aabb2d_tests { } #[test] - fn padded() { + fn grow() { let a = Aabb2d { min: Vec2::new(-1., -1.), max: Vec2::new(1., 1.), }; - let padded = a.padded(Vec2::ONE); + 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)); @@ -156,12 +171,12 @@ mod aabb2d_tests { } #[test] - fn shrunk() { + fn shrink() { let a = Aabb2d { min: Vec2::new(-2., -2.), max: Vec2::new(2., 2.), }; - let shrunk = a.shrunk(Vec2::ONE); + 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)); @@ -200,13 +215,18 @@ impl BoundingCircle { impl BoundingVolume for BoundingCircle { type Position = Vec2; - type Padding = f32; + type HalfSize = f32; #[inline(always)] fn center(&self) -> Self::Position { self.center } + #[inline(always)] + fn half_size(&self) -> Self::HalfSize { + self.circle.radius + } + #[inline(always)] fn visible_area(&self) -> f32 { std::f32::consts::PI * self.radius() * self.radius() @@ -219,7 +239,7 @@ impl BoundingVolume for BoundingCircle { } #[inline(always)] - fn merged(&self, other: &Self) -> Self { + fn merge(&self, other: &Self) -> Self { let diff = other.center - self.center; let length = diff.length(); if self.radius() >= length + other.radius() { @@ -236,13 +256,13 @@ impl BoundingVolume for BoundingCircle { } #[inline(always)] - fn padded(&self, amount: Self::Padding) -> Self { + fn grow(&self, amount: Self::HalfSize) -> Self { debug_assert!(amount >= 0.); Self::new(self.center, self.radius() + amount) } #[inline(always)] - fn shrunk(&self, amount: Self::Padding) -> Self { + fn shrink(&self, amount: Self::HalfSize) -> Self { debug_assert!(amount >= 0.); debug_assert!(self.radius() >= amount); Self::new(self.center, self.radius() - amount) @@ -277,12 +297,12 @@ mod bounding_circle_tests { } #[test] - fn merged() { + 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.merged(&b); + 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)); @@ -293,13 +313,13 @@ mod bounding_circle_tests { // 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.merged(&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.merged(&b); + let merged = a.merge(&b); assert_eq!(merged.center, a.center); assert_eq!(merged.radius(), b.radius()); } @@ -307,24 +327,24 @@ mod bounding_circle_tests { #[test] fn merge_identical() { let a = BoundingCircle::new(Vec2::ONE, 5.); - let merged = a.merged(&a); + let merged = a.merge(&a); assert_eq!(merged.center, a.center); assert_eq!(merged.radius(), a.radius()); } #[test] - fn padded() { + fn grow() { let a = BoundingCircle::new(Vec2::ONE, 5.); - let padded = a.padded(1.25); + 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 shrunk() { + fn shrink() { let a = BoundingCircle::new(Vec2::ONE, 5.); - let shrunk = a.shrunk(0.5); + let shrunk = a.shrink(0.5); assert!((shrunk.radius() - 4.5).abs() < std::f32::EPSILON); assert!(a.contains(&shrunk)); assert!(!shrunk.contains(&a)); diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs index f8f5b57b06efd..822cc3872b463 100644 --- a/crates/bevy_math/src/bounding/bounded3d.rs +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -20,13 +20,18 @@ pub struct Aabb3d { impl BoundingVolume for Aabb3d { type Position = Vec3; - type Padding = 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; @@ -44,7 +49,7 @@ impl BoundingVolume for Aabb3d { } #[inline(always)] - fn merged(&self, other: &Self) -> Self { + fn merge(&self, other: &Self) -> Self { Self { min: self.min.min(other.min), max: self.max.max(other.max), @@ -52,7 +57,7 @@ impl BoundingVolume for Aabb3d { } #[inline(always)] - fn padded(&self, amount: Self::Padding) -> Self { + fn grow(&self, amount: Self::HalfSize) -> Self { let b = Self { min: self.min - amount, max: self.max + amount, @@ -62,7 +67,7 @@ impl BoundingVolume for Aabb3d { } #[inline(always)] - fn shrunk(&self, amount: Self::Padding) -> Self { + fn shrink(&self, amount: Self::HalfSize) -> Self { let b = Self { min: self.min + amount, max: self.max - amount, @@ -91,6 +96,15 @@ mod aabb3d_tests { 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 { @@ -124,7 +138,7 @@ mod aabb3d_tests { } #[test] - fn merged() { + fn merge() { let a = Aabb3d { min: Vec3::new(-1., -1., -1.), max: Vec3::new(1., 0.5, 1.), @@ -133,7 +147,7 @@ mod aabb3d_tests { min: Vec3::new(-2., -0.5, -0.), max: Vec3::new(0.75, 1., 2.), }; - let merged = a.merged(&b); + 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)); @@ -143,12 +157,12 @@ mod aabb3d_tests { } #[test] - fn padded() { + fn grow() { let a = Aabb3d { min: Vec3::new(-1., -1., -1.), max: Vec3::new(1., 1., 1.), }; - let padded = a.padded(Vec3::ONE); + 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)); @@ -156,12 +170,12 @@ mod aabb3d_tests { } #[test] - fn shrunk() { + fn shrink() { let a = Aabb3d { min: Vec3::new(-2., -2., -2.), max: Vec3::new(2., 2., 2.), }; - let shrunk = a.shrunk(Vec3::ONE); + 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)); @@ -189,70 +203,69 @@ impl BoundingSphere { 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 Padding = f32; + type HalfSize = f32; #[inline(always)] fn center(&self) -> Self::Position { self.center } + #[inline(always)] + fn half_size(&self) -> Self::HalfSize { + self.sphere.radius + } + #[inline(always)] fn visible_area(&self) -> f32 { - 2. * std::f32::consts::PI * self.radius() * self.radius() + 2. * std::f32::consts::PI * self.half_size() * self.half_size() } #[inline(always)] fn contains(&self, other: &Self) -> bool { - let diff = self.radius() - other.radius(); + let diff = self.half_size() - other.half_size(); self.center.distance_squared(other.center) <= diff.powi(2).copysign(diff) } #[inline(always)] - fn merged(&self, other: &Self) -> Self { + fn merge(&self, other: &Self) -> Self { let diff = other.center - self.center; let length = diff.length(); - if self.radius() >= length + other.radius() { + if self.half_size() >= length + other.half_size() { return self.clone(); } - if other.radius() >= length + self.radius() { + if other.half_size() >= length + self.half_size() { 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., + (self.center + other.center) / 2. + dir * ((other.half_size() - self.half_size()) / 2.), + (length + self.half_size() + other.half_size()) / 2., ) } #[inline(always)] - fn padded(&self, amount: Self::Padding) -> Self { + fn grow(&self, amount: Self::HalfSize) -> Self { debug_assert!(amount >= 0.); Self { center: self.center, sphere: Sphere { - radius: self.radius() + amount, + radius: self.half_size() + amount, }, } } #[inline(always)] - fn shrunk(&self, amount: Self::Padding) -> Self { + fn shrink(&self, amount: Self::HalfSize) -> Self { debug_assert!(amount >= 0.); - debug_assert!(self.radius() >= amount); + debug_assert!(self.half_size() >= amount); Self { center: self.center, sphere: Sphere { - radius: self.radius() - amount, + radius: self.half_size() - amount, }, } } @@ -286,14 +299,14 @@ mod bounding_sphere_tests { } #[test] - fn merged() { + 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.merged(&b); + 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.half_size() - 5.5).abs() < std::f32::EPSILON); assert!(merged.contains(&a)); assert!(merged.contains(&b)); assert!(!a.contains(&merged)); @@ -302,39 +315,39 @@ mod bounding_sphere_tests { // 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.merged(&b); + let merged = a.merge(&b); assert_eq!(merged.center, a.center); - assert_eq!(merged.radius(), a.radius()); + assert_eq!(merged.half_size(), a.half_size()); // When two circles are at the same point, we use the bigger radius let b = BoundingSphere::new(Vec3::ONE, 6.); - let merged = a.merged(&b); + let merged = a.merge(&b); assert_eq!(merged.center, a.center); - assert_eq!(merged.radius(), b.radius()); + assert_eq!(merged.half_size(), b.half_size()); } #[test] fn merge_identical() { let a = BoundingSphere::new(Vec3::ONE, 5.); - let merged = a.merged(&a); + let merged = a.merge(&a); assert_eq!(merged.center, a.center); - assert_eq!(merged.radius(), a.radius()); + assert_eq!(merged.half_size(), a.half_size()); } #[test] - fn padded() { + fn grow() { let a = BoundingSphere::new(Vec3::ONE, 5.); - let padded = a.padded(1.25); - assert!((padded.radius() - 6.25).abs() < std::f32::EPSILON); + let padded = a.grow(1.25); + assert!((padded.half_size() - 6.25).abs() < std::f32::EPSILON); assert!(padded.contains(&a)); assert!(!a.contains(&padded)); } #[test] - fn shrunk() { + fn shrink() { let a = BoundingSphere::new(Vec3::ONE, 5.); - let shrunk = a.shrunk(0.5); - assert!((shrunk.radius() - 4.5).abs() < std::f32::EPSILON); + let shrunk = a.shrink(0.5); + assert!((shrunk.half_size() - 4.5).abs() < std::f32::EPSILON); assert!(a.contains(&shrunk)); assert!(!shrunk.contains(&a)); } diff --git a/crates/bevy_math/src/bounding/mod.rs b/crates/bevy_math/src/bounding/mod.rs index a720e669f3654..7a07626fddba7 100644 --- a/crates/bevy_math/src/bounding/mod.rs +++ b/crates/bevy_math/src/bounding/mod.rs @@ -13,13 +13,17 @@ 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, an `f32` radius for - /// circles and spheres. - type Padding; + /// 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. @@ -32,13 +36,13 @@ pub trait BoundingVolume { fn contains(&self, other: &Self) -> bool; /// Computes the smallest bounding volume that contains both `self` and `other`. - fn merged(&self, other: &Self) -> Self; + fn merge(&self, other: &Self) -> Self; - /// Expand the bounding volume in each direction by the given amount - fn padded(&self, amount: Self::Padding) -> Self; + /// Increase the size of the bounding volume in each direction by the given amount + fn grow(&self, amount: Self::HalfSize) -> Self; - /// Shrink the bounding volume in each direction by the given amount - fn shrunk(&self, amount: Self::Padding) -> 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. From c70388ac664553bb3528100049b1b40f1c3f144f Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Wed, 10 Jan 2024 22:52:01 +0100 Subject: [PATCH 8/9] Fix some oversights --- crates/bevy_math/src/bounding/bounded2d.rs | 3 ++- crates/bevy_math/src/bounding/bounded3d.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index 9b9a6bb0b8c37..1d7367fddffe9 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -7,7 +7,8 @@ pub trait Bounded2d { /// 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 - fn bounding_circle(&self, translation: Vec2) -> BoundingCircle; + /// 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 diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs index 822cc3872b463..3a7d655e64a6c 100644 --- a/crates/bevy_math/src/bounding/bounded3d.rs +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -5,17 +5,17 @@ use crate::prelude::{Quat, Vec3}; 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 - fn bounding_sphere(&self, translation: Vec3) -> BoundingSphere; + /// 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 - min: Vec3, + pub min: Vec3, /// The maximum point of the box - max: Vec3, + pub max: Vec3, } impl BoundingVolume for Aabb3d { @@ -189,9 +189,9 @@ use crate::primitives::Sphere; #[derive(Clone, Debug)] pub struct BoundingSphere { /// The center of the bounding sphere - center: Vec3, + pub center: Vec3, /// The sphere - sphere: Sphere, + pub sphere: Sphere, } impl BoundingSphere { From 8d8417ae0e18ccdd3cbcaaa00e52d6ec2af27d29 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Wed, 10 Jan 2024 22:59:51 +0100 Subject: [PATCH 9/9] Bring back radius on BoundingSphere --- crates/bevy_math/src/bounding/bounded2d.rs | 2 +- crates/bevy_math/src/bounding/bounded3d.rs | 38 +++++++++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded2d.rs b/crates/bevy_math/src/bounding/bounded2d.rs index 1d7367fddffe9..78caf6abe4ad9 100644 --- a/crates/bevy_math/src/bounding/bounded2d.rs +++ b/crates/bevy_math/src/bounding/bounded2d.rs @@ -225,7 +225,7 @@ impl BoundingVolume for BoundingCircle { #[inline(always)] fn half_size(&self) -> Self::HalfSize { - self.circle.radius + self.radius() } #[inline(always)] diff --git a/crates/bevy_math/src/bounding/bounded3d.rs b/crates/bevy_math/src/bounding/bounded3d.rs index 3a7d655e64a6c..fb9bd7a58a86d 100644 --- a/crates/bevy_math/src/bounding/bounded3d.rs +++ b/crates/bevy_math/src/bounding/bounded3d.rs @@ -203,6 +203,12 @@ impl BoundingSphere { sphere: Sphere { radius }, } } + + /// Get the radius of the bounding sphere + #[inline(always)] + pub fn radius(&self) -> f32 { + self.sphere.radius + } } impl BoundingVolume for BoundingSphere { @@ -216,17 +222,17 @@ impl BoundingVolume for BoundingSphere { #[inline(always)] fn half_size(&self) -> Self::HalfSize { - self.sphere.radius + self.radius() } #[inline(always)] fn visible_area(&self) -> f32 { - 2. * std::f32::consts::PI * self.half_size() * self.half_size() + 2. * std::f32::consts::PI * self.radius() * self.radius() } #[inline(always)] fn contains(&self, other: &Self) -> bool { - let diff = self.half_size() - other.half_size(); + let diff = self.radius() - other.radius(); self.center.distance_squared(other.center) <= diff.powi(2).copysign(diff) } @@ -234,16 +240,16 @@ impl BoundingVolume for BoundingSphere { fn merge(&self, other: &Self) -> Self { let diff = other.center - self.center; let length = diff.length(); - if self.half_size() >= length + other.half_size() { + if self.radius() >= length + other.radius() { return self.clone(); } - if other.half_size() >= length + self.half_size() { + if other.radius() >= length + self.radius() { return other.clone(); } let dir = diff / length; Self::new( - (self.center + other.center) / 2. + dir * ((other.half_size() - self.half_size()) / 2.), - (length + self.half_size() + other.half_size()) / 2., + (self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.), + (length + self.radius() + other.radius()) / 2., ) } @@ -253,7 +259,7 @@ impl BoundingVolume for BoundingSphere { Self { center: self.center, sphere: Sphere { - radius: self.half_size() + amount, + radius: self.radius() + amount, }, } } @@ -261,11 +267,11 @@ impl BoundingVolume for BoundingSphere { #[inline(always)] fn shrink(&self, amount: Self::HalfSize) -> Self { debug_assert!(amount >= 0.); - debug_assert!(self.half_size() >= amount); + debug_assert!(self.radius() >= amount); Self { center: self.center, sphere: Sphere { - radius: self.half_size() - amount, + radius: self.radius() - amount, }, } } @@ -306,7 +312,7 @@ mod bounding_sphere_tests { 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.half_size() - 5.5).abs() < std::f32::EPSILON); + assert!((merged.radius() - 5.5).abs() < std::f32::EPSILON); assert!(merged.contains(&a)); assert!(merged.contains(&b)); assert!(!a.contains(&merged)); @@ -317,13 +323,13 @@ mod bounding_sphere_tests { assert!(a.contains(&b)); let merged = a.merge(&b); assert_eq!(merged.center, a.center); - assert_eq!(merged.half_size(), a.half_size()); + 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.half_size(), b.half_size()); + assert_eq!(merged.radius(), b.radius()); } #[test] @@ -331,14 +337,14 @@ mod bounding_sphere_tests { let a = BoundingSphere::new(Vec3::ONE, 5.); let merged = a.merge(&a); assert_eq!(merged.center, a.center); - assert_eq!(merged.half_size(), a.half_size()); + assert_eq!(merged.radius(), a.radius()); } #[test] fn grow() { let a = BoundingSphere::new(Vec3::ONE, 5.); let padded = a.grow(1.25); - assert!((padded.half_size() - 6.25).abs() < std::f32::EPSILON); + assert!((padded.radius() - 6.25).abs() < std::f32::EPSILON); assert!(padded.contains(&a)); assert!(!a.contains(&padded)); } @@ -347,7 +353,7 @@ mod bounding_sphere_tests { fn shrink() { let a = BoundingSphere::new(Vec3::ONE, 5.); let shrunk = a.shrink(0.5); - assert!((shrunk.half_size() - 4.5).abs() < std::f32::EPSILON); + assert!((shrunk.radius() - 4.5).abs() < std::f32::EPSILON); assert!(a.contains(&shrunk)); assert!(!shrunk.contains(&a)); }