Path: blob/main/crates/bevy_math/src/bounding/bounded3d/extrusion.rs
6598 views
use core::f32::consts::FRAC_PI_2;12use glam::{Vec2, Vec3A, Vec3Swizzles};34use crate::{5bounding::{BoundingCircle, BoundingVolume},6ops,7primitives::{8Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d, Primitive2d, Rectangle,9RegularPolygon, Segment2d, Triangle2d,10},11Isometry2d, Isometry3d, Quat, Rot2,12};1314#[cfg(feature = "alloc")]15use crate::primitives::{Polygon, Polyline2d};1617use crate::{bounding::Bounded2d, primitives::Circle};1819use super::{Aabb3d, Bounded3d, BoundingSphere};2021impl BoundedExtrusion for Circle {22fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {23// Reference: http://iquilezles.org/articles/diskbbox/2425let isometry = isometry.into();2627let segment_dir = isometry.rotation * Vec3A::Z;28let top = (segment_dir * half_depth).abs();2930let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);31let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));3233Aabb3d {34min: isometry.translation - half_size - top,35max: isometry.translation + half_size + top,36}37}38}3940impl BoundedExtrusion for Ellipse {41fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {42let isometry = isometry.into();43let Vec2 { x: a, y: b } = self.half_size;44let normal = isometry.rotation * Vec3A::Z;45let conjugate_rot = isometry.rotation.conjugate();4647let [max_x, max_y, max_z] = Vec3A::AXES.map(|axis| {48let Some(axis) = (conjugate_rot * axis.reject_from(normal))49.xy()50.try_normalize()51else {52return Vec3A::ZERO;53};5455if axis.element_product() == 0. {56return isometry.rotation * Vec3A::new(a * axis.y, b * axis.x, 0.);57}58let m = -axis.x / axis.y;59let signum = axis.signum();6061let y = signum.y * b * b / ops::sqrt(b * b + m * m * a * a);62let x = signum.x * a * ops::sqrt(1. - y * y / b / b);63isometry.rotation * Vec3A::new(x, y, 0.)64});6566let half_size = Vec3A::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();67Aabb3d::new(isometry.translation, half_size)68}69}7071impl BoundedExtrusion for Line2d {72fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {73let isometry = isometry.into();74let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));75let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();7677let max = f32::MAX / 2.;78let half_size = Vec3A::new(79if dir.x == 0. { half_depth.x } else { max },80if dir.y == 0. { half_depth.y } else { max },81if dir.z == 0. { half_depth.z } else { max },82);8384Aabb3d::new(isometry.translation, half_size)85}86}8788impl BoundedExtrusion for Segment2d {89fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {90let isometry = isometry.into();91let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));92let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);9394Aabb3d::new(isometry.translation, half_size.abs() + depth.abs())95}96}9798#[cfg(feature = "alloc")]99impl BoundedExtrusion for Polyline2d {100fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {101let isometry = isometry.into();102let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));103let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);104105aabb.grow(depth.abs())106}107}108109impl BoundedExtrusion for Triangle2d {110fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {111let isometry = isometry.into();112let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));113let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);114115aabb.grow(depth.abs())116}117}118119impl BoundedExtrusion for Rectangle {120fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {121Cuboid {122half_size: self.half_size.extend(half_depth),123}124.aabb_3d(isometry)125}126}127128#[cfg(feature = "alloc")]129impl BoundedExtrusion for Polygon {130fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {131let isometry = isometry.into();132let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));133let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);134135aabb.grow(depth.abs())136}137}138139impl BoundedExtrusion for RegularPolygon {140fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {141let isometry = isometry.into();142let aabb = Aabb3d::from_point_cloud(143isometry,144self.vertices(0.).into_iter().map(|v| v.extend(0.)),145);146let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);147148aabb.grow(depth.abs())149}150}151152impl BoundedExtrusion for Capsule2d {153fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {154let isometry = isometry.into();155let aabb = Cylinder {156half_height: half_depth,157radius: self.radius,158}159.aabb_3d(isometry.rotation * Quat::from_rotation_x(FRAC_PI_2));160161let up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);162let half_size = aabb.max + up.abs();163Aabb3d::new(isometry.translation, half_size)164}165}166167impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {168fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {169self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)170}171172fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {173self.base_shape174.extrusion_bounding_sphere(self.half_depth, isometry)175}176}177178/// A trait implemented on 2D shapes which determines the 3D bounding volumes of their extrusions.179///180/// Since default implementations can be inferred from 2D bounding volumes, this allows a `Bounded2d`181/// implementation on some shape `MyShape` to be extrapolated to a `Bounded3d` implementation on182/// `Extrusion<MyShape>` without supplying any additional data; e.g.:183/// `impl BoundedExtrusion for MyShape {}`184pub trait BoundedExtrusion: Primitive2d + Bounded2d {185/// 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`.186fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {187let isometry = isometry.into();188let cap_normal = isometry.rotation * Vec3A::Z;189let conjugate_rot = isometry.rotation.conjugate();190191// The `(halfsize, offset)` for each axis192let axis_values = Vec3A::AXES.map(|ax| {193// 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.194let intersect_line = ax.cross(cap_normal);195if intersect_line.length_squared() <= f32::EPSILON {196return (0., 0.);197};198199// This is the normal vector of the intersection line rotated to be in the XY-plane200let line_normal = (conjugate_rot * intersect_line).yx();201let angle = line_normal.to_angle();202203// Since the plane containing the caps of the extrusion is not guaranteed to be orthogonal to the `ax` plane, only a certain "scale" factor204// of the `Aabb2d` will actually go towards the dimensions of the `Aabb3d`205let scale = cap_normal.reject_from(ax).length();206207// 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.208// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane209let aabb2d = self.aabb_2d(Rot2::radians(angle));210(aabb2d.half_size().x * scale, aabb2d.center().x * scale)211});212213let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));214let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();215let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);216217Aabb3d::new(isometry.translation - offset, cap_size + depth.abs())218}219220/// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation221fn extrusion_bounding_sphere(222&self,223half_depth: f32,224isometry: impl Into<Isometry3d>,225) -> BoundingSphere {226let isometry = isometry.into();227228// We calculate the bounding circle of the base shape.229// Since each of the extrusions bases will have the same distance from its center,230// and they are just shifted along the Z-axis, the minimum bounding sphere will be the bounding sphere231// of the cylinder defined by the two bounding circles of the bases for any base shape232let BoundingCircle {233center,234circle: Circle { radius },235} = self.bounding_circle(Isometry2d::IDENTITY);236let radius = ops::hypot(radius, half_depth);237let center = isometry * Vec3A::from(center.extend(0.));238239BoundingSphere::new(center, radius)240}241}242243#[cfg(test)]244mod tests {245use core::f32::consts::FRAC_PI_4;246247use glam::{EulerRot, Quat, Vec2, Vec3, Vec3A};248249use crate::{250bounding::{Bounded3d, BoundingVolume},251ops,252primitives::{253Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,254RegularPolygon, Segment2d, Triangle2d,255},256Dir2, Isometry3d,257};258259#[test]260fn circle() {261let cylinder = Extrusion::new(Circle::new(0.5), 2.0);262let translation = Vec3::new(2.0, 1.0, 0.0);263264let aabb = cylinder.aabb_3d(translation);265assert_eq!(aabb.center(), Vec3A::from(translation));266assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));267268let bounding_sphere = cylinder.bounding_sphere(translation);269assert_eq!(bounding_sphere.center, translation.into());270assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));271}272273#[test]274fn ellipse() {275let extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);276let translation = Vec3::new(3., 4., 5.);277let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4);278let isometry = Isometry3d::new(translation, rotation);279280let aabb = extrusion.aabb_3d(isometry);281assert_eq!(aabb.center(), Vec3A::from(translation));282assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141));283284let bounding_sphere = extrusion.bounding_sphere(isometry);285assert_eq!(bounding_sphere.center, translation.into());286assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));287}288289#[test]290fn line() {291let extrusion = Extrusion::new(292Line2d {293direction: Dir2::new_unchecked(Vec2::Y),294},2954.,296);297let translation = Vec3::new(3., 4., 5.);298let rotation = Quat::from_rotation_y(FRAC_PI_4);299let isometry = Isometry3d::new(translation, rotation);300301let aabb = extrusion.aabb_3d(isometry);302assert_eq!(aabb.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));303assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213));304305let bounding_sphere = extrusion.bounding_sphere(isometry);306assert_eq!(bounding_sphere.center(), translation.into());307assert_eq!(bounding_sphere.radius(), f32::MAX / 2.);308}309310#[test]311fn rectangle() {312let extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);313let translation = Vec3::new(3., 4., 5.);314let rotation = Quat::from_rotation_z(FRAC_PI_4);315let isometry = Isometry3d::new(translation, rotation);316317let aabb = extrusion.aabb_3d(isometry);318assert_eq!(aabb.center(), translation.into());319assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.));320321let bounding_sphere = extrusion.bounding_sphere(isometry);322assert_eq!(bounding_sphere.center, translation.into());323assert_eq!(bounding_sphere.radius(), 2.291288);324}325326#[test]327fn segment() {328let extrusion = Extrusion::new(329Segment2d::new(Vec2::new(0.0, -1.5), Vec2::new(0.0, 1.5)),3304.0,331);332let translation = Vec3::new(3., 4., 5.);333let rotation = Quat::from_rotation_x(FRAC_PI_4);334let isometry = Isometry3d::new(translation, rotation);335336let aabb = extrusion.aabb_3d(isometry);337assert_eq!(aabb.center(), translation.into());338assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735));339340let bounding_sphere = extrusion.bounding_sphere(isometry);341assert_eq!(bounding_sphere.center, translation.into());342assert_eq!(bounding_sphere.radius(), 2.5);343}344345#[test]346fn polyline() {347let polyline = Polyline2d::new([348Vec2::ONE,349Vec2::new(-1.0, 1.0),350Vec2::NEG_ONE,351Vec2::new(1.0, -1.0),352]);353let extrusion = Extrusion::new(polyline, 3.0);354let translation = Vec3::new(3., 4., 5.);355let rotation = Quat::from_rotation_x(FRAC_PI_4);356let isometry = Isometry3d::new(translation, rotation);357358let aabb = extrusion.aabb_3d(isometry);359assert_eq!(aabb.center(), translation.into());360assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));361362let bounding_sphere = extrusion.bounding_sphere(isometry);363assert_eq!(bounding_sphere.center, translation.into());364assert_eq!(bounding_sphere.radius(), 2.0615528);365}366367#[test]368fn triangle() {369let triangle = Triangle2d::new(370Vec2::new(0.0, 1.0),371Vec2::new(-10.0, -1.0),372Vec2::new(10.0, -1.0),373);374let extrusion = Extrusion::new(triangle, 3.0);375let translation = Vec3::new(3., 4., 5.);376let rotation = Quat::from_rotation_x(FRAC_PI_4);377let isometry = Isometry3d::new(translation, rotation);378379let aabb = extrusion.aabb_3d(isometry);380assert_eq!(aabb.center(), translation.into());381assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668));382383let bounding_sphere = extrusion.bounding_sphere(isometry);384assert_eq!(385bounding_sphere.center,386Vec3A::new(3.0, 3.2928934, 4.2928934)387);388assert_eq!(bounding_sphere.radius(), 10.111875);389}390391#[test]392fn polygon() {393let polygon = Polygon::new([394Vec2::ONE,395Vec2::new(-1.0, 1.0),396Vec2::NEG_ONE,397Vec2::new(1.0, -1.0),398]);399let extrusion = Extrusion::new(polygon, 3.0);400let translation = Vec3::new(3., 4., 5.);401let rotation = Quat::from_rotation_x(FRAC_PI_4);402let isometry = Isometry3d::new(translation, rotation);403404let aabb = extrusion.aabb_3d(isometry);405assert_eq!(aabb.center(), translation.into());406assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));407408let bounding_sphere = extrusion.bounding_sphere(isometry);409assert_eq!(bounding_sphere.center, translation.into());410assert_eq!(bounding_sphere.radius(), 2.0615528);411}412413#[test]414fn regular_polygon() {415let extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);416let translation = Vec3::new(3., 4., 5.);417let rotation = Quat::from_rotation_x(FRAC_PI_4);418let isometry = Isometry3d::new(translation, rotation);419420let aabb = extrusion.aabb_3d(isometry);421assert_eq!(422aabb.center(),423Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)424);425assert_eq!(426aabb.half_size(),427Vec3A::new(1.9498558, 2.7584014, 2.7584019)428);429430let bounding_sphere = extrusion.bounding_sphere(isometry);431assert_eq!(bounding_sphere.center, translation.into());432assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));433}434435#[test]436fn capsule() {437let extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);438let translation = Vec3::new(3., 4., 5.);439let rotation = Quat::from_rotation_x(FRAC_PI_4);440let isometry = Isometry3d::new(translation, rotation);441442let aabb = extrusion.aabb_3d(isometry);443assert_eq!(aabb.center(), translation.into());444assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735));445446let bounding_sphere = extrusion.bounding_sphere(isometry);447assert_eq!(bounding_sphere.center, translation.into());448assert_eq!(bounding_sphere.radius(), 2.5);449}450}451452453