use std::{ f32::consts::PI, ops::{Add, Div, Mul, Neg, Rem, Sub}, }; use bevy::reflect::Reflect; use super::Precision; #[derive(Reflect, Copy, Clone, Debug, PartialEq, Eq)] pub enum AngleUnit { Degrees(T), Radians(T), } impl Default for AngleUnit { fn default() -> Self { Self::Degrees(Default::default()) } } #[derive(Reflect, Copy, Default, Clone, Debug, PartialEq, Eq)] pub struct Angle { value: AngleUnit, } impl + Default + Send + Sync + Reflect + Copy> From for Angle { fn from(i: i16) -> Self { Self { value: AngleUnit::Degrees(T::from(i)), } } } impl> Rem for Angle { type Output = Self; fn rem(self, rhs: T) -> Self::Output { match self.value { AngleUnit::Degrees(v) => Self::Output::degrees(v % rhs), AngleUnit::Radians(v) => Self::Output::radians(v % rhs), } } } impl> Mul for Angle { type Output = Self; fn mul(self, rhs: T) -> Self::Output { match self.value { AngleUnit::Degrees(v) => Self::Output::degrees(v * rhs), AngleUnit::Radians(v) => Self::Output::radians(v * rhs), } } } impl Angle { pub fn limit_smaller_than_full_circle(self) -> Self { match self.value { AngleUnit::Degrees(v) => Self { value: AngleUnit::Degrees(v % 360.), }, AngleUnit::Radians(v) => Self { value: AngleUnit::Radians(v % (2. * PI)), }, } } } impl> Div for Angle { type Output = Self; fn div(self, rhs: T) -> Self::Output { match self.value { AngleUnit::Degrees(v) => Self::Output::degrees(v / rhs), AngleUnit::Radians(v) => Self::Output::radians(v / rhs), } } } impl> Neg for Angle { type Output = Self; fn neg(self) -> Self::Output { match self.value { AngleUnit::Degrees(v) => Self::Output::degrees(-v), AngleUnit::Radians(v) => Self::Output::radians(-v), } } } impl> Neg for &Angle { type Output = Angle; fn neg(self) -> Self::Output { match self.value.clone() { AngleUnit::Degrees(v) => Self::Output::degrees(-v), AngleUnit::Radians(v) => Self::Output::radians(-v), } } } impl Angle { pub fn degrees(value: T) -> Angle { Self { value: AngleUnit::Degrees(value), } } pub fn radians(value: T) -> Angle { Self { value: AngleUnit::Radians(value), } } pub fn value(&self) -> T { match self.value.clone() { AngleUnit::Degrees(v) => v, AngleUnit::Radians(v) => v, } } } impl Angle { pub fn to_radians(self) -> Self { match self.value { AngleUnit::Degrees(v) => Self { value: AngleUnit::Radians(v.to_radians()), }, AngleUnit::Radians(_) => self, } } pub fn to_degrees(self) -> Self { match self.value { AngleUnit::Degrees(_) => self, AngleUnit::Radians(v) => Self { value: AngleUnit::Degrees(v.to_degrees()), }, } } } impl + Send + Sync + Reflect + Copy + Default + num_traits::float::Float> Add for Angle { type Output = Angle; fn add(self, rhs: Self) -> Self::Output { match (self.value, rhs.value) { (AngleUnit::Degrees(v), AngleUnit::Degrees(o)) => Self::Output { value: AngleUnit::Degrees(v + o), }, (AngleUnit::Degrees(v), AngleUnit::Radians(o)) => Self::Output { value: AngleUnit::Radians(v.to_radians() + o), }, (AngleUnit::Radians(v), AngleUnit::Degrees(o)) => Self::Output { value: AngleUnit::Radians(v + o.to_radians()), }, (AngleUnit::Radians(v), AngleUnit::Radians(o)) => Self::Output { value: AngleUnit::Radians(v + o), }, } } } impl + Default + Send + Sync + Reflect + Copy + num_traits::float::Float> Sub for Angle { type Output = Angle; fn sub(self, rhs: Self) -> Self::Output { match (self.value, rhs.value) { (AngleUnit::Degrees(v), AngleUnit::Degrees(o)) => Self::Output { value: AngleUnit::Degrees(v - o), }, (AngleUnit::Degrees(v), AngleUnit::Radians(o)) => Self::Output { value: AngleUnit::Radians(v.to_radians() - o), }, (AngleUnit::Radians(v), AngleUnit::Degrees(o)) => Self::Output { value: AngleUnit::Radians(v - o.to_radians()), }, (AngleUnit::Radians(v), AngleUnit::Radians(o)) => Self::Output { value: AngleUnit::Radians(v - o), }, } } } #[test] fn convert_to_radians() { let radi = Angle::radians(30f32.to_radians()); let degr = Angle::degrees(30f32); let converted = degr.to_radians(); assert_eq!(radi, converted) } #[test] fn sum_degrees() { let fst = Angle::degrees(30f32); let snd = Angle::degrees(30f32); let sum = fst + snd; assert!((sum.value() - 60f32).abs() < 0.0001); assert!((sum.to_radians().value() - 60f32.to_radians()).abs() < 0.0001); } #[test] fn sum_mixed() { let fst = Angle::degrees(30f32); let snd = Angle::radians(30f32.to_radians()); let sum = fst + snd; assert!((sum.to_degrees().value() - 60f32).abs() < 0.0001); assert!((sum.to_radians().value() - 60f32.to_radians()).abs() < 0.0001); }