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::Angle;
8
9#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash)]
27#[repr(transparent)]
28pub struct AngleUnbounded<F> {
29 radians: F,
30}
31
32impl<F: Float> AngleUnbounded<F> {
37 pub const ZERO: Self = AngleUnbounded::from_radians(F::ZERO);
39
40 pub const EPSILON: Self = AngleUnbounded::from_radians(F::DOUBLE_EPSILON);
44}
45
46impl<F: Float> AngleUnbounded<F> {
47 pub const RAD_PI: Self = AngleUnbounded::from_radians(F::PI);
49 pub const RAD_FRAC_PI_2: Self = AngleUnbounded::from_radians(F::FRAC_PI_2);
51 pub const RAD_FRAC_PI_3: Self = AngleUnbounded::from_radians(F::FRAC_PI_3);
53 pub const RAD_FRAC_PI_4: Self = AngleUnbounded::from_radians(F::FRAC_PI_4);
55 pub const RAD_FRAC_PI_6: Self = AngleUnbounded::from_radians(F::FRAC_PI_6);
57 pub const RAD_FRAC_PI_8: Self = AngleUnbounded::from_radians(F::FRAC_PI_8);
59}
60
61impl<F: Float> AngleUnbounded<F> {
62 pub const DEG_180: Self = Self::RAD_PI;
64 pub const DEG_90: Self = Self::RAD_FRAC_PI_2;
66 pub const DEG_60: Self = Self::RAD_FRAC_PI_3;
68 pub const DEG_45: Self = Self::RAD_FRAC_PI_4;
70 pub const DEG_30: Self = Self::RAD_FRAC_PI_6;
72 pub const DEG_22_5: Self = Self::RAD_FRAC_PI_8;
74}
75
76impl<F: Float> AngleUnbounded<F> {
77 pub const HALF: Self = Self::RAD_PI;
79 pub const QUARTER: Self = Self::RAD_FRAC_PI_2;
81 pub const SIXTH: Self = Self::RAD_FRAC_PI_3;
83 pub const EIGHTH: Self = Self::RAD_FRAC_PI_4;
85 pub const TWELFTH: Self = Self::RAD_FRAC_PI_6;
87 pub const SIXTEENTH: Self = Self::RAD_FRAC_PI_8;
89}
90
91impl<F: Float> AngleUnbounded<F> {
92 pub const GRAD_200: Self = Self::RAD_PI;
94 pub const GRAD_100: Self = Self::RAD_FRAC_PI_2;
96 pub const GRAD_66_6: Self = Self::RAD_FRAC_PI_3;
98 pub const GRAD_50: Self = Self::RAD_FRAC_PI_4;
100 pub const GRAD_33_3: Self = Self::RAD_FRAC_PI_6;
102 pub const GRAD_25: Self = Self::RAD_FRAC_PI_8;
104}
105
106impl<F: Debug> Debug for AngleUnbounded<F> {
111 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112 f.debug_tuple("AngleUnbounded")
113 .field(&self.radians)
114 .finish()
115 }
116}
117
118impl<F: Float> Default for AngleUnbounded<F> {
119 #[inline]
120 fn default() -> Self {
121 Self::ZERO
122 }
123}
124
125impl<F> AngleUnbounded<F> {
130 #[inline]
132 pub const fn from_radians(radians: F) -> Self {
133 Self { radians }
134 }
135}
136
137impl<F: Float> AngleUnbounded<F> {
138 #[inline]
140 pub fn from_degrees(degrees: F) -> Self {
141 Self::from_radians(degrees * F::DEG_TO_RAD)
142 }
143
144 #[inline]
146 pub fn from_turns(turns: F) -> Self {
147 Self::from_radians(turns * F::TURNS_TO_RAD)
148 }
149
150 #[inline]
152 pub fn from_gradians(gradians: F) -> Self {
153 Self::from_radians(gradians * F::GRAD_TO_RAD)
154 }
155}
156
157impl<F: Copy> AngleUnbounded<F> {
162 #[must_use = "this returns the result of the operation, without modifying the original"]
164 #[inline]
165 pub const fn to_radians(self) -> F {
166 self.radians
167 }
168}
169
170impl<F: Float> AngleUnbounded<F> {
171 #[must_use = "this returns the result of the operation, without modifying the original"]
173 #[inline]
174 pub fn to_degrees(self) -> F {
175 self.radians * F::RAD_TO_DEG
176 }
177
178 #[must_use = "this returns the result of the operation, without modifying the original"]
180 #[inline]
181 pub fn to_turns(self) -> F {
182 self.radians * F::RAD_TO_TURNS
183 }
184
185 #[must_use = "this returns the result of the operation, without modifying the original"]
187 #[inline]
188 pub fn to_gradians(self) -> F {
189 self.radians * F::RAD_TO_GRAD
190 }
191}
192
193impl<F: Float> AngleUnbounded<F> {
198 #[must_use = "this returns the result of the operation, without modifying the original"]
200 #[inline]
201 pub fn to_bounded(self) -> Angle<F> {
202 Angle::from_radians(self.radians)
203 }
204}
205
206impl<F: Copy> From<Angle<F>> for AngleUnbounded<F> {
207 #[inline]
208 fn from(angle: Angle<F>) -> Self {
209 Self::from_radians(angle.to_radians())
210 }
211}
212
213impl AngleUnbounded<f32> {
218 #[must_use = "this returns the result of the operation, without modifying the original"]
220 #[inline]
221 pub fn to_f64(self) -> AngleUnbounded<f64> {
222 let radians = f64::from(self.radians);
223 AngleUnbounded::from_radians(radians)
224 }
225}
226
227impl AngleUnbounded<f64> {
228 #[must_use = "this returns the result of the operation, without modifying the original"]
230 #[inline]
231 pub fn to_f32(self) -> AngleUnbounded<f32> {
232 #[allow(clippy::cast_possible_truncation)]
233 let radians = self.radians as f32;
234 AngleUnbounded::from_radians(radians)
235 }
236}
237
238impl From<AngleUnbounded<f64>> for AngleUnbounded<f32> {
239 #[inline]
240 fn from(value: AngleUnbounded<f64>) -> Self {
241 value.to_f32()
242 }
243}
244
245impl From<AngleUnbounded<f32>> for AngleUnbounded<f64> {
246 #[inline]
247 fn from(value: AngleUnbounded<f32>) -> Self {
248 value.to_f64()
249 }
250}
251
252#[cfg(any(feature = "std", feature = "libm"))]
257impl<F: crate::float::FloatMath> AngleUnbounded<F> {
258 #[must_use = "this returns the result of the operation, without modifying the original"]
260 #[inline]
261 pub fn sin(self) -> F {
262 self.radians.sin()
263 }
264
265 #[must_use = "this returns the result of the operation, without modifying the original"]
267 #[inline]
268 pub fn cos(self) -> F {
269 self.radians.cos()
270 }
271
272 #[must_use = "this returns the result of the operation, without modifying the original"]
274 #[inline]
275 pub fn tan(self) -> F {
276 self.radians.tan()
277 }
278
279 #[must_use = "this returns the result of the operation, without modifying the original"]
281 #[inline]
282 pub fn sin_cos(self) -> (F, F) {
283 self.radians.sin_cos()
284 }
285}
286
287impl<F: Float> Add for AngleUnbounded<F> {
292 type Output = Self;
293
294 #[inline]
295 fn add(self, rhs: Self) -> Self::Output {
296 Self::from_radians(self.radians + rhs.radians)
297 }
298}
299
300forward_ref_binop!(impl<F: Float> Add, add for AngleUnbounded<F>, AngleUnbounded<F>);
301
302impl<F: Float> AddAssign for AngleUnbounded<F> {
303 #[inline]
304 fn add_assign(&mut self, rhs: Self) {
305 self.radians += rhs.radians;
306 }
307}
308
309forward_ref_op_assign!(impl<F: Float> AddAssign, add_assign for AngleUnbounded<F>, AngleUnbounded<F>);
310
311impl<F: Float> Sub for AngleUnbounded<F> {
312 type Output = Self;
313
314 #[inline]
315 fn sub(self, rhs: Self) -> Self::Output {
316 Self::from_radians(self.radians - rhs.radians)
317 }
318}
319
320forward_ref_binop!(impl<F: Float> Sub, sub for AngleUnbounded<F>, AngleUnbounded<F>);
321
322impl<F: Float> SubAssign for AngleUnbounded<F> {
323 #[inline]
324 fn sub_assign(&mut self, rhs: Self) {
325 self.radians -= rhs.radians;
326 }
327}
328
329forward_ref_op_assign!(impl<F: Float> SubAssign, sub_assign for AngleUnbounded<F>, AngleUnbounded<F>);
330
331impl<F: Float> Mul<F> for AngleUnbounded<F> {
332 type Output = Self;
333
334 #[inline]
335 fn mul(self, rhs: F) -> Self::Output {
336 Self::from_radians(self.radians * rhs)
337 }
338}
339
340forward_ref_binop!(impl<F: Float> Mul, mul for AngleUnbounded<F>, F);
341
342impl Mul<AngleUnbounded<f32>> for f32 {
343 type Output = AngleUnbounded<f32>;
344
345 #[inline]
346 fn mul(self, rhs: AngleUnbounded<f32>) -> Self::Output {
347 rhs * self
348 }
349}
350
351forward_ref_binop!(impl Mul, mul for f32, AngleUnbounded<f32>);
352
353impl Mul<AngleUnbounded<f64>> for f64 {
354 type Output = AngleUnbounded<f64>;
355
356 #[inline]
357 fn mul(self, rhs: AngleUnbounded<f64>) -> Self::Output {
358 rhs * self
359 }
360}
361
362forward_ref_binop!(impl Mul, mul for f64, AngleUnbounded<f64>);
363
364impl<F: Float> MulAssign<F> for AngleUnbounded<F> {
365 #[inline]
366 fn mul_assign(&mut self, rhs: F) {
367 self.radians *= rhs;
368 }
369}
370
371forward_ref_op_assign!(impl<F: Float> MulAssign, mul_assign for AngleUnbounded<F>, F);
372
373impl<F: Float> Div<F> for AngleUnbounded<F> {
374 type Output = Self;
375
376 #[inline]
377 fn div(self, rhs: F) -> Self::Output {
378 Self::from_radians(self.radians / rhs)
379 }
380}
381
382forward_ref_binop!(impl<F: Float> Div, div for AngleUnbounded<F>, F);
383
384impl<F: Float> DivAssign<F> for AngleUnbounded<F> {
385 #[inline]
386 fn div_assign(&mut self, rhs: F) {
387 self.radians /= rhs;
388 }
389}
390
391forward_ref_op_assign!(impl<F: Float> DivAssign, div_assign for AngleUnbounded<F>, F);
392
393impl<F: Float> Neg for AngleUnbounded<F> {
394 type Output = Self;
395
396 #[inline]
397 fn neg(self) -> Self::Output {
398 Self::from_radians(-self.radians)
399 }
400}
401
402forward_ref_unop!(impl<F: Float> Neg, neg for AngleUnbounded<F>);
403
404impl<F: Sum> Sum for AngleUnbounded<F> {
405 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
406 AngleUnbounded::from_radians(iter.map(|x| x.radians).sum())
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use float_eq::assert_float_eq;
413
414 use crate::AngleUnbounded32;
415
416 #[test]
417 fn angle_unbounded_sum_is_accurate() {
418 const ANGLES: [f32; 20] = [
419 -1.093_766_9,
420 -2.507_797_2,
421 -1.995_534_5,
422 -0.704_018_65,
423 0.601_837_7,
424 -1.887_757_9,
425 0.630_587_64,
426 -0.860_579_43,
427 2.683_119,
428 0.664_140_76,
429 0.018_360_304,
430 0.041_261_05,
431 2.733_847_6,
432 2.532_730_3,
433 -3.082_243_2,
434 -1.973_592_4,
435 2.883_761_2,
436 0.876_528_8,
437 -1.492_470_1,
438 -1.600_921_4,
439 ];
440
441 let angles = ANGLES.map(AngleUnbounded32::from_radians);
442
443 let sum: AngleUnbounded32 = angles.iter().copied().sum();
444 let add = angles.iter().fold(AngleUnbounded32::ZERO, |a, b| a + b);
445
446 assert_float_eq!(sum.to_radians(), add.to_radians(), abs <= 1e-5);
447 }
448}