nannou_core/
math.rs

1//! A mathematical foundation for nannou including point and vector types and a range of
2//! helper/utility functions.
3
4use core::ops::Add;
5use num_traits::{Float, NumCast, One};
6
7pub use num_traits;
8
9const ONE_TURN_DEGREES_F32: f32 = 360.0;
10const ONE_TURN_DEGREES_F64: f64 = 360.0;
11
12/// Functions for converting between angle representations.
13///
14/// Only implemented for `f32` and `f64`.
15pub trait ConvertAngle {
16    fn deg_to_rad(self) -> Self;
17    fn rad_to_deg(self) -> Self;
18    fn turns_to_rad(self) -> Self;
19    fn rad_to_turns(self) -> Self;
20}
21
22/// Short-hand for retrieving the angle of the vector in radians.
23pub trait Vec2Angle {
24    /// The angle of the vector in radians.
25    ///
26    /// Implemented internally as `glam::Vec2::X.angle_between(self)`.
27    fn angle(self) -> f32;
28}
29
30pub trait Vec2Rotate {
31    /// Rotate the vector around 0.0.
32    fn rotate(self, radians: f32) -> Self;
33}
34
35/// Create a transformation matrix that will cause a vector to point at `dir` using `up` for
36/// orientation.
37// NOTE: Remove this if we can land something similar upstream in `glam`.
38pub trait Mat4LookTo {
39    fn look_to_rh(eye: glam::Vec3, dir: glam::Vec3, up: glam::Vec3) -> glam::Mat4 {
40        let f = dir.normalize();
41        let s = f.cross(up).normalize();
42        let u = s.cross(f);
43        glam::Mat4::from_cols(
44            glam::vec4(s.x, u.x, -f.x, 0.0),
45            glam::vec4(s.y, u.y, -f.y, 0.0),
46            glam::vec4(s.z, u.z, -f.z, 0.0),
47            glam::vec4(-eye.dot(s), -eye.dot(u), eye.dot(f), 1.0),
48        )
49    }
50
51    fn look_to_lh(eye: glam::Vec3, dir: glam::Vec3, up: glam::Vec3) -> glam::Mat4 {
52        Self::look_to_rh(eye, -dir, up)
53    }
54}
55
56impl ConvertAngle for f32 {
57    fn deg_to_rad(self) -> Self {
58        self * core::f32::consts::TAU / ONE_TURN_DEGREES_F32
59    }
60    fn rad_to_deg(self) -> Self {
61        self * ONE_TURN_DEGREES_F32 / core::f32::consts::TAU
62    }
63    fn turns_to_rad(self) -> Self {
64        self * core::f32::consts::TAU
65    }
66    fn rad_to_turns(self) -> Self {
67        self / core::f32::consts::TAU
68    }
69}
70
71impl ConvertAngle for f64 {
72    fn deg_to_rad(self) -> Self {
73        self * core::f64::consts::TAU / ONE_TURN_DEGREES_F64
74    }
75    fn rad_to_deg(self) -> Self {
76        self * ONE_TURN_DEGREES_F64 / core::f64::consts::TAU
77    }
78    fn turns_to_rad(self) -> Self {
79        self * core::f64::consts::TAU
80    }
81    fn rad_to_turns(self) -> Self {
82        self / core::f64::consts::TAU
83    }
84}
85
86impl Vec2Angle for glam::Vec2 {
87    fn angle(self) -> f32 {
88        glam::Vec2::X.angle_between(self)
89    }
90}
91
92impl Vec2Rotate for glam::Vec2 {
93    fn rotate(self, radians: f32) -> Self {
94        let rad_cos = radians.cos();
95        let rad_sin = radians.sin();
96        let x = self.x * rad_cos - self.y * rad_sin;
97        let y = self.x * rad_sin + self.y * rad_cos;
98        glam::vec2(x, y)
99    }
100}
101
102impl Mat4LookTo for glam::Mat4 {}
103
104/// Maps a value from an input range to an output range.
105///
106/// Note that `map_range` doesn't clamp the output: if `val` is outside the input range, the mapped
107/// value will be outside the output range. (Use `clamp` to restrict the output, if desired.)
108///
109/// # Examples
110/// ```
111/// # use nannou_core::prelude::*;
112/// assert_eq!(map_range(128, 0, 255, 0.0, 1.0), 0.5019607843137255);
113/// ```
114/// ```
115/// # use nannou_core::prelude::*;
116/// assert_eq!(map_range(3, 0, 10, 0.0, 1.0), 0.3);
117/// ```
118/// ```
119/// # use nannou_core::prelude::*;
120/// // When the value is outside the input range, the result will be outside the output range.
121/// let result = map_range(15, 0, 10, 0.0, 1.0);
122/// assert_eq!(result, 1.5);
123/// assert_eq!(clamp(result, 0.0, 1.0), 1.0);
124/// ```
125// TODO: Should consider refactoring this to only allow for conversions between types that cannot
126// fail. This would break some code but make users think about issues like converting to signed
127// ranges with unsigned types, etc. Would also reduce size of code generated due to all the panic
128// branches.
129pub fn map_range<X, Y>(val: X, in_min: X, in_max: X, out_min: Y, out_max: Y) -> Y
130where
131    X: NumCast,
132    Y: NumCast,
133{
134    macro_rules! unwrap_or_panic {
135        ($result:expr, $arg:expr) => {
136            $result.unwrap_or_else(|| panic!("[map_range] failed to cast {} arg to `f64`", $arg))
137        };
138    }
139
140    let val_f: f64 = unwrap_or_panic!(NumCast::from(val), "first");
141    let in_min_f: f64 = unwrap_or_panic!(NumCast::from(in_min), "second");
142    let in_max_f: f64 = unwrap_or_panic!(NumCast::from(in_max), "third");
143    let out_min_f: f64 = unwrap_or_panic!(NumCast::from(out_min), "fourth");
144    let out_max_f: f64 = unwrap_or_panic!(NumCast::from(out_max), "fifth");
145
146    NumCast::from((val_f - in_min_f) / (in_max_f - in_min_f) * (out_max_f - out_min_f) + out_min_f)
147        .unwrap_or_else(|| panic!("[map_range] failed to cast result to target type"))
148}
149
150/// The max between two partially ordered values.
151pub fn partial_max<T>(a: T, b: T) -> T
152where
153    T: PartialOrd,
154{
155    if a >= b {
156        a
157    } else {
158        b
159    }
160}
161
162/// The min between two partially ordered values.
163pub fn partial_min<T>(a: T, b: T) -> T
164where
165    T: PartialOrd,
166{
167    if a <= b {
168        a
169    } else {
170        b
171    }
172}
173
174/// Clamp a value between some range.
175pub fn clamp<T>(n: T, start: T, end: T) -> T
176where
177    T: PartialOrd,
178{
179    if start <= end {
180        if n < start {
181            start
182        } else if n > end {
183            end
184        } else {
185            n
186        }
187    } else {
188        if n < end {
189            end
190        } else if n > start {
191            start
192        } else {
193            n
194        }
195    }
196}
197
198pub fn two<S>() -> S
199where
200    S: Add<Output = S> + One,
201{
202    S::one() + S::one()
203}
204
205/// Models the C++ fmod function.
206#[inline]
207pub fn fmod<F>(numer: F, denom: F) -> F
208where
209    F: Float,
210{
211    let rquot: F = (numer / denom).floor();
212    numer - rquot * denom
213}
214
215/// Convert the given angle in degrees to the same angle in radians.
216pub fn deg_to_rad<S>(s: S) -> S
217where
218    S: ConvertAngle,
219{
220    s.deg_to_rad()
221}
222
223/// Convert the given angle in radians to the same angle in degrees.
224pub fn rad_to_deg<S>(s: S) -> S
225where
226    S: ConvertAngle,
227{
228    s.rad_to_deg()
229}
230
231/// Convert the given value as a number of "turns" into the equivalent angle in radians.
232pub fn turns_to_rad<S>(s: S) -> S
233where
234    S: ConvertAngle,
235{
236    s.turns_to_rad()
237}
238
239/// Convert the given value in radians to the equivalent value as a number of turns.
240pub fn rad_to_turns<S>(s: S) -> S
241where
242    S: ConvertAngle,
243{
244    s.rad_to_turns()
245}