Skip to content

Commit f72de1d

Browse files
Jondolfmatiqo15
authored andcommitted
Support transforming bounding volumes (bevyengine#11681)
# Objective Make it straightforward to translate and rotate bounding volumes. ## Solution Add `translate_by`/`translated_by`, `rotate_by`/`rotated_by`, `transform_by`/`transformed_by` methods to the `BoundingVolume` trait. This follows the naming used for mesh transformations (see bevyengine#11454 and bevyengine#11675). --- ## Changelog - Added `translate_by`/`translated_by`, `rotate_by`/`rotated_by`, `transform_by`/`transformed_by` methods to the `BoundingVolume` trait and implemented them for the bounding volumes - Renamed `Position` associated type to `Translation` --------- Co-authored-by: Mateusz Wachowiak <[email protected]>
1 parent 685986e commit f72de1d

File tree

3 files changed

+255
-15
lines changed

3 files changed

+255
-15
lines changed

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

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,12 @@ impl Aabb2d {
9393
}
9494

9595
impl BoundingVolume for Aabb2d {
96-
type Position = Vec2;
96+
type Translation = Vec2;
97+
type Rotation = f32;
9798
type HalfSize = Vec2;
9899

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

@@ -147,6 +148,66 @@ impl BoundingVolume for Aabb2d {
147148
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
148149
b
149150
}
151+
152+
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
153+
///
154+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
155+
///
156+
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
157+
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
158+
/// and consider storing the original AABB and rotating that every time instead.
159+
#[inline(always)]
160+
fn transformed_by(mut self, translation: Self::Translation, rotation: Self::Rotation) -> Self {
161+
self.transform_by(translation, rotation);
162+
self
163+
}
164+
165+
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
166+
///
167+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
168+
///
169+
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
170+
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
171+
/// and consider storing the original AABB and rotating that every time instead.
172+
#[inline(always)]
173+
fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) {
174+
self.rotate_by(rotation);
175+
self.translate_by(translation);
176+
}
177+
178+
#[inline(always)]
179+
fn translate_by(&mut self, translation: Self::Translation) {
180+
self.min += translation;
181+
self.max += translation;
182+
}
183+
184+
/// Rotates the bounding volume around the origin by the given rotation.
185+
///
186+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
187+
///
188+
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
189+
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
190+
/// and consider storing the original AABB and rotating that every time instead.
191+
#[inline(always)]
192+
fn rotated_by(mut self, rotation: Self::Rotation) -> Self {
193+
self.rotate_by(rotation);
194+
self
195+
}
196+
197+
/// Rotates the bounding volume around the origin by the given rotation.
198+
///
199+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
200+
///
201+
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
202+
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
203+
/// and consider storing the original AABB and rotating that every time instead.
204+
#[inline(always)]
205+
fn rotate_by(&mut self, rotation: Self::Rotation) {
206+
let rot_mat = Mat2::from_angle(rotation);
207+
let abs_rot_mat = Mat2::from_cols(rot_mat.x_axis.abs(), rot_mat.y_axis.abs());
208+
let half_size = abs_rot_mat * self.half_size();
209+
*self = Self::new(rot_mat * self.center(), half_size);
210+
}
150211
}
151212

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

341+
#[test]
342+
fn transform() {
343+
let a = Aabb2d {
344+
min: Vec2::new(-2.0, -2.0),
345+
max: Vec2::new(2.0, 2.0),
346+
};
347+
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), std::f32::consts::FRAC_PI_4);
348+
let half_length = 2_f32.hypot(2.0);
349+
assert_eq!(
350+
transformed.min,
351+
Vec2::new(2.0 - half_length, -half_length - 2.0)
352+
);
353+
assert_eq!(
354+
transformed.max,
355+
Vec2::new(2.0 + half_length, half_length - 2.0)
356+
);
357+
}
358+
280359
#[test]
281360
fn closest_point() {
282361
let aabb = Aabb2d {
@@ -396,11 +475,12 @@ impl BoundingCircle {
396475
}
397476

398477
impl BoundingVolume for BoundingCircle {
399-
type Position = Vec2;
478+
type Translation = Vec2;
479+
type Rotation = f32;
400480
type HalfSize = f32;
401481

402482
#[inline(always)]
403-
fn center(&self) -> Self::Position {
483+
fn center(&self) -> Self::Translation {
404484
self.center
405485
}
406486

@@ -449,6 +529,16 @@ impl BoundingVolume for BoundingCircle {
449529
debug_assert!(self.radius() >= amount);
450530
Self::new(self.center, self.radius() - amount)
451531
}
532+
533+
#[inline(always)]
534+
fn translate_by(&mut self, translation: Vec2) {
535+
self.center += translation;
536+
}
537+
538+
#[inline(always)]
539+
fn rotate_by(&mut self, rotation: f32) {
540+
self.center = Mat2::from_angle(rotation) * self.center;
541+
}
452542
}
453543

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

644+
#[test]
645+
fn transform() {
646+
let a = BoundingCircle::new(Vec2::ONE, 5.0);
647+
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), std::f32::consts::FRAC_PI_4);
648+
assert_eq!(
649+
transformed.center,
650+
Vec2::new(2.0, std::f32::consts::SQRT_2 - 2.0)
651+
);
652+
assert_eq!(transformed.radius(), 5.0);
653+
}
654+
554655
#[test]
555656
fn closest_point() {
556657
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);

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

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

3+
use glam::Mat3;
4+
35
use super::{BoundingVolume, IntersectsVolume};
46
use crate::prelude::{Quat, Vec3};
57

@@ -87,11 +89,12 @@ impl Aabb3d {
8789
}
8890

8991
impl BoundingVolume for Aabb3d {
90-
type Position = Vec3;
92+
type Translation = Vec3;
93+
type Rotation = Quat;
9194
type HalfSize = Vec3;
9295

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

@@ -143,6 +146,54 @@ impl BoundingVolume for Aabb3d {
143146
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z);
144147
b
145148
}
149+
150+
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
151+
///
152+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
153+
#[inline(always)]
154+
fn transformed_by(mut self, translation: Self::Translation, rotation: Self::Rotation) -> Self {
155+
self.transform_by(translation, rotation);
156+
self
157+
}
158+
159+
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
160+
///
161+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
162+
#[inline(always)]
163+
fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) {
164+
self.rotate_by(rotation);
165+
self.translate_by(translation);
166+
}
167+
168+
#[inline(always)]
169+
fn translate_by(&mut self, translation: Self::Translation) {
170+
self.min += translation;
171+
self.max += translation;
172+
}
173+
174+
/// Rotates the bounding volume around the origin by the given rotation.
175+
///
176+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
177+
#[inline(always)]
178+
fn rotated_by(mut self, rotation: Self::Rotation) -> Self {
179+
self.rotate_by(rotation);
180+
self
181+
}
182+
183+
/// Rotates the bounding volume around the origin by the given rotation.
184+
///
185+
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
186+
#[inline(always)]
187+
fn rotate_by(&mut self, rotation: Self::Rotation) {
188+
let rot_mat = Mat3::from_quat(rotation);
189+
let abs_rot_mat = Mat3::from_cols(
190+
rot_mat.x_axis.abs(),
191+
rot_mat.y_axis.abs(),
192+
rot_mat.z_axis.abs(),
193+
);
194+
let half_size = abs_rot_mat * self.half_size();
195+
*self = Self::new(rot_mat * self.center(), half_size);
196+
}
146197
}
147198

148199
impl IntersectsVolume<Self> for Aabb3d {
@@ -170,7 +221,7 @@ mod aabb3d_tests {
170221
use super::Aabb3d;
171222
use crate::{
172223
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
173-
Vec3,
224+
Quat, Vec3,
174225
};
175226

176227
#[test]
@@ -273,6 +324,27 @@ mod aabb3d_tests {
273324
assert!(!shrunk.contains(&a));
274325
}
275326

327+
#[test]
328+
fn transform() {
329+
let a = Aabb3d {
330+
min: Vec3::new(-2.0, -2.0, -2.0),
331+
max: Vec3::new(2.0, 2.0, 2.0),
332+
};
333+
let transformed = a.transformed_by(
334+
Vec3::new(2.0, -2.0, 4.0),
335+
Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
336+
);
337+
let half_length = 2_f32.hypot(2.0);
338+
assert_eq!(
339+
transformed.min,
340+
Vec3::new(2.0 - half_length, -half_length - 2.0, 2.0)
341+
);
342+
assert_eq!(
343+
transformed.max,
344+
Vec3::new(2.0 + half_length, half_length - 2.0, 6.0)
345+
);
346+
}
347+
276348
#[test]
277349
fn closest_point() {
278350
let aabb = Aabb3d {
@@ -388,11 +460,12 @@ impl BoundingSphere {
388460
}
389461

390462
impl BoundingVolume for BoundingSphere {
391-
type Position = Vec3;
463+
type Translation = Vec3;
464+
type Rotation = Quat;
392465
type HalfSize = f32;
393466

394467
#[inline(always)]
395-
fn center(&self) -> Self::Position {
468+
fn center(&self) -> Self::Translation {
396469
self.center
397470
}
398471

@@ -451,6 +524,16 @@ impl BoundingVolume for BoundingSphere {
451524
},
452525
}
453526
}
527+
528+
#[inline(always)]
529+
fn translate_by(&mut self, translation: Vec3) {
530+
self.center += translation;
531+
}
532+
533+
#[inline(always)]
534+
fn rotate_by(&mut self, rotation: Quat) {
535+
self.center = rotation * self.center;
536+
}
454537
}
455538

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

472555
#[cfg(test)]
473556
mod bounding_sphere_tests {
557+
use approx::assert_relative_eq;
558+
474559
use super::BoundingSphere;
475560
use crate::{
476561
bounding::{BoundingVolume, IntersectsVolume},
477-
Vec3,
562+
Quat, Vec3,
478563
};
479564

480565
#[test]
@@ -553,6 +638,20 @@ mod bounding_sphere_tests {
553638
assert!(!shrunk.contains(&a));
554639
}
555640

641+
#[test]
642+
fn transform() {
643+
let a = BoundingSphere::new(Vec3::ONE, 5.0);
644+
let transformed = a.transformed_by(
645+
Vec3::new(2.0, -2.0, 4.0),
646+
Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
647+
);
648+
assert_relative_eq!(
649+
transformed.center,
650+
Vec3::new(2.0, std::f32::consts::SQRT_2 - 2.0, 5.0)
651+
);
652+
assert_eq!(transformed.radius(), 5.0);
653+
}
654+
556655
#[test]
557656
fn closest_point() {
558657
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);

0 commit comments

Comments
 (0)