mirror of
https://github.com/bevyengine/bevy
synced 2024-12-19 01:23:09 +00:00
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 <benjamin.brienen@outlook.com> Co-authored-by: Matty <2975848+mweatherley@users.noreply.github.com> Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
This commit is contained in:
parent
aa600ae95e
commit
a8b9c945c7
33 changed files with 627 additions and 268 deletions
|
@ -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
|
||||
|
|
|
@ -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" },
|
||||
]
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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<Rot2>) -> 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<const N: usize> Bounded2d for Polyline2d<N> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Bounded2d for BoxedPolyline2d {
|
||||
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> 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<const N: usize> Bounded2d for Polygon<N> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Bounded2d for BoxedPolygon {
|
||||
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> 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();
|
||||
|
||||
|
|
|
@ -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<const N: usize> BoundedExtrusion for Polyline2d<N> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl BoundedExtrusion for BoxedPolyline2d {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
|
||||
let isometry = isometry.into();
|
||||
|
@ -144,6 +148,7 @@ impl<const N: usize> BoundedExtrusion for Polygon<N> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl BoundedExtrusion for BoxedPolygon {
|
||||
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> 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]
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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<const N: usize> Bounded3d for Polyline3d<N> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Bounded3d for BoxedPolyline3d {
|
||||
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> 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);
|
||||
|
|
|
@ -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:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ impl NormedVectorSpace for Vec2 {
|
|||
impl NormedVectorSpace for f32 {
|
||||
#[inline]
|
||||
fn norm(self) -> f32 {
|
||||
self.abs()
|
||||
ops::abs(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<P: VectorSpace> {
|
||||
|
@ -54,6 +60,7 @@ pub struct CubicBezier<P: VectorSpace> {
|
|||
pub control_points: Vec<[P; 4]>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicBezier<P> {
|
||||
/// Create a new cubic Bezier curve from sets of control points.
|
||||
pub fn new(control_points: impl Into<Vec<[P; 4]>>) -> Self {
|
||||
|
@ -62,6 +69,8 @@ impl<P: VectorSpace> CubicBezier<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicBezier<P> {
|
||||
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<P: VectorSpace> {
|
||||
/// The control points of the Hermite curve.
|
||||
pub control_points: Vec<(P, P)>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicHermite<P> {
|
||||
/// Create a new Hermite curve from sets of control points.
|
||||
pub fn new(
|
||||
|
@ -172,6 +184,8 @@ impl<P: VectorSpace> CubicHermite<P> {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicHermite<P> {
|
||||
type Error = InsufficientDataError;
|
||||
|
||||
|
@ -196,6 +210,8 @@ impl<P: VectorSpace> CubicGenerator<P> for CubicHermite<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicHermite<P> {
|
||||
type Error = InsufficientDataError;
|
||||
|
||||
|
@ -258,6 +274,7 @@ impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicHermite<P> {
|
|||
/// ```
|
||||
///
|
||||
/// [`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<P: VectorSpace> {
|
||||
|
@ -267,6 +284,7 @@ pub struct CubicCardinalSpline<P: VectorSpace> {
|
|||
pub control_points: Vec<P>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicCardinalSpline<P> {
|
||||
/// Build a new Cardinal spline.
|
||||
pub fn new(tension: f32, control_points: impl Into<Vec<P>>) -> Self {
|
||||
|
@ -299,6 +317,8 @@ impl<P: VectorSpace> CubicCardinalSpline<P> {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicCardinalSpline<P> {
|
||||
type Error = InsufficientDataError;
|
||||
|
||||
|
@ -335,6 +355,8 @@ impl<P: VectorSpace> CubicGenerator<P> for CubicCardinalSpline<P> {
|
|||
Ok(CubicCurve { segments })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicCardinalSpline<P> {
|
||||
type Error = InsufficientDataError;
|
||||
|
||||
|
@ -411,12 +433,15 @@ impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicCardinalSpline<P> {
|
|||
/// ```
|
||||
///
|
||||
/// [`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<P: VectorSpace> {
|
||||
/// The control points of the spline
|
||||
pub control_points: Vec<P>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicBSpline<P> {
|
||||
/// Build a new B-Spline.
|
||||
pub fn new(control_points: impl Into<Vec<P>>) -> Self {
|
||||
|
@ -448,6 +473,8 @@ impl<P: VectorSpace> CubicBSpline<P> {
|
|||
char_matrix
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicBSpline<P> {
|
||||
type Error = InsufficientDataError;
|
||||
|
||||
|
@ -470,6 +497,7 @@ impl<P: VectorSpace> CubicGenerator<P> for CubicBSpline<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicBSpline<P> {
|
||||
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<P: VectorSpace> {
|
||||
|
@ -588,6 +617,8 @@ pub struct CubicNurbs<P: VectorSpace> {
|
|||
/// Knots
|
||||
pub knots: Vec<f32>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicNurbs<P> {
|
||||
/// Build a Non-Uniform Rational B-Spline.
|
||||
///
|
||||
|
@ -748,6 +779,8 @@ impl<P: VectorSpace> CubicNurbs<P> {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> RationalGenerator<P> for CubicNurbs<P> {
|
||||
type Error = InsufficientDataError;
|
||||
|
||||
|
@ -802,12 +835,15 @@ impl<P: VectorSpace> RationalGenerator<P> for CubicNurbs<P> {
|
|||
/// 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<P: VectorSpace> {
|
||||
/// The control points of the linear spline.
|
||||
pub points: Vec<P>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> LinearSpline<P> {
|
||||
/// Create a new linear spline from a list of points to be interpolated.
|
||||
pub fn new(points: impl Into<Vec<P>>) -> Self {
|
||||
|
@ -816,6 +852,8 @@ impl<P: VectorSpace> LinearSpline<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicGenerator<P> for LinearSpline<P> {
|
||||
type Error = InsufficientDataError;
|
||||
|
||||
|
@ -843,6 +881,8 @@ impl<P: VectorSpace> CubicGenerator<P> for LinearSpline<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CyclicCubicGenerator<P> for LinearSpline<P> {
|
||||
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<P: VectorSpace> {
|
||||
/// An error type indicating why construction might fail.
|
||||
type Error;
|
||||
|
@ -888,6 +929,7 @@ pub trait CubicGenerator<P: VectorSpace> {
|
|||
/// 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<P: VectorSpace> {
|
||||
/// An error type indicating why construction might fail.
|
||||
type Error;
|
||||
|
@ -937,6 +979,7 @@ impl<P: VectorSpace> CubicSegment<P> {
|
|||
}
|
||||
|
||||
/// 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<Vec2> {
|
|||
///
|
||||
/// 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<Vec2>, p2: impl Into<Vec2>) -> Self {
|
||||
let (p0, p3) = (Vec2::ZERO, Vec2::ONE);
|
||||
let bezier = CubicBezier::new([[p0, p1.into(), p2.into(), p3]])
|
||||
|
@ -984,9 +1028,12 @@ impl CubicSegment<Vec2> {
|
|||
///
|
||||
/// ```
|
||||
/// # 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<Vec2> {
|
|||
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<P: VectorSpace> Curve<P> for CubicSegment<P> {
|
|||
///
|
||||
/// 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<P: VectorSpace> {
|
|||
segments: Vec<CubicSegment<P>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicCurve<P> {
|
||||
/// 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<P: VectorSpace> CubicCurve<P> {
|
|||
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<P: VectorSpace> Curve<P> for CubicCurve<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
|
@ -1214,12 +1263,14 @@ impl<P: VectorSpace> Curve<P> for CubicCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> Extend<CubicSegment<P>> for CubicCurve<P> {
|
||||
fn extend<T: IntoIterator<Item = CubicSegment<P>>>(&mut self, iter: T) {
|
||||
self.segments.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> IntoIterator for CubicCurve<P> {
|
||||
type IntoIter = <Vec<CubicSegment<P>> as IntoIterator>::IntoIter;
|
||||
|
||||
|
@ -1231,6 +1282,7 @@ impl<P: VectorSpace> IntoIterator for CubicCurve<P> {
|
|||
}
|
||||
|
||||
/// Implement this on cubic splines that can generate a rational cubic curve from their spline parameters.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub trait RationalGenerator<P: VectorSpace> {
|
||||
/// An error type indicating why construction might fail.
|
||||
type Error;
|
||||
|
@ -1334,6 +1386,7 @@ impl<P: VectorSpace> RationalSegment<P> {
|
|||
}
|
||||
|
||||
/// 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<P: VectorSpace> Curve<P> for RationalSegment<P> {
|
|||
///
|
||||
/// 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<P: VectorSpace> {
|
|||
segments: Vec<RationalSegment<P>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> RationalCurve<P> {
|
||||
/// 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<P: VectorSpace> RationalCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "curve")]
|
||||
#[cfg(all(feature = "curve", feature = "alloc"))]
|
||||
impl<P: VectorSpace> Curve<P> for RationalCurve<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
|
@ -1543,12 +1598,14 @@ impl<P: VectorSpace> Curve<P> for RationalCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> Extend<RationalSegment<P>> for RationalCurve<P> {
|
||||
fn extend<T: IntoIterator<Item = RationalSegment<P>>>(&mut self, iter: T) {
|
||||
self.segments.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> IntoIterator for RationalCurve<P> {
|
||||
type IntoIter = <Vec<RationalSegment<P>> as IntoIterator>::IntoIter;
|
||||
|
||||
|
@ -1569,6 +1626,7 @@ impl<P: VectorSpace> From<CubicSegment<P>> for RationalSegment<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> From<CubicCurve<P>> for RationalCurve<P> {
|
||||
fn from(value: CubicCurve<P>) -> Self {
|
||||
Self {
|
||||
|
@ -1577,10 +1635,9 @@ impl<P: VectorSpace> From<CubicCurve<P>> for RationalCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<T> InterpolationDatum<T> {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[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<T> EvenCore<T> {
|
||||
/// 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<T> UnevenCore<T> {
|
||||
/// 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<T> UnevenCore<T> {
|
|||
/// 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<T> ChunkedUnevenCore<T> {
|
||||
/// 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<T> ChunkedUnevenCore<T> {
|
|||
}
|
||||
|
||||
/// Sort the given times, deduplicate them, and filter them to only finite times.
|
||||
#[cfg(feature = "alloc")]
|
||||
fn filter_sort_dedup_times(times: impl IntoIterator<Item = f32>) -> Vec<f32> {
|
||||
// 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<usize> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "alloc"))]
|
||||
mod tests {
|
||||
use super::{ChunkedUnevenCore, EvenCore, UnevenCore};
|
||||
use crate::curve::{cores::InterpolationDatum, interval};
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -198,6 +198,8 @@ pub fn interval(start: f32, end: f32) -> Result<Interval, InvalidIntervalError>
|
|||
|
||||
#[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);
|
||||
|
|
|
@ -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<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T> IterableCurve<T> for ConstantCurve<Vec<T>>
|
||||
where
|
||||
T: Clone,
|
||||
|
|
|
@ -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<T> {
|
|||
/// // 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<I>(
|
||||
&self,
|
||||
segments: usize,
|
||||
|
@ -743,6 +750,7 @@ pub trait Curve<T> {
|
|||
/// domain, then a [`ResamplingError`] is returned.
|
||||
///
|
||||
/// [automatic interpolation]: crate::common_traits::StableInterpolate
|
||||
#[cfg(feature = "alloc")]
|
||||
fn resample_auto(&self, segments: usize) -> Result<SampleAutoCurve<T>, ResamplingError>
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -794,6 +802,7 @@ pub trait Curve<T> {
|
|||
/// 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<I>(
|
||||
&self,
|
||||
sample_times: impl IntoIterator<Item = f32>,
|
||||
|
@ -832,6 +841,7 @@ pub trait Curve<T> {
|
|||
/// sample times of the iterator.
|
||||
///
|
||||
/// [automatic interpolation]: crate::common_traits::StableInterpolate
|
||||
#[cfg(feature = "alloc")]
|
||||
fn resample_uneven_auto(
|
||||
&self,
|
||||
sample_times: impl IntoIterator<Item = f32>,
|
||||
|
|
|
@ -356,6 +356,7 @@ impl<T> UnevenSampleAutoCurve<T> {
|
|||
}
|
||||
|
||||
#[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:
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<const N: usize> Polyline2d<N> {
|
|||
/// 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<Vec2> for BoxedPolyline2d {
|
||||
fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
|
||||
let vertices: Vec<Vec2> = iter.into_iter().collect();
|
||||
|
@ -1318,6 +1325,7 @@ impl FromIterator<Vec2> for BoxedPolyline2d {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl BoxedPolyline2d {
|
||||
/// Create a new `BoxedPolyline2d` from its vertices
|
||||
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> 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<const N: usize> TryFrom<Polygon<N>> for ConvexPolygon<N> {
|
|||
/// 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<Vec2> for BoxedPolygon {
|
||||
fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
|
||||
let vertices: Vec<Vec2> = iter.into_iter().collect();
|
||||
|
@ -1726,6 +1738,7 @@ impl FromIterator<Vec2> for BoxedPolygon {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl BoxedPolygon {
|
||||
/// Create a new `BoxedPolygon` from its vertices
|
||||
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
|
||||
|
|
|
@ -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<const N: usize> Polyline3d<N> {
|
|||
/// 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<Vec3> for BoxedPolyline3d {
|
||||
fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
|
||||
let vertices: Vec<Vec3> = iter.into_iter().collect();
|
||||
|
@ -457,6 +467,7 @@ impl FromIterator<Vec3> for BoxedPolyline3d {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl BoxedPolyline3d {
|
||||
/// Create a new `BoxedPolyline3d` from its vertices
|
||||
pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> 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");
|
||||
|
|
|
@ -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::<T>::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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<f32> {
|
||||
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<f32> {
|
||||
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);
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::{
|
|||
primitives::{Measured2d, Triangle3d},
|
||||
ShapeSample, Vec3,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use rand::Rng;
|
||||
use rand_distr::{Distribution, WeightedAliasIndex, WeightedError};
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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<R: Rng + ?Sized>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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::<Self>(
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue