Skip to main content

euv_engine/easing/
impl.rs

1use super::*;
2
3/// The overshoot amplitude used by the `Back` easing family.
4pub(crate) const BACK_OVERSHOOT: f64 = 1.70158;
5
6/// The scaled overshoot used by `InOutBack` so both halves peak at the
7/// same amplitude as the single-direction variants.
8pub(crate) const BACK_OVERSHOOT_INOUT: f64 = BACK_OVERSHOOT * 1.525;
9
10/// The number of bounce subdivisions used by the `Bounce` easing family.
11pub(crate) const BOUNCE_DIVISIONS: f64 = 2.75;
12
13/// The period base used by the `Elastic` easing family.
14pub(crate) const ELASTIC_PERIOD: f64 = TWO_PI / 3.0;
15
16/// The period base used by the `InOutElastic` easing variant.
17pub(crate) const ELASTIC_PERIOD_INOUT: f64 = TWO_PI / 4.5;
18
19/// Implements easing curve evaluation for `Easing`.
20impl Easing {
21    /// Evaluates the easing curve at the given normalized time.
22    ///
23    /// The input is clamped into the range 0.0 to 1.0 before evaluation,
24    /// so callers may pass slightly out-of-range accumulators without
25    /// producing out-of-range output.
26    ///
27    /// # Arguments
28    ///
29    /// - `f64` - The normalized time, typically in the range 0.0 to 1.0.
30    ///
31    /// # Returns
32    ///
33    /// - `f64` - The eased value, where 0.0 maps to the start and 1.0 to the end.
34    pub fn evaluate(&self, t: f64) -> f64 {
35        let t: f64 = Numeric::clamp(t, 0.0, 1.0);
36        match self {
37            Easing::Linear => t,
38            Easing::InQuad => t * t,
39            Easing::OutQuad => 1.0 - (1.0 - t) * (1.0 - t),
40            Easing::InOutQuad => {
41                if t < 0.5 {
42                    2.0 * t * t
43                } else {
44                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
45                }
46            }
47            Easing::InCubic => t * t * t,
48            Easing::OutCubic => 1.0 - (1.0 - t).powi(3),
49            Easing::InOutCubic => {
50                if t < 0.5 {
51                    4.0 * t * t * t
52                } else {
53                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
54                }
55            }
56            Easing::InQuart => t * t * t * t,
57            Easing::OutQuart => 1.0 - (1.0 - t).powi(4),
58            Easing::InOutQuart => {
59                if t < 0.5 {
60                    8.0 * t * t * t * t
61                } else {
62                    1.0 - (-2.0 * t + 2.0).powi(4) / 2.0
63                }
64            }
65            Easing::InQuint => t * t * t * t * t,
66            Easing::OutQuint => 1.0 - (1.0 - t).powi(5),
67            Easing::InOutQuint => {
68                if t < 0.5 {
69                    16.0 * t * t * t * t * t
70                } else {
71                    1.0 - (-2.0 * t + 2.0).powi(5) / 2.0
72                }
73            }
74            Easing::InSine => 1.0 - (t * HALF_PI).cos(),
75            Easing::OutSine => (t * HALF_PI).sin(),
76            Easing::InOutSine => -((t * PI).cos() - 1.0) / 2.0,
77            Easing::InExpo => {
78                if t == 0.0 {
79                    0.0
80                } else {
81                    2.0_f64.powf(10.0 * t - 10.0)
82                }
83            }
84            Easing::OutExpo => {
85                if t == 1.0 {
86                    1.0
87                } else {
88                    1.0 - 2.0_f64.powf(-10.0 * t)
89                }
90            }
91            Easing::InOutExpo => {
92                if t == 0.0 {
93                    0.0
94                } else if t == 1.0 {
95                    1.0
96                } else if t < 0.5 {
97                    2.0_f64.powf(20.0 * t - 10.0) / 2.0
98                } else {
99                    (2.0 - 2.0_f64.powf(-20.0 * t + 10.0)) / 2.0
100                }
101            }
102            Easing::InCirc => 1.0 - (1.0 - t * t).sqrt(),
103            Easing::OutCirc => (1.0 - (t - 1.0).powi(2)).sqrt(),
104            Easing::InOutCirc => {
105                if t < 0.5 {
106                    (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0
107                } else {
108                    ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0
109                }
110            }
111            Easing::InBack => {
112                let c: f64 = BACK_OVERSHOOT;
113                (c + 1.0) * t * t * t - c * t * t
114            }
115            Easing::OutBack => {
116                let c: f64 = BACK_OVERSHOOT;
117                1.0 + (c + 1.0) * (t - 1.0).powi(3) + c * (t - 1.0).powi(2)
118            }
119            Easing::InOutBack => {
120                let c: f64 = BACK_OVERSHOOT_INOUT;
121                if t < 0.5 {
122                    ((2.0 * t).powi(2) * ((c + 1.0) * 2.0 * t - c)) / 2.0
123                } else {
124                    ((2.0 * t - 2.0).powi(2) * ((c + 1.0) * (2.0 * t - 2.0) + c) + 2.0) / 2.0
125                }
126            }
127            Easing::InElastic => {
128                if t == 0.0 {
129                    0.0
130                } else if t == 1.0 {
131                    1.0
132                } else {
133                    -2.0_f64.powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * ELASTIC_PERIOD).sin()
134                }
135            }
136            Easing::OutElastic => {
137                if t == 0.0 {
138                    0.0
139                } else if t == 1.0 {
140                    1.0
141                } else {
142                    2.0_f64.powf(-10.0 * t) * ((t * 10.0 - 0.75) * ELASTIC_PERIOD).sin() + 1.0
143                }
144            }
145            Easing::InOutElastic => {
146                if t == 0.0 {
147                    0.0
148                } else if t == 1.0 {
149                    1.0
150                } else if t < 0.5 {
151                    -(2.0_f64.powf(20.0 * t - 10.0)
152                        * ((20.0 * t - 11.125) * ELASTIC_PERIOD_INOUT).sin())
153                        / 2.0
154                } else {
155                    (2.0_f64.powf(-20.0 * t + 10.0)
156                        * ((20.0 * t - 11.125) * ELASTIC_PERIOD_INOUT).sin())
157                        / 2.0
158                        + 1.0
159                }
160            }
161            Easing::InBounce => 1.0 - Easing::OutBounce.evaluate(1.0 - t),
162            Easing::OutBounce => Easing::bounce_out(t),
163            Easing::InOutBounce => {
164                if t < 0.5 {
165                    (1.0 - Easing::bounce_out(1.0 - 2.0 * t)) / 2.0
166                } else {
167                    (1.0 + Easing::bounce_out(2.0 * t - 1.0)) / 2.0
168                }
169            }
170        }
171    }
172
173    /// Applies this easing curve to interpolate between two scalar values.
174    ///
175    /// # Arguments
176    ///
177    /// - `f64` - The start value.
178    /// - `f64` - The end value.
179    /// - `f64` - The normalized time, typically in the range 0.0 to 1.0.
180    ///
181    /// # Returns
182    ///
183    /// - `f64` - The eased interpolation between `start` and `end`.
184    pub fn interpolate(&self, start: f64, end: f64, t: f64) -> f64 {
185        Numeric::lerp(start, end, self.evaluate(t))
186    }
187
188    /// The shared `OutBounce` curve, factored out so `InBounce` and
189    /// `InOutBounce` can mirror it without duplicating the subdivision table.
190    ///
191    /// # Arguments
192    ///
193    /// - `f64` - The normalized time in the range 0.0 to 1.0.
194    ///
195    /// # Returns
196    ///
197    /// - `f64` - The bounced value.
198    fn bounce_out(t: f64) -> f64 {
199        let n: f64 = BOUNCE_DIVISIONS;
200        if t < 1.0 / n {
201            7.5625 * t * t
202        } else if t < 2.0 / n {
203            let t: f64 = t - 1.5 / n;
204            7.5625 * t * t + 0.75
205        } else if t < 2.5 / n {
206            let t: f64 = t - 2.25 / n;
207            7.5625 * t * t + 0.9375
208        } else {
209            let t: f64 = t - 2.625 / n;
210            7.5625 * t * t + 0.984375
211        }
212    }
213}