vizia_core/animation/
timing_function.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#[derive(Debug, Clone, Copy)]
pub(crate) struct TimingFunction {
    x1: f32,
    x2: f32,
    y1: f32,
    y2: f32,
}

impl Default for TimingFunction {
    fn default() -> Self {
        Self::ease_in_out()
    }
}

impl TimingFunction {
    pub fn linear() -> Self {
        Self::new(0., 0., 1., 1.)
    }
    pub fn ease() -> Self {
        Self::new(0.25, 0.1, 0.25, 1.)
    }
    pub fn ease_in() -> Self {
        Self::new(0.42, 0., 1., 1.)
    }
    pub fn ease_out() -> Self {
        Self::new(0., 0., 0.58, 1.)
    }
    pub fn ease_in_out() -> Self {
        Self::new(0.42, 0., 0.58, 1.)
    }
}

impl TimingFunction {
    pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
        Self { x1, y1, x2, y2 }
    }

    pub fn value(&self, x: f32) -> f32 {
        // Linear
        if self.x1 == self.y1 && self.x2 == self.y2 {
            return x;
        }

        Self::calc_bezier(self.find_t_for_x(x), self.y1, self.y2)
    }

    fn calc_bezier(t: f32, a1: f32, a2: f32) -> f32 {
        let a = |a1: f32, a2: f32| 1.0 - 3.0 * a2 + 3.0 * a1;
        let b = |a1: f32, a2: f32| 3.0 * a2 - 6.0 * a1;
        let c = |a1: f32| 3.0 * a1;

        ((a(a1, a2) * t + b(a1, a2)) * t + c(a1)) * t
    }

    fn calc_bezier_slope(t: f32, a1: f32, a2: f32) -> f32 {
        let a = |a1: f32, a2: f32| 1.0 - 3.0 * a2 + 3.0 * a1;
        let b = |a1: f32, a2: f32| 3.0 * a2 - 6.0 * a1;
        let c = |a1: f32| 3.0 * a1;

        3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1)
    }

    fn find_t_for_x(&self, x: f32) -> f32 {
        let mut guess = x;
        let mut error = f32::MAX;
        for _ in 0..8 {
            let pos = Self::calc_bezier(guess, self.x1, self.x2);
            error = pos - x;
            if error.abs() <= 0.0000001 {
                return guess;
            }
            let slope = Self::calc_bezier_slope(guess, self.x1, self.x2);
            guess -= error / slope;
        }
        if error.abs() <= 0.0000001 {
            guess
        } else {
            x
        }
    }
}

#[cfg(test)]
mod tests {
    use super::TimingFunction;

    #[test]
    fn linear() {
        let timing_func = TimingFunction::linear();
        assert_eq!(timing_func.value(0.5), 0.5);
    }

    #[test]
    fn ease() {
        let timing_func = TimingFunction::ease();
        assert_eq!(timing_func.value(0.25), 0.4085106);
    }
}