zng_var/animation/
easing.rs

1//! Common easing functions.
2
3use std::f32::consts::{FRAC_PI_2, TAU};
4
5use super::*;
6
7/// Easing function output.
8///
9/// Usually in the [0..=1] range, but can overshoot. An easing function converts a [`EasingTime`]
10/// into this factor.
11///
12/// # Examples
13///
14/// ```
15/// use zng_unit::*;
16/// use zng_var::animation::easing::{EasingStep, EasingTime};
17///
18/// /// Cubic animation curve.
19/// fn cubic(time: EasingTime) -> EasingStep {
20///     let f = time.fct();
21///     f * f * f
22/// }
23/// ```
24///
25/// Note that all the common easing functions are implemented in [`easing`].
26pub type EasingStep = Factor;
27
28/// Easing function input.
29///
30/// An easing function converts this time into a [`EasingStep`] factor.
31///
32/// The time is always in the [0..=1] range, factors are clamped to this range on creation.
33#[derive(Debug, PartialEq, Copy, Clone, Hash, PartialOrd)]
34pub struct EasingTime(Factor);
35impl_from_and_into_var! {
36    fn from(factor: Factor) -> EasingTime {
37        EasingTime::new(factor)
38    }
39}
40impl EasingTime {
41    /// New from [`Factor`].
42    ///
43    /// The `factor` is clamped to the [0..=1] range.
44    ///
45    /// [`Factor`]: zng_unit::Factor
46    pub fn new(factor: Factor) -> Self {
47        EasingTime(factor.clamp_range())
48    }
49
50    /// New easing time from total `duration`, `elapsed` time and `time_scale`.
51    ///
52    /// If `elapsed >= duration` the time is 1.
53    pub fn elapsed(duration: Duration, elapsed: Duration, time_scale: Factor) -> Self {
54        EasingTime::new(elapsed.as_secs_f32().fct() / duration.as_secs_f32().fct() * time_scale)
55    }
56
57    /// Gets the start time, zero.
58    pub fn start() -> Self {
59        EasingTime(0.fct())
60    }
61
62    /// Gets the end time, one.
63    pub fn end() -> Self {
64        EasingTime(1.fct())
65    }
66
67    /// If the time represents the start of the animation.
68    pub fn is_start(self) -> bool {
69        self == Self::start()
70    }
71
72    /// If the time represents the end of the animation.
73    pub fn is_end(self) -> bool {
74        self == Self::end()
75    }
76
77    /// Get the time as a [`Factor`].
78    ///
79    /// [`Factor`]: zng_unit::Factor
80    pub fn fct(self) -> Factor {
81        self.0
82    }
83
84    /// Get the time as a [`FactorPercent`].
85    ///
86    /// [`FactorPercent`]: zng_unit::FactorPercent
87    pub fn pct(self) -> FactorPercent {
88        self.0.0.pct()
89    }
90
91    /// Flip the time.
92    ///
93    /// Returns `1 - self`.
94    pub fn reverse(self) -> Self {
95        EasingTime(self.0.flip())
96    }
97}
98impl ops::Add for EasingTime {
99    type Output = Self;
100
101    fn add(self, rhs: Self) -> Self::Output {
102        Self(self.0 + rhs.0)
103    }
104}
105impl ops::AddAssign for EasingTime {
106    fn add_assign(&mut self, rhs: Self) {
107        self.0 += rhs.0;
108    }
109}
110impl ops::Sub for EasingTime {
111    type Output = Self;
112
113    fn sub(self, rhs: Self) -> Self::Output {
114        Self(self.0 - rhs.0)
115    }
116}
117impl ops::SubAssign for EasingTime {
118    fn sub_assign(&mut self, rhs: Self) {
119        self.0 -= rhs.0;
120    }
121}
122
123/// Easing functions as a value.
124#[derive(Clone)]
125pub enum EasingFn {
126    /// [`easing::linear`].
127    Linear,
128    /// [`easing::sine`].
129    Sine,
130    /// [`easing::quad`].
131    Quad,
132    /// [`easing::cubic`].
133    Cubic,
134    /// [`easing::quart`].
135    Quart,
136    /// [`easing::quint`].
137    Quint,
138    /// [`easing::expo`].
139    Expo,
140    /// [`easing::circ`].
141    Circ,
142    /// [`easing::back`].
143    Back,
144    /// [`easing::elastic`].
145    Elastic,
146    /// [`easing::bounce`].
147    Bounce,
148    /// [`easing::none`].
149    None,
150    ///Custom function.
151    Custom(Arc<dyn Fn(EasingTime) -> EasingStep + Send + Sync>),
152}
153impl PartialEq for EasingFn {
154    fn eq(&self, other: &Self) -> bool {
155        match (self, other) {
156            (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
157            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
158        }
159    }
160}
161impl Eq for EasingFn {}
162impl fmt::Debug for EasingFn {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match self {
165            Self::Linear => write!(f, "linear"),
166            Self::Sine => write!(f, "sine"),
167            Self::Quad => write!(f, "quad"),
168            Self::Cubic => write!(f, "cubic"),
169            Self::Quart => write!(f, "quart"),
170            Self::Quint => write!(f, "quint"),
171            Self::Expo => write!(f, "expo"),
172            Self::Circ => write!(f, "circ"),
173            Self::Back => write!(f, "back"),
174            Self::Elastic => write!(f, "elastic"),
175            Self::Bounce => write!(f, "bounce"),
176            Self::None => write!(f, "none"),
177            Self::Custom(_) => f.debug_tuple("Custom").finish(),
178        }
179    }
180}
181impl EasingFn {
182    /// Create a closure that calls the easing function.
183    pub fn ease_fn(&self) -> impl Fn(EasingTime) -> EasingStep + Send + Sync + 'static {
184        let me = self.clone();
185        move |t| me(t)
186    }
187
188    /// New custom function.
189    pub fn custom(f: impl Fn(EasingTime) -> EasingStep + Send + Sync + 'static) -> Self {
190        Self::Custom(Arc::new(f))
191    }
192
193    /// Creates a custom function that is `self` modified by `modifier`
194    pub fn modified(self, modifier: impl Fn(&dyn Fn(EasingTime) -> EasingStep, EasingTime) -> EasingStep + Send + Sync + 'static) -> Self {
195        Self::custom(move |t| modifier(&*self, t))
196    }
197
198    /// Creates a custom function that is `self` modified by [`easing::ease_out`].
199    pub fn ease_out(self) -> Self {
200        self.modified(|f, t| easing::ease_out(f, t))
201    }
202
203    /// Creates a custom function that is `self` modified by [`easing::ease_in_out`].
204    pub fn ease_in_out(self) -> Self {
205        self.modified(|f, t| easing::ease_in_out(f, t))
206    }
207
208    /// Creates a custom function that is `self` modified by [`easing::ease_out_in`].
209    pub fn ease_out_in(self) -> Self {
210        self.modified(|f, t| easing::ease_out_in(f, t))
211    }
212
213    /// Creates a custom function that is `self` modified by [`easing::reverse`].
214    pub fn reverse(self) -> Self {
215        self.modified(|f, t| easing::reverse(f, t))
216    }
217
218    /// Creates a custom function that is `self` modified by [`easing::reverse_out`].
219    pub fn reverse_out(self) -> Self {
220        self.modified(|f, t| easing::reverse_out(f, t))
221    }
222}
223impl ops::Deref for EasingFn {
224    type Target = dyn Fn(EasingTime) -> EasingStep + Send + Sync;
225
226    fn deref(&self) -> &Self::Target {
227        match self {
228            EasingFn::Linear => &easing::linear,
229            EasingFn::Sine => &easing::sine,
230            EasingFn::Quad => &easing::quad,
231            EasingFn::Cubic => &easing::cubic,
232            EasingFn::Quart => &easing::quad,
233            EasingFn::Quint => &easing::quint,
234            EasingFn::Expo => &easing::expo,
235            EasingFn::Circ => &easing::circ,
236            EasingFn::Back => &easing::back,
237            EasingFn::Elastic => &easing::elastic,
238            EasingFn::Bounce => &easing::bounce,
239            EasingFn::None => &easing::none,
240            EasingFn::Custom(c) => &**c,
241        }
242    }
243}
244
245/// Simple linear transition, no easing, no acceleration.
246pub fn linear(time: EasingTime) -> EasingStep {
247    time.fct()
248}
249
250/// Quadratic transition (t²).
251pub fn quad(time: EasingTime) -> EasingStep {
252    let f = time.fct();
253    f * f
254}
255
256/// Cubic transition (t³).
257pub fn cubic(time: EasingTime) -> EasingStep {
258    let f = time.fct();
259    f * f * f
260}
261
262/// Fourth power transition (t⁴).
263pub fn quart(time: EasingTime) -> EasingStep {
264    let f = time.fct();
265    f * f * f * f
266}
267
268/// Fifth power transition (t⁵).
269pub fn quint(time: EasingTime) -> EasingStep {
270    let f = time.fct();
271    f * f * f * f * f
272}
273
274/// Sine transition. Slow start, fast end.
275pub fn sine(time: EasingTime) -> EasingStep {
276    let f = time.fct().0;
277    (1.0 - (f * FRAC_PI_2).cos()).fct()
278}
279
280/// Exponential transition. Very slow start, very fast end.
281pub fn expo(time: EasingTime) -> EasingStep {
282    let f = time.fct();
283    if f == 0.fct() {
284        0.fct()
285    } else {
286        2.0_f32.powf(10.0 * f.0 - 10.0).fct()
287    }
288}
289
290/// Cubic transition with slightly slowed start then [`cubic`].
291pub fn circ(time: EasingTime) -> EasingStep {
292    let f = time.fct().0;
293    (1.0 - (1.0 - f.powf(2.0)).sqrt()).fct()
294}
295
296/// Cubic transition that goes slightly negative to start and ends very fast.
297///
298/// Like it backs-up and the shoots out.
299pub fn back(time: EasingTime) -> EasingStep {
300    let f = time.fct().0;
301    (f * f * (2.70158 * f - 1.70158)).fct()
302}
303
304/// Oscillating transition that grows in magnitude, goes negative twice.
305pub fn elastic(time: EasingTime) -> EasingStep {
306    let t = time.fct();
307
308    const C: f32 = TAU / 3.0;
309
310    if t == 0.fct() || t == 1.fct() {
311        t
312    } else {
313        let t = t.0;
314        let s = -(2.0_f32.powf(10.0 * t - 10.0)) * ((t * 10.0 - 10.75) * C).sin();
315        s.fct()
316    }
317}
318
319/// Oscillating transition that grows in magnitude, does not go negative, when the curve
320/// is about to go negative it sharply transitions to a new arc of larger magnitude.
321pub fn bounce(time: EasingTime) -> EasingStep {
322    const N: f32 = 7.5625;
323    const D: f32 = 2.75;
324
325    let mut t = 1.0 - time.fct().0;
326
327    let f = if t < 1.0 / D {
328        N * t * t
329    } else if t < 2.0 / D {
330        t -= 1.5 / D;
331        N * t * t + 0.75
332    } else if t < 2.5 / D {
333        t -= 2.25 / D;
334        N * t * t + 0.9375
335    } else {
336        t -= 2.625 / D;
337        N * t * t + 0.984375
338    };
339
340    (1.0 - f).fct()
341}
342
343/// X coordinate is time, Y coordinate is function advancement.
344/// The nominal range for both is 0 to 1.
345///
346/// The start and end points are always (0, 0) and (1, 1) so that a transition or animation
347/// starts at 0% and ends at 100%.
348pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32, time: EasingTime) -> EasingStep {
349    let f = time.fct().0 as f64;
350    (Bezier::new(x1, y1, x2, y2).solve(f, 0.00001) as f32).fct()
351}
352
353/// Jumps to the final value by a number of `steps`.
354///
355/// Starts from the first step value immediately.
356pub fn step_ceil(steps: u32, time: EasingTime) -> EasingStep {
357    let steps = steps as f32;
358    let step = (steps * time.fct().0).ceil();
359    (step / steps).fct()
360}
361
362/// Jumps to the final value by a number of `steps`.
363///
364/// Waits until first step to output the first step value.
365pub fn step_floor(steps: u32, time: EasingTime) -> EasingStep {
366    let steps = steps as f32;
367    let step = (steps * time.fct().0).floor();
368    (step / steps).fct()
369}
370
371/// Always `1.fct()`, that is, the completed transition.
372pub fn none(_: EasingTime) -> EasingStep {
373    1.fct()
374}
375
376/// Applies the `ease_fn`.
377pub fn ease_in(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
378    ease_fn(time)
379}
380
381/// Applies the `ease_fn` in reverse and flipped.
382pub fn ease_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
383    ease_fn(time.reverse()).flip()
384}
385
386/// Applies [`ease_in`] for the first half then [`ease_out`] scaled to fit a single duration (1.0).
387pub fn ease_in_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
388    let t = time.fct();
389    if t <= 0.5.fct() {
390        ease_in(&ease_fn, EasingTime::new(t * 2.fct())) / 2.fct()
391    } else {
392        ease_out(ease_fn, EasingTime::new((t - 0.5.fct()) * 2.fct())) / 2.fct() + 0.5.fct()
393    }
394}
395
396/// Applies [`ease_out`] for the first half then [`ease_in`] scaled to fit a single duration (1.0).
397pub fn ease_out_in(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
398    let t = time.fct();
399    if t <= 0.5.fct() {
400        ease_out(&ease_fn, EasingTime::new(t * 2.fct())) / 2.fct()
401    } else {
402        ease_in(ease_fn, EasingTime::new((t - 0.5.fct()) * 2.fct())) / 2.fct() + 0.5.fct()
403    }
404}
405
406/// Applies the `ease_fn` in reverse.
407pub fn reverse(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
408    ease_fn(time.reverse())
409}
410
411/// Applies the `ease_fn` flipped.
412pub fn reverse_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
413    ease_fn(time).flip()
414}
415
416pub use bezier::*;
417use zng_unit::FactorPercent;
418
419mod bezier {
420    /* This Source Code Form is subject to the terms of the Mozilla Public
421     * License, v. 2.0. If a copy of the MPL was not distributed with this
422     * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
423
424    const NEWTON_METHOD_ITERATIONS: u8 = 8;
425
426    /// A unit cubic Bézier curve, used for timing functions in CSS transitions and animations.
427    pub struct Bezier {
428        ax: f64,
429        bx: f64,
430        cx: f64,
431        ay: f64,
432        by: f64,
433        cy: f64,
434    }
435
436    impl Bezier {
437        /// Create a unit cubic Bézier curve from the two middle control points.
438        ///
439        /// X coordinate is time, Y coordinate is function advancement.
440        /// The nominal range for both is 0 to 1.
441        ///
442        /// The start and end points are always (0, 0) and (1, 1) so that a transition or animation
443        /// starts at 0% and ends at 100%.
444        pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Bezier {
445            let cx = 3. * x1 as f64;
446            let bx = 3. * (x2 as f64 - x1 as f64) - cx;
447
448            let cy = 3. * y1 as f64;
449            let by = 3. * (y2 as f64 - y1 as f64) - cy;
450
451            Bezier {
452                ax: 1.0 - cx - bx,
453                bx,
454                cx,
455                ay: 1.0 - cy - by,
456                by,
457                cy,
458            }
459        }
460
461        fn sample_curve_x(&self, t: f64) -> f64 {
462            // ax * t^3 + bx * t^2 + cx * t
463            ((self.ax * t + self.bx) * t + self.cx) * t
464        }
465
466        fn sample_curve_y(&self, t: f64) -> f64 {
467            ((self.ay * t + self.by) * t + self.cy) * t
468        }
469
470        fn sample_curve_derivative_x(&self, t: f64) -> f64 {
471            (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
472        }
473
474        fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
475            // Fast path: Use Newton's method.
476            let mut t = x;
477            for _ in 0..NEWTON_METHOD_ITERATIONS {
478                let x2 = self.sample_curve_x(t);
479                if x2.approx_eq(x, epsilon) {
480                    return t;
481                }
482                let dx = self.sample_curve_derivative_x(t);
483                if dx.approx_eq(0.0, 1e-6) {
484                    break;
485                }
486                t -= (x2 - x) / dx;
487            }
488
489            // Slow path: Use bisection.
490            let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
491
492            if t < lo {
493                return lo;
494            }
495            if t > hi {
496                return hi;
497            }
498
499            while lo < hi {
500                let x2 = self.sample_curve_x(t);
501                if x2.approx_eq(x, epsilon) {
502                    return t;
503                }
504                if x > x2 {
505                    lo = t
506                } else {
507                    hi = t
508                }
509                t = (hi - lo) / 2.0 + lo
510            }
511
512            t
513        }
514
515        /// Solve the bezier curve for a given `x` and an `epsilon`, that should be
516        /// between zero and one.
517        pub fn solve(&self, x: f64, epsilon: f64) -> f64 {
518            self.sample_curve_y(self.solve_curve_x(x, epsilon))
519        }
520    }
521
522    trait ApproxEq {
523        fn approx_eq(self, value: Self, epsilon: Self) -> bool;
524    }
525
526    impl ApproxEq for f64 {
527        fn approx_eq(self, value: f64, epsilon: f64) -> bool {
528            (self - value).abs() < epsilon
529        }
530    }
531}