ravenrs_animation/
easing.rs1#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct CubicBezier {
5 pub p1: (f32, f32),
7 pub p2: (f32, f32),
9}
10
11impl CubicBezier {
12 pub const fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
14 Self { p1: (x1, y1), p2: (x2, y2) }
15 }
16
17 pub fn sample(&self, x: f32) -> f32 {
20 if x <= 0.0 {
21 return 0.0;
22 }
23 if x >= 1.0 {
24 return 1.0;
25 }
26 let curve = |a: f32, b: f32, t: f32| {
27 let u = 1.0 - t;
28 3.0 * u * u * t * a + 3.0 * u * t * t * b + t * t * t
29 };
30 let slope = |a: f32, b: f32, t: f32| {
31 let u = 1.0 - t;
32 3.0 * u * u * a + 6.0 * u * t * (b - a) + 3.0 * t * t * (1.0 - b)
33 };
34 let (x1, x2) = (self.p1.0.clamp(0.0, 1.0), self.p2.0.clamp(0.0, 1.0));
35 let mut t = x;
36 let mut solved = false;
37 for _ in 0..6 {
38 let error = curve(x1, x2, t) - x;
39 if error.abs() < 1e-4 {
40 solved = true;
41 break;
42 }
43 let d = slope(x1, x2, t);
44 if d.abs() < 1e-5 {
45 break;
46 }
47 t = (t - error / d).clamp(0.0, 1.0);
48 }
49 if !solved {
50 let (mut lo, mut hi) = (0.0_f32, 1.0_f32);
51 for _ in 0..20 {
52 t = (lo + hi) / 2.0;
53 if curve(x1, x2, t) < x {
54 lo = t;
55 } else {
56 hi = t;
57 }
58 }
59 }
60 curve(self.p1.1, self.p2.1, t)
61 }
62}
63
64#[derive(Clone, Copy, Debug, Default, PartialEq)]
66#[non_exhaustive]
67pub enum Easing {
68 Linear,
70 EaseIn,
72 #[default]
74 EaseOut,
75 EaseInOut,
77 Emphasized,
79 Bezier(CubicBezier),
81}
82
83impl Easing {
84 pub fn sample(&self, t: f32) -> f32 {
86 match self {
87 Self::Linear => t.clamp(0.0, 1.0),
88 Self::EaseIn => CubicBezier::new(0.42, 0.0, 1.0, 1.0).sample(t),
89 Self::EaseOut => CubicBezier::new(0.0, 0.0, 0.2, 1.0).sample(t),
90 Self::EaseInOut => CubicBezier::new(0.4, 0.0, 0.2, 1.0).sample(t),
91 Self::Emphasized => CubicBezier::new(0.05, 0.7, 0.1, 1.0).sample(t),
92 Self::Bezier(curve) => curve.sample(t),
93 }
94 }
95}