Skip to main content

scenix_math/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! Custom scalar `f32` math primitives for scenix.
4//!
5//! The crate is intentionally dependency-light and can be used without `std`.
6//! Enable the `libm` feature when targeting `no_std` platforms that need
7//! portable trigonometric functions.
8
9pub mod bounds;
10pub mod cylindrical;
11pub mod euler;
12pub mod mat;
13pub mod plane;
14pub mod quat;
15pub mod ray;
16pub mod spherical;
17pub mod transform;
18pub mod vec;
19
20pub use bounds::{Aabb, Sphere};
21pub use cylindrical::Cylindrical;
22pub use euler::{Euler, RotationOrder};
23pub use mat::{Mat3, Mat4};
24pub use plane::Plane;
25pub use quat::Quat;
26pub use ray::Ray3;
27pub use spherical::Spherical;
28pub use transform::Transform;
29pub use vec::{Vec2, Vec3, Vec4};
30
31pub(crate) const EPSILON: f32 = 1.0e-6;
32#[cfg(all(not(feature = "libm"), not(feature = "std")))]
33pub(crate) const PI: f32 = core::f32::consts::PI;
34#[cfg(all(not(feature = "libm"), not(feature = "std")))]
35pub(crate) const FRAC_PI_2: f32 = core::f32::consts::FRAC_PI_2;
36
37#[inline]
38pub(crate) fn clamp(value: f32, min: f32, max: f32) -> f32 {
39    value.max(min).min(max)
40}
41
42#[inline]
43pub(crate) fn sqrt(value: f32) -> f32 {
44    #[cfg(feature = "libm")]
45    {
46        libm::sqrtf(value)
47    }
48    #[cfg(all(not(feature = "libm"), feature = "std"))]
49    {
50        value.sqrt()
51    }
52    #[cfg(all(not(feature = "libm"), not(feature = "std")))]
53    {
54        if value <= 0.0 {
55            return 0.0;
56        }
57        let mut x = if value >= 1.0 { value } else { 1.0 };
58        let mut i = 0;
59        while i < 8 {
60            x = 0.5 * (x + value / x);
61            i += 1;
62        }
63        x
64    }
65}
66
67#[inline]
68pub(crate) fn sin(value: f32) -> f32 {
69    #[cfg(feature = "libm")]
70    {
71        libm::sinf(value)
72    }
73    #[cfg(all(not(feature = "libm"), feature = "std"))]
74    {
75        value.sin()
76    }
77    #[cfg(all(not(feature = "libm"), not(feature = "std")))]
78    {
79        let x = reduce_half_pi(value);
80        let x2 = x * x;
81        x * (1.0 - x2 / 6.0 + (x2 * x2) / 120.0 - (x2 * x2 * x2) / 5040.0
82            + (x2 * x2 * x2 * x2) / 362_880.0)
83    }
84}
85
86#[inline]
87pub(crate) fn cos(value: f32) -> f32 {
88    #[cfg(feature = "libm")]
89    {
90        libm::cosf(value)
91    }
92    #[cfg(all(not(feature = "libm"), feature = "std"))]
93    {
94        value.cos()
95    }
96    #[cfg(all(not(feature = "libm"), not(feature = "std")))]
97    {
98        let mut x = reduce_pi(value);
99        let mut sign = 1.0;
100        if x > FRAC_PI_2 {
101            x = PI - x;
102            sign = -1.0;
103        } else if x < -FRAC_PI_2 {
104            x = -PI - x;
105            sign = -1.0;
106        }
107        let x2 = x * x;
108        sign * (1.0 - x2 / 2.0 + (x2 * x2) / 24.0 - (x2 * x2 * x2) / 720.0
109            + (x2 * x2 * x2 * x2) / 40_320.0)
110    }
111}
112
113#[inline]
114pub(crate) fn tan(value: f32) -> f32 {
115    #[cfg(feature = "libm")]
116    {
117        libm::tanf(value)
118    }
119    #[cfg(all(not(feature = "libm"), feature = "std"))]
120    {
121        value.tan()
122    }
123    #[cfg(all(not(feature = "libm"), not(feature = "std")))]
124    {
125        let c = cos(value);
126        if c.abs() <= EPSILON {
127            if value >= 0.0 {
128                f32::INFINITY
129            } else {
130                -f32::INFINITY
131            }
132        } else {
133            sin(value) / c
134        }
135    }
136}
137
138#[inline]
139pub(crate) fn acos(value: f32) -> f32 {
140    #[cfg(feature = "libm")]
141    {
142        libm::acosf(value)
143    }
144    #[cfg(all(not(feature = "libm"), feature = "std"))]
145    {
146        value.acos()
147    }
148    #[cfg(all(not(feature = "libm"), not(feature = "std")))]
149    {
150        FRAC_PI_2 - asin(value)
151    }
152}
153
154#[inline]
155pub(crate) fn asin(value: f32) -> f32 {
156    #[cfg(feature = "libm")]
157    {
158        libm::asinf(value)
159    }
160    #[cfg(all(not(feature = "libm"), feature = "std"))]
161    {
162        value.asin()
163    }
164    #[cfg(all(not(feature = "libm"), not(feature = "std")))]
165    {
166        let x = clamp(value, -1.0, 1.0);
167        atan2(x, sqrt((1.0 - x * x).max(0.0)))
168    }
169}
170
171#[inline]
172pub(crate) fn atan2(y: f32, x: f32) -> f32 {
173    #[cfg(feature = "libm")]
174    {
175        libm::atan2f(y, x)
176    }
177    #[cfg(all(not(feature = "libm"), feature = "std"))]
178    {
179        y.atan2(x)
180    }
181    #[cfg(all(not(feature = "libm"), not(feature = "std")))]
182    {
183        if x > 0.0 {
184            atan_approx(y / x)
185        } else if x < 0.0 && y >= 0.0 {
186            atan_approx(y / x) + PI
187        } else if x < 0.0 && y < 0.0 {
188            atan_approx(y / x) - PI
189        } else if y > 0.0 {
190            FRAC_PI_2
191        } else if y < 0.0 {
192            -FRAC_PI_2
193        } else {
194            0.0
195        }
196    }
197}
198
199#[cfg(all(not(feature = "libm"), not(feature = "std")))]
200#[inline]
201fn reduce_pi(mut value: f32) -> f32 {
202    let two_pi = 2.0 * PI;
203    while value > PI {
204        value -= two_pi;
205    }
206    while value < -PI {
207        value += two_pi;
208    }
209    value
210}
211
212#[cfg(all(not(feature = "libm"), not(feature = "std")))]
213#[inline]
214fn reduce_half_pi(value: f32) -> f32 {
215    let value = reduce_pi(value);
216    if value > FRAC_PI_2 {
217        PI - value
218    } else if value < -FRAC_PI_2 {
219        -PI - value
220    } else {
221        value
222    }
223}
224
225#[cfg(all(not(feature = "libm"), not(feature = "std")))]
226#[inline]
227fn atan_approx(value: f32) -> f32 {
228    let sign = value.signum();
229    let abs = value.abs();
230    if abs > 1.0 {
231        sign * FRAC_PI_2 - sign * atan_approx(1.0 / abs)
232    } else if abs > 0.414_213_57 {
233        sign * (core::f32::consts::FRAC_PI_4 + atan_approx((abs - 1.0) / (abs + 1.0)))
234    } else {
235        let x2 = value * value;
236        value
237            * (1.0 - x2 / 3.0 + (x2 * x2) / 5.0 - (x2 * x2 * x2) / 7.0 + (x2 * x2 * x2 * x2) / 9.0
238                - (x2 * x2 * x2 * x2 * x2) / 11.0)
239    }
240}
241
242#[cfg(test)]
243pub(crate) fn assert_close(a: f32, b: f32) {
244    assert!((a - b).abs() <= 1.0e-4, "{a} != {b}");
245}