Skip to content

Support transforming bounding volumes #11681

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 5, 2024
109 changes: 105 additions & 4 deletions crates/bevy_math/src/bounding/bounded2d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,12 @@ impl Aabb2d {
}

impl BoundingVolume for Aabb2d {
type Position = Vec2;
type Translation = Vec2;
type Rotation = f32;
type HalfSize = Vec2;

#[inline(always)]
fn center(&self) -> Self::Position {
fn center(&self) -> Self::Translation {
(self.min + self.max) / 2.
}

Expand Down Expand Up @@ -147,6 +148,66 @@ impl BoundingVolume for Aabb2d {
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
}

/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transformed_by(mut self, translation: Self::Translation, rotation: Self::Rotation) -> Self {
self.transform_by(translation, rotation);
self
}

/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) {
self.rotate_by(rotation);
self.translate_by(translation);
}

#[inline(always)]
fn translate_by(&mut self, translation: Self::Translation) {
self.min += translation;
self.max += translation;
}

/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotated_by(mut self, rotation: Self::Rotation) -> Self {
self.rotate_by(rotation);
self
}

/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotate_by(&mut self, rotation: Self::Rotation) {
let rot_mat = Mat2::from_angle(rotation);
let abs_rot_mat = Mat2::from_cols(rot_mat.x_axis.abs(), rot_mat.y_axis.abs());
let half_size = abs_rot_mat * self.half_size();
*self = Self::new(rot_mat * self.center(), half_size);
}
}

impl IntersectsVolume<Self> for Aabb2d {
Expand Down Expand Up @@ -277,6 +338,24 @@ mod aabb2d_tests {
assert!(!shrunk.contains(&a));
}

#[test]
fn transform() {
let a = Aabb2d {
min: Vec2::new(-2.0, -2.0),
max: Vec2::new(2.0, 2.0),
};
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), std::f32::consts::FRAC_PI_4);
let half_length = 2_f32.hypot(2.0);
assert_eq!(
transformed.min,
Vec2::new(2.0 - half_length, -half_length - 2.0)
);
assert_eq!(
transformed.max,
Vec2::new(2.0 + half_length, half_length - 2.0)
);
}

#[test]
fn closest_point() {
let aabb = Aabb2d {
Expand Down Expand Up @@ -396,11 +475,12 @@ impl BoundingCircle {
}

impl BoundingVolume for BoundingCircle {
type Position = Vec2;
type Translation = Vec2;
type Rotation = f32;
type HalfSize = f32;

#[inline(always)]
fn center(&self) -> Self::Position {
fn center(&self) -> Self::Translation {
self.center
}

Expand Down Expand Up @@ -449,6 +529,16 @@ impl BoundingVolume for BoundingCircle {
debug_assert!(self.radius() >= amount);
Self::new(self.center, self.radius() - amount)
}

#[inline(always)]
fn translate_by(&mut self, translation: Vec2) {
self.center += translation;
}

#[inline(always)]
fn rotate_by(&mut self, rotation: f32) {
self.center = Mat2::from_angle(rotation) * self.center;
}
}

impl IntersectsVolume<Self> for BoundingCircle {
Expand Down Expand Up @@ -551,6 +641,17 @@ mod bounding_circle_tests {
assert!(!shrunk.contains(&a));
}

#[test]
fn transform() {
let a = BoundingCircle::new(Vec2::ONE, 5.0);
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), std::f32::consts::FRAC_PI_4);
assert_eq!(
transformed.center,
Vec2::new(2.0, std::f32::consts::SQRT_2 - 2.0)
);
assert_eq!(transformed.radius(), 5.0);
}

#[test]
fn closest_point() {
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
Expand Down
111 changes: 105 additions & 6 deletions crates/bevy_math/src/bounding/bounded3d/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod primitive_impls;

use glam::Mat3;

use super::{BoundingVolume, IntersectsVolume};
use crate::prelude::{Quat, Vec3};

Expand Down Expand Up @@ -87,11 +89,12 @@ impl Aabb3d {
}

impl BoundingVolume for Aabb3d {
type Position = Vec3;
type Translation = Vec3;
type Rotation = Quat;
type HalfSize = Vec3;

#[inline(always)]
fn center(&self) -> Self::Position {
fn center(&self) -> Self::Translation {
(self.min + self.max) / 2.
}

Expand Down Expand Up @@ -143,6 +146,54 @@ impl BoundingVolume for Aabb3d {
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z);
b
}

/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
#[inline(always)]
fn transformed_by(mut self, translation: Self::Translation, rotation: Self::Rotation) -> Self {
self.transform_by(translation, rotation);
self
}

/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
#[inline(always)]
fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) {
self.rotate_by(rotation);
self.translate_by(translation);
}

#[inline(always)]
fn translate_by(&mut self, translation: Self::Translation) {
self.min += translation;
self.max += translation;
}

/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
#[inline(always)]
fn rotated_by(mut self, rotation: Self::Rotation) -> Self {
self.rotate_by(rotation);
self
}

/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
#[inline(always)]
fn rotate_by(&mut self, rotation: Self::Rotation) {
let rot_mat = Mat3::from_quat(rotation);
let abs_rot_mat = Mat3::from_cols(
rot_mat.x_axis.abs(),
rot_mat.y_axis.abs(),
rot_mat.z_axis.abs(),
);
let half_size = abs_rot_mat * self.half_size();
*self = Self::new(rot_mat * self.center(), half_size);
}
}

impl IntersectsVolume<Self> for Aabb3d {
Expand Down Expand Up @@ -170,7 +221,7 @@ mod aabb3d_tests {
use super::Aabb3d;
use crate::{
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
Vec3,
Quat, Vec3,
};

#[test]
Expand Down Expand Up @@ -273,6 +324,27 @@ mod aabb3d_tests {
assert!(!shrunk.contains(&a));
}

#[test]
fn transform() {
let a = Aabb3d {
min: Vec3::new(-2.0, -2.0, -2.0),
max: Vec3::new(2.0, 2.0, 2.0),
};
let transformed = a.transformed_by(
Vec3::new(2.0, -2.0, 4.0),
Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
);
let half_length = 2_f32.hypot(2.0);
assert_eq!(
transformed.min,
Vec3::new(2.0 - half_length, -half_length - 2.0, 2.0)
);
assert_eq!(
transformed.max,
Vec3::new(2.0 + half_length, half_length - 2.0, 6.0)
);
}

#[test]
fn closest_point() {
let aabb = Aabb3d {
Expand Down Expand Up @@ -388,11 +460,12 @@ impl BoundingSphere {
}

impl BoundingVolume for BoundingSphere {
type Position = Vec3;
type Translation = Vec3;
type Rotation = Quat;
type HalfSize = f32;

#[inline(always)]
fn center(&self) -> Self::Position {
fn center(&self) -> Self::Translation {
self.center
}

Expand Down Expand Up @@ -451,6 +524,16 @@ impl BoundingVolume for BoundingSphere {
},
}
}

#[inline(always)]
fn translate_by(&mut self, translation: Vec3) {
self.center += translation;
}

#[inline(always)]
fn rotate_by(&mut self, rotation: Quat) {
self.center = rotation * self.center;
}
}

impl IntersectsVolume<Self> for BoundingSphere {
Expand All @@ -471,10 +554,12 @@ impl IntersectsVolume<Aabb3d> for BoundingSphere {

#[cfg(test)]
mod bounding_sphere_tests {
use approx::assert_relative_eq;

use super::BoundingSphere;
use crate::{
bounding::{BoundingVolume, IntersectsVolume},
Vec3,
Quat, Vec3,
};

#[test]
Expand Down Expand Up @@ -553,6 +638,20 @@ mod bounding_sphere_tests {
assert!(!shrunk.contains(&a));
}

#[test]
fn transform() {
let a = BoundingSphere::new(Vec3::ONE, 5.0);
let transformed = a.transformed_by(
Vec3::new(2.0, -2.0, 4.0),
Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
);
assert_relative_eq!(
transformed.center,
Vec3::new(2.0, std::f32::consts::SQRT_2 - 2.0, 5.0)
);
assert_eq!(transformed.radius(), 5.0);
}

#[test]
fn closest_point() {
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
Expand Down
Loading