gardesk/garchomp / e75534a

Browse files

add bounce and cubic bezier easing curves

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
e75534a55123372566f5711411ea696f8517f6ee
Parents
1ea8621
Tree
49cdca3

1 changed file

StatusFile+-
M garchomp/src/compositor/animation.rs 86 1
garchomp/src/compositor/animation.rsmodified
@@ -3,12 +3,15 @@
33
 use std::time::{Duration, Instant};
44
 
55
 /// Easing function type.
6
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6
+#[derive(Debug, Clone, Copy, PartialEq)]
77
 pub enum Easing {
88
     Linear,
99
     EaseIn,
1010
     EaseOut,
1111
     EaseInOut,
12
+    Bounce,
13
+    /// Cubic bezier with control points (x1, y1, x2, y2).
14
+    CubicBezier(f32, f32, f32, f32),
1215
 }
1316
 
1417
 impl Easing {
@@ -26,6 +29,88 @@ impl Easing {
2629
                     1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
2730
                 }
2831
             }
32
+            Easing::Bounce => Self::bounce_ease_out(t),
33
+            Easing::CubicBezier(x1, y1, x2, y2) => Self::cubic_bezier(t, x1, y1, x2, y2),
34
+        }
35
+    }
36
+
37
+    /// Bounce easing (ease-out).
38
+    fn bounce_ease_out(t: f32) -> f32 {
39
+        const N1: f32 = 7.5625;
40
+        const D1: f32 = 2.75;
41
+
42
+        if t < 1.0 / D1 {
43
+            N1 * t * t
44
+        } else if t < 2.0 / D1 {
45
+            let t = t - 1.5 / D1;
46
+            N1 * t * t + 0.75
47
+        } else if t < 2.5 / D1 {
48
+            let t = t - 2.25 / D1;
49
+            N1 * t * t + 0.9375
50
+        } else {
51
+            let t = t - 2.625 / D1;
52
+            N1 * t * t + 0.984375
53
+        }
54
+    }
55
+
56
+    /// Cubic bezier easing.
57
+    /// Uses Newton-Raphson iteration to find t for the given x.
58
+    fn cubic_bezier(t: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
59
+        // For t=0 or t=1, return directly
60
+        if t <= 0.0 {
61
+            return 0.0;
62
+        }
63
+        if t >= 1.0 {
64
+            return 1.0;
65
+        }
66
+
67
+        // Newton-Raphson to find the parameter for x
68
+        let mut guess = t;
69
+        for _ in 0..8 {
70
+            let x = Self::bezier_sample(guess, x1, x2) - t;
71
+            if x.abs() < 0.001 {
72
+                break;
73
+            }
74
+            let dx = Self::bezier_slope(guess, x1, x2);
75
+            if dx.abs() < 0.000001 {
76
+                break;
77
+            }
78
+            guess -= x / dx;
79
+        }
80
+
81
+        Self::bezier_sample(guess.clamp(0.0, 1.0), y1, y2)
82
+    }
83
+
84
+    /// Sample a cubic bezier curve at parameter t.
85
+    fn bezier_sample(t: f32, p1: f32, p2: f32) -> f32 {
86
+        // B(t) = 3(1-t)²t·P1 + 3(1-t)t²·P2 + t³
87
+        let t2 = t * t;
88
+        let t3 = t2 * t;
89
+        let mt = 1.0 - t;
90
+        let mt2 = mt * mt;
91
+
92
+        3.0 * mt2 * t * p1 + 3.0 * mt * t2 * p2 + t3
93
+    }
94
+
95
+    /// Get the slope of a cubic bezier curve at parameter t.
96
+    fn bezier_slope(t: f32, p1: f32, p2: f32) -> f32 {
97
+        // B'(t) = 3(1-t)²·P1 + 6(1-t)t·(P2-P1) + 3t²·(1-P2)
98
+        let t2 = t * t;
99
+        let mt = 1.0 - t;
100
+        let mt2 = mt * mt;
101
+
102
+        3.0 * mt2 * p1 + 6.0 * mt * t * (p2 - p1) + 3.0 * t2 * (1.0 - p2)
103
+    }
104
+
105
+    /// Parse easing from name string.
106
+    pub fn from_name(name: &str) -> Self {
107
+        match name.to_lowercase().as_str() {
108
+            "linear" => Self::Linear,
109
+            "ease-in" | "easein" => Self::EaseIn,
110
+            "ease-out" | "easeout" => Self::EaseOut,
111
+            "ease-in-out" | "easeinout" => Self::EaseInOut,
112
+            "bounce" => Self::Bounce,
113
+            _ => Self::EaseOut, // Default
29114
         }
30115
     }
31116
 }