Skip to main content

hermes_five/animations/
easing.rs

1use simple_easing::*;
2
3/// Represents a set of easing function.
4///
5/// An easing function is a temporal function that takes a time between 0 and 1 (beginning / end)
6/// and associate to it a number value according to an ease curve.
7///
8/// See <https://easings.net> for a representation of easing methods.
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[derive(Default, Clone, Copy, Debug, PartialEq)]
11pub enum Easing {
12    /// <https://easings.net/#easeInBack>
13    BackIn,
14    /// <https://easings.net/#easeInOutBack>
15    BackInOuT,
16    /// <https://easings.net/#easeOutBack>
17    BackOut,
18    /// <https://easings.net/#easeInBounce>
19    BounceIn,
20    /// <https://easings.net/#easeInOutBounce>
21    BounceInOut,
22    /// <https://easings.net/#easeOutBounce>
23    BounceOut,
24    /// <https://easings.net/#easeInCirc>
25    CircIn,
26    /// <https://easings.net/#easeInOutCirc>
27    CircInOut,
28    /// <https://easings.net/#easeOutCirc>
29    CircOut,
30    /// <https://easings.net/#easeInCubic>
31    CubicIn,
32    /// <https://easings.net/#easeInOutCubic>
33    CubicInOut,
34    /// <https://easings.net/#easeOutCubic>
35    CubicOut,
36    /// <https://easings.net/#easeInElastic>
37    ElasticIn,
38    /// <https://easings.net/#easeInOutElastic>
39    ElasticInOut,
40    /// <https://easings.net/#easeOutElastic>
41    ElasticOut,
42    /// <https://easings.net/#easeInExpo>
43    ExpoIn,
44    /// <https://easings.net/#easeInOutExpo>
45    ExpoInOut,
46    /// <https://easings.net/#easeOutExpo>
47    ExpoOut,
48    // Applies no transformation (default).
49    #[default]
50    Linear,
51    /// <https://easings.net/#easeInQuad>
52    QuadIn,
53    /// <https://easings.net/#easeInOutQuad>
54    QuadInOut,
55    /// <https://easings.net/#easeOutQuad>
56    QuadOut,
57    /// <https://easings.net/#easeInQuart>
58    QuartIn,
59    /// <https://easings.net/#easeInOutQuart>
60    QuartInOut,
61    /// <https://easings.net/#easeOutQuart>
62    QuartOut,
63    /// <https://easings.net/#easeInQuint>
64    QuintIn,
65    /// <https://easings.net/#easeInOutQuint>
66    QuintInOut,
67    /// <https://easings.net/#easeOutQuint>
68    QuintOut,
69    // A linear easing that goes from 1.0 to 0.0.
70    Reverse,
71    // A linear easing that goes from 0.0 to 1.0 and back to 0.0. That might be used in combination with other easing functions.
72    RoundTrip,
73    /// <https://easings.net/#easeInSine>
74    SineIn,
75    /// <https://easings.net/#easeInOutSine>
76    SineInOut,
77    /// <https://easings.net/#easeOutSine>
78    SineOut,
79}
80
81impl Easing {
82    /// Call the easing function.
83    pub(crate) fn call(&self, t: f32) -> f32 {
84        match self {
85            Easing::BackIn => back_in(t),
86            Easing::BackInOuT => back_in_out(t),
87            Easing::BackOut => back_out(t),
88            Easing::BounceIn => bounce_in(t),
89            Easing::BounceInOut => bounce_in_out(t),
90            Easing::BounceOut => bounce_out(t),
91            Easing::CircIn => circ_in(t),
92            Easing::CircInOut => circ_in_out(t),
93            Easing::CircOut => circ_out(t),
94            Easing::CubicIn => cubic_in(t),
95            Easing::CubicInOut => cubic_in_out(t),
96            Easing::CubicOut => cubic_out(t),
97            Easing::ElasticIn => elastic_in(t),
98            Easing::ElasticInOut => elastic_in_out(t),
99            Easing::ElasticOut => elastic_out(t),
100            Easing::ExpoIn => expo_in(t),
101            Easing::ExpoInOut => expo_in_out(t),
102            Easing::ExpoOut => expo_out(t),
103            Easing::Linear => t,
104            Easing::QuadIn => quad_in(t),
105            Easing::QuadInOut => quad_in_out(t),
106            Easing::QuadOut => quad_out(t),
107            Easing::QuartIn => quart_in(t),
108            Easing::QuartInOut => quart_in_out(t),
109            Easing::QuartOut => quart_out(t),
110            Easing::QuintIn => quint_in(t),
111            Easing::QuintInOut => quint_in_out(t),
112            Easing::QuintOut => quint_out(t),
113            Easing::Reverse => reverse(t),
114            Easing::RoundTrip => roundtrip(t),
115            Easing::SineIn => sine_in(t),
116            Easing::SineInOut => sine_in_out(t),
117            Easing::SineOut => sine_out(t),
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    fn assert_easing_approx_equal(easing: Easing, input: f32, expected: f32) {
127        let result = easing.call(input);
128        assert!(
129            (result - expected).abs() < 1e-6,
130            "Expected {}, got {}",
131            expected,
132            result
133        );
134    }
135
136    #[test]
137    fn test_back_in() {
138        assert_easing_approx_equal(Easing::BackIn, 0.0, 0.0);
139        assert_easing_approx_equal(Easing::BackIn, 0.5, -0.0876975);
140        assert_easing_approx_equal(Easing::BackIn, 1.0, 1.0);
141    }
142
143    #[test]
144    fn test_back_in_out() {
145        assert_easing_approx_equal(Easing::BackInOuT, 0.0, 0.0);
146        assert_easing_approx_equal(Easing::BackInOuT, 0.2, -0.092556);
147        assert_easing_approx_equal(Easing::BackInOuT, 0.5, 0.5);
148        assert_easing_approx_equal(Easing::BackInOuT, 0.8, 1.0925556);
149        assert_easing_approx_equal(Easing::BackInOuT, 1.0, 1.0);
150    }
151
152    #[test]
153    fn test_back_out() {
154        assert_easing_approx_equal(Easing::BackOut, 0.0, 0.0);
155        assert_easing_approx_equal(Easing::BackOut, 0.5, 1.0876975);
156        assert_easing_approx_equal(Easing::BackOut, 1.0, 1.0);
157    }
158
159    #[test]
160    fn test_bounce_in() {
161        assert_easing_approx_equal(Easing::BounceIn, 0.0, 0.0);
162        assert_easing_approx_equal(Easing::BounceIn, 0.5, 0.234375);
163        assert_easing_approx_equal(Easing::BounceIn, 1.0, 1.0);
164    }
165
166    #[test]
167    fn test_bounce_in_out() {
168        assert_easing_approx_equal(Easing::BounceInOut, 0.0, 0.0);
169        assert_easing_approx_equal(Easing::BounceInOut, 0.2, 0.113750);
170        assert_easing_approx_equal(Easing::BounceInOut, 0.5, 0.5);
171        assert_easing_approx_equal(Easing::BounceInOut, 0.8, 0.88625);
172        assert_easing_approx_equal(Easing::BounceInOut, 1.0, 1.0);
173    }
174
175    #[test]
176    fn test_bounce_out() {
177        assert_easing_approx_equal(Easing::BounceOut, 0.0, 0.0);
178        assert_easing_approx_equal(Easing::BounceOut, 0.5, 0.765625);
179        assert_easing_approx_equal(Easing::BounceOut, 1.0, 1.0);
180    }
181
182    #[test]
183    fn test_circ_in() {
184        assert_easing_approx_equal(Easing::CircIn, 0.0, 0.0);
185        assert_easing_approx_equal(Easing::CircIn, 0.5, 0.133975);
186        assert_easing_approx_equal(Easing::CircIn, 1.0, 1.0);
187    }
188
189    #[test]
190    fn test_circ_in_out() {
191        assert_easing_approx_equal(Easing::CircInOut, 0.0, 0.0);
192        assert_easing_approx_equal(Easing::CircInOut, 0.2, 0.041742);
193        assert_easing_approx_equal(Easing::CircInOut, 0.5, 0.5);
194        assert_easing_approx_equal(Easing::CircInOut, 0.8, 0.958257);
195        assert_easing_approx_equal(Easing::CircInOut, 1.0, 1.0);
196    }
197
198    #[test]
199    fn test_circ_out() {
200        assert_easing_approx_equal(Easing::CircOut, 0.0, 0.0);
201        assert_easing_approx_equal(Easing::CircOut, 0.5, 0.866025);
202        assert_easing_approx_equal(Easing::CircOut, 1.0, 1.0);
203    }
204
205    #[test]
206    fn test_cubic_in() {
207        assert_easing_approx_equal(Easing::CubicIn, 0.0, 0.0);
208        assert_easing_approx_equal(Easing::CubicIn, 0.5, 0.125);
209        assert_easing_approx_equal(Easing::CubicIn, 1.0, 1.0);
210    }
211
212    #[test]
213    fn test_cubic_in_out() {
214        assert_easing_approx_equal(Easing::CubicInOut, 0.0, 0.0);
215        assert_easing_approx_equal(Easing::CubicInOut, 0.2, 0.032);
216        assert_easing_approx_equal(Easing::CubicInOut, 0.5, 0.5);
217        assert_easing_approx_equal(Easing::CubicInOut, 0.8, 0.968);
218        assert_easing_approx_equal(Easing::CubicInOut, 1.0, 1.0);
219    }
220
221    #[test]
222    fn test_cubic_out() {
223        assert_easing_approx_equal(Easing::CubicOut, 0.0, 0.0);
224        assert_easing_approx_equal(Easing::CubicOut, 0.5, 0.875);
225        assert_easing_approx_equal(Easing::CubicOut, 1.0, 1.0);
226    }
227
228    #[test]
229    fn test_elastic_in() {
230        assert_easing_approx_equal(Easing::ElasticIn, 0.0, 0.0);
231        assert_easing_approx_equal(Easing::ElasticIn, 0.5, -0.015625);
232        assert_easing_approx_equal(Easing::ElasticIn, 1.0, 1.0);
233    }
234
235    #[test]
236    fn test_elastic_in_out() {
237        assert_easing_approx_equal(Easing::ElasticInOut, 0.0, 0.0);
238        assert_easing_approx_equal(Easing::ElasticInOut, 0.2, -0.003906);
239        assert_easing_approx_equal(Easing::ElasticInOut, 0.5, 0.5);
240        assert_easing_approx_equal(Easing::ElasticInOut, 0.8, 1.0039063);
241        assert_easing_approx_equal(Easing::ElasticInOut, 1.0, 1.0);
242    }
243
244    #[test]
245    fn test_elastic_out() {
246        assert_easing_approx_equal(Easing::ElasticOut, 0.0, 0.0);
247        assert_easing_approx_equal(Easing::ElasticOut, 0.5, 1.015625);
248        assert_easing_approx_equal(Easing::ElasticOut, 1.0, 1.0);
249    }
250
251    #[test]
252    fn test_expo_in() {
253        assert_easing_approx_equal(Easing::ExpoIn, 0.0, 0.0);
254        assert_easing_approx_equal(Easing::ExpoIn, 0.5, 0.03125);
255        assert_easing_approx_equal(Easing::ExpoIn, 1.0, 1.0);
256    }
257
258    #[test]
259    fn test_expo_in_out() {
260        assert_easing_approx_equal(Easing::ExpoInOut, 0.0, 0.0);
261        assert_easing_approx_equal(Easing::ExpoInOut, 0.2, 0.007812);
262        assert_easing_approx_equal(Easing::ExpoInOut, 0.5, 0.5);
263        assert_easing_approx_equal(Easing::ExpoInOut, 0.8, 0.992187);
264        assert_easing_approx_equal(Easing::ExpoInOut, 1.0, 1.0);
265    }
266
267    #[test]
268    fn test_expo_out() {
269        assert_easing_approx_equal(Easing::ExpoOut, 0.0, 0.0);
270        assert_easing_approx_equal(Easing::ExpoOut, 0.5, 0.96875);
271        assert_easing_approx_equal(Easing::ExpoOut, 1.0, 1.0);
272    }
273
274    #[test]
275    fn test_linear() {
276        assert_easing_approx_equal(Easing::Linear, 0.0, 0.0);
277        assert_easing_approx_equal(Easing::Linear, 0.5, 0.5);
278        assert_easing_approx_equal(Easing::Linear, 1.0, 1.0);
279    }
280
281    #[test]
282    fn test_quad_in() {
283        assert_easing_approx_equal(Easing::QuadIn, 0.0, 0.0);
284        assert_easing_approx_equal(Easing::QuadIn, 0.5, 0.25);
285        assert_easing_approx_equal(Easing::QuadIn, 1.0, 1.0);
286    }
287
288    #[test]
289    fn test_quad_in_out() {
290        assert_easing_approx_equal(Easing::QuadInOut, 0.0, 0.0);
291        assert_easing_approx_equal(Easing::QuadInOut, 0.2, 0.08000);
292        assert_easing_approx_equal(Easing::QuadInOut, 0.5, 0.5);
293        assert_easing_approx_equal(Easing::QuadInOut, 0.8, 0.92);
294        assert_easing_approx_equal(Easing::QuadInOut, 1.0, 1.0);
295    }
296
297    #[test]
298    fn test_quad_out() {
299        assert_easing_approx_equal(Easing::QuadOut, 0.0, 0.0);
300        assert_easing_approx_equal(Easing::QuadOut, 0.5, 0.75);
301        assert_easing_approx_equal(Easing::QuadOut, 1.0, 1.0);
302    }
303
304    #[test]
305    fn test_quart_in() {
306        assert_easing_approx_equal(Easing::QuartIn, 0.0, 0.0);
307        assert_easing_approx_equal(Easing::QuartIn, 0.5, 0.0625);
308        assert_easing_approx_equal(Easing::QuartIn, 1.0, 1.0);
309    }
310
311    #[test]
312    fn test_quart_in_out() {
313        assert_easing_approx_equal(Easing::QuartInOut, 0.0, 0.0);
314        assert_easing_approx_equal(Easing::QuartInOut, 0.2, 0.0128);
315        assert_easing_approx_equal(Easing::QuartInOut, 0.5, 0.5);
316        assert_easing_approx_equal(Easing::QuartInOut, 0.8, 0.9872);
317        assert_easing_approx_equal(Easing::QuartInOut, 1.0, 1.0);
318    }
319
320    #[test]
321    fn test_quart_out() {
322        assert_easing_approx_equal(Easing::QuartOut, 0.0, 0.0);
323        assert_easing_approx_equal(Easing::QuartOut, 0.5, 0.9375);
324        assert_easing_approx_equal(Easing::QuartOut, 1.0, 1.0);
325    }
326
327    #[test]
328    fn test_quint_in() {
329        assert_easing_approx_equal(Easing::QuintIn, 0.0, 0.0);
330        assert_easing_approx_equal(Easing::QuintIn, 0.5, 0.0625);
331        assert_easing_approx_equal(Easing::QuintIn, 1.0, 1.0);
332    }
333
334    #[test]
335    fn test_quint_in_out() {
336        assert_easing_approx_equal(Easing::QuintInOut, 0.0, 0.0);
337        assert_easing_approx_equal(Easing::QuintInOut, 0.2, 0.00512);
338        assert_easing_approx_equal(Easing::QuintInOut, 0.5, 0.5);
339        assert_easing_approx_equal(Easing::QuintInOut, 0.8, 0.99488);
340        assert_easing_approx_equal(Easing::QuintInOut, 1.0, 1.0);
341    }
342
343    #[test]
344    fn test_quint_out() {
345        assert_easing_approx_equal(Easing::QuintOut, 0.0, 0.0);
346        assert_easing_approx_equal(Easing::QuintOut, 0.5, 0.96875);
347        assert_easing_approx_equal(Easing::QuintOut, 1.0, 1.0);
348    }
349
350    #[test]
351    fn test_reverse() {
352        assert_eq!(Easing::Reverse.call(0.0), 1.0);
353        assert_eq!(Easing::Reverse.call(0.5), 0.5);
354        assert_eq!(Easing::Reverse.call(1.0), 0.0);
355    }
356
357    #[test]
358    fn test_roundtrip() {
359        assert_eq!(Easing::RoundTrip.call(0.0), 0.0);
360        assert_eq!(Easing::RoundTrip.call(0.5), 1.0);
361        assert_eq!(Easing::RoundTrip.call(1.0), 0.0);
362    }
363
364    #[test]
365    fn test_sine_in() {
366        assert_easing_approx_equal(Easing::SineIn, 0.0, 0.0);
367        assert_easing_approx_equal(Easing::SineIn, 0.5, 0.292893);
368        assert_easing_approx_equal(Easing::SineIn, 1.0, 1.0);
369    }
370
371    #[test]
372    fn test_sine_in_out() {
373        assert_easing_approx_equal(Easing::SineInOut, 0.0, 0.0);
374        assert_easing_approx_equal(Easing::SineInOut, 0.2, 0.0954915);
375        assert_easing_approx_equal(Easing::SineInOut, 0.5, 0.5);
376        assert_easing_approx_equal(Easing::SineInOut, 0.8, 0.9045085);
377        assert_easing_approx_equal(Easing::SineInOut, 1.0, 1.0);
378    }
379
380    #[test]
381    fn test_sine_out() {
382        assert_easing_approx_equal(Easing::SineOut, 0.0, 0.0);
383        assert_easing_approx_equal(Easing::SineOut, 0.5, std::f32::consts::FRAC_1_SQRT_2);
384        assert_easing_approx_equal(Easing::SineOut, 1.0, 1.0);
385    }
386}