use core::fmt::Debug;
use core::iter::Sum;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use crate::float::Float;
use crate::macros::{forward_ref_binop, forward_ref_op_assign, forward_ref_unop};
use crate::Angle;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash)]
#[repr(transparent)]
pub struct AngleUnbounded<F> {
radians: F,
}
impl<F: Float> AngleUnbounded<F> {
pub const ZERO: Self = AngleUnbounded::from_radians(F::ZERO);
pub const EPSILON: Self = AngleUnbounded::from_radians(F::DOUBLE_EPSILON);
}
impl<F: Float> AngleUnbounded<F> {
pub const RAD_PI: Self = AngleUnbounded::from_radians(F::PI);
pub const RAD_FRAC_PI_2: Self = AngleUnbounded::from_radians(F::FRAC_PI_2);
pub const RAD_FRAC_PI_3: Self = AngleUnbounded::from_radians(F::FRAC_PI_3);
pub const RAD_FRAC_PI_4: Self = AngleUnbounded::from_radians(F::FRAC_PI_4);
pub const RAD_FRAC_PI_6: Self = AngleUnbounded::from_radians(F::FRAC_PI_6);
pub const RAD_FRAC_PI_8: Self = AngleUnbounded::from_radians(F::FRAC_PI_8);
}
impl<F: Float> AngleUnbounded<F> {
pub const DEG_180: Self = Self::RAD_PI;
pub const DEG_90: Self = Self::RAD_FRAC_PI_2;
pub const DEG_60: Self = Self::RAD_FRAC_PI_3;
pub const DEG_45: Self = Self::RAD_FRAC_PI_4;
pub const DEG_30: Self = Self::RAD_FRAC_PI_6;
pub const DEG_22_5: Self = Self::RAD_FRAC_PI_8;
}
impl<F: Float> AngleUnbounded<F> {
pub const HALF: Self = Self::RAD_PI;
pub const QUARTER: Self = Self::RAD_FRAC_PI_2;
pub const SIXTH: Self = Self::RAD_FRAC_PI_3;
pub const EIGHTH: Self = Self::RAD_FRAC_PI_4;
pub const TWELFTH: Self = Self::RAD_FRAC_PI_6;
pub const SIXTEENTH: Self = Self::RAD_FRAC_PI_8;
}
impl<F: Float> AngleUnbounded<F> {
pub const GRAD_200: Self = Self::RAD_PI;
pub const GRAD_100: Self = Self::RAD_FRAC_PI_2;
pub const GRAD_66_6: Self = Self::RAD_FRAC_PI_3;
pub const GRAD_50: Self = Self::RAD_FRAC_PI_4;
pub const GRAD_33_3: Self = Self::RAD_FRAC_PI_6;
pub const GRAD_25: Self = Self::RAD_FRAC_PI_8;
}
impl<F: Debug> Debug for AngleUnbounded<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("AngleUnbounded")
.field(&self.radians)
.finish()
}
}
impl<F: Float> Default for AngleUnbounded<F> {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl<F> AngleUnbounded<F> {
#[inline]
pub const fn from_radians(radians: F) -> Self {
Self { radians }
}
}
impl<F: Float> AngleUnbounded<F> {
#[inline]
pub fn from_degrees(degrees: F) -> Self {
Self::from_radians(degrees * F::DEG_TO_RAD)
}
#[inline]
pub fn from_turns(turns: F) -> Self {
Self::from_radians(turns * F::TURNS_TO_RAD)
}
#[inline]
pub fn from_gradians(gradians: F) -> Self {
Self::from_radians(gradians * F::GRAD_TO_RAD)
}
}
impl<F: Copy> AngleUnbounded<F> {
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub const fn to_radians(self) -> F {
self.radians
}
}
impl<F: Float> AngleUnbounded<F> {
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn to_degrees(self) -> F {
self.radians * F::RAD_TO_DEG
}
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn to_turns(self) -> F {
self.radians * F::RAD_TO_TURNS
}
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn to_gradians(self) -> F {
self.radians * F::RAD_TO_GRAD
}
}
impl<F: Float> AngleUnbounded<F> {
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn to_bounded(self) -> Angle<F> {
Angle::from_radians(self.radians)
}
}
impl<F: Copy> From<Angle<F>> for AngleUnbounded<F> {
#[inline]
fn from(angle: Angle<F>) -> Self {
Self::from_radians(angle.to_radians())
}
}
impl AngleUnbounded<f32> {
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn to_f64(self) -> AngleUnbounded<f64> {
let radians = f64::from(self.radians);
AngleUnbounded::from_radians(radians)
}
}
impl AngleUnbounded<f64> {
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn to_f32(self) -> AngleUnbounded<f32> {
#[allow(clippy::cast_possible_truncation)]
let radians = self.radians as f32;
AngleUnbounded::from_radians(radians)
}
}
impl From<AngleUnbounded<f64>> for AngleUnbounded<f32> {
#[inline]
fn from(value: AngleUnbounded<f64>) -> Self {
value.to_f32()
}
}
impl From<AngleUnbounded<f32>> for AngleUnbounded<f64> {
#[inline]
fn from(value: AngleUnbounded<f32>) -> Self {
value.to_f64()
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<F: crate::float::FloatMath> AngleUnbounded<F> {
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn sin(self) -> F {
self.radians.sin()
}
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn cos(self) -> F {
self.radians.cos()
}
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn tan(self) -> F {
self.radians.tan()
}
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
pub fn sin_cos(self) -> (F, F) {
self.radians.sin_cos()
}
}
impl<F: Float> Add for AngleUnbounded<F> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
Self::from_radians(self.radians + rhs.radians)
}
}
forward_ref_binop!(impl<F: Float> Add, add for AngleUnbounded<F>, AngleUnbounded<F>);
impl<F: Float> AddAssign for AngleUnbounded<F> {
#[inline]
fn add_assign(&mut self, rhs: Self) {
self.radians += rhs.radians;
}
}
forward_ref_op_assign!(impl<F: Float> AddAssign, add_assign for AngleUnbounded<F>, AngleUnbounded<F>);
impl<F: Float> Sub for AngleUnbounded<F> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
Self::from_radians(self.radians - rhs.radians)
}
}
forward_ref_binop!(impl<F: Float> Sub, sub for AngleUnbounded<F>, AngleUnbounded<F>);
impl<F: Float> SubAssign for AngleUnbounded<F> {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
self.radians -= rhs.radians;
}
}
forward_ref_op_assign!(impl<F: Float> SubAssign, sub_assign for AngleUnbounded<F>, AngleUnbounded<F>);
impl<F: Float> Mul<F> for AngleUnbounded<F> {
type Output = Self;
#[inline]
fn mul(self, rhs: F) -> Self::Output {
Self::from_radians(self.radians * rhs)
}
}
forward_ref_binop!(impl<F: Float> Mul, mul for AngleUnbounded<F>, F);
impl Mul<AngleUnbounded<f32>> for f32 {
type Output = AngleUnbounded<f32>;
#[inline]
fn mul(self, rhs: AngleUnbounded<f32>) -> Self::Output {
rhs * self
}
}
forward_ref_binop!(impl Mul, mul for f32, AngleUnbounded<f32>);
impl Mul<AngleUnbounded<f64>> for f64 {
type Output = AngleUnbounded<f64>;
#[inline]
fn mul(self, rhs: AngleUnbounded<f64>) -> Self::Output {
rhs * self
}
}
forward_ref_binop!(impl Mul, mul for f64, AngleUnbounded<f64>);
impl<F: Float> MulAssign<F> for AngleUnbounded<F> {
#[inline]
fn mul_assign(&mut self, rhs: F) {
self.radians *= rhs;
}
}
forward_ref_op_assign!(impl<F: Float> MulAssign, mul_assign for AngleUnbounded<F>, F);
impl<F: Float> Div<F> for AngleUnbounded<F> {
type Output = Self;
#[inline]
fn div(self, rhs: F) -> Self::Output {
Self::from_radians(self.radians / rhs)
}
}
forward_ref_binop!(impl<F: Float> Div, div for AngleUnbounded<F>, F);
impl<F: Float> DivAssign<F> for AngleUnbounded<F> {
#[inline]
fn div_assign(&mut self, rhs: F) {
self.radians /= rhs;
}
}
forward_ref_op_assign!(impl<F: Float> DivAssign, div_assign for AngleUnbounded<F>, F);
impl<F: Float> Neg for AngleUnbounded<F> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Self::from_radians(-self.radians)
}
}
forward_ref_unop!(impl<F: Float> Neg, neg for AngleUnbounded<F>);
impl<F: Sum> Sum for AngleUnbounded<F> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
AngleUnbounded::from_radians(iter.map(|x| x.radians).sum())
}
}
#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
use crate::AngleUnbounded32;
#[test]
fn angle_unbounded_sum_is_accurate() {
const ANGLES: [f32; 20] = [
-1.093_766_9,
-2.507_797_2,
-1.995_534_5,
-0.704_018_65,
0.601_837_7,
-1.887_757_9,
0.630_587_64,
-0.860_579_43,
2.683_119,
0.664_140_76,
0.018_360_304,
0.041_261_05,
2.733_847_6,
2.532_730_3,
-3.082_243_2,
-1.973_592_4,
2.883_761_2,
0.876_528_8,
-1.492_470_1,
-1.600_921_4,
];
let angles = ANGLES.map(AngleUnbounded32::from_radians);
let sum: AngleUnbounded32 = angles.iter().copied().sum();
let add = angles.iter().fold(AngleUnbounded32::ZERO, |a, b| a + b);
assert_float_eq!(sum.to_radians(), add.to_radians(), abs <= 1e-5);
}
}