Refactor Bounded2d/Bounded3d to use isometries (#14485)

# Objective

Previously, this area of bevy_math used raw translation and rotations to
encode isometries, which did not exist earlier. The goal of this PR is
to make the codebase of bevy_math more harmonious by using actual
isometries (`Isometry2d`/`Isometry3d`) in these places instead — this
will hopefully make the interfaces more digestible for end-users, in
addition to facilitating conversions.

For instance, together with the addition of #14478, this means that a
bounding box for a collider with an isometric `Transform` can be
computed as
```rust
collider.aabb_3d(collider_transform.to_isometry())
```
instead of using manual destructuring. 

## Solution

- The traits `Bounded2d` and `Bounded3d` now use `Isometry2d` and
`Isometry3d` (respectively) instead of `translation` and `rotation`
parameters; e.g.:
  ```rust
  /// A trait with methods that return 3D bounding volumes for a shape.
  pub trait Bounded3d {
/// Get an axis-aligned bounding box for the shape translated and
rotated by the given isometry.
      fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d;
/// Get a bounding sphere for the shape translated and rotated by the
given isometry.
      fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere;
  }
  ```
- Similarly, the `from_point_cloud` constructors for axis-aligned
bounding boxes and bounding circles/spheres now take isometries instead
of separate `translation` and `rotation`; e.g.:
  ```rust
/// Computes the smallest [`Aabb3d`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
    ///
    /// # Panics
    ///
    /// Panics if the given set of points is empty.
    #[inline(always)]
    pub fn from_point_cloud(
        isometry: Isometry3d,
        points: impl Iterator<Item = impl Into<Vec3A>>,
    ) -> Aabb3d { //... }
  ```

This has a couple additional results:
1. The end-user no longer interacts directly with `Into<Vec3A>` or
`Into<Rot2>` parameters; these conversions all happen earlier now,
inside the isometry types.
2. Similarly, almost all intermediate `Vec3 -> Vec3A` conversions have
been eliminated from the `Bounded3d` implementations for primitives.
This probably has some performance benefit, but I have not measured it
as of now.

## Testing

Existing unit tests help ensure that nothing has been broken in the
refactor.

---

## Migration Guide

The `Bounded2d` and `Bounded3d` traits now take `Isometry2d` and
`Isometry3d` parameters (respectively) instead of separate translation
and rotation arguments. Existing calls to `aabb_2d`, `bounding_circle`,
`aabb_3d`, and `bounding_sphere` will have to be changed to use
isometries instead. A straightforward conversion is to refactor just by
calling `Isometry2d/3d::new`, as follows:
```rust
// Old:
let aabb = my_shape.aabb_2d(my_translation, my_rotation);

// New:
let aabb = my_shape.aabb_2d(Isometry2d::new(my_translation, my_rotation));
```

However, if the old translation and rotation are 3d
translation/rotations originating from a `Transform` or
`GlobalTransform`, then `to_isometry` may be used instead. For example:
```rust
// Old:
let bounding_sphere = my_shape.bounding_sphere(shape_transform.translation, shape_transform.rotation);

// New:
let bounding_sphere = my_shape.bounding_sphere(shape_transform.to_isometry());
```

This discussion also applies to the `from_point_cloud` construction
method of `Aabb2d`/`BoundingCircle`/`Aabb3d`/`BoundingSphere`, which has
similarly been altered to use isometries.
This commit is contained in:
Matty 2024-07-29 19:37:02 -04:00 committed by GitHub
parent 7573b3c765
commit 601cf6b9e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 491 additions and 474 deletions

View file

@ -1,7 +1,10 @@
mod primitive_impls;
use super::{BoundingVolume, IntersectsVolume};
use crate::prelude::{Mat2, Rot2, Vec2};
use crate::{
prelude::{Mat2, Rot2, Vec2},
Isometry2d,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
@ -18,14 +21,12 @@ fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
points.iter().fold(Vec2::ZERO, |acc, point| acc + *point) * denom
}
/// A trait with methods that return 2D bounded volumes for a shape
/// A trait with methods that return 2D bounding 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, counterclockwise, with 0 meaning no rotation.
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d;
/// Get a bounding circle for the shape
/// The rotation is in radians, counterclockwise, with 0 meaning no rotation.
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle;
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d;
/// Get a bounding circle for the shape translated and rotated by the given isometry.
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle;
}
/// A 2D axis-aligned bounding box, or bounding rectangle
@ -51,20 +52,15 @@ impl Aabb2d {
}
/// Computes the smallest [`Aabb2d`] containing the given set of points,
/// transformed by `translation` and `rotation`.
/// transformed by the rotation and translation of the given isometry.
///
/// # Panics
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(
translation: Vec2,
rotation: impl Into<Rot2>,
points: &[Vec2],
) -> Aabb2d {
pub fn from_point_cloud(isometry: Isometry2d, points: &[Vec2]) -> Aabb2d {
// Transform all points by rotation
let rotation: Rot2 = rotation.into();
let mut iter = points.iter().map(|point| rotation * *point);
let mut iter = points.iter().map(|point| isometry.rotation * *point);
let first = iter
.next()
@ -75,8 +71,8 @@ impl Aabb2d {
});
Aabb2d {
min: min + translation,
max: max + translation,
min: min + isometry.translation,
max: max + isometry.translation,
}
}
@ -472,16 +468,11 @@ impl BoundingCircle {
}
/// Computes a [`BoundingCircle`] containing the given set of points,
/// transformed by `translation` and `rotation`.
/// transformed by the rotation and translation of the given isometry.
///
/// The bounding circle is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(
translation: Vec2,
rotation: impl Into<Rot2>,
points: &[Vec2],
) -> BoundingCircle {
let rotation: Rot2 = rotation.into();
pub fn from_point_cloud(isometry: Isometry2d, points: &[Vec2]) -> BoundingCircle {
let center = point_cloud_2d_center(points);
let mut radius_squared = 0.0;
@ -493,7 +484,7 @@ impl BoundingCircle {
}
}
BoundingCircle::new(rotation * center + translation, radius_squared.sqrt())
BoundingCircle::new(isometry * center, radius_squared.sqrt())
}
/// Get the radius of the bounding circle

View file

@ -6,7 +6,7 @@ use crate::{
CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon,
Rhombus, Segment2d, Triangle2d,
},
Dir2, Mat2, Rot2, Vec2,
Dir2, Isometry2d, Mat2, Rot2, Vec2,
};
use std::f32::consts::{FRAC_PI_2, PI, TAU};
@ -15,12 +15,12 @@ use smallvec::SmallVec;
use super::{Aabb2d, Bounded2d, BoundingCircle};
impl Bounded2d for Circle {
fn aabb_2d(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::new(translation, Vec2::splat(self.radius))
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
Aabb2d::new(isometry.translation, Vec2::splat(self.radius))
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.radius)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, self.radius)
}
}
@ -57,49 +57,52 @@ fn arc_bounding_points(arc: Arc2d, rotation: impl Into<Rot2>) -> SmallVec<[Vec2;
}
impl Bounded2d for Arc2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
// If our arc covers more than a circle, just return the bounding box of the circle.
if self.half_angle >= PI {
return Circle::new(self.radius).aabb_2d(translation, rotation);
return Circle::new(self.radius).aabb_2d(isometry);
}
Aabb2d::from_point_cloud(translation, 0.0, &arc_bounding_points(*self, rotation))
Aabb2d::from_point_cloud(
Isometry2d::from_translation(isometry.translation),
&arc_bounding_points(*self, isometry.rotation),
)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
// There are two possibilities for the bounding circle.
if self.is_major() {
// If the arc is major, then the widest distance between two points is a diameter of the arc's circle;
// therefore, that circle is the bounding radius.
BoundingCircle::new(translation, self.radius)
BoundingCircle::new(isometry.translation, self.radius)
} else {
// Otherwise, the widest distance between two points is the chord,
// so a circle of that diameter around the midpoint will contain the entire arc.
let center = rotation.into() * self.chord_midpoint();
BoundingCircle::new(center + translation, self.half_chord_length())
let center = isometry.rotation * self.chord_midpoint();
BoundingCircle::new(center + isometry.translation, self.half_chord_length())
}
}
}
impl Bounded2d for CircularSector {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
// If our sector covers more than a circle, just return the bounding box of the circle.
if self.half_angle() >= PI {
return Circle::new(self.radius()).aabb_2d(translation, rotation);
return Circle::new(self.radius()).aabb_2d(isometry);
}
// Otherwise, we use the same logic as for Arc2d, above, just with the circle's center as an additional possibility.
let mut bounds = arc_bounding_points(self.arc, rotation);
let mut bounds = arc_bounding_points(self.arc, isometry.rotation);
bounds.push(Vec2::ZERO);
Aabb2d::from_point_cloud(translation, 0.0, &bounds)
Aabb2d::from_point_cloud(Isometry2d::from_translation(isometry.translation), &bounds)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
if self.arc.is_major() {
// If the arc is major, that is, greater than a semicircle,
// then bounding circle is just the circle defining the sector.
BoundingCircle::new(translation, self.arc.radius)
BoundingCircle::new(isometry.translation, self.arc.radius)
} else {
// However, when the arc is minor,
// we need our bounding circle to include both endpoints of the arc as well as the circle center.
@ -111,25 +114,23 @@ impl Bounded2d for CircularSector {
self.arc.left_endpoint(),
self.arc.right_endpoint(),
)
.bounding_circle(translation, rotation)
.bounding_circle(isometry)
}
}
}
impl Bounded2d for CircularSegment {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
self.arc.aabb_2d(translation, rotation)
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
self.arc.aabb_2d(isometry)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
self.arc.bounding_circle(translation, rotation)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
self.arc.bounding_circle(isometry)
}
}
impl Bounded2d for Ellipse {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
// V = (hh * cos(beta), hh * sin(beta))
// #####*#####
// ### | ###
@ -142,7 +143,7 @@ impl Bounded2d for Ellipse {
let (hw, hh) = (self.half_size.x, self.half_size.y);
// Sine and cosine of rotation angle alpha.
let (alpha_sin, alpha_cos) = rotation.sin_cos();
let (alpha_sin, alpha_cos) = isometry.rotation.sin_cos();
// Sine and cosine of alpha + pi/2. We can avoid the trigonometric functions:
// sin(beta) = sin(alpha + pi/2) = cos(alpha)
@ -155,51 +156,48 @@ impl Bounded2d for Ellipse {
let half_size = Vec2::new(ux.hypot(vx), uy.hypot(vy));
Aabb2d::new(translation, half_size)
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.semi_major())
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, self.semi_major())
}
}
impl Bounded2d for Annulus {
fn aabb_2d(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::new(translation, Vec2::splat(self.outer_circle.radius))
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
Aabb2d::new(isometry.translation, Vec2::splat(self.outer_circle.radius))
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.outer_circle.radius)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, self.outer_circle.radius)
}
}
impl Bounded2d for Rhombus {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation_mat = rotation.into();
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
let [rotated_x_half_diagonal, rotated_y_half_diagonal] = [
rotation_mat * Vec2::new(self.half_diagonals.x, 0.0),
rotation_mat * Vec2::new(0.0, self.half_diagonals.y),
isometry.rotation * Vec2::new(self.half_diagonals.x, 0.0),
isometry.rotation * Vec2::new(0.0, self.half_diagonals.y),
];
let aabb_half_extent = rotated_x_half_diagonal
.abs()
.max(rotated_y_half_diagonal.abs());
Aabb2d {
min: -aabb_half_extent + translation,
max: aabb_half_extent + translation,
min: -aabb_half_extent + isometry.translation,
max: aabb_half_extent + isometry.translation,
}
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.circumradius())
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, self.circumradius())
}
}
impl Bounded2d for Plane2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let normal = rotation * *self.normal;
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
let normal = isometry.rotation * *self.normal;
let facing_x = normal == Vec2::X || normal == Vec2::NEG_X;
let facing_y = normal == Vec2::Y || normal == Vec2::NEG_Y;
@ -209,18 +207,17 @@ impl Bounded2d for Plane2d {
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
let half_size = Vec2::new(half_width, half_height);
Aabb2d::new(translation, half_size)
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, f32::MAX / 2.0)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded2d for Line2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let direction = rotation * *self.direction;
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
let direction = isometry.rotation * *self.direction;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
// like growing or shrinking the AABB without breaking things.
@ -229,65 +226,62 @@ impl Bounded2d for Line2d {
let half_height = if direction.y == 0.0 { 0.0 } else { max };
let half_size = Vec2::new(half_width, half_height);
Aabb2d::new(translation, half_size)
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, f32::MAX / 2.0)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded2d for Segment2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
// Rotate the segment by `rotation`
let rotation: Rot2 = rotation.into();
let direction = rotation * *self.direction;
let direction = isometry.rotation * *self.direction;
let half_size = (self.half_length * direction).abs();
Aabb2d::new(translation, half_size)
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.half_length)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, self.half_length)
}
}
impl<const N: usize> Bounded2d for Polyline2d<N> {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for BoxedPolyline2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for Triangle2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
let [a, b, c] = self.vertices.map(|vtx| rotation * vtx);
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
let [a, b, c] = self.vertices.map(|vtx| isometry.rotation * vtx);
let min = Vec2::new(a.x.min(b.x).min(c.x), a.y.min(b.y).min(c.y));
let max = Vec2::new(a.x.max(b.x).max(c.x), a.y.max(b.y).max(c.y));
Aabb2d {
min: min + translation,
max: max + translation,
min: min + isometry.translation,
max: max + isometry.translation,
}
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
let rotation: Rot2 = rotation.into();
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
let [a, b, c] = self.vertices;
// The points of the segment opposite to the obtuse or right angle if one exists
@ -308,85 +302,79 @@ impl Bounded2d for Triangle2d {
// The triangle is obtuse or right, so the minimum bounding circle's diameter is equal to the longest side.
// We can compute the minimum bounding circle from the line segment of the longest side.
let (segment, center) = Segment2d::from_points(point1, point2);
segment.bounding_circle(rotation * center + translation, rotation)
segment.bounding_circle(isometry * Isometry2d::from_translation(center))
} else {
// The triangle is acute, so the smallest bounding circle is the circumcircle.
let (Circle { radius }, circumcenter) = self.circumcircle();
BoundingCircle::new(rotation * circumcenter + translation, radius)
BoundingCircle::new(isometry * circumcenter, radius)
}
}
}
impl Bounded2d for Rectangle {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
// Compute the AABB of the rotated rectangle by transforming the half-extents
// by an absolute rotation matrix.
let (sin, cos) = rotation.sin_cos();
let (sin, cos) = isometry.rotation.sin_cos();
let abs_rot_mat = Mat2::from_cols_array(&[cos.abs(), sin.abs(), sin.abs(), cos.abs()]);
let half_size = abs_rot_mat * self.half_size;
Aabb2d::new(translation, half_size)
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
let radius = self.half_size.length();
BoundingCircle::new(translation, radius)
BoundingCircle::new(isometry.translation, radius)
}
}
impl<const N: usize> Bounded2d for Polygon<N> {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for BoxedPolygon {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for RegularPolygon {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
let mut min = Vec2::ZERO;
let mut max = Vec2::ZERO;
for vertex in self.vertices(rotation.as_radians()) {
for vertex in self.vertices(isometry.rotation.as_radians()) {
min = min.min(vertex);
max = max.max(vertex);
}
Aabb2d {
min: min + translation,
max: max + translation,
min: min + isometry.translation,
max: max + isometry.translation,
}
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.circumcircle.radius)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, self.circumcircle.radius)
}
}
impl Bounded2d for Capsule2d {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation: Rot2 = rotation.into();
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
// Get the line segment between the hemicircles of the rotated capsule
let segment = Segment2d {
// Multiplying a normalized vector (Vec2::Y) with a rotation returns a normalized vector.
direction: rotation * Dir2::Y,
direction: isometry.rotation * Dir2::Y,
half_length: self.half_length,
};
let (a, b) = (segment.point1(), segment.point2());
@ -396,13 +384,13 @@ impl Bounded2d for Capsule2d {
let max = a.max(b) + Vec2::splat(self.radius);
Aabb2d {
min: min + translation,
max: max + translation,
min: min + isometry.translation,
max: max + isometry.translation,
}
}
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
BoundingCircle::new(translation, self.radius + self.half_length)
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
BoundingCircle::new(isometry.translation, self.radius + self.half_length)
}
}
@ -420,19 +408,20 @@ mod tests {
Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon, Rhombus, Segment2d,
Triangle2d,
},
Dir2,
Dir2, Isometry2d, Rot2,
};
#[test]
fn circle() {
let circle = Circle { radius: 1.0 };
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = circle.aabb_2d(translation, 0.0);
let aabb = circle.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
let bounding_circle = circle.bounding_circle(translation, 0.0);
let bounding_circle = circle.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
}
@ -451,6 +440,12 @@ mod tests {
bounding_circle_radius: f32,
}
impl TestCase {
fn isometry(&self) -> Isometry2d {
Isometry2d::new(self.translation, self.rotation.into())
}
}
// The apothem of an arc covering 1/6th of a circle.
let apothem = f32::sqrt(3.0) / 2.0;
let tests = [
@ -565,17 +560,17 @@ mod tests {
println!("subtest case: {}", test.name);
let segment: CircularSegment = test.arc.into();
let arc_aabb = test.arc.aabb_2d(test.translation, test.rotation);
let arc_aabb = test.arc.aabb_2d(test.isometry());
assert_abs_diff_eq!(test.aabb_min, arc_aabb.min);
assert_abs_diff_eq!(test.aabb_max, arc_aabb.max);
let segment_aabb = segment.aabb_2d(test.translation, test.rotation);
let segment_aabb = segment.aabb_2d(test.isometry());
assert_abs_diff_eq!(test.aabb_min, segment_aabb.min);
assert_abs_diff_eq!(test.aabb_max, segment_aabb.max);
let arc_bounding_circle = test.arc.bounding_circle(test.translation, test.rotation);
let arc_bounding_circle = test.arc.bounding_circle(test.isometry());
assert_abs_diff_eq!(test.bounding_circle_center, arc_bounding_circle.center);
assert_abs_diff_eq!(test.bounding_circle_radius, arc_bounding_circle.radius());
let segment_bounding_circle = segment.bounding_circle(test.translation, test.rotation);
let segment_bounding_circle = segment.bounding_circle(test.isometry());
assert_abs_diff_eq!(test.bounding_circle_center, segment_bounding_circle.center);
assert_abs_diff_eq!(
test.bounding_circle_radius,
@ -597,6 +592,12 @@ mod tests {
bounding_circle_radius: f32,
}
impl TestCase {
fn isometry(&self) -> Isometry2d {
Isometry2d::new(self.translation, self.rotation.into())
}
}
// The apothem of an arc covering 1/6th of a circle.
let apothem = f32::sqrt(3.0) / 2.0;
let inv_sqrt_3 = f32::sqrt(3.0).recip();
@ -715,11 +716,11 @@ mod tests {
println!("subtest case: {}", test.name);
let sector: CircularSector = test.arc.into();
let aabb = sector.aabb_2d(test.translation, test.rotation);
let aabb = sector.aabb_2d(test.isometry());
assert_abs_diff_eq!(test.aabb_min, aabb.min);
assert_abs_diff_eq!(test.aabb_max, aabb.max);
let bounding_circle = sector.bounding_circle(test.translation, test.rotation);
let bounding_circle = sector.bounding_circle(test.isometry());
assert_abs_diff_eq!(test.bounding_circle_center, bounding_circle.center);
assert_abs_diff_eq!(test.bounding_circle_radius, bounding_circle.radius());
}
@ -729,12 +730,13 @@ mod tests {
fn ellipse() {
let ellipse = Ellipse::new(1.0, 0.5);
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = ellipse.aabb_2d(translation, 0.0);
let aabb = ellipse.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.0, 0.5));
assert_eq!(aabb.max, Vec2::new(3.0, 1.5));
let bounding_circle = ellipse.bounding_circle(translation, 0.0);
let bounding_circle = ellipse.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
}
@ -743,12 +745,14 @@ mod tests {
fn annulus() {
let annulus = Annulus::new(1.0, 2.0);
let translation = Vec2::new(2.0, 1.0);
let rotation = Rot2::radians(1.0);
let isometry = Isometry2d::new(translation, rotation);
let aabb = annulus.aabb_2d(translation, 1.0);
let aabb = annulus.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(0.0, -1.0));
assert_eq!(aabb.max, Vec2::new(4.0, 3.0));
let bounding_circle = annulus.bounding_circle(translation, 1.0);
let bounding_circle = annulus.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 2.0);
}
@ -757,23 +761,26 @@ mod tests {
fn rhombus() {
let rhombus = Rhombus::new(2.0, 1.0);
let translation = Vec2::new(2.0, 1.0);
let rotation = Rot2::radians(std::f32::consts::FRAC_PI_4);
let isometry = Isometry2d::new(translation, rotation);
let aabb = rhombus.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
let aabb = rhombus.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.2928932, 0.29289323));
assert_eq!(aabb.max, Vec2::new(2.7071068, 1.7071068));
let bounding_circle = rhombus.bounding_circle(translation, std::f32::consts::FRAC_PI_4);
let bounding_circle = rhombus.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
let rhombus = Rhombus::new(0.0, 0.0);
let translation = Vec2::new(0.0, 0.0);
let isometry = Isometry2d::new(translation, rotation);
let aabb = rhombus.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
let aabb = rhombus.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(0.0, 0.0));
assert_eq!(aabb.max, Vec2::new(0.0, 0.0));
let bounding_circle = rhombus.bounding_circle(translation, std::f32::consts::FRAC_PI_4);
let bounding_circle = rhombus.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 0.0);
}
@ -781,20 +788,21 @@ mod tests {
#[test]
fn plane() {
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb1 = Plane2d::new(Vec2::X).aabb_2d(translation, 0.0);
let aabb1 = Plane2d::new(Vec2::X).aabb_2d(isometry);
assert_eq!(aabb1.min, Vec2::new(2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec2::new(2.0, f32::MAX / 2.0));
let aabb2 = Plane2d::new(Vec2::Y).aabb_2d(translation, 0.0);
let aabb2 = Plane2d::new(Vec2::Y).aabb_2d(isometry);
assert_eq!(aabb2.min, Vec2::new(-f32::MAX / 2.0, 1.0));
assert_eq!(aabb2.max, Vec2::new(f32::MAX / 2.0, 1.0));
let aabb3 = Plane2d::new(Vec2::ONE).aabb_2d(translation, 0.0);
let aabb3 = Plane2d::new(Vec2::ONE).aabb_2d(isometry);
assert_eq!(aabb3.min, Vec2::new(-f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec2::new(f32::MAX / 2.0, f32::MAX / 2.0));
let bounding_circle = Plane2d::new(Vec2::Y).bounding_circle(translation, 0.0);
let bounding_circle = Plane2d::new(Vec2::Y).bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), f32::MAX / 2.0);
}
@ -802,23 +810,24 @@ mod tests {
#[test]
fn line() {
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb1 = Line2d { direction: Dir2::Y }.aabb_2d(translation, 0.0);
let aabb1 = Line2d { direction: Dir2::Y }.aabb_2d(isometry);
assert_eq!(aabb1.min, Vec2::new(2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec2::new(2.0, f32::MAX / 2.0));
let aabb2 = Line2d { direction: Dir2::X }.aabb_2d(translation, 0.0);
let aabb2 = Line2d { direction: Dir2::X }.aabb_2d(isometry);
assert_eq!(aabb2.min, Vec2::new(-f32::MAX / 2.0, 1.0));
assert_eq!(aabb2.max, Vec2::new(f32::MAX / 2.0, 1.0));
let aabb3 = Line2d {
direction: Dir2::from_xy(1.0, 1.0).unwrap(),
}
.aabb_2d(translation, 0.0);
.aabb_2d(isometry);
assert_eq!(aabb3.min, Vec2::new(-f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec2::new(f32::MAX / 2.0, f32::MAX / 2.0));
let bounding_circle = Line2d { direction: Dir2::Y }.bounding_circle(translation, 0.0);
let bounding_circle = Line2d { direction: Dir2::Y }.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), f32::MAX / 2.0);
}
@ -826,13 +835,14 @@ mod tests {
#[test]
fn segment() {
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let segment = Segment2d::from_points(Vec2::new(-1.0, -0.5), Vec2::new(1.0, 0.5)).0;
let aabb = segment.aabb_2d(translation, 0.0);
let aabb = segment.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.0, 0.5));
assert_eq!(aabb.max, Vec2::new(3.0, 1.5));
let bounding_circle = segment.bounding_circle(translation, 0.0);
let bounding_circle = segment.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0_f32.hypot(0.5));
}
@ -846,12 +856,13 @@ mod tests {
Vec2::new(1.0, -1.0),
]);
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = polyline.aabb_2d(translation, 0.0);
let aabb = polyline.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
let bounding_circle = polyline.bounding_circle(translation, 0.0);
let bounding_circle = polyline.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), std::f32::consts::SQRT_2);
}
@ -861,14 +872,15 @@ mod tests {
let acute_triangle =
Triangle2d::new(Vec2::new(0.0, 1.0), Vec2::NEG_ONE, Vec2::new(1.0, -1.0));
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = acute_triangle.aabb_2d(translation, 0.0);
let aabb = acute_triangle.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
// For acute triangles, the center is the circumcenter
let (Circle { radius }, circumcenter) = acute_triangle.circumcircle();
let bounding_circle = acute_triangle.bounding_circle(translation, 0.0);
let bounding_circle = acute_triangle.bounding_circle(isometry);
assert_eq!(bounding_circle.center, circumcenter + translation);
assert_eq!(bounding_circle.radius(), radius);
}
@ -881,13 +893,14 @@ mod tests {
Vec2::new(10.0, -1.0),
);
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = obtuse_triangle.aabb_2d(translation, 0.0);
let aabb = obtuse_triangle.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(-8.0, 0.0));
assert_eq!(aabb.max, Vec2::new(12.0, 2.0));
// For obtuse and right triangles, the center is the midpoint of the longest side (diameter of bounding circle)
let bounding_circle = obtuse_triangle.bounding_circle(translation, 0.0);
let bounding_circle = obtuse_triangle.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation - Vec2::Y);
assert_eq!(bounding_circle.radius(), 10.0);
}
@ -897,12 +910,15 @@ mod tests {
let rectangle = Rectangle::new(2.0, 1.0);
let translation = Vec2::new(2.0, 1.0);
let aabb = rectangle.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
let aabb = rectangle.aabb_2d(Isometry2d::new(
translation,
Rot2::radians(std::f32::consts::FRAC_PI_4),
));
let expected_half_size = Vec2::splat(1.0606601);
assert_eq!(aabb.min, translation - expected_half_size);
assert_eq!(aabb.max, translation + expected_half_size);
let bounding_circle = rectangle.bounding_circle(translation, 0.0);
let bounding_circle = rectangle.bounding_circle(Isometry2d::from_translation(translation));
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0_f32.hypot(0.5));
}
@ -916,12 +932,13 @@ mod tests {
Vec2::new(1.0, -1.0),
]);
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = polygon.aabb_2d(translation, 0.0);
let aabb = polygon.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
let bounding_circle = polygon.bounding_circle(translation, 0.0);
let bounding_circle = polygon.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), std::f32::consts::SQRT_2);
}
@ -930,12 +947,13 @@ mod tests {
fn regular_polygon() {
let regular_polygon = RegularPolygon::new(1.0, 5);
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = regular_polygon.aabb_2d(translation, 0.0);
let aabb = regular_polygon.aabb_2d(isometry);
assert!((aabb.min - (translation - Vec2::new(0.9510565, 0.8090169))).length() < 1e-6);
assert!((aabb.max - (translation + Vec2::new(0.9510565, 1.0))).length() < 1e-6);
let bounding_circle = regular_polygon.bounding_circle(translation, 0.0);
let bounding_circle = regular_polygon.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.0);
}
@ -944,12 +962,13 @@ mod tests {
fn capsule() {
let capsule = Capsule2d::new(0.5, 2.0);
let translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation);
let aabb = capsule.aabb_2d(translation, 0.0);
let aabb = capsule.aabb_2d(isometry);
assert_eq!(aabb.min, translation - Vec2::new(0.5, 1.5));
assert_eq!(aabb.max, translation + Vec2::new(0.5, 1.5));
let bounding_circle = capsule.bounding_circle(translation, 0.0);
let bounding_circle = capsule.bounding_circle(isometry);
assert_eq!(bounding_circle.center, translation);
assert_eq!(bounding_circle.radius(), 1.5);
}

View file

@ -7,194 +7,176 @@ use crate::primitives::{
BoxedPolygon, BoxedPolyline2d, Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d,
Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d,
};
use crate::{Quat, Vec3};
use crate::{Isometry2d, Isometry3d, Quat, Rot2};
use crate::{bounding::Bounded2d, primitives::Circle};
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl BoundedExtrusion for Circle {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let segment_dir = rotation * Vec3::Z;
let segment_dir = isometry.rotation * Vec3A::Z;
let top = (segment_dir * half_depth).abs();
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
let half_size = self.radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
Aabb3d {
min: (translation - half_size - top).into(),
max: (translation + half_size + top).into(),
min: isometry.translation - half_size - top,
max: isometry.translation + half_size + top,
}
}
}
impl BoundedExtrusion for Ellipse {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let Vec2 { x: a, y: b } = self.half_size;
let normal = rotation * Vec3::Z;
let conjugate_rot = rotation.conjugate();
let normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
let [max_x, max_y, max_z] = Vec3::AXES.map(|axis: Vec3| {
let [max_x, max_y, max_z] = Vec3A::AXES.map(|axis| {
let Some(axis) = (conjugate_rot * axis.reject_from(normal))
.xy()
.try_normalize()
else {
return Vec3::ZERO;
return Vec3A::ZERO;
};
if axis.element_product() == 0. {
return rotation * Vec3::new(a * axis.y, b * axis.x, 0.);
return isometry.rotation * Vec3A::new(a * axis.y, b * axis.x, 0.);
}
let m = -axis.x / axis.y;
let signum = axis.signum();
let y = signum.y * b * b / (b * b + m * m * a * a).sqrt();
let x = signum.x * a * (1. - y * y / b / b).sqrt();
rotation * Vec3::new(x, y, 0.)
isometry.rotation * Vec3A::new(x, y, 0.)
});
let half_size = Vec3::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
Aabb3d::new(translation, half_size)
let half_size = Vec3A::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
Aabb3d::new(isometry.translation, half_size)
}
}
impl BoundedExtrusion for Line2d {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let dir = rotation * self.direction.extend(0.);
let half_depth = (rotation * Vec3::new(0., 0., half_depth)).abs();
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));
let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();
let max = f32::MAX / 2.;
let half_size = Vec3::new(
let half_size = Vec3A::new(
if dir.x == 0. { half_depth.x } else { max },
if dir.y == 0. { half_depth.y } else { max },
if dir.z == 0. { half_depth.z } else { max },
);
Aabb3d::new(translation, half_size)
Aabb3d::new(isometry.translation, half_size)
}
}
impl BoundedExtrusion for Segment2d {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let half_size = rotation * self.point1().extend(0.);
let depth = rotation * Vec3::new(0., 0., half_depth);
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
Aabb3d::new(translation, half_size.abs() + depth.abs())
Aabb3d::new(isometry.translation, half_size.abs() + depth.abs())
}
}
impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(
translation,
rotation,
self.vertices.map(|v| v.extend(0.)).into_iter(),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for BoxedPolyline2d {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(
translation,
rotation,
self.vertices.iter().map(|v| v.extend(0.)),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Triangle2d {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(
translation,
rotation,
self.vertices.iter().map(|v| v.extend(0.)),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Rectangle {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
Cuboid {
half_size: self.half_size.extend(half_depth),
}
.aabb_3d(translation, rotation)
.aabb_3d(isometry)
}
}
impl<const N: usize> BoundedExtrusion for Polygon<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(
translation,
rotation,
self.vertices.map(|v| v.extend(0.)).into_iter(),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for BoxedPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(
translation,
rotation,
self.vertices.iter().map(|v| v.extend(0.)),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for RegularPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud(
translation,
rotation,
isometry,
self.vertices(0.).into_iter().map(|v| v.extend(0.)),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Capsule2d {
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let aabb = Cylinder {
half_height: half_depth,
radius: self.radius,
}
.aabb_3d(Vec3::ZERO, rotation * Quat::from_rotation_x(FRAC_PI_2));
.aabb_3d(Isometry3d::from_rotation(
isometry.rotation * Quat::from_rotation_x(FRAC_PI_2),
));
let up = rotation * Vec3::new(0., self.half_length, 0.);
let half_size = Into::<Vec3>::into(aabb.max) + up.abs();
Aabb3d::new(translation, half_size)
let up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);
let half_size = aabb.max + up.abs();
Aabb3d::new(isometry.translation, half_size)
}
}
impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
self.base_shape
.extrusion_aabb_3d(self.half_depth, translation, rotation)
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)
}
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
self.base_shape
.extrusion_bounding_sphere(self.half_depth, translation, rotation)
.extrusion_bounding_sphere(self.half_depth, isometry)
}
}
@ -206,12 +188,12 @@ impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
/// `impl BoundedExtrusion for MyShape {}`
pub trait BoundedExtrusion: Primitive2d + Bounded2d {
/// Get an axis-aligned bounding box for an extrusion with this shape as a base and the given `half_depth`, transformed by the given `translation` and `rotation`.
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let cap_normal = rotation * Vec3::Z;
let conjugate_rot = rotation.conjugate();
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let cap_normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
// The `(halfsize, offset)` for each axis
let axis_values = Vec3::AXES.map(|ax| {
let axis_values = Vec3A::AXES.map(|ax| {
// This is the direction of the line of intersection of a plane with the `ax` normal and the plane containing the cap of the extrusion.
let intersect_line = ax.cross(cap_normal);
if intersect_line.length_squared() <= f32::EPSILON {
@ -228,24 +210,19 @@ pub trait BoundedExtrusion: Primitive2d + Bounded2d {
// Calculate the `Aabb2d` of the base shape. The shape is rotated so that the line of intersection is parallel to the Y axis in the `Aabb2d` calculations.
// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane
let aabb2d = self.aabb_2d(Vec2::ZERO, angle);
let aabb2d = self.aabb_2d(Isometry2d::from_rotation(Rot2::radians(angle)));
(aabb2d.half_size().x * scale, aabb2d.center().x * scale)
});
let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));
let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();
let depth = rotation * Vec3A::new(0., 0., half_depth);
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
Aabb3d::new(Vec3A::from(translation) - offset, cap_size + depth.abs())
Aabb3d::new(isometry.translation - offset, cap_size + depth.abs())
}
/// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation
fn extrusion_bounding_sphere(
&self,
half_depth: f32,
translation: Vec3,
rotation: Quat,
) -> BoundingSphere {
fn extrusion_bounding_sphere(&self, half_depth: f32, isometry: Isometry3d) -> BoundingSphere {
// We calculate the bounding circle of the base shape.
// Since each of the extrusions bases will have the same distance from its center,
// and they are just shifted along the Z-axis, the minimum bounding sphere will be the bounding sphere
@ -253,9 +230,9 @@ pub trait BoundedExtrusion: Primitive2d + Bounded2d {
let BoundingCircle {
center,
circle: Circle { radius },
} = self.bounding_circle(Vec2::ZERO, 0.);
} = self.bounding_circle(Isometry2d::IDENTITY);
let radius = radius.hypot(half_depth);
let center = translation + rotation * center.extend(0.);
let center = isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.));
BoundingSphere::new(center, radius)
}
@ -273,19 +250,20 @@ mod tests {
Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,
RegularPolygon, Segment2d, Triangle2d,
},
Dir2,
Dir2, Isometry3d,
};
#[test]
fn circle() {
let cylinder = Extrusion::new(Circle::new(0.5), 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = cylinder.aabb_3d(translation, Quat::IDENTITY);
let aabb = cylinder.aabb_3d(isometry);
assert_eq!(aabb.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));
let bounding_sphere = cylinder.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = cylinder.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1f32.hypot(0.5));
}
@ -295,12 +273,13 @@ mod tests {
let extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 8f32.sqrt());
}
@ -315,12 +294,13 @@ mod tests {
);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_y(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));
assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center(), translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.);
}
@ -330,12 +310,13 @@ mod tests {
let extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_z(std::f32::consts::FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.291288);
}
@ -345,12 +326,13 @@ mod tests {
let extrusion = Extrusion::new(Segment2d::new(Dir2::new_unchecked(Vec2::NEG_Y), 3.), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5);
}
@ -366,12 +348,13 @@ mod tests {
let extrusion = Extrusion::new(polyline, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528);
}
@ -386,12 +369,13 @@ mod tests {
let extrusion = Extrusion::new(triangle, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(
bounding_sphere.center,
Vec3A::new(3.0, 3.2928934, 4.2928934)
@ -410,12 +394,13 @@ mod tests {
let extrusion = Extrusion::new(polygon, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528);
}
@ -425,8 +410,9 @@ mod tests {
let extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(
aabb.center(),
Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)
@ -436,7 +422,7 @@ mod tests {
Vec3A::new(1.9498558, 2.7584014, 2.7584019)
);
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 8f32.sqrt());
}
@ -446,12 +432,13 @@ mod tests {
let extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735));
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5);
}

View file

@ -4,7 +4,7 @@ mod primitive_impls;
use glam::Mat3;
use super::{BoundingVolume, IntersectsVolume};
use crate::{Quat, Vec3, Vec3A};
use crate::{Isometry3d, Quat, Vec3A};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
@ -24,12 +24,12 @@ fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3
acc / len as f32
}
/// A trait with methods that return 3D bounded volumes for a shape
/// A trait with methods that return 3D bounding 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 with the given translation and rotation
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere;
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d;
/// Get a bounding sphere for the shape translated and rotated by the given isometry.
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere;
}
/// A 3D axis-aligned bounding box
@ -55,19 +55,18 @@ impl Aabb3d {
}
/// Computes the smallest [`Aabb3d`] containing the given set of points,
/// transformed by `translation` and `rotation`.
/// transformed by the rotation and translation of the given isometry.
///
/// # Panics
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(
translation: impl Into<Vec3A>,
rotation: Quat,
isometry: Isometry3d,
points: impl Iterator<Item = impl Into<Vec3A>>,
) -> Aabb3d {
// Transform all points by rotation
let mut iter = points.map(|point| rotation * point.into());
let mut iter = points.map(|point| isometry.rotation * point.into());
let first = iter
.next()
@ -77,10 +76,9 @@ impl Aabb3d {
(point.min(prev_min), point.max(prev_max))
});
let translation = translation.into();
Aabb3d {
min: min + translation,
max: max + translation,
min: min + isometry.translation,
max: max + isometry.translation,
}
}
@ -473,13 +471,12 @@ impl BoundingSphere {
}
/// Computes a [`BoundingSphere`] containing the given set of points,
/// transformed by `translation` and `rotation`.
/// transformed by the rotation and translation of the given isometry.
///
/// The bounding sphere is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(
translation: impl Into<Vec3A>,
rotation: Quat,
isometry: Isometry3d,
points: &[impl Copy + Into<Vec3A>],
) -> BoundingSphere {
let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));
@ -493,10 +490,7 @@ impl BoundingSphere {
}
}
BoundingSphere::new(
rotation * center + translation.into(),
radius_squared.sqrt(),
)
BoundingSphere::new(isometry * center, radius_squared.sqrt())
}
/// Get the radius of the bounding sphere

View file

@ -1,29 +1,31 @@
//! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives).
use glam::Vec3A;
use crate::{
bounding::{Bounded2d, BoundingCircle},
primitives::{
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d,
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, Triangle3d,
},
Dir3, Mat3, Quat, Vec2, Vec3,
Isometry2d, Isometry3d, Mat3, Vec2, Vec3,
};
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl Bounded3d for Sphere {
fn aabb_3d(&self, translation: Vec3, _rotation: Quat) -> Aabb3d {
Aabb3d::new(translation, Vec3::splat(self.radius))
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
Aabb3d::new(isometry.translation, Vec3::splat(self.radius))
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, self.radius)
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::new(isometry.translation, self.radius)
}
}
impl Bounded3d for InfinitePlane3d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
let normal = rotation * *self.normal;
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
let normal = isometry.rotation * *self.normal;
let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;
let facing_y = normal == Vec3::Y || normal == Vec3::NEG_Y;
let facing_z = normal == Vec3::Z || normal == Vec3::NEG_Z;
@ -33,19 +35,19 @@ impl Bounded3d for InfinitePlane3d {
let half_width = if facing_x { 0.0 } else { f32::MAX / 2.0 };
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
let half_depth = if facing_z { 0.0 } else { f32::MAX / 2.0 };
let half_size = Vec3::new(half_width, half_height, half_depth);
let half_size = Vec3A::new(half_width, half_height, half_depth);
Aabb3d::new(translation, half_size)
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, f32::MAX / 2.0)
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Line3d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
let direction = rotation * *self.direction;
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
let direction = isometry.rotation * *self.direction;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
// like growing or shrinking the AABB without breaking things.
@ -53,55 +55,55 @@ impl Bounded3d for Line3d {
let half_width = if direction.x == 0.0 { 0.0 } else { max };
let half_height = if direction.y == 0.0 { 0.0 } else { max };
let half_depth = if direction.z == 0.0 { 0.0 } else { max };
let half_size = Vec3::new(half_width, half_height, half_depth);
let half_size = Vec3A::new(half_width, half_height, half_depth);
Aabb3d::new(translation, half_size)
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, f32::MAX / 2.0)
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Segment3d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
// Rotate the segment by `rotation`
let direction = rotation * *self.direction;
let direction = isometry.rotation * *self.direction;
let half_size = (self.half_length * direction).abs();
Aabb3d::new(translation, half_size)
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, self.half_length)
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::new(isometry.translation, self.half_length)
}
}
impl<const N: usize> Bounded3d for Polyline3d<N> {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
Aabb3d::from_point_cloud(translation, rotation, self.vertices.iter().copied())
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
BoundingSphere::from_point_cloud(translation, rotation, &self.vertices)
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded3d for BoxedPolyline3d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
Aabb3d::from_point_cloud(translation, rotation, self.vertices.iter().copied())
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
BoundingSphere::from_point_cloud(translation, rotation, &self.vertices)
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded3d for Cuboid {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
// Compute the AABB of the rotated cuboid by transforming the half-size
// by an absolute rotation matrix.
let rot_mat = Mat3::from_quat(rotation);
let rot_mat = Mat3::from_quat(isometry.rotation);
let abs_rot_mat = Mat3::from_cols(
rot_mat.x_axis.abs(),
rot_mat.y_axis.abs(),
@ -109,80 +111,77 @@ impl Bounded3d for Cuboid {
);
let half_size = abs_rot_mat * self.half_size;
Aabb3d::new(translation, half_size)
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, self.half_size.length())
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::new(isometry.translation, self.half_size.length())
}
}
impl Bounded3d for Cylinder {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let segment_dir = rotation * Vec3::Y;
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_height;
let bottom = -top;
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
let half_size = self.radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
Aabb3d {
min: (translation + (top - half_size).min(bottom - half_size)).into(),
max: (translation + (top + half_size).max(bottom + half_size)).into(),
min: isometry.translation + (top - half_size).min(bottom - half_size),
max: isometry.translation + (top + half_size).max(bottom + half_size),
}
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
let radius = self.radius.hypot(self.half_height);
BoundingSphere::new(translation, radius)
BoundingSphere::new(isometry.translation, radius)
}
}
impl Bounded3d for Capsule3d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
// Get the line segment between the hemispheres of the rotated capsule
let segment = Segment3d {
// Multiplying a normalized vector (Vec3::Y) with a rotation returns a normalized vector.
direction: rotation * Dir3::Y,
half_length: self.half_length,
};
let (a, b) = (segment.point1(), segment.point2());
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_length;
let bottom = -top;
// Expand the line segment by the capsule radius to get the capsule half-extents
let min = a.min(b) - Vec3::splat(self.radius);
let max = a.max(b) + Vec3::splat(self.radius);
let min = bottom.min(top) - Vec3A::splat(self.radius);
let max = bottom.max(top) + Vec3A::splat(self.radius);
Aabb3d {
min: (min + translation).into(),
max: (max + translation).into(),
min: min + isometry.translation,
max: max + isometry.translation,
}
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, self.radius + self.half_length)
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::new(isometry.translation, self.radius + self.half_length)
}
}
impl Bounded3d for Cone {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let segment_dir = rotation * Vec3::Y;
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
let half_extents = Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_extents = Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
Aabb3d {
min: (translation + top.min(bottom - self.radius * half_extents)).into(),
max: (translation + top.max(bottom + self.radius * half_extents)).into(),
min: isometry.translation + top.min(bottom - self.radius * half_extents),
max: isometry.translation + top.max(bottom + self.radius * half_extents),
}
}
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
// Get the triangular cross-section of the cone.
let half_height = 0.5 * self.height;
let triangle = Triangle2d::new(
@ -193,36 +192,37 @@ impl Bounded3d for Cone {
// Because of circular symmetry, we can use the bounding circle of the triangle
// for the bounding sphere of the cone.
let BoundingCircle { circle, center } = triangle.bounding_circle(Vec2::ZERO, 0.0);
let BoundingCircle { circle, center } = triangle.bounding_circle(Isometry2d::IDENTITY);
BoundingSphere::new(rotation * center.extend(0.0) + translation, circle.radius)
BoundingSphere::new(
isometry.rotation * Vec3A::from(center.extend(0.0)) + isometry.translation,
circle.radius,
)
}
}
impl Bounded3d for ConicalFrustum {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let segment_dir = rotation * Vec3::Y;
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
let half_extents = Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_extents = Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
Aabb3d {
min: (translation
min: isometry.translation
+ (top - self.radius_top * half_extents)
.min(bottom - self.radius_bottom * half_extents))
.into(),
max: (translation
.min(bottom - self.radius_bottom * half_extents),
max: isometry.translation
+ (top + self.radius_top * half_extents)
.max(bottom + self.radius_bottom * half_extents))
.into(),
.max(bottom + self.radius_bottom * half_extents),
}
}
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
let half_height = 0.5 * self.height;
// To compute the bounding sphere, we'll get the center and radius of the circumcircle
@ -277,42 +277,45 @@ impl Bounded3d for ConicalFrustum {
(circumcenter, a.distance(circumcenter))
};
BoundingSphere::new(translation + rotation * center.extend(0.0), radius)
BoundingSphere::new(
isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.0)),
radius,
)
}
}
impl Bounded3d for Torus {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
// Compute the AABB of a flat disc with the major radius of the torus.
// Reference: http://iquilezles.org/articles/diskbbox/
let normal = rotation * Vec3::Y;
let e = (Vec3::ONE - normal * normal).max(Vec3::ZERO);
let disc_half_size = self.major_radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
let normal = isometry.rotation * Vec3A::Y;
let e = (Vec3A::ONE - normal * normal).max(Vec3A::ZERO);
let disc_half_size = self.major_radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
// Expand the disc by the minor radius to get the torus half-size
let half_size = disc_half_size + Vec3::splat(self.minor_radius);
let half_size = disc_half_size + Vec3A::splat(self.minor_radius);
Aabb3d::new(translation, half_size)
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, self.outer_radius())
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
BoundingSphere::new(isometry.translation, self.outer_radius())
}
}
impl Bounded3d for Triangle3d {
/// Get the bounding box of the triangle.
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
let [a, b, c] = self.vertices;
let a = rotation * a;
let b = rotation * b;
let c = rotation * c;
let a = isometry.rotation * a;
let b = isometry.rotation * b;
let c = isometry.rotation * c;
let min = a.min(b).min(c);
let max = a.max(b).max(c);
let min = Vec3A::from(a.min(b).min(c));
let max = Vec3A::from(a.max(b).max(c));
let bounding_center = (max + min) / 2.0 + translation;
let bounding_center = (max + min) / 2.0 + isometry.translation;
let half_extents = (max - min) / 2.0;
Aabb3d::new(bounding_center, half_extents)
@ -323,25 +326,26 @@ impl Bounded3d for Triangle3d {
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
/// that contains the largest side of the triangle.
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
if self.is_degenerate() || self.is_obtuse() {
let (p1, p2) = self.largest_side();
let (p1, p2) = (Vec3A::from(p1), Vec3A::from(p2));
let mid_point = (p1 + p2) / 2.0;
let radius = mid_point.distance(p1);
BoundingSphere::new(mid_point + translation, radius)
BoundingSphere::new(mid_point + isometry.translation, radius)
} else {
let [a, _, _] = self.vertices;
let circumcenter = self.circumcenter();
let radius = circumcenter.distance(a);
BoundingSphere::new(circumcenter + translation, radius)
BoundingSphere::new(Vec3A::from(circumcenter) + isometry.translation, radius)
}
}
}
#[cfg(test)]
mod tests {
use crate::bounding::BoundingVolume;
use crate::{bounding::BoundingVolume, Isometry3d};
use glam::{Quat, Vec3, Vec3A};
use crate::{
@ -357,12 +361,13 @@ mod tests {
fn sphere() {
let sphere = Sphere { radius: 1.0 };
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = sphere.aabb_3d(translation, Quat::IDENTITY);
let aabb = sphere.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = sphere.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = sphere.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0);
}
@ -370,25 +375,25 @@ mod tests {
#[test]
fn plane() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation, Quat::IDENTITY);
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(isometry);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation, Quat::IDENTITY);
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(isometry);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation, Quat::IDENTITY);
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(isometry);
assert_eq!(aabb3.min, Vec3A::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb3.max, Vec3A::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation, Quat::IDENTITY);
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(isometry);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere =
InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
@ -396,28 +401,28 @@ mod tests {
#[test]
fn line() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(translation, Quat::IDENTITY);
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(isometry);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, 0.0));
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(translation, Quat::IDENTITY);
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(isometry);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, 0.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, 0.0));
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(translation, Quat::IDENTITY);
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(isometry);
assert_eq!(aabb3.min, Vec3A::new(2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec3A::new(2.0, 1.0, f32::MAX / 2.0));
let aabb4 = Line3d {
direction: Dir3::from_xyz(1.0, 1.0, 1.0).unwrap(),
}
.aabb_3d(translation, Quat::IDENTITY);
.aabb_3d(isometry);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere =
Line3d { direction: Dir3::Y }.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
@ -425,14 +430,16 @@ mod tests {
#[test]
fn segment() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let segment =
Segment3d::from_points(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0)).0;
let aabb = segment.aabb_3d(translation, Quat::IDENTITY);
let aabb = segment.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 1.5, 0.0));
let bounding_sphere = segment.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = segment.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(0.5));
}
@ -446,12 +453,13 @@ mod tests {
Vec3::new(1.0, -1.0, -1.0),
]);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = polyline.aabb_3d(translation, Quat::IDENTITY);
let aabb = polyline.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = polyline.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = polyline.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(1.0).hypot(1.0));
}
@ -461,15 +469,15 @@ mod tests {
let cuboid = Cuboid::new(2.0, 1.0, 1.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cuboid.aabb_3d(
let aabb = cuboid.aabb_3d(Isometry3d::new(
translation,
Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
);
));
let expected_half_size = Vec3A::new(1.0606601, 1.0606601, 0.5);
assert_eq!(aabb.min, Vec3A::from(translation) - expected_half_size);
assert_eq!(aabb.max, Vec3A::from(translation) + expected_half_size);
let bounding_sphere = cuboid.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = cuboid.bounding_sphere(Isometry3d::from_translation(translation));
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(0.5).hypot(0.5));
}
@ -478,8 +486,9 @@ mod tests {
fn cylinder() {
let cylinder = Cylinder::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = cylinder.aabb_3d(translation, Quat::IDENTITY);
let aabb = cylinder.aabb_3d(isometry);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.0, 0.5)
@ -489,7 +498,7 @@ mod tests {
Vec3A::from(translation) + Vec3A::new(0.5, 1.0, 0.5)
);
let bounding_sphere = cylinder.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = cylinder.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(0.5));
}
@ -498,8 +507,9 @@ mod tests {
fn capsule() {
let capsule = Capsule3d::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = capsule.aabb_3d(translation, Quat::IDENTITY);
let aabb = capsule.aabb_3d(isometry);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.5, 0.5)
@ -509,7 +519,7 @@ mod tests {
Vec3A::from(translation) + Vec3A::new(0.5, 1.5, 0.5)
);
let bounding_sphere = capsule.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = capsule.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}
@ -521,12 +531,13 @@ mod tests {
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = cone.aabb_3d(translation, Quat::IDENTITY);
let aabb = cone.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = cone.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = cone.bounding_sphere(isometry);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.25
@ -542,12 +553,13 @@ mod tests {
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = conical_frustum.aabb_3d(translation, Quat::IDENTITY);
let aabb = conical_frustum.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = conical_frustum.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = conical_frustum.bounding_sphere(isometry);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.1875
@ -563,14 +575,15 @@ mod tests {
height: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = conical_frustum.aabb_3d(translation, Quat::IDENTITY);
let aabb = conical_frustum.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(-3.0, 0.5, -5.0));
assert_eq!(aabb.max, Vec3A::new(7.0, 1.5, 5.0));
// For wide conical frusta like this, the circumcenter can be outside the frustum,
// so the center and radius should be clamped to the longest side.
let bounding_sphere = conical_frustum.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = conical_frustum.bounding_sphere(isometry);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.5
@ -585,12 +598,13 @@ mod tests {
major_radius: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = torus.aabb_3d(translation, Quat::IDENTITY);
let aabb = torus.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(0.5, 0.5, -1.5));
assert_eq!(aabb.max, Vec3A::new(3.5, 1.5, 1.5));
let bounding_sphere = torus.bounding_sphere(translation, Quat::IDENTITY);
let bounding_sphere = torus.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}
@ -599,7 +613,7 @@ mod tests {
fn triangle3d() {
let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
let br = zero_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
let br = zero_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),
@ -611,7 +625,7 @@ mod tests {
"incorrect bounding box half extents"
);
let bs = zero_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
let bs = zero_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
@ -620,14 +634,14 @@ mod tests {
assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius");
let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
let bs = dup_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
let bs = dup_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 0.5, "incorrect bounding sphere radius");
let br = dup_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
let br = dup_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::new(0.5, 0.0, 0.0).into(),
@ -640,14 +654,14 @@ mod tests {
);
let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
let bs = collinear_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
let bs = collinear_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 1.0, "incorrect bounding sphere radius");
let br = collinear_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
let br = collinear_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),

View file

@ -1,6 +1,10 @@
//! This example demonstrates bounding volume intersections.
use bevy::{color::palettes::css::*, math::bounding::*, prelude::*};
use bevy::{
color::palettes::css::*,
math::{bounding::*, Isometry2d},
prelude::*,
};
fn main() {
App::new()
@ -146,26 +150,27 @@ fn update_volumes(
for (entity, desired_volume, shape, transform) in query.iter() {
let translation = transform.translation.xy();
let rotation = transform.rotation.to_euler(EulerRot::YXZ).2;
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
match desired_volume {
DesiredVolume::Aabb => {
let aabb = match shape {
Shape::Rectangle(r) => r.aabb_2d(translation, rotation),
Shape::Circle(c) => c.aabb_2d(translation, rotation),
Shape::Triangle(t) => t.aabb_2d(translation, rotation),
Shape::Line(l) => l.aabb_2d(translation, rotation),
Shape::Capsule(c) => c.aabb_2d(translation, rotation),
Shape::Polygon(p) => p.aabb_2d(translation, rotation),
Shape::Rectangle(r) => r.aabb_2d(isometry),
Shape::Circle(c) => c.aabb_2d(isometry),
Shape::Triangle(t) => t.aabb_2d(isometry),
Shape::Line(l) => l.aabb_2d(isometry),
Shape::Capsule(c) => c.aabb_2d(isometry),
Shape::Polygon(p) => p.aabb_2d(isometry),
};
commands.entity(entity).insert(CurrentVolume::Aabb(aabb));
}
DesiredVolume::Circle => {
let circle = match shape {
Shape::Rectangle(r) => r.bounding_circle(translation, rotation),
Shape::Circle(c) => c.bounding_circle(translation, rotation),
Shape::Triangle(t) => t.bounding_circle(translation, rotation),
Shape::Line(l) => l.bounding_circle(translation, rotation),
Shape::Capsule(c) => c.bounding_circle(translation, rotation),
Shape::Polygon(p) => p.bounding_circle(translation, rotation),
Shape::Rectangle(r) => r.bounding_circle(isometry),
Shape::Circle(c) => c.bounding_circle(isometry),
Shape::Triangle(t) => t.bounding_circle(isometry),
Shape::Line(l) => l.bounding_circle(isometry),
Shape::Capsule(c) => c.bounding_circle(isometry),
Shape::Polygon(p) => p.bounding_circle(isometry),
};
commands
.entity(entity)

View file

@ -5,7 +5,10 @@ use std::f32::consts::FRAC_PI_2;
use bevy::{
color::palettes::css::{BLUE, DARK_SLATE_GREY, RED},
math::bounding::{Bounded2d, BoundingVolume},
math::{
bounding::{Bounded2d, BoundingVolume},
Isometry2d,
},
prelude::*,
render::mesh::{CircularMeshUvMode, CircularSectorMeshBuilder, CircularSegmentMeshBuilder},
sprite::MaterialMesh2dBundle,
@ -114,11 +117,12 @@ fn draw_bounds<Shape: Bounded2d + Send + Sync + 'static>(
let (_, rotation, translation) = transform.to_scale_rotation_translation();
let translation = translation.truncate();
let rotation = rotation.to_euler(EulerRot::XYZ).2;
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
let aabb = shape.0.aabb_2d(translation, rotation);
let aabb = shape.0.aabb_2d(isometry);
gizmos.rect_2d(aabb.center(), 0.0, aabb.half_size() * 2.0, RED);
let bounding_circle = shape.0.bounding_circle(translation, rotation);
let bounding_circle = shape.0.bounding_circle(isometry);
gizmos.circle_2d(bounding_circle.center, bounding_circle.radius(), BLUE);
}
}

View file

@ -6,8 +6,11 @@ use std::f32::consts::{PI, SQRT_2};
use bevy::{
color::palettes::css::{RED, WHITE},
input::common_conditions::input_just_pressed,
math::bounding::{
Aabb2d, Bounded2d, Bounded3d, BoundedExtrusion, BoundingCircle, BoundingVolume,
math::{
bounding::{
Aabb2d, Bounded2d, Bounded3d, BoundedExtrusion, BoundingCircle, BoundingVolume,
},
Isometry2d,
},
prelude::*,
render::{
@ -199,18 +202,20 @@ fn bounding_shapes_2d(
for transform in shapes.iter() {
// Get the rotation angle from the 3D rotation.
let rotation = transform.rotation.to_scaled_axis().z;
let rotation = Rot2::radians(rotation);
let isometry = Isometry2d::new(transform.translation.xy(), rotation);
match bounding_shape.get() {
BoundingShape::None => (),
BoundingShape::BoundingBox => {
// Get the AABB of the primitive with the rotation and translation of the mesh.
let aabb = HEART.aabb_2d(transform.translation.xy(), rotation);
let aabb = HEART.aabb_2d(isometry);
gizmos.rect_2d(aabb.center(), 0., aabb.half_size() * 2., WHITE);
}
BoundingShape::BoundingSphere => {
// Get the bounding sphere of the primitive with the rotation and translation of the mesh.
let bounding_circle = HEART.bounding_circle(transform.translation.xy(), rotation);
let bounding_circle = HEART.bounding_circle(isometry);
gizmos
.circle_2d(bounding_circle.center(), bounding_circle.radius(), WHITE)
@ -240,7 +245,7 @@ fn bounding_shapes_3d(
BoundingShape::None => (),
BoundingShape::BoundingBox => {
// Get the AABB of the extrusion with the rotation and translation of the mesh.
let aabb = EXTRUSION.aabb_3d(transform.translation, transform.rotation);
let aabb = EXTRUSION.aabb_3d(transform.to_isometry());
gizmos.primitive_3d(
&Cuboid::from_size(Vec3::from(aabb.half_size()) * 2.),
@ -251,8 +256,7 @@ fn bounding_shapes_3d(
}
BoundingShape::BoundingSphere => {
// Get the bounding sphere of the extrusion with the rotation and translation of the mesh.
let bounding_sphere =
EXTRUSION.bounding_sphere(transform.translation, transform.rotation);
let bounding_sphere = EXTRUSION.bounding_sphere(transform.to_isometry());
gizmos.sphere(
bounding_sphere.center().into(),
@ -338,29 +342,28 @@ impl Measured2d for Heart {
// The `Bounded2d` or `Bounded3d` traits are used to compute the Axis Aligned Bounding Boxes or bounding circles / spheres for primitives.
impl Bounded2d for Heart {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
let rotation = rotation.into();
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
// The center of the circle at the center of the right wing of the heart
let circle_center = rotation * Vec2::new(self.radius, 0.0);
let circle_center = isometry.rotation * Vec2::new(self.radius, 0.0);
// The maximum X and Y positions of the two circles of the wings of the heart.
let max_circle = circle_center.abs() + Vec2::splat(self.radius);
// Since the two circles of the heart are mirrored around the origin, the minimum position is the negative of the maximum.
let min_circle = -max_circle;
// The position of the tip at the bottom of the heart
let tip_position = rotation * Vec2::new(0.0, -self.radius * (1. + SQRT_2));
let tip_position = isometry.rotation * Vec2::new(0.0, -self.radius * (1. + SQRT_2));
Aabb2d {
min: translation + min_circle.min(tip_position),
max: translation + max_circle.max(tip_position),
min: isometry.translation + min_circle.min(tip_position),
max: isometry.translation + max_circle.max(tip_position),
}
}
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
// The bounding circle of the heart is not at its origin. This `offset` is the offset between the center of the bounding circle and its translation.
let offset = self.radius / 2f32.powf(1.5);
// The center of the bounding circle
let center = translation + rotation.into() * Vec2::new(0.0, -offset);
let center = isometry * Vec2::new(0.0, -offset);
// The radius of the bounding circle
let radius = self.radius * (1.0 + 2f32.sqrt()) - offset;