From a8b9c945c753dee998079978e41d060dc42d2b3e Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Wed, 4 Dec 2024 04:14:51 +1100 Subject: [PATCH] Add `no_std` Support to `bevy_math` (#15810) # Objective - Contributes to #15460 ## Solution - Added two new features, `std` (default) and `alloc`, gating `std` and `alloc` behind them respectively. - Added missing `f32` functions to `std_ops` as required. These `f32` methods have been added to the `clippy.toml` deny list to aid in `no_std` development. ## Testing - CI - `cargo clippy -p bevy_math --no-default-features --features libm --target "x86_64-unknown-none"` - `cargo test -p bevy_math --no-default-features --features libm` - `cargo test -p bevy_math --no-default-features --features "libm, alloc"` - `cargo test -p bevy_math --no-default-features --features "libm, alloc, std"` - `cargo test -p bevy_math --no-default-features --features "std"` ## Notes The following items require the `alloc` feature to be enabled: - `CubicBSpline` - `CubicBezier` - `CubicCardinalSpline` - `CubicCurve` - `CubicGenerator` - `CubicHermite` - `CubicNurbs` - `CyclicCubicGenerator` - `RationalCurve` - `RationalGenerator` - `BoxedPolygon` - `BoxedPolyline2d` - `BoxedPolyline3d` - `SampleCurve` - `SampleAutoCurve` - `UnevenSampleCurve` - `UnevenSampleAutoCurve` - `EvenCore` - `UnevenCore` - `ChunkedUnevenCore` This requirement could be relaxed in certain cases, but I had erred on the side of gating rather than modifying. Since `no_std` is a new set of platforms we are adding support to, and the `alloc` feature is enabled by default, this is not a breaking change. --------- Co-authored-by: Benjamin Brienen Co-authored-by: Matty <2975848+mweatherley@users.noreply.github.com> Co-authored-by: Joona Aalto --- crates/bevy_math/Cargo.toml | 41 +++-- crates/bevy_math/clippy.toml | 9 ++ .../bevy_math/src/bounding/bounded2d/mod.rs | 21 +-- .../src/bounding/bounded2d/primitive_impls.rs | 33 ++-- .../src/bounding/bounded3d/extrusion.rs | 19 ++- .../bevy_math/src/bounding/bounded3d/mod.rs | 27 ++-- .../src/bounding/bounded3d/primitive_impls.rs | 17 +- crates/bevy_math/src/bounding/raycast2d.rs | 117 +++++++------- crates/bevy_math/src/bounding/raycast3d.rs | 117 +++++++------- crates/bevy_math/src/common_traits.rs | 2 +- crates/bevy_math/src/compass.rs | 28 ++-- crates/bevy_math/src/cubic_splines.rs | 79 +++++++-- crates/bevy_math/src/curve/adaptors.rs | 5 +- crates/bevy_math/src/curve/cores.rs | 19 ++- crates/bevy_math/src/curve/easing.rs | 10 +- crates/bevy_math/src/curve/interval.rs | 6 +- crates/bevy_math/src/curve/iterable.rs | 6 +- crates/bevy_math/src/curve/mod.rs | 18 ++- crates/bevy_math/src/curve/sample_curves.rs | 1 + crates/bevy_math/src/direction.rs | 30 ++-- crates/bevy_math/src/float_ord.rs | 5 +- crates/bevy_math/src/lib.rs | 17 +- crates/bevy_math/src/ops.rs | 150 ++++++++++++++++++ crates/bevy_math/src/primitives/dim2.rs | 35 ++-- crates/bevy_math/src/primitives/dim3.rs | 19 ++- crates/bevy_math/src/primitives/serde.rs | 20 ++- crates/bevy_math/src/ray.rs | 5 +- crates/bevy_math/src/rects/rect.rs | 6 +- crates/bevy_math/src/rotation2d.rs | 14 +- .../bevy_math/src/sampling/mesh_sampling.rs | 1 + crates/bevy_math/src/sampling/mod.rs | 2 + .../bevy_math/src/sampling/shape_sampling.rs | 8 +- tools/ci/src/commands/compile_check_no_std.rs | 8 + 33 files changed, 627 insertions(+), 268 deletions(-) diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index ec2287e698c23..9da2e9c045d51 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -10,20 +10,20 @@ keywords = ["bevy"] rust-version = "1.81.0" [dependencies] -glam = { version = "0.29", features = ["bytemuck"] } +glam = { version = "0.29", default-features = false, features = ["bytemuck"] } derive_more = { version = "1", default-features = false, features = [ "error", "from", "display", "into", ] } -itertools = "0.13.0" -serde = { version = "1", features = ["derive"], optional = true } +itertools = { version = "0.13.0", default-features = false } +serde = { version = "1", default-features = false, features = [ + "derive", +], optional = true } libm = { version = "0.2", optional = true } -approx = { version = "0.5", optional = true } -rand = { version = "0.8", features = [ - "alloc", -], default-features = false, optional = true } +approx = { version = "0.5", default-features = false, optional = true } +rand = { version = "0.8", default-features = false, optional = true } rand_distr = { version = "0.4.3", optional = true } smallvec = { version = "1.11" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ @@ -36,11 +36,30 @@ approx = "0.5" rand = "0.8" rand_chacha = "0.3" # Enable the approx feature when testing. -bevy_math = { path = ".", version = "0.15.0-dev", features = ["approx"] } -glam = { version = "0.29", features = ["approx"] } +bevy_math = { path = ".", version = "0.15.0-dev", default-features = false, features = [ + "approx", +] } +glam = { version = "0.29", default-features = false, features = ["approx"] } [features] -default = ["rand", "bevy_reflect", "curve"] +default = ["std", "rand", "bevy_reflect", "curve"] +std = [ + "alloc", + "glam/std", + "derive_more/std", + "itertools/use_std", + "serde?/std", + "approx?/std", + "rand?/std", + "rand_distr?/std", +] +alloc = [ + "itertools/use_alloc", + "serde?/alloc", + "rand?/alloc", + "rand_distr?/alloc", +] + serialize = ["dep:serde", "glam/serde"] # Enable approx for glam types to approximate floating point equality comparisons and assertions approx = ["dep:approx", "glam/approx"] @@ -57,6 +76,8 @@ debug_glam_assert = ["glam/debug-glam-assert"] rand = ["dep:rand", "dep:rand_distr", "glam/rand"] # Include code related to the Curve trait curve = [] +# Enable bevy_reflect (requires std) +bevy_reflect = ["dep:bevy_reflect", "std"] [lints] workspace = true diff --git a/crates/bevy_math/clippy.toml b/crates/bevy_math/clippy.toml index bad8801c01736..0fb122e4dcef6 100644 --- a/crates/bevy_math/clippy.toml +++ b/crates/bevy_math/clippy.toml @@ -26,4 +26,13 @@ disallowed-methods = [ { path = "f32::asinh", reason = "use ops::asinh instead for libm determinism" }, { path = "f32::acosh", reason = "use ops::acosh instead for libm determinism" }, { path = "f32::atanh", reason = "use ops::atanh instead for libm determinism" }, + # These methods have defined precision, but are only available from the standard library, + # not in core. Using these substitutes allows for no_std compatibility. + { path = "f32::rem_euclid", reason = "use ops::rem_euclid instead for no_std compatibility" }, + { path = "f32::abs", reason = "use ops::abs instead for no_std compatibility" }, + { path = "f32::sqrt", reason = "use ops::sqrt instead for no_std compatibility" }, + { path = "f32::copysign", reason = "use ops::copysign instead for no_std compatibility" }, + { path = "f32::round", reason = "use ops::round instead for no_std compatibility" }, + { path = "f32::floor", reason = "use ops::floor instead for no_std compatibility" }, + { path = "f32::fract", reason = "use ops::fract instead for no_std compatibility" }, ] diff --git a/crates/bevy_math/src/bounding/bounded2d/mod.rs b/crates/bevy_math/src/bounding/bounded2d/mod.rs index 7e824101bc790..c5be831a86f86 100644 --- a/crates/bevy_math/src/bounding/bounded2d/mod.rs +++ b/crates/bevy_math/src/bounding/bounded2d/mod.rs @@ -2,6 +2,7 @@ mod primitive_impls; use super::{BoundingVolume, IntersectsVolume}; use crate::{ + ops, prelude::{Mat2, Rot2, Vec2}, FloatPow, Isometry2d, }; @@ -296,12 +297,12 @@ mod aabb2d_tests { min: Vec2::new(-1., -1.), max: Vec2::new(1., 1.), }; - assert!((aabb.visible_area() - 4.).abs() < f32::EPSILON); + assert!(ops::abs(aabb.visible_area() - 4.) < f32::EPSILON); let aabb = Aabb2d { min: Vec2::new(0., 0.), max: Vec2::new(1., 0.5), }; - assert!((aabb.visible_area() - 0.5).abs() < f32::EPSILON); + assert!(ops::abs(aabb.visible_area() - 0.5) < f32::EPSILON); } #[test] @@ -488,7 +489,7 @@ impl BoundingCircle { } } - BoundingCircle::new(isometry * center, radius_squared.sqrt()) + BoundingCircle::new(isometry * center, ops::sqrt(radius_squared)) } /// Get the radius of the bounding circle @@ -539,7 +540,7 @@ impl BoundingVolume for BoundingCircle { #[inline(always)] fn contains(&self, other: &Self) -> bool { let diff = self.radius() - other.radius(); - self.center.distance_squared(other.center) <= diff.squared().copysign(diff) + self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff) } #[inline(always)] @@ -614,14 +615,14 @@ mod bounding_circle_tests { use super::BoundingCircle; use crate::{ bounding::{BoundingVolume, IntersectsVolume}, - Vec2, + ops, Vec2, }; #[test] fn area() { let circle = BoundingCircle::new(Vec2::ONE, 5.); // Since this number is messy we check it with a higher threshold - assert!((circle.visible_area() - 78.5398).abs() < 0.001); + assert!(ops::abs(circle.visible_area() - 78.5398) < 0.001); } #[test] @@ -647,7 +648,7 @@ mod bounding_circle_tests { let b = BoundingCircle::new(Vec2::new(1., -4.), 1.); let merged = a.merge(&b); assert!((merged.center - Vec2::new(1., 0.5)).length() < f32::EPSILON); - assert!((merged.radius() - 5.5).abs() < f32::EPSILON); + assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON); assert!(merged.contains(&a)); assert!(merged.contains(&b)); assert!(!a.contains(&merged)); @@ -679,7 +680,7 @@ mod bounding_circle_tests { fn grow() { let a = BoundingCircle::new(Vec2::ONE, 5.); let padded = a.grow(1.25); - assert!((padded.radius() - 6.25).abs() < f32::EPSILON); + assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON); assert!(padded.contains(&a)); assert!(!a.contains(&padded)); } @@ -688,7 +689,7 @@ mod bounding_circle_tests { fn shrink() { let a = BoundingCircle::new(Vec2::ONE, 5.); let shrunk = a.shrink(0.5); - assert!((shrunk.radius() - 4.5).abs() < f32::EPSILON); + assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON); assert!(a.contains(&shrunk)); assert!(!shrunk.contains(&a)); } @@ -697,7 +698,7 @@ mod bounding_circle_tests { fn scale_around_center() { let a = BoundingCircle::new(Vec2::ONE, 5.); let scaled = a.scale_around_center(2.); - assert!((scaled.radius() - 10.).abs() < f32::EPSILON); + assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON); assert!(!a.contains(&scaled)); assert!(scaled.contains(&a)); } diff --git a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs index d4d211cd127a5..aeee66f51e919 100644 --- a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs @@ -3,14 +3,16 @@ use crate::{ ops, primitives::{ - Annulus, Arc2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, CircularSector, - CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon, - Rhombus, Segment2d, Triangle2d, + Annulus, Arc2d, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Line2d, + Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon, Rhombus, Segment2d, Triangle2d, }, Dir2, Isometry2d, Mat2, Rot2, Vec2, }; use core::f32::consts::{FRAC_PI_2, PI, TAU}; +#[cfg(feature = "alloc")] +use crate::primitives::{BoxedPolygon, BoxedPolyline2d}; + use smallvec::SmallVec; use super::{Aabb2d, Bounded2d, BoundingCircle}; @@ -41,11 +43,11 @@ fn arc_bounding_points(arc: Arc2d, rotation: impl Into) -> SmallVec<[Vec2; // The half-angles are measured from a starting point of π/2, being the angle of Vec2::Y. // Compute the normalized angles of the endpoints with the rotation taken into account, and then // check if we are looking for an angle that is between or outside them. - let left_angle = (FRAC_PI_2 + arc.half_angle + rotation.as_radians()).rem_euclid(TAU); - let right_angle = (FRAC_PI_2 - arc.half_angle + rotation.as_radians()).rem_euclid(TAU); + let left_angle = ops::rem_euclid(FRAC_PI_2 + arc.half_angle + rotation.as_radians(), TAU); + let right_angle = ops::rem_euclid(FRAC_PI_2 - arc.half_angle + rotation.as_radians(), TAU); let inverted = left_angle < right_angle; for extremum in [Vec2::X, Vec2::Y, Vec2::NEG_X, Vec2::NEG_Y] { - let angle = extremum.to_angle().rem_euclid(TAU); + let angle = ops::rem_euclid(extremum.to_angle(), TAU); // If inverted = true, then right_angle > left_angle, so we are looking for an angle that is not between them. // There's a chance that this condition fails due to rounding error, if the endpoint angle is juuuust shy of the axis. // But in that case, the endpoint itself is within rounding error of the axis and will define the bounds just fine. @@ -286,6 +288,7 @@ impl Bounded2d for Polyline2d { } } +#[cfg(feature = "alloc")] impl Bounded2d for BoxedPolyline2d { fn aabb_2d(&self, isometry: impl Into) -> Aabb2d { Aabb2d::from_point_cloud(isometry, &self.vertices) @@ -348,7 +351,8 @@ impl Bounded2d for Rectangle { // Compute the AABB of the rotated rectangle by transforming the half-extents // by an absolute rotation matrix. let (sin, cos) = isometry.rotation.sin_cos(); - let abs_rot_mat = Mat2::from_cols_array(&[cos.abs(), sin.abs(), sin.abs(), cos.abs()]); + let abs_rot_mat = + Mat2::from_cols_array(&[ops::abs(cos), ops::abs(sin), ops::abs(sin), ops::abs(cos)]); let half_size = abs_rot_mat * self.half_size; Aabb2d::new(isometry.translation, half_size) @@ -371,6 +375,7 @@ impl Bounded2d for Polygon { } } +#[cfg(feature = "alloc")] impl Bounded2d for BoxedPolygon { fn aabb_2d(&self, isometry: impl Into) -> Aabb2d { Aabb2d::from_point_cloud(isometry, &self.vertices) @@ -470,6 +475,7 @@ mod tests { // Arcs and circular segments have the same bounding shapes so they share test cases. fn arc_and_segment() { struct TestCase { + #[allow(unused)] name: &'static str, arc: Arc2d, translation: Vec2, @@ -487,7 +493,7 @@ mod tests { } // The apothem of an arc covering 1/6th of a circle. - let apothem = f32::sqrt(3.0) / 2.0; + let apothem = ops::sqrt(3.0) / 2.0; let tests = [ // Test case: a basic minor arc TestCase { @@ -558,7 +564,7 @@ mod tests { aabb_min: Vec2::ZERO, aabb_max: Vec2::splat(1.0), bounding_circle_center: Vec2::splat(0.5), - bounding_circle_radius: f32::sqrt(2.0) / 2.0, + bounding_circle_radius: ops::sqrt(2.0) / 2.0, }, // Test case: a basic major arc TestCase { @@ -597,6 +603,7 @@ mod tests { ]; for test in tests { + #[cfg(feature = "std")] println!("subtest case: {}", test.name); let segment: CircularSegment = test.arc.into(); @@ -622,6 +629,7 @@ mod tests { #[test] fn circular_sector() { struct TestCase { + #[allow(unused)] name: &'static str, arc: Arc2d, translation: Vec2, @@ -639,8 +647,8 @@ mod tests { } // The apothem of an arc covering 1/6th of a circle. - let apothem = f32::sqrt(3.0) / 2.0; - let inv_sqrt_3 = f32::sqrt(3.0).recip(); + let apothem = ops::sqrt(3.0) / 2.0; + let inv_sqrt_3 = ops::sqrt(3.0).recip(); let tests = [ // Test case: An sector whose arc is minor, but whose bounding circle is not the circumcircle of the endpoints and center TestCase { @@ -717,7 +725,7 @@ mod tests { aabb_min: Vec2::ZERO, aabb_max: Vec2::splat(1.0), bounding_circle_center: Vec2::splat(0.5), - bounding_circle_radius: f32::sqrt(2.0) / 2.0, + bounding_circle_radius: ops::sqrt(2.0) / 2.0, }, TestCase { name: "5/6th circle untransformed", @@ -753,6 +761,7 @@ mod tests { ]; for test in tests { + #[cfg(feature = "std")] println!("subtest case: {}", test.name); let sector: CircularSector = test.arc.into(); diff --git a/crates/bevy_math/src/bounding/bounded3d/extrusion.rs b/crates/bevy_math/src/bounding/bounded3d/extrusion.rs index 8eb3eb9791795..f70101bdd0594 100644 --- a/crates/bevy_math/src/bounding/bounded3d/extrusion.rs +++ b/crates/bevy_math/src/bounding/bounded3d/extrusion.rs @@ -6,12 +6,15 @@ use crate::{ bounding::{BoundingCircle, BoundingVolume}, ops, primitives::{ - BoxedPolygon, BoxedPolyline2d, Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d, - Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d, + Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Primitive2d, + Rectangle, RegularPolygon, Segment2d, Triangle2d, }, Isometry2d, Isometry3d, Quat, Rot2, }; +#[cfg(feature = "alloc")] +use crate::primitives::{BoxedPolygon, BoxedPolyline2d}; + use crate::{bounding::Bounded2d, primitives::Circle}; use super::{Aabb3d, Bounded3d, BoundingSphere}; @@ -26,7 +29,7 @@ impl BoundedExtrusion for Circle { let top = (segment_dir * half_depth).abs(); let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO); - let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt()); + let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z)); Aabb3d { min: isometry.translation - half_size - top, @@ -56,8 +59,8 @@ impl BoundedExtrusion for Ellipse { let m = -axis.x / axis.y; let signum = axis.signum(); - let y = signum.y * b * b / (b * b + m * m * a * a).sqrt(); - let x = signum.x * a * (1. - y * y / b / b).sqrt(); + let y = signum.y * b * b / ops::sqrt(b * b + m * m * a * a); + let x = signum.x * a * ops::sqrt(1. - y * y / b / b); isometry.rotation * Vec3A::new(x, y, 0.) }); @@ -104,6 +107,7 @@ impl BoundedExtrusion for Polyline2d { } } +#[cfg(feature = "alloc")] impl BoundedExtrusion for BoxedPolyline2d { fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into) -> Aabb3d { let isometry = isometry.into(); @@ -144,6 +148,7 @@ impl BoundedExtrusion for Polygon { } } +#[cfg(feature = "alloc")] impl BoundedExtrusion for BoxedPolygon { fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into) -> Aabb3d { let isometry = isometry.into(); @@ -301,7 +306,7 @@ mod tests { let bounding_sphere = extrusion.bounding_sphere(isometry); assert_eq!(bounding_sphere.center, translation.into()); - assert_eq!(bounding_sphere.radius(), 8f32.sqrt()); + assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32)); } #[test] @@ -444,7 +449,7 @@ mod tests { let bounding_sphere = extrusion.bounding_sphere(isometry); assert_eq!(bounding_sphere.center, translation.into()); - assert_eq!(bounding_sphere.radius(), 8f32.sqrt()); + assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32)); } #[test] diff --git a/crates/bevy_math/src/bounding/bounded3d/mod.rs b/crates/bevy_math/src/bounding/bounded3d/mod.rs index 38460fc08e09f..c4f3c979f67cb 100644 --- a/crates/bevy_math/src/bounding/bounded3d/mod.rs +++ b/crates/bevy_math/src/bounding/bounded3d/mod.rs @@ -4,7 +4,10 @@ mod primitive_impls; use glam::Mat3; use super::{BoundingVolume, IntersectsVolume}; -use crate::{ops::FloatPow, Isometry3d, Quat, Vec3A}; +use crate::{ + ops::{self, FloatPow}, + Isometry3d, Quat, Vec3A, +}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -297,12 +300,12 @@ mod aabb3d_tests { min: Vec3A::new(-1., -1., -1.), max: Vec3A::new(1., 1., 1.), }; - assert!((aabb.visible_area() - 12.).abs() < f32::EPSILON); + assert!(ops::abs(aabb.visible_area() - 12.) < f32::EPSILON); let aabb = Aabb3d { min: Vec3A::new(0., 0., 0.), max: Vec3A::new(1., 0.5, 0.25), }; - assert!((aabb.visible_area() - 0.875).abs() < f32::EPSILON); + assert!(ops::abs(aabb.visible_area() - 0.875) < f32::EPSILON); } #[test] @@ -494,7 +497,7 @@ impl BoundingSphere { } } - BoundingSphere::new(isometry * center, radius_squared.sqrt()) + BoundingSphere::new(isometry * center, ops::sqrt(radius_squared)) } /// Get the radius of the bounding sphere @@ -528,7 +531,7 @@ impl BoundingSphere { } else { // The point is outside the sphere. // Find the closest point on the surface of the sphere. - let dir_to_point = point / distance_squared.sqrt(); + let dir_to_point = point / ops::sqrt(distance_squared); self.center + radius * dir_to_point } } @@ -557,7 +560,7 @@ impl BoundingVolume for BoundingSphere { #[inline(always)] fn contains(&self, other: &Self) -> bool { let diff = self.radius() - other.radius(); - self.center.distance_squared(other.center) <= diff.squared().copysign(diff) + self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff) } #[inline(always)] @@ -644,14 +647,14 @@ mod bounding_sphere_tests { use super::BoundingSphere; use crate::{ bounding::{BoundingVolume, IntersectsVolume}, - Quat, Vec3, Vec3A, + ops, Quat, Vec3, Vec3A, }; #[test] fn area() { let sphere = BoundingSphere::new(Vec3::ONE, 5.); // Since this number is messy we check it with a higher threshold - assert!((sphere.visible_area() - 157.0796).abs() < 0.001); + assert!(ops::abs(sphere.visible_area() - 157.0796) < 0.001); } #[test] @@ -677,7 +680,7 @@ mod bounding_sphere_tests { let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.); let merged = a.merge(&b); assert!((merged.center - Vec3A::new(1., 1., 0.5)).length() < f32::EPSILON); - assert!((merged.radius() - 5.5).abs() < f32::EPSILON); + assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON); assert!(merged.contains(&a)); assert!(merged.contains(&b)); assert!(!a.contains(&merged)); @@ -709,7 +712,7 @@ mod bounding_sphere_tests { fn grow() { let a = BoundingSphere::new(Vec3::ONE, 5.); let padded = a.grow(1.25); - assert!((padded.radius() - 6.25).abs() < f32::EPSILON); + assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON); assert!(padded.contains(&a)); assert!(!a.contains(&padded)); } @@ -718,7 +721,7 @@ mod bounding_sphere_tests { fn shrink() { let a = BoundingSphere::new(Vec3::ONE, 5.); let shrunk = a.shrink(0.5); - assert!((shrunk.radius() - 4.5).abs() < f32::EPSILON); + assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON); assert!(a.contains(&shrunk)); assert!(!shrunk.contains(&a)); } @@ -727,7 +730,7 @@ mod bounding_sphere_tests { fn scale_around_center() { let a = BoundingSphere::new(Vec3::ONE, 5.); let scaled = a.scale_around_center(2.); - assert!((scaled.radius() - 10.).abs() < f32::EPSILON); + assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON); assert!(!a.contains(&scaled)); assert!(scaled.contains(&a)); } diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 3eea8df3b5c61..679d8577f0d41 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -4,12 +4,15 @@ use crate::{ bounding::{Bounded2d, BoundingCircle}, ops, primitives::{ - BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, - Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, Triangle3d, + Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d, + Segment3d, Sphere, Torus, Triangle2d, Triangle3d, }, Isometry2d, Isometry3d, Mat3, Vec2, Vec3, Vec3A, }; +#[cfg(feature = "alloc")] +use crate::primitives::BoxedPolyline3d; + use super::{Aabb3d, Bounded3d, BoundingSphere}; impl Bounded3d for Sphere { @@ -98,6 +101,7 @@ impl Bounded3d for Polyline3d { } } +#[cfg(feature = "alloc")] impl Bounded3d for BoxedPolyline3d { fn aabb_3d(&self, isometry: impl Into) -> Aabb3d { Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied()) @@ -142,7 +146,7 @@ impl Bounded3d for Cylinder { let bottom = -top; let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO); - let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt()); + let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z)); Aabb3d { min: isometry.translation + (top - half_size).min(bottom - half_size), @@ -193,7 +197,7 @@ impl Bounded3d for Cone { let bottom = -top; let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO); - let half_extents = Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt()); + let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z)); Aabb3d { min: isometry.translation + top.min(bottom - self.radius * half_extents), @@ -234,7 +238,7 @@ impl Bounded3d for ConicalFrustum { let bottom = -top; let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO); - let half_extents = Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt()); + let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z)); Aabb3d { min: isometry.translation @@ -317,7 +321,8 @@ impl Bounded3d for Torus { // Reference: http://iquilezles.org/articles/diskbbox/ let normal = isometry.rotation * Vec3A::Y; let e = (Vec3A::ONE - normal * normal).max(Vec3A::ZERO); - let disc_half_size = self.major_radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt()); + let disc_half_size = + self.major_radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z)); // Expand the disc by the minor radius to get the torus half-size let half_size = disc_half_size + Vec3A::splat(self.minor_radius); diff --git a/crates/bevy_math/src/bounding/raycast2d.rs b/crates/bevy_math/src/bounding/raycast2d.rs index 745e53de8267c..3b46bcfba62c3 100644 --- a/crates/bevy_math/src/bounding/raycast2d.rs +++ b/crates/bevy_math/src/bounding/raycast2d.rs @@ -1,5 +1,8 @@ use super::{Aabb2d, BoundingCircle, IntersectsVolume}; -use crate::{ops::FloatPow, Dir2, Ray2d, Vec2}; +use crate::{ + ops::{self, FloatPow}, + Dir2, Ray2d, Vec2, +}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -77,10 +80,12 @@ impl RayCast2d { let projected = offset.dot(*self.ray.direction); let closest_point = offset - projected * *self.ray.direction; let distance_squared = circle.radius().squared() - closest_point.length_squared(); - if distance_squared < 0. || projected.squared().copysign(-projected) < -distance_squared { + if distance_squared < 0. + || ops::copysign(projected.squared(), -projected) < -distance_squared + { None } else { - let toi = -projected - distance_squared.sqrt(); + let toi = -projected - ops::sqrt(distance_squared); if toi > self.max { None } else { @@ -224,21 +229,21 @@ mod tests { 4.996, ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.circle_intersection_at(volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast2d::new(test.ray.origin, -test.ray.direction, test.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } @@ -263,9 +268,7 @@ mod tests { ] { assert!( !test.intersects(volume), - "Case:\n Test: {:?}\n Volume: {:?}", - test, - volume, + "Case:\n Test: {test:?}\n Volume: {volume:?}", ); } } @@ -278,14 +281,17 @@ mod tests { for max in &[0., 1., 900.] { let test = RayCast2d::new(*origin, *direction, *max); - let case = format!( - "Case:\n origin: {:?}\n Direction: {:?}\n Max: {}", - origin, direction, max, + assert!( + test.intersects(&volume), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", ); - assert!(test.intersects(&volume), "{}", case); let actual_distance = test.circle_intersection_at(&volume); - assert_eq!(actual_distance, Some(0.), "{}", case); + assert_eq!( + actual_distance, + Some(0.), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", + ); } } } @@ -331,21 +337,21 @@ mod tests { 1.414, ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.aabb_intersection_at(volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast2d::new(test.ray.origin, -test.ray.direction, test.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } @@ -370,9 +376,7 @@ mod tests { ] { assert!( !test.intersects(volume), - "Case:\n Test: {:?}\n Volume: {:?}", - test, - volume, + "Case:\n Test: {test:?}\n Volume: {volume:?}", ); } } @@ -385,14 +389,17 @@ mod tests { for max in &[0., 1., 900.] { let test = RayCast2d::new(*origin, *direction, *max); - let case = format!( - "Case:\n origin: {:?}\n Direction: {:?}\n Max: {}", - origin, direction, max, + assert!( + test.intersects(&volume), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", ); - assert!(test.intersects(&volume), "{}", case); let actual_distance = test.aabb_intersection_at(&volume); - assert_eq!(actual_distance, Some(0.), "{}", case,); + assert_eq!( + actual_distance, + Some(0.), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", + ); } } } @@ -441,22 +448,22 @@ mod tests { 3., ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.aabb_collision_at(*volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } @@ -508,22 +515,22 @@ mod tests { 3.677, ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.circle_collision_at(*volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } } diff --git a/crates/bevy_math/src/bounding/raycast3d.rs b/crates/bevy_math/src/bounding/raycast3d.rs index 3a369d9fe142a..bfd5d17a0dd8c 100644 --- a/crates/bevy_math/src/bounding/raycast3d.rs +++ b/crates/bevy_math/src/bounding/raycast3d.rs @@ -1,5 +1,8 @@ use super::{Aabb3d, BoundingSphere, IntersectsVolume}; -use crate::{ops::FloatPow, Dir3A, Ray3d, Vec3A}; +use crate::{ + ops::{self, FloatPow}, + Dir3A, Ray3d, Vec3A, +}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -74,10 +77,12 @@ impl RayCast3d { let projected = offset.dot(*self.direction); let closest_point = offset - projected * *self.direction; let distance_squared = sphere.radius().squared() - closest_point.length_squared(); - if distance_squared < 0. || projected.squared().copysign(-projected) < -distance_squared { + if distance_squared < 0. + || ops::copysign(projected.squared(), -projected) < -distance_squared + { None } else { - let toi = -projected - distance_squared.sqrt(); + let toi = -projected - ops::sqrt(distance_squared); if toi > self.max { None } else { @@ -236,21 +241,21 @@ mod tests { 4.996, ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.sphere_intersection_at(volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast3d::new(test.origin, -test.direction, test.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } @@ -275,9 +280,7 @@ mod tests { ] { assert!( !test.intersects(volume), - "Case:\n Test: {:?}\n Volume: {:?}", - test, - volume, + "Case:\n Test: {test:?}\n Volume: {volume:?}", ); } } @@ -290,14 +293,17 @@ mod tests { for max in &[0., 1., 900.] { let test = RayCast3d::new(*origin, *direction, *max); - let case = format!( - "Case:\n origin: {:?}\n Direction: {:?}\n Max: {}", - origin, direction, max, + assert!( + test.intersects(&volume), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", ); - assert!(test.intersects(&volume), "{}", case); let actual_distance = test.sphere_intersection_at(&volume); - assert_eq!(actual_distance, Some(0.), "{}", case,); + assert_eq!( + actual_distance, + Some(0.), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", + ); } } } @@ -343,21 +349,21 @@ mod tests { 1.732, ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.aabb_intersection_at(volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast3d::new(test.origin, -test.direction, test.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } @@ -382,9 +388,7 @@ mod tests { ] { assert!( !test.intersects(volume), - "Case:\n Test: {:?}\n Volume: {:?}", - test, - volume, + "Case:\n Test: {test:?}\n Volume: {volume:?}", ); } } @@ -397,14 +401,17 @@ mod tests { for max in &[0., 1., 900.] { let test = RayCast3d::new(*origin, *direction, *max); - let case = format!( - "Case:\n origin: {:?}\n Direction: {:?}\n Max: {}", - origin, direction, max, + assert!( + test.intersects(&volume), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", ); - assert!(test.intersects(&volume), "{}", case); let actual_distance = test.aabb_intersection_at(&volume); - assert_eq!(actual_distance, Some(0.), "{}", case,); + assert_eq!( + actual_distance, + Some(0.), + "Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}", + ); } } } @@ -453,21 +460,21 @@ mod tests { 3., ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.aabb_collision_at(*volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast3d::new(test.ray.origin, -test.ray.direction, test.ray.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } @@ -519,21 +526,21 @@ mod tests { 3.677, ), ] { - let case = format!( - "Case:\n Test: {:?}\n Volume: {:?}\n Expected distance: {:?}", - test, volume, expected_distance + assert!( + test.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", ); - assert!(test.intersects(volume), "{}", case); let actual_distance = test.sphere_collision_at(*volume).unwrap(); assert!( - (actual_distance - expected_distance).abs() < EPSILON, - "{}\n Actual distance: {}", - case, - actual_distance + ops::abs(actual_distance - expected_distance) < EPSILON, + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}", ); let inverted_ray = RayCast3d::new(test.ray.origin, -test.ray.direction, test.ray.max); - assert!(!inverted_ray.intersects(volume), "{}", case); + assert!( + !inverted_ray.intersects(volume), + "Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}", + ); } } } diff --git a/crates/bevy_math/src/common_traits.rs b/crates/bevy_math/src/common_traits.rs index c6bc43ae9ce9a..455f3dd62854f 100644 --- a/crates/bevy_math/src/common_traits.rs +++ b/crates/bevy_math/src/common_traits.rs @@ -157,7 +157,7 @@ impl NormedVectorSpace for Vec2 { impl NormedVectorSpace for f32 { #[inline] fn norm(self) -> f32 { - self.abs() + ops::abs(self) } #[inline] diff --git a/crates/bevy_math/src/compass.rs b/crates/bevy_math/src/compass.rs index 244c37962eb4d..5ee224df4b118 100644 --- a/crates/bevy_math/src/compass.rs +++ b/crates/bevy_math/src/compass.rs @@ -140,7 +140,7 @@ mod test_compass_quadrant { #[test] fn test_cardinal_directions() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(1.0, 0.0)).unwrap(), CompassQuadrant::East, @@ -166,7 +166,7 @@ mod test_compass_quadrant { #[test] fn test_north_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(), CompassQuadrant::North, @@ -184,7 +184,7 @@ mod test_compass_quadrant { #[test] fn test_east_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(0.9, 0.1)).unwrap(), CompassQuadrant::East, @@ -202,7 +202,7 @@ mod test_compass_quadrant { #[test] fn test_south_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(), CompassQuadrant::South, @@ -220,7 +220,7 @@ mod test_compass_quadrant { #[test] fn test_west_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(), CompassQuadrant::West, @@ -243,7 +243,7 @@ mod test_compass_octant { #[test] fn test_cardinal_directions() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.5, 0.5)).unwrap(), CompassOctant::NorthWest, @@ -282,7 +282,7 @@ mod test_compass_octant { #[test] fn test_north_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(), CompassOctant::North, @@ -300,7 +300,7 @@ mod test_compass_octant { #[test] fn test_north_east_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(0.4, 0.6)).unwrap(), CompassOctant::NorthEast, @@ -318,7 +318,7 @@ mod test_compass_octant { #[test] fn test_east_pie_slice() { - let tests = vec![ + let tests = [ (Dir2::new(Vec2::new(0.9, 0.1)).unwrap(), CompassOctant::East), ( Dir2::new(Vec2::new(0.9, -0.1)).unwrap(), @@ -333,7 +333,7 @@ mod test_compass_octant { #[test] fn test_south_east_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(0.4, -0.6)).unwrap(), CompassOctant::SouthEast, @@ -351,7 +351,7 @@ mod test_compass_octant { #[test] fn test_south_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(), CompassOctant::South, @@ -369,7 +369,7 @@ mod test_compass_octant { #[test] fn test_south_west_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.4, -0.6)).unwrap(), CompassOctant::SouthWest, @@ -387,7 +387,7 @@ mod test_compass_octant { #[test] fn test_west_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(), CompassOctant::West, @@ -405,7 +405,7 @@ mod test_compass_octant { #[test] fn test_north_west_pie_slice() { - let tests = vec![ + let tests = [ ( Dir2::new(Vec2::new(-0.4, 0.6)).unwrap(), CompassOctant::NorthWest, diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 87d95b0fc2d85..8e6e8801085f7 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1,11 +1,16 @@ //! Provides types for building cubic splines for rendering curves and use with animation easing. -use core::{fmt::Debug, iter::once}; +use core::fmt::Debug; -use crate::{ops::FloatPow, Vec2, VectorSpace}; +use crate::{ + ops::{self, FloatPow}, + Vec2, VectorSpace, +}; use derive_more::derive::{Display, Error}; -use itertools::Itertools; + +#[cfg(feature = "alloc")] +use {alloc::vec, alloc::vec::Vec, core::iter::once, itertools::Itertools}; #[cfg(feature = "curve")] use crate::curve::{Curve, Interval}; @@ -47,6 +52,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// let bezier = CubicBezier::new(points).to_curve().unwrap(); /// let positions: Vec<_> = bezier.iter_positions(100).collect(); /// ``` +#[cfg(feature = "alloc")] #[derive(Clone, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] pub struct CubicBezier { @@ -54,6 +60,7 @@ pub struct CubicBezier { pub control_points: Vec<[P; 4]>, } +#[cfg(feature = "alloc")] impl CubicBezier

{ /// Create a new cubic Bezier curve from sets of control points. pub fn new(control_points: impl Into>) -> Self { @@ -62,6 +69,8 @@ impl CubicBezier

{ } } } + +#[cfg(feature = "alloc")] impl CubicGenerator

for CubicBezier

{ type Error = CubicBezierError; @@ -140,12 +149,15 @@ pub struct CubicBezierError; /// ``` /// /// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic +#[cfg(feature = "alloc")] #[derive(Clone, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] pub struct CubicHermite { /// The control points of the Hermite curve. pub control_points: Vec<(P, P)>, } + +#[cfg(feature = "alloc")] impl CubicHermite

{ /// Create a new Hermite curve from sets of control points. pub fn new( @@ -172,6 +184,8 @@ impl CubicHermite

{ ] } } + +#[cfg(feature = "alloc")] impl CubicGenerator

for CubicHermite

{ type Error = InsufficientDataError; @@ -196,6 +210,8 @@ impl CubicGenerator

for CubicHermite

{ } } } + +#[cfg(feature = "alloc")] impl CyclicCubicGenerator

for CubicHermite

{ type Error = InsufficientDataError; @@ -258,6 +274,7 @@ impl CyclicCubicGenerator

for CubicHermite

{ /// ``` /// /// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic +#[cfg(feature = "alloc")] #[derive(Clone, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] pub struct CubicCardinalSpline { @@ -267,6 +284,7 @@ pub struct CubicCardinalSpline { pub control_points: Vec

, } +#[cfg(feature = "alloc")] impl CubicCardinalSpline

{ /// Build a new Cardinal spline. pub fn new(tension: f32, control_points: impl Into>) -> Self { @@ -299,6 +317,8 @@ impl CubicCardinalSpline

{ ] } } + +#[cfg(feature = "alloc")] impl CubicGenerator

for CubicCardinalSpline

{ type Error = InsufficientDataError; @@ -335,6 +355,8 @@ impl CubicGenerator

for CubicCardinalSpline

{ Ok(CubicCurve { segments }) } } + +#[cfg(feature = "alloc")] impl CyclicCubicGenerator

for CubicCardinalSpline

{ type Error = InsufficientDataError; @@ -411,12 +433,15 @@ impl CyclicCubicGenerator

for CubicCardinalSpline

{ /// ``` /// /// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic +#[cfg(feature = "alloc")] #[derive(Clone, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] pub struct CubicBSpline { /// The control points of the spline pub control_points: Vec

, } + +#[cfg(feature = "alloc")] impl CubicBSpline

{ /// Build a new B-Spline. pub fn new(control_points: impl Into>) -> Self { @@ -448,6 +473,8 @@ impl CubicBSpline

{ char_matrix } } + +#[cfg(feature = "alloc")] impl CubicGenerator

for CubicBSpline

{ type Error = InsufficientDataError; @@ -470,6 +497,7 @@ impl CubicGenerator

for CubicBSpline

{ } } +#[cfg(feature = "alloc")] impl CyclicCubicGenerator

for CubicBSpline

{ type Error = InsufficientDataError; @@ -578,6 +606,7 @@ pub enum CubicNurbsError { /// .unwrap(); /// let positions: Vec<_> = nurbs.iter_positions(100).collect(); /// ``` +#[cfg(feature = "alloc")] #[derive(Clone, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] pub struct CubicNurbs { @@ -588,6 +617,8 @@ pub struct CubicNurbs { /// Knots pub knots: Vec, } + +#[cfg(feature = "alloc")] impl CubicNurbs

{ /// Build a Non-Uniform Rational B-Spline. /// @@ -748,6 +779,8 @@ impl CubicNurbs

{ ] } } + +#[cfg(feature = "alloc")] impl RationalGenerator

for CubicNurbs

{ type Error = InsufficientDataError; @@ -802,12 +835,15 @@ impl RationalGenerator

for CubicNurbs

{ /// formed with [`to_curve_cyclic`], the final segment connects the last control point with the first. /// /// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic +#[cfg(feature = "alloc")] #[derive(Clone, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] pub struct LinearSpline { /// The control points of the linear spline. pub points: Vec

, } + +#[cfg(feature = "alloc")] impl LinearSpline

{ /// Create a new linear spline from a list of points to be interpolated. pub fn new(points: impl Into>) -> Self { @@ -816,6 +852,8 @@ impl LinearSpline

{ } } } + +#[cfg(feature = "alloc")] impl CubicGenerator

for LinearSpline

{ type Error = InsufficientDataError; @@ -843,6 +881,8 @@ impl CubicGenerator

for LinearSpline

{ } } } + +#[cfg(feature = "alloc")] impl CyclicCubicGenerator

for LinearSpline

{ type Error = InsufficientDataError; @@ -877,6 +917,7 @@ pub struct InsufficientDataError { } /// Implement this on cubic splines that can generate a cubic curve from their spline parameters. +#[cfg(feature = "alloc")] pub trait CubicGenerator { /// An error type indicating why construction might fail. type Error; @@ -888,6 +929,7 @@ pub trait CubicGenerator { /// Implement this on cubic splines that can generate a cyclic cubic curve from their spline parameters. /// /// This makes sense only when the control data can be interpreted cyclically. +#[cfg(feature = "alloc")] pub trait CyclicCubicGenerator { /// An error type indicating why construction might fail. type Error; @@ -937,6 +979,7 @@ impl CubicSegment

{ } /// Calculate polynomial coefficients for the cubic curve using a characteristic matrix. + #[allow(unused)] #[inline] fn coefficients(p: [P; 4], char_matrix: [[f32; 4]; 4]) -> Self { let [c0, c1, c2, c3] = char_matrix; @@ -964,6 +1007,7 @@ impl CubicSegment { /// /// This is a very common tool for UI animations that accelerate and decelerate smoothly. For /// example, the ubiquitous "ease-in-out" is defined as `(0.25, 0.1), (0.25, 1.0)`. + #[cfg(feature = "alloc")] pub fn new_bezier(p1: impl Into, p2: impl Into) -> Self { let (p0, p3) = (Vec2::ZERO, Vec2::ONE); let bezier = CubicBezier::new([[p0, p1.into(), p2.into(), p3]]) @@ -984,9 +1028,12 @@ impl CubicSegment { /// /// ``` /// # use bevy_math::prelude::*; + /// # #[cfg(feature = "alloc")] + /// # { /// let cubic_bezier = CubicSegment::new_bezier((0.25, 0.1), (0.25, 1.0)); /// assert_eq!(cubic_bezier.ease(0.0), 0.0); /// assert_eq!(cubic_bezier.ease(1.0), 1.0); + /// # } /// ``` /// /// # How cubic easing works @@ -1050,7 +1097,7 @@ impl CubicSegment { for _ in 0..Self::MAX_ITERS { pos_guess = self.position(t_guess); let error = pos_guess.x - x; - if error.abs() <= Self::MAX_ERROR { + if ops::abs(error) <= Self::MAX_ERROR { break; } // Using Newton's method, use the tangent line to estimate a better guess value. @@ -1079,6 +1126,7 @@ impl Curve

for CubicSegment

{ /// /// Use any struct that implements the [`CubicGenerator`] trait to create a new curve, such as /// [`CubicBezier`]. +#[cfg(feature = "alloc")] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] @@ -1087,6 +1135,7 @@ pub struct CubicCurve { segments: Vec>, } +#[cfg(feature = "alloc")] impl CubicCurve

{ /// Create a new curve from a collection of segments. If the collection of segments is empty, /// a curve cannot be built and `None` will be returned instead. @@ -1193,13 +1242,13 @@ impl CubicCurve

{ if self.segments.len() == 1 { (&self.segments[0], t) } else { - let i = (t.floor() as usize).clamp(0, self.segments.len() - 1); + let i = (ops::floor(t) as usize).clamp(0, self.segments.len() - 1); (&self.segments[i], t - i as f32) } } } -#[cfg(feature = "curve")] +#[cfg(all(feature = "curve", feature = "alloc"))] impl Curve

for CubicCurve

{ #[inline] fn domain(&self) -> Interval { @@ -1214,12 +1263,14 @@ impl Curve

for CubicCurve

{ } } +#[cfg(feature = "alloc")] impl Extend> for CubicCurve

{ fn extend>>(&mut self, iter: T) { self.segments.extend(iter); } } +#[cfg(feature = "alloc")] impl IntoIterator for CubicCurve

{ type IntoIter = > as IntoIterator>::IntoIter; @@ -1231,6 +1282,7 @@ impl IntoIterator for CubicCurve

{ } /// Implement this on cubic splines that can generate a rational cubic curve from their spline parameters. +#[cfg(feature = "alloc")] pub trait RationalGenerator { /// An error type indicating why construction might fail. type Error; @@ -1334,6 +1386,7 @@ impl RationalSegment

{ } /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix. + #[allow(unused)] #[inline] fn coefficients( control_points: [P; 4], @@ -1389,6 +1442,7 @@ impl Curve

for RationalSegment

{ /// /// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as /// [`CubicNurbs`], or convert [`CubicCurve`] using `into/from`. +#[cfg(feature = "alloc")] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] @@ -1397,6 +1451,7 @@ pub struct RationalCurve { segments: Vec>, } +#[cfg(feature = "alloc")] impl RationalCurve

{ /// Create a new curve from a collection of segments. If the collection of segments is empty, /// a curve cannot be built and `None` will be returned instead. @@ -1528,7 +1583,7 @@ impl RationalCurve

{ } } -#[cfg(feature = "curve")] +#[cfg(all(feature = "curve", feature = "alloc"))] impl Curve

for RationalCurve

{ #[inline] fn domain(&self) -> Interval { @@ -1543,12 +1598,14 @@ impl Curve

for RationalCurve

{ } } +#[cfg(feature = "alloc")] impl Extend> for RationalCurve

{ fn extend>>(&mut self, iter: T) { self.segments.extend(iter); } } +#[cfg(feature = "alloc")] impl IntoIterator for RationalCurve

{ type IntoIter = > as IntoIterator>::IntoIter; @@ -1569,6 +1626,7 @@ impl From> for RationalSegment

{ } } +#[cfg(feature = "alloc")] impl From> for RationalCurve

{ fn from(value: CubicCurve

) -> Self { Self { @@ -1577,10 +1635,9 @@ impl From> for RationalCurve

{ } } +#[cfg(feature = "alloc")] #[cfg(test)] mod tests { - use glam::{vec2, Vec2}; - use crate::{ cubic_splines::{ CubicBSpline, CubicBezier, CubicGenerator, CubicNurbs, CubicSegment, RationalCurve, @@ -1588,6 +1645,8 @@ mod tests { }, ops::{self, FloatPow}, }; + use alloc::vec::Vec; + use glam::{vec2, Vec2}; /// How close two floats can be and still be considered equal const FLOAT_EQ: f32 = 1e-5; @@ -1760,7 +1819,7 @@ mod tests { let curve = spline.to_curve().unwrap(); for (i, point) in curve.iter_positions(10).enumerate() { assert!( - f32::abs(point.length() - 1.0) < EPSILON, + ops::abs(point.length() - 1.0) < EPSILON, "Point {i} is not on the unit circle: {point:?} has length {}", point.length() ); diff --git a/crates/bevy_math/src/curve/adaptors.rs b/crates/bevy_math/src/curve/adaptors.rs index 1d186706f264d..1dfdecdbbce3e 100644 --- a/crates/bevy_math/src/curve/adaptors.rs +++ b/crates/bevy_math/src/curve/adaptors.rs @@ -3,6 +3,7 @@ use super::interval::*; use super::Curve; +use crate::ops; use crate::VectorSpace; use core::any::type_name; use core::fmt::{self, Debug}; @@ -621,7 +622,7 @@ where fn sample_unchecked(&self, t: f32) -> T { // the domain is bounded by construction let d = self.curve.domain(); - let cyclic_t = (t - d.start()).rem_euclid(d.length()); + let cyclic_t = ops::rem_euclid(t - d.start(), d.length()); let t = if t != d.start() && cyclic_t == 0.0 { d.end() } else { @@ -669,7 +670,7 @@ where fn sample_unchecked(&self, t: f32) -> T { // the domain is bounded by construction let d = self.curve.domain(); - let cyclic_t = (t - d.start()).rem_euclid(d.length()); + let cyclic_t = ops::rem_euclid(t - d.start(), d.length()); let t = if t != d.start() && cyclic_t == 0.0 { d.end() } else { diff --git a/crates/bevy_math/src/curve/cores.rs b/crates/bevy_math/src/curve/cores.rs index 6c63eabb29cfa..237ee47f58771 100644 --- a/crates/bevy_math/src/curve/cores.rs +++ b/crates/bevy_math/src/curve/cores.rs @@ -6,10 +6,14 @@ //! provided methods all maintain the invariants, so this is only a concern if you manually mutate //! the fields. +use crate::ops; + use super::interval::Interval; use core::fmt::Debug; use derive_more::derive::{Display, Error}; -use itertools::Itertools; + +#[cfg(feature = "alloc")] +use {alloc::vec::Vec, itertools::Itertools}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -112,6 +116,7 @@ impl InterpolationDatum { /// } /// } /// ``` +#[cfg(feature = "alloc")] #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] @@ -146,6 +151,7 @@ pub enum EvenCoreError { UnboundedDomain, } +#[cfg(feature = "alloc")] impl EvenCore { /// Create a new [`EvenCore`] from the specified `domain` and `samples`. The samples are /// regarded to be evenly spaced within the given domain interval, so that the outermost @@ -243,11 +249,11 @@ pub fn even_interp(domain: Interval, samples: usize, t: f32) -> InterpolationDat // To the right side of all the samples InterpolationDatum::RightTail(samples - 1) } else { - let lower_index = steps_taken.floor() as usize; + let lower_index = ops::floor(steps_taken) as usize; // This upper index is always valid because `steps_taken` is a finite value // strictly less than `samples - 1`, so its floor is at most `samples - 2` let upper_index = lower_index + 1; - let s = steps_taken.fract(); + let s = ops::fract(steps_taken); InterpolationDatum::Between(lower_index, upper_index, s) } } @@ -314,6 +320,7 @@ pub fn even_interp(domain: Interval, samples: usize, t: f32) -> InterpolationDat /// [`domain`]: UnevenCore::domain /// [`sample_with`]: UnevenCore::sample_with /// [the provided constructor]: UnevenCore::new +#[cfg(feature = "alloc")] #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] @@ -346,6 +353,7 @@ pub enum UnevenCoreError { }, } +#[cfg(feature = "alloc")] impl UnevenCore { /// Create a new [`UnevenCore`]. The given samples are filtered to finite times and /// sorted internally; if there are not at least 2 valid timed samples, an error will be @@ -453,6 +461,7 @@ impl UnevenCore { /// if the sample type can effectively be encoded as a fixed-length slice of values. /// /// [sampling width]: ChunkedUnevenCore::width +#[cfg(feature = "alloc")] #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] @@ -507,6 +516,7 @@ pub enum ChunkedUnevenCoreError { }, } +#[cfg(feature = "alloc")] impl ChunkedUnevenCore { /// Create a new [`ChunkedUnevenCore`]. The given `times` are sorted, filtered to finite times, /// and deduplicated. See the [type-level documentation] for more information about this type. @@ -644,6 +654,7 @@ impl ChunkedUnevenCore { } /// Sort the given times, deduplicate them, and filter them to only finite times. +#[cfg(feature = "alloc")] fn filter_sort_dedup_times(times: impl IntoIterator) -> Vec { // Filter before sorting/deduplication so that NAN doesn't interfere with them. let mut times = times.into_iter().filter(|t| t.is_finite()).collect_vec(); @@ -682,7 +693,7 @@ pub fn uneven_interp(times: &[f32], t: f32) -> InterpolationDatum { } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { use super::{ChunkedUnevenCore, EvenCore, UnevenCore}; use crate::curve::{cores::InterpolationDatum, interval}; diff --git a/crates/bevy_math/src/curve/easing.rs b/crates/bevy_math/src/curve/easing.rs index a31946239dd74..0e5406fa732c7 100644 --- a/crates/bevy_math/src/curve/easing.rs +++ b/crates/bevy_math/src/curve/easing.rs @@ -309,18 +309,18 @@ mod easing_functions { #[inline] pub(crate) fn circular_in(t: f32) -> f32 { - 1.0 - (1.0 - t.squared()).sqrt() + 1.0 - ops::sqrt(1.0 - t.squared()) } #[inline] pub(crate) fn circular_out(t: f32) -> f32 { - (1.0 - (t - 1.0).squared()).sqrt() + ops::sqrt(1.0 - (t - 1.0).squared()) } #[inline] pub(crate) fn circular_in_out(t: f32) -> f32 { if t < 0.5 { - (1.0 - (1.0 - (2.0 * t).squared()).sqrt()) / 2.0 + (1.0 - ops::sqrt(1.0 - (2.0 * t).squared())) / 2.0 } else { - ((1.0 - (-2.0 * t + 2.0).squared()).sqrt() + 1.0) / 2.0 + (ops::sqrt(1.0 - (-2.0 * t + 2.0).squared()) + 1.0) / 2.0 } } @@ -411,7 +411,7 @@ mod easing_functions { #[inline] pub(crate) fn steps(num_steps: usize, t: f32) -> f32 { - (t * num_steps as f32).round() / num_steps.max(1) as f32 + ops::round(t * num_steps as f32) / num_steps.max(1) as f32 } #[inline] diff --git a/crates/bevy_math/src/curve/interval.rs b/crates/bevy_math/src/curve/interval.rs index 291d0499c0af1..f15b21e1cb1d7 100644 --- a/crates/bevy_math/src/curve/interval.rs +++ b/crates/bevy_math/src/curve/interval.rs @@ -198,6 +198,8 @@ pub fn interval(start: f32, end: f32) -> Result #[cfg(test)] mod tests { + use crate::ops; + use super::*; use approx::{assert_abs_diff_eq, AbsDiffEq}; @@ -237,10 +239,10 @@ mod tests { #[test] fn lengths() { let ivl = interval(-5.0, 10.0).unwrap(); - assert!((ivl.length() - 15.0).abs() <= f32::EPSILON); + assert!(ops::abs(ivl.length() - 15.0) <= f32::EPSILON); let ivl = interval(5.0, 100.0).unwrap(); - assert!((ivl.length() - 95.0).abs() <= f32::EPSILON); + assert!(ops::abs(ivl.length() - 95.0) <= f32::EPSILON); let ivl = interval(0.0, f32::INFINITY).unwrap(); assert_eq!(ivl.length(), f32::INFINITY); diff --git a/crates/bevy_math/src/curve/iterable.rs b/crates/bevy_math/src/curve/iterable.rs index b8adcd30d5c45..ee29372dd41b8 100644 --- a/crates/bevy_math/src/curve/iterable.rs +++ b/crates/bevy_math/src/curve/iterable.rs @@ -1,7 +1,10 @@ //! Iterable curves, which sample in the form of an iterator in order to support `Vec`-like //! output whose length cannot be known statically. -use super::{ConstantCurve, Interval}; +use super::Interval; + +#[cfg(feature = "alloc")] +use {super::ConstantCurve, alloc::vec::Vec}; /// A curve which provides samples in the form of [`Iterator`]s. /// @@ -39,6 +42,7 @@ pub trait IterableCurve { } } +#[cfg(feature = "alloc")] impl IterableCurve for ConstantCurve> where T: Clone, diff --git a/crates/bevy_math/src/curve/mod.rs b/crates/bevy_math/src/curve/mod.rs index 34afaeca94ee4..a1d24301aaf24 100644 --- a/crates/bevy_math/src/curve/mod.rs +++ b/crates/bevy_math/src/curve/mod.rs @@ -290,21 +290,27 @@ pub mod cores; pub mod easing; pub mod interval; pub mod iterable; + +#[cfg(feature = "alloc")] pub mod sample_curves; // bevy_math::curve re-exports all commonly-needed curve-related items. pub use adaptors::*; pub use easing::*; pub use interval::{interval, Interval}; -pub use sample_curves::*; -use cores::{EvenCore, UnevenCore}; +#[cfg(feature = "alloc")] +pub use { + crate::StableInterpolate, + cores::{EvenCore, UnevenCore}, + itertools::Itertools, + sample_curves::*, +}; -use crate::{StableInterpolate, VectorSpace}; +use crate::VectorSpace; use core::{marker::PhantomData, ops::Deref}; use derive_more::derive::{Display, Error}; use interval::InvalidIntervalError; -use itertools::Itertools; /// A trait for a type that can represent values of type `T` parametrized over a fixed interval. /// @@ -716,6 +722,7 @@ pub trait Curve { /// // A curve which only stores three data points and uses `nlerp` to interpolate them: /// let resampled_rotation = quarter_rotation.resample(3, |x, y, t| x.nlerp(*y, t)); /// ``` + #[cfg(feature = "alloc")] fn resample( &self, segments: usize, @@ -743,6 +750,7 @@ pub trait Curve { /// domain, then a [`ResamplingError`] is returned. /// /// [automatic interpolation]: crate::common_traits::StableInterpolate + #[cfg(feature = "alloc")] fn resample_auto(&self, segments: usize) -> Result, ResamplingError> where Self: Sized, @@ -794,6 +802,7 @@ pub trait Curve { /// The interpolation takes two values by reference together with a scalar parameter and /// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and /// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively. + #[cfg(feature = "alloc")] fn resample_uneven( &self, sample_times: impl IntoIterator, @@ -832,6 +841,7 @@ pub trait Curve { /// sample times of the iterator. /// /// [automatic interpolation]: crate::common_traits::StableInterpolate + #[cfg(feature = "alloc")] fn resample_uneven_auto( &self, sample_times: impl IntoIterator, diff --git a/crates/bevy_math/src/curve/sample_curves.rs b/crates/bevy_math/src/curve/sample_curves.rs index b1abcb0399803..7a37f55640090 100644 --- a/crates/bevy_math/src/curve/sample_curves.rs +++ b/crates/bevy_math/src/curve/sample_curves.rs @@ -356,6 +356,7 @@ impl UnevenSampleAutoCurve { } #[cfg(test)] +#[cfg(feature = "bevy_reflect")] mod tests { //! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve` //! can be `Reflect` under reasonable circumstances where their interpolation is defined by: diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index acbc5816ad3f2..d427a24261645 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -55,17 +55,23 @@ impl core::fmt::Display for InvalidDirectionError { /// and similarly for the error. #[cfg(debug_assertions)] fn assert_is_normalized(message: &str, length_squared: f32) { - let length_error_squared = (length_squared - 1.0).abs(); + use crate::ops; + + let length_error_squared = ops::abs(length_squared - 1.0); // Panic for large error and warn for slight error. if length_error_squared > 2e-2 || length_error_squared.is_nan() { // Length error is approximately 1e-2 or more. - panic!("Error: {message} The length is {}.", length_squared.sqrt()); + panic!( + "Error: {message} The length is {}.", + ops::sqrt(length_squared) + ); } else if length_error_squared > 2e-4 { // Length error is approximately 1e-4 or more. + #[cfg(feature = "std")] eprintln!( "Warning: {message} The length is {}.", - length_squared.sqrt() + ops::sqrt(length_squared) ); } } @@ -886,17 +892,17 @@ mod tests { fn dir2_slerp() { assert_relative_eq!( Dir2::X.slerp(Dir2::Y, 0.5), - Dir2::from_xy(0.5_f32.sqrt(), 0.5_f32.sqrt()).unwrap() + Dir2::from_xy(ops::sqrt(0.5_f32), ops::sqrt(0.5_f32)).unwrap() ); assert_eq!(Dir2::Y.slerp(Dir2::X, 0.0), Dir2::Y); assert_relative_eq!(Dir2::X.slerp(Dir2::Y, 1.0), Dir2::Y); assert_relative_eq!( Dir2::Y.slerp(Dir2::X, 1.0 / 3.0), - Dir2::from_xy(0.5, 0.75_f32.sqrt()).unwrap() + Dir2::from_xy(0.5, ops::sqrt(0.75_f32)).unwrap() ); assert_relative_eq!( Dir2::X.slerp(Dir2::Y, 2.0 / 3.0), - Dir2::from_xy(0.5, 0.75_f32.sqrt()).unwrap() + Dir2::from_xy(0.5, ops::sqrt(0.75_f32)).unwrap() ); } @@ -967,18 +973,18 @@ mod tests { fn dir3_slerp() { assert_relative_eq!( Dir3::X.slerp(Dir3::Y, 0.5), - Dir3::from_xyz(0.5f32.sqrt(), 0.5f32.sqrt(), 0.0).unwrap() + Dir3::from_xyz(ops::sqrt(0.5f32), ops::sqrt(0.5f32), 0.0).unwrap() ); assert_relative_eq!(Dir3::Y.slerp(Dir3::Z, 0.0), Dir3::Y); assert_relative_eq!(Dir3::Z.slerp(Dir3::X, 1.0), Dir3::X, epsilon = 0.000001); assert_relative_eq!( Dir3::X.slerp(Dir3::Z, 1.0 / 3.0), - Dir3::from_xyz(0.75f32.sqrt(), 0.0, 0.5).unwrap(), + Dir3::from_xyz(ops::sqrt(0.75f32), 0.0, 0.5).unwrap(), epsilon = 0.000001 ); assert_relative_eq!( Dir3::Z.slerp(Dir3::Y, 2.0 / 3.0), - Dir3::from_xyz(0.0, 0.75f32.sqrt(), 0.5).unwrap() + Dir3::from_xyz(0.0, ops::sqrt(0.75f32), 0.5).unwrap() ); } @@ -1038,18 +1044,18 @@ mod tests { fn dir3a_slerp() { assert_relative_eq!( Dir3A::X.slerp(Dir3A::Y, 0.5), - Dir3A::from_xyz(0.5f32.sqrt(), 0.5f32.sqrt(), 0.0).unwrap() + Dir3A::from_xyz(ops::sqrt(0.5f32), ops::sqrt(0.5f32), 0.0).unwrap() ); assert_relative_eq!(Dir3A::Y.slerp(Dir3A::Z, 0.0), Dir3A::Y); assert_relative_eq!(Dir3A::Z.slerp(Dir3A::X, 1.0), Dir3A::X, epsilon = 0.000001); assert_relative_eq!( Dir3A::X.slerp(Dir3A::Z, 1.0 / 3.0), - Dir3A::from_xyz(0.75f32.sqrt(), 0.0, 0.5).unwrap(), + Dir3A::from_xyz(ops::sqrt(0.75f32), 0.0, 0.5).unwrap(), epsilon = 0.000001 ); assert_relative_eq!( Dir3A::Z.slerp(Dir3A::Y, 2.0 / 3.0), - Dir3A::from_xyz(0.0, 0.75f32.sqrt(), 0.5).unwrap() + Dir3A::from_xyz(0.0, ops::sqrt(0.75f32), 0.5).unwrap() ); } diff --git a/crates/bevy_math/src/float_ord.rs b/crates/bevy_math/src/float_ord.rs index e53a083f773e6..b5f72d1c7cf5e 100644 --- a/crates/bevy_math/src/float_ord.rs +++ b/crates/bevy_math/src/float_ord.rs @@ -95,8 +95,6 @@ impl Neg for FloatOrd { #[cfg(test)] mod tests { - use std::hash::DefaultHasher; - use super::*; const NAN: FloatOrd = FloatOrd(f32::NAN); @@ -157,10 +155,11 @@ mod tests { assert!(ONE >= ZERO); } + #[cfg(feature = "std")] #[test] fn float_ord_hash() { let hash = |num| { - let mut h = DefaultHasher::new(); + let mut h = std::hash::DefaultHasher::new(); FloatOrd(num).hash(&mut h); h.finish() }; diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index c1ef37e20a9d3..1d99edd051566 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -6,6 +6,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] +#![cfg_attr(not(feature = "std"), no_std)] //! Provides math types and functionality for the Bevy game engine. //! @@ -13,6 +14,9 @@ //! matrices like [`Mat2`], [`Mat3`] and [`Mat4`] and orientation representations //! like [`Quat`]. +#[cfg(feature = "alloc")] +extern crate alloc; + mod affine3; mod aspect_ratio; pub mod bounding; @@ -59,11 +63,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ bvec2, bvec3, bvec3a, bvec4, bvec4a, - cubic_splines::{ - CubicBSpline, CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator, - CubicHermite, CubicNurbs, CubicNurbsError, CubicSegment, CyclicCubicGenerator, - RationalCurve, RationalGenerator, RationalSegment, - }, + cubic_splines::{CubicNurbsError, CubicSegment, RationalSegment}, direction::{Dir2, Dir3, Dir3A}, ivec2, ivec3, ivec4, mat2, mat3, mat3a, mat4, ops, primitives::*, @@ -80,6 +80,13 @@ pub mod prelude { #[doc(hidden)] #[cfg(feature = "rand")] pub use crate::sampling::{FromRng, ShapeSample}; + + #[cfg(feature = "alloc")] + #[doc(hidden)] + pub use crate::cubic_splines::{ + CubicBSpline, CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator, CubicHermite, + CubicNurbs, CyclicCubicGenerator, RationalCurve, RationalGenerator, + }; } pub use glam::*; diff --git a/crates/bevy_math/src/ops.rs b/crates/bevy_math/src/ops.rs index 2e9c3a0ca4a85..143f98ec0f5b8 100644 --- a/crates/bevy_math/src/ops.rs +++ b/crates/bevy_math/src/ops.rs @@ -4,6 +4,9 @@ //! //! All the functions here are named according to their versions in the standard //! library. +//! +//! It also provides `no_std` compatible alternatives to certain floating-point +//! operations which are not provided in the [`core`] library. #![allow(dead_code)] #![allow(clippy::disallowed_methods)] @@ -444,12 +447,159 @@ mod libm_ops { } } +#[cfg(all(feature = "libm", not(feature = "std")))] +mod libm_ops_for_no_std { + //! Provides standardized names for [`f32`] operations which may not be + //! supported on `no_std` platforms. + //! On `no_std` platforms, this forwards to the implementations provided + //! by [`libm`]. + + /// Calculates the least nonnegative remainder of `self (mod rhs)`. + /// + /// Precision is specified when the `libm` feature is enabled. + #[inline(always)] + pub fn rem_euclid(x: f32, y: f32) -> f32 { + let result = libm::remainderf(x, y); + + // libm::remainderf has a range of -y/2 to +y/2 + if result < 0. { + result + y + } else { + result + } + } + + /// Computes the absolute value of x. + /// + /// Precision is specified when the `libm` feature is enabled. + #[inline(always)] + pub fn abs(x: f32) -> f32 { + libm::fabsf(x) + } + + /// Returns the square root of a number. + /// + /// Precision is specified when the `libm` feature is enabled. + #[inline(always)] + pub fn sqrt(x: f32) -> f32 { + libm::sqrtf(x) + } + + /// Returns a number composed of the magnitude of `x` and the sign of `y`. + /// + /// Precision is specified when the `libm` feature is enabled. + #[inline(always)] + pub fn copysign(x: f32, y: f32) -> f32 { + libm::copysignf(x, y) + } + + /// Returns the nearest integer to `x`. If a value is half-way between two integers, round away from `0.0`. + /// + /// Precision is specified when the `libm` feature is enabled. + #[inline(always)] + pub fn round(x: f32) -> f32 { + libm::roundf(x) + } + + /// Returns the largest integer less than or equal to `x`. + /// + /// Precision is specified when the `libm` feature is enabled. + #[inline(always)] + pub fn floor(x: f32) -> f32 { + libm::floorf(x) + } + + /// Returns the fractional part of `x`. + /// + /// This function always returns the precise result. + #[inline(always)] + pub fn fract(x: f32) -> f32 { + libm::modff(x).0 + } +} + +#[cfg(feature = "std")] +mod std_ops_for_no_std { + //! Provides standardized names for [`f32`] operations which may not be + //! supported on `no_std` platforms. + //! On `std` platforms, this forwards directly to the implementations provided + //! by [`std`]. + + /// Calculates the least nonnegative remainder of `x (mod y)`. + /// + /// The result of this operation is guaranteed to be the rounded infinite-precision result. + #[inline(always)] + pub fn rem_euclid(x: f32, y: f32) -> f32 { + f32::rem_euclid(x, y) + } + + /// Computes the absolute value of x. + /// + /// This function always returns the precise result. + #[inline(always)] + pub fn abs(x: f32) -> f32 { + f32::abs(x) + } + + /// Returns the square root of a number. + /// + /// The result of this operation is guaranteed to be the rounded infinite-precision result. + /// It is specified by IEEE 754 as `squareRoot` and guaranteed not to change. + #[inline(always)] + pub fn sqrt(x: f32) -> f32 { + f32::sqrt(x) + } + + /// Returns a number composed of the magnitude of `x` and the sign of `y`. + /// + /// Equal to `x` if the sign of `x` and `y` are the same, otherwise equal to `-x`. If `x` is a + /// `NaN`, then a `NaN` with the sign bit of `y` is returned. Note, however, that conserving the + /// sign bit on `NaN` across arithmetical operations is not generally guaranteed. + #[inline(always)] + pub fn copysign(x: f32, y: f32) -> f32 { + f32::copysign(x, y) + } + + /// Returns the nearest integer to `x`. If a value is half-way between two integers, round away from `0.0`. + /// + /// This function always returns the precise result. + #[inline(always)] + pub fn round(x: f32) -> f32 { + f32::round(x) + } + + /// Returns the largest integer less than or equal to `x`. + /// + /// This function always returns the precise result. + #[inline(always)] + pub fn floor(x: f32) -> f32 { + f32::floor(x) + } + + /// Returns the fractional part of `x`. + /// + /// This function always returns the precise result. + #[inline(always)] + pub fn fract(x: f32) -> f32 { + f32::fract(x) + } +} + #[cfg(feature = "libm")] pub use libm_ops::*; #[cfg(not(feature = "libm"))] pub use std_ops::*; +#[cfg(feature = "std")] +pub use std_ops_for_no_std::*; + +#[cfg(all(feature = "libm", not(feature = "std")))] +pub use libm_ops_for_no_std::*; + +#[cfg(all(not(feature = "libm"), not(feature = "std")))] +compile_error!("Either the `libm` feature or the `std` feature must be enabled."); + /// This extension trait covers shortfall in determinacy from the lack of a `libm` counterpart /// to `f32::powi`. Use this for the common small exponents. pub trait FloatPow { diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index 62d1633622dde..446a94fcba81e 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -12,6 +12,9 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, vec::Vec}; + /// A circle primitive, representing the set of points some distance from the origin #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -64,7 +67,7 @@ impl Circle { } else { // The point is outside the circle. // Find the closest point on the perimeter of the circle. - let dir_to_point = point / distance_squared.sqrt(); + let dir_to_point = point / ops::sqrt(distance_squared); self.radius * dir_to_point } } @@ -230,7 +233,7 @@ impl Arc2d { // used by Wolfram MathWorld, which is the distance rather than the segment. pub fn apothem(&self) -> f32 { let sign = if self.is_minor() { 1.0 } else { -1.0 }; - sign * f32::sqrt(self.radius.squared() - self.half_chord_length().squared()) + sign * ops::sqrt(self.radius.squared() - self.half_chord_length().squared()) } /// Get the length of the sagitta of this arc, that is, @@ -280,7 +283,7 @@ impl Arc2d { )] pub struct CircularSector { /// The arc defining the sector - #[cfg_attr(feature = "serialize", serde(flatten))] + #[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))] pub arc: Arc2d, } impl Primitive2d for CircularSector {} @@ -423,7 +426,7 @@ impl CircularSector { )] pub struct CircularSegment { /// The arc defining the segment - #[cfg_attr(feature = "serialize", serde(flatten))] + #[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))] pub arc: Arc2d, } impl Primitive2d for CircularSegment {} @@ -676,7 +679,7 @@ mod arc_tests { #[test] fn quarter_circle() { - let sqrt_half: f32 = f32::sqrt(0.5); + let sqrt_half: f32 = ops::sqrt(0.5); let tests = ArcTestCase { radius: 1.0, half_angle: FRAC_PI_4, @@ -687,7 +690,7 @@ mod arc_tests { endpoints: [Vec2::new(-sqrt_half, sqrt_half), Vec2::splat(sqrt_half)], midpoint: Vec2::Y, half_chord_length: sqrt_half, - chord_length: f32::sqrt(2.0), + chord_length: ops::sqrt(2.0), chord_midpoint: Vec2::new(0.0, sqrt_half), apothem: sqrt_half, sagitta: 1.0 - sqrt_half, @@ -822,7 +825,7 @@ impl Ellipse { let a = self.semi_major(); let b = self.semi_minor(); - (a * a - b * b).sqrt() / a + ops::sqrt(a * a - b * b) / a } #[inline(always)] @@ -833,7 +836,7 @@ impl Ellipse { let a = self.semi_major(); let b = self.semi_minor(); - (a * a - b * b).sqrt() + ops::sqrt(a * a - b * b) } /// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse. @@ -982,13 +985,13 @@ impl Annulus { } else { // The point is outside the annulus and closer to the outer perimeter. // Find the closest point on the perimeter of the annulus. - let dir_to_point = point / distance_squared.sqrt(); + let dir_to_point = point / ops::sqrt(distance_squared); self.outer_circle.radius * dir_to_point } } else { // The point is outside the annulus and closer to the inner perimeter. // Find the closest point on the perimeter of the annulus. - let dir_to_point = point / distance_squared.sqrt(); + let dir_to_point = point / ops::sqrt(distance_squared); self.inner_circle.radius * dir_to_point } } @@ -1301,14 +1304,18 @@ impl Polyline2d { /// in a `Box<[Vec2]>`. /// /// For a version without alloc: [`Polyline2d`] +#[cfg(feature = "alloc")] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct BoxedPolyline2d { /// The vertices of the polyline pub vertices: Box<[Vec2]>, } + +#[cfg(feature = "alloc")] impl Primitive2d for BoxedPolyline2d {} +#[cfg(feature = "alloc")] impl FromIterator for BoxedPolyline2d { fn from_iter>(iter: I) -> Self { let vertices: Vec = iter.into_iter().collect(); @@ -1318,6 +1325,7 @@ impl FromIterator for BoxedPolyline2d { } } +#[cfg(feature = "alloc")] impl BoxedPolyline2d { /// Create a new `BoxedPolyline2d` from its vertices pub fn new(vertices: impl IntoIterator) -> Self { @@ -1480,7 +1488,7 @@ impl Measured2d for Triangle2d { #[inline(always)] fn area(&self) -> f32 { let [a, b, c] = self.vertices; - (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)).abs() / 2.0 + ops::abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.0 } /// Get the perimeter of the triangle @@ -1709,14 +1717,18 @@ impl TryFrom> for ConvexPolygon { /// in a `Box<[Vec2]>`. /// /// For a version without alloc: [`Polygon`] +#[cfg(feature = "alloc")] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct BoxedPolygon { /// The vertices of the `BoxedPolygon` pub vertices: Box<[Vec2]>, } + +#[cfg(feature = "alloc")] impl Primitive2d for BoxedPolygon {} +#[cfg(feature = "alloc")] impl FromIterator for BoxedPolygon { fn from_iter>(iter: I) -> Self { let vertices: Vec = iter.into_iter().collect(); @@ -1726,6 +1738,7 @@ impl FromIterator for BoxedPolygon { } } +#[cfg(feature = "alloc")] impl BoxedPolygon { /// Create a new `BoxedPolygon` from its vertices pub fn new(vertices: impl IntoIterator) -> Self { diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index cd1b5013e6b72..8d25df5b83d0a 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1,7 +1,10 @@ use core::f32::consts::{FRAC_PI_3, PI}; use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d}; -use crate::{ops, ops::FloatPow, Dir3, InvalidDirectionError, Isometry3d, Mat3, Vec2, Vec3}; +use crate::{ + ops::{self, FloatPow}, + Dir3, InvalidDirectionError, Isometry3d, Mat3, Vec2, Vec3, +}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -9,6 +12,9 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use glam::Quat; +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, vec::Vec}; + /// A sphere primitive, representing the set of all points some distance from the origin #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -61,7 +67,7 @@ impl Sphere { } else { // The point is outside the sphere. // Find the closest point on the surface of the sphere. - let dir_to_point = point / distance_squared.sqrt(); + let dir_to_point = point / ops::sqrt(distance_squared); self.radius * dir_to_point } } @@ -440,14 +446,18 @@ impl Polyline3d { /// in a `Box<[Vec3]>`. /// /// For a version without alloc: [`Polyline3d`] +#[cfg(feature = "alloc")] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct BoxedPolyline3d { /// The vertices of the polyline pub vertices: Box<[Vec3]>, } + +#[cfg(feature = "alloc")] impl Primitive3d for BoxedPolyline3d {} +#[cfg(feature = "alloc")] impl FromIterator for BoxedPolyline3d { fn from_iter>(iter: I) -> Self { let vertices: Vec = iter.into_iter().collect(); @@ -457,6 +467,7 @@ impl FromIterator for BoxedPolyline3d { } } +#[cfg(feature = "alloc")] impl BoxedPolyline3d { /// Create a new `BoxedPolyline3d` from its vertices pub fn new(vertices: impl IntoIterator) -> Self { @@ -1246,7 +1257,7 @@ impl Measured3d for Tetrahedron { /// Get the volume of the tetrahedron. #[inline(always)] fn volume(&self) -> f32 { - self.signed_volume().abs() + ops::abs(self.signed_volume()) } } @@ -1573,7 +1584,7 @@ mod tests { assert_eq!(default_triangle.area(), 0.5, "incorrect area"); assert_relative_eq!( default_triangle.perimeter(), - 1.0 + 2.0 * 1.25_f32.sqrt(), + 1.0 + 2.0 * ops::sqrt(1.25_f32), epsilon = 10e-9 ); assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal"); diff --git a/crates/bevy_math/src/primitives/serde.rs b/crates/bevy_math/src/primitives/serde.rs index bdbf72f69e737..7db6be9700114 100644 --- a/crates/bevy_math/src/primitives/serde.rs +++ b/crates/bevy_math/src/primitives/serde.rs @@ -31,7 +31,7 @@ pub(crate) mod array { type Value = [T; N]; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - formatter.write_str(&format!("an array of length {}", N)) + formatter.write_fmt(format_args!("an array of length {}", N)) } #[inline] @@ -39,17 +39,21 @@ pub(crate) mod array { where A: SeqAccess<'de>, { - let mut data = Vec::with_capacity(N); - for _ in 0..N { + let mut data = [const { Option::::None }; N]; + + for element in data.iter_mut() { match (seq.next_element())? { - Some(val) => data.push(val), + Some(val) => *element = Some(val), None => return Err(serde::de::Error::invalid_length(N, &self)), } } - match data.try_into() { - Ok(arr) => Ok(arr), - Err(_) => unreachable!(), - } + + let data = data.map(|value| match value { + Some(value) => value, + None => unreachable!(), + }); + + Ok(data) } } diff --git a/crates/bevy_math/src/ray.rs b/crates/bevy_math/src/ray.rs index e083d7b4a1b3d..273ed61fa4b97 100644 --- a/crates/bevy_math/src/ray.rs +++ b/crates/bevy_math/src/ray.rs @@ -1,4 +1,5 @@ use crate::{ + ops, primitives::{InfinitePlane3d, Plane2d}, Dir2, Dir3, Vec2, Vec3, }; @@ -40,7 +41,7 @@ impl Ray2d { #[inline] pub fn intersect_plane(&self, plane_origin: Vec2, plane: Plane2d) -> Option { let denominator = plane.normal.dot(*self.direction); - if denominator.abs() > f32::EPSILON { + if ops::abs(denominator) > f32::EPSILON { let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator; if distance > f32::EPSILON { return Some(distance); @@ -82,7 +83,7 @@ impl Ray3d { #[inline] pub fn intersect_plane(&self, plane_origin: Vec3, plane: InfinitePlane3d) -> Option { let denominator = plane.normal.dot(*self.direction); - if denominator.abs() > f32::EPSILON { + if ops::abs(denominator) > f32::EPSILON { let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator; if distance > f32::EPSILON { return Some(distance); diff --git a/crates/bevy_math/src/rects/rect.rs b/crates/bevy_math/src/rects/rect.rs index 8e804dcd085f0..901a569a71f4e 100644 --- a/crates/bevy_math/src/rects/rect.rs +++ b/crates/bevy_math/src/rects/rect.rs @@ -371,6 +371,8 @@ impl Rect { #[cfg(test)] mod tests { + use crate::ops; + use super::*; #[test] @@ -382,8 +384,8 @@ mod tests { assert!(r.center().abs_diff_eq(Vec2::new(3., -5.), 1e-5)); - assert!((r.width() - 8.).abs() <= 1e-5); - assert!((r.height() - 11.).abs() <= 1e-5); + assert!(ops::abs(r.width() - 8.) <= 1e-5); + assert!(ops::abs(r.height() - 11.) <= 1e-5); assert!(r.size().abs_diff_eq(Vec2::new(8., 11.), 1e-5)); assert!(r.half_size().abs_diff_eq(Vec2::new(4., 5.5), 1e-5)); diff --git a/crates/bevy_math/src/rotation2d.rs b/crates/bevy_math/src/rotation2d.rs index 2b73d0ce330c3..5b0bc816bc5ca 100644 --- a/crates/bevy_math/src/rotation2d.rs +++ b/crates/bevy_math/src/rotation2d.rs @@ -318,7 +318,7 @@ impl Rot2 { // The allowed length is 1 +/- 1e-4, so the largest allowed // squared length is (1 + 1e-4)^2 = 1.00020001, which makes // the threshold for the squared length approximately 2e-4. - (self.length_squared() - 1.0).abs() <= 2e-4 + ops::abs(self.length_squared() - 1.0) <= 2e-4 } /// Returns `true` if the rotation is near [`Rot2::IDENTITY`]. @@ -326,7 +326,7 @@ impl Rot2 { pub fn is_near_identity(self) -> bool { // Same as `Quat::is_near_identity`, but using sine and cosine let threshold_angle_sin = 0.000_049_692_047; // let threshold_angle = 0.002_847_144_6; - self.cos > 0.0 && self.sin.abs() < threshold_angle_sin + self.cos > 0.0 && ops::abs(self.sin) < threshold_angle_sin } /// Returns the angle in radians needed to make `self` and `other` coincide. @@ -520,7 +520,7 @@ mod tests { use approx::assert_relative_eq; - use crate::{Dir2, Rot2, Vec2}; + use crate::{ops, Dir2, Rot2, Vec2}; #[test] fn creation() { @@ -589,7 +589,7 @@ mod tests { assert_eq!(rotation.length_squared(), 125.0); assert_eq!(rotation.length(), 11.18034); - assert!((rotation.normalize().length() - 1.0).abs() < 10e-7); + assert!(ops::abs(rotation.normalize().length() - 1.0) < 10e-7); } #[test] @@ -702,7 +702,7 @@ mod tests { assert!(rot1.nlerp(rot2, 0.0).is_near_identity()); // At 0.5, there is no valid rotation, so the fallback is the original angle. assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 0.0); - assert_eq!(rot1.nlerp(rot2, 1.0).as_degrees().abs(), 180.0); + assert_eq!(ops::abs(rot1.nlerp(rot2, 1.0).as_degrees()), 180.0); } #[test] @@ -718,9 +718,9 @@ mod tests { let rot1 = Rot2::IDENTITY; let rot2 = Rot2::from_sin_cos(0.0, -1.0); - assert!((rot1.slerp(rot2, 1.0 / 3.0).as_degrees() - 60.0).abs() < 10e-6); + assert!(ops::abs(rot1.slerp(rot2, 1.0 / 3.0).as_degrees() - 60.0) < 10e-6); assert!(rot1.slerp(rot2, 0.0).is_near_identity()); assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 90.0); - assert_eq!(rot1.slerp(rot2, 1.0).as_degrees().abs(), 180.0); + assert_eq!(ops::abs(rot1.slerp(rot2, 1.0).as_degrees()), 180.0); } } diff --git a/crates/bevy_math/src/sampling/mesh_sampling.rs b/crates/bevy_math/src/sampling/mesh_sampling.rs index e5163c0148367..898198dea4261 100644 --- a/crates/bevy_math/src/sampling/mesh_sampling.rs +++ b/crates/bevy_math/src/sampling/mesh_sampling.rs @@ -4,6 +4,7 @@ use crate::{ primitives::{Measured2d, Triangle3d}, ShapeSample, Vec3, }; +use alloc::vec::Vec; use rand::Rng; use rand_distr::{Distribution, WeightedAliasIndex, WeightedError}; diff --git a/crates/bevy_math/src/sampling/mod.rs b/crates/bevy_math/src/sampling/mod.rs index c4e482acbb39d..78db92588b1f6 100644 --- a/crates/bevy_math/src/sampling/mod.rs +++ b/crates/bevy_math/src/sampling/mod.rs @@ -2,10 +2,12 @@ //! //! To use this, the "rand" feature must be enabled. +#[cfg(feature = "alloc")] pub mod mesh_sampling; pub mod shape_sampling; pub mod standard; +#[cfg(feature = "alloc")] pub use mesh_sampling::*; pub use shape_sampling::*; pub use standard::*; diff --git a/crates/bevy_math/src/sampling/shape_sampling.rs b/crates/bevy_math/src/sampling/shape_sampling.rs index fdec36aa0b101..8489a88f19e2d 100644 --- a/crates/bevy_math/src/sampling/shape_sampling.rs +++ b/crates/bevy_math/src/sampling/shape_sampling.rs @@ -154,7 +154,7 @@ impl ShapeSample for Circle { // https://mathworld.wolfram.com/DiskPointPicking.html let theta = rng.gen_range(0.0..TAU); let r_squared = rng.gen_range(0.0..=(self.radius * self.radius)); - let r = r_squared.sqrt(); + let r = ops::sqrt(r_squared); let (sin, cos) = ops::sin_cos(theta); Vec2::new(r * cos, r * sin) } @@ -171,7 +171,7 @@ impl ShapeSample for Circle { fn sample_unit_sphere_boundary(rng: &mut R) -> Vec3 { let z = rng.gen_range(-1f32..=1f32); let (a_sin, a_cos) = ops::sin_cos(rng.gen_range(-PI..=PI)); - let c = (1f32 - z * z).sqrt(); + let c = ops::sqrt(1f32 - z * z); let x = a_sin * c; let y = a_cos * c; @@ -202,7 +202,7 @@ impl ShapeSample for Annulus { // Like random sampling for a circle, radius is weighted by the square. let r_squared = rng.gen_range((inner_radius * inner_radius)..(outer_radius * outer_radius)); - let r = r_squared.sqrt(); + let r = ops::sqrt(r_squared); let theta = rng.gen_range(0.0..TAU); let (sin, cos) = ops::sin_cos(theta); @@ -627,7 +627,7 @@ mod tests { let point = circle.sample_boundary(&mut rng); let angle = ops::atan(point.y / point.x) + PI / 2.0; - let wedge = (angle * 8.0 / PI).floor() as usize; + let wedge = ops::floor(angle * 8.0 / PI) as usize; wedge_hits[wedge] += 1; } diff --git a/tools/ci/src/commands/compile_check_no_std.rs b/tools/ci/src/commands/compile_check_no_std.rs index ddb7c5b4e8bd5..82ba3d4688eb4 100644 --- a/tools/ci/src/commands/compile_check_no_std.rs +++ b/tools/ci/src/commands/compile_check_no_std.rs @@ -62,6 +62,14 @@ impl Prepare for CompileCheckNoStdCommand { "Please fix compiler errors in output above for bevy_mikktspace no_std compatibility.", )); + commands.push(PreparedCommand::new::( + cmd!( + sh, + "cargo check -p bevy_math --no-default-features --features libm --target {target}" + ), + "Please fix compiler errors in output above for bevy_math no_std compatibility.", + )); + commands } }