pub const PI: f32 = std::f32::consts::PI;
pub const PI_2: f32 = PI * 2.0;
pub const INV_PI_2: f32 = 1.0 / PI_2;
pub const DEG_RAD: f32 = PI / 180.0;
pub const RAD_DEG: f32 = 180.0 / PI;
pub const EPSILON: f32 = 0.00001;
pub const EPSILON_SQ: f32 = EPSILON * EPSILON;
#[inline]
#[must_use]
pub fn sin_deg(degrees: f32) -> f32 {
degrees.to_radians().sin()
}
#[inline]
#[must_use]
pub fn cos_deg(degrees: f32) -> f32 {
degrees.to_radians().cos()
}
#[inline]
#[must_use]
pub fn atan2_deg(y: f32, x: f32) -> f32 {
y.atan2(x).to_degrees()
}
#[inline]
#[must_use]
pub fn wrap_deg(degrees: f32) -> f32 {
let mut d = degrees % 360.0;
if d >= 180.0 {
d -= 360.0;
} else if d < -180.0 {
d += 360.0;
}
d
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use proptest::prelude::*;
const EPS: f32 = 1e-5;
#[test]
fn sin_deg_known_values() {
assert_abs_diff_eq!(sin_deg(0.0), 0.0, epsilon = EPS);
assert_abs_diff_eq!(sin_deg(30.0), 0.5, epsilon = EPS);
assert_abs_diff_eq!(sin_deg(90.0), 1.0, epsilon = EPS);
assert_abs_diff_eq!(sin_deg(180.0), 0.0, epsilon = EPS);
assert_abs_diff_eq!(sin_deg(270.0), -1.0, epsilon = EPS);
assert_abs_diff_eq!(sin_deg(360.0), 0.0, epsilon = EPS);
}
#[test]
fn cos_deg_known_values() {
assert_abs_diff_eq!(cos_deg(0.0), 1.0, epsilon = EPS);
assert_abs_diff_eq!(cos_deg(60.0), 0.5, epsilon = EPS);
assert_abs_diff_eq!(cos_deg(90.0), 0.0, epsilon = EPS);
assert_abs_diff_eq!(cos_deg(180.0), -1.0, epsilon = EPS);
assert_abs_diff_eq!(cos_deg(270.0), 0.0, epsilon = EPS);
assert_abs_diff_eq!(cos_deg(360.0), 1.0, epsilon = EPS);
}
#[test]
fn atan2_deg_known_values() {
assert_abs_diff_eq!(atan2_deg(0.0, 1.0), 0.0, epsilon = EPS);
assert_abs_diff_eq!(atan2_deg(1.0, 0.0), 90.0, epsilon = EPS);
assert_abs_diff_eq!(atan2_deg(1.0, 1.0), 45.0, epsilon = EPS);
assert_abs_diff_eq!(atan2_deg(0.0, -1.0), 180.0, epsilon = EPS);
assert_abs_diff_eq!(atan2_deg(-1.0, 0.0), -90.0, epsilon = EPS);
}
#[test]
fn wrap_deg_known_values() {
assert_abs_diff_eq!(wrap_deg(0.0), 0.0);
assert_abs_diff_eq!(wrap_deg(179.0), 179.0);
assert_abs_diff_eq!(wrap_deg(-179.0), -179.0);
assert_abs_diff_eq!(wrap_deg(180.0), -180.0);
assert_abs_diff_eq!(wrap_deg(181.0), -179.0);
assert_abs_diff_eq!(wrap_deg(-181.0), 179.0);
assert_abs_diff_eq!(wrap_deg(540.0), -180.0);
assert_abs_diff_eq!(wrap_deg(-540.0), -180.0);
}
proptest! {
#[test]
fn atan2_deg_round_trip(
angle in -179.0f32..=179.0,
r in 1.0e-3f32..1.0e3,
) {
let x = r * cos_deg(angle);
let y = r * sin_deg(angle);
let recovered = atan2_deg(y, x);
prop_assert!(
(recovered - angle).abs() < 1.0e-3,
"angle={angle} recovered={recovered}",
);
}
#[test]
fn trig_pythagorean_identity(angle in -1.0e6f32..1.0e6) {
let s = sin_deg(angle);
let c = cos_deg(angle);
prop_assert!((s * s + c * c - 1.0).abs() < 1.0e-4, "angle={angle}");
}
#[test]
fn wrap_deg_in_range(d in -1.0e6f32..1.0e6) {
let w = wrap_deg(d);
prop_assert!((-180.0..180.0).contains(&w), "w={w}");
}
}
}