Path: blob/main/crates/bevy_math/src/bounding/bounded3d/mod.rs
9356 views
mod extrusion;1mod primitive_impls;23use glam::Mat3;45use super::{BoundingVolume, IntersectsVolume};6use crate::{7ops::{self, FloatPow},8primitives::Cuboid,9Isometry3d, Quat, Vec3A,10};1112#[cfg(feature = "bevy_reflect")]13use bevy_reflect::Reflect;14#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]15use bevy_reflect::{ReflectDeserialize, ReflectSerialize};16#[cfg(feature = "serialize")]17use serde::{Deserialize, Serialize};1819pub use extrusion::BoundedExtrusion;2021/// Computes the geometric center of the given set of points.22#[inline]23fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3A {24let (acc, len) = points.fold((Vec3A::ZERO, 0), |(acc, len), point| {25(acc + point.into(), len + 1)26});2728assert!(29len > 0,30"cannot compute the center of an empty set of points"31);32acc / len as f3233}3435/// A trait with methods that return 3D bounding volumes for a shape.36pub trait Bounded3d {37/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.38fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;39/// Get a bounding sphere for the shape translated and rotated by the given isometry.40fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;41}4243/// A 3D axis-aligned bounding box44#[derive(Clone, Copy, Debug, PartialEq)]45#[cfg_attr(46feature = "bevy_reflect",47derive(Reflect),48reflect(Debug, PartialEq, Clone)49)]50#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]51#[cfg_attr(52all(feature = "serialize", feature = "bevy_reflect"),53reflect(Serialize, Deserialize)54)]55pub struct Aabb3d {56/// The minimum point of the box57pub min: Vec3A,58/// The maximum point of the box59pub max: Vec3A,60}6162impl Aabb3d {63/// Constructs an AABB from its center and half-size.64#[inline]65pub fn new(center: impl Into<Vec3A>, half_size: impl Into<Vec3A>) -> Self {66let (center, half_size) = (center.into(), half_size.into());67debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0 && half_size.z >= 0.0);68Self {69min: center - half_size,70max: center + half_size,71}72}7374/// Constructs an AABB from its minimum and maximum extent.75#[inline]76pub fn from_min_max(min: impl Into<Vec3A>, max: impl Into<Vec3A>) -> Self {77let (min, max) = (min.into(), max.into());78debug_assert!(min.x <= max.x && min.y <= max.y && min.z <= max.z);79Self { min, max }80}8182/// Computes the smallest [`Aabb3d`] containing the given set of points,83/// transformed by the rotation and translation of the given isometry.84///85/// # Panics86///87/// Panics if the given set of points is empty.88#[inline]89pub fn from_point_cloud(90isometry: impl Into<Isometry3d>,91points: impl Iterator<Item = impl Into<Vec3A>>,92) -> Aabb3d {93let isometry = isometry.into();9495// Transform all points by rotation96let mut iter = points.map(|point| isometry.rotation * point.into());9798let first = iter99.next()100.expect("point cloud must contain at least one point for Aabb3d construction");101102let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {103(point.min(prev_min), point.max(prev_max))104});105106Aabb3d {107min: min + isometry.translation,108max: max + isometry.translation,109}110}111112/// Computes the smallest [`BoundingSphere`] containing this [`Aabb3d`].113#[inline]114pub fn bounding_sphere(&self) -> BoundingSphere {115let radius = self.min.distance(self.max) / 2.0;116BoundingSphere::new(self.center(), radius)117}118119/// Finds the point on the AABB that is closest to the given `point`.120///121/// If the point is outside the AABB, the returned point will be on the surface of the AABB.122/// Otherwise, it will be inside the AABB and returned as is.123#[inline]124pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {125// Clamp point coordinates to the AABB126point.into().clamp(self.min, self.max)127}128}129130impl From<Cuboid> for Aabb3d {131fn from(value: Cuboid) -> Self {132Aabb3d {133min: (-value.half_size).into(),134max: value.half_size.into(),135}136}137}138139impl BoundingVolume for Aabb3d {140type Translation = Vec3A;141type Rotation = Quat;142type HalfSize = Vec3A;143144#[inline]145fn center(&self) -> Self::Translation {146(self.min + self.max) / 2.147}148149#[inline]150fn half_size(&self) -> Self::HalfSize {151(self.max - self.min) / 2.152}153154#[inline]155fn visible_area(&self) -> f32 {156let b = (self.max - self.min).max(Vec3A::ZERO);157b.x * (b.y + b.z) + b.y * b.z158}159160#[inline]161fn contains(&self, other: &Self) -> bool {162other.min.cmpge(self.min).all() && other.max.cmple(self.max).all()163}164165#[inline]166fn merge(&self, other: &Self) -> Self {167Self {168min: self.min.min(other.min),169max: self.max.max(other.max),170}171}172173#[inline]174fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {175let amount = amount.into();176let b = Self {177min: self.min - amount,178max: self.max + amount,179};180debug_assert!(b.min.cmple(b.max).all());181b182}183184#[inline]185fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {186let amount = amount.into();187let b = Self {188min: self.min + amount,189max: self.max - amount,190};191debug_assert!(b.min.cmple(b.max).all());192b193}194195#[inline]196fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {197let scale = scale.into();198let b = Self {199min: self.center() - (self.half_size() * scale),200max: self.center() + (self.half_size() * scale),201};202debug_assert!(b.min.cmple(b.max).all());203b204}205206/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.207///208/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.209///210/// Note that the result may not be as tightly fitting as the original, and repeated rotations211/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,212/// and consider storing the original AABB and rotating that every time instead.213#[inline]214fn transformed_by(215mut self,216translation: impl Into<Self::Translation>,217rotation: impl Into<Self::Rotation>,218) -> Self {219self.transform_by(translation, rotation);220self221}222223/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.224///225/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.226///227/// Note that the result may not be as tightly fitting as the original, and repeated rotations228/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,229/// and consider storing the original AABB and rotating that every time instead.230#[inline]231fn transform_by(232&mut self,233translation: impl Into<Self::Translation>,234rotation: impl Into<Self::Rotation>,235) {236self.rotate_by(rotation);237self.translate_by(translation);238}239240#[inline]241fn translate_by(&mut self, translation: impl Into<Self::Translation>) {242let translation = translation.into();243self.min += translation;244self.max += translation;245}246247/// Rotates the bounding volume around the origin by the given rotation.248///249/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.250///251/// Note that the result may not be as tightly fitting as the original, and repeated rotations252/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,253/// and consider storing the original AABB and rotating that every time instead.254#[inline]255fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {256self.rotate_by(rotation);257self258}259260/// Rotates the bounding volume around the origin by the given rotation.261///262/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.263///264/// Note that the result may not be as tightly fitting as the original, and repeated rotations265/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,266/// and consider storing the original AABB and rotating that every time instead.267#[inline]268fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {269let rot_mat = Mat3::from_quat(rotation.into());270let half_size = rot_mat.abs() * self.half_size();271*self = Self::new(rot_mat * self.center(), half_size);272}273}274275impl IntersectsVolume<Self> for Aabb3d {276#[inline]277fn intersects(&self, other: &Self) -> bool {278self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()279}280}281282impl IntersectsVolume<BoundingSphere> for Aabb3d {283#[inline]284fn intersects(&self, sphere: &BoundingSphere) -> bool {285let closest_point = self.closest_point(sphere.center);286let distance_squared = sphere.center.distance_squared(closest_point);287let radius_squared = sphere.radius().squared();288distance_squared <= radius_squared289}290}291292#[cfg(test)]293mod aabb3d_tests {294use approx::assert_relative_eq;295296use super::Aabb3d;297use crate::{298bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},299ops, Quat, Vec3, Vec3A,300};301302#[test]303fn center() {304let aabb = Aabb3d {305min: Vec3A::new(-0.5, -1., -0.5),306max: Vec3A::new(1., 1., 2.),307};308assert!((aabb.center() - Vec3A::new(0.25, 0., 0.75)).length() < f32::EPSILON);309let aabb = Aabb3d {310min: Vec3A::new(5., 5., -10.),311max: Vec3A::new(10., 10., -5.),312};313assert!((aabb.center() - Vec3A::new(7.5, 7.5, -7.5)).length() < f32::EPSILON);314}315316#[test]317fn half_size() {318let aabb = Aabb3d {319min: Vec3A::new(-0.5, -1., -0.5),320max: Vec3A::new(1., 1., 2.),321};322assert!((aabb.half_size() - Vec3A::new(0.75, 1., 1.25)).length() < f32::EPSILON);323}324325#[test]326fn area() {327let aabb = Aabb3d {328min: Vec3A::new(-1., -1., -1.),329max: Vec3A::new(1., 1., 1.),330};331assert!(ops::abs(aabb.visible_area() - 12.) < f32::EPSILON);332let aabb = Aabb3d {333min: Vec3A::new(0., 0., 0.),334max: Vec3A::new(1., 0.5, 0.25),335};336assert!(ops::abs(aabb.visible_area() - 0.875) < f32::EPSILON);337}338339#[test]340fn contains() {341let a = Aabb3d {342min: Vec3A::new(-1., -1., -1.),343max: Vec3A::new(1., 1., 1.),344};345let b = Aabb3d {346min: Vec3A::new(-2., -1., -1.),347max: Vec3A::new(1., 1., 1.),348};349assert!(!a.contains(&b));350let b = Aabb3d {351min: Vec3A::new(-0.25, -0.8, -0.9),352max: Vec3A::new(1., 1., 0.9),353};354assert!(a.contains(&b));355}356357#[test]358fn merge() {359let a = Aabb3d {360min: Vec3A::new(-1., -1., -1.),361max: Vec3A::new(1., 0.5, 1.),362};363let b = Aabb3d {364min: Vec3A::new(-2., -0.5, -0.),365max: Vec3A::new(0.75, 1., 2.),366};367let merged = a.merge(&b);368assert!((merged.min - Vec3A::new(-2., -1., -1.)).length() < f32::EPSILON);369assert!((merged.max - Vec3A::new(1., 1., 2.)).length() < f32::EPSILON);370assert!(merged.contains(&a));371assert!(merged.contains(&b));372assert!(!a.contains(&merged));373assert!(!b.contains(&merged));374}375376#[test]377fn grow() {378let a = Aabb3d {379min: Vec3A::new(-1., -1., -1.),380max: Vec3A::new(1., 1., 1.),381};382let padded = a.grow(Vec3A::ONE);383assert!((padded.min - Vec3A::new(-2., -2., -2.)).length() < f32::EPSILON);384assert!((padded.max - Vec3A::new(2., 2., 2.)).length() < f32::EPSILON);385assert!(padded.contains(&a));386assert!(!a.contains(&padded));387}388389#[test]390fn shrink() {391let a = Aabb3d {392min: Vec3A::new(-2., -2., -2.),393max: Vec3A::new(2., 2., 2.),394};395let shrunk = a.shrink(Vec3A::ONE);396assert!((shrunk.min - Vec3A::new(-1., -1., -1.)).length() < f32::EPSILON);397assert!((shrunk.max - Vec3A::new(1., 1., 1.)).length() < f32::EPSILON);398assert!(a.contains(&shrunk));399assert!(!shrunk.contains(&a));400}401402#[test]403fn scale_around_center() {404let a = Aabb3d {405min: Vec3A::NEG_ONE,406max: Vec3A::ONE,407};408let scaled = a.scale_around_center(Vec3A::splat(2.));409assert!((scaled.min - Vec3A::splat(-2.)).length() < f32::EPSILON);410assert!((scaled.max - Vec3A::splat(2.)).length() < f32::EPSILON);411assert!(!a.contains(&scaled));412assert!(scaled.contains(&a));413}414415#[test]416fn rotate() {417use core::f32::consts::PI;418let a = Aabb3d {419min: Vec3A::new(-2.0, -2.0, -2.0),420max: Vec3A::new(2.0, 2.0, 2.0),421};422let rotation = Quat::from_euler(glam::EulerRot::XYZ, PI, PI, 0.0);423let rotated = a.rotated_by(rotation);424assert_relative_eq!(rotated.min, a.min);425assert_relative_eq!(rotated.max, a.max);426}427428#[test]429fn transform() {430let a = Aabb3d {431min: Vec3A::new(-2.0, -2.0, -2.0),432max: Vec3A::new(2.0, 2.0, 2.0),433};434let transformed = a.transformed_by(435Vec3A::new(2.0, -2.0, 4.0),436Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),437);438let half_length = ops::hypot(2.0, 2.0);439assert_eq!(440transformed.min,441Vec3A::new(2.0 - half_length, -half_length - 2.0, 2.0)442);443assert_eq!(444transformed.max,445Vec3A::new(2.0 + half_length, half_length - 2.0, 6.0)446);447}448449#[test]450fn closest_point() {451let aabb = Aabb3d {452min: Vec3A::NEG_ONE,453max: Vec3A::ONE,454};455assert_eq!(aabb.closest_point(Vec3A::X * 10.0), Vec3A::X);456assert_eq!(aabb.closest_point(Vec3A::NEG_ONE * 10.0), Vec3A::NEG_ONE);457assert_eq!(458aabb.closest_point(Vec3A::new(0.25, 0.1, 0.3)),459Vec3A::new(0.25, 0.1, 0.3)460);461}462463#[test]464fn intersect_aabb() {465let aabb = Aabb3d {466min: Vec3A::NEG_ONE,467max: Vec3A::ONE,468};469assert!(aabb.intersects(&aabb));470assert!(aabb.intersects(&Aabb3d {471min: Vec3A::splat(0.5),472max: Vec3A::splat(2.0),473}));474assert!(aabb.intersects(&Aabb3d {475min: Vec3A::splat(-2.0),476max: Vec3A::splat(-0.5),477}));478assert!(!aabb.intersects(&Aabb3d {479min: Vec3A::new(1.1, 0.0, 0.0),480max: Vec3A::new(2.0, 0.5, 0.25),481}));482}483484#[test]485fn intersect_bounding_sphere() {486let aabb = Aabb3d {487min: Vec3A::NEG_ONE,488max: Vec3A::ONE,489};490assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));491assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));492assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));493assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));494}495}496497use crate::primitives::Sphere;498499/// A bounding sphere500#[derive(Clone, Copy, Debug, PartialEq)]501#[cfg_attr(502feature = "bevy_reflect",503derive(Reflect),504reflect(Debug, PartialEq, Clone)505)]506#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]507#[cfg_attr(508all(feature = "serialize", feature = "bevy_reflect"),509reflect(Serialize, Deserialize)510)]511pub struct BoundingSphere {512/// The center of the bounding sphere513pub center: Vec3A,514/// The sphere515pub sphere: Sphere,516}517518impl BoundingSphere {519/// Constructs a bounding sphere from its center and radius.520pub fn new(center: impl Into<Vec3A>, radius: f32) -> Self {521debug_assert!(radius >= 0.);522Self {523center: center.into(),524sphere: Sphere { radius },525}526}527528/// Computes a [`BoundingSphere`] containing the given set of points,529/// transformed by the rotation and translation of the given isometry.530///531/// The bounding sphere is not guaranteed to be the smallest possible.532#[inline]533pub fn from_point_cloud(534isometry: impl Into<Isometry3d>,535points: &[impl Copy + Into<Vec3A>],536) -> BoundingSphere {537let isometry = isometry.into();538539let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));540let mut radius_squared: f32 = 0.0;541542for point in points {543// Get squared version to avoid unnecessary sqrt calls544let distance_squared = Into::<Vec3A>::into(*point).distance_squared(center);545if distance_squared > radius_squared {546radius_squared = distance_squared;547}548}549550BoundingSphere::new(isometry * center, ops::sqrt(radius_squared))551}552553/// Get the radius of the bounding sphere554#[inline]555pub fn radius(&self) -> f32 {556self.sphere.radius557}558559/// Computes the smallest [`Aabb3d`] containing this [`BoundingSphere`].560#[inline]561pub fn aabb_3d(&self) -> Aabb3d {562Aabb3d {563min: self.center - self.radius(),564max: self.center + self.radius(),565}566}567568/// Finds the point on the bounding sphere that is closest to the given `point`.569///570/// If the point is outside the sphere, the returned point will be on the surface of the sphere.571/// Otherwise, it will be inside the sphere and returned as is.572#[inline]573pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {574let point = point.into();575let radius = self.radius();576let distance_squared = (point - self.center).length_squared();577578if distance_squared <= radius.squared() {579// The point is inside the sphere.580point581} else {582// The point is outside the sphere.583// Find the closest point on the surface of the sphere.584let dir_to_point = point / ops::sqrt(distance_squared);585self.center + radius * dir_to_point586}587}588}589590impl BoundingVolume for BoundingSphere {591type Translation = Vec3A;592type Rotation = Quat;593type HalfSize = f32;594595#[inline]596fn center(&self) -> Self::Translation {597self.center598}599600#[inline]601fn half_size(&self) -> Self::HalfSize {602self.radius()603}604605#[inline]606fn visible_area(&self) -> f32 {6072. * core::f32::consts::PI * self.radius() * self.radius()608}609610#[inline]611fn contains(&self, other: &Self) -> bool {612let diff = self.radius() - other.radius();613self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)614}615616#[inline]617fn merge(&self, other: &Self) -> Self {618let diff = other.center - self.center;619let length = diff.length();620if self.radius() >= length + other.radius() {621return *self;622}623if other.radius() >= length + self.radius() {624return *other;625}626let dir = diff / length;627Self::new(628(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),629(length + self.radius() + other.radius()) / 2.,630)631}632633#[inline]634fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {635let amount = amount.into();636debug_assert!(amount >= 0.);637Self {638center: self.center,639sphere: Sphere {640radius: self.radius() + amount,641},642}643}644645#[inline]646fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {647let amount = amount.into();648debug_assert!(amount >= 0.);649debug_assert!(self.radius() >= amount);650Self {651center: self.center,652sphere: Sphere {653radius: self.radius() - amount,654},655}656}657658#[inline]659fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {660let scale = scale.into();661debug_assert!(scale >= 0.);662Self::new(self.center, self.radius() * scale)663}664665#[inline]666fn translate_by(&mut self, translation: impl Into<Self::Translation>) {667self.center += translation.into();668}669670#[inline]671fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {672let rotation: Quat = rotation.into();673self.center = rotation * self.center;674}675}676677impl IntersectsVolume<Self> for BoundingSphere {678#[inline]679fn intersects(&self, other: &Self) -> bool {680let center_distance_squared = self.center.distance_squared(other.center);681let radius_sum_squared = (self.radius() + other.radius()).squared();682center_distance_squared <= radius_sum_squared683}684}685686impl IntersectsVolume<Aabb3d> for BoundingSphere {687#[inline]688fn intersects(&self, aabb: &Aabb3d) -> bool {689aabb.intersects(self)690}691}692693#[cfg(test)]694mod bounding_sphere_tests {695use approx::assert_relative_eq;696697use super::BoundingSphere;698use crate::{699bounding::{BoundingVolume, IntersectsVolume},700ops, Quat, Vec3, Vec3A,701};702703#[test]704fn area() {705let sphere = BoundingSphere::new(Vec3::ONE, 5.);706// Since this number is messy we check it with a higher threshold707assert!(ops::abs(sphere.visible_area() - 157.0796) < 0.001);708}709710#[test]711fn contains() {712let a = BoundingSphere::new(Vec3::ONE, 5.);713let b = BoundingSphere::new(Vec3::new(5.5, 1., 1.), 1.);714assert!(!a.contains(&b));715let b = BoundingSphere::new(Vec3::new(1., -3.5, 1.), 0.5);716assert!(a.contains(&b));717}718719#[test]720fn contains_identical() {721let a = BoundingSphere::new(Vec3::ONE, 5.);722assert!(a.contains(&a));723}724725#[test]726fn merge() {727// When merging two circles that don't contain each other, we find a center position that728// contains both729let a = BoundingSphere::new(Vec3::ONE, 5.);730let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.);731let merged = a.merge(&b);732assert!((merged.center - Vec3A::new(1., 1., 0.5)).length() < f32::EPSILON);733assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);734assert!(merged.contains(&a));735assert!(merged.contains(&b));736assert!(!a.contains(&merged));737assert!(!b.contains(&merged));738739// When one circle contains the other circle, we use the bigger circle740let b = BoundingSphere::new(Vec3::ZERO, 3.);741assert!(a.contains(&b));742let merged = a.merge(&b);743assert_eq!(merged.center, a.center);744assert_eq!(merged.radius(), a.radius());745746// When two circles are at the same point, we use the bigger radius747let b = BoundingSphere::new(Vec3::ONE, 6.);748let merged = a.merge(&b);749assert_eq!(merged.center, a.center);750assert_eq!(merged.radius(), b.radius());751}752753#[test]754fn merge_identical() {755let a = BoundingSphere::new(Vec3::ONE, 5.);756let merged = a.merge(&a);757assert_eq!(merged.center, a.center);758assert_eq!(merged.radius(), a.radius());759}760761#[test]762fn grow() {763let a = BoundingSphere::new(Vec3::ONE, 5.);764let padded = a.grow(1.25);765assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);766assert!(padded.contains(&a));767assert!(!a.contains(&padded));768}769770#[test]771fn shrink() {772let a = BoundingSphere::new(Vec3::ONE, 5.);773let shrunk = a.shrink(0.5);774assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);775assert!(a.contains(&shrunk));776assert!(!shrunk.contains(&a));777}778779#[test]780fn scale_around_center() {781let a = BoundingSphere::new(Vec3::ONE, 5.);782let scaled = a.scale_around_center(2.);783assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);784assert!(!a.contains(&scaled));785assert!(scaled.contains(&a));786}787788#[test]789fn transform() {790let a = BoundingSphere::new(Vec3::ONE, 5.0);791let transformed = a.transformed_by(792Vec3::new(2.0, -2.0, 4.0),793Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),794);795assert_relative_eq!(796transformed.center,797Vec3A::new(2.0, core::f32::consts::SQRT_2 - 2.0, 5.0)798);799assert_eq!(transformed.radius(), 5.0);800}801802#[test]803fn closest_point() {804let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);805assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3A::X);806assert_eq!(807sphere.closest_point(Vec3::NEG_ONE * 10.0),808Vec3A::NEG_ONE.normalize()809);810assert_eq!(811sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),812Vec3A::new(0.25, 0.1, 0.3)813);814}815816#[test]817fn intersect_bounding_sphere() {818let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);819assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));820assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));821assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));822assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));823}824}825826827