use core::{
f32::consts::{PI, TAU},
fmt::{self, Debug, Display},
marker::PhantomData,
ops::{Add, Div, Mul, Neg, Rem, Sub},
};
use crate::math::{Affine, ApproxEq, Linear, Vector, vary::ZDiv};
#[cfg(feature = "fp")]
use crate::math::{Vec2, Vec3, float::f32, vec2, vec3};
#[derive(Copy, Clone, Default, PartialEq)]
#[repr(transparent)]
pub struct Angle(f32);
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Polar<B>(PhantomData<B>);
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Spherical<B>(PhantomData<B>);
pub type PolarVec<B = ()> = Vector<[f32; 2], Polar<B>>;
pub type SphericalVec<B = ()> = Vector<[f32; 3], Spherical<B>>;
pub fn rads(a: f32) -> Angle {
Angle(a)
}
pub fn degs(a: f32) -> Angle {
Angle(a * RADS_PER_DEG)
}
pub fn turns(a: f32) -> Angle {
Angle(a * RADS_PER_TURN)
}
#[cfg(feature = "fp")]
pub fn asin(x: f32) -> Angle {
assert!(-1.0 <= x && x <= 1.0);
Angle(f32::asin(x))
}
#[cfg(feature = "fp")]
pub fn acos(x: f32) -> Angle {
Angle(f32::acos(x))
}
#[cfg(feature = "fp")]
pub fn atan2(y: f32, x: f32) -> Angle {
Angle(f32::atan2(y, x))
}
pub const fn polar<B>(r: f32, az: Angle) -> PolarVec<B> {
Vector::new([r, az.to_rads()])
}
pub const fn spherical<B>(r: f32, az: Angle, alt: Angle) -> SphericalVec<B> {
Vector::new([r, az.to_rads(), alt.to_rads()])
}
const RADS_PER_DEG: f32 = PI / 180.0;
const RADS_PER_TURN: f32 = TAU;
impl Angle {
pub const ZERO: Self = Self(0.0);
pub const RIGHT: Self = Self(RADS_PER_TURN / 4.0);
pub const STRAIGHT: Self = Self(RADS_PER_TURN / 2.0);
pub const FULL: Self = Self(RADS_PER_TURN);
pub const fn to_rads(self) -> f32 {
self.0
}
pub fn to_degs(self) -> f32 {
self.0 / RADS_PER_DEG
}
pub fn to_turns(self) -> f32 {
self.0 / RADS_PER_TURN
}
pub fn min(self, other: Self) -> Self {
Self(self.0.min(other.0))
}
pub fn max(self, other: Self) -> Self {
Self(self.0.max(other.0))
}
#[must_use]
pub fn clamp(self, min: Self, max: Self) -> Self {
Self(self.0.clamp(min.0, max.0))
}
}
#[cfg(feature = "fp")]
impl Angle {
pub fn sin(self) -> f32 {
f32::sin(self.0)
}
pub fn cos(self) -> f32 {
f32::cos(self.0)
}
pub fn sin_cos(self) -> (f32, f32) {
(self.sin(), self.cos())
}
pub fn tan(self) -> f32 {
f32::tan(self.0)
}
#[must_use]
pub fn wrap(self, min: Self, max: Self) -> Self {
Self(min.0 + f32::rem_euclid(self.0 - min.0, max.0 - min.0))
}
}
impl<B> PolarVec<B> {
#[inline]
pub fn r(&self) -> f32 {
self.0[0]
}
#[inline]
pub fn az(&self) -> Angle {
rads(self.0[1])
}
#[cfg(feature = "fp")]
pub fn to_cart(&self) -> Vec2<B> {
let (y, x) = self.az().sin_cos();
vec2(x, y) * self.r()
}
}
impl<B> SphericalVec<B> {
#[inline]
pub fn r(&self) -> f32 {
self.0[0]
}
#[inline]
pub fn az(&self) -> Angle {
rads(self.0[1])
}
#[inline]
pub fn alt(&self) -> Angle {
rads(self.0[2])
}
#[cfg(feature = "fp")]
pub fn to_cart(&self) -> Vec3<B> {
let (sin_alt, cos_alt) = self.alt().sin_cos();
let (sin_az, cos_az) = self.az().sin_cos();
let x = cos_az * cos_alt;
let z = sin_az * cos_alt;
let y = sin_alt;
self.r() * vec3(x, y, z)
}
}
#[cfg(feature = "fp")]
impl<B> Vec2<B> {
pub fn to_polar(&self) -> PolarVec<B> {
let r = self.len();
let az = atan2(self.y(), self.x());
polar(r, az)
}
}
#[cfg(feature = "fp")]
impl<B> Vec3<B> {
pub fn to_spherical(&self) -> SphericalVec<B> {
let [x, y, z] = self.0;
let az = atan2(z, x);
let alt = atan2(y, f32::sqrt(x * x + z * z));
let r = self.len();
spherical(r, az, alt)
}
}
impl ApproxEq for Angle {
fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
self.0.approx_eq_eps(&other.0, &eps.0)
}
fn relative_epsilon() -> Self {
Self(f32::relative_epsilon())
}
}
impl Affine for Angle {
type Space = ();
type Diff = Self;
const DIM: usize = 1;
#[inline]
fn add(&self, other: &Self) -> Self {
*self + *other
}
#[inline]
fn sub(&self, other: &Self) -> Self {
*self - *other
}
}
impl Linear for Angle {
type Scalar = f32;
#[inline]
fn zero() -> Self {
Self::ZERO
}
#[inline]
fn mul(&self, scalar: f32) -> Self {
*self * scalar
}
}
impl ZDiv for Angle {}
impl<B> Default for SphericalVec<B> {
fn default() -> Self {
Self::new([1.0, 0.0, 0.0])
}
}
impl<B> Default for PolarVec<B> {
fn default() -> Self {
Self::new([1.0, 0.0])
}
}
impl Display for Angle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (val, unit) = if f.alternate() {
(self.to_rads() / PI, "𝜋 rad")
} else {
(self.to_degs(), "°")
};
Display::fmt(&val, f)?;
f.write_str(unit)
}
}
impl Debug for Angle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Angle(")?;
Display::fmt(self, f)?;
f.write_str(")")
}
}
impl Add for Angle {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl Sub for Angle {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
impl Neg for Angle {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
impl Mul<f32> for Angle {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self(self.0 * rhs)
}
}
impl Div<f32> for Angle {
type Output = Self;
fn div(self, rhs: f32) -> Self {
Self(self.0 / rhs)
}
}
impl Rem for Angle {
type Output = Self;
fn rem(self, rhs: Self) -> Self {
Self(self.0 % rhs.0)
}
}
#[cfg(feature = "fp")]
impl<B> From<PolarVec<B>> for Vec2<B> {
fn from(p: PolarVec<B>) -> Self {
p.to_cart()
}
}
#[cfg(feature = "fp")]
impl<B> From<Vec2<B>> for PolarVec<B> {
fn from(v: Vec2<B>) -> Self {
v.to_polar()
}
}
#[cfg(feature = "fp")]
impl<B> From<SphericalVec<B>> for Vec3<B> {
fn from(v: SphericalVec<B>) -> Self {
v.to_cart()
}
}
#[cfg(feature = "fp")]
impl<B> From<Vec3<B>> for SphericalVec<B> {
fn from(v: Vec3<B>) -> Self {
v.to_spherical()
}
}
#[cfg(test)]
#[allow(unused, nonstandard_style)]
mod tests {
use core::f32::consts::{PI, TAU};
use crate::{
assert_approx_eq,
math::{self, Lerp, Vary, Vec2, Vec3},
};
use super::*;
const SQRT_3: f32 = 1.7320508;
#[test]
fn rads_to_degs() {
assert_eq!(rads(PI).to_degs(), 180.0);
}
#[test]
fn rads_to_turns() {
assert_eq!(rads(PI).to_turns(), 0.5);
}
#[test]
fn degs_to_rads() {
assert_eq!(degs(180.0).to_rads(), PI);
}
#[test]
fn degs_to_turns() {
assert_eq!(degs(360.0).to_turns(), 1.0);
}
#[test]
fn turns_to_rads() {
assert_eq!(turns(1.0).to_rads(), TAU);
}
#[test]
fn turns_to_degs() {
assert_eq!(turns(1.0).to_degs(), 360.0);
}
#[test]
fn clamping() {
let min = degs(-45.0);
let max = degs(45.0);
assert_eq!(degs(60.0).clamp(min, max), max);
assert_eq!(degs(10.0).clamp(min, max), degs(10.0));
assert_eq!(degs(-50.0).clamp(min, max), min);
}
#[cfg(feature = "fp")]
#[test]
fn trig_functions() {
assert_eq!(degs(0.0).sin(), 0.0);
assert_eq!(degs(0.0).cos(), 1.0);
assert_approx_eq!(degs(30.0).sin(), 0.5);
assert_approx_eq!(degs(60.0).cos(), 0.5);
let (sin, cos) = degs(90.0).sin_cos();
assert_approx_eq!(sin, 1.0);
assert_approx_eq!(cos, 0.0);
assert_approx_eq!(degs(-45.0).tan(), -1.0);
assert_approx_eq!(degs(0.0).tan(), 0.0);
assert_approx_eq!(degs(45.0).tan(), 1.0);
assert_approx_eq!(degs(135.0).tan(), -1.0);
assert_approx_eq!(degs(225.0).tan(), 1.0);
assert_approx_eq!(degs(315.0).tan(), -1.0);
}
#[cfg(all(feature = "fp", not(feature = "mm")))]
#[test]
fn inverse_trig_functions() {
assert_approx_eq!(asin(-1.0), degs(-90.0));
assert_approx_eq!(asin(0.0), degs(0.0));
assert_approx_eq!(asin(0.5), degs(30.0));
assert_approx_eq!(asin(1.0), degs(90.0));
assert_approx_eq!(acos(-1.0), degs(180.0));
assert_approx_eq!(acos(0.0), degs(90.0));
assert_approx_eq!(acos(0.5), degs(60.0));
assert_approx_eq!(acos(1.0), degs(0.0));
assert_approx_eq!(atan2(0.0, 1.0), degs(0.0));
assert_approx_eq!(atan2(1.0, SQRT_3), degs(30.0));
assert_approx_eq!(atan2(1.0, -1.0), degs(135.0));
assert_approx_eq!(atan2(-SQRT_3, -1.0), degs(-120.0));
assert_approx_eq!(atan2(-1.0, 1.0), degs(-45.0));
}
#[cfg(feature = "fp")]
#[test]
fn wrapping() {
use crate::assert_approx_eq;
let a = degs(540.0).wrap(Angle::ZERO, Angle::FULL);
assert_approx_eq!(a, degs(180.0));
let a = degs(225.0).wrap(-Angle::STRAIGHT, Angle::STRAIGHT);
assert_approx_eq!(a, degs(-135.0));
}
#[test]
fn lerping() {
let a = degs(30.0).lerp(°s(60.0), 0.2);
assert_eq!(a, degs(36.0));
}
#[test]
fn varying() {
let mut i = degs(45.0).vary(degs(15.0), Some(4));
assert_approx_eq!(i.next(), Some(degs(45.0)));
assert_approx_eq!(i.next(), Some(degs(60.0)));
assert_approx_eq!(i.next(), Some(degs(75.0)));
assert_approx_eq!(i.next(), Some(degs(90.0)));
assert_approx_eq!(i.next(), None);
}
const vec2: fn(f32, f32) -> Vec2 = math::vec2;
const vec3: fn(f32, f32, f32) -> Vec3 = math::vec3;
#[cfg(feature = "fp")]
#[test]
fn polar_to_cartesian_zero_r() {
assert_eq!(polar(0.0, degs(0.0)).to_cart(), vec2(0.0, 0.0));
assert_eq!(polar(0.0, degs(30.0)).to_cart(), vec2(0.0, 0.0));
assert_eq!(polar(0.0, degs(-120.0)).to_cart(), vec2(0.0, 0.0));
}
#[cfg(feature = "fp")]
#[test]
fn polar_to_cartesian_zero_az() {
assert_eq!(polar(2.0, degs(0.0)).to_cart(), vec2(2.0, 0.0));
assert_eq!(polar(-3.0, degs(0.0)).to_cart(), vec2(-3.0, 0.0));
}
#[cfg(feature = "fp")]
#[test]
fn polar_to_cartesian() {
assert_approx_eq!(polar(2.0, degs(60.0)).to_cart(), vec2(1.0, SQRT_3));
assert_approx_eq!(
polar(3.0, degs(-90.0)).to_cart(),
vec2(0.0, -3.0),
eps = 1e-6
);
assert_approx_eq!(polar(4.0, degs(270.0)).to_cart(), vec2(0.0, -4.0));
assert_approx_eq!(
polar(5.0, turns(1.25)).to_cart(),
vec2(0.0, 5.0),
eps = 2e-6
);
}
#[cfg(feature = "fp")]
#[test]
fn cartesian_to_polar_zero_y() {
assert_approx_eq!(vec2(0.0, 0.0).to_polar(), polar(0.0, degs(0.0)));
assert_eq!(vec2(1.0, 0.0).to_polar(), polar(1.0, degs(0.0)));
}
#[cfg(feature = "fp")]
#[test]
fn cartesian_to_polar() {
assert_approx_eq!(vec2(SQRT_3, 1.0).to_polar(), polar(2.0, degs(30.0)));
assert_eq!(vec2(0.0, 2.0).to_polar(), polar(2.0, degs(90.0)));
assert_approx_eq!(vec2(-3.0, 0.0).to_polar(), polar(3.0, degs(180.0)));
assert_eq!(vec2(0.0, -4.0).to_polar(), polar(4.0, degs(-90.0)));
}
#[cfg(feature = "fp")]
#[test]
fn spherical_to_cartesian() {
let spherical = spherical::<()>;
assert_eq!(
spherical(0.0, degs(0.0), degs(0.0)).to_cart(),
vec3(0.0, 0.0, 0.0)
);
assert_eq!(
spherical(1.0, degs(0.0), degs(0.0)).to_cart(),
vec3(1.0, 0.0, 0.0)
);
assert_approx_eq!(
spherical(2.0, degs(60.0), degs(0.0)).to_cart(),
vec3(1.0, 0.0, SQRT_3)
);
assert_approx_eq!(
spherical(2.0, degs(90.0), degs(0.0)).to_cart(),
vec3(0.0, 0.0, 2.0)
);
assert_approx_eq!(
spherical(3.0, degs(123.0), degs(90.0)).to_cart(),
vec3(0.0, 3.0, 0.0)
);
}
#[cfg(feature = "fp")]
#[test]
fn cartesian_to_spherical_zero_alt() {
assert_approx_eq!(
vec3(0.0, 0.0, 0.0).to_spherical(),
spherical(0.0, degs(0.0), degs(0.0))
);
assert_eq!(
vec3(1.0, 0.0, 0.0).to_spherical(),
spherical(1.0, degs(0.0), degs(0.0))
);
assert_approx_eq!(
vec3(1.0, SQRT_3, 0.0).to_spherical(),
spherical(2.0, degs(0.0), degs(60.0))
);
assert_eq!(
vec3(0.0, 2.0, 0.0).to_spherical(),
spherical(2.0, degs(0.0), degs(90.0))
);
}
#[cfg(feature = "fp")]
#[test]
fn cartesian_to_spherical() {
use core::f32::consts::SQRT_2;
assert_approx_eq!(
vec3(SQRT_3, 0.0, 1.0).to_spherical(),
spherical(2.0, degs(30.0), degs(0.0))
);
assert_approx_eq!(
vec3(1.0, SQRT_2, 1.0).to_spherical(),
spherical(2.0, degs(45.0), degs(45.0))
);
assert_approx_eq!(
vec3(0.0, 0.0, 3.0).to_spherical(),
spherical(3.0, degs(90.0), degs(0.0))
);
}
}