mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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:
parent
7573b3c765
commit
601cf6b9e5
8 changed files with 491 additions and 474 deletions
|
@ -1,7 +1,10 @@
|
|||
mod primitive_impls;
|
||||
|
||||
use super::{BoundingVolume, IntersectsVolume};
|
||||
use crate::prelude::{Mat2, Rot2, Vec2};
|
||||
use crate::{
|
||||
prelude::{Mat2, Rot2, Vec2},
|
||||
Isometry2d,
|
||||
};
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
|
@ -18,14 +21,12 @@ fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
|
|||
points.iter().fold(Vec2::ZERO, |acc, point| acc + *point) * denom
|
||||
}
|
||||
|
||||
/// A trait with methods that return 2D bounded volumes for a shape
|
||||
/// A trait with methods that return 2D bounding volumes for a shape.
|
||||
pub trait Bounded2d {
|
||||
/// Get an axis-aligned bounding box for the shape with the given translation and rotation.
|
||||
/// The rotation is in radians, counterclockwise, with 0 meaning no rotation.
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d;
|
||||
/// Get a bounding circle for the shape
|
||||
/// The rotation is in radians, counterclockwise, with 0 meaning no rotation.
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle;
|
||||
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d;
|
||||
/// Get a bounding circle for the shape translated and rotated by the given isometry.
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle;
|
||||
}
|
||||
|
||||
/// A 2D axis-aligned bounding box, or bounding rectangle
|
||||
|
@ -51,20 +52,15 @@ impl Aabb2d {
|
|||
}
|
||||
|
||||
/// Computes the smallest [`Aabb2d`] containing the given set of points,
|
||||
/// transformed by `translation` and `rotation`.
|
||||
/// transformed by the rotation and translation of the given isometry.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the given set of points is empty.
|
||||
#[inline(always)]
|
||||
pub fn from_point_cloud(
|
||||
translation: Vec2,
|
||||
rotation: impl Into<Rot2>,
|
||||
points: &[Vec2],
|
||||
) -> Aabb2d {
|
||||
pub fn from_point_cloud(isometry: Isometry2d, points: &[Vec2]) -> Aabb2d {
|
||||
// Transform all points by rotation
|
||||
let rotation: Rot2 = rotation.into();
|
||||
let mut iter = points.iter().map(|point| rotation * *point);
|
||||
let mut iter = points.iter().map(|point| isometry.rotation * *point);
|
||||
|
||||
let first = iter
|
||||
.next()
|
||||
|
@ -75,8 +71,8 @@ impl Aabb2d {
|
|||
});
|
||||
|
||||
Aabb2d {
|
||||
min: min + translation,
|
||||
max: max + translation,
|
||||
min: min + isometry.translation,
|
||||
max: max + isometry.translation,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,16 +468,11 @@ impl BoundingCircle {
|
|||
}
|
||||
|
||||
/// Computes a [`BoundingCircle`] containing the given set of points,
|
||||
/// transformed by `translation` and `rotation`.
|
||||
/// transformed by the rotation and translation of the given isometry.
|
||||
///
|
||||
/// The bounding circle is not guaranteed to be the smallest possible.
|
||||
#[inline(always)]
|
||||
pub fn from_point_cloud(
|
||||
translation: Vec2,
|
||||
rotation: impl Into<Rot2>,
|
||||
points: &[Vec2],
|
||||
) -> BoundingCircle {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
pub fn from_point_cloud(isometry: Isometry2d, points: &[Vec2]) -> BoundingCircle {
|
||||
let center = point_cloud_2d_center(points);
|
||||
let mut radius_squared = 0.0;
|
||||
|
||||
|
@ -493,7 +484,7 @@ impl BoundingCircle {
|
|||
}
|
||||
}
|
||||
|
||||
BoundingCircle::new(rotation * center + translation, radius_squared.sqrt())
|
||||
BoundingCircle::new(isometry * center, radius_squared.sqrt())
|
||||
}
|
||||
|
||||
/// Get the radius of the bounding circle
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon,
|
||||
Rhombus, Segment2d, Triangle2d,
|
||||
},
|
||||
Dir2, Mat2, Rot2, Vec2,
|
||||
Dir2, Isometry2d, Mat2, Rot2, Vec2,
|
||||
};
|
||||
use std::f32::consts::{FRAC_PI_2, PI, TAU};
|
||||
|
||||
|
@ -15,12 +15,12 @@ use smallvec::SmallVec;
|
|||
use super::{Aabb2d, Bounded2d, BoundingCircle};
|
||||
|
||||
impl Bounded2d for Circle {
|
||||
fn aabb_2d(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
Aabb2d::new(translation, Vec2::splat(self.radius))
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
Aabb2d::new(isometry.translation, Vec2::splat(self.radius))
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, self.radius)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, self.radius)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,49 +57,52 @@ fn arc_bounding_points(arc: Arc2d, rotation: impl Into<Rot2>) -> SmallVec<[Vec2;
|
|||
}
|
||||
|
||||
impl Bounded2d for Arc2d {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
// If our arc covers more than a circle, just return the bounding box of the circle.
|
||||
if self.half_angle >= PI {
|
||||
return Circle::new(self.radius).aabb_2d(translation, rotation);
|
||||
return Circle::new(self.radius).aabb_2d(isometry);
|
||||
}
|
||||
|
||||
Aabb2d::from_point_cloud(translation, 0.0, &arc_bounding_points(*self, rotation))
|
||||
Aabb2d::from_point_cloud(
|
||||
Isometry2d::from_translation(isometry.translation),
|
||||
&arc_bounding_points(*self, isometry.rotation),
|
||||
)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
// There are two possibilities for the bounding circle.
|
||||
if self.is_major() {
|
||||
// If the arc is major, then the widest distance between two points is a diameter of the arc's circle;
|
||||
// therefore, that circle is the bounding radius.
|
||||
BoundingCircle::new(translation, self.radius)
|
||||
BoundingCircle::new(isometry.translation, self.radius)
|
||||
} else {
|
||||
// Otherwise, the widest distance between two points is the chord,
|
||||
// so a circle of that diameter around the midpoint will contain the entire arc.
|
||||
let center = rotation.into() * self.chord_midpoint();
|
||||
BoundingCircle::new(center + translation, self.half_chord_length())
|
||||
let center = isometry.rotation * self.chord_midpoint();
|
||||
BoundingCircle::new(center + isometry.translation, self.half_chord_length())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for CircularSector {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
// If our sector covers more than a circle, just return the bounding box of the circle.
|
||||
if self.half_angle() >= PI {
|
||||
return Circle::new(self.radius()).aabb_2d(translation, rotation);
|
||||
return Circle::new(self.radius()).aabb_2d(isometry);
|
||||
}
|
||||
|
||||
// Otherwise, we use the same logic as for Arc2d, above, just with the circle's center as an additional possibility.
|
||||
let mut bounds = arc_bounding_points(self.arc, rotation);
|
||||
let mut bounds = arc_bounding_points(self.arc, isometry.rotation);
|
||||
bounds.push(Vec2::ZERO);
|
||||
|
||||
Aabb2d::from_point_cloud(translation, 0.0, &bounds)
|
||||
Aabb2d::from_point_cloud(Isometry2d::from_translation(isometry.translation), &bounds)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
if self.arc.is_major() {
|
||||
// If the arc is major, that is, greater than a semicircle,
|
||||
// then bounding circle is just the circle defining the sector.
|
||||
BoundingCircle::new(translation, self.arc.radius)
|
||||
BoundingCircle::new(isometry.translation, self.arc.radius)
|
||||
} else {
|
||||
// However, when the arc is minor,
|
||||
// we need our bounding circle to include both endpoints of the arc as well as the circle center.
|
||||
|
@ -111,25 +114,23 @@ impl Bounded2d for CircularSector {
|
|||
self.arc.left_endpoint(),
|
||||
self.arc.right_endpoint(),
|
||||
)
|
||||
.bounding_circle(translation, rotation)
|
||||
.bounding_circle(isometry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for CircularSegment {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
self.arc.aabb_2d(translation, rotation)
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
self.arc.aabb_2d(isometry)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
self.arc.bounding_circle(translation, rotation)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
self.arc.bounding_circle(isometry)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Ellipse {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
// V = (hh * cos(beta), hh * sin(beta))
|
||||
// #####*#####
|
||||
// ### | ###
|
||||
|
@ -142,7 +143,7 @@ impl Bounded2d for Ellipse {
|
|||
let (hw, hh) = (self.half_size.x, self.half_size.y);
|
||||
|
||||
// Sine and cosine of rotation angle alpha.
|
||||
let (alpha_sin, alpha_cos) = rotation.sin_cos();
|
||||
let (alpha_sin, alpha_cos) = isometry.rotation.sin_cos();
|
||||
|
||||
// Sine and cosine of alpha + pi/2. We can avoid the trigonometric functions:
|
||||
// sin(beta) = sin(alpha + pi/2) = cos(alpha)
|
||||
|
@ -155,51 +156,48 @@ impl Bounded2d for Ellipse {
|
|||
|
||||
let half_size = Vec2::new(ux.hypot(vx), uy.hypot(vy));
|
||||
|
||||
Aabb2d::new(translation, half_size)
|
||||
Aabb2d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, self.semi_major())
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, self.semi_major())
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Annulus {
|
||||
fn aabb_2d(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
Aabb2d::new(translation, Vec2::splat(self.outer_circle.radius))
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
Aabb2d::new(isometry.translation, Vec2::splat(self.outer_circle.radius))
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, self.outer_circle.radius)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, self.outer_circle.radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Rhombus {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation_mat = rotation.into();
|
||||
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
let [rotated_x_half_diagonal, rotated_y_half_diagonal] = [
|
||||
rotation_mat * Vec2::new(self.half_diagonals.x, 0.0),
|
||||
rotation_mat * Vec2::new(0.0, self.half_diagonals.y),
|
||||
isometry.rotation * Vec2::new(self.half_diagonals.x, 0.0),
|
||||
isometry.rotation * Vec2::new(0.0, self.half_diagonals.y),
|
||||
];
|
||||
let aabb_half_extent = rotated_x_half_diagonal
|
||||
.abs()
|
||||
.max(rotated_y_half_diagonal.abs());
|
||||
|
||||
Aabb2d {
|
||||
min: -aabb_half_extent + translation,
|
||||
max: aabb_half_extent + translation,
|
||||
min: -aabb_half_extent + isometry.translation,
|
||||
max: aabb_half_extent + isometry.translation,
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, self.circumradius())
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, self.circumradius())
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Plane2d {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
let normal = rotation * *self.normal;
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
let normal = isometry.rotation * *self.normal;
|
||||
let facing_x = normal == Vec2::X || normal == Vec2::NEG_X;
|
||||
let facing_y = normal == Vec2::Y || normal == Vec2::NEG_Y;
|
||||
|
||||
|
@ -209,18 +207,17 @@ impl Bounded2d for Plane2d {
|
|||
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
|
||||
let half_size = Vec2::new(half_width, half_height);
|
||||
|
||||
Aabb2d::new(translation, half_size)
|
||||
Aabb2d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, f32::MAX / 2.0)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, f32::MAX / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Line2d {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
let direction = rotation * *self.direction;
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
let direction = isometry.rotation * *self.direction;
|
||||
|
||||
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
|
||||
// like growing or shrinking the AABB without breaking things.
|
||||
|
@ -229,65 +226,62 @@ impl Bounded2d for Line2d {
|
|||
let half_height = if direction.y == 0.0 { 0.0 } else { max };
|
||||
let half_size = Vec2::new(half_width, half_height);
|
||||
|
||||
Aabb2d::new(translation, half_size)
|
||||
Aabb2d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, f32::MAX / 2.0)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, f32::MAX / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Segment2d {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
// Rotate the segment by `rotation`
|
||||
let rotation: Rot2 = rotation.into();
|
||||
let direction = rotation * *self.direction;
|
||||
let direction = isometry.rotation * *self.direction;
|
||||
let half_size = (self.half_length * direction).abs();
|
||||
|
||||
Aabb2d::new(translation, half_size)
|
||||
Aabb2d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, self.half_length)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, self.half_length)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Bounded2d for Polyline2d<N> {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for BoxedPolyline2d {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Triangle2d {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
let [a, b, c] = self.vertices.map(|vtx| rotation * vtx);
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
let [a, b, c] = self.vertices.map(|vtx| isometry.rotation * vtx);
|
||||
|
||||
let min = Vec2::new(a.x.min(b.x).min(c.x), a.y.min(b.y).min(c.y));
|
||||
let max = Vec2::new(a.x.max(b.x).max(c.x), a.y.max(b.y).max(c.y));
|
||||
|
||||
Aabb2d {
|
||||
min: min + translation,
|
||||
max: max + translation,
|
||||
min: min + isometry.translation,
|
||||
max: max + isometry.translation,
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
let [a, b, c] = self.vertices;
|
||||
|
||||
// The points of the segment opposite to the obtuse or right angle if one exists
|
||||
|
@ -308,85 +302,79 @@ impl Bounded2d for Triangle2d {
|
|||
// The triangle is obtuse or right, so the minimum bounding circle's diameter is equal to the longest side.
|
||||
// We can compute the minimum bounding circle from the line segment of the longest side.
|
||||
let (segment, center) = Segment2d::from_points(point1, point2);
|
||||
segment.bounding_circle(rotation * center + translation, rotation)
|
||||
segment.bounding_circle(isometry * Isometry2d::from_translation(center))
|
||||
} else {
|
||||
// The triangle is acute, so the smallest bounding circle is the circumcircle.
|
||||
let (Circle { radius }, circumcenter) = self.circumcircle();
|
||||
BoundingCircle::new(rotation * circumcenter + translation, radius)
|
||||
BoundingCircle::new(isometry * circumcenter, radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Rectangle {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
// Compute the AABB of the rotated rectangle by transforming the half-extents
|
||||
// by an absolute rotation matrix.
|
||||
let (sin, cos) = rotation.sin_cos();
|
||||
let (sin, cos) = isometry.rotation.sin_cos();
|
||||
let abs_rot_mat = Mat2::from_cols_array(&[cos.abs(), sin.abs(), sin.abs(), cos.abs()]);
|
||||
let half_size = abs_rot_mat * self.half_size;
|
||||
|
||||
Aabb2d::new(translation, half_size)
|
||||
Aabb2d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
let radius = self.half_size.length();
|
||||
BoundingCircle::new(translation, radius)
|
||||
BoundingCircle::new(isometry.translation, radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Bounded2d for Polygon<N> {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for BoxedPolygon {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
Aabb2d::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for RegularPolygon {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
let mut min = Vec2::ZERO;
|
||||
let mut max = Vec2::ZERO;
|
||||
|
||||
for vertex in self.vertices(rotation.as_radians()) {
|
||||
for vertex in self.vertices(isometry.rotation.as_radians()) {
|
||||
min = min.min(vertex);
|
||||
max = max.max(vertex);
|
||||
}
|
||||
|
||||
Aabb2d {
|
||||
min: min + translation,
|
||||
max: max + translation,
|
||||
min: min + isometry.translation,
|
||||
max: max + isometry.translation,
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, self.circumcircle.radius)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, self.circumcircle.radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded2d for Capsule2d {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation: Rot2 = rotation.into();
|
||||
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
// Get the line segment between the hemicircles of the rotated capsule
|
||||
let segment = Segment2d {
|
||||
// Multiplying a normalized vector (Vec2::Y) with a rotation returns a normalized vector.
|
||||
direction: rotation * Dir2::Y,
|
||||
direction: isometry.rotation * Dir2::Y,
|
||||
half_length: self.half_length,
|
||||
};
|
||||
let (a, b) = (segment.point1(), segment.point2());
|
||||
|
@ -396,13 +384,13 @@ impl Bounded2d for Capsule2d {
|
|||
let max = a.max(b) + Vec2::splat(self.radius);
|
||||
|
||||
Aabb2d {
|
||||
min: min + translation,
|
||||
max: max + translation,
|
||||
min: min + isometry.translation,
|
||||
max: max + isometry.translation,
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, _rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
BoundingCircle::new(translation, self.radius + self.half_length)
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
BoundingCircle::new(isometry.translation, self.radius + self.half_length)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,19 +408,20 @@ mod tests {
|
|||
Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon, Rhombus, Segment2d,
|
||||
Triangle2d,
|
||||
},
|
||||
Dir2,
|
||||
Dir2, Isometry2d, Rot2,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn circle() {
|
||||
let circle = Circle { radius: 1.0 };
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = circle.aabb_2d(translation, 0.0);
|
||||
let aabb = circle.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
|
||||
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
|
||||
|
||||
let bounding_circle = circle.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = circle.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 1.0);
|
||||
}
|
||||
|
@ -451,6 +440,12 @@ mod tests {
|
|||
bounding_circle_radius: f32,
|
||||
}
|
||||
|
||||
impl TestCase {
|
||||
fn isometry(&self) -> Isometry2d {
|
||||
Isometry2d::new(self.translation, self.rotation.into())
|
||||
}
|
||||
}
|
||||
|
||||
// The apothem of an arc covering 1/6th of a circle.
|
||||
let apothem = f32::sqrt(3.0) / 2.0;
|
||||
let tests = [
|
||||
|
@ -565,17 +560,17 @@ mod tests {
|
|||
println!("subtest case: {}", test.name);
|
||||
let segment: CircularSegment = test.arc.into();
|
||||
|
||||
let arc_aabb = test.arc.aabb_2d(test.translation, test.rotation);
|
||||
let arc_aabb = test.arc.aabb_2d(test.isometry());
|
||||
assert_abs_diff_eq!(test.aabb_min, arc_aabb.min);
|
||||
assert_abs_diff_eq!(test.aabb_max, arc_aabb.max);
|
||||
let segment_aabb = segment.aabb_2d(test.translation, test.rotation);
|
||||
let segment_aabb = segment.aabb_2d(test.isometry());
|
||||
assert_abs_diff_eq!(test.aabb_min, segment_aabb.min);
|
||||
assert_abs_diff_eq!(test.aabb_max, segment_aabb.max);
|
||||
|
||||
let arc_bounding_circle = test.arc.bounding_circle(test.translation, test.rotation);
|
||||
let arc_bounding_circle = test.arc.bounding_circle(test.isometry());
|
||||
assert_abs_diff_eq!(test.bounding_circle_center, arc_bounding_circle.center);
|
||||
assert_abs_diff_eq!(test.bounding_circle_radius, arc_bounding_circle.radius());
|
||||
let segment_bounding_circle = segment.bounding_circle(test.translation, test.rotation);
|
||||
let segment_bounding_circle = segment.bounding_circle(test.isometry());
|
||||
assert_abs_diff_eq!(test.bounding_circle_center, segment_bounding_circle.center);
|
||||
assert_abs_diff_eq!(
|
||||
test.bounding_circle_radius,
|
||||
|
@ -597,6 +592,12 @@ mod tests {
|
|||
bounding_circle_radius: f32,
|
||||
}
|
||||
|
||||
impl TestCase {
|
||||
fn isometry(&self) -> Isometry2d {
|
||||
Isometry2d::new(self.translation, self.rotation.into())
|
||||
}
|
||||
}
|
||||
|
||||
// The apothem of an arc covering 1/6th of a circle.
|
||||
let apothem = f32::sqrt(3.0) / 2.0;
|
||||
let inv_sqrt_3 = f32::sqrt(3.0).recip();
|
||||
|
@ -715,11 +716,11 @@ mod tests {
|
|||
println!("subtest case: {}", test.name);
|
||||
let sector: CircularSector = test.arc.into();
|
||||
|
||||
let aabb = sector.aabb_2d(test.translation, test.rotation);
|
||||
let aabb = sector.aabb_2d(test.isometry());
|
||||
assert_abs_diff_eq!(test.aabb_min, aabb.min);
|
||||
assert_abs_diff_eq!(test.aabb_max, aabb.max);
|
||||
|
||||
let bounding_circle = sector.bounding_circle(test.translation, test.rotation);
|
||||
let bounding_circle = sector.bounding_circle(test.isometry());
|
||||
assert_abs_diff_eq!(test.bounding_circle_center, bounding_circle.center);
|
||||
assert_abs_diff_eq!(test.bounding_circle_radius, bounding_circle.radius());
|
||||
}
|
||||
|
@ -729,12 +730,13 @@ mod tests {
|
|||
fn ellipse() {
|
||||
let ellipse = Ellipse::new(1.0, 0.5);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = ellipse.aabb_2d(translation, 0.0);
|
||||
let aabb = ellipse.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(1.0, 0.5));
|
||||
assert_eq!(aabb.max, Vec2::new(3.0, 1.5));
|
||||
|
||||
let bounding_circle = ellipse.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = ellipse.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 1.0);
|
||||
}
|
||||
|
@ -743,12 +745,14 @@ mod tests {
|
|||
fn annulus() {
|
||||
let annulus = Annulus::new(1.0, 2.0);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let rotation = Rot2::radians(1.0);
|
||||
let isometry = Isometry2d::new(translation, rotation);
|
||||
|
||||
let aabb = annulus.aabb_2d(translation, 1.0);
|
||||
let aabb = annulus.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(0.0, -1.0));
|
||||
assert_eq!(aabb.max, Vec2::new(4.0, 3.0));
|
||||
|
||||
let bounding_circle = annulus.bounding_circle(translation, 1.0);
|
||||
let bounding_circle = annulus.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 2.0);
|
||||
}
|
||||
|
@ -757,23 +761,26 @@ mod tests {
|
|||
fn rhombus() {
|
||||
let rhombus = Rhombus::new(2.0, 1.0);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let rotation = Rot2::radians(std::f32::consts::FRAC_PI_4);
|
||||
let isometry = Isometry2d::new(translation, rotation);
|
||||
|
||||
let aabb = rhombus.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
|
||||
let aabb = rhombus.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(1.2928932, 0.29289323));
|
||||
assert_eq!(aabb.max, Vec2::new(2.7071068, 1.7071068));
|
||||
|
||||
let bounding_circle = rhombus.bounding_circle(translation, std::f32::consts::FRAC_PI_4);
|
||||
let bounding_circle = rhombus.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 1.0);
|
||||
|
||||
let rhombus = Rhombus::new(0.0, 0.0);
|
||||
let translation = Vec2::new(0.0, 0.0);
|
||||
let isometry = Isometry2d::new(translation, rotation);
|
||||
|
||||
let aabb = rhombus.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
|
||||
let aabb = rhombus.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(0.0, 0.0));
|
||||
assert_eq!(aabb.max, Vec2::new(0.0, 0.0));
|
||||
|
||||
let bounding_circle = rhombus.bounding_circle(translation, std::f32::consts::FRAC_PI_4);
|
||||
let bounding_circle = rhombus.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 0.0);
|
||||
}
|
||||
|
@ -781,20 +788,21 @@ mod tests {
|
|||
#[test]
|
||||
fn plane() {
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb1 = Plane2d::new(Vec2::X).aabb_2d(translation, 0.0);
|
||||
let aabb1 = Plane2d::new(Vec2::X).aabb_2d(isometry);
|
||||
assert_eq!(aabb1.min, Vec2::new(2.0, -f32::MAX / 2.0));
|
||||
assert_eq!(aabb1.max, Vec2::new(2.0, f32::MAX / 2.0));
|
||||
|
||||
let aabb2 = Plane2d::new(Vec2::Y).aabb_2d(translation, 0.0);
|
||||
let aabb2 = Plane2d::new(Vec2::Y).aabb_2d(isometry);
|
||||
assert_eq!(aabb2.min, Vec2::new(-f32::MAX / 2.0, 1.0));
|
||||
assert_eq!(aabb2.max, Vec2::new(f32::MAX / 2.0, 1.0));
|
||||
|
||||
let aabb3 = Plane2d::new(Vec2::ONE).aabb_2d(translation, 0.0);
|
||||
let aabb3 = Plane2d::new(Vec2::ONE).aabb_2d(isometry);
|
||||
assert_eq!(aabb3.min, Vec2::new(-f32::MAX / 2.0, -f32::MAX / 2.0));
|
||||
assert_eq!(aabb3.max, Vec2::new(f32::MAX / 2.0, f32::MAX / 2.0));
|
||||
|
||||
let bounding_circle = Plane2d::new(Vec2::Y).bounding_circle(translation, 0.0);
|
||||
let bounding_circle = Plane2d::new(Vec2::Y).bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), f32::MAX / 2.0);
|
||||
}
|
||||
|
@ -802,23 +810,24 @@ mod tests {
|
|||
#[test]
|
||||
fn line() {
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb1 = Line2d { direction: Dir2::Y }.aabb_2d(translation, 0.0);
|
||||
let aabb1 = Line2d { direction: Dir2::Y }.aabb_2d(isometry);
|
||||
assert_eq!(aabb1.min, Vec2::new(2.0, -f32::MAX / 2.0));
|
||||
assert_eq!(aabb1.max, Vec2::new(2.0, f32::MAX / 2.0));
|
||||
|
||||
let aabb2 = Line2d { direction: Dir2::X }.aabb_2d(translation, 0.0);
|
||||
let aabb2 = Line2d { direction: Dir2::X }.aabb_2d(isometry);
|
||||
assert_eq!(aabb2.min, Vec2::new(-f32::MAX / 2.0, 1.0));
|
||||
assert_eq!(aabb2.max, Vec2::new(f32::MAX / 2.0, 1.0));
|
||||
|
||||
let aabb3 = Line2d {
|
||||
direction: Dir2::from_xy(1.0, 1.0).unwrap(),
|
||||
}
|
||||
.aabb_2d(translation, 0.0);
|
||||
.aabb_2d(isometry);
|
||||
assert_eq!(aabb3.min, Vec2::new(-f32::MAX / 2.0, -f32::MAX / 2.0));
|
||||
assert_eq!(aabb3.max, Vec2::new(f32::MAX / 2.0, f32::MAX / 2.0));
|
||||
|
||||
let bounding_circle = Line2d { direction: Dir2::Y }.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = Line2d { direction: Dir2::Y }.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), f32::MAX / 2.0);
|
||||
}
|
||||
|
@ -826,13 +835,14 @@ mod tests {
|
|||
#[test]
|
||||
fn segment() {
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
let segment = Segment2d::from_points(Vec2::new(-1.0, -0.5), Vec2::new(1.0, 0.5)).0;
|
||||
|
||||
let aabb = segment.aabb_2d(translation, 0.0);
|
||||
let aabb = segment.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(1.0, 0.5));
|
||||
assert_eq!(aabb.max, Vec2::new(3.0, 1.5));
|
||||
|
||||
let bounding_circle = segment.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = segment.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 1.0_f32.hypot(0.5));
|
||||
}
|
||||
|
@ -846,12 +856,13 @@ mod tests {
|
|||
Vec2::new(1.0, -1.0),
|
||||
]);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = polyline.aabb_2d(translation, 0.0);
|
||||
let aabb = polyline.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
|
||||
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
|
||||
|
||||
let bounding_circle = polyline.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = polyline.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), std::f32::consts::SQRT_2);
|
||||
}
|
||||
|
@ -861,14 +872,15 @@ mod tests {
|
|||
let acute_triangle =
|
||||
Triangle2d::new(Vec2::new(0.0, 1.0), Vec2::NEG_ONE, Vec2::new(1.0, -1.0));
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = acute_triangle.aabb_2d(translation, 0.0);
|
||||
let aabb = acute_triangle.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
|
||||
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
|
||||
|
||||
// For acute triangles, the center is the circumcenter
|
||||
let (Circle { radius }, circumcenter) = acute_triangle.circumcircle();
|
||||
let bounding_circle = acute_triangle.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = acute_triangle.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, circumcenter + translation);
|
||||
assert_eq!(bounding_circle.radius(), radius);
|
||||
}
|
||||
|
@ -881,13 +893,14 @@ mod tests {
|
|||
Vec2::new(10.0, -1.0),
|
||||
);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = obtuse_triangle.aabb_2d(translation, 0.0);
|
||||
let aabb = obtuse_triangle.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(-8.0, 0.0));
|
||||
assert_eq!(aabb.max, Vec2::new(12.0, 2.0));
|
||||
|
||||
// For obtuse and right triangles, the center is the midpoint of the longest side (diameter of bounding circle)
|
||||
let bounding_circle = obtuse_triangle.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = obtuse_triangle.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation - Vec2::Y);
|
||||
assert_eq!(bounding_circle.radius(), 10.0);
|
||||
}
|
||||
|
@ -897,12 +910,15 @@ mod tests {
|
|||
let rectangle = Rectangle::new(2.0, 1.0);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
|
||||
let aabb = rectangle.aabb_2d(translation, std::f32::consts::FRAC_PI_4);
|
||||
let aabb = rectangle.aabb_2d(Isometry2d::new(
|
||||
translation,
|
||||
Rot2::radians(std::f32::consts::FRAC_PI_4),
|
||||
));
|
||||
let expected_half_size = Vec2::splat(1.0606601);
|
||||
assert_eq!(aabb.min, translation - expected_half_size);
|
||||
assert_eq!(aabb.max, translation + expected_half_size);
|
||||
|
||||
let bounding_circle = rectangle.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = rectangle.bounding_circle(Isometry2d::from_translation(translation));
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 1.0_f32.hypot(0.5));
|
||||
}
|
||||
|
@ -916,12 +932,13 @@ mod tests {
|
|||
Vec2::new(1.0, -1.0),
|
||||
]);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = polygon.aabb_2d(translation, 0.0);
|
||||
let aabb = polygon.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, Vec2::new(1.0, 0.0));
|
||||
assert_eq!(aabb.max, Vec2::new(3.0, 2.0));
|
||||
|
||||
let bounding_circle = polygon.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = polygon.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), std::f32::consts::SQRT_2);
|
||||
}
|
||||
|
@ -930,12 +947,13 @@ mod tests {
|
|||
fn regular_polygon() {
|
||||
let regular_polygon = RegularPolygon::new(1.0, 5);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = regular_polygon.aabb_2d(translation, 0.0);
|
||||
let aabb = regular_polygon.aabb_2d(isometry);
|
||||
assert!((aabb.min - (translation - Vec2::new(0.9510565, 0.8090169))).length() < 1e-6);
|
||||
assert!((aabb.max - (translation + Vec2::new(0.9510565, 1.0))).length() < 1e-6);
|
||||
|
||||
let bounding_circle = regular_polygon.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = regular_polygon.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 1.0);
|
||||
}
|
||||
|
@ -944,12 +962,13 @@ mod tests {
|
|||
fn capsule() {
|
||||
let capsule = Capsule2d::new(0.5, 2.0);
|
||||
let translation = Vec2::new(2.0, 1.0);
|
||||
let isometry = Isometry2d::from_translation(translation);
|
||||
|
||||
let aabb = capsule.aabb_2d(translation, 0.0);
|
||||
let aabb = capsule.aabb_2d(isometry);
|
||||
assert_eq!(aabb.min, translation - Vec2::new(0.5, 1.5));
|
||||
assert_eq!(aabb.max, translation + Vec2::new(0.5, 1.5));
|
||||
|
||||
let bounding_circle = capsule.bounding_circle(translation, 0.0);
|
||||
let bounding_circle = capsule.bounding_circle(isometry);
|
||||
assert_eq!(bounding_circle.center, translation);
|
||||
assert_eq!(bounding_circle.radius(), 1.5);
|
||||
}
|
||||
|
|
|
@ -7,194 +7,176 @@ use crate::primitives::{
|
|||
BoxedPolygon, BoxedPolyline2d, Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d,
|
||||
Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d,
|
||||
};
|
||||
use crate::{Quat, Vec3};
|
||||
use crate::{Isometry2d, Isometry3d, Quat, Rot2};
|
||||
|
||||
use crate::{bounding::Bounded2d, primitives::Circle};
|
||||
|
||||
use super::{Aabb3d, Bounded3d, BoundingSphere};
|
||||
|
||||
impl BoundedExtrusion for Circle {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
// Reference: http://iquilezles.org/articles/diskbbox/
|
||||
|
||||
let segment_dir = rotation * Vec3::Z;
|
||||
let segment_dir = isometry.rotation * Vec3A::Z;
|
||||
let top = (segment_dir * half_depth).abs();
|
||||
|
||||
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
|
||||
let half_size = self.radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
|
||||
let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
|
||||
Aabb3d {
|
||||
min: (translation - half_size - top).into(),
|
||||
max: (translation + half_size + top).into(),
|
||||
min: isometry.translation - half_size - top,
|
||||
max: isometry.translation + half_size + top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for Ellipse {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let Vec2 { x: a, y: b } = self.half_size;
|
||||
let normal = rotation * Vec3::Z;
|
||||
let conjugate_rot = rotation.conjugate();
|
||||
let normal = isometry.rotation * Vec3A::Z;
|
||||
let conjugate_rot = isometry.rotation.conjugate();
|
||||
|
||||
let [max_x, max_y, max_z] = Vec3::AXES.map(|axis: Vec3| {
|
||||
let [max_x, max_y, max_z] = Vec3A::AXES.map(|axis| {
|
||||
let Some(axis) = (conjugate_rot * axis.reject_from(normal))
|
||||
.xy()
|
||||
.try_normalize()
|
||||
else {
|
||||
return Vec3::ZERO;
|
||||
return Vec3A::ZERO;
|
||||
};
|
||||
|
||||
if axis.element_product() == 0. {
|
||||
return rotation * Vec3::new(a * axis.y, b * axis.x, 0.);
|
||||
return isometry.rotation * Vec3A::new(a * axis.y, b * axis.x, 0.);
|
||||
}
|
||||
let m = -axis.x / axis.y;
|
||||
let signum = axis.signum();
|
||||
|
||||
let y = signum.y * b * b / (b * b + m * m * a * a).sqrt();
|
||||
let x = signum.x * a * (1. - y * y / b / b).sqrt();
|
||||
rotation * Vec3::new(x, y, 0.)
|
||||
isometry.rotation * Vec3A::new(x, y, 0.)
|
||||
});
|
||||
|
||||
let half_size = Vec3::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
|
||||
Aabb3d::new(translation, half_size)
|
||||
let half_size = Vec3A::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for Line2d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let dir = rotation * self.direction.extend(0.);
|
||||
let half_depth = (rotation * Vec3::new(0., 0., half_depth)).abs();
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));
|
||||
let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();
|
||||
|
||||
let max = f32::MAX / 2.;
|
||||
let half_size = Vec3::new(
|
||||
let half_size = Vec3A::new(
|
||||
if dir.x == 0. { half_depth.x } else { max },
|
||||
if dir.y == 0. { half_depth.y } else { max },
|
||||
if dir.z == 0. { half_depth.z } else { max },
|
||||
);
|
||||
|
||||
Aabb3d::new(translation, half_size)
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for Segment2d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let half_size = rotation * self.point1().extend(0.);
|
||||
let depth = rotation * Vec3::new(0., 0., half_depth);
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
Aabb3d::new(translation, half_size.abs() + depth.abs())
|
||||
Aabb3d::new(isometry.translation, half_size.abs() + depth.abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(
|
||||
translation,
|
||||
rotation,
|
||||
self.vertices.map(|v| v.extend(0.)).into_iter(),
|
||||
);
|
||||
let depth = rotation * Vec3A::new(0., 0., half_depth);
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let aabb =
|
||||
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
aabb.grow(depth.abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for BoxedPolyline2d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(
|
||||
translation,
|
||||
rotation,
|
||||
self.vertices.iter().map(|v| v.extend(0.)),
|
||||
);
|
||||
let depth = rotation * Vec3A::new(0., 0., half_depth);
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
aabb.grow(depth.abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for Triangle2d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(
|
||||
translation,
|
||||
rotation,
|
||||
self.vertices.iter().map(|v| v.extend(0.)),
|
||||
);
|
||||
let depth = rotation * Vec3A::new(0., 0., half_depth);
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
aabb.grow(depth.abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for Rectangle {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
Cuboid {
|
||||
half_size: self.half_size.extend(half_depth),
|
||||
}
|
||||
.aabb_3d(translation, rotation)
|
||||
.aabb_3d(isometry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> BoundedExtrusion for Polygon<N> {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(
|
||||
translation,
|
||||
rotation,
|
||||
self.vertices.map(|v| v.extend(0.)).into_iter(),
|
||||
);
|
||||
let depth = rotation * Vec3A::new(0., 0., half_depth);
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let aabb =
|
||||
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
aabb.grow(depth.abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for BoxedPolygon {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(
|
||||
translation,
|
||||
rotation,
|
||||
self.vertices.iter().map(|v| v.extend(0.)),
|
||||
);
|
||||
let depth = rotation * Vec3A::new(0., 0., half_depth);
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
aabb.grow(depth.abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for RegularPolygon {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let aabb = Aabb3d::from_point_cloud(
|
||||
translation,
|
||||
rotation,
|
||||
isometry,
|
||||
self.vertices(0.).into_iter().map(|v| v.extend(0.)),
|
||||
);
|
||||
let depth = rotation * Vec3A::new(0., 0., half_depth);
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
aabb.grow(depth.abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedExtrusion for Capsule2d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let aabb = Cylinder {
|
||||
half_height: half_depth,
|
||||
radius: self.radius,
|
||||
}
|
||||
.aabb_3d(Vec3::ZERO, rotation * Quat::from_rotation_x(FRAC_PI_2));
|
||||
.aabb_3d(Isometry3d::from_rotation(
|
||||
isometry.rotation * Quat::from_rotation_x(FRAC_PI_2),
|
||||
));
|
||||
|
||||
let up = rotation * Vec3::new(0., self.half_length, 0.);
|
||||
let half_size = Into::<Vec3>::into(aabb.max) + up.abs();
|
||||
Aabb3d::new(translation, half_size)
|
||||
let up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);
|
||||
let half_size = aabb.max + up.abs();
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
self.base_shape
|
||||
.extrusion_aabb_3d(self.half_depth, translation, rotation)
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
self.base_shape
|
||||
.extrusion_bounding_sphere(self.half_depth, translation, rotation)
|
||||
.extrusion_bounding_sphere(self.half_depth, isometry)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,12 +188,12 @@ impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
|
|||
/// `impl BoundedExtrusion for MyShape {}`
|
||||
pub trait BoundedExtrusion: Primitive2d + Bounded2d {
|
||||
/// Get an axis-aligned bounding box for an extrusion with this shape as a base and the given `half_depth`, transformed by the given `translation` and `rotation`.
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let cap_normal = rotation * Vec3::Z;
|
||||
let conjugate_rot = rotation.conjugate();
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
|
||||
let cap_normal = isometry.rotation * Vec3A::Z;
|
||||
let conjugate_rot = isometry.rotation.conjugate();
|
||||
|
||||
// The `(halfsize, offset)` for each axis
|
||||
let axis_values = Vec3::AXES.map(|ax| {
|
||||
let axis_values = Vec3A::AXES.map(|ax| {
|
||||
// This is the direction of the line of intersection of a plane with the `ax` normal and the plane containing the cap of the extrusion.
|
||||
let intersect_line = ax.cross(cap_normal);
|
||||
if intersect_line.length_squared() <= f32::EPSILON {
|
||||
|
@ -228,24 +210,19 @@ pub trait BoundedExtrusion: Primitive2d + Bounded2d {
|
|||
|
||||
// Calculate the `Aabb2d` of the base shape. The shape is rotated so that the line of intersection is parallel to the Y axis in the `Aabb2d` calculations.
|
||||
// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane
|
||||
let aabb2d = self.aabb_2d(Vec2::ZERO, angle);
|
||||
let aabb2d = self.aabb_2d(Isometry2d::from_rotation(Rot2::radians(angle)));
|
||||
(aabb2d.half_size().x * scale, aabb2d.center().x * scale)
|
||||
});
|
||||
|
||||
let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));
|
||||
let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();
|
||||
let depth = rotation * Vec3A::new(0., 0., half_depth);
|
||||
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
|
||||
|
||||
Aabb3d::new(Vec3A::from(translation) - offset, cap_size + depth.abs())
|
||||
Aabb3d::new(isometry.translation - offset, cap_size + depth.abs())
|
||||
}
|
||||
|
||||
/// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation
|
||||
fn extrusion_bounding_sphere(
|
||||
&self,
|
||||
half_depth: f32,
|
||||
translation: Vec3,
|
||||
rotation: Quat,
|
||||
) -> BoundingSphere {
|
||||
fn extrusion_bounding_sphere(&self, half_depth: f32, isometry: Isometry3d) -> BoundingSphere {
|
||||
// We calculate the bounding circle of the base shape.
|
||||
// Since each of the extrusions bases will have the same distance from its center,
|
||||
// and they are just shifted along the Z-axis, the minimum bounding sphere will be the bounding sphere
|
||||
|
@ -253,9 +230,9 @@ pub trait BoundedExtrusion: Primitive2d + Bounded2d {
|
|||
let BoundingCircle {
|
||||
center,
|
||||
circle: Circle { radius },
|
||||
} = self.bounding_circle(Vec2::ZERO, 0.);
|
||||
} = self.bounding_circle(Isometry2d::IDENTITY);
|
||||
let radius = radius.hypot(half_depth);
|
||||
let center = translation + rotation * center.extend(0.);
|
||||
let center = isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.));
|
||||
|
||||
BoundingSphere::new(center, radius)
|
||||
}
|
||||
|
@ -273,19 +250,20 @@ mod tests {
|
|||
Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,
|
||||
RegularPolygon, Segment2d, Triangle2d,
|
||||
},
|
||||
Dir2,
|
||||
Dir2, Isometry3d,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn circle() {
|
||||
let cylinder = Extrusion::new(Circle::new(0.5), 2.0);
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = cylinder.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = cylinder.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), Vec3A::from(translation));
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));
|
||||
|
||||
let bounding_sphere = cylinder.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = cylinder.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1f32.hypot(0.5));
|
||||
}
|
||||
|
@ -295,12 +273,13 @@ mod tests {
|
|||
let extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), Vec3A::from(translation));
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 8f32.sqrt());
|
||||
}
|
||||
|
@ -315,12 +294,13 @@ mod tests {
|
|||
);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_y(FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));
|
||||
assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center(), translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.);
|
||||
}
|
||||
|
@ -330,12 +310,13 @@ mod tests {
|
|||
let extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_z(std::f32::consts::FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), translation.into());
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 2.291288);
|
||||
}
|
||||
|
@ -345,12 +326,13 @@ mod tests {
|
|||
let extrusion = Extrusion::new(Segment2d::new(Dir2::new_unchecked(Vec2::NEG_Y), 3.), 4.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), translation.into());
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 2.5);
|
||||
}
|
||||
|
@ -366,12 +348,13 @@ mod tests {
|
|||
let extrusion = Extrusion::new(polyline, 3.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), translation.into());
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 2.0615528);
|
||||
}
|
||||
|
@ -386,12 +369,13 @@ mod tests {
|
|||
let extrusion = Extrusion::new(triangle, 3.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), translation.into());
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(
|
||||
bounding_sphere.center,
|
||||
Vec3A::new(3.0, 3.2928934, 4.2928934)
|
||||
|
@ -410,12 +394,13 @@ mod tests {
|
|||
let extrusion = Extrusion::new(polygon, 3.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), translation.into());
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 2.0615528);
|
||||
}
|
||||
|
@ -425,8 +410,9 @@ mod tests {
|
|||
let extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(
|
||||
aabb.center(),
|
||||
Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)
|
||||
|
@ -436,7 +422,7 @@ mod tests {
|
|||
Vec3A::new(1.9498558, 2.7584014, 2.7584019)
|
||||
);
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 8f32.sqrt());
|
||||
}
|
||||
|
@ -446,12 +432,13 @@ mod tests {
|
|||
let extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);
|
||||
let translation = Vec3::new(3., 4., 5.);
|
||||
let rotation = Quat::from_rotation_x(FRAC_PI_4);
|
||||
let isometry = Isometry3d::new(translation, rotation);
|
||||
|
||||
let aabb = extrusion.aabb_3d(translation, rotation);
|
||||
let aabb = extrusion.aabb_3d(isometry);
|
||||
assert_eq!(aabb.center(), translation.into());
|
||||
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735));
|
||||
|
||||
let bounding_sphere = extrusion.bounding_sphere(translation, rotation);
|
||||
let bounding_sphere = extrusion.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 2.5);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ mod primitive_impls;
|
|||
use glam::Mat3;
|
||||
|
||||
use super::{BoundingVolume, IntersectsVolume};
|
||||
use crate::{Quat, Vec3, Vec3A};
|
||||
use crate::{Isometry3d, Quat, Vec3A};
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
|
@ -24,12 +24,12 @@ fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3
|
|||
acc / len as f32
|
||||
}
|
||||
|
||||
/// A trait with methods that return 3D bounded volumes for a shape
|
||||
/// A trait with methods that return 3D bounding volumes for a shape.
|
||||
pub trait Bounded3d {
|
||||
/// Get an axis-aligned bounding box for the shape with the given translation and rotation
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d;
|
||||
/// Get a bounding sphere for the shape with the given translation and rotation
|
||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere;
|
||||
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d;
|
||||
/// Get a bounding sphere for the shape translated and rotated by the given isometry.
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere;
|
||||
}
|
||||
|
||||
/// A 3D axis-aligned bounding box
|
||||
|
@ -55,19 +55,18 @@ impl Aabb3d {
|
|||
}
|
||||
|
||||
/// Computes the smallest [`Aabb3d`] containing the given set of points,
|
||||
/// transformed by `translation` and `rotation`.
|
||||
/// transformed by the rotation and translation of the given isometry.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the given set of points is empty.
|
||||
#[inline(always)]
|
||||
pub fn from_point_cloud(
|
||||
translation: impl Into<Vec3A>,
|
||||
rotation: Quat,
|
||||
isometry: Isometry3d,
|
||||
points: impl Iterator<Item = impl Into<Vec3A>>,
|
||||
) -> Aabb3d {
|
||||
// Transform all points by rotation
|
||||
let mut iter = points.map(|point| rotation * point.into());
|
||||
let mut iter = points.map(|point| isometry.rotation * point.into());
|
||||
|
||||
let first = iter
|
||||
.next()
|
||||
|
@ -77,10 +76,9 @@ impl Aabb3d {
|
|||
(point.min(prev_min), point.max(prev_max))
|
||||
});
|
||||
|
||||
let translation = translation.into();
|
||||
Aabb3d {
|
||||
min: min + translation,
|
||||
max: max + translation,
|
||||
min: min + isometry.translation,
|
||||
max: max + isometry.translation,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,13 +471,12 @@ impl BoundingSphere {
|
|||
}
|
||||
|
||||
/// Computes a [`BoundingSphere`] containing the given set of points,
|
||||
/// transformed by `translation` and `rotation`.
|
||||
/// transformed by the rotation and translation of the given isometry.
|
||||
///
|
||||
/// The bounding sphere is not guaranteed to be the smallest possible.
|
||||
#[inline(always)]
|
||||
pub fn from_point_cloud(
|
||||
translation: impl Into<Vec3A>,
|
||||
rotation: Quat,
|
||||
isometry: Isometry3d,
|
||||
points: &[impl Copy + Into<Vec3A>],
|
||||
) -> BoundingSphere {
|
||||
let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));
|
||||
|
@ -493,10 +490,7 @@ impl BoundingSphere {
|
|||
}
|
||||
}
|
||||
|
||||
BoundingSphere::new(
|
||||
rotation * center + translation.into(),
|
||||
radius_squared.sqrt(),
|
||||
)
|
||||
BoundingSphere::new(isometry * center, radius_squared.sqrt())
|
||||
}
|
||||
|
||||
/// Get the radius of the bounding sphere
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
//! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives).
|
||||
|
||||
use glam::Vec3A;
|
||||
|
||||
use crate::{
|
||||
bounding::{Bounded2d, BoundingCircle},
|
||||
primitives::{
|
||||
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d,
|
||||
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, Triangle3d,
|
||||
},
|
||||
Dir3, Mat3, Quat, Vec2, Vec3,
|
||||
Isometry2d, Isometry3d, Mat3, Vec2, Vec3,
|
||||
};
|
||||
|
||||
use super::{Aabb3d, Bounded3d, BoundingSphere};
|
||||
|
||||
impl Bounded3d for Sphere {
|
||||
fn aabb_3d(&self, translation: Vec3, _rotation: Quat) -> Aabb3d {
|
||||
Aabb3d::new(translation, Vec3::splat(self.radius))
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
Aabb3d::new(isometry.translation, Vec3::splat(self.radius))
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::new(translation, self.radius)
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::new(isometry.translation, self.radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for InfinitePlane3d {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let normal = rotation * *self.normal;
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
let normal = isometry.rotation * *self.normal;
|
||||
let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;
|
||||
let facing_y = normal == Vec3::Y || normal == Vec3::NEG_Y;
|
||||
let facing_z = normal == Vec3::Z || normal == Vec3::NEG_Z;
|
||||
|
@ -33,19 +35,19 @@ impl Bounded3d for InfinitePlane3d {
|
|||
let half_width = if facing_x { 0.0 } else { f32::MAX / 2.0 };
|
||||
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
|
||||
let half_depth = if facing_z { 0.0 } else { f32::MAX / 2.0 };
|
||||
let half_size = Vec3::new(half_width, half_height, half_depth);
|
||||
let half_size = Vec3A::new(half_width, half_height, half_depth);
|
||||
|
||||
Aabb3d::new(translation, half_size)
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::new(translation, f32::MAX / 2.0)
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Line3d {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
let direction = rotation * *self.direction;
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
let direction = isometry.rotation * *self.direction;
|
||||
|
||||
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
|
||||
// like growing or shrinking the AABB without breaking things.
|
||||
|
@ -53,55 +55,55 @@ impl Bounded3d for Line3d {
|
|||
let half_width = if direction.x == 0.0 { 0.0 } else { max };
|
||||
let half_height = if direction.y == 0.0 { 0.0 } else { max };
|
||||
let half_depth = if direction.z == 0.0 { 0.0 } else { max };
|
||||
let half_size = Vec3::new(half_width, half_height, half_depth);
|
||||
let half_size = Vec3A::new(half_width, half_height, half_depth);
|
||||
|
||||
Aabb3d::new(translation, half_size)
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::new(translation, f32::MAX / 2.0)
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Segment3d {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
// Rotate the segment by `rotation`
|
||||
let direction = rotation * *self.direction;
|
||||
let direction = isometry.rotation * *self.direction;
|
||||
let half_size = (self.half_length * direction).abs();
|
||||
|
||||
Aabb3d::new(translation, half_size)
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::new(translation, self.half_length)
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::new(isometry.translation, self.half_length)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Bounded3d for Polyline3d<N> {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
Aabb3d::from_point_cloud(translation, rotation, self.vertices.iter().copied())
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for BoxedPolyline3d {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
Aabb3d::from_point_cloud(translation, rotation, self.vertices.iter().copied())
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::from_point_cloud(translation, rotation, &self.vertices)
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::from_point_cloud(isometry, &self.vertices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Cuboid {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
// Compute the AABB of the rotated cuboid by transforming the half-size
|
||||
// by an absolute rotation matrix.
|
||||
let rot_mat = Mat3::from_quat(rotation);
|
||||
let rot_mat = Mat3::from_quat(isometry.rotation);
|
||||
let abs_rot_mat = Mat3::from_cols(
|
||||
rot_mat.x_axis.abs(),
|
||||
rot_mat.y_axis.abs(),
|
||||
|
@ -109,80 +111,77 @@ impl Bounded3d for Cuboid {
|
|||
);
|
||||
let half_size = abs_rot_mat * self.half_size;
|
||||
|
||||
Aabb3d::new(translation, half_size)
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::new(translation, self.half_size.length())
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::new(isometry.translation, self.half_size.length())
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Cylinder {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
// Reference: http://iquilezles.org/articles/diskbbox/
|
||||
|
||||
let segment_dir = rotation * Vec3::Y;
|
||||
let segment_dir = isometry.rotation * Vec3A::Y;
|
||||
let top = segment_dir * self.half_height;
|
||||
let bottom = -top;
|
||||
|
||||
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
|
||||
let half_size = self.radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
|
||||
let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
|
||||
Aabb3d {
|
||||
min: (translation + (top - half_size).min(bottom - half_size)).into(),
|
||||
max: (translation + (top + half_size).max(bottom + half_size)).into(),
|
||||
min: isometry.translation + (top - half_size).min(bottom - half_size),
|
||||
max: isometry.translation + (top + half_size).max(bottom + half_size),
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
let radius = self.radius.hypot(self.half_height);
|
||||
BoundingSphere::new(translation, radius)
|
||||
BoundingSphere::new(isometry.translation, radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Capsule3d {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
// Get the line segment between the hemispheres of the rotated capsule
|
||||
let segment = Segment3d {
|
||||
// Multiplying a normalized vector (Vec3::Y) with a rotation returns a normalized vector.
|
||||
direction: rotation * Dir3::Y,
|
||||
half_length: self.half_length,
|
||||
};
|
||||
let (a, b) = (segment.point1(), segment.point2());
|
||||
let segment_dir = isometry.rotation * Vec3A::Y;
|
||||
let top = segment_dir * self.half_length;
|
||||
let bottom = -top;
|
||||
|
||||
// Expand the line segment by the capsule radius to get the capsule half-extents
|
||||
let min = a.min(b) - Vec3::splat(self.radius);
|
||||
let max = a.max(b) + Vec3::splat(self.radius);
|
||||
let min = bottom.min(top) - Vec3A::splat(self.radius);
|
||||
let max = bottom.max(top) + Vec3A::splat(self.radius);
|
||||
|
||||
Aabb3d {
|
||||
min: (min + translation).into(),
|
||||
max: (max + translation).into(),
|
||||
min: min + isometry.translation,
|
||||
max: max + isometry.translation,
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::new(translation, self.radius + self.half_length)
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::new(isometry.translation, self.radius + self.half_length)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Cone {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
// Reference: http://iquilezles.org/articles/diskbbox/
|
||||
|
||||
let segment_dir = rotation * Vec3::Y;
|
||||
let segment_dir = isometry.rotation * Vec3A::Y;
|
||||
let top = segment_dir * 0.5 * self.height;
|
||||
let bottom = -top;
|
||||
|
||||
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
|
||||
let half_extents = Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
|
||||
let half_extents = Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
|
||||
Aabb3d {
|
||||
min: (translation + top.min(bottom - self.radius * half_extents)).into(),
|
||||
max: (translation + top.max(bottom + self.radius * half_extents)).into(),
|
||||
min: isometry.translation + top.min(bottom - self.radius * half_extents),
|
||||
max: isometry.translation + top.max(bottom + self.radius * half_extents),
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
// Get the triangular cross-section of the cone.
|
||||
let half_height = 0.5 * self.height;
|
||||
let triangle = Triangle2d::new(
|
||||
|
@ -193,36 +192,37 @@ impl Bounded3d for Cone {
|
|||
|
||||
// Because of circular symmetry, we can use the bounding circle of the triangle
|
||||
// for the bounding sphere of the cone.
|
||||
let BoundingCircle { circle, center } = triangle.bounding_circle(Vec2::ZERO, 0.0);
|
||||
let BoundingCircle { circle, center } = triangle.bounding_circle(Isometry2d::IDENTITY);
|
||||
|
||||
BoundingSphere::new(rotation * center.extend(0.0) + translation, circle.radius)
|
||||
BoundingSphere::new(
|
||||
isometry.rotation * Vec3A::from(center.extend(0.0)) + isometry.translation,
|
||||
circle.radius,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for ConicalFrustum {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
// Reference: http://iquilezles.org/articles/diskbbox/
|
||||
|
||||
let segment_dir = rotation * Vec3::Y;
|
||||
let segment_dir = isometry.rotation * Vec3A::Y;
|
||||
let top = segment_dir * 0.5 * self.height;
|
||||
let bottom = -top;
|
||||
|
||||
let e = (Vec3::ONE - segment_dir * segment_dir).max(Vec3::ZERO);
|
||||
let half_extents = Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
|
||||
let half_extents = Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
|
||||
Aabb3d {
|
||||
min: (translation
|
||||
min: isometry.translation
|
||||
+ (top - self.radius_top * half_extents)
|
||||
.min(bottom - self.radius_bottom * half_extents))
|
||||
.into(),
|
||||
max: (translation
|
||||
.min(bottom - self.radius_bottom * half_extents),
|
||||
max: isometry.translation
|
||||
+ (top + self.radius_top * half_extents)
|
||||
.max(bottom + self.radius_bottom * half_extents))
|
||||
.into(),
|
||||
.max(bottom + self.radius_bottom * half_extents),
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
let half_height = 0.5 * self.height;
|
||||
|
||||
// To compute the bounding sphere, we'll get the center and radius of the circumcircle
|
||||
|
@ -277,42 +277,45 @@ impl Bounded3d for ConicalFrustum {
|
|||
(circumcenter, a.distance(circumcenter))
|
||||
};
|
||||
|
||||
BoundingSphere::new(translation + rotation * center.extend(0.0), radius)
|
||||
BoundingSphere::new(
|
||||
isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.0)),
|
||||
radius,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Torus {
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
// Compute the AABB of a flat disc with the major radius of the torus.
|
||||
// Reference: http://iquilezles.org/articles/diskbbox/
|
||||
let normal = rotation * Vec3::Y;
|
||||
let e = (Vec3::ONE - normal * normal).max(Vec3::ZERO);
|
||||
let disc_half_size = self.major_radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
let normal = isometry.rotation * Vec3A::Y;
|
||||
let e = (Vec3A::ONE - normal * normal).max(Vec3A::ZERO);
|
||||
let disc_half_size = self.major_radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
|
||||
|
||||
// Expand the disc by the minor radius to get the torus half-size
|
||||
let half_size = disc_half_size + Vec3::splat(self.minor_radius);
|
||||
let half_size = disc_half_size + Vec3A::splat(self.minor_radius);
|
||||
|
||||
Aabb3d::new(translation, half_size)
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
BoundingSphere::new(translation, self.outer_radius())
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
BoundingSphere::new(isometry.translation, self.outer_radius())
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounded3d for Triangle3d {
|
||||
/// Get the bounding box of the triangle.
|
||||
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d {
|
||||
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
|
||||
let [a, b, c] = self.vertices;
|
||||
|
||||
let a = rotation * a;
|
||||
let b = rotation * b;
|
||||
let c = rotation * c;
|
||||
let a = isometry.rotation * a;
|
||||
let b = isometry.rotation * b;
|
||||
let c = isometry.rotation * c;
|
||||
|
||||
let min = a.min(b).min(c);
|
||||
let max = a.max(b).max(c);
|
||||
let min = Vec3A::from(a.min(b).min(c));
|
||||
let max = Vec3A::from(a.max(b).max(c));
|
||||
|
||||
let bounding_center = (max + min) / 2.0 + translation;
|
||||
let bounding_center = (max + min) / 2.0 + isometry.translation;
|
||||
let half_extents = (max - min) / 2.0;
|
||||
|
||||
Aabb3d::new(bounding_center, half_extents)
|
||||
|
@ -323,25 +326,26 @@ impl Bounded3d for Triangle3d {
|
|||
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
|
||||
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
|
||||
/// that contains the largest side of the triangle.
|
||||
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
|
||||
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
|
||||
if self.is_degenerate() || self.is_obtuse() {
|
||||
let (p1, p2) = self.largest_side();
|
||||
let (p1, p2) = (Vec3A::from(p1), Vec3A::from(p2));
|
||||
let mid_point = (p1 + p2) / 2.0;
|
||||
let radius = mid_point.distance(p1);
|
||||
BoundingSphere::new(mid_point + translation, radius)
|
||||
BoundingSphere::new(mid_point + isometry.translation, radius)
|
||||
} else {
|
||||
let [a, _, _] = self.vertices;
|
||||
|
||||
let circumcenter = self.circumcenter();
|
||||
let radius = circumcenter.distance(a);
|
||||
BoundingSphere::new(circumcenter + translation, radius)
|
||||
BoundingSphere::new(Vec3A::from(circumcenter) + isometry.translation, radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bounding::BoundingVolume;
|
||||
use crate::{bounding::BoundingVolume, Isometry3d};
|
||||
use glam::{Quat, Vec3, Vec3A};
|
||||
|
||||
use crate::{
|
||||
|
@ -357,12 +361,13 @@ mod tests {
|
|||
fn sphere() {
|
||||
let sphere = Sphere { radius: 1.0 };
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = sphere.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = sphere.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
|
||||
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
|
||||
|
||||
let bounding_sphere = sphere.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = sphere.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1.0);
|
||||
}
|
||||
|
@ -370,25 +375,25 @@ mod tests {
|
|||
#[test]
|
||||
fn plane() {
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(isometry);
|
||||
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));
|
||||
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));
|
||||
|
||||
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(isometry);
|
||||
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));
|
||||
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));
|
||||
|
||||
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(isometry);
|
||||
assert_eq!(aabb3.min, Vec3A::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));
|
||||
assert_eq!(aabb3.max, Vec3A::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));
|
||||
|
||||
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(isometry);
|
||||
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
|
||||
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
|
||||
|
||||
let bounding_sphere =
|
||||
InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
|
||||
}
|
||||
|
@ -396,28 +401,28 @@ mod tests {
|
|||
#[test]
|
||||
fn line() {
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(isometry);
|
||||
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, 0.0));
|
||||
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, 0.0));
|
||||
|
||||
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(isometry);
|
||||
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, 0.0));
|
||||
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, 0.0));
|
||||
|
||||
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(isometry);
|
||||
assert_eq!(aabb3.min, Vec3A::new(2.0, 1.0, -f32::MAX / 2.0));
|
||||
assert_eq!(aabb3.max, Vec3A::new(2.0, 1.0, f32::MAX / 2.0));
|
||||
|
||||
let aabb4 = Line3d {
|
||||
direction: Dir3::from_xyz(1.0, 1.0, 1.0).unwrap(),
|
||||
}
|
||||
.aabb_3d(translation, Quat::IDENTITY);
|
||||
.aabb_3d(isometry);
|
||||
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
|
||||
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
|
||||
|
||||
let bounding_sphere =
|
||||
Line3d { direction: Dir3::Y }.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
|
||||
}
|
||||
|
@ -425,14 +430,16 @@ mod tests {
|
|||
#[test]
|
||||
fn segment() {
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let segment =
|
||||
Segment3d::from_points(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0)).0;
|
||||
|
||||
let aabb = segment.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = segment.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));
|
||||
assert_eq!(aabb.max, Vec3A::new(3.0, 1.5, 0.0));
|
||||
|
||||
let bounding_sphere = segment.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = segment.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(0.5));
|
||||
}
|
||||
|
@ -446,12 +453,13 @@ mod tests {
|
|||
Vec3::new(1.0, -1.0, -1.0),
|
||||
]);
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = polyline.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = polyline.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
|
||||
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
|
||||
|
||||
let bounding_sphere = polyline.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = polyline.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(1.0).hypot(1.0));
|
||||
}
|
||||
|
@ -461,15 +469,15 @@ mod tests {
|
|||
let cuboid = Cuboid::new(2.0, 1.0, 1.0);
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
|
||||
let aabb = cuboid.aabb_3d(
|
||||
let aabb = cuboid.aabb_3d(Isometry3d::new(
|
||||
translation,
|
||||
Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
|
||||
);
|
||||
));
|
||||
let expected_half_size = Vec3A::new(1.0606601, 1.0606601, 0.5);
|
||||
assert_eq!(aabb.min, Vec3A::from(translation) - expected_half_size);
|
||||
assert_eq!(aabb.max, Vec3A::from(translation) + expected_half_size);
|
||||
|
||||
let bounding_sphere = cuboid.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = cuboid.bounding_sphere(Isometry3d::from_translation(translation));
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(0.5).hypot(0.5));
|
||||
}
|
||||
|
@ -478,8 +486,9 @@ mod tests {
|
|||
fn cylinder() {
|
||||
let cylinder = Cylinder::new(0.5, 2.0);
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = cylinder.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = cylinder.aabb_3d(isometry);
|
||||
assert_eq!(
|
||||
aabb.min,
|
||||
Vec3A::from(translation) - Vec3A::new(0.5, 1.0, 0.5)
|
||||
|
@ -489,7 +498,7 @@ mod tests {
|
|||
Vec3A::from(translation) + Vec3A::new(0.5, 1.0, 0.5)
|
||||
);
|
||||
|
||||
let bounding_sphere = cylinder.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = cylinder.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1.0_f32.hypot(0.5));
|
||||
}
|
||||
|
@ -498,8 +507,9 @@ mod tests {
|
|||
fn capsule() {
|
||||
let capsule = Capsule3d::new(0.5, 2.0);
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = capsule.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = capsule.aabb_3d(isometry);
|
||||
assert_eq!(
|
||||
aabb.min,
|
||||
Vec3A::from(translation) - Vec3A::new(0.5, 1.5, 0.5)
|
||||
|
@ -509,7 +519,7 @@ mod tests {
|
|||
Vec3A::from(translation) + Vec3A::new(0.5, 1.5, 0.5)
|
||||
);
|
||||
|
||||
let bounding_sphere = capsule.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = capsule.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1.5);
|
||||
}
|
||||
|
@ -521,12 +531,13 @@ mod tests {
|
|||
height: 2.0,
|
||||
};
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = cone.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = cone.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
|
||||
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
|
||||
|
||||
let bounding_sphere = cone.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = cone.bounding_sphere(isometry);
|
||||
assert_eq!(
|
||||
bounding_sphere.center,
|
||||
Vec3A::from(translation) + Vec3A::NEG_Y * 0.25
|
||||
|
@ -542,12 +553,13 @@ mod tests {
|
|||
height: 2.0,
|
||||
};
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = conical_frustum.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = conical_frustum.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
|
||||
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
|
||||
|
||||
let bounding_sphere = conical_frustum.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = conical_frustum.bounding_sphere(isometry);
|
||||
assert_eq!(
|
||||
bounding_sphere.center,
|
||||
Vec3A::from(translation) + Vec3A::NEG_Y * 0.1875
|
||||
|
@ -563,14 +575,15 @@ mod tests {
|
|||
height: 1.0,
|
||||
};
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = conical_frustum.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = conical_frustum.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(-3.0, 0.5, -5.0));
|
||||
assert_eq!(aabb.max, Vec3A::new(7.0, 1.5, 5.0));
|
||||
|
||||
// For wide conical frusta like this, the circumcenter can be outside the frustum,
|
||||
// so the center and radius should be clamped to the longest side.
|
||||
let bounding_sphere = conical_frustum.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = conical_frustum.bounding_sphere(isometry);
|
||||
assert_eq!(
|
||||
bounding_sphere.center,
|
||||
Vec3A::from(translation) + Vec3A::NEG_Y * 0.5
|
||||
|
@ -585,12 +598,13 @@ mod tests {
|
|||
major_radius: 1.0,
|
||||
};
|
||||
let translation = Vec3::new(2.0, 1.0, 0.0);
|
||||
let isometry = Isometry3d::from_translation(translation);
|
||||
|
||||
let aabb = torus.aabb_3d(translation, Quat::IDENTITY);
|
||||
let aabb = torus.aabb_3d(isometry);
|
||||
assert_eq!(aabb.min, Vec3A::new(0.5, 0.5, -1.5));
|
||||
assert_eq!(aabb.max, Vec3A::new(3.5, 1.5, 1.5));
|
||||
|
||||
let bounding_sphere = torus.bounding_sphere(translation, Quat::IDENTITY);
|
||||
let bounding_sphere = torus.bounding_sphere(isometry);
|
||||
assert_eq!(bounding_sphere.center, translation.into());
|
||||
assert_eq!(bounding_sphere.radius(), 1.5);
|
||||
}
|
||||
|
@ -599,7 +613,7 @@ mod tests {
|
|||
fn triangle3d() {
|
||||
let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
|
||||
|
||||
let br = zero_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
|
||||
let br = zero_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
|
||||
assert_eq!(
|
||||
br.center(),
|
||||
Vec3::ZERO.into(),
|
||||
|
@ -611,7 +625,7 @@ mod tests {
|
|||
"incorrect bounding box half extents"
|
||||
);
|
||||
|
||||
let bs = zero_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
|
||||
let bs = zero_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
|
||||
assert_eq!(
|
||||
bs.center,
|
||||
Vec3::ZERO.into(),
|
||||
|
@ -620,14 +634,14 @@ mod tests {
|
|||
assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius");
|
||||
|
||||
let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
|
||||
let bs = dup_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
|
||||
let bs = dup_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
|
||||
assert_eq!(
|
||||
bs.center,
|
||||
Vec3::new(0.5, 0.0, 0.0).into(),
|
||||
"incorrect bounding sphere center"
|
||||
);
|
||||
assert_eq!(bs.sphere.radius, 0.5, "incorrect bounding sphere radius");
|
||||
let br = dup_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
|
||||
let br = dup_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
|
||||
assert_eq!(
|
||||
br.center(),
|
||||
Vec3::new(0.5, 0.0, 0.0).into(),
|
||||
|
@ -640,14 +654,14 @@ mod tests {
|
|||
);
|
||||
|
||||
let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
|
||||
let bs = collinear_degenerate_triangle.bounding_sphere(Vec3::ZERO, Quat::IDENTITY);
|
||||
let bs = collinear_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
|
||||
assert_eq!(
|
||||
bs.center,
|
||||
Vec3::ZERO.into(),
|
||||
"incorrect bounding sphere center"
|
||||
);
|
||||
assert_eq!(bs.sphere.radius, 1.0, "incorrect bounding sphere radius");
|
||||
let br = collinear_degenerate_triangle.aabb_3d(Vec3::ZERO, Quat::IDENTITY);
|
||||
let br = collinear_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
|
||||
assert_eq!(
|
||||
br.center(),
|
||||
Vec3::ZERO.into(),
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
//! This example demonstrates bounding volume intersections.
|
||||
|
||||
use bevy::{color::palettes::css::*, math::bounding::*, prelude::*};
|
||||
use bevy::{
|
||||
color::palettes::css::*,
|
||||
math::{bounding::*, Isometry2d},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
|
@ -146,26 +150,27 @@ fn update_volumes(
|
|||
for (entity, desired_volume, shape, transform) in query.iter() {
|
||||
let translation = transform.translation.xy();
|
||||
let rotation = transform.rotation.to_euler(EulerRot::YXZ).2;
|
||||
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
|
||||
match desired_volume {
|
||||
DesiredVolume::Aabb => {
|
||||
let aabb = match shape {
|
||||
Shape::Rectangle(r) => r.aabb_2d(translation, rotation),
|
||||
Shape::Circle(c) => c.aabb_2d(translation, rotation),
|
||||
Shape::Triangle(t) => t.aabb_2d(translation, rotation),
|
||||
Shape::Line(l) => l.aabb_2d(translation, rotation),
|
||||
Shape::Capsule(c) => c.aabb_2d(translation, rotation),
|
||||
Shape::Polygon(p) => p.aabb_2d(translation, rotation),
|
||||
Shape::Rectangle(r) => r.aabb_2d(isometry),
|
||||
Shape::Circle(c) => c.aabb_2d(isometry),
|
||||
Shape::Triangle(t) => t.aabb_2d(isometry),
|
||||
Shape::Line(l) => l.aabb_2d(isometry),
|
||||
Shape::Capsule(c) => c.aabb_2d(isometry),
|
||||
Shape::Polygon(p) => p.aabb_2d(isometry),
|
||||
};
|
||||
commands.entity(entity).insert(CurrentVolume::Aabb(aabb));
|
||||
}
|
||||
DesiredVolume::Circle => {
|
||||
let circle = match shape {
|
||||
Shape::Rectangle(r) => r.bounding_circle(translation, rotation),
|
||||
Shape::Circle(c) => c.bounding_circle(translation, rotation),
|
||||
Shape::Triangle(t) => t.bounding_circle(translation, rotation),
|
||||
Shape::Line(l) => l.bounding_circle(translation, rotation),
|
||||
Shape::Capsule(c) => c.bounding_circle(translation, rotation),
|
||||
Shape::Polygon(p) => p.bounding_circle(translation, rotation),
|
||||
Shape::Rectangle(r) => r.bounding_circle(isometry),
|
||||
Shape::Circle(c) => c.bounding_circle(isometry),
|
||||
Shape::Triangle(t) => t.bounding_circle(isometry),
|
||||
Shape::Line(l) => l.bounding_circle(isometry),
|
||||
Shape::Capsule(c) => c.bounding_circle(isometry),
|
||||
Shape::Polygon(p) => p.bounding_circle(isometry),
|
||||
};
|
||||
commands
|
||||
.entity(entity)
|
||||
|
|
|
@ -5,7 +5,10 @@ use std::f32::consts::FRAC_PI_2;
|
|||
|
||||
use bevy::{
|
||||
color::palettes::css::{BLUE, DARK_SLATE_GREY, RED},
|
||||
math::bounding::{Bounded2d, BoundingVolume},
|
||||
math::{
|
||||
bounding::{Bounded2d, BoundingVolume},
|
||||
Isometry2d,
|
||||
},
|
||||
prelude::*,
|
||||
render::mesh::{CircularMeshUvMode, CircularSectorMeshBuilder, CircularSegmentMeshBuilder},
|
||||
sprite::MaterialMesh2dBundle,
|
||||
|
@ -114,11 +117,12 @@ fn draw_bounds<Shape: Bounded2d + Send + Sync + 'static>(
|
|||
let (_, rotation, translation) = transform.to_scale_rotation_translation();
|
||||
let translation = translation.truncate();
|
||||
let rotation = rotation.to_euler(EulerRot::XYZ).2;
|
||||
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
|
||||
|
||||
let aabb = shape.0.aabb_2d(translation, rotation);
|
||||
let aabb = shape.0.aabb_2d(isometry);
|
||||
gizmos.rect_2d(aabb.center(), 0.0, aabb.half_size() * 2.0, RED);
|
||||
|
||||
let bounding_circle = shape.0.bounding_circle(translation, rotation);
|
||||
let bounding_circle = shape.0.bounding_circle(isometry);
|
||||
gizmos.circle_2d(bounding_circle.center, bounding_circle.radius(), BLUE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,11 @@ use std::f32::consts::{PI, SQRT_2};
|
|||
use bevy::{
|
||||
color::palettes::css::{RED, WHITE},
|
||||
input::common_conditions::input_just_pressed,
|
||||
math::bounding::{
|
||||
Aabb2d, Bounded2d, Bounded3d, BoundedExtrusion, BoundingCircle, BoundingVolume,
|
||||
math::{
|
||||
bounding::{
|
||||
Aabb2d, Bounded2d, Bounded3d, BoundedExtrusion, BoundingCircle, BoundingVolume,
|
||||
},
|
||||
Isometry2d,
|
||||
},
|
||||
prelude::*,
|
||||
render::{
|
||||
|
@ -199,18 +202,20 @@ fn bounding_shapes_2d(
|
|||
for transform in shapes.iter() {
|
||||
// Get the rotation angle from the 3D rotation.
|
||||
let rotation = transform.rotation.to_scaled_axis().z;
|
||||
let rotation = Rot2::radians(rotation);
|
||||
let isometry = Isometry2d::new(transform.translation.xy(), rotation);
|
||||
|
||||
match bounding_shape.get() {
|
||||
BoundingShape::None => (),
|
||||
BoundingShape::BoundingBox => {
|
||||
// Get the AABB of the primitive with the rotation and translation of the mesh.
|
||||
let aabb = HEART.aabb_2d(transform.translation.xy(), rotation);
|
||||
let aabb = HEART.aabb_2d(isometry);
|
||||
|
||||
gizmos.rect_2d(aabb.center(), 0., aabb.half_size() * 2., WHITE);
|
||||
}
|
||||
BoundingShape::BoundingSphere => {
|
||||
// Get the bounding sphere of the primitive with the rotation and translation of the mesh.
|
||||
let bounding_circle = HEART.bounding_circle(transform.translation.xy(), rotation);
|
||||
let bounding_circle = HEART.bounding_circle(isometry);
|
||||
|
||||
gizmos
|
||||
.circle_2d(bounding_circle.center(), bounding_circle.radius(), WHITE)
|
||||
|
@ -240,7 +245,7 @@ fn bounding_shapes_3d(
|
|||
BoundingShape::None => (),
|
||||
BoundingShape::BoundingBox => {
|
||||
// Get the AABB of the extrusion with the rotation and translation of the mesh.
|
||||
let aabb = EXTRUSION.aabb_3d(transform.translation, transform.rotation);
|
||||
let aabb = EXTRUSION.aabb_3d(transform.to_isometry());
|
||||
|
||||
gizmos.primitive_3d(
|
||||
&Cuboid::from_size(Vec3::from(aabb.half_size()) * 2.),
|
||||
|
@ -251,8 +256,7 @@ fn bounding_shapes_3d(
|
|||
}
|
||||
BoundingShape::BoundingSphere => {
|
||||
// Get the bounding sphere of the extrusion with the rotation and translation of the mesh.
|
||||
let bounding_sphere =
|
||||
EXTRUSION.bounding_sphere(transform.translation, transform.rotation);
|
||||
let bounding_sphere = EXTRUSION.bounding_sphere(transform.to_isometry());
|
||||
|
||||
gizmos.sphere(
|
||||
bounding_sphere.center().into(),
|
||||
|
@ -338,29 +342,28 @@ impl Measured2d for Heart {
|
|||
|
||||
// The `Bounded2d` or `Bounded3d` traits are used to compute the Axis Aligned Bounding Boxes or bounding circles / spheres for primitives.
|
||||
impl Bounded2d for Heart {
|
||||
fn aabb_2d(&self, translation: Vec2, rotation: impl Into<Rot2>) -> Aabb2d {
|
||||
let rotation = rotation.into();
|
||||
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
|
||||
// The center of the circle at the center of the right wing of the heart
|
||||
let circle_center = rotation * Vec2::new(self.radius, 0.0);
|
||||
let circle_center = isometry.rotation * Vec2::new(self.radius, 0.0);
|
||||
// The maximum X and Y positions of the two circles of the wings of the heart.
|
||||
let max_circle = circle_center.abs() + Vec2::splat(self.radius);
|
||||
// Since the two circles of the heart are mirrored around the origin, the minimum position is the negative of the maximum.
|
||||
let min_circle = -max_circle;
|
||||
|
||||
// The position of the tip at the bottom of the heart
|
||||
let tip_position = rotation * Vec2::new(0.0, -self.radius * (1. + SQRT_2));
|
||||
let tip_position = isometry.rotation * Vec2::new(0.0, -self.radius * (1. + SQRT_2));
|
||||
|
||||
Aabb2d {
|
||||
min: translation + min_circle.min(tip_position),
|
||||
max: translation + max_circle.max(tip_position),
|
||||
min: isometry.translation + min_circle.min(tip_position),
|
||||
max: isometry.translation + max_circle.max(tip_position),
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, translation: Vec2, rotation: impl Into<Rot2>) -> BoundingCircle {
|
||||
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
|
||||
// The bounding circle of the heart is not at its origin. This `offset` is the offset between the center of the bounding circle and its translation.
|
||||
let offset = self.radius / 2f32.powf(1.5);
|
||||
// The center of the bounding circle
|
||||
let center = translation + rotation.into() * Vec2::new(0.0, -offset);
|
||||
let center = isometry * Vec2::new(0.0, -offset);
|
||||
// The radius of the bounding circle
|
||||
let radius = self.radius * (1.0 + 2f32.sqrt()) - offset;
|
||||
|
||||
|
|
Loading…
Reference in a new issue