bevy_math/curve/
easing.rs

1//! Module containing different [easing functions] to control the transition between two values and
2//! the [`EasingCurve`] struct to make use of them.
3//!
4//! [easing functions]: EaseFunction
5
6use crate::{
7    curve::{FunctionCurve, Interval},
8    Curve, Dir2, Dir3, Dir3A, Quat, Rot2, VectorSpace,
9};
10
11// TODO: Think about merging `Ease` with `StableInterpolate`
12
13/// A type whose values can be eased between.
14///
15/// This requires the construction of an interpolation curve that actually extends
16/// beyond the curve segment that connects two values, because an easing curve may
17/// extrapolate before the starting value and after the ending value. This is
18/// especially common in easing functions that mimic elastic or springlike behavior.
19pub trait Ease: Sized {
20    /// Given `start` and `end` values, produce a curve with [unlimited domain]
21    /// that:
22    /// - takes a value equivalent to `start` at `t = 0`
23    /// - takes a value equivalent to `end` at `t = 1`
24    /// - has constant speed everywhere, including outside of `[0, 1]`
25    ///
26    /// [unlimited domain]: Interval::EVERYWHERE
27    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self>;
28}
29
30impl<V: VectorSpace> Ease for V {
31    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
32        FunctionCurve::new(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
33    }
34}
35
36impl Ease for Rot2 {
37    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
38        FunctionCurve::new(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
39    }
40}
41
42impl Ease for Quat {
43    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
44        let dot = start.dot(end);
45        let end_adjusted = if dot < 0.0 { -end } else { end };
46        let difference = end_adjusted * start.inverse();
47        let (axis, angle) = difference.to_axis_angle();
48        FunctionCurve::new(Interval::EVERYWHERE, move |s| {
49            Quat::from_axis_angle(axis, angle * s) * start
50        })
51    }
52}
53
54impl Ease for Dir2 {
55    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
56        FunctionCurve::new(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
57    }
58}
59
60impl Ease for Dir3 {
61    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
62        let difference_quat = Quat::from_rotation_arc(start.as_vec3(), end.as_vec3());
63        Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
64    }
65}
66
67impl Ease for Dir3A {
68    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
69        let difference_quat =
70            Quat::from_rotation_arc(start.as_vec3a().into(), end.as_vec3a().into());
71        Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
72    }
73}
74
75/// A [`Curve`] that is defined by
76///
77/// - an initial `start` sample value at `t = 0`
78/// - a final `end` sample value at `t = 1`
79/// - an [easing function] to interpolate between the two values.
80///
81/// The resulting curve's domain is always [the unit interval].
82///
83/// [easing function]: EaseFunction
84/// [the unit interval]: Interval::UNIT
85#[derive(Clone, Debug)]
86#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
87#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
88pub struct EasingCurve<T> {
89    start: T,
90    end: T,
91    ease_fn: EaseFunction,
92}
93
94impl<T> EasingCurve<T> {
95    /// Given a `start` and `end` value, create a curve parametrized over [the unit interval]
96    /// that connects them, using the given [ease function] to determine the form of the
97    /// curve in between.
98    ///
99    /// [the unit interval]: Interval::UNIT
100    /// [ease function]: EaseFunction
101    pub fn new(start: T, end: T, ease_fn: EaseFunction) -> Self {
102        Self {
103            start,
104            end,
105            ease_fn,
106        }
107    }
108}
109
110impl<T> Curve<T> for EasingCurve<T>
111where
112    T: Ease + Clone,
113{
114    #[inline]
115    fn domain(&self) -> Interval {
116        Interval::UNIT
117    }
118
119    #[inline]
120    fn sample_unchecked(&self, t: f32) -> T {
121        let remapped_t = self.ease_fn.eval(t);
122        T::interpolating_curve_unbounded(self.start.clone(), self.end.clone())
123            .sample_unchecked(remapped_t)
124    }
125}
126
127/// Curve functions over the [unit interval], commonly used for easing transitions.
128///
129/// [unit interval]: `Interval::UNIT`
130#[derive(Debug, Copy, Clone, PartialEq)]
131#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
132#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
133pub enum EaseFunction {
134    /// `f(t) = t`
135    Linear,
136
137    /// `f(t) = t²`
138    QuadraticIn,
139    /// `f(t) = -(t * (t - 2.0))`
140    QuadraticOut,
141    /// Behaves as `EaseFunction::QuadraticIn` for t < 0.5 and as `EaseFunction::QuadraticOut` for t >= 0.5
142    QuadraticInOut,
143
144    /// `f(t) = t³`
145    CubicIn,
146    /// `f(t) = (t - 1.0)³ + 1.0`
147    CubicOut,
148    /// Behaves as `EaseFunction::CubicIn` for t < 0.5 and as `EaseFunction::CubicOut` for t >= 0.5
149    CubicInOut,
150
151    /// `f(t) = t⁴`
152    QuarticIn,
153    /// `f(t) = (t - 1.0)³ * (1.0 - t) + 1.0`
154    QuarticOut,
155    /// Behaves as `EaseFunction::QuarticIn` for t < 0.5 and as `EaseFunction::QuarticOut` for t >= 0.5
156    QuarticInOut,
157
158    /// `f(t) = t⁵`
159    QuinticIn,
160    /// `f(t) = (t - 1.0)⁵ + 1.0`
161    QuinticOut,
162    /// Behaves as `EaseFunction::QuinticIn` for t < 0.5 and as `EaseFunction::QuinticOut` for t >= 0.5
163    QuinticInOut,
164
165    /// `f(t) = 1.0 - cos(t * π / 2.0)`
166    SineIn,
167    /// `f(t) = sin(t * π / 2.0)`
168    SineOut,
169    /// Behaves as `EaseFunction::SineIn` for t < 0.5 and as `EaseFunction::SineOut` for t >= 0.5
170    SineInOut,
171
172    /// `f(t) = 1.0 - sqrt(1.0 - t²)`
173    CircularIn,
174    /// `f(t) = sqrt((2.0 - t) * t)`
175    CircularOut,
176    /// Behaves as `EaseFunction::CircularIn` for t < 0.5 and as `EaseFunction::CircularOut` for t >= 0.5
177    CircularInOut,
178
179    /// `f(t) = 2.0^(10.0 * (t - 1.0))`
180    ExponentialIn,
181    /// `f(t) = 1.0 - 2.0^(-10.0 * t)`
182    ExponentialOut,
183    /// Behaves as `EaseFunction::ExponentialIn` for t < 0.5 and as `EaseFunction::ExponentialOut` for t >= 0.5
184    ExponentialInOut,
185
186    /// `f(t) = -2.0^(10.0 * t - 10.0) * sin((t * 10.0 - 10.75) * 2.0 * π / 3.0)`
187    ElasticIn,
188    /// `f(t) = 2.0^(-10.0 * t) * sin((t * 10.0 - 0.75) * 2.0 * π / 3.0) + 1.0`
189    ElasticOut,
190    /// Behaves as `EaseFunction::ElasticIn` for t < 0.5 and as `EaseFunction::ElasticOut` for t >= 0.5
191    ElasticInOut,
192
193    /// `f(t) = 2.70158 * t³ - 1.70158 * t²`
194    BackIn,
195    /// `f(t) = 1.0 +  2.70158 * (t - 1.0)³ - 1.70158 * (t - 1.0)²`
196    BackOut,
197    /// Behaves as `EaseFunction::BackIn` for t < 0.5 and as `EaseFunction::BackOut` for t >= 0.5
198    BackInOut,
199
200    /// bouncy at the start!
201    BounceIn,
202    /// bouncy at the end!
203    BounceOut,
204    /// Behaves as `EaseFunction::BounceIn` for t < 0.5 and as `EaseFunction::BounceOut` for t >= 0.5
205    BounceInOut,
206
207    /// `n` steps connecting the start and the end
208    Steps(usize),
209
210    /// `f(omega,t) = 1 - (1 - t)²(2sin(omega * t) / omega + cos(omega * t))`, parametrized by `omega`
211    Elastic(f32),
212}
213
214mod easing_functions {
215    use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, PI};
216
217    use crate::{ops, FloatPow};
218
219    #[inline]
220    pub(crate) fn linear(t: f32) -> f32 {
221        t
222    }
223
224    #[inline]
225    pub(crate) fn quadratic_in(t: f32) -> f32 {
226        t.squared()
227    }
228    #[inline]
229    pub(crate) fn quadratic_out(t: f32) -> f32 {
230        1.0 - (1.0 - t).squared()
231    }
232    #[inline]
233    pub(crate) fn quadratic_in_out(t: f32) -> f32 {
234        if t < 0.5 {
235            2.0 * t.squared()
236        } else {
237            1.0 - (-2.0 * t + 2.0).squared() / 2.0
238        }
239    }
240
241    #[inline]
242    pub(crate) fn cubic_in(t: f32) -> f32 {
243        t.cubed()
244    }
245    #[inline]
246    pub(crate) fn cubic_out(t: f32) -> f32 {
247        1.0 - (1.0 - t).cubed()
248    }
249    #[inline]
250    pub(crate) fn cubic_in_out(t: f32) -> f32 {
251        if t < 0.5 {
252            4.0 * t.cubed()
253        } else {
254            1.0 - (-2.0 * t + 2.0).cubed() / 2.0
255        }
256    }
257
258    #[inline]
259    pub(crate) fn quartic_in(t: f32) -> f32 {
260        t * t * t * t
261    }
262    #[inline]
263    pub(crate) fn quartic_out(t: f32) -> f32 {
264        1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
265    }
266    #[inline]
267    pub(crate) fn quartic_in_out(t: f32) -> f32 {
268        if t < 0.5 {
269            8.0 * t * t * t * t
270        } else {
271            1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
272        }
273    }
274
275    #[inline]
276    pub(crate) fn quintic_in(t: f32) -> f32 {
277        t * t * t * t * t
278    }
279    #[inline]
280    pub(crate) fn quintic_out(t: f32) -> f32 {
281        1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
282    }
283    #[inline]
284    pub(crate) fn quintic_in_out(t: f32) -> f32 {
285        if t < 0.5 {
286            16.0 * t * t * t * t * t
287        } else {
288            1.0 - (-2.0 * t + 2.0)
289                * (-2.0 * t + 2.0)
290                * (-2.0 * t + 2.0)
291                * (-2.0 * t + 2.0)
292                * (-2.0 * t + 2.0)
293                / 2.0
294        }
295    }
296
297    #[inline]
298    pub(crate) fn sine_in(t: f32) -> f32 {
299        1.0 - ops::cos(t * FRAC_PI_2)
300    }
301    #[inline]
302    pub(crate) fn sine_out(t: f32) -> f32 {
303        ops::sin(t * FRAC_PI_2)
304    }
305    #[inline]
306    pub(crate) fn sine_in_out(t: f32) -> f32 {
307        -(ops::cos(PI * t) - 1.0) / 2.0
308    }
309
310    #[inline]
311    pub(crate) fn circular_in(t: f32) -> f32 {
312        1.0 - (1.0 - t.squared()).sqrt()
313    }
314    #[inline]
315    pub(crate) fn circular_out(t: f32) -> f32 {
316        (1.0 - (t - 1.0).squared()).sqrt()
317    }
318    #[inline]
319    pub(crate) fn circular_in_out(t: f32) -> f32 {
320        if t < 0.5 {
321            (1.0 - (1.0 - (2.0 * t).squared()).sqrt()) / 2.0
322        } else {
323            ((1.0 - (-2.0 * t + 2.0).squared()).sqrt() + 1.0) / 2.0
324        }
325    }
326
327    #[inline]
328    pub(crate) fn exponential_in(t: f32) -> f32 {
329        ops::powf(2.0, 10.0 * t - 10.0)
330    }
331    #[inline]
332    pub(crate) fn exponential_out(t: f32) -> f32 {
333        1.0 - ops::powf(2.0, -10.0 * t)
334    }
335    #[inline]
336    pub(crate) fn exponential_in_out(t: f32) -> f32 {
337        if t < 0.5 {
338            ops::powf(2.0, 20.0 * t - 10.0) / 2.0
339        } else {
340            (2.0 - ops::powf(2.0, -20.0 * t + 10.0)) / 2.0
341        }
342    }
343
344    #[inline]
345    pub(crate) fn back_in(t: f32) -> f32 {
346        let c = 1.70158;
347
348        (c + 1.0) * t.cubed() - c * t.squared()
349    }
350    #[inline]
351    pub(crate) fn back_out(t: f32) -> f32 {
352        let c = 1.70158;
353
354        1.0 + (c + 1.0) * (t - 1.0).cubed() + c * (t - 1.0).squared()
355    }
356    #[inline]
357    pub(crate) fn back_in_out(t: f32) -> f32 {
358        let c1 = 1.70158;
359        let c2 = c1 + 1.525;
360
361        if t < 0.5 {
362            (2.0 * t).squared() * ((c2 + 1.0) * 2.0 * t - c2) / 2.0
363        } else {
364            ((2.0 * t - 2.0).squared() * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
365        }
366    }
367
368    #[inline]
369    pub(crate) fn elastic_in(t: f32) -> f32 {
370        -ops::powf(2.0, 10.0 * t - 10.0) * ops::sin((t * 10.0 - 10.75) * 2.0 * FRAC_PI_3)
371    }
372    #[inline]
373    pub(crate) fn elastic_out(t: f32) -> f32 {
374        ops::powf(2.0, -10.0 * t) * ops::sin((t * 10.0 - 0.75) * 2.0 * FRAC_PI_3) + 1.0
375    }
376    #[inline]
377    pub(crate) fn elastic_in_out(t: f32) -> f32 {
378        let c = (2.0 * PI) / 4.5;
379
380        if t < 0.5 {
381            -ops::powf(2.0, 20.0 * t - 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0
382        } else {
383            ops::powf(2.0, -20.0 * t + 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0 + 1.0
384        }
385    }
386
387    #[inline]
388    pub(crate) fn bounce_in(t: f32) -> f32 {
389        1.0 - bounce_out(1.0 - t)
390    }
391    #[inline]
392    pub(crate) fn bounce_out(t: f32) -> f32 {
393        if t < 4.0 / 11.0 {
394            (121.0 * t.squared()) / 16.0
395        } else if t < 8.0 / 11.0 {
396            (363.0 / 40.0 * t.squared()) - (99.0 / 10.0 * t) + 17.0 / 5.0
397        } else if t < 9.0 / 10.0 {
398            (4356.0 / 361.0 * t.squared()) - (35442.0 / 1805.0 * t) + 16061.0 / 1805.0
399        } else {
400            (54.0 / 5.0 * t.squared()) - (513.0 / 25.0 * t) + 268.0 / 25.0
401        }
402    }
403    #[inline]
404    pub(crate) fn bounce_in_out(t: f32) -> f32 {
405        if t < 0.5 {
406            (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
407        } else {
408            (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
409        }
410    }
411
412    #[inline]
413    pub(crate) fn steps(num_steps: usize, t: f32) -> f32 {
414        (t * num_steps as f32).round() / num_steps.max(1) as f32
415    }
416
417    #[inline]
418    pub(crate) fn elastic(omega: f32, t: f32) -> f32 {
419        1.0 - (1.0 - t).squared() * (2.0 * ops::sin(omega * t) / omega + ops::cos(omega * t))
420    }
421}
422
423impl EaseFunction {
424    fn eval(&self, t: f32) -> f32 {
425        match self {
426            EaseFunction::Linear => easing_functions::linear(t),
427            EaseFunction::QuadraticIn => easing_functions::quadratic_in(t),
428            EaseFunction::QuadraticOut => easing_functions::quadratic_out(t),
429            EaseFunction::QuadraticInOut => easing_functions::quadratic_in_out(t),
430            EaseFunction::CubicIn => easing_functions::cubic_in(t),
431            EaseFunction::CubicOut => easing_functions::cubic_out(t),
432            EaseFunction::CubicInOut => easing_functions::cubic_in_out(t),
433            EaseFunction::QuarticIn => easing_functions::quartic_in(t),
434            EaseFunction::QuarticOut => easing_functions::quartic_out(t),
435            EaseFunction::QuarticInOut => easing_functions::quartic_in_out(t),
436            EaseFunction::QuinticIn => easing_functions::quintic_in(t),
437            EaseFunction::QuinticOut => easing_functions::quintic_out(t),
438            EaseFunction::QuinticInOut => easing_functions::quintic_in_out(t),
439            EaseFunction::SineIn => easing_functions::sine_in(t),
440            EaseFunction::SineOut => easing_functions::sine_out(t),
441            EaseFunction::SineInOut => easing_functions::sine_in_out(t),
442            EaseFunction::CircularIn => easing_functions::circular_in(t),
443            EaseFunction::CircularOut => easing_functions::circular_out(t),
444            EaseFunction::CircularInOut => easing_functions::circular_in_out(t),
445            EaseFunction::ExponentialIn => easing_functions::exponential_in(t),
446            EaseFunction::ExponentialOut => easing_functions::exponential_out(t),
447            EaseFunction::ExponentialInOut => easing_functions::exponential_in_out(t),
448            EaseFunction::ElasticIn => easing_functions::elastic_in(t),
449            EaseFunction::ElasticOut => easing_functions::elastic_out(t),
450            EaseFunction::ElasticInOut => easing_functions::elastic_in_out(t),
451            EaseFunction::BackIn => easing_functions::back_in(t),
452            EaseFunction::BackOut => easing_functions::back_out(t),
453            EaseFunction::BackInOut => easing_functions::back_in_out(t),
454            EaseFunction::BounceIn => easing_functions::bounce_in(t),
455            EaseFunction::BounceOut => easing_functions::bounce_out(t),
456            EaseFunction::BounceInOut => easing_functions::bounce_in_out(t),
457            EaseFunction::Steps(num_steps) => easing_functions::steps(*num_steps, t),
458            EaseFunction::Elastic(omega) => easing_functions::elastic(*omega, t),
459        }
460    }
461}