Path: blob/main/crates/bevy_math/src/bounding/bounded3d/extrusion.rs
9418 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, Ring, 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> BoundedExtrusion for Ring<T> {168fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {169self.outer_shape.extrusion_aabb_3d(half_depth, isometry)170}171172fn extrusion_bounding_sphere(173&self,174half_depth: f32,175isometry: impl Into<Isometry3d>,176) -> BoundingSphere {177self.outer_shape178.extrusion_bounding_sphere(half_depth, isometry)179}180}181182impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {183fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {184self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)185}186187fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {188self.base_shape189.extrusion_bounding_sphere(self.half_depth, isometry)190}191}192193/// A trait implemented on 2D shapes which determines the 3D bounding volumes of their extrusions.194///195/// Since default implementations can be inferred from 2D bounding volumes, this allows a `Bounded2d`196/// implementation on some shape `MyShape` to be extrapolated to a `Bounded3d` implementation on197/// `Extrusion<MyShape>` without supplying any additional data; e.g.:198/// `impl BoundedExtrusion for MyShape {}`199pub trait BoundedExtrusion: Primitive2d + Bounded2d {200/// 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`.201fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {202let isometry = isometry.into();203let cap_normal = isometry.rotation * Vec3A::Z;204let conjugate_rot = isometry.rotation.conjugate();205206// The `(halfsize, offset)` for each axis207let axis_values = Vec3A::AXES.map(|ax| {208// 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.209let intersect_line = ax.cross(cap_normal);210if intersect_line.length_squared() <= f32::EPSILON {211return (0., 0.);212};213214// This is the normal vector of the intersection line rotated to be in the XY-plane215let line_normal = (conjugate_rot * intersect_line).yx();216let angle = line_normal.to_angle();217218// Since the plane containing the caps of the extrusion is not guaranteed to be orthogonal to the `ax` plane, only a certain "scale" factor219// of the `Aabb2d` will actually go towards the dimensions of the `Aabb3d`220let scale = cap_normal.reject_from(ax).length();221222// 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.223// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane224let aabb2d = self.aabb_2d(Rot2::radians(angle));225(aabb2d.half_size().x * scale, aabb2d.center().x * scale)226});227228let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));229let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();230let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);231232Aabb3d::new(isometry.translation - offset, cap_size + depth.abs())233}234235/// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation236fn extrusion_bounding_sphere(237&self,238half_depth: f32,239isometry: impl Into<Isometry3d>,240) -> BoundingSphere {241let isometry = isometry.into();242243// We calculate the bounding circle of the base shape.244// Since each of the extrusions bases will have the same distance from its center,245// and they are just shifted along the Z-axis, the minimum bounding sphere will be the bounding sphere246// of the cylinder defined by the two bounding circles of the bases for any base shape247let BoundingCircle {248center,249circle: Circle { radius },250} = self.bounding_circle(Isometry2d::IDENTITY);251let radius = ops::hypot(radius, half_depth);252let center = isometry * Vec3A::from(center.extend(0.));253254BoundingSphere::new(center, radius)255}256}257258#[cfg(test)]259mod tests {260use core::f32::consts::FRAC_PI_4;261262use glam::{EulerRot, Quat, Vec2, Vec3, Vec3A};263264use crate::{265bounding::{Bounded3d, BoundingVolume},266ops,267primitives::{268Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,269RegularPolygon, Segment2d, Triangle2d,270},271Dir2, Isometry3d,272};273274#[test]275fn circle() {276let cylinder = Extrusion::new(Circle::new(0.5), 2.0);277let translation = Vec3::new(2.0, 1.0, 0.0);278279let aabb = cylinder.aabb_3d(translation);280assert_eq!(aabb.center(), Vec3A::from(translation));281assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));282283let bounding_sphere = cylinder.bounding_sphere(translation);284assert_eq!(bounding_sphere.center, translation.into());285assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));286}287288#[test]289fn ellipse() {290let extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);291let translation = Vec3::new(3., 4., 5.);292let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4);293let isometry = Isometry3d::new(translation, rotation);294295let aabb = extrusion.aabb_3d(isometry);296assert_eq!(aabb.center(), Vec3A::from(translation));297assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141));298299let bounding_sphere = extrusion.bounding_sphere(isometry);300assert_eq!(bounding_sphere.center, translation.into());301assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));302}303304#[test]305fn line() {306let extrusion = Extrusion::new(307Line2d {308direction: Dir2::new_unchecked(Vec2::Y),309},3104.,311);312let translation = Vec3::new(3., 4., 5.);313let rotation = Quat::from_rotation_y(FRAC_PI_4);314let isometry = Isometry3d::new(translation, rotation);315316let aabb = extrusion.aabb_3d(isometry);317assert_eq!(aabb.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));318assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213));319320let bounding_sphere = extrusion.bounding_sphere(isometry);321assert_eq!(bounding_sphere.center(), translation.into());322assert_eq!(bounding_sphere.radius(), f32::MAX / 2.);323}324325#[test]326fn rectangle() {327let extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);328let translation = Vec3::new(3., 4., 5.);329let rotation = Quat::from_rotation_z(FRAC_PI_4);330let isometry = Isometry3d::new(translation, rotation);331332let aabb = extrusion.aabb_3d(isometry);333assert_eq!(aabb.center(), translation.into());334assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.));335336let bounding_sphere = extrusion.bounding_sphere(isometry);337assert_eq!(bounding_sphere.center, translation.into());338assert_eq!(bounding_sphere.radius(), 2.291288);339}340341#[test]342fn segment() {343let extrusion = Extrusion::new(344Segment2d::new(Vec2::new(0.0, -1.5), Vec2::new(0.0, 1.5)),3454.0,346);347let translation = Vec3::new(3., 4., 5.);348let rotation = Quat::from_rotation_x(FRAC_PI_4);349let isometry = Isometry3d::new(translation, rotation);350351let aabb = extrusion.aabb_3d(isometry);352assert_eq!(aabb.center(), translation.into());353assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735));354355let bounding_sphere = extrusion.bounding_sphere(isometry);356assert_eq!(bounding_sphere.center, translation.into());357assert_eq!(bounding_sphere.radius(), 2.5);358}359360#[test]361fn polyline() {362let polyline = Polyline2d::new([363Vec2::ONE,364Vec2::new(-1.0, 1.0),365Vec2::NEG_ONE,366Vec2::new(1.0, -1.0),367]);368let extrusion = Extrusion::new(polyline, 3.0);369let translation = Vec3::new(3., 4., 5.);370let rotation = Quat::from_rotation_x(FRAC_PI_4);371let isometry = Isometry3d::new(translation, rotation);372373let aabb = extrusion.aabb_3d(isometry);374assert_eq!(aabb.center(), translation.into());375assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));376377let bounding_sphere = extrusion.bounding_sphere(isometry);378assert_eq!(bounding_sphere.center, translation.into());379assert_eq!(bounding_sphere.radius(), 2.0615528);380}381382#[test]383fn triangle() {384let triangle = Triangle2d::new(385Vec2::new(0.0, 1.0),386Vec2::new(-10.0, -1.0),387Vec2::new(10.0, -1.0),388);389let extrusion = Extrusion::new(triangle, 3.0);390let translation = Vec3::new(3., 4., 5.);391let rotation = Quat::from_rotation_x(FRAC_PI_4);392let isometry = Isometry3d::new(translation, rotation);393394let aabb = extrusion.aabb_3d(isometry);395assert_eq!(aabb.center(), translation.into());396assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668));397398let bounding_sphere = extrusion.bounding_sphere(isometry);399assert_eq!(400bounding_sphere.center,401Vec3A::new(3.0, 3.2928934, 4.2928934)402);403assert_eq!(bounding_sphere.radius(), 10.111875);404}405406#[test]407fn polygon() {408let polygon = Polygon::new([409Vec2::ONE,410Vec2::new(-1.0, 1.0),411Vec2::NEG_ONE,412Vec2::new(1.0, -1.0),413]);414let extrusion = Extrusion::new(polygon, 3.0);415let translation = Vec3::new(3., 4., 5.);416let rotation = Quat::from_rotation_x(FRAC_PI_4);417let isometry = Isometry3d::new(translation, rotation);418419let aabb = extrusion.aabb_3d(isometry);420assert_eq!(aabb.center(), translation.into());421assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));422423let bounding_sphere = extrusion.bounding_sphere(isometry);424assert_eq!(bounding_sphere.center, translation.into());425assert_eq!(bounding_sphere.radius(), 2.0615528);426}427428#[test]429fn regular_polygon() {430let extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);431let translation = Vec3::new(3., 4., 5.);432let rotation = Quat::from_rotation_x(FRAC_PI_4);433let isometry = Isometry3d::new(translation, rotation);434435let aabb = extrusion.aabb_3d(isometry);436assert_eq!(437aabb.center(),438Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)439);440assert_eq!(441aabb.half_size(),442Vec3A::new(1.9498558, 2.7584014, 2.7584019)443);444445let bounding_sphere = extrusion.bounding_sphere(isometry);446assert_eq!(bounding_sphere.center, translation.into());447assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));448}449450#[test]451fn capsule() {452let extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);453let translation = Vec3::new(3., 4., 5.);454let rotation = Quat::from_rotation_x(FRAC_PI_4);455let isometry = Isometry3d::new(translation, rotation);456457let aabb = extrusion.aabb_3d(isometry);458assert_eq!(aabb.center(), translation.into());459assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735));460461let bounding_sphere = extrusion.bounding_sphere(isometry);462assert_eq!(bounding_sphere.center, translation.into());463assert_eq!(bounding_sphere.radius(), 2.5);464}465}466467468