Path: blob/main/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs
6598 views
//! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives).12use crate::{3bounding::{Bounded2d, BoundingCircle, BoundingVolume},4ops,5primitives::{6Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Segment3d,7Sphere, Torus, Triangle2d, Triangle3d,8},9Isometry2d, Isometry3d, Mat3, Vec2, Vec3, Vec3A,10};1112#[cfg(feature = "alloc")]13use crate::primitives::Polyline3d;1415use super::{Aabb3d, Bounded3d, BoundingSphere};1617impl Bounded3d for Sphere {18fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {19let isometry = isometry.into();20Aabb3d::new(isometry.translation, Vec3::splat(self.radius))21}2223fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {24let isometry = isometry.into();25BoundingSphere::new(isometry.translation, self.radius)26}27}2829impl Bounded3d for InfinitePlane3d {30fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {31let isometry = isometry.into();3233let normal = isometry.rotation * *self.normal;34let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;35let facing_y = normal == Vec3::Y || normal == Vec3::NEG_Y;36let facing_z = normal == Vec3::Z || normal == Vec3::NEG_Z;3738// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations39// like growing or shrinking the AABB without breaking things.40let half_width = if facing_x { 0.0 } else { f32::MAX / 2.0 };41let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };42let half_depth = if facing_z { 0.0 } else { f32::MAX / 2.0 };43let half_size = Vec3A::new(half_width, half_height, half_depth);4445Aabb3d::new(isometry.translation, half_size)46}4748fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {49let isometry = isometry.into();50BoundingSphere::new(isometry.translation, f32::MAX / 2.0)51}52}5354impl Bounded3d for Line3d {55fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {56let isometry = isometry.into();57let direction = isometry.rotation * *self.direction;5859// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations60// like growing or shrinking the AABB without breaking things.61let max = f32::MAX / 2.0;62let half_width = if direction.x == 0.0 { 0.0 } else { max };63let half_height = if direction.y == 0.0 { 0.0 } else { max };64let half_depth = if direction.z == 0.0 { 0.0 } else { max };65let half_size = Vec3A::new(half_width, half_height, half_depth);6667Aabb3d::new(isometry.translation, half_size)68}6970fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {71let isometry = isometry.into();72BoundingSphere::new(isometry.translation, f32::MAX / 2.0)73}74}7576impl Bounded3d for Segment3d {77fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {78Aabb3d::from_point_cloud(isometry, [self.point1(), self.point2()].iter().copied())79}8081fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {82let isometry = isometry.into();83let local_sphere = BoundingSphere::new(self.center(), self.length() / 2.);84local_sphere.transformed_by(isometry.translation, isometry.rotation)85}86}8788#[cfg(feature = "alloc")]89impl Bounded3d for Polyline3d {90fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {91Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())92}9394fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {95BoundingSphere::from_point_cloud(isometry, &self.vertices)96}97}9899impl Bounded3d for Cuboid {100fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {101let isometry = isometry.into();102103// Compute the AABB of the rotated cuboid by transforming the half-size104// by an absolute rotation matrix.105let rot_mat = Mat3::from_quat(isometry.rotation);106let abs_rot_mat = Mat3::from_cols(107rot_mat.x_axis.abs(),108rot_mat.y_axis.abs(),109rot_mat.z_axis.abs(),110);111let half_size = abs_rot_mat * self.half_size;112113Aabb3d::new(isometry.translation, half_size)114}115116fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {117let isometry = isometry.into();118BoundingSphere::new(isometry.translation, self.half_size.length())119}120}121122impl Bounded3d for Cylinder {123fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {124// Reference: http://iquilezles.org/articles/diskbbox/125126let isometry = isometry.into();127128let segment_dir = isometry.rotation * Vec3A::Y;129let top = segment_dir * self.half_height;130let bottom = -top;131132let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);133let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));134135Aabb3d {136min: isometry.translation + (top - half_size).min(bottom - half_size),137max: isometry.translation + (top + half_size).max(bottom + half_size),138}139}140141fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {142let isometry = isometry.into();143let radius = ops::hypot(self.radius, self.half_height);144BoundingSphere::new(isometry.translation, radius)145}146}147148impl Bounded3d for Capsule3d {149fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {150let isometry = isometry.into();151152// Get the line segment between the hemispheres of the rotated capsule153let segment_dir = isometry.rotation * Vec3A::Y;154let top = segment_dir * self.half_length;155let bottom = -top;156157// Expand the line segment by the capsule radius to get the capsule half-extents158let min = bottom.min(top) - Vec3A::splat(self.radius);159let max = bottom.max(top) + Vec3A::splat(self.radius);160161Aabb3d {162min: min + isometry.translation,163max: max + isometry.translation,164}165}166167fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {168let isometry = isometry.into();169BoundingSphere::new(isometry.translation, self.radius + self.half_length)170}171}172173impl Bounded3d for Cone {174fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {175// Reference: http://iquilezles.org/articles/diskbbox/176177let isometry = isometry.into();178179let segment_dir = isometry.rotation * Vec3A::Y;180let top = segment_dir * 0.5 * self.height;181let bottom = -top;182183let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);184let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));185186Aabb3d {187min: isometry.translation + top.min(bottom - self.radius * half_extents),188max: isometry.translation + top.max(bottom + self.radius * half_extents),189}190}191192fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {193let isometry = isometry.into();194195// Get the triangular cross-section of the cone.196let half_height = 0.5 * self.height;197let triangle = Triangle2d::new(198half_height * Vec2::Y,199Vec2::new(-self.radius, -half_height),200Vec2::new(self.radius, -half_height),201);202203// Because of circular symmetry, we can use the bounding circle of the triangle204// for the bounding sphere of the cone.205let BoundingCircle { circle, center } = triangle.bounding_circle(Isometry2d::IDENTITY);206207BoundingSphere::new(208isometry.rotation * Vec3A::from(center.extend(0.0)) + isometry.translation,209circle.radius,210)211}212}213214impl Bounded3d for ConicalFrustum {215fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {216// Reference: http://iquilezles.org/articles/diskbbox/217218let isometry = isometry.into();219220let segment_dir = isometry.rotation * Vec3A::Y;221let top = segment_dir * 0.5 * self.height;222let bottom = -top;223224let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);225let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));226227Aabb3d {228min: isometry.translation229+ (top - self.radius_top * half_extents)230.min(bottom - self.radius_bottom * half_extents),231max: isometry.translation232+ (top + self.radius_top * half_extents)233.max(bottom + self.radius_bottom * half_extents),234}235}236237fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {238let isometry = isometry.into();239let half_height = 0.5 * self.height;240241// To compute the bounding sphere, we'll get the center and radius of the circumcircle242// passing through all four vertices of the trapezoidal cross-section of the conical frustum.243//244// If the circumcenter is inside the trapezoid, we can use that for the bounding sphere.245// Otherwise, we clamp it to the longer parallel side to get a more tightly fitting bounding sphere.246//247// The circumcenter is at the intersection of the bisectors perpendicular to the sides.248// For the isosceles trapezoid, the X coordinate is zero at the center, so a single bisector is enough.249//250// A251// *-------*252// / | \253// / | \254// AB / \ | / \255// / \ | / \256// / C \257// *-------------------*258// B259260let a = Vec2::new(-self.radius_top, half_height);261let b = Vec2::new(-self.radius_bottom, -half_height);262let ab = a - b;263let ab_midpoint = b + 0.5 * ab;264let bisector = ab.perp();265266// Compute intersection between bisector and vertical line at x = 0.267//268// x = ab_midpoint.x + t * bisector.x = 0269// y = ab_midpoint.y + t * bisector.y = ?270//271// Because ab_midpoint.y = 0 for our conical frustum, we get:272// y = t * bisector.y273//274// Solve x for t:275// t = -ab_midpoint.x / bisector.x276//277// Substitute t to solve for y:278// y = -ab_midpoint.x / bisector.x * bisector.y279let circumcenter_y = -ab_midpoint.x / bisector.x * bisector.y;280281// If the circumcenter is outside the trapezoid, the bounding circle is too large.282// In those cases, we clamp it to the longer parallel side.283let (center, radius) = if circumcenter_y <= -half_height {284(Vec2::new(0.0, -half_height), self.radius_bottom)285} else if circumcenter_y >= half_height {286(Vec2::new(0.0, half_height), self.radius_top)287} else {288let circumcenter = Vec2::new(0.0, circumcenter_y);289// We can use the distance from an arbitrary vertex because they all lie on the circumcircle.290(circumcenter, a.distance(circumcenter))291};292293BoundingSphere::new(294isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.0)),295radius,296)297}298}299300impl Bounded3d for Torus {301fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {302let isometry = isometry.into();303304// Compute the AABB of a flat disc with the major radius of the torus.305// Reference: http://iquilezles.org/articles/diskbbox/306let normal = isometry.rotation * Vec3A::Y;307let e = (Vec3A::ONE - normal * normal).max(Vec3A::ZERO);308let disc_half_size =309self.major_radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));310311// Expand the disc by the minor radius to get the torus half-size312let half_size = disc_half_size + Vec3A::splat(self.minor_radius);313314Aabb3d::new(isometry.translation, half_size)315}316317fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {318let isometry = isometry.into();319BoundingSphere::new(isometry.translation, self.outer_radius())320}321}322323impl Bounded3d for Triangle3d {324/// Get the bounding box of the triangle.325fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {326let isometry = isometry.into();327let [a, b, c] = self.vertices;328329let a = isometry.rotation * a;330let b = isometry.rotation * b;331let c = isometry.rotation * c;332333let min = Vec3A::from(a.min(b).min(c));334let max = Vec3A::from(a.max(b).max(c));335336let bounding_center = (max + min) / 2.0 + isometry.translation;337let half_extents = (max - min) / 2.0;338339Aabb3d::new(bounding_center, half_extents)340}341342/// Get the bounding sphere of the triangle.343///344/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as345/// the center of the sphere. For the others, the bounding sphere is the minimal sphere346/// that contains the largest side of the triangle.347fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {348let isometry = isometry.into();349350if self.is_degenerate() || self.is_obtuse() {351let (p1, p2) = self.largest_side();352let (p1, p2) = (Vec3A::from(p1), Vec3A::from(p2));353let mid_point = (p1 + p2) / 2.0;354let radius = mid_point.distance(p1);355BoundingSphere::new(mid_point + isometry.translation, radius)356} else {357let [a, _, _] = self.vertices;358359let circumcenter = self.circumcenter();360let radius = circumcenter.distance(a);361BoundingSphere::new(Vec3A::from(circumcenter) + isometry.translation, radius)362}363}364}365366#[cfg(test)]367mod tests {368use crate::{bounding::BoundingVolume, ops, Isometry3d};369use glam::{Quat, Vec3, Vec3A};370371use crate::{372bounding::Bounded3d,373primitives::{374Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,375Segment3d, Sphere, Torus, Triangle3d,376},377Dir3,378};379380#[test]381fn sphere() {382let sphere = Sphere { radius: 1.0 };383let translation = Vec3::new(2.0, 1.0, 0.0);384385let aabb = sphere.aabb_3d(translation);386assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));387assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));388389let bounding_sphere = sphere.bounding_sphere(translation);390assert_eq!(bounding_sphere.center, translation.into());391assert_eq!(bounding_sphere.radius(), 1.0);392}393394#[test]395fn plane() {396let translation = Vec3::new(2.0, 1.0, 0.0);397398let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation);399assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));400assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));401402let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation);403assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));404assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));405406let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation);407assert_eq!(aabb3.min, Vec3A::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));408assert_eq!(aabb3.max, Vec3A::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));409410let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation);411assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));412assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));413414let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation);415assert_eq!(bounding_sphere.center, translation.into());416assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);417}418419#[test]420fn line() {421let translation = Vec3::new(2.0, 1.0, 0.0);422423let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(translation);424assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, 0.0));425assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, 0.0));426427let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(translation);428assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, 0.0));429assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, 0.0));430431let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(translation);432assert_eq!(aabb3.min, Vec3A::new(2.0, 1.0, -f32::MAX / 2.0));433assert_eq!(aabb3.max, Vec3A::new(2.0, 1.0, f32::MAX / 2.0));434435let aabb4 = Line3d {436direction: Dir3::from_xyz(1.0, 1.0, 1.0).unwrap(),437}438.aabb_3d(translation);439assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));440assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));441442let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(translation);443assert_eq!(bounding_sphere.center, translation.into());444assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);445}446447#[test]448fn segment() {449let segment = Segment3d::new(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0));450let translation = Vec3::new(2.0, 1.0, 0.0);451452let aabb = segment.aabb_3d(translation);453assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));454assert_eq!(aabb.max, Vec3A::new(3.0, 1.5, 0.0));455456let bounding_sphere = segment.bounding_sphere(translation);457assert_eq!(bounding_sphere.center, translation.into());458assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));459}460461#[test]462fn polyline() {463let polyline = Polyline3d::new([464Vec3::ONE,465Vec3::new(-1.0, 1.0, 1.0),466Vec3::NEG_ONE,467Vec3::new(1.0, -1.0, -1.0),468]);469let translation = Vec3::new(2.0, 1.0, 0.0);470471let aabb = polyline.aabb_3d(translation);472assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));473assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));474475let bounding_sphere = polyline.bounding_sphere(translation);476assert_eq!(bounding_sphere.center, translation.into());477assert_eq!(478bounding_sphere.radius(),479ops::hypot(ops::hypot(1.0, 1.0), 1.0)480);481}482483#[test]484fn cuboid() {485let cuboid = Cuboid::new(2.0, 1.0, 1.0);486let translation = Vec3::new(2.0, 1.0, 0.0);487488let aabb = cuboid.aabb_3d(Isometry3d::new(489translation,490Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),491));492let expected_half_size = Vec3A::new(1.0606601, 1.0606601, 0.5);493assert_eq!(aabb.min, Vec3A::from(translation) - expected_half_size);494assert_eq!(aabb.max, Vec3A::from(translation) + expected_half_size);495496let bounding_sphere = cuboid.bounding_sphere(translation);497assert_eq!(bounding_sphere.center, translation.into());498assert_eq!(499bounding_sphere.radius(),500ops::hypot(ops::hypot(1.0, 0.5), 0.5)501);502}503504#[test]505fn cylinder() {506let cylinder = Cylinder::new(0.5, 2.0);507let translation = Vec3::new(2.0, 1.0, 0.0);508509let aabb = cylinder.aabb_3d(translation);510assert_eq!(511aabb.min,512Vec3A::from(translation) - Vec3A::new(0.5, 1.0, 0.5)513);514assert_eq!(515aabb.max,516Vec3A::from(translation) + Vec3A::new(0.5, 1.0, 0.5)517);518519let bounding_sphere = cylinder.bounding_sphere(translation);520assert_eq!(bounding_sphere.center, translation.into());521assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));522}523524#[test]525fn capsule() {526let capsule = Capsule3d::new(0.5, 2.0);527let translation = Vec3::new(2.0, 1.0, 0.0);528529let aabb = capsule.aabb_3d(translation);530assert_eq!(531aabb.min,532Vec3A::from(translation) - Vec3A::new(0.5, 1.5, 0.5)533);534assert_eq!(535aabb.max,536Vec3A::from(translation) + Vec3A::new(0.5, 1.5, 0.5)537);538539let bounding_sphere = capsule.bounding_sphere(translation);540assert_eq!(bounding_sphere.center, translation.into());541assert_eq!(bounding_sphere.radius(), 1.5);542}543544#[test]545fn cone() {546let cone = Cone {547radius: 1.0,548height: 2.0,549};550let translation = Vec3::new(2.0, 1.0, 0.0);551552let aabb = cone.aabb_3d(translation);553assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));554assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));555556let bounding_sphere = cone.bounding_sphere(translation);557assert_eq!(558bounding_sphere.center,559Vec3A::from(translation) + Vec3A::NEG_Y * 0.25560);561assert_eq!(bounding_sphere.radius(), 1.25);562}563564#[test]565fn conical_frustum() {566let conical_frustum = ConicalFrustum {567radius_top: 0.5,568radius_bottom: 1.0,569height: 2.0,570};571let translation = Vec3::new(2.0, 1.0, 0.0);572573let aabb = conical_frustum.aabb_3d(translation);574assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));575assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));576577let bounding_sphere = conical_frustum.bounding_sphere(translation);578assert_eq!(579bounding_sphere.center,580Vec3A::from(translation) + Vec3A::NEG_Y * 0.1875581);582assert_eq!(bounding_sphere.radius(), 1.2884705);583}584585#[test]586fn wide_conical_frustum() {587let conical_frustum = ConicalFrustum {588radius_top: 0.5,589radius_bottom: 5.0,590height: 1.0,591};592let translation = Vec3::new(2.0, 1.0, 0.0);593594let aabb = conical_frustum.aabb_3d(translation);595assert_eq!(aabb.min, Vec3A::new(-3.0, 0.5, -5.0));596assert_eq!(aabb.max, Vec3A::new(7.0, 1.5, 5.0));597598// For wide conical frusta like this, the circumcenter can be outside the frustum,599// so the center and radius should be clamped to the longest side.600let bounding_sphere = conical_frustum.bounding_sphere(translation);601assert_eq!(602bounding_sphere.center,603Vec3A::from(translation) + Vec3A::NEG_Y * 0.5604);605assert_eq!(bounding_sphere.radius(), 5.0);606}607608#[test]609fn torus() {610let torus = Torus {611minor_radius: 0.5,612major_radius: 1.0,613};614let translation = Vec3::new(2.0, 1.0, 0.0);615616let aabb = torus.aabb_3d(translation);617assert_eq!(aabb.min, Vec3A::new(0.5, 0.5, -1.5));618assert_eq!(aabb.max, Vec3A::new(3.5, 1.5, 1.5));619620let bounding_sphere = torus.bounding_sphere(translation);621assert_eq!(bounding_sphere.center, translation.into());622assert_eq!(bounding_sphere.radius(), 1.5);623}624625#[test]626fn triangle3d() {627let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);628629let br = zero_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);630assert_eq!(631br.center(),632Vec3::ZERO.into(),633"incorrect bounding box center"634);635assert_eq!(636br.half_size(),637Vec3::ZERO.into(),638"incorrect bounding box half extents"639);640641let bs = zero_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);642assert_eq!(643bs.center,644Vec3::ZERO.into(),645"incorrect bounding sphere center"646);647assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius");648649let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);650let bs = dup_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);651assert_eq!(652bs.center,653Vec3::new(0.5, 0.0, 0.0).into(),654"incorrect bounding sphere center"655);656assert_eq!(bs.sphere.radius, 0.5, "incorrect bounding sphere radius");657let br = dup_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);658assert_eq!(659br.center(),660Vec3::new(0.5, 0.0, 0.0).into(),661"incorrect bounding box center"662);663assert_eq!(664br.half_size(),665Vec3::new(0.5, 0.0, 0.0).into(),666"incorrect bounding box half extents"667);668669let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);670let bs = collinear_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);671assert_eq!(672bs.center,673Vec3::ZERO.into(),674"incorrect bounding sphere center"675);676assert_eq!(bs.sphere.radius, 1.0, "incorrect bounding sphere radius");677let br = collinear_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);678assert_eq!(679br.center(),680Vec3::ZERO.into(),681"incorrect bounding box center"682);683assert_eq!(684br.half_size(),685Vec3::new(1.0, 0.0, 0.0).into(),686"incorrect bounding box half extents"687);688}689}690691692