1use core::f32::consts::PI;
2
3#[inline]
4pub fn ease_in_quad(t: f32) -> f32 {
5 t * t
6}
7
8#[inline]
9pub fn ease_out_quad(t: f32) -> f32 {
10 1.0 - (1.0 - t) * (1.0 - t)
11}
12
13#[inline]
14pub fn ease_in_out_quad(t: f32) -> f32 {
15 if t < 0.5 {
16 2.0 * t * t
17 } else {
18 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
19 }
20}
21
22#[inline]
23pub fn ease_in_cubic(t: f32) -> f32 {
24 t * t * t
25}
26
27#[inline]
28pub fn ease_out_cubic(t: f32) -> f32 {
29 1.0 - (1.0 - t).powi(3)
30}
31
32#[inline]
33pub fn ease_in_out_cubic(t: f32) -> f32 {
34 if t < 0.5 {
35 4.0 * t * t * t
36 } else {
37 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
38 }
39}
40
41#[inline]
42pub fn ease_in_quart(t: f32) -> f32 {
43 t * t * t * t
44}
45
46#[inline]
47pub fn ease_out_quart(t: f32) -> f32 {
48 1.0 - (1.0 - t).powi(4)
49}
50
51#[inline]
52pub fn ease_in_out_quart(t: f32) -> f32 {
53 if t < 0.5 {
54 8.0 * t * t * t * t
55 } else {
56 1.0 - (-2.0 * t + 2.0).powi(4) / 2.0
57 }
58}
59
60#[inline]
61pub fn ease_in_sine(t: f32) -> f32 {
62 1.0 - ((t * PI) / 2.0).cos()
63}
64
65#[inline]
66pub fn ease_out_sine(t: f32) -> f32 {
67 ((t * PI) / 2.0).sin()
68}
69
70#[inline]
71pub fn ease_in_out_sine(t: f32) -> f32 {
72 -((PI * t).cos() - 1.0) / 2.0
73}
74
75#[inline]
76pub fn ease_in_expo(t: f32) -> f32 {
77 if t <= 0.0 {
78 0.0
79 } else if t >= 1.0 {
80 1.0
81 } else {
82 2.0_f32.powf(10.0 * t - 10.0)
83 }
84}
85
86#[inline]
87pub fn ease_out_expo(t: f32) -> f32 {
88 if t <= 0.0 {
89 0.0
90 } else if t >= 1.0 {
91 1.0
92 } else {
93 1.0 - 2.0_f32.powf(-10.0 * t)
94 }
95}
96
97#[inline]
98pub fn ease_in_out_expo(t: f32) -> f32 {
99 if t <= 0.0 {
100 0.0
101 } else if t >= 1.0 {
102 1.0
103 } else if t < 0.5 {
104 2.0_f32.powf(20.0 * t - 10.0) / 2.0
105 } else {
106 (2.0 - 2.0_f32.powf(-20.0 * t + 10.0)) / 2.0
107 }
108}
109
110#[inline]
111pub fn ease_in_back(t: f32) -> f32 {
112 let c1 = 1.70158;
113 let c3 = c1 + 1.0;
114 c3 * t * t * t - c1 * t * t
115}
116
117#[inline]
118pub fn ease_out_back(t: f32) -> f32 {
119 let c1 = 1.70158;
120 let c3 = c1 + 1.0;
121 1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
122}
123
124#[inline]
125pub fn ease_in_out_back(t: f32) -> f32 {
126 let c1 = 1.70158;
127 let c2 = c1 * 1.525;
128
129 if t < 0.5 {
130 ((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) / 2.0
131 } else {
132 ((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0)
133 / 2.0
134 }
135}
136
137#[inline]
138pub fn ease_in_elastic(t: f32) -> f32 {
139 let c4 = (2.0 * PI) / 3.0;
140
141 if t <= 0.0 {
142 0.0
143 } else if t >= 1.0 {
144 1.0
145 } else {
146 -(2.0_f32.powf(10.0 * t - 10.0)) * ((t * 10.0 - 10.75) * c4).sin()
147 }
148}
149
150#[inline]
151pub fn ease_out_elastic(t: f32) -> f32 {
152 let c4 = (2.0 * PI) / 3.0;
153
154 if t <= 0.0 {
155 0.0
156 } else if t >= 1.0 {
157 1.0
158 } else {
159 2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
160 }
161}
162
163#[inline]
164pub fn ease_in_out_elastic(t: f32) -> f32 {
165 let c5 = (2.0 * PI) / 4.5;
166
167 if t <= 0.0 {
168 0.0
169 } else if t >= 1.0 {
170 1.0
171 } else if t < 0.5 {
172 -(2.0_f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * c5).sin()) / 2.0
173 } else {
174 (2.0_f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * c5).sin()) / 2.0 + 1.0
175 }
176}
177
178#[inline]
179fn ease_out_bounce_internal(t: f32) -> f32 {
180 let n1 = 7.5625;
181 let d1 = 2.75;
182
183 if t < 1.0 / d1 {
184 n1 * t * t
185 } else if t < 2.0 / d1 {
186 let x = t - 1.5 / d1;
187 n1 * x * x + 0.75
188 } else if t < 2.5 / d1 {
189 let x = t - 2.25 / d1;
190 n1 * x * x + 0.9375
191 } else {
192 let x = t - 2.625 / d1;
193 n1 * x * x + 0.984375
194 }
195}
196
197#[inline]
198pub fn ease_out_bounce(t: f32) -> f32 {
199 ease_out_bounce_internal(t)
200}
201
202#[inline]
203pub fn ease_in_bounce(t: f32) -> f32 {
204 1.0 - ease_out_bounce_internal(1.0 - t)
205}
206
207#[inline]
208pub fn ease_in_out_bounce(t: f32) -> f32 {
209 if t < 0.5 {
210 (1.0 - ease_out_bounce_internal(1.0 - 2.0 * t)) / 2.0
211 } else {
212 (1.0 + ease_out_bounce_internal(2.0 * t - 1.0)) / 2.0
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 type EaseFn = fn(f32) -> f32;
221
222 fn assert_close(a: f32, b: f32) {
223 assert!(
224 (a - b).abs() <= 0.0001,
225 "expected {} ~= {} (delta {})",
226 a,
227 b,
228 (a - b).abs()
229 );
230 }
231
232 #[test]
233 fn test_all_easing_endpoints() {
234 let all: [EaseFn; 24] = [
235 ease_in_quad,
236 ease_out_quad,
237 ease_in_out_quad,
238 ease_in_cubic,
239 ease_out_cubic,
240 ease_in_out_cubic,
241 ease_in_quart,
242 ease_out_quart,
243 ease_in_out_quart,
244 ease_in_sine,
245 ease_out_sine,
246 ease_in_out_sine,
247 ease_in_expo,
248 ease_out_expo,
249 ease_in_out_expo,
250 ease_in_elastic,
251 ease_out_elastic,
252 ease_in_out_elastic,
253 ease_in_bounce,
254 ease_out_bounce,
255 ease_in_out_bounce,
256 ease_in_back,
257 ease_out_back,
258 ease_in_out_back,
259 ];
260
261 for ease in all {
262 assert_close(ease(0.0), 0.0);
263 assert_close(ease(1.0), 1.0);
264 }
265 }
266
267 #[test]
268 fn test_simple_midpoints() {
269 assert_close(ease_in_quad(0.5), 0.25);
270 assert_close(ease_out_quad(0.5), 0.75);
271 assert_close(ease_in_out_quad(0.5), 0.5);
272
273 assert_close(ease_in_cubic(0.5), 0.125);
274 assert_close(ease_out_cubic(0.5), 0.875);
275 assert_close(ease_in_out_cubic(0.5), 0.5);
276
277 assert_close(ease_in_quart(0.5), 0.0625);
278 assert_close(ease_out_quart(0.5), 0.9375);
279 assert_close(ease_in_out_quart(0.5), 0.5);
280
281 assert_close(ease_in_sine(0.5), 0.29289323);
282 assert_close(ease_out_sine(0.5), 0.70710677);
283 assert_close(ease_in_out_sine(0.5), 0.5);
284
285 assert_close(ease_in_expo(0.5), 0.03125);
286 assert_close(ease_out_expo(0.5), 0.96875);
287 assert_close(ease_in_out_expo(0.5), 0.5);
288 }
289
290 #[test]
291 fn test_non_overshoot_easing_stays_in_unit_range() {
292 let non_overshoot: [EaseFn; 18] = [
293 ease_in_quad,
294 ease_out_quad,
295 ease_in_out_quad,
296 ease_in_cubic,
297 ease_out_cubic,
298 ease_in_out_cubic,
299 ease_in_quart,
300 ease_out_quart,
301 ease_in_out_quart,
302 ease_in_sine,
303 ease_out_sine,
304 ease_in_out_sine,
305 ease_in_expo,
306 ease_out_expo,
307 ease_in_out_expo,
308 ease_in_bounce,
309 ease_out_bounce,
310 ease_in_out_bounce,
311 ];
312
313 for ease in non_overshoot {
314 for i in 0..=1000 {
315 let t = i as f32 / 1000.0;
316 let y = ease(t);
317 assert!(
318 (0.0..=1.0).contains(&y),
319 "value {} out of range for t={}",
320 y,
321 t
322 );
323 }
324 }
325 }
326
327 #[test]
328 fn test_back_and_elastic_overshoot() {
329 let overshooting: [EaseFn; 6] = [
330 ease_in_back,
331 ease_out_back,
332 ease_in_out_back,
333 ease_in_elastic,
334 ease_out_elastic,
335 ease_in_out_elastic,
336 ];
337
338 for ease in overshooting {
339 let mut found = false;
340 for i in 0..=1000 {
341 let t = i as f32 / 1000.0;
342 let y = ease(t);
343 if !(0.0..=1.0).contains(&y) {
344 found = true;
345 break;
346 }
347 }
348 assert!(found, "expected overshoot for easing function");
349 }
350
351 assert!(ease_in_back(0.5) < 0.0);
352 assert!(ease_out_back(0.5) > 1.0);
353 }
354}