pix_engine/
math.rs

1//! Math functions and constants.
2
3use crate::prelude::Vector;
4use num_traits::{
5    Float as FloatT, Num as NumT, NumAssignOps, NumAssignRef, NumCast, NumOps, NumRef,
6};
7use rand::{self, distributions::uniform::SampleUniform, Rng};
8use std::ops::{AddAssign, Range};
9
10use once_cell::sync::Lazy;
11/// Default math constants.
12pub use std::f64::consts::*;
13
14/// Default number trait used for objects and shapes.
15pub trait Num:
16    NumT + NumOps + NumAssignOps + NumAssignRef + Copy + Default + PartialOrd + PartialEq
17{
18}
19
20/// Default floating-point number trait used math operations.
21pub trait Float: Num + FloatT {}
22
23impl<T> Num for T where
24    T: NumT
25        + NumOps
26        + NumRef
27        + NumAssignOps
28        + NumAssignRef
29        + Copy
30        + Default
31        + PartialOrd
32        + PartialEq
33{
34}
35
36impl<T> Float for T where T: Num + FloatT {}
37
38const PERLIN_YWRAPB: usize = 4;
39const PERLIN_YWRAP: usize = 1 << PERLIN_YWRAPB;
40const PERLIN_ZWRAPB: usize = 8;
41const PERLIN_ZWRAP: usize = 1 << PERLIN_ZWRAPB;
42const PERLIN_SIZE: usize = 4095;
43
44static PERLIN: Lazy<Vec<f64>> = Lazy::new(|| {
45    let mut perlin = Vec::with_capacity(PERLIN_SIZE + 1);
46    for _ in 0..=PERLIN_SIZE {
47        perlin.push(random(1.0));
48    }
49    perlin
50});
51
52/// Returns a random number within a range.
53///
54/// # Examples
55///
56/// ```
57/// use pix_engine::math::random_rng;
58///
59/// let x = random_rng(0.0..1.0); // x will range from (0.0..1.0]
60/// assert!(x >= 0.0 && x < 1.0);
61///
62/// let x = random_rng(20..50); // x will range from (20..50]
63/// assert!(x >= 20 && x < 50);
64pub fn random_rng<T, R>(val: R) -> T
65where
66    T: SampleUniform + PartialOrd,
67    R: Into<Range<T>>,
68{
69    let val = val.into();
70    rand::thread_rng().gen_range(val)
71}
72
73/// Returns a random number between `0` and a given `value`.
74///
75/// # Examples
76///
77/// ```
78/// use pix_engine::math::random;
79///
80/// let x = random(100); // x will range from (0..100]
81/// assert!(x >= 0 && x < 100);
82///
83/// let x = random(100.0); // x will range from (0.0..100.0]
84/// assert!(x >= 0.0 && x < 100.0);
85pub fn random<T>(val: T) -> T
86where
87    T: Num + SampleUniform + PartialOrd,
88{
89    if val > T::zero() {
90        random_rng(T::zero()..val)
91    } else {
92        random_rng(val..T::zero())
93    }
94}
95
96/// Returns the [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise) value at specified coordinates.
97///
98/// # Examples
99///
100/// ```
101/// use pix_engine::math::noise;
102///
103/// let n = noise([5.0]);
104/// assert!(n >= 0.0 && n < 1.0);
105///
106/// let n = noise([2.0, 1.5]);
107/// assert!(n >= 0.0 && n < 1.0);
108///
109/// let n = noise([2.0, 1.5, 3.0]);
110/// assert!(n >= 0.0 && n < 1.0);
111/// ```
112pub fn noise<V, const N: usize>(vector: V) -> f64
113where
114    V: Into<Vector<f64, N>>,
115{
116    let v = vector.into();
117
118    let values = v.coords();
119    let x = values.first().unwrap_or(&0.0).abs();
120    let y = values.get(1).unwrap_or(&0.0).abs();
121    let z = values.get(2).unwrap_or(&0.0).abs();
122
123    let mut xi: usize = x.trunc() as usize;
124    let mut yi: usize = y.trunc() as usize;
125    let mut zi: usize = z.trunc() as usize;
126
127    let mut xf = x.fract();
128    let mut yf = y.fract();
129    let mut zf = z.fract();
130    let (mut rxf, mut ryf);
131
132    let mut noise_result = 0.0;
133    let mut ampl = 0.5;
134
135    let (mut n1, mut n2, mut n3);
136
137    let scaled_cosine = |i: f64| 0.5 * (1.0 - (i - PI).cos());
138
139    let perlin_octaves = 4; // default to medium smooth
140    let perlin_amp_falloff = 0.5; // 50% reduction/octave
141    for _ in 0..perlin_octaves {
142        let mut of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB);
143
144        rxf = scaled_cosine(xf);
145        ryf = scaled_cosine(yf);
146
147        n1 = PERLIN[of & PERLIN_SIZE];
148        n1 += rxf * (PERLIN[(of + 1) & PERLIN_SIZE] - n1);
149        n2 = PERLIN[(of + PERLIN_YWRAP) & PERLIN_SIZE];
150        n2 += rxf * (PERLIN[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2);
151        n1 += ryf * (n2 - n1);
152
153        of += PERLIN_ZWRAP;
154        n2 = PERLIN[of & PERLIN_SIZE];
155        n2 += rxf * (PERLIN[(of + 1) & PERLIN_SIZE] - n2);
156        n3 = PERLIN[(of + PERLIN_YWRAP) & PERLIN_SIZE];
157        n3 += rxf * (PERLIN[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n3);
158        n2 += ryf * (n3 - n2);
159
160        n1 += scaled_cosine(zf) * (n2 - n1);
161
162        noise_result += n1 * ampl;
163        ampl *= perlin_amp_falloff;
164        xi <<= 1;
165        xf *= 2.0;
166        yi <<= 1;
167        yf *= 2.0;
168        zi <<= 1;
169        zf *= 2.0;
170
171        if xf >= 1.0 {
172            xi += 1;
173            xf -= 1.0;
174        }
175        if yf >= 1.0 {
176            yi += 1;
177            yf -= 1.0;
178        }
179        if zf >= 1.0 {
180            zi += 1;
181            zf -= 1.0;
182        }
183    }
184    noise_result
185}
186
187/// Returns a random number within a range.
188///
189/// # Examples
190///
191/// ```
192/// # use pix_engine::prelude::*;
193/// let x = random!(); // x will range from (0.0..1.0]
194/// assert!(x >= 0.0 && x < 1.0);
195///
196/// let x = random!(100); // x will range from (0..100]
197/// assert!(x >= 0 && x < 100);
198/// let y = random!(20, 50); // x will range from (20..50]
199/// assert!(y >= 20 && y < 50);
200///
201/// let x = random!(100.0); // x will range from (0.0..100.0]
202/// assert!(x >= 0.0 && x < 100.0);
203/// let y = random!(20.0, 50.0); // x will range from (20.0..50.0]
204/// assert!(y >= 20.0 && y < 50.0);
205/// ```
206#[macro_export]
207macro_rules! random {
208    () => {
209        $crate::math::random(1.0)
210    };
211    ($v:expr) => {
212        $crate::math::random($v)
213    };
214    ($s:expr, $e:expr$(,)?) => {
215        $crate::math::random_rng($s..$e)
216    };
217}
218
219/// Returns the [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise) value at specified
220/// coordinates.
221///
222/// # Examples
223///
224/// ```
225/// # use pix_engine::prelude::*;
226/// let n = noise!(5.0);
227/// assert!(n >= 0.0 && n < 1.0);
228///
229/// let n = noise!(2.0, 1.5);
230/// assert!(n >= 0.0 && n < 1.0);
231///
232/// let n = noise!(2.0, 1.5, 3.0);
233/// assert!(n >= 0.0 && n < 1.0);
234/// ```
235#[macro_export]
236macro_rules! noise {
237    ($x:expr$(,)?) => {
238        $crate::math::noise([$x])
239    };
240    ($x:expr, $y:expr$(,)?) => {
241        $crate::math::noise([$x, $y])
242    };
243    ($x:expr, $y:expr, $z:expr$(,)?) => {
244        $crate::math::noise([$x, $y, $z])
245    };
246}
247
248/// Remaps a number from one range to another.
249///
250/// Map range defaults to `0.0..=f64::MAX` in the event casting to [f64] fails.
251/// NaN will result in the max mapped value.
252///
253/// # Example
254///
255/// ```
256/// # use pix_engine::prelude::*;
257/// let value = 25;
258/// let m = map(value, 0, 100, 0, 800);
259/// assert_eq!(m, 200);
260///
261/// let value = 50.0;
262/// let m = map(value, 0.0, 100.0, 0.0, 1.0);
263/// assert_eq!(m, 0.5);
264///
265/// let value = f64::NAN;
266/// let m = map(value, 0.0, 100.0, 0.0, 1.0);
267/// assert!(m.is_nan());
268///
269/// let value = f64::INFINITY;
270/// let m = map(value, 0.0, 100.0, 0.0, 1.0);
271/// assert_eq!(m, 1.0);
272///
273/// let value = f64::NEG_INFINITY;
274/// let m = map(value, 0.0, 100.0, 0.0, 1.0);
275/// assert_eq!(m, 0.0);
276/// ```
277pub fn map<T>(value: T, start1: T, end1: T, start2: T, end2: T) -> T
278where
279    T: NumCast + Into<f64> + PartialOrd + Copy,
280{
281    let default = end2;
282    let start1 = start1.into();
283    let end1 = end1.into();
284    let start2 = start2.into();
285    let end2 = end2.into();
286    let value = value.into();
287    let new_val = ((value - start1) / (end1 - start1)).mul_add(end2 - start2, start2);
288    NumCast::from(new_val.clamp(start2, end2)).unwrap_or(default)
289}
290
291/// Linear interpolates between two values by a given amount.
292///
293/// # Examples
294///
295/// ```
296/// use pix_engine::math::lerp;
297///
298/// let start = 0.0;
299/// let end = 5.0;
300/// let amount = 0.5;
301/// let value = lerp(start, end, amount);
302/// assert_eq!(value, 2.5);
303/// ```
304pub fn lerp<T>(start: T, end: T, amount: T) -> T
305where
306    T: Num + Copy + PartialOrd,
307{
308    (T::one() - amount) * start + amount * end
309}
310
311/// Linear interpolates values for a range of independent values based on depdendent values.
312///
313/// # Examples
314///
315/// ```
316/// use pix_engine::math::lerp_map;
317///
318/// let x1 = 0;
319/// let x2 = 5;
320/// let y1 = 0;
321/// let y2 = 10;
322/// let values = lerp_map(x1, x2, y1, y2);
323/// assert_eq!(values, vec![0, 2, 4, 6, 8, 10]);
324///
325/// let x1 = 0.0;
326/// let x2 = 4.0;
327/// let y1 = 0.0;
328/// let y2 = 14.0;
329/// let values = lerp_map(x1, x2, y1, y2);
330/// assert_eq!(values, vec![0.0, 3.5, 7.0, 10.5, 14.0]);
331/// ```
332pub fn lerp_map<T>(start1: T, end1: T, start2: T, end2: T) -> Vec<T>
333where
334    T: Num + NumCast + Copy + PartialOrd + AddAssign,
335{
336    if start1 == end1 {
337        vec![start2]
338    } else {
339        let size: usize = NumCast::from(end1 - start1).unwrap_or(4);
340        let mut values = Vec::with_capacity(size);
341        let a = (end2 - start2) / (end1 - start1);
342        let mut d = start2;
343        let mut i = start1;
344        while i <= end1 {
345            values.push(d);
346            d += a;
347            i += T::one();
348        }
349        values
350    }
351}