Allow Bounded3d implementations for custom primitives (#13688)

# Objective

- Due to coherency, it was previously not possible to implement
`Bounded3d` for `Extrusion<MyCustomPrimitive>`. This PR fixes that.

## Solution

- Added a new trait `BoundedExtrusion: Primitive2d + Bounded2d` which
provides functions for bounding boxes and spheres of extrusions of 2D
primitives.
- Changed all implementations of `Bounded3d for Extrusion<T>` to
`BoundedExtrusion for T`
- Implemented `Bounded3d for Extrusion<T: BoundedExtrusion>`
- Removed the `extrusion_bounding_box` and `extrusion_bounding_sphere`
functions and used them as default implementations in `BoundedExtrusion`

## Testing

- This PR does not change any implementations

---------

Co-authored-by: Lynn Büttgenbach <62256001+solis-lumine-vorago@users.noreply.github.com>
Co-authored-by: Matty <weatherleymatthew@gmail.com>
This commit is contained in:
Lynn 2024-06-05 21:40:02 +02:00 committed by GitHub
parent 519abbca11
commit fb3a560a1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 111 additions and 146 deletions

View File

@ -13,30 +13,26 @@ use crate::{bounding::Bounded2d, primitives::Circle};
use super::{Aabb3d, Bounded3d, BoundingSphere}; use super::{Aabb3d, Bounded3d, BoundingSphere};
impl Bounded3d for Extrusion<Circle> { impl BoundedExtrusion for Circle {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/ // Reference: http://iquilezles.org/articles/diskbbox/
let segment_dir = rotation * Vec3::Z; let segment_dir = rotation * Vec3::Z;
let top = (segment_dir * self.half_depth).abs(); let top = (segment_dir * half_depth).abs();
let e = Vec3::ONE - segment_dir * segment_dir; let e = Vec3::ONE - segment_dir * segment_dir;
let half_size = self.base_shape.radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt()); let half_size = self.radius * Vec3::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
Aabb3d { Aabb3d {
min: (translation - half_size - top).into(), min: (translation - half_size - top).into(),
max: (translation + half_size + top).into(), max: (translation + half_size + top).into(),
} }
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<Ellipse> { impl BoundedExtrusion for Ellipse {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let Vec2 { x: a, y: b } = self.base_shape.half_size; let Vec2 { x: a, y: b } = self.half_size;
let normal = rotation * Vec3::Z; let normal = rotation * Vec3::Z;
let conjugate_rot = rotation.conjugate(); let conjugate_rot = rotation.conjugate();
@ -59,20 +55,15 @@ impl Bounded3d for Extrusion<Ellipse> {
rotation * Vec3::new(x, y, 0.) rotation * Vec3::new(x, y, 0.)
}); });
let half_size = let half_size = Vec3::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
Vec3::new(max_x.x, max_y.y, max_z.z).abs() + (normal * self.half_depth).abs();
Aabb3d::new(translation, half_size) Aabb3d::new(translation, half_size)
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<Line2d> { impl BoundedExtrusion for Line2d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let dir = rotation * self.base_shape.direction.extend(0.); let dir = rotation * self.direction.extend(0.);
let half_depth = (rotation * Vec3::new(0., 0., self.half_depth)).abs(); let half_depth = (rotation * Vec3::new(0., 0., half_depth)).abs();
let max = f32::MAX / 2.; let max = f32::MAX / 2.;
let half_size = Vec3::new( let half_size = Vec3::new(
@ -83,167 +74,139 @@ impl Bounded3d for Extrusion<Line2d> {
Aabb3d::new(translation, half_size) Aabb3d::new(translation, half_size)
} }
fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere {
BoundingSphere::new(translation, f32::MAX / 2.)
}
} }
impl Bounded3d for Extrusion<Segment2d> { impl BoundedExtrusion for Segment2d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let half_size = rotation * self.base_shape.point1().extend(0.); let half_size = rotation * self.point1().extend(0.);
let depth = rotation * Vec3::new(0., 0., self.half_depth); let depth = rotation * Vec3::new(0., 0., half_depth);
Aabb3d::new(translation, half_size.abs() + depth.abs()) Aabb3d::new(translation, half_size.abs() + depth.abs())
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl<const N: usize> Bounded3d for Extrusion<Polyline2d<N>> { impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud( let aabb = Aabb3d::from_point_cloud(
translation, translation,
rotation, rotation,
self.base_shape.vertices.map(|v| v.extend(0.)).into_iter(), self.vertices.map(|v| v.extend(0.)).into_iter(),
); );
let depth = rotation * Vec3A::new(0., 0., self.half_depth); let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<BoxedPolyline2d> { impl BoundedExtrusion for BoxedPolyline2d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud( let aabb = Aabb3d::from_point_cloud(
translation, translation,
rotation, rotation,
self.base_shape.vertices.iter().map(|v| v.extend(0.)), self.vertices.iter().map(|v| v.extend(0.)),
); );
let depth = rotation * Vec3A::new(0., 0., self.half_depth); let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<Triangle2d> { impl BoundedExtrusion for Triangle2d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud( let aabb = Aabb3d::from_point_cloud(
translation, translation,
rotation, rotation,
self.base_shape.vertices.iter().map(|v| v.extend(0.)), self.vertices.iter().map(|v| v.extend(0.)),
); );
let depth = rotation * Vec3A::new(0., 0., self.half_depth); let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<Rectangle> { impl BoundedExtrusion for Rectangle {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
Cuboid { Cuboid {
half_size: self.base_shape.half_size.extend(self.half_depth), half_size: self.half_size.extend(half_depth),
} }
.aabb_3d(translation, rotation) .aabb_3d(translation, rotation)
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl<const N: usize> Bounded3d for Extrusion<Polygon<N>> { impl<const N: usize> BoundedExtrusion for Polygon<N> {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud( let aabb = Aabb3d::from_point_cloud(
translation, translation,
rotation, rotation,
self.base_shape.vertices.map(|v| v.extend(0.)).into_iter(), self.vertices.map(|v| v.extend(0.)).into_iter(),
); );
let depth = rotation * Vec3A::new(0., 0., self.half_depth); let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<BoxedPolygon> { impl BoundedExtrusion for BoxedPolygon {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud( let aabb = Aabb3d::from_point_cloud(
translation, translation,
rotation, rotation,
self.base_shape.vertices.iter().map(|v| v.extend(0.)), self.vertices.iter().map(|v| v.extend(0.)),
); );
let depth = rotation * Vec3A::new(0., 0., self.half_depth); let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<RegularPolygon> { impl BoundedExtrusion for RegularPolygon {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Aabb3d::from_point_cloud( let aabb = Aabb3d::from_point_cloud(
translation, translation,
rotation, rotation,
self.base_shape self.vertices(0.).into_iter().map(|v| v.extend(0.)),
.vertices(0.)
.into_iter()
.map(|v| v.extend(0.)),
); );
let depth = rotation * Vec3A::new(0., 0., self.half_depth); let depth = rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs()) aabb.grow(depth.abs())
} }
fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation)
}
} }
impl Bounded3d for Extrusion<Capsule2d> { impl BoundedExtrusion for Capsule2d {
fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { fn extrusion_aabb_3d(&self, half_depth: f32, translation: Vec3, rotation: Quat) -> Aabb3d {
let aabb = Cylinder { let aabb = Cylinder {
half_height: self.half_depth, half_height: half_depth,
radius: self.base_shape.radius, radius: self.radius,
} }
.aabb_3d(Vec3::ZERO, rotation * Quat::from_rotation_x(FRAC_PI_2)); .aabb_3d(Vec3::ZERO, rotation * Quat::from_rotation_x(FRAC_PI_2));
let up = rotation * Vec3::new(0., self.base_shape.half_length, 0.); let up = rotation * Vec3::new(0., self.half_length, 0.);
let half_size = Into::<Vec3>::into(aabb.max) + up.abs(); let half_size = Into::<Vec3>::into(aabb.max) + up.abs();
Aabb3d::new(translation, half_size) Aabb3d::new(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 bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere { fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere {
extrusion_bounding_sphere(self, translation, rotation) self.base_shape
.extrusion_bounding_sphere(self.half_depth, translation, rotation)
} }
} }
/// Computes the axis aligned bounding box ([`Aabb3d`]) for an extrusion given its translation and rotation. /// A trait implemented on 2D shapes which determines the 3D bounding volumes of their extrusions.
pub fn extrusion_bounding_box<T: Primitive2d + Bounded2d>( ///
extrusion: &Extrusion<T>, /// Since default implementations can be inferred from 2D bounding volumes, this allows a `Bounded2d`
translation: Vec3, /// implementation on some shape `MyShape` to be extrapolated to a `Bounded3d` implementation on
rotation: Quat, /// `Extrusion<MyShape>` without supplying any additional data; e.g.:
) -> Aabb3d { /// `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 cap_normal = rotation * Vec3::Z;
let conjugate_rot = rotation.conjugate(); let conjugate_rot = rotation.conjugate();
@ -265,20 +228,21 @@ pub fn extrusion_bounding_box<T: Primitive2d + Bounded2d>(
// Calculate the `Aabb2d` of the base shape. The shape is rotated so that the line of intersection is parallel to the Y axis in the `Aabb2d` calculations. // Calculate the `Aabb2d` of the base shape. The shape is rotated so that the line of intersection is parallel to the Y axis in the `Aabb2d` calculations.
// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane // This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane
let aabb2d = extrusion.base_shape.aabb_2d(Vec2::ZERO, angle); let aabb2d = self.aabb_2d(Vec2::ZERO, angle);
(aabb2d.half_size().x * scale, aabb2d.center().x * scale) (aabb2d.half_size().x * scale, aabb2d.center().x * scale)
}); });
let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset)); let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));
let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs(); let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();
let depth = rotation * Vec3A::new(0., 0., extrusion.half_depth); let depth = rotation * Vec3A::new(0., 0., half_depth);
Aabb3d::new(Vec3A::from(translation) - offset, cap_size + depth.abs()) Aabb3d::new(Vec3A::from(translation) - offset, cap_size + depth.abs())
} }
/// Computes the [`BoundingSphere`] for an extrusion given its translation and rotation. /// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation
pub fn extrusion_bounding_sphere<T: Primitive2d + Bounded2d>( fn extrusion_bounding_sphere(
extrusion: &Extrusion<T>, &self,
half_depth: f32,
translation: Vec3, translation: Vec3,
rotation: Quat, rotation: Quat,
) -> BoundingSphere { ) -> BoundingSphere {
@ -289,12 +253,13 @@ pub fn extrusion_bounding_sphere<T: Primitive2d + Bounded2d>(
let BoundingCircle { let BoundingCircle {
center, center,
circle: Circle { radius }, circle: Circle { radius },
} = extrusion.base_shape.bounding_circle(Vec2::ZERO, 0.); } = self.bounding_circle(Vec2::ZERO, 0.);
let radius = radius.hypot(extrusion.half_depth); let radius = radius.hypot(half_depth);
let center = translation + rotation * center.extend(0.); let center = translation + rotation * center.extend(0.);
BoundingSphere::new(center, radius) BoundingSphere::new(center, radius)
} }
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -8,7 +8,7 @@ use crate::{Quat, Vec3, Vec3A};
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
pub use extrusion::{extrusion_bounding_box, extrusion_bounding_sphere}; pub use extrusion::BoundedExtrusion;
/// Computes the geometric center of the given set of points. /// Computes the geometric center of the given set of points.
#[inline(always)] #[inline(always)]