zng_unit/
angle.rs

1use crate::{about_eq_hash, about_eq_ord};
2
3use super::{EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, about_eq};
4
5use std::{
6    f32::consts::{PI, TAU},
7    fmt, ops,
8};
9
10/// Angle in radians.
11///
12/// See [`AngleUnits`] for more details.
13///
14/// # Equality
15///
16/// Equality is determined using [`about_eq`] with `0.00001` granularity.
17#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
18#[serde(transparent)]
19pub struct AngleRadian(pub f32);
20impl ops::Add for AngleRadian {
21    type Output = Self;
22
23    fn add(self, rhs: Self) -> Self::Output {
24        Self(self.0 + rhs.0)
25    }
26}
27impl ops::AddAssign for AngleRadian {
28    fn add_assign(&mut self, rhs: Self) {
29        self.0 += rhs.0;
30    }
31}
32impl ops::Sub for AngleRadian {
33    type Output = Self;
34
35    fn sub(self, rhs: Self) -> Self::Output {
36        Self(self.0 - rhs.0)
37    }
38}
39impl ops::SubAssign for AngleRadian {
40    fn sub_assign(&mut self, rhs: Self) {
41        self.0 -= rhs.0;
42    }
43}
44impl ops::Neg for AngleRadian {
45    type Output = Self;
46
47    fn neg(self) -> Self::Output {
48        Self(-self.0)
49    }
50}
51impl AngleRadian {
52    /// Radians in `[0.0 ..= TAU]`.
53    pub fn modulo(self) -> Self {
54        AngleRadian(self.0.rem_euclid(TAU))
55    }
56
57    /// Linear interpolation.
58    pub fn lerp(self, to: Self, factor: Factor) -> Self {
59        Self(lerp(self.0, to.0, factor))
60    }
61
62    /// Spherical linear interpolation.
63    ///
64    /// Always uses the shortest path from `self` to `to`.
65    ///
66    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
67    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
68    /// normalized.
69    ///
70    /// [`lerp`]: Self::lerp
71    pub fn slerp(self, to: Self, factor: Factor) -> Self {
72        Self(slerp(self.0, to.0, TAU, factor))
73    }
74}
75
76impl PartialEq for AngleRadian {
77    fn eq(&self, other: &Self) -> bool {
78        about_eq(self.0, other.0, EQ_GRANULARITY)
79    }
80}
81impl Eq for AngleRadian {}
82impl std::hash::Hash for AngleRadian {
83    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
84        about_eq_hash(self.0, EQ_GRANULARITY, state);
85    }
86}
87impl Ord for AngleRadian {
88    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
89        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
90    }
91}
92impl PartialOrd for AngleRadian {
93    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
94        Some(self.cmp(other))
95    }
96}
97
98impl From<AngleGradian> for AngleRadian {
99    fn from(grad: AngleGradian) -> Self {
100        AngleRadian(grad.0 * PI / 200.0)
101    }
102}
103impl From<AngleDegree> for AngleRadian {
104    fn from(deg: AngleDegree) -> Self {
105        AngleRadian(deg.0.to_radians())
106    }
107}
108impl From<AngleTurn> for AngleRadian {
109    fn from(turn: AngleTurn) -> Self {
110        AngleRadian(turn.0 * TAU)
111    }
112}
113impl From<AngleRadian> for euclid::Angle<f32> {
114    fn from(rad: AngleRadian) -> Self {
115        euclid::Angle::radians(rad.0)
116    }
117}
118
119impl fmt::Debug for AngleRadian {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        if f.alternate() {
122            f.debug_tuple("AngleRadian").field(&self.0).finish()
123        } else {
124            write!(f, "{}.rad()", self.0)
125        }
126    }
127}
128impl fmt::Display for AngleRadian {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "{} rad", self.0)
131    }
132}
133
134/// Angle in gradians.
135///
136/// See [`AngleUnits`] for more details.
137///
138/// # Equality
139///
140/// Equality is determined using [`about_eq`] with `0.001` granularity.
141#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
142#[serde(transparent)]
143pub struct AngleGradian(pub f32);
144impl AngleGradian {
145    /// Gradians in `[0.0 ..= 400.0]`.
146    pub fn modulo(self) -> Self {
147        AngleGradian(self.0.rem_euclid(400.0))
148    }
149
150    /// Linear interpolation.
151    pub fn lerp(self, to: Self, factor: Factor) -> Self {
152        Self(lerp(self.0, to.0, factor))
153    }
154
155    /// Spherical linear interpolation.
156    ///
157    /// Always uses the shortest path from `self` to `to`.
158    ///
159    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
160    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
161    /// normalized.
162    ///
163    /// [`lerp`]: Self::lerp
164    pub fn slerp(self, to: Self, factor: Factor) -> Self {
165        Self(slerp(self.0, to.0, 400.0, factor))
166    }
167}
168impl ops::Add for AngleGradian {
169    type Output = Self;
170
171    fn add(self, rhs: Self) -> Self::Output {
172        Self(self.0 + rhs.0)
173    }
174}
175impl ops::AddAssign for AngleGradian {
176    fn add_assign(&mut self, rhs: Self) {
177        self.0 += rhs.0;
178    }
179}
180impl ops::Sub for AngleGradian {
181    type Output = Self;
182
183    fn sub(self, rhs: Self) -> Self::Output {
184        Self(self.0 - rhs.0)
185    }
186}
187impl ops::SubAssign for AngleGradian {
188    fn sub_assign(&mut self, rhs: Self) {
189        self.0 -= rhs.0;
190    }
191}
192impl ops::Neg for AngleGradian {
193    type Output = Self;
194
195    fn neg(self) -> Self::Output {
196        Self(-self.0)
197    }
198}
199impl Ord for AngleGradian {
200    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
201        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
202    }
203}
204impl PartialOrd for AngleGradian {
205    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
206        Some(self.cmp(other))
207    }
208}
209
210impl PartialEq for AngleGradian {
211    fn eq(&self, other: &Self) -> bool {
212        about_eq(self.0, other.0, EQ_GRANULARITY_100)
213    }
214}
215impl Eq for AngleGradian {}
216impl std::hash::Hash for AngleGradian {
217    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
218        about_eq_hash(self.0, EQ_GRANULARITY_100, state);
219    }
220}
221impl From<AngleRadian> for AngleGradian {
222    fn from(rad: AngleRadian) -> Self {
223        AngleGradian(rad.0 * 200.0 / PI)
224    }
225}
226impl From<AngleDegree> for AngleGradian {
227    fn from(deg: AngleDegree) -> Self {
228        AngleGradian(deg.0 * 10.0 / 9.0)
229    }
230}
231impl From<AngleTurn> for AngleGradian {
232    fn from(turn: AngleTurn) -> Self {
233        AngleGradian(turn.0 * 400.0)
234    }
235}
236impl fmt::Debug for AngleGradian {
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        if f.alternate() {
239            f.debug_tuple("AngleGradian").field(&self.0).finish()
240        } else {
241            write!(f, "{}.grad()", self.0)
242        }
243    }
244}
245impl fmt::Display for AngleGradian {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        write!(f, "{} gon", self.0)
248    }
249}
250
251/// Angle in degrees.
252///
253/// See [`AngleUnits`] for more details.
254///
255/// # Equality
256///
257/// Equality is determined using [`about_eq`] with `0.001` granularity.
258#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
259#[serde(transparent)]
260pub struct AngleDegree(pub f32);
261impl AngleDegree {
262    /// Degrees in `[0.0 ..= 360.0]`.
263    pub fn modulo(self) -> Self {
264        AngleDegree(self.0.rem_euclid(360.0))
265    }
266
267    /// Linear interpolation.
268    pub fn lerp(self, to: Self, factor: Factor) -> Self {
269        Self(lerp(self.0, to.0, factor))
270    }
271
272    /// Spherical linear interpolation.
273    ///
274    /// Always uses the shortest path from `self` to `to`.
275    ///
276    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
277    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
278    /// normalized.
279    ///
280    /// [`lerp`]: Self::lerp
281    pub fn slerp(self, to: Self, factor: Factor) -> Self {
282        Self(slerp(self.0, to.0, 360.0, factor))
283    }
284}
285impl ops::Add for AngleDegree {
286    type Output = Self;
287
288    fn add(self, rhs: Self) -> Self::Output {
289        Self(self.0 + rhs.0)
290    }
291}
292impl ops::AddAssign for AngleDegree {
293    fn add_assign(&mut self, rhs: Self) {
294        self.0 += rhs.0;
295    }
296}
297impl ops::Sub for AngleDegree {
298    type Output = Self;
299
300    fn sub(self, rhs: Self) -> Self::Output {
301        Self(self.0 - rhs.0)
302    }
303}
304impl ops::SubAssign for AngleDegree {
305    fn sub_assign(&mut self, rhs: Self) {
306        self.0 -= rhs.0;
307    }
308}
309impl ops::Neg for AngleDegree {
310    type Output = Self;
311
312    fn neg(self) -> Self::Output {
313        Self(-self.0)
314    }
315}
316
317impl PartialEq for AngleDegree {
318    fn eq(&self, other: &Self) -> bool {
319        about_eq(self.0, other.0, EQ_GRANULARITY_100)
320    }
321}
322impl Eq for AngleDegree {}
323impl Ord for AngleDegree {
324    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
325        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
326    }
327}
328impl PartialOrd for AngleDegree {
329    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
330        Some(self.cmp(other))
331    }
332}
333impl std::hash::Hash for AngleDegree {
334    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
335        about_eq_hash(self.0, EQ_GRANULARITY_100, state);
336    }
337}
338impl From<AngleRadian> for AngleDegree {
339    fn from(rad: AngleRadian) -> Self {
340        AngleDegree(rad.0.to_degrees())
341    }
342}
343impl From<AngleGradian> for AngleDegree {
344    fn from(grad: AngleGradian) -> Self {
345        AngleDegree(grad.0 * 9.0 / 10.0)
346    }
347}
348impl From<AngleTurn> for AngleDegree {
349    fn from(turn: AngleTurn) -> Self {
350        AngleDegree(turn.0 * 360.0)
351    }
352}
353impl fmt::Debug for AngleDegree {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        if f.alternate() {
356            f.debug_tuple("AngleDegree").field(&self.0).finish()
357        } else {
358            write!(f, "{}.deg()", self.0)
359        }
360    }
361}
362impl fmt::Display for AngleDegree {
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        write!(f, "{}º", self.0)
365    }
366}
367
368/// Angle in turns (complete rotations).
369///
370/// See [`AngleUnits`] for more details.
371///
372/// # Equality
373///
374/// Equality is determined using [`about_eq`] with `0.00001` granularity.
375#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
376#[serde(transparent)]
377pub struct AngleTurn(pub f32);
378impl AngleTurn {
379    /// Turns in `[0.0 ..= 1.0]`.
380    pub fn modulo(self) -> Self {
381        AngleTurn(self.0.rem_euclid(1.0))
382    }
383
384    /// Linear interpolation.
385    pub fn lerp(self, to: Self, factor: Factor) -> Self {
386        Self(lerp(self.0, to.0, factor))
387    }
388
389    /// Spherical linear interpolation.
390    ///
391    /// Always uses the shortest path from `self` to `to`.
392    ///
393    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
394    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
395    /// normalized.
396    ///
397    /// [`lerp`]: Self::lerp
398    pub fn slerp(self, to: Self, factor: Factor) -> Self {
399        Self(slerp(self.0, to.0, 1.0, factor))
400    }
401}
402impl ops::Add for AngleTurn {
403    type Output = Self;
404
405    fn add(self, rhs: Self) -> Self::Output {
406        Self(self.0 + rhs.0)
407    }
408}
409impl ops::AddAssign for AngleTurn {
410    fn add_assign(&mut self, rhs: Self) {
411        self.0 += rhs.0;
412    }
413}
414impl ops::Sub for AngleTurn {
415    type Output = Self;
416
417    fn sub(self, rhs: Self) -> Self::Output {
418        Self(self.0 - rhs.0)
419    }
420}
421impl ops::SubAssign for AngleTurn {
422    fn sub_assign(&mut self, rhs: Self) {
423        self.0 -= rhs.0;
424    }
425}
426impl ops::Neg for AngleTurn {
427    type Output = Self;
428
429    fn neg(self) -> Self::Output {
430        Self(-self.0)
431    }
432}
433
434impl fmt::Debug for AngleTurn {
435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436        if f.alternate() {
437            f.debug_tuple("AngleTurn").field(&self.0).finish()
438        } else {
439            write!(f, "{}.turn()", self.0)
440        }
441    }
442}
443impl fmt::Display for AngleTurn {
444    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445        if (self.0 - 1.0).abs() < 0.0001 {
446            write!(f, "1 turn")
447        } else {
448            write!(f, "{} turns", self.0)
449        }
450    }
451}
452impl PartialEq for AngleTurn {
453    fn eq(&self, other: &Self) -> bool {
454        about_eq(self.0, other.0, EQ_GRANULARITY)
455    }
456}
457impl Eq for AngleTurn {}
458impl Ord for AngleTurn {
459    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
460        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
461    }
462}
463impl PartialOrd for AngleTurn {
464    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
465        Some(self.cmp(other))
466    }
467}
468impl std::hash::Hash for AngleTurn {
469    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
470        about_eq_hash(self.0, EQ_GRANULARITY, state);
471    }
472}
473
474impl From<AngleRadian> for AngleTurn {
475    fn from(rad: AngleRadian) -> Self {
476        AngleTurn(rad.0 / TAU)
477    }
478}
479impl From<AngleGradian> for AngleTurn {
480    fn from(grad: AngleGradian) -> Self {
481        AngleTurn(grad.0 / 400.0)
482    }
483}
484impl From<AngleDegree> for AngleTurn {
485    fn from(deg: AngleDegree) -> Self {
486        AngleTurn(deg.0 / 360.0)
487    }
488}
489
490/// Extension methods for initializing angle units.
491///
492/// This trait is implemented for [`f32`] and [`u32`] allowing initialization of angle unit types using the `<number>.<unit>()` syntax.
493///
494/// # Examples
495///
496/// ```
497/// # use zng_unit::*;
498/// let radians = 6.28318.rad();
499/// let gradians = 400.grad();
500/// let degrees = 360.deg();
501/// let turns = 1.turn();
502/// ```
503pub trait AngleUnits {
504    /// Radians
505    fn rad(self) -> AngleRadian;
506    /// Gradians
507    fn grad(self) -> AngleGradian;
508    /// Degrees
509    fn deg(self) -> AngleDegree;
510    /// Turns
511    fn turn(self) -> AngleTurn;
512}
513impl AngleUnits for f32 {
514    fn rad(self) -> AngleRadian {
515        AngleRadian(self)
516    }
517
518    fn grad(self) -> AngleGradian {
519        AngleGradian(self)
520    }
521
522    fn deg(self) -> AngleDegree {
523        AngleDegree(self)
524    }
525
526    fn turn(self) -> AngleTurn {
527        AngleTurn(self)
528    }
529}
530impl AngleUnits for i32 {
531    fn rad(self) -> AngleRadian {
532        AngleRadian(self as f32)
533    }
534
535    fn grad(self) -> AngleGradian {
536        AngleGradian(self as f32)
537    }
538
539    fn deg(self) -> AngleDegree {
540        AngleDegree(self as f32)
541    }
542
543    fn turn(self) -> AngleTurn {
544        AngleTurn(self as f32)
545    }
546}
547
548fn lerp(from: f32, to: f32, factor: Factor) -> f32 {
549    from + (to - from) * factor.0
550}
551
552fn slerp(from: f32, to: f32, turn: f32, factor: Factor) -> f32 {
553    let angle_to = {
554        let d = (to - from) % turn;
555        2.0 * d % turn - d
556    };
557    from + angle_to * factor.0
558}