Skip to main content

wallr_core/easing/
mod.rs

1//! Easing curves used by animation packages and validation tooling.
2
3use crate::animation::Easing;
4
5/// Evaluate an easing curve at `t` in the inclusive range `0..=1`.
6pub fn sample(curve: &Easing, t: f32) -> f32 {
7    let t = t.clamp(0.0, 1.0);
8    match curve {
9        Easing::Linear => t,
10        Easing::EaseIn => t * t * t,
11        Easing::EaseOut => 1.0 - (1.0 - t).powi(3),
12        Easing::EaseInOut => cubic_bezier(t, 0.4, 0.0, 0.2, 1.0),
13        Easing::Emphatic => cubic_bezier(t, 0.18, 0.89, 0.32, 1.28),
14        Easing::Spring => spring(t, 1.0, 170.0, 26.0),
15    }
16}
17
18/// Evaluate a cubic Bézier timing curve using Newton iteration and bisection.
19pub fn cubic_bezier(t: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
20    let t = t.clamp(0.0, 1.0);
21    let mut u = t;
22    for _ in 0..8 {
23        let x = bezier(u, x1, x2) - t;
24        let dx = 3.0 * (1.0 - u).powi(2) * x1
25            + 6.0 * (1.0 - u) * u * (x2 - x1)
26            + 3.0 * u.powi(2) * (1.0 - x2);
27        if dx.abs() < 1e-5 {
28            break;
29        }
30        u = (u - x / dx).clamp(0.0, 1.0);
31    }
32    bezier(u, y1, y2)
33}
34
35fn bezier(t: f32, p1: f32, p2: f32) -> f32 {
36    3.0 * (1.0 - t).powi(2) * t * p1 + 3.0 * (1.0 - t) * t.powi(2) * p2 + t.powi(3)
37}
38
39/// Evaluate a damped spring. Parameters are mass, stiffness, and damping.
40pub fn spring(t: f32, mass: f32, stiffness: f32, damping: f32) -> f32 {
41    let omega = (stiffness / mass.max(0.001)).sqrt();
42    let zeta = damping / (2.0 * (stiffness * mass).sqrt().max(0.001));
43    let e = (-zeta * omega * t * 6.0).exp();
44    if zeta < 1.0 {
45        let wd = omega * (1.0 - zeta * zeta).sqrt();
46        1.0 - e * ((wd * t * 6.0).cos() + zeta * omega / wd * (wd * t * 6.0).sin())
47    } else {
48        1.0 - e * (1.0 + omega * t * 6.0)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    #[test]
56    fn curves_start_and_end_at_expected_points() {
57        for curve in [Easing::Linear, Easing::EaseInOut, Easing::Emphatic] {
58            assert!((sample(&curve, 0.0)).abs() < 0.01);
59            assert!((sample(&curve, 1.0) - 1.0).abs() < 0.01);
60        }
61    }
62}