1use core::fmt::Debug;
2use core::iter::Sum;
3use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
4
5use crate::float::Float;
6use crate::macros::{forward_ref_binop, forward_ref_op_assign, forward_ref_unop};
7use crate::AngleUnbounded;
8
9#[derive(Copy, Clone, PartialEq, Eq, Hash)]
51#[repr(transparent)]
52pub struct Angle<F> {
53 radians: F,
54}
55
56impl<F: Float> Angle<F> {
61 pub const ZERO: Self = Angle::from_radians_unchecked(F::ZERO);
63
64 pub const EPSILON: Self = Angle::from_radians_unchecked(F::DOUBLE_EPSILON);
68}
69
70impl<F: Float> Angle<F> {
71 pub const RAD_PI: Self = Angle::from_radians_unchecked(F::PI);
73 pub const RAD_FRAC_PI_2: Self = Angle::from_radians_unchecked(F::FRAC_PI_2);
75 pub const RAD_FRAC_PI_3: Self = Angle::from_radians_unchecked(F::FRAC_PI_3);
77 pub const RAD_FRAC_PI_4: Self = Angle::from_radians_unchecked(F::FRAC_PI_4);
79 pub const RAD_FRAC_PI_6: Self = Angle::from_radians_unchecked(F::FRAC_PI_6);
81 pub const RAD_FRAC_PI_8: Self = Angle::from_radians_unchecked(F::FRAC_PI_8);
83}
84
85impl<F: Float> Angle<F> {
86 pub const DEG_180: Self = Self::RAD_PI;
88 pub const DEG_90: Self = Self::RAD_FRAC_PI_2;
90 pub const DEG_60: Self = Self::RAD_FRAC_PI_3;
92 pub const DEG_45: Self = Self::RAD_FRAC_PI_4;
94 pub const DEG_30: Self = Self::RAD_FRAC_PI_6;
96 pub const DEG_22_5: Self = Self::RAD_FRAC_PI_8;
98}
99
100impl<F: Float> Angle<F> {
101 pub const HALF: Self = Self::RAD_PI;
103 pub const QUARTER: Self = Self::RAD_FRAC_PI_2;
105 pub const SIXTH: Self = Self::RAD_FRAC_PI_3;
107 pub const EIGHTH: Self = Self::RAD_FRAC_PI_4;
109 pub const TWELFTH: Self = Self::RAD_FRAC_PI_6;
111 pub const SIXTEENTH: Self = Self::RAD_FRAC_PI_8;
113}
114
115impl<F: Float> Angle<F> {
116 pub const GRAD_200: Self = Self::RAD_PI;
118 pub const GRAD_100: Self = Self::RAD_FRAC_PI_2;
120 pub const GRAD_66_6: Self = Self::RAD_FRAC_PI_3;
122 pub const GRAD_50: Self = Self::RAD_FRAC_PI_4;
124 pub const GRAD_33_3: Self = Self::RAD_FRAC_PI_6;
126 pub const GRAD_25: Self = Self::RAD_FRAC_PI_8;
128}
129
130impl<F: Debug> Debug for Angle<F> {
135 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136 f.debug_tuple("Angle").field(&self.radians).finish()
137 }
138}
139
140impl<F: Float> Default for Angle<F> {
141 #[inline]
142 fn default() -> Self {
143 Self::ZERO
144 }
145}
146
147impl<F> Angle<F> {
152 #[inline]
155 pub const fn from_radians_unchecked(radians: F) -> Self {
156 Self { radians }
157 }
158}
159
160impl<F: Float> Angle<F> {
161 #[inline]
164 fn from_radians_partially_unchecked(radians: F) -> Self {
165 debug_assert!(radians.is_nan() || (-F::TAU <= radians && radians <= F::TAU));
166 let radians = if radians > F::PI {
167 radians - F::TAU
168 } else if radians <= -F::PI {
169 radians + F::TAU
170 } else {
171 radians
172 };
173 Self::from_radians_unchecked(radians)
174 }
175
176 #[inline]
178 pub fn from_radians(radians: F) -> Self {
179 Self::from_radians_partially_unchecked(radians % F::TAU)
180 }
181
182 #[inline]
184 pub fn from_degrees(degrees: F) -> Self {
185 Self::from_radians(degrees * F::DEG_TO_RAD)
186 }
187
188 #[inline]
190 pub fn from_turns(turns: F) -> Self {
191 Self::from_radians_partially_unchecked((turns % F::ONE) * F::TURNS_TO_RAD)
194 }
195
196 #[inline]
198 pub fn from_gradians(gradians: F) -> Self {
199 Self::from_radians(gradians * F::GRAD_TO_RAD)
200 }
201}
202
203impl<F: Copy> Angle<F> {
208 #[must_use = "this returns the result of the operation, without modifying the original"]
212 #[inline]
213 pub const fn to_radians(self) -> F {
214 self.radians
215 }
216}
217
218impl<F: Float> Angle<F> {
219 #[must_use = "this returns the result of the operation, without modifying the original"]
223 #[inline]
224 pub fn to_degrees(self) -> F {
225 self.radians * F::RAD_TO_DEG
226 }
227
228 #[must_use = "this returns the result of the operation, without modifying the original"]
232 #[inline]
233 pub fn to_turns(self) -> F {
234 self.radians * F::RAD_TO_TURNS
235 }
236
237 #[must_use = "this returns the result of the operation, without modifying the original"]
241 #[inline]
242 pub fn to_gradians(self) -> F {
243 self.radians * F::RAD_TO_GRAD
244 }
245}
246
247impl<F: Float> Angle<F> {
248 #[must_use]
252 #[inline]
253 pub fn is_nan(self) -> bool {
254 self.radians.is_nan()
255 }
256}
257
258impl<F: Copy> Angle<F> {
263 #[must_use = "this returns the result of the operation, without modifying the original"]
265 #[inline]
266 pub const fn to_unbounded(self) -> AngleUnbounded<F> {
267 AngleUnbounded::from_radians(self.radians)
268 }
269}
270
271impl<F: Float> From<AngleUnbounded<F>> for Angle<F> {
272 #[inline]
273 fn from(angle: AngleUnbounded<F>) -> Self {
274 Self::from_radians(angle.to_radians())
275 }
276}
277
278impl Angle<f32> {
283 #[must_use = "this returns the result of the operation, without modifying the original"]
285 #[inline]
286 pub fn to_f64(self) -> Angle<f64> {
287 let radians = f64::from(self.radians);
288 debug_assert!(
289 radians.is_nan()
290 || (-core::f64::consts::PI < radians && radians <= core::f64::consts::PI)
291 );
292 Angle::from_radians_unchecked(radians)
294 }
295}
296
297impl Angle<f64> {
298 #[must_use = "this returns the result of the operation, without modifying the original"]
300 #[inline]
301 pub fn to_f32(self) -> Angle<f32> {
302 #[allow(clippy::cast_possible_truncation)]
303 let radians = self.radians as f32;
304 Angle::from_radians(radians)
305 }
306}
307
308impl From<Angle<f64>> for Angle<f32> {
309 #[inline]
310 fn from(value: Angle<f64>) -> Self {
311 value.to_f32()
312 }
313}
314
315impl From<Angle<f32>> for Angle<f64> {
316 #[inline]
317 fn from(value: Angle<f32>) -> Self {
318 value.to_f64()
319 }
320}
321
322#[cfg(any(feature = "std", feature = "libm"))]
327impl<F: crate::float::FloatMath> Angle<F> {
328 #[must_use = "this returns the result of the operation, without modifying the original"]
330 #[inline]
331 pub fn sin(self) -> F {
332 self.radians.sin()
333 }
334
335 #[must_use = "this returns the result of the operation, without modifying the original"]
337 #[inline]
338 pub fn cos(self) -> F {
339 self.radians.cos()
340 }
341
342 #[must_use = "this returns the result of the operation, without modifying the original"]
344 #[inline]
345 pub fn tan(self) -> F {
346 self.radians.tan()
347 }
348
349 #[must_use = "this returns the result of the operation, without modifying the original"]
351 #[inline]
352 pub fn sin_cos(self) -> (F, F) {
353 self.radians.sin_cos()
354 }
355}
356
357impl<F: Float> Add for Angle<F> {
362 type Output = Self;
363
364 #[inline]
365 fn add(self, rhs: Self) -> Self::Output {
366 Self::from_radians(self.radians + rhs.radians)
367 }
368}
369
370forward_ref_binop!(impl<F: Float> Add, add for Angle<F>, Angle<F>);
371
372impl<F: Float> AddAssign for Angle<F> {
373 #[inline]
374 fn add_assign(&mut self, rhs: Self) {
375 *self = *self + rhs;
376 }
377}
378
379forward_ref_op_assign!(impl<F: Float> AddAssign, add_assign for Angle<F>, Angle<F>);
380
381impl<F: Float> Sub for Angle<F> {
382 type Output = Self;
383
384 #[inline]
385 fn sub(self, rhs: Self) -> Self::Output {
386 Self::from_radians(self.radians - rhs.radians)
387 }
388}
389
390forward_ref_binop!(impl<F: Float> Sub, sub for Angle<F>, Angle<F>);
391
392impl<F: Float> SubAssign for Angle<F> {
393 #[inline]
394 fn sub_assign(&mut self, rhs: Self) {
395 *self = *self - rhs;
396 }
397}
398
399forward_ref_op_assign!(impl<F: Float> SubAssign, sub_assign for Angle<F>, Angle<F>);
400
401impl<F: Float> Mul<F> for Angle<F> {
402 type Output = Self;
403
404 #[inline]
405 fn mul(self, rhs: F) -> Self::Output {
406 Self::from_radians(self.radians * rhs)
407 }
408}
409
410forward_ref_binop!(impl<F: Float> Mul, mul for Angle<F>, F);
411
412impl Mul<Angle<f32>> for f32 {
413 type Output = Angle<f32>;
414
415 #[inline]
416 fn mul(self, rhs: Angle<f32>) -> Self::Output {
417 rhs * self
418 }
419}
420
421forward_ref_binop!(impl Mul, mul for f32, Angle<f32>);
422
423impl Mul<Angle<f64>> for f64 {
424 type Output = Angle<f64>;
425
426 #[inline]
427 fn mul(self, rhs: Angle<f64>) -> Self::Output {
428 rhs * self
429 }
430}
431
432forward_ref_binop!(impl Mul, mul for f64, Angle<f64>);
433
434impl<F: Float> MulAssign<F> for Angle<F> {
435 #[inline]
436 fn mul_assign(&mut self, rhs: F) {
437 *self = *self * rhs;
438 }
439}
440
441forward_ref_op_assign!(impl<F: Float> MulAssign, mul_assign for Angle<F>, F);
442
443impl<F: Float> Div<F> for Angle<F> {
444 type Output = Self;
445
446 #[inline]
447 fn div(self, rhs: F) -> Self::Output {
448 Self::from_radians(self.radians / rhs)
449 }
450}
451
452forward_ref_binop!(impl<F: Float> Div, div for Angle<F>, F);
453
454impl<F: Float> DivAssign<F> for Angle<F> {
455 #[inline]
456 fn div_assign(&mut self, rhs: F) {
457 *self = *self / rhs;
458 }
459}
460
461forward_ref_op_assign!(impl<F: Float> DivAssign, div_assign for Angle<F>, F);
462
463impl<F: Float> Neg for Angle<F> {
464 type Output = Self;
465
466 #[inline]
467 fn neg(self) -> Self::Output {
468 debug_assert!(self.radians.is_nan() || (-F::PI < self.radians && self.radians <= F::PI));
469 if self.radians == F::PI {
470 self
471 } else {
472 Self::from_radians_unchecked(-self.radians)
473 }
474 }
475}
476
477forward_ref_unop!(impl<F: Float> Neg, neg for Angle<F>);
478
479impl<F: Float + Sum> Sum for Angle<F> {
480 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
481 Angle::from_radians(iter.map(|x| x.radians).sum())
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use float_eq::assert_float_eq;
488
489 use crate::{Angle, Angle32};
490
491 #[test]
492 fn angle_pi_eq_neg_pi() {
493 assert_eq!(
494 Angle::from_radians(core::f32::consts::PI),
495 Angle::from_radians(-core::f32::consts::PI),
496 );
497 }
498
499 #[test]
500 fn angle_sum_is_accurate() {
501 const ANGLES: [f32; 20] = [
502 0.711_889,
503 0.612_456_56,
504 -1.165_211_3,
505 -1.452_307,
506 -0.587_785_5,
507 0.593_006_5,
508 0.012_860_533,
509 -1.142_349_6,
510 -1.302_776_9,
511 0.510_909_6,
512 2.365_249_2,
513 1.743_016_8,
514 1.165_635_7,
515 -2.191_822_8,
516 2.505_914_4,
517 0.498_677,
518 2.496_595,
519 -0.108_386_315,
520 0.991_436_9,
521 -2.835_525_5,
522 ];
523
524 let angles = ANGLES.map(Angle32::from_radians);
525
526 let sum: Angle32 = angles.iter().copied().sum();
527 let add = angles.iter().copied().fold(Angle::ZERO, |a, b| a + b);
528
529 assert_float_eq!(sum.to_radians(), add.to_radians(), abs <= 1e-5);
530 }
531
532 #[test]
533 fn angle_from_nan_is_nan() {
534 macro_rules! test {
535 (
536 $($nan:expr),*
537 ) => {
538 $(
539 assert!(Angle::from_radians($nan).is_nan());
540 assert!(Angle::from_degrees($nan).is_nan());
541 assert!(Angle::from_turns($nan).is_nan());
542 assert!(Angle::from_gradians($nan).is_nan());
543 )*
544 };
545 }
546 test!(f32::NAN, f64::NAN);
547 }
548
549 #[test]
550 fn angle_from_infinity_is_nan() {
551 macro_rules! test {
552 (
553 $($inf:expr),*
554 ) => {
555 $(
556 assert!(Angle::from_radians($inf).is_nan());
557 assert!(Angle::from_degrees($inf).is_nan());
558 assert!(Angle::from_turns($inf).is_nan());
559 assert!(Angle::from_gradians($inf).is_nan());
560 )*
561 };
562 }
563 test!(
564 f32::INFINITY,
565 f32::NEG_INFINITY,
566 f64::INFINITY,
567 f64::NEG_INFINITY
568 );
569 }
570
571 #[test]
572 fn angle_from_big_value_is_not_nan() {
573 macro_rules! test {
574 (
575 $($big_value:expr),*
576 ) => {
577 $(
578 assert!(!Angle::from_radians($big_value).is_nan());
579 assert!(!Angle::from_degrees($big_value).is_nan());
580 assert!(!Angle::from_turns($big_value).is_nan());
581 assert!(!Angle::from_gradians($big_value).is_nan());
582 )*
583 };
584 }
585 test!(f32::MAX, f32::MIN, f64::MAX, f64::MIN);
586 }
587}