1type BFloat = f32;
9
10const NEWTON_ITERATIONS: usize = 4;
11const NEWTON_MIN_SLOPE: BFloat = 0.001;
12const SUBDIVISION_PRECISION: BFloat = 0.0000001;
13const SUBDIVISION_MAX_ITERATIONS: usize = 10;
14
15const K_SPLINE_TABLE_SIZE: usize = 11;
16const K_SAMPLE_STEP_SIZE: BFloat = 1.0 / (K_SPLINE_TABLE_SIZE - 1) as BFloat;
17
18#[inline]
19fn a(a_a1: BFloat, a_a2: BFloat) -> BFloat {
20 1.0 - 3.0 * a_a2 + 3.0 * a_a1
21}
22
23#[inline]
24fn b(a_a1: BFloat, a_a2: BFloat) -> BFloat {
25 3.0 * a_a2 - 6.0 * a_a1
26}
27
28#[inline]
29fn c(a_a1: BFloat) -> BFloat {
30 3.0 * a_a1
31}
32
33#[inline]
34fn calc_bezier(a_t: BFloat, a_a1: BFloat, a_a2: BFloat) -> BFloat {
35 ((a(a_a1, a_a2) * a_t + b(a_a1, a_a2)) * a_t + c(a_a1)) * a_t
36}
37
38#[inline]
39fn get_slope(a_t: BFloat, a_a1: BFloat, a_a2: BFloat) -> BFloat {
40 3.0 * a(a_a1, a_a2) * a_t * a_t + 2.0 * b(a_a1, a_a2) * a_t + c(a_a1)
41}
42
43#[inline]
44fn binary_subdivide(a_x: BFloat, a_a: BFloat, a_b: BFloat, m_x1: BFloat, m_x2: BFloat) -> BFloat {
45 let mut m_x1 = m_x1;
46 let mut m_x2 = m_x2;
47 let mut current_x: BFloat;
48 let mut current_t = 0.0;
49 let mut i = 0;
50 while i < SUBDIVISION_MAX_ITERATIONS {
51 current_t = m_x1 + (m_x2 - m_x1) / 2.0;
52 current_x = calc_bezier(current_t, a_a, a_b) - a_x;
53 if current_x > 0.0 {
54 m_x2 = current_t;
55 } else {
56 m_x1 = current_t;
57 }
58 if current_x.abs() < SUBDIVISION_PRECISION {
59 break;
60 }
61 i += 1;
62 }
63 current_t
64}
65
66#[inline]
67fn newton_raphson_iterate(a_x: BFloat, a_guess_t: BFloat, a_a: BFloat, a_b: BFloat) -> BFloat {
68 let mut guess_t = a_guess_t;
69 for _ in 0..NEWTON_ITERATIONS {
70 let current_slope = get_slope(guess_t, a_a, a_b);
71 if current_slope == 0.0 {
72 return guess_t;
73 }
74 let current_x = calc_bezier(guess_t, a_a, a_b) - a_x;
75 guess_t -= current_x / current_slope;
76 }
77 guess_t
78}
79
80#[inline]
81fn linear_easing(x: BFloat) -> BFloat {
82 x
83}
84
85#[inline]
86fn calc_sample_values(m_x1: BFloat, m_x2: BFloat) -> [BFloat; K_SPLINE_TABLE_SIZE] {
87 let mut sample_values = [0.0; K_SPLINE_TABLE_SIZE];
88 for (i, value) in sample_values.iter_mut().enumerate() {
89 *value = calc_bezier(i as BFloat * K_SAMPLE_STEP_SIZE, m_x1, m_x2);
90 }
91 sample_values
92}
93
94#[inline]
95fn get_t_for_x(x: BFloat, m_x1: BFloat, m_x2: BFloat) -> BFloat {
96 let mut interval_start = 0.0;
97 let mut current_sample = 1;
98 let last_sample = K_SPLINE_TABLE_SIZE - 1;
99 let sample_values = calc_sample_values(m_x1, m_x2);
100
101 while current_sample != last_sample && sample_values[current_sample] <= x {
102 interval_start += K_SAMPLE_STEP_SIZE;
103 current_sample += 1;
104 }
105 current_sample -= 1;
106
107 let dist = (x - sample_values[current_sample])
108 / (sample_values[current_sample + 1] - sample_values[current_sample]);
109 let guess_for_t = interval_start + dist * K_SAMPLE_STEP_SIZE;
110 let initial_slope = get_slope(guess_for_t, m_x1, m_x2);
111 if initial_slope >= NEWTON_MIN_SLOPE {
112 newton_raphson_iterate(x, guess_for_t, m_x1, m_x2)
113 } else if initial_slope == 0.0 {
114 guess_for_t
115 } else {
116 binary_subdivide(
117 x,
118 interval_start,
119 interval_start + K_SAMPLE_STEP_SIZE,
120 m_x1,
121 m_x2,
122 )
123 }
124}
125
126#[derive(Debug, Clone)]
127pub struct BezierEasingError(String);
128
129impl std::fmt::Display for BezierEasingError {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(f, "{:#?}", self.0)
132 }
133}
134
135impl std::error::Error for BezierEasingError {
136 fn description(&self) -> &str {
137 &self.0
138 }
139}
140
141pub fn bezier_easing(
151 m_x1: BFloat,
152 m_y1: BFloat,
153 m_x2: BFloat,
154 m_y2: BFloat,
155) -> Result<impl Fn(BFloat) -> BFloat, BezierEasingError> {
156 if !((0.0..=1.0).contains(&m_x1) && (0.0..=1.0).contains(&m_x2)) {
157 return Err(BezierEasingError("x values must be in [0, 1]".to_string()));
158 }
159 Ok(move |x: BFloat| {
160 if m_x1 == m_y1 && m_x2 == m_y2 {
161 return linear_easing(x);
162 }
163 if x == 0.0 || x == 1.0 {
164 return x;
165 }
166 calc_bezier(get_t_for_x(x, m_x1, m_x2), m_y1, m_y2)
167 })
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn it_works() -> Result<(), BezierEasingError> {
176 let ease = bezier_easing(0.0, 0.0, 1.0, 0.5)?;
177 assert_eq!(ease(0.0), 0.0);
178 assert_eq!(ease(0.5), 0.3125);
179 assert_eq!(ease(1.0), 1.0);
180
181 Ok(())
182 }
183}