Support transforming bounding volumes (#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 #11454 and #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 <mateusz_wachowiak@outlook.com>
This commit is contained in:
		
							parent
							
								
									9a6fc76148
								
							
						
					
					
						commit
						921ba54acf
					
				| @ -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. | ||||
|     } | ||||
| 
 | ||||
| @ -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 { | ||||
| @ -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 { | ||||
| @ -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 | ||||
|     } | ||||
| 
 | ||||
| @ -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 { | ||||
| @ -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); | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| mod primitive_impls; | ||||
| 
 | ||||
| use glam::Mat3; | ||||
| 
 | ||||
| use super::{BoundingVolume, IntersectsVolume}; | ||||
| use crate::prelude::{Quat, Vec3}; | ||||
| 
 | ||||
| @ -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. | ||||
|     } | ||||
| 
 | ||||
| @ -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 { | ||||
| @ -170,7 +221,7 @@ mod aabb3d_tests { | ||||
|     use super::Aabb3d; | ||||
|     use crate::{ | ||||
|         bounding::{BoundingSphere, BoundingVolume, IntersectsVolume}, | ||||
|         Vec3, | ||||
|         Quat, Vec3, | ||||
|     }; | ||||
| 
 | ||||
|     #[test] | ||||
| @ -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 { | ||||
| @ -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 | ||||
|     } | ||||
| 
 | ||||
| @ -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 { | ||||
| @ -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] | ||||
| @ -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); | ||||
|  | ||||
| @ -10,16 +10,20 @@ | ||||
| /// overlapping elements or finding intersections.
 | ||||
| ///
 | ||||
| /// This trait supports both 2D and 3D bounding shapes.
 | ||||
| pub trait BoundingVolume { | ||||
| pub trait BoundingVolume: Sized { | ||||
|     /// The position type used for the volume. This should be `Vec2` for 2D and `Vec3` for 3D.
 | ||||
|     type Position: Clone + Copy + PartialEq; | ||||
|     type Translation: Clone + Copy + PartialEq; | ||||
| 
 | ||||
|     /// The rotation type used for the volume. This should be `f32` for 2D and `Quat` for 3D.
 | ||||
|     type Rotation: Clone + Copy + PartialEq; | ||||
| 
 | ||||
|     /// The type used for the size of the bounding volume. Usually a half size. For example an
 | ||||
|     /// `f32` radius for a circle, or a `Vec3` with half sizes for x, y and z for a 3D axis-aligned
 | ||||
|     /// bounding box
 | ||||
|     type HalfSize; | ||||
| 
 | ||||
|     /// Returns the center of the bounding volume.
 | ||||
|     fn center(&self) -> Self::Position; | ||||
|     fn center(&self) -> Self::Translation; | ||||
| 
 | ||||
|     /// Returns the half size of the bounding volume.
 | ||||
|     fn half_size(&self) -> Self::HalfSize; | ||||
| @ -38,11 +42,47 @@ pub trait BoundingVolume { | ||||
|     /// Computes the smallest bounding volume that contains both `self` and `other`.
 | ||||
|     fn merge(&self, other: &Self) -> Self; | ||||
| 
 | ||||
|     /// Increase the size of the bounding volume in each direction by the given amount
 | ||||
|     /// Increases the size of the bounding volume in each direction by the given amount.
 | ||||
|     fn grow(&self, amount: Self::HalfSize) -> Self; | ||||
| 
 | ||||
|     /// Decrease the size of the bounding volume in each direction by the given amount
 | ||||
|     /// Decreases the size of the bounding volume in each direction by the given amount.
 | ||||
|     fn shrink(&self, amount: Self::HalfSize) -> Self; | ||||
| 
 | ||||
|     /// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
 | ||||
|     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.
 | ||||
|     fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) { | ||||
|         self.rotate_by(rotation); | ||||
|         self.translate_by(translation); | ||||
|     } | ||||
| 
 | ||||
|     /// Translates the bounding volume by the given translation.
 | ||||
|     fn translated_by(mut self, translation: Self::Translation) -> Self { | ||||
|         self.translate_by(translation); | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     /// Translates the bounding volume by the given translation.
 | ||||
|     fn translate_by(&mut self, translation: Self::Translation); | ||||
| 
 | ||||
|     /// Rotates the bounding volume around the origin by the given rotation.
 | ||||
|     ///
 | ||||
|     /// The result is a combination of the original volume and the rotated volume,
 | ||||
|     /// so it is guaranteed to be either the same size or larger than the original.
 | ||||
|     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 a combination of the original volume and the rotated volume,
 | ||||
|     /// so it is guaranteed to be either the same size or larger than the original.
 | ||||
|     fn rotate_by(&mut self, rotation: Self::Rotation); | ||||
| } | ||||
| 
 | ||||
| /// A trait that generalizes intersection tests against a volume.
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Joona Aalto
						Joona Aalto