Path: blob/main/crates/bevy_math/src/bounding/bounded3d/mod.rs
6598 views
mod extrusion;1mod primitive_impls;23use glam::Mat3;45use super::{BoundingVolume, IntersectsVolume};6use crate::{7ops::{self, FloatPow},8Isometry3d, Quat, Vec3A,9};1011#[cfg(feature = "bevy_reflect")]12use bevy_reflect::Reflect;13#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]14use bevy_reflect::{ReflectDeserialize, ReflectSerialize};15#[cfg(feature = "serialize")]16use serde::{Deserialize, Serialize};1718pub use extrusion::BoundedExtrusion;1920/// Computes the geometric center of the given set of points.21#[inline(always)]22fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3A {23let (acc, len) = points.fold((Vec3A::ZERO, 0), |(acc, len), point| {24(acc + point.into(), len + 1)25});2627assert!(28len > 0,29"cannot compute the center of an empty set of points"30);31acc / len as f3232}3334/// A trait with methods that return 3D bounding volumes for a shape.35pub trait Bounded3d {36/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.37fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;38/// Get a bounding sphere for the shape translated and rotated by the given isometry.39fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;40}4142/// A 3D axis-aligned bounding box43#[derive(Clone, Copy, Debug, PartialEq)]44#[cfg_attr(45feature = "bevy_reflect",46derive(Reflect),47reflect(Debug, PartialEq, Clone)48)]49#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]50#[cfg_attr(51all(feature = "serialize", feature = "bevy_reflect"),52reflect(Serialize, Deserialize)53)]54pub struct Aabb3d {55/// The minimum point of the box56pub min: Vec3A,57/// The maximum point of the box58pub max: Vec3A,59}6061impl Aabb3d {62/// Constructs an AABB from its center and half-size.63#[inline(always)]64pub fn new(center: impl Into<Vec3A>, half_size: impl Into<Vec3A>) -> Self {65let (center, half_size) = (center.into(), half_size.into());66debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0 && half_size.z >= 0.0);67Self {68min: center - half_size,69max: center + half_size,70}71}7273/// Computes the smallest [`Aabb3d`] containing the given set of points,74/// transformed by the rotation and translation of the given isometry.75///76/// # Panics77///78/// Panics if the given set of points is empty.79#[inline(always)]80pub fn from_point_cloud(81isometry: impl Into<Isometry3d>,82points: impl Iterator<Item = impl Into<Vec3A>>,83) -> Aabb3d {84let isometry = isometry.into();8586// Transform all points by rotation87let mut iter = points.map(|point| isometry.rotation * point.into());8889let first = iter90.next()91.expect("point cloud must contain at least one point for Aabb3d construction");9293let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {94(point.min(prev_min), point.max(prev_max))95});9697Aabb3d {98min: min + isometry.translation,99max: max + isometry.translation,100}101}102103/// Computes the smallest [`BoundingSphere`] containing this [`Aabb3d`].104#[inline(always)]105pub fn bounding_sphere(&self) -> BoundingSphere {106let radius = self.min.distance(self.max) / 2.0;107BoundingSphere::new(self.center(), radius)108}109110/// Finds the point on the AABB that is closest to the given `point`.111///112/// If the point is outside the AABB, the returned point will be on the surface of the AABB.113/// Otherwise, it will be inside the AABB and returned as is.114#[inline(always)]115pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {116// Clamp point coordinates to the AABB117point.into().clamp(self.min, self.max)118}119}120121impl BoundingVolume for Aabb3d {122type Translation = Vec3A;123type Rotation = Quat;124type HalfSize = Vec3A;125126#[inline(always)]127fn center(&self) -> Self::Translation {128(self.min + self.max) / 2.129}130131#[inline(always)]132fn half_size(&self) -> Self::HalfSize {133(self.max - self.min) / 2.134}135136#[inline(always)]137fn visible_area(&self) -> f32 {138let b = (self.max - self.min).max(Vec3A::ZERO);139b.x * (b.y + b.z) + b.y * b.z140}141142#[inline(always)]143fn contains(&self, other: &Self) -> bool {144other.min.cmpge(self.min).all() && other.max.cmple(self.max).all()145}146147#[inline(always)]148fn merge(&self, other: &Self) -> Self {149Self {150min: self.min.min(other.min),151max: self.max.max(other.max),152}153}154155#[inline(always)]156fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {157let amount = amount.into();158let b = Self {159min: self.min - amount,160max: self.max + amount,161};162debug_assert!(b.min.cmple(b.max).all());163b164}165166#[inline(always)]167fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {168let amount = amount.into();169let b = Self {170min: self.min + amount,171max: self.max - amount,172};173debug_assert!(b.min.cmple(b.max).all());174b175}176177#[inline(always)]178fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {179let scale = scale.into();180let b = Self {181min: self.center() - (self.half_size() * scale),182max: self.center() + (self.half_size() * scale),183};184debug_assert!(b.min.cmple(b.max).all());185b186}187188/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.189///190/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.191///192/// Note that the result may not be as tightly fitting as the original, and repeated rotations193/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,194/// and consider storing the original AABB and rotating that every time instead.195#[inline(always)]196fn transformed_by(197mut self,198translation: impl Into<Self::Translation>,199rotation: impl Into<Self::Rotation>,200) -> Self {201self.transform_by(translation, rotation);202self203}204205/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.206///207/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.208///209/// Note that the result may not be as tightly fitting as the original, and repeated rotations210/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,211/// and consider storing the original AABB and rotating that every time instead.212#[inline(always)]213fn transform_by(214&mut self,215translation: impl Into<Self::Translation>,216rotation: impl Into<Self::Rotation>,217) {218self.rotate_by(rotation);219self.translate_by(translation);220}221222#[inline(always)]223fn translate_by(&mut self, translation: impl Into<Self::Translation>) {224let translation = translation.into();225self.min += translation;226self.max += translation;227}228229/// Rotates the bounding volume around the origin by the given rotation.230///231/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.232///233/// Note that the result may not be as tightly fitting as the original, and repeated rotations234/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,235/// and consider storing the original AABB and rotating that every time instead.236#[inline(always)]237fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {238self.rotate_by(rotation);239self240}241242/// Rotates the bounding volume around the origin by the given rotation.243///244/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.245///246/// Note that the result may not be as tightly fitting as the original, and repeated rotations247/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,248/// and consider storing the original AABB and rotating that every time instead.249#[inline(always)]250fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {251let rot_mat = Mat3::from_quat(rotation.into());252let half_size = rot_mat.abs() * self.half_size();253*self = Self::new(rot_mat * self.center(), half_size);254}255}256257impl IntersectsVolume<Self> for Aabb3d {258#[inline(always)]259fn intersects(&self, other: &Self) -> bool {260self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()261}262}263264impl IntersectsVolume<BoundingSphere> for Aabb3d {265#[inline(always)]266fn intersects(&self, sphere: &BoundingSphere) -> bool {267let closest_point = self.closest_point(sphere.center);268let distance_squared = sphere.center.distance_squared(closest_point);269let radius_squared = sphere.radius().squared();270distance_squared <= radius_squared271}272}273274#[cfg(test)]275mod aabb3d_tests {276use approx::assert_relative_eq;277278use super::Aabb3d;279use crate::{280bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},281ops, Quat, Vec3, Vec3A,282};283284#[test]285fn center() {286let aabb = Aabb3d {287min: Vec3A::new(-0.5, -1., -0.5),288max: Vec3A::new(1., 1., 2.),289};290assert!((aabb.center() - Vec3A::new(0.25, 0., 0.75)).length() < f32::EPSILON);291let aabb = Aabb3d {292min: Vec3A::new(5., 5., -10.),293max: Vec3A::new(10., 10., -5.),294};295assert!((aabb.center() - Vec3A::new(7.5, 7.5, -7.5)).length() < f32::EPSILON);296}297298#[test]299fn half_size() {300let aabb = Aabb3d {301min: Vec3A::new(-0.5, -1., -0.5),302max: Vec3A::new(1., 1., 2.),303};304assert!((aabb.half_size() - Vec3A::new(0.75, 1., 1.25)).length() < f32::EPSILON);305}306307#[test]308fn area() {309let aabb = Aabb3d {310min: Vec3A::new(-1., -1., -1.),311max: Vec3A::new(1., 1., 1.),312};313assert!(ops::abs(aabb.visible_area() - 12.) < f32::EPSILON);314let aabb = Aabb3d {315min: Vec3A::new(0., 0., 0.),316max: Vec3A::new(1., 0.5, 0.25),317};318assert!(ops::abs(aabb.visible_area() - 0.875) < f32::EPSILON);319}320321#[test]322fn contains() {323let a = Aabb3d {324min: Vec3A::new(-1., -1., -1.),325max: Vec3A::new(1., 1., 1.),326};327let b = Aabb3d {328min: Vec3A::new(-2., -1., -1.),329max: Vec3A::new(1., 1., 1.),330};331assert!(!a.contains(&b));332let b = Aabb3d {333min: Vec3A::new(-0.25, -0.8, -0.9),334max: Vec3A::new(1., 1., 0.9),335};336assert!(a.contains(&b));337}338339#[test]340fn merge() {341let a = Aabb3d {342min: Vec3A::new(-1., -1., -1.),343max: Vec3A::new(1., 0.5, 1.),344};345let b = Aabb3d {346min: Vec3A::new(-2., -0.5, -0.),347max: Vec3A::new(0.75, 1., 2.),348};349let merged = a.merge(&b);350assert!((merged.min - Vec3A::new(-2., -1., -1.)).length() < f32::EPSILON);351assert!((merged.max - Vec3A::new(1., 1., 2.)).length() < f32::EPSILON);352assert!(merged.contains(&a));353assert!(merged.contains(&b));354assert!(!a.contains(&merged));355assert!(!b.contains(&merged));356}357358#[test]359fn grow() {360let a = Aabb3d {361min: Vec3A::new(-1., -1., -1.),362max: Vec3A::new(1., 1., 1.),363};364let padded = a.grow(Vec3A::ONE);365assert!((padded.min - Vec3A::new(-2., -2., -2.)).length() < f32::EPSILON);366assert!((padded.max - Vec3A::new(2., 2., 2.)).length() < f32::EPSILON);367assert!(padded.contains(&a));368assert!(!a.contains(&padded));369}370371#[test]372fn shrink() {373let a = Aabb3d {374min: Vec3A::new(-2., -2., -2.),375max: Vec3A::new(2., 2., 2.),376};377let shrunk = a.shrink(Vec3A::ONE);378assert!((shrunk.min - Vec3A::new(-1., -1., -1.)).length() < f32::EPSILON);379assert!((shrunk.max - Vec3A::new(1., 1., 1.)).length() < f32::EPSILON);380assert!(a.contains(&shrunk));381assert!(!shrunk.contains(&a));382}383384#[test]385fn scale_around_center() {386let a = Aabb3d {387min: Vec3A::NEG_ONE,388max: Vec3A::ONE,389};390let scaled = a.scale_around_center(Vec3A::splat(2.));391assert!((scaled.min - Vec3A::splat(-2.)).length() < f32::EPSILON);392assert!((scaled.max - Vec3A::splat(2.)).length() < f32::EPSILON);393assert!(!a.contains(&scaled));394assert!(scaled.contains(&a));395}396397#[test]398fn rotate() {399use core::f32::consts::PI;400let a = Aabb3d {401min: Vec3A::new(-2.0, -2.0, -2.0),402max: Vec3A::new(2.0, 2.0, 2.0),403};404let rotation = Quat::from_euler(glam::EulerRot::XYZ, PI, PI, 0.0);405let rotated = a.rotated_by(rotation);406assert_relative_eq!(rotated.min, a.min);407assert_relative_eq!(rotated.max, a.max);408}409410#[test]411fn transform() {412let a = Aabb3d {413min: Vec3A::new(-2.0, -2.0, -2.0),414max: Vec3A::new(2.0, 2.0, 2.0),415};416let transformed = a.transformed_by(417Vec3A::new(2.0, -2.0, 4.0),418Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),419);420let half_length = ops::hypot(2.0, 2.0);421assert_eq!(422transformed.min,423Vec3A::new(2.0 - half_length, -half_length - 2.0, 2.0)424);425assert_eq!(426transformed.max,427Vec3A::new(2.0 + half_length, half_length - 2.0, 6.0)428);429}430431#[test]432fn closest_point() {433let aabb = Aabb3d {434min: Vec3A::NEG_ONE,435max: Vec3A::ONE,436};437assert_eq!(aabb.closest_point(Vec3A::X * 10.0), Vec3A::X);438assert_eq!(aabb.closest_point(Vec3A::NEG_ONE * 10.0), Vec3A::NEG_ONE);439assert_eq!(440aabb.closest_point(Vec3A::new(0.25, 0.1, 0.3)),441Vec3A::new(0.25, 0.1, 0.3)442);443}444445#[test]446fn intersect_aabb() {447let aabb = Aabb3d {448min: Vec3A::NEG_ONE,449max: Vec3A::ONE,450};451assert!(aabb.intersects(&aabb));452assert!(aabb.intersects(&Aabb3d {453min: Vec3A::splat(0.5),454max: Vec3A::splat(2.0),455}));456assert!(aabb.intersects(&Aabb3d {457min: Vec3A::splat(-2.0),458max: Vec3A::splat(-0.5),459}));460assert!(!aabb.intersects(&Aabb3d {461min: Vec3A::new(1.1, 0.0, 0.0),462max: Vec3A::new(2.0, 0.5, 0.25),463}));464}465466#[test]467fn intersect_bounding_sphere() {468let aabb = Aabb3d {469min: Vec3A::NEG_ONE,470max: Vec3A::ONE,471};472assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));473assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));474assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));475assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));476}477}478479use crate::primitives::Sphere;480481/// A bounding sphere482#[derive(Clone, Copy, Debug, PartialEq)]483#[cfg_attr(484feature = "bevy_reflect",485derive(Reflect),486reflect(Debug, PartialEq, Clone)487)]488#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]489#[cfg_attr(490all(feature = "serialize", feature = "bevy_reflect"),491reflect(Serialize, Deserialize)492)]493pub struct BoundingSphere {494/// The center of the bounding sphere495pub center: Vec3A,496/// The sphere497pub sphere: Sphere,498}499500impl BoundingSphere {501/// Constructs a bounding sphere from its center and radius.502pub fn new(center: impl Into<Vec3A>, radius: f32) -> Self {503debug_assert!(radius >= 0.);504Self {505center: center.into(),506sphere: Sphere { radius },507}508}509510/// Computes a [`BoundingSphere`] containing the given set of points,511/// transformed by the rotation and translation of the given isometry.512///513/// The bounding sphere is not guaranteed to be the smallest possible.514#[inline(always)]515pub fn from_point_cloud(516isometry: impl Into<Isometry3d>,517points: &[impl Copy + Into<Vec3A>],518) -> BoundingSphere {519let isometry = isometry.into();520521let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));522let mut radius_squared: f32 = 0.0;523524for point in points {525// Get squared version to avoid unnecessary sqrt calls526let distance_squared = Into::<Vec3A>::into(*point).distance_squared(center);527if distance_squared > radius_squared {528radius_squared = distance_squared;529}530}531532BoundingSphere::new(isometry * center, ops::sqrt(radius_squared))533}534535/// Get the radius of the bounding sphere536#[inline(always)]537pub fn radius(&self) -> f32 {538self.sphere.radius539}540541/// Computes the smallest [`Aabb3d`] containing this [`BoundingSphere`].542#[inline(always)]543pub fn aabb_3d(&self) -> Aabb3d {544Aabb3d {545min: self.center - self.radius(),546max: self.center + self.radius(),547}548}549550/// Finds the point on the bounding sphere that is closest to the given `point`.551///552/// If the point is outside the sphere, the returned point will be on the surface of the sphere.553/// Otherwise, it will be inside the sphere and returned as is.554#[inline(always)]555pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {556let point = point.into();557let radius = self.radius();558let distance_squared = (point - self.center).length_squared();559560if distance_squared <= radius.squared() {561// The point is inside the sphere.562point563} else {564// The point is outside the sphere.565// Find the closest point on the surface of the sphere.566let dir_to_point = point / ops::sqrt(distance_squared);567self.center + radius * dir_to_point568}569}570}571572impl BoundingVolume for BoundingSphere {573type Translation = Vec3A;574type Rotation = Quat;575type HalfSize = f32;576577#[inline(always)]578fn center(&self) -> Self::Translation {579self.center580}581582#[inline(always)]583fn half_size(&self) -> Self::HalfSize {584self.radius()585}586587#[inline(always)]588fn visible_area(&self) -> f32 {5892. * core::f32::consts::PI * self.radius() * self.radius()590}591592#[inline(always)]593fn contains(&self, other: &Self) -> bool {594let diff = self.radius() - other.radius();595self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)596}597598#[inline(always)]599fn merge(&self, other: &Self) -> Self {600let diff = other.center - self.center;601let length = diff.length();602if self.radius() >= length + other.radius() {603return *self;604}605if other.radius() >= length + self.radius() {606return *other;607}608let dir = diff / length;609Self::new(610(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),611(length + self.radius() + other.radius()) / 2.,612)613}614615#[inline(always)]616fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {617let amount = amount.into();618debug_assert!(amount >= 0.);619Self {620center: self.center,621sphere: Sphere {622radius: self.radius() + amount,623},624}625}626627#[inline(always)]628fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {629let amount = amount.into();630debug_assert!(amount >= 0.);631debug_assert!(self.radius() >= amount);632Self {633center: self.center,634sphere: Sphere {635radius: self.radius() - amount,636},637}638}639640#[inline(always)]641fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {642let scale = scale.into();643debug_assert!(scale >= 0.);644Self::new(self.center, self.radius() * scale)645}646647#[inline(always)]648fn translate_by(&mut self, translation: impl Into<Self::Translation>) {649self.center += translation.into();650}651652#[inline(always)]653fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {654let rotation: Quat = rotation.into();655self.center = rotation * self.center;656}657}658659impl IntersectsVolume<Self> for BoundingSphere {660#[inline(always)]661fn intersects(&self, other: &Self) -> bool {662let center_distance_squared = self.center.distance_squared(other.center);663let radius_sum_squared = (self.radius() + other.radius()).squared();664center_distance_squared <= radius_sum_squared665}666}667668impl IntersectsVolume<Aabb3d> for BoundingSphere {669#[inline(always)]670fn intersects(&self, aabb: &Aabb3d) -> bool {671aabb.intersects(self)672}673}674675#[cfg(test)]676mod bounding_sphere_tests {677use approx::assert_relative_eq;678679use super::BoundingSphere;680use crate::{681bounding::{BoundingVolume, IntersectsVolume},682ops, Quat, Vec3, Vec3A,683};684685#[test]686fn area() {687let sphere = BoundingSphere::new(Vec3::ONE, 5.);688// Since this number is messy we check it with a higher threshold689assert!(ops::abs(sphere.visible_area() - 157.0796) < 0.001);690}691692#[test]693fn contains() {694let a = BoundingSphere::new(Vec3::ONE, 5.);695let b = BoundingSphere::new(Vec3::new(5.5, 1., 1.), 1.);696assert!(!a.contains(&b));697let b = BoundingSphere::new(Vec3::new(1., -3.5, 1.), 0.5);698assert!(a.contains(&b));699}700701#[test]702fn contains_identical() {703let a = BoundingSphere::new(Vec3::ONE, 5.);704assert!(a.contains(&a));705}706707#[test]708fn merge() {709// When merging two circles that don't contain each other, we find a center position that710// contains both711let a = BoundingSphere::new(Vec3::ONE, 5.);712let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.);713let merged = a.merge(&b);714assert!((merged.center - Vec3A::new(1., 1., 0.5)).length() < f32::EPSILON);715assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);716assert!(merged.contains(&a));717assert!(merged.contains(&b));718assert!(!a.contains(&merged));719assert!(!b.contains(&merged));720721// When one circle contains the other circle, we use the bigger circle722let b = BoundingSphere::new(Vec3::ZERO, 3.);723assert!(a.contains(&b));724let merged = a.merge(&b);725assert_eq!(merged.center, a.center);726assert_eq!(merged.radius(), a.radius());727728// When two circles are at the same point, we use the bigger radius729let b = BoundingSphere::new(Vec3::ONE, 6.);730let merged = a.merge(&b);731assert_eq!(merged.center, a.center);732assert_eq!(merged.radius(), b.radius());733}734735#[test]736fn merge_identical() {737let a = BoundingSphere::new(Vec3::ONE, 5.);738let merged = a.merge(&a);739assert_eq!(merged.center, a.center);740assert_eq!(merged.radius(), a.radius());741}742743#[test]744fn grow() {745let a = BoundingSphere::new(Vec3::ONE, 5.);746let padded = a.grow(1.25);747assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);748assert!(padded.contains(&a));749assert!(!a.contains(&padded));750}751752#[test]753fn shrink() {754let a = BoundingSphere::new(Vec3::ONE, 5.);755let shrunk = a.shrink(0.5);756assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);757assert!(a.contains(&shrunk));758assert!(!shrunk.contains(&a));759}760761#[test]762fn scale_around_center() {763let a = BoundingSphere::new(Vec3::ONE, 5.);764let scaled = a.scale_around_center(2.);765assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);766assert!(!a.contains(&scaled));767assert!(scaled.contains(&a));768}769770#[test]771fn transform() {772let a = BoundingSphere::new(Vec3::ONE, 5.0);773let transformed = a.transformed_by(774Vec3::new(2.0, -2.0, 4.0),775Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),776);777assert_relative_eq!(778transformed.center,779Vec3A::new(2.0, core::f32::consts::SQRT_2 - 2.0, 5.0)780);781assert_eq!(transformed.radius(), 5.0);782}783784#[test]785fn closest_point() {786let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);787assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3A::X);788assert_eq!(789sphere.closest_point(Vec3::NEG_ONE * 10.0),790Vec3A::NEG_ONE.normalize()791);792assert_eq!(793sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),794Vec3A::new(0.25, 0.1, 0.3)795);796}797798#[test]799fn intersect_bounding_sphere() {800let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);801assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));802assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));803assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));804assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));805}806}807808809