use approx::{AbsDiffEq, RelativeEq, UlpsEq};
use bytemuck::{Pod, Zeroable};
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
use crate::{
bindings::Quat, peel, scalar::FloatScalar, traits::marker::PodValue, Scalar, Transparent, Unit,
Vector3,
};
#[repr(transparent)]
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(
all(not(target_arch = "wasm32"), feature = "wasmtime"),
derive(
wasmtime::component::ComponentType,
wasmtime::component::Lower,
wasmtime::component::Lift
)
)]
#[cfg_attr(
all(not(target_arch = "wasm32"), feature = "wasmtime"),
component(record)
)]
pub struct Angle<U: Scalar = f32> {
pub radians: U,
}
unsafe impl<T: Scalar> Zeroable for Angle<T> {}
unsafe impl<T: Scalar> Pod for Angle<T> {}
unsafe impl<T: Scalar> Transparent for Angle<T> {
type Wrapped = T;
}
pub trait AngleConsts {
const PI: Self;
const TAU: Self;
const FRAG_1_PI: Self;
const FRAG_2_PI: Self;
const FRAG_PI_2: Self;
const FRAG_PI_3: Self;
const FRAG_PI_4: Self;
const FRAG_PI_6: Self;
const FRAG_PI_8: Self;
}
macro_rules! impl_float_angle_consts {
($float:ident) => {
impl AngleConsts for $float {
const PI: Self = core::$float::consts::PI;
const TAU: Self = Self::PI + Self::PI;
const FRAG_1_PI: Self = 1.0 / Self::PI;
const FRAG_2_PI: Self = 2.0 / Self::PI;
const FRAG_PI_2: Self = Self::PI / 2.0;
const FRAG_PI_3: Self = Self::PI / 3.0;
const FRAG_PI_4: Self = Self::PI / 4.0;
const FRAG_PI_6: Self = Self::PI / 6.0;
const FRAG_PI_8: Self = Self::PI / 8.0;
}
};
}
impl_float_angle_consts!(f32);
impl_float_angle_consts!(f64);
macro_rules! forward_to_float_as_angle {
(
$(#[$attr:meta])?
fn $f:ident(self $(, $args:ident: $args_ty:ty)*) -> Angle<Self>) => {
$(#[$attr])?
#[must_use]
#[inline]
fn $f(self, $($args: $args_ty),*) -> Angle<Self> {
Angle::from_radians(<Self as num_traits::Float>::$f(self $($args),*))
}
};
}
pub trait FloatAngleExt: FloatScalar + AngleConsts + PodValue {
forward_to_float_as_angle!(
#[doc = "asin()"]
fn asin(self) -> Angle<Self>
);
forward_to_float_as_angle!(
#[doc = "acos()"]
fn acos(self) -> Angle<Self>);
forward_to_float_as_angle!(
#[doc = "atan()"]
fn atan(self) -> Angle<Self>
);
}
impl FloatAngleExt for f32 {}
impl FloatAngleExt for f64 {}
impl<T: Scalar + AngleConsts> core::fmt::Debug for Angle<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use core::fmt::Write;
f.write_str("Angle(")?;
let r = self.radians;
if r == T::PI {
f.write_char('π')?;
} else if r == T::TAU {
f.write_str("2π")?;
} else if r == T::FRAG_1_PI {
f.write_str("1/π")?;
} else if r == T::FRAG_2_PI {
f.write_str("2/π")?;
} else if r == T::FRAG_PI_2 {
f.write_str("π/2")?;
} else if r == T::FRAG_PI_3 {
f.write_str("π/3")?;
} else if r == T::FRAG_PI_4 {
f.write_str("π/4")?;
} else if r == T::FRAG_PI_6 {
f.write_str("π/6")?;
} else if r == T::FRAG_PI_8 {
f.write_str("π/8")?;
} else {
write!(f, "{r:0.5}")?;
}
f.write_char(')')
}
}
impl<T> Unit for Angle<T>
where
T: Scalar + AngleConsts,
{
type Scalar = T;
}
impl<T: Scalar> AbsDiffEq for Angle<T> {
type Epsilon = T::Epsilon;
#[inline]
fn default_epsilon() -> Self::Epsilon {
T::default_epsilon()
}
#[inline]
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.radians.abs_diff_eq(&other.radians, epsilon)
}
#[inline]
fn abs_diff_ne(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.radians.abs_diff_ne(&other.radians, epsilon)
}
}
impl<T: FloatScalar> UlpsEq for Angle<T> {
#[inline]
fn default_max_ulps() -> u32 {
T::default_max_ulps()
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.radians.ulps_eq(&other.radians, epsilon, max_ulps)
}
#[inline]
fn ulps_ne(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.radians.ulps_ne(&other.radians, epsilon, max_ulps)
}
}
impl<T: FloatScalar> RelativeEq for Angle<T> {
#[inline]
fn default_max_relative() -> Self::Epsilon {
T::default_max_relative()
}
#[inline]
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.radians
.relative_eq(&other.radians, epsilon, max_relative)
}
#[inline]
fn relative_ne(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.radians
.relative_ne(&other.radians, epsilon, max_relative)
}
}
macro_rules! forward_to_unit {
(
$(#[$attr:meta])?
fn $f:ident(self $(, $args:ident: $args_ty:ty)*) -> $ret:ty) => {
$(#[$attr])*
#[must_use]
#[inline]
pub fn $f(self, $($args: $args_ty),*) -> $ret {
self.radians.$f($($args),*)
}
};
}
macro_rules! forward_to_unit_as_self {
(
$(#[$attr:meta])?
fn $f:ident(self $(, $args:ident: $args_ty:ty)*) -> Self) => {
$(#[$attr])*
#[must_use]
#[inline]
pub fn $f(self, $($args: $args_ty),*) -> Angle<T> {
Angle::from_radians(self.radians.$f($($args.radians),*))
}
};
}
impl<T: FloatScalar> Angle<T> {
#[inline]
#[must_use]
pub const fn new(radians: T) -> Self {
Angle { radians }
}
#[inline]
#[must_use]
pub const fn from_radians(radians: T) -> Self {
Angle::new(radians)
}
#[inline]
#[must_use]
pub fn from_degrees(degrees: T) -> Self {
Self::from_radians(degrees.to_radians())
}
#[inline]
#[must_use]
pub const fn to_radians(self) -> T {
self.radians
}
forward_to_unit!(
#[doc = "See [num_traits::Float::to_degrees()]"]
fn to_degrees(self) -> T);
forward_to_unit!(
#[doc = "See [num_traits::Float::sin()]."]
fn sin(self) -> T);
forward_to_unit!(
#[doc = "See [num_traits::Float::cos()]."]
fn cos(self) -> T);
forward_to_unit!(
#[doc = "See [num_traits::Float::tan()]."]
fn tan(self) -> T);
forward_to_unit!(
#[doc = "See [num_traits::Float::sin_cos()]."]
fn sin_cos(self) -> (T, T));
forward_to_unit_as_self!(
#[doc = "See [num_traits::Float::min()]."]
fn min(self, other: Self) -> Self);
forward_to_unit_as_self!(
#[doc = "See [num_traits::Float::max()]."]
fn max(self, other: Self) -> Self);
}
impl<T: FloatScalar + AngleConsts> Angle<T> {
pub const CIRCLE: Self = Self::TAU;
pub const HALF_CIRCLE: Self = Self::PI;
pub const QUARTER_CIRCLE: Self = Self::FRAG_PI_2;
}
impl<T: Scalar> From<T> for Angle<T> {
fn from(radians: T) -> Self {
Angle { radians }
}
}
macro_rules! forward_op_scalar {
($trait_name:ident, $op:ident) => {
impl<T: FloatScalar> $trait_name<T> for Angle<T> {
type Output = Self;
fn $op(self, rhs: T) -> Self {
Angle {
radians: self.radians.$op(rhs),
}
}
}
};
}
macro_rules! forward_op_scalar_assign {
($trait_name:ident, $op:ident) => {
impl<T: FloatScalar> $trait_name<T> for Angle<T> {
fn $op(&mut self, rhs: T) {
self.radians.$op(rhs);
}
}
};
}
macro_rules! forward_op_self {
($trait_name:ident, $op:ident) => {
impl<T: FloatScalar> $trait_name for Angle<T> {
type Output = Self;
fn $op(self, rhs: Self) -> Self {
Angle {
radians: self.radians.$op(rhs.radians),
}
}
}
};
}
macro_rules! forward_op_self_assign {
($trait_name:ident, $op:ident) => {
impl<T: FloatScalar> $trait_name for Angle<T> {
fn $op(&mut self, rhs: Self) {
self.radians.$op(rhs.radians);
}
}
};
}
forward_op_scalar!(Mul, mul);
forward_op_scalar!(Div, div);
forward_op_scalar!(Rem, rem);
forward_op_scalar_assign!(MulAssign, mul_assign);
forward_op_scalar_assign!(DivAssign, div_assign);
forward_op_scalar_assign!(RemAssign, rem_assign);
forward_op_self!(Add, add);
forward_op_self!(Sub, sub);
forward_op_self!(Rem, rem);
forward_op_self_assign!(AddAssign, add_assign);
forward_op_self_assign!(SubAssign, sub_assign);
forward_op_self_assign!(RemAssign, rem_assign);
impl<T: Scalar> Div<Angle<T>> for Angle<T> {
type Output = T;
fn div(self, rhs: Angle<T>) -> Self::Output {
self.radians / rhs.radians
}
}
impl<T: FloatScalar + AngleConsts> AngleConsts for Angle<T> {
const PI: Self = Angle { radians: T::PI };
const TAU: Self = Angle { radians: T::TAU };
const FRAG_1_PI: Self = Angle {
radians: T::FRAG_1_PI,
};
const FRAG_2_PI: Self = Angle {
radians: T::FRAG_2_PI,
};
const FRAG_PI_2: Self = Angle {
radians: T::FRAG_PI_2,
};
const FRAG_PI_3: Self = Angle {
radians: T::FRAG_PI_3,
};
const FRAG_PI_4: Self = Angle {
radians: T::FRAG_PI_4,
};
const FRAG_PI_6: Self = Angle {
radians: T::FRAG_PI_6,
};
const FRAG_PI_8: Self = Angle {
radians: T::FRAG_PI_8,
};
}
impl<T: FloatScalar> Angle<T> {
#[inline]
#[must_use]
pub fn to_rotation(self, axis: Vector3<T>) -> T::Quat {
<T::Quat as Quat<T>>::from_axis_angle(peel(axis), self.radians)
}
}
#[cfg(test)]
mod tests {
use approx::{
assert_abs_diff_eq, assert_abs_diff_ne, assert_relative_eq, assert_relative_ne,
assert_ulps_eq, assert_ulps_ne,
};
use crate::{peel_mut, peel_ref};
use super::*;
type Angle = super::Angle<f32>;
#[test]
fn forward_to_float() {
use super::FloatAngleExt;
let s: Angle = FloatAngleExt::asin(1.0f32);
let c: Angle = FloatAngleExt::acos(1.0f32);
let t: Angle = FloatAngleExt::atan(1.0f32);
assert_eq!(s.to_radians(), f32::asin(1.0f32));
assert_eq!(c.to_radians(), f32::acos(1.0f32));
assert_eq!(t.to_radians(), f32::atan(1.0f32));
assert_eq!(Angle::PI.clone(), Angle::PI);
assert_eq!(Angle::default(), Angle::from_radians(0.0));
assert!(Angle::PI >= Angle::default());
}
#[test]
fn min_max() {
assert_eq!(
Angle::from_radians(1.0).max(Angle::from_radians(2.0)),
Angle::from_radians(2.0)
);
assert_eq!(
Angle::from_radians(1.0).min(Angle::from_radians(2.0)),
Angle::from_radians(1.0)
);
}
#[test]
fn approx_comparison() {
assert_eq!(Angle::PI, Angle::from_radians(f32::PI));
assert_ne!(Angle::PI, Angle::CIRCLE);
assert_abs_diff_eq!(Angle::PI, Angle::PI);
assert_relative_eq!(Angle::PI, Angle::PI);
assert_ulps_eq!(Angle::PI, Angle::PI);
assert_abs_diff_ne!(Angle::PI, Angle::CIRCLE);
assert_relative_ne!(Angle::PI, Angle::CIRCLE);
assert_ulps_ne!(Angle::PI, Angle::CIRCLE);
}
#[test]
fn angle_arithmetic() {
let a = Angle::from_radians(1.0);
let b = Angle::from_radians(-0.5);
assert_abs_diff_eq!(a + b, Angle::from_radians(0.5));
assert_abs_diff_eq!(a * 2.0, Angle::from_radians(2.0));
assert_abs_diff_eq!(a / 2.0, Angle::from_radians(0.5));
assert_abs_diff_eq!(a / b, -2.0);
let mut a = Angle::from_radians(1.0);
a += Angle::from_radians(2.0);
assert_eq!(a, Angle::from_radians(3.0));
a -= Angle::from_radians(4.0);
assert_eq!(a, Angle::from_radians(-1.0));
let mut a = Angle::from_radians(3.0);
a %= Angle::from_radians(2.0);
assert_eq!(a, Angle::from_radians(1.0));
a *= 2.0;
assert_eq!(a, Angle::from_radians(2.0));
a /= 2.0;
assert_eq!(a, Angle::from_radians(1.0));
}
#[test]
fn angle_degrees() {
let circle = Angle::CIRCLE;
assert_abs_diff_eq!(circle, Angle::from_degrees(360.0));
assert_ulps_eq!(circle, Angle::from_degrees(360.0));
assert_relative_eq!(circle, Angle::from_degrees(360.0));
let quarter_circle = Angle::FRAG_PI_2;
assert_abs_diff_eq!(quarter_circle, Angle::from_degrees(90.0));
assert_ulps_eq!(quarter_circle, Angle::from_degrees(90.0));
assert_relative_eq!(quarter_circle, Angle::from_degrees(90.0));
assert_eq!(Angle::from_degrees(90.0).to_degrees(), 90.0);
}
#[test]
fn angle_cast() {
let mut a = Angle::CIRCLE;
let _: &f32 = peel_ref(&a);
let _: &mut f32 = peel_mut(&mut a);
let _: f32 = peel(a);
let _: f32 = a.to_radians();
let _: Angle = 1.0.into();
}
#[test]
fn to_rotation() {
let angle = Angle::HALF_CIRCLE;
let quat = angle.to_rotation(Vector3::Z);
assert_abs_diff_eq!(quat, glam::Quat::from_axis_angle(glam::Vec3::Z, f32::PI));
let v = quat * Vector3::<f32>::X;
assert_abs_diff_eq!(v, -Vector3::X);
}
}