Skip to main content

use_trigonometry/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Trigonometric utilities for `RustUse`.
5
6use core::f64::consts::{PI, TAU};
7
8/// Explicit angle value stored in radians.
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Angle {
11    radians: f64,
12}
13
14impl Angle {
15    /// Creates an angle from a radians value.
16    #[must_use]
17    pub const fn from_radians(radians: f64) -> Self {
18        Self { radians }
19    }
20
21    /// Creates an angle from a degrees value.
22    #[must_use]
23    pub const fn from_degrees(degrees: f64) -> Self {
24        Self::from_radians(degrees.to_radians())
25    }
26
27    /// Returns the angle in radians.
28    #[must_use]
29    pub const fn radians(self) -> f64 {
30        self.radians
31    }
32
33    /// Returns the angle in degrees.
34    #[must_use]
35    pub const fn degrees(self) -> f64 {
36        self.radians.to_degrees()
37    }
38
39    /// Returns the angle normalized into the interval `[0, 2π)`.
40    #[must_use]
41    pub fn normalized(self) -> Self {
42        Self::from_radians(self.radians.rem_euclid(TAU))
43    }
44
45    /// Returns the angle normalized into the interval `(-π, π]`.
46    #[must_use]
47    pub fn normalized_signed(self) -> Self {
48        let radians = self.normalized().radians();
49
50        if radians > PI {
51            Self::from_radians(radians - TAU)
52        } else {
53            Self::from_radians(radians)
54        }
55    }
56
57    /// Returns the sine of the angle.
58    #[must_use]
59    pub fn sin(self) -> f64 {
60        self.radians.sin()
61    }
62
63    /// Returns the cosine of the angle.
64    #[must_use]
65    pub fn cos(self) -> f64 {
66        self.radians.cos()
67    }
68
69    /// Returns the tangent of the angle.
70    #[must_use]
71    pub fn tan(self) -> f64 {
72        self.radians.tan()
73    }
74
75    /// Returns the sine and cosine of the angle as a pair.
76    #[must_use]
77    pub fn sin_cos(self) -> (f64, f64) {
78        self.radians.sin_cos()
79    }
80}
81
82/// Converts a degrees value into radians.
83#[must_use]
84pub const fn degrees_to_radians(degrees: f64) -> f64 {
85    degrees.to_radians()
86}
87
88/// Converts a radians value into degrees.
89#[must_use]
90pub const fn radians_to_degrees(radians: f64) -> f64 {
91    radians.to_degrees()
92}
93
94/// Normalizes a degrees value into the interval `[0, 360)`.
95#[must_use]
96pub fn normalize_degrees(degrees: f64) -> f64 {
97    degrees.rem_euclid(360.0)
98}
99
100/// Normalizes a radians value into the interval `[0, 2π)`.
101#[must_use]
102pub fn normalize_radians(radians: f64) -> f64 {
103    radians.rem_euclid(TAU)
104}
105
106/// Evaluates `sin` for a degrees input.
107#[must_use]
108pub fn sin_deg(degrees: f64) -> f64 {
109    degrees_to_radians(degrees).sin()
110}
111
112/// Evaluates `cos` for a degrees input.
113#[must_use]
114pub fn cos_deg(degrees: f64) -> f64 {
115    degrees_to_radians(degrees).cos()
116}
117
118/// Evaluates `tan` for a degrees input.
119#[must_use]
120pub fn tan_deg(degrees: f64) -> f64 {
121    degrees_to_radians(degrees).tan()
122}
123
124pub mod prelude;
125
126#[cfg(test)]
127mod tests {
128    use super::{
129        Angle, cos_deg, degrees_to_radians, normalize_degrees, normalize_radians,
130        radians_to_degrees, sin_deg, tan_deg,
131    };
132    use core::f64::consts::{PI, TAU};
133
134    fn assert_close(left: f64, right: f64) {
135        assert!((left - right).abs() < 1.0e-12, "left={left}, right={right}");
136    }
137
138    #[test]
139    fn converts_between_degrees_and_radians() {
140        let straight = Angle::from_degrees(180.0);
141        let right = Angle::from_radians(PI / 2.0);
142
143        assert_close(straight.radians(), PI);
144        assert_close(right.degrees(), 90.0);
145        assert_close(degrees_to_radians(60.0), PI / 3.0);
146        assert_close(radians_to_degrees(PI / 6.0), 30.0);
147    }
148
149    #[test]
150    fn normalizes_angles_and_evaluates_trig_helpers() {
151        let wrapped = Angle::from_degrees(-30.0).normalized();
152        let signed = Angle::from_degrees(450.0).normalized_signed();
153        let (sine, cosine) = Angle::from_degrees(30.0).sin_cos();
154
155        assert_close(wrapped.degrees(), 330.0);
156        assert_close(signed.degrees(), 90.0);
157        assert_close(sine, 0.5);
158        assert_close(cosine, (3.0_f64).sqrt() / 2.0);
159        assert_close(sin_deg(30.0), 0.5);
160        assert_close(cos_deg(60.0), 0.5);
161        assert_close(tan_deg(45.0), 1.0);
162        assert_close(normalize_degrees(-90.0), 270.0);
163        assert_close(normalize_radians(-PI / 2.0), TAU - (PI / 2.0));
164    }
165}