zng_var/animation/
easing.rs

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