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 {
|
impl BoundingVolume for Aabb2d {
|
||||||
type Position = Vec2;
|
type Translation = Vec2;
|
||||||
|
type Rotation = f32;
|
||||||
type HalfSize = Vec2;
|
type HalfSize = Vec2;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn center(&self) -> Self::Position {
|
fn center(&self) -> Self::Translation {
|
||||||
(self.min + self.max) / 2.
|
(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);
|
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
|
||||||
b
|
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 {
|
impl IntersectsVolume<Self> for Aabb2d {
|
||||||
@ -277,6 +338,24 @@ mod aabb2d_tests {
|
|||||||
assert!(!shrunk.contains(&a));
|
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]
|
#[test]
|
||||||
fn closest_point() {
|
fn closest_point() {
|
||||||
let aabb = Aabb2d {
|
let aabb = Aabb2d {
|
||||||
@ -396,11 +475,12 @@ impl BoundingCircle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BoundingVolume for BoundingCircle {
|
impl BoundingVolume for BoundingCircle {
|
||||||
type Position = Vec2;
|
type Translation = Vec2;
|
||||||
|
type Rotation = f32;
|
||||||
type HalfSize = f32;
|
type HalfSize = f32;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn center(&self) -> Self::Position {
|
fn center(&self) -> Self::Translation {
|
||||||
self.center
|
self.center
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,6 +529,16 @@ impl BoundingVolume for BoundingCircle {
|
|||||||
debug_assert!(self.radius() >= amount);
|
debug_assert!(self.radius() >= amount);
|
||||||
Self::new(self.center, 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 {
|
impl IntersectsVolume<Self> for BoundingCircle {
|
||||||
@ -551,6 +641,17 @@ mod bounding_circle_tests {
|
|||||||
assert!(!shrunk.contains(&a));
|
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]
|
#[test]
|
||||||
fn closest_point() {
|
fn closest_point() {
|
||||||
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
|
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
mod primitive_impls;
|
mod primitive_impls;
|
||||||
|
|
||||||
|
use glam::Mat3;
|
||||||
|
|
||||||
use super::{BoundingVolume, IntersectsVolume};
|
use super::{BoundingVolume, IntersectsVolume};
|
||||||
use crate::prelude::{Quat, Vec3};
|
use crate::prelude::{Quat, Vec3};
|
||||||
|
|
||||||
@ -87,11 +89,12 @@ impl Aabb3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BoundingVolume for Aabb3d {
|
impl BoundingVolume for Aabb3d {
|
||||||
type Position = Vec3;
|
type Translation = Vec3;
|
||||||
|
type Rotation = Quat;
|
||||||
type HalfSize = Vec3;
|
type HalfSize = Vec3;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn center(&self) -> Self::Position {
|
fn center(&self) -> Self::Translation {
|
||||||
(self.min + self.max) / 2.
|
(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);
|
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z);
|
||||||
b
|
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 {
|
impl IntersectsVolume<Self> for Aabb3d {
|
||||||
@ -170,7 +221,7 @@ mod aabb3d_tests {
|
|||||||
use super::Aabb3d;
|
use super::Aabb3d;
|
||||||
use crate::{
|
use crate::{
|
||||||
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
|
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
|
||||||
Vec3,
|
Quat, Vec3,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -273,6 +324,27 @@ mod aabb3d_tests {
|
|||||||
assert!(!shrunk.contains(&a));
|
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]
|
#[test]
|
||||||
fn closest_point() {
|
fn closest_point() {
|
||||||
let aabb = Aabb3d {
|
let aabb = Aabb3d {
|
||||||
@ -388,11 +460,12 @@ impl BoundingSphere {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BoundingVolume for BoundingSphere {
|
impl BoundingVolume for BoundingSphere {
|
||||||
type Position = Vec3;
|
type Translation = Vec3;
|
||||||
|
type Rotation = Quat;
|
||||||
type HalfSize = f32;
|
type HalfSize = f32;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn center(&self) -> Self::Position {
|
fn center(&self) -> Self::Translation {
|
||||||
self.center
|
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 {
|
impl IntersectsVolume<Self> for BoundingSphere {
|
||||||
@ -471,10 +554,12 @@ impl IntersectsVolume<Aabb3d> for BoundingSphere {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod bounding_sphere_tests {
|
mod bounding_sphere_tests {
|
||||||
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
use super::BoundingSphere;
|
use super::BoundingSphere;
|
||||||
use crate::{
|
use crate::{
|
||||||
bounding::{BoundingVolume, IntersectsVolume},
|
bounding::{BoundingVolume, IntersectsVolume},
|
||||||
Vec3,
|
Quat, Vec3,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -553,6 +638,20 @@ mod bounding_sphere_tests {
|
|||||||
assert!(!shrunk.contains(&a));
|
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]
|
#[test]
|
||||||
fn closest_point() {
|
fn closest_point() {
|
||||||
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
|
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
|
||||||
|
|||||||
@ -10,16 +10,20 @@
|
|||||||
/// overlapping elements or finding intersections.
|
/// overlapping elements or finding intersections.
|
||||||
///
|
///
|
||||||
/// This trait supports both 2D and 3D bounding shapes.
|
/// 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.
|
/// 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
|
/// 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
|
/// `f32` radius for a circle, or a `Vec3` with half sizes for x, y and z for a 3D axis-aligned
|
||||||
/// bounding box
|
/// bounding box
|
||||||
type HalfSize;
|
type HalfSize;
|
||||||
|
|
||||||
/// Returns the center of the bounding volume.
|
/// 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.
|
/// Returns the half size of the bounding volume.
|
||||||
fn half_size(&self) -> Self::HalfSize;
|
fn half_size(&self) -> Self::HalfSize;
|
||||||
@ -38,11 +42,47 @@ pub trait BoundingVolume {
|
|||||||
/// Computes the smallest bounding volume that contains both `self` and `other`.
|
/// Computes the smallest bounding volume that contains both `self` and `other`.
|
||||||
fn merge(&self, other: &Self) -> Self;
|
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;
|
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;
|
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.
|
/// A trait that generalizes intersection tests against a volume.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user