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

View file

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

View file

@ -7,194 +7,176 @@ use crate::primitives::{
BoxedPolygon, BoxedPolyline2d, Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d,
Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d, Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d,
}; };
use crate::{Quat, Vec3}; use crate::{Isometry2d, Isometry3d, Quat, Rot2};
use crate::{bounding::Bounded2d, primitives::Circle}; use crate::{bounding::Bounded2d, primitives::Circle};
use super::{Aabb3d, Bounded3d, BoundingSphere}; use super::{Aabb3d, Bounded3d, BoundingSphere};
impl BoundedExtrusion for Circle { 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/ // 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 top = (segment_dir * half_depth).abs();
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO); let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_size = self.radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt()); let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
Aabb3d { Aabb3d {
min: (translation - half_size - top).into(), min: isometry.translation - half_size - top,
max: (translation + half_size + top).into(), max: isometry.translation + half_size + top,
} }
} }
} }
impl BoundedExtrusion for Ellipse { 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 Vec2 { x: a, y: b } = self.half_size;
let normal = rotation * Vec3::Z; let normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = rotation.conjugate(); 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)) let Some(axis) = (conjugate_rot * axis.reject_from(normal))
.xy() .xy()
.try_normalize() .try_normalize()
else { else {
return Vec3::ZERO; return Vec3A::ZERO;
}; };
if axis.element_product() == 0. { 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 m = -axis.x / axis.y;
let signum = axis.signum(); let signum = axis.signum();
let y = signum.y * b * b / (b * b + m * m * a * a).sqrt(); let y = signum.y * b * b / (b * b + m * m * a * a).sqrt();
let x = signum.x * a * (1. - y * y / b / b).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(); let half_size = Vec3A::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
Aabb3d::new(translation, half_size) Aabb3d::new(isometry.translation, half_size)
} }
} }
impl BoundedExtrusion for Line2d { impl BoundedExtrusion for Line2d {
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 dir = rotation * self.direction.extend(0.); let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));
let half_depth = (rotation * Vec3::new(0., 0., half_depth)).abs(); let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();
let max = f32::MAX / 2.; 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.x == 0. { half_depth.x } else { max },
if dir.y == 0. { half_depth.y } else { max }, if dir.y == 0. { half_depth.y } else { max },
if dir.z == 0. { half_depth.z } 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 { impl BoundedExtrusion for Segment2d {
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 half_size = rotation * self.point1().extend(0.); let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));
let depth = rotation * Vec3::new(0., 0., half_depth); 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> { impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
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( let aabb =
translation, Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
rotation, let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
self.vertices.map(|v| v.extend(0.)).into_iter(),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
} }
impl BoundedExtrusion for BoxedPolyline2d { impl BoundedExtrusion for BoxedPolyline2d {
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( let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
translation, let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
rotation,
self.vertices.iter().map(|v| v.extend(0.)),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
} }
impl BoundedExtrusion for Triangle2d { impl BoundedExtrusion for Triangle2d {
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( let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
translation, let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
rotation,
self.vertices.iter().map(|v| v.extend(0.)),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
} }
impl BoundedExtrusion for Rectangle { 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 { Cuboid {
half_size: self.half_size.extend(half_depth), half_size: self.half_size.extend(half_depth),
} }
.aabb_3d(translation, rotation) .aabb_3d(isometry)
} }
} }
impl<const N: usize> BoundedExtrusion for Polygon<N> { impl<const N: usize> BoundedExtrusion for Polygon<N> {
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( let aabb =
translation, Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
rotation, let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
self.vertices.map(|v| v.extend(0.)).into_iter(),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
} }
impl BoundedExtrusion for BoxedPolygon { impl BoundedExtrusion for BoxedPolygon {
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( let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
translation, let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
rotation,
self.vertices.iter().map(|v| v.extend(0.)),
);
let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
} }
impl BoundedExtrusion for RegularPolygon { 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( let aabb = Aabb3d::from_point_cloud(
translation, isometry,
rotation,
self.vertices(0.).into_iter().map(|v| v.extend(0.)), 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()) aabb.grow(depth.abs())
} }
} }
impl BoundedExtrusion for Capsule2d { 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 { let aabb = Cylinder {
half_height: half_depth, half_height: half_depth,
radius: self.radius, 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 up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);
let half_size = Into::<Vec3>::into(aabb.max) + up.abs(); let half_size = aabb.max + up.abs();
Aabb3d::new(translation, half_size) Aabb3d::new(isometry.translation, half_size)
} }
} }
impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> { impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
self.base_shape self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)
.extrusion_aabb_3d(self.half_depth, translation, rotation)
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere { fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
self.base_shape 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 {}` /// `impl BoundedExtrusion for MyShape {}`
pub trait BoundedExtrusion: Primitive2d + Bounded2d { 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`. /// 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 { fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
let cap_normal = rotation * Vec3::Z; let cap_normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = rotation.conjugate(); let conjugate_rot = isometry.rotation.conjugate();
// The `(halfsize, offset)` for each axis // 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. // 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); let intersect_line = ax.cross(cap_normal);
if intersect_line.length_squared() <= f32::EPSILON { 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. // 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 // 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) (aabb2d.half_size().x * scale, aabb2d.center().x * scale)
}); });
let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset)); 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 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 /// 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( fn extrusion_bounding_sphere(&self, half_depth: f32, isometry: Isometry3d) -> BoundingSphere {
&self,
half_depth: f32,
translation: Vec3,
rotation: Quat,
) -> BoundingSphere {
// We calculate the bounding circle of the base shape. // We calculate the bounding circle of the base shape.
// Since each of the extrusions bases will have the same distance from its center, // 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 // 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 { let BoundingCircle {
center, center,
circle: Circle { radius }, circle: Circle { radius },
} = self.bounding_circle(Vec2::ZERO, 0.); } = self.bounding_circle(Isometry2d::IDENTITY);
let radius = radius.hypot(half_depth); 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) BoundingSphere::new(center, radius)
} }
@ -273,19 +250,20 @@ mod tests {
Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle, Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,
RegularPolygon, Segment2d, Triangle2d, RegularPolygon, Segment2d, Triangle2d,
}, },
Dir2, Dir2, Isometry3d,
}; };
#[test] #[test]
fn circle() { fn circle() {
let cylinder = Extrusion::new(Circle::new(0.5), 2.0); let cylinder = Extrusion::new(Circle::new(0.5), 2.0);
let translation = Vec3::new(2.0, 1.0, 0.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.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0)); 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1f32.hypot(0.5)); 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 extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4); 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.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141)); 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 8f32.sqrt()); assert_eq!(bounding_sphere.radius(), 8f32.sqrt());
} }
@ -315,12 +294,13 @@ mod tests {
); );
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_y(FRAC_PI_4); 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.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));
assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213)); 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.center(), translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.); 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 extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_z(std::f32::consts::FRAC_PI_4); 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.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.)); 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.291288); 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 extrusion = Extrusion::new(Segment2d::new(Dir2::new_unchecked(Vec2::NEG_Y), 3.), 4.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4); 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.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735)); 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5); assert_eq!(bounding_sphere.radius(), 2.5);
} }
@ -366,12 +348,13 @@ mod tests {
let extrusion = Extrusion::new(polyline, 3.0); let extrusion = Extrusion::new(polyline, 3.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4); 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.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668)); 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528); assert_eq!(bounding_sphere.radius(), 2.0615528);
} }
@ -386,12 +369,13 @@ mod tests {
let extrusion = Extrusion::new(triangle, 3.0); let extrusion = Extrusion::new(triangle, 3.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4); 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.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668)); 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!( assert_eq!(
bounding_sphere.center, bounding_sphere.center,
Vec3A::new(3.0, 3.2928934, 4.2928934) Vec3A::new(3.0, 3.2928934, 4.2928934)
@ -410,12 +394,13 @@ mod tests {
let extrusion = Extrusion::new(polygon, 3.0); let extrusion = Extrusion::new(polygon, 3.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4); 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.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668)); 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528); 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 extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4); 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!( assert_eq!(
aabb.center(), aabb.center(),
Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254) Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)
@ -436,7 +422,7 @@ mod tests {
Vec3A::new(1.9498558, 2.7584014, 2.7584019) 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 8f32.sqrt()); 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 extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4); 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.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735)); 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.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5); assert_eq!(bounding_sphere.radius(), 2.5);
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -6,9 +6,12 @@ use std::f32::consts::{PI, SQRT_2};
use bevy::{ use bevy::{
color::palettes::css::{RED, WHITE}, color::palettes::css::{RED, WHITE},
input::common_conditions::input_just_pressed, input::common_conditions::input_just_pressed,
math::bounding::{ math::{
bounding::{
Aabb2d, Bounded2d, Bounded3d, BoundedExtrusion, BoundingCircle, BoundingVolume, Aabb2d, Bounded2d, Bounded3d, BoundedExtrusion, BoundingCircle, BoundingVolume,
}, },
Isometry2d,
},
prelude::*, prelude::*,
render::{ render::{
camera::ScalingMode, camera::ScalingMode,
@ -199,18 +202,20 @@ fn bounding_shapes_2d(
for transform in shapes.iter() { for transform in shapes.iter() {
// Get the rotation angle from the 3D rotation. // Get the rotation angle from the 3D rotation.
let rotation = transform.rotation.to_scaled_axis().z; 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() { match bounding_shape.get() {
BoundingShape::None => (), BoundingShape::None => (),
BoundingShape::BoundingBox => { BoundingShape::BoundingBox => {
// Get the AABB of the primitive with the rotation and translation of the mesh. // 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); gizmos.rect_2d(aabb.center(), 0., aabb.half_size() * 2., WHITE);
} }
BoundingShape::BoundingSphere => { BoundingShape::BoundingSphere => {
// Get the bounding sphere of the primitive with the rotation and translation of the mesh. // 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 gizmos
.circle_2d(bounding_circle.center(), bounding_circle.radius(), WHITE) .circle_2d(bounding_circle.center(), bounding_circle.radius(), WHITE)
@ -240,7 +245,7 @@ fn bounding_shapes_3d(
BoundingShape::None => (), BoundingShape::None => (),
BoundingShape::BoundingBox => { BoundingShape::BoundingBox => {
// Get the AABB of the extrusion with the rotation and translation of the mesh. // 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( gizmos.primitive_3d(
&Cuboid::from_size(Vec3::from(aabb.half_size()) * 2.), &Cuboid::from_size(Vec3::from(aabb.half_size()) * 2.),
@ -251,8 +256,7 @@ fn bounding_shapes_3d(
} }
BoundingShape::BoundingSphere => { BoundingShape::BoundingSphere => {
// Get the bounding sphere of the extrusion with the rotation and translation of the mesh. // Get the bounding sphere of the extrusion with the rotation and translation of the mesh.
let bounding_sphere = let bounding_sphere = EXTRUSION.bounding_sphere(transform.to_isometry());
EXTRUSION.bounding_sphere(transform.translation, transform.rotation);
gizmos.sphere( gizmos.sphere(
bounding_sphere.center().into(), 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. // The `Bounded2d` or `Bounded3d` traits are used to compute the Axis Aligned Bounding Boxes or bounding circles / spheres for primitives.
impl Bounded2d for Heart { impl Bounded2d for Heart {
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d { fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
let rotation = rotation.into();
// The center of the circle at the center of the right wing of the heart // 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. // 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); 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. // 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; let min_circle = -max_circle;
// The position of the tip at the bottom of the heart // 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 { Aabb2d {
min: translation + min_circle.min(tip_position), min: isometry.translation + min_circle.min(tip_position),
max: translation + max_circle.max(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. // 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); let offset = self.radius / 2f32.powf(1.5);
// The center of the bounding circle // 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 // The radius of the bounding circle
let radius = self.radius * (1.0 + 2f32.sqrt()) - offset; let radius = self.radius * (1.0 + 2f32.sqrt()) - offset;