melvin_ob/util/math/
helpers.rs

1use fixed::types::{I32F32, I64F64};
2
3pub const MAX_DEC: u8 = 2;
4
5/// Helper function to calculate the greatest common divisor (GCD) for fixed-point numbers using `I32F32`.
6///
7/// # Arguments
8/// - `a`: The first `I32F32` number.
9/// - `b`: The second `I32F32` number.
10///
11/// # Returns
12/// - An `I32F32` representing the greatest common divisor of `a` and `b`.
13pub fn gcd_fixed64(a: I32F32, b: I32F32, dec: u8) -> I32F32 {
14    let scale_factor = I32F32::from_num(10u32.pow(u32::from(dec)));
15    let a_int = (a * scale_factor).round().to_num::<i64>();
16    let b_int = (b * scale_factor).round().to_num::<i64>();
17    I32F32::from_num(gcd_i64(a_int, b_int)) / scale_factor
18}
19
20/// Helper function to calculate the greatest common divisor (GCD) for fixed-point numbers using `I64F64`.
21///
22/// # Arguments
23/// - `a`: The first `I64F64` number.
24/// - `b`: The second `I64F64` number.
25///
26/// # Returns
27/// - An `I64F64` representing the greatest common divisor of `a` and `b`.
28pub fn gcd_fixed128(a: I64F64, b: I64F64, dec: u8) -> I64F64 {
29    let scale_factor = I64F64::from_num(10u32.pow(u32::from(dec)));
30    let a_int = (a * scale_factor).round().to_num::<i64>();
31    let b_int = (b * scale_factor).round().to_num::<i64>();
32    I64F64::from_num(gcd_i64(a_int, b_int)) / scale_factor
33}
34
35/// Helper function to calculate the greatest common divisor (GCD) for signed integers.
36///
37/// # Arguments
38/// - `a`: The first integer.
39/// - `b`: The second integer.
40///
41/// # Returns
42/// - An `i32` representing the greatest common divisor of `a` and `b`.
43pub fn gcd_i64(a: i64, b: i64) -> i64 {
44    let mut x = a.abs();
45    let mut y = b.abs();
46    while y != 0 {
47        let temp = y;
48        y = x % y;
49        x = temp;
50    }
51    x
52}
53
54/// Helper function to calculate the modulo for fixed-point numbers using `I32F32`.
55///
56/// # Arguments
57/// - `a`: The dividend.
58/// - `b`: The divisor.
59///
60/// # Returns
61/// - An `I32F32` representing the modulo result.
62pub fn fmod_fixed64(a: I32F32, b: I32F32) -> I32F32 { ((a % b) + b) % b }
63
64/// Calculate the least common multiple (LCM) for fixed-point numbers using `I32F32`.
65///
66/// # Arguments
67/// - `a`: The first `I32F32` number.
68/// - `b`: The second `I32F32` number.
69///
70/// # Returns
71/// - An `I32F32` representing the least common multiple of `a` and `b`.
72pub fn lcm_fixed64(a: I32F32, b: I32F32) -> I32F32 { (a * b / gcd_fixed64(a, b, MAX_DEC)).abs() }
73
74/// Calculate the least common multiple (LCM) for fixed-point numbers using `I64F64`.
75///
76/// # Arguments
77/// - `a`: The first `I64F64` number.
78/// - `b`: The second `I64F64` number.
79///
80/// # Returns
81/// - An `I64F64` representing the least common multiple of `a` and `b`.
82pub fn lcm_fixed128(a: I64F64, b: I64F64) -> I64F64 { (a * b / gcd_fixed128(a, b, MAX_DEC)).abs() }
83
84/// Calculate the least common multiple (LCM) for signed integers.
85///
86/// # Arguments
87/// - `a`: The first integer.
88/// - `b`: The second integer.
89///
90/// # Returns
91/// - An `i32` representing the least common multiple of `a` and `b`.
92pub fn lcm_i64(a: i64, b: i64) -> i64 { (a / gcd_i64(a, b)) * b }
93
94/// Generalized method to normalize a value within a given range.
95///
96/// # Arguments
97/// - `value`: The value to normalize.
98/// - `min`: The minimum value of the range.
99/// - `max`: The maximum value of the range.
100///
101/// # Returns
102/// - A `Some(I32F32)` representing the normalized value in the range `[0.0, 1.0]`.
103/// - Returns `None` if `min` and `max` are effectively the same (to prevent division by zero).
104pub fn normalize_fixed32(value: I32F32, min: I32F32, max: I32F32) -> Option<I32F32> {
105    if (max - min).abs() <= I32F32::DELTA {
106        // Avoid division by zero when min and max are effectively the same
107        None
108    } else {
109        Some((value - min) / (max - min))
110    }
111}
112
113/// Linearly interpolates a value `t` between two points `(x1, y1)` and `(x2, y2)`.
114///
115/// # Arguments
116/// - `x1`, `x2`: The x-coordinates of the two points.
117/// - `y1`, `y2`: The y-coordinates of the two points.
118/// - `t`: The x-coordinate for which the interpolated y-value is to be calculated.
119///
120/// # Returns
121/// - An `I32F32` representing the interpolated y-value.
122pub fn interpolate(x1: I32F32, x2: I32F32, y1: I32F32, y2: I32F32, t: I32F32) -> I32F32 {
123    let r_t = t.clamp(x1, x2);
124    y1 + (r_t - x1) * (y2 - y1) / (x2 - x1)
125}
126
127/// Finds the minimum absolute y-coordinate for a range of x-values, represented by two points.
128///
129/// # Arguments
130/// - `a_x`: The x-coordinate of the first point.
131/// - `a_y`: The range of y-values for the first point `(min_y, max_y)`.
132/// - `b_x`: The x-coordinate of the second point.
133/// - `b_y`: The range of y-values for the second point `(min_y, max_y)`.
134///
135/// # Returns
136/// - A tuple of `(t_min_clamped, pos_min)`, where:
137///   - `t_min_clamped`: The clamped parameter along x that minimizes the absolute y-value.
138///   - `pos_min`: A tuple `(x, y)` representing the position (x-coordinate and y-coordinate) at `t_min`.
139pub fn find_min_y_abs_for_x_range(
140    a_x: I32F32,
141    a_y: (I32F32, I32F32),
142    b_x: I32F32,
143    b_y: (I32F32, I32F32),
144) -> (I32F32, (I32F32, I32F32)) {
145    // Determine the smaller and larger time bounds
146    let (min_t, min_pos, max_t, max_pos) =
147        if a_x < b_x { (a_x, a_y, b_x, b_y) } else { (b_x, b_y, a_x, a_y) };
148
149    let (min_x, min_y) = min_pos;
150    let (max_x, max_y) = max_pos;
151
152    // Calculate deltas
153    let dx = max_x - min_x;
154    let dy = max_y - min_y;
155    let dt = max_t - min_t;
156
157    // Coefficients for the quadratic function f(t) = c2 * t^2 + c1 * t + c0
158    let c2 = (dx / dt) * (dx / dt) + (dy / dt) * (dy / dt);
159    let c1 = I32F32::lit("2.0") * ((min_x * dx / dt) + (min_y * dy / dt));
160
161    // Minimize f(t) -> t_min = -c1 / (2 * c2)
162    let t_min = -c1 / (I32F32::lit("2.0") * c2);
163
164    // Clamp t_min to the [min_t, max_t] range
165    let t_min_clamped = t_min.clamp(min_t, max_t);
166
167    // Compute the position at t_min
168    let alpha = (t_min_clamped - min_t) / dt;
169    let pos_min = (
170        min_x + alpha * dx, // Interpolated x
171        min_y + alpha * dy, // Interpolated y
172    );
173
174    // Return the clamped t_min and the corresponding position
175    (t_min_clamped, pos_min)
176}