azalea_core/
math.rs

1use std::{
2    f64::consts::PI,
3    ops::{Add, Div, Sub},
4    sync::LazyLock,
5};
6
7pub const EPSILON: f64 = 1.0E-7;
8
9pub static SIN: LazyLock<[f32; 65536]> = LazyLock::new(|| {
10    let mut sin = [0.0; 65536];
11    for (i, item) in sin.iter_mut().enumerate() {
12        *item = f64::sin((i as f64) * PI * 2.0 / 65536.0) as f32;
13    }
14    sin
15});
16
17/// A sine function that uses a lookup table.
18pub fn sin(x: f32) -> f32 {
19    let x = x * 10430.378;
20    let x = x as i32 as usize & 65535;
21    SIN[x]
22}
23
24/// A cosine function that uses a lookup table.
25pub fn cos(x: f32) -> f32 {
26    let x = x * 10430.378 + 16384.0;
27    let x = x as i32 as usize & 65535;
28    SIN[x]
29}
30
31pub fn binary_search<
32    T: Ord + PartialOrd + Add<Output = T> + Sub<Output = T> + Div<Output = T> + From<u8> + Copy,
33>(
34    mut min: T,
35    max: T,
36    predicate: impl Fn(T) -> bool,
37) -> T {
38    let mut diff = max - min;
39    while diff > T::from(0) {
40        let diff_mid = diff / T::from(2);
41        let mid = min + diff_mid;
42        if predicate(mid) {
43            diff = diff_mid;
44        } else {
45            min = mid + T::from(1);
46            diff = diff - (diff_mid + T::from(1));
47        }
48    }
49
50    min
51}
52
53pub fn lcm(a: u32, b: u32) -> u64 {
54    let gcd = gcd(a, b);
55    (a as u64) * (b / gcd) as u64
56}
57pub fn gcd(mut a: u32, mut b: u32) -> u32 {
58    while b != 0 {
59        let t = b;
60        b = a % b;
61        a = t;
62    }
63    a
64}
65
66pub fn lerp<T: num_traits::Float>(amount: T, a: T, b: T) -> T {
67    a + amount * (b - a)
68}
69
70pub fn ceil_log2(x: u32) -> u32 {
71    u32::BITS - x.leading_zeros()
72}
73
74pub fn fract(x: f64) -> f64 {
75    let x_int = x as i64 as f64;
76    let floor = if x < x_int { x_int - 1. } else { x_int };
77    x - floor
78}
79
80// these are copied from the java standard library, we don't calculate the
81// consts ourself to make sure it's the same as java
82pub fn to_radians(degrees: f64) -> f64 {
83    degrees * 0.017453292519943295
84}
85pub fn to_degrees(radians: f64) -> f64 {
86    radians * 57.29577951308232
87}
88
89/// Returns either -1, 0, or 1, depending on whether the number is negative,
90/// zero, or positive.
91///
92/// This function exists because f64::signum doesn't check for 0.
93pub fn sign(num: f64) -> f64 {
94    if num == 0. { 0. } else { num.signum() }
95}
96pub fn sign_as_int(num: f64) -> i32 {
97    if num == 0. { 0 } else { num.signum() as i32 }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_gcd() {
106        assert_eq!(gcd(0, 0), 0);
107        assert_eq!(gcd(1, 1), 1);
108
109        assert_eq!(gcd(0, 1), 1);
110        assert_eq!(gcd(1, 0), 1);
111
112        assert_eq!(gcd(12, 8), 4);
113        assert_eq!(gcd(8, 12), 4);
114
115        assert_eq!(gcd(12, 9), 3);
116        assert_eq!(gcd(9, 12), 3);
117
118        assert_eq!(gcd(12, 7), 1);
119        assert_eq!(gcd(7, 12), 1);
120    }
121
122    #[test]
123    fn test_sin() {
124        const PI: f32 = std::f32::consts::PI;
125        // check that they're close enough
126        fn assert_sin_eq_enough(number: f32) {
127            let a = sin(number);
128            let b = f32::sin(number);
129            assert!((a - b).abs() < 0.01, "sin({number}) failed, {a} != {b}");
130        }
131        assert_sin_eq_enough(0.0);
132        assert_sin_eq_enough(PI / 2.0);
133        assert_sin_eq_enough(PI);
134        assert_sin_eq_enough(PI * 2.0);
135        assert_sin_eq_enough(PI * 3.0 / 2.0);
136        assert_sin_eq_enough(-PI / 2.0);
137        assert_sin_eq_enough(-PI);
138        assert_sin_eq_enough(-PI * 2.0);
139        assert_sin_eq_enough(-PI * 3.0 / 2.0);
140    }
141}