Skip to main content

bevy_curve/
easing.rs

1//! Module containing different easing functions.
2//!
3//! An easing function is a [`Curve`] that's used to transition between two
4//! values. It takes a time parameter, where a time of zero means the start of
5//! the transition and a time of one means the end.
6//!
7//! Easing functions come in a variety of shapes - one might [transition smoothly],
8//! while another might have a [bouncing motion].
9//!
10//! There are several ways to use easing functions. The simplest option is a
11//! struct thats represents a single easing function, like [`SmoothStepCurve`]
12//! and [`StepsCurve`]. These structs can only transition from a value of zero
13//! to a value of one.
14//!
15//! ```
16//! # use bevy_math::prelude::*;
17//! # use bevy_curve::prelude::*;
18//! # let time = 0.0;
19//! let smoothed_value = SmoothStepCurve.sample(time);
20//! ```
21//!
22//! ```
23//! # use bevy_math::prelude::*;
24//! # use bevy_curve::prelude::*;
25//! # let time = 0.0;
26//! let stepped_value = StepsCurve(5, JumpAt::Start).sample(time);
27//! ```
28//!
29//! Another option is [`EaseFunction`]. Unlike the single function structs,
30//! which require you to choose a function at compile time, `EaseFunction` lets
31//! you choose at runtime. It can also be serialized.
32//!
33//! ```
34//! # use bevy_math::prelude::*;
35//! # use bevy_curve::prelude::*;
36//! # let time = 0.0;
37//! # let make_it_smooth = false;
38//! let mut curve = EaseFunction::Linear;
39//!
40//! if make_it_smooth {
41//!     curve = EaseFunction::SmoothStep;
42//! }
43//!
44//! let value = curve.sample(time);
45//! ```
46//!
47//! The final option is [`EasingCurve`]. This lets you transition between any
48//! two values - not just zero to one. `EasingCurve` can use any value that
49//! implements the [`Ease`] trait, including vectors and directions.
50//!
51//! ```
52//! # use bevy_math::prelude::*;
53//! # use bevy_curve::prelude::*;
54//! # let time = 0.0;
55//! // Make a curve that smoothly transitions between two positions.
56//! let start_position = vec2(1.0, 2.0);
57//! let end_position = vec2(5.0, 10.0);
58//! let curve = EasingCurve::new(start_position, end_position, EaseFunction::SmoothStep);
59//!
60//! let smoothed_position = curve.sample(time);
61//! ```
62//!
63//! Like `EaseFunction`, the values and easing function of `EasingCurve` can be
64//! chosen at runtime and serialized.
65//!
66//! [transition smoothly]: `SmoothStepCurve`
67//! [bouncing motion]: `BounceInCurve`
68//! [`sample`]: `Curve::sample`
69//! [`sample_clamped`]: `Curve::sample_clamped`
70//! [`sample_unchecked`]: `Curve::sample_unchecked`
71//!
72
73use crate::{Curve, CurveExt, FunctionCurve, Interval};
74use bevy_math::{
75    Dir2, Dir3, Dir3A, Isometry2d, Isometry3d, Quat, Rot2, Sum, Vec2, Vec3, Vec3A, Vec4,
76    VectorSpace,
77};
78
79#[cfg(feature = "bevy_reflect")]
80use bevy_reflect::std_traits::ReflectDefault;
81
82use variadics_please::all_tuples_enumerated;
83
84// TODO: Think about merging `Ease` with `StableInterpolate`
85
86/// A type whose values can be eased between.
87///
88/// This requires the construction of an interpolation curve that actually extends
89/// beyond the curve segment that connects two values, because an easing curve may
90/// extrapolate before the starting value and after the ending value. This is
91/// especially common in easing functions that mimic elastic or springlike behavior.
92pub trait Ease: Sized {
93    /// Given `start` and `end` values, produce a curve with [unlimited domain]
94    /// that:
95    /// - takes a value equivalent to `start` at `t = 0`
96    /// - takes a value equivalent to `end` at `t = 1`
97    /// - has constant speed everywhere, including outside of `[0, 1]`
98    ///
99    /// [unlimited domain]: Interval::EVERYWHERE
100    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self>;
101}
102
103/// Marker trait that indicates:
104///
105/// - a) A type that supports the mathematical operations of a f32 vector space, irrespective of
106///   dimension. See [`VectorSpace`]
107/// - b) The type can be used for easing curve
108///
109/// This trait was introduced since implementing the [`Ease`] trait for all [`VectorSpace`]
110/// implementors isn't possible over crate boundaries. This is because the crate defining the
111/// [`VectorSpace`] trait could always add new implementations that can create conflicts in the
112/// crate using the trait. Hence we have to explicitly mark each type that should support the easing
113/// functionality.
114pub trait EaseVectorSpace: VectorSpace<Scalar = f32> {}
115
116impl EaseVectorSpace for Vec2 {}
117impl EaseVectorSpace for Vec3 {}
118impl EaseVectorSpace for Vec4 {}
119impl EaseVectorSpace for Vec3A {}
120impl EaseVectorSpace for f32 {}
121impl<V, W> EaseVectorSpace for Sum<V, W>
122where
123    V: EaseVectorSpace,
124    W: EaseVectorSpace,
125{
126}
127
128impl<V: EaseVectorSpace> Ease for V {
129    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
130        FunctionCurve::new(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
131    }
132}
133
134impl Ease for Rot2 {
135    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
136        FunctionCurve::new(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
137    }
138}
139
140impl Ease for Quat {
141    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
142        let dot = start.dot(end);
143        let end_adjusted = if dot < 0.0 { -end } else { end };
144        let difference = end_adjusted * start.inverse();
145        let (axis, angle) = difference.to_axis_angle();
146        FunctionCurve::new(Interval::EVERYWHERE, move |s| {
147            Quat::from_axis_angle(axis, angle * s) * start
148        })
149    }
150}
151
152impl Ease for Dir2 {
153    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
154        FunctionCurve::new(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
155    }
156}
157
158impl Ease for Dir3 {
159    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
160        let difference_quat = Quat::from_rotation_arc(start.as_vec3(), end.as_vec3());
161        Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
162    }
163}
164
165impl Ease for Dir3A {
166    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
167        let difference_quat =
168            Quat::from_rotation_arc(start.as_vec3a().into(), end.as_vec3a().into());
169        Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
170    }
171}
172
173impl Ease for Isometry3d {
174    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
175        FunctionCurve::new(Interval::EVERYWHERE, move |t| {
176            // we can use sample_unchecked here, since both interpolating_curve_unbounded impls
177            // used are defined on the whole domain
178            Isometry3d {
179                rotation: Quat::interpolating_curve_unbounded(start.rotation, end.rotation)
180                    .sample_unchecked(t),
181                translation: Vec3A::interpolating_curve_unbounded(
182                    start.translation,
183                    end.translation,
184                )
185                .sample_unchecked(t),
186            }
187        })
188    }
189}
190
191impl Ease for Isometry2d {
192    fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
193        FunctionCurve::new(Interval::EVERYWHERE, move |t| {
194            // we can use sample_unchecked here, since both interpolating_curve_unbounded impls
195            // used are defined on the whole domain
196            Isometry2d {
197                rotation: Rot2::interpolating_curve_unbounded(start.rotation, end.rotation)
198                    .sample_unchecked(t),
199                translation: Vec2::interpolating_curve_unbounded(
200                    start.translation,
201                    end.translation,
202                )
203                .sample_unchecked(t),
204            }
205        })
206    }
207}
208
209macro_rules! impl_ease_tuple {
210    ($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => {
211        $(#[$meta])*
212        impl<$($T: Ease),*> Ease for ($($T,)*) {
213            fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
214                let curve_tuple =
215                (
216                    $(
217                        <$T as Ease>::interpolating_curve_unbounded(start.$n, end.$n),
218                    )*
219                );
220
221                FunctionCurve::new(Interval::EVERYWHERE, move |t|
222                    (
223                        $(
224                            curve_tuple.$n.sample_unchecked(t),
225                        )*
226                    )
227                )
228            }
229        }
230    };
231}
232
233all_tuples_enumerated!(
234    #[doc(fake_variadic)]
235    impl_ease_tuple,
236    1,
237    11,
238    T
239);
240
241/// A [`Curve`] that is defined by
242///
243/// - an initial `start` sample value at `t = 0`
244/// - a final `end` sample value at `t = 1`
245/// - an [easing function] to interpolate between the two values.
246///
247/// The resulting curve's domain is always [the unit interval].
248///
249/// # Example
250///
251/// Create a linear curve that interpolates between `2.0` and `4.0`.
252///
253/// ```
254/// # use bevy_math::prelude::*;
255/// # use bevy_curve::prelude::*;
256/// let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear);
257/// ```
258///
259/// [`sample`] the curve at various points. This will return `None` if the parameter
260/// is outside the unit interval.
261///
262/// ```
263/// # use bevy_math::prelude::*;
264/// # use bevy_curve::prelude::*;
265/// # let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear);
266/// assert_eq!(c.sample(-1.0), None);
267/// assert_eq!(c.sample(0.0), Some(2.0));
268/// assert_eq!(c.sample(0.5), Some(3.0));
269/// assert_eq!(c.sample(1.0), Some(4.0));
270/// assert_eq!(c.sample(2.0), None);
271/// ```
272///
273/// [`sample_clamped`] will clamp the parameter to the unit interval, so it
274/// always returns a value.
275///
276/// ```
277/// # use bevy_math::prelude::*;
278/// # use bevy_curve::prelude::*;
279/// # let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear);
280/// assert_eq!(c.sample_clamped(-1.0), 2.0);
281/// assert_eq!(c.sample_clamped(0.0), 2.0);
282/// assert_eq!(c.sample_clamped(0.5), 3.0);
283/// assert_eq!(c.sample_clamped(1.0), 4.0);
284/// assert_eq!(c.sample_clamped(2.0), 4.0);
285/// ```
286///
287/// `EasingCurve` can be used with any type that implements the [`Ease`] trait.
288/// This includes many math types, like vectors and rotations.
289///
290/// ```
291/// # use bevy_math::prelude::*;
292/// # use bevy_curve::prelude::*;
293/// let c = EasingCurve::new(
294///     Vec2::new(0.0, 4.0),
295///     Vec2::new(2.0, 8.0),
296///     EaseFunction::Linear,
297/// );
298///
299/// assert_eq!(c.sample_clamped(0.5), Vec2::new(1.0, 6.0));
300/// ```
301///
302/// ```
303/// # use bevy_math::prelude::*;
304/// # use bevy_curve::prelude::*;
305/// # use approx::assert_abs_diff_eq;
306/// let c = EasingCurve::new(
307///     Rot2::degrees(10.0),
308///     Rot2::degrees(20.0),
309///     EaseFunction::Linear,
310/// );
311///
312/// assert_abs_diff_eq!(c.sample_clamped(0.5), Rot2::degrees(15.0));
313/// ```
314///
315/// As a shortcut, an `EasingCurve` between `0.0` and `1.0` can be replaced by
316/// [`EaseFunction`].
317///
318/// ```
319/// # use bevy_math::prelude::*;
320/// # use bevy_curve::prelude::*;
321/// # let t = 0.5;
322/// let f = EaseFunction::SineIn;
323/// let c = EasingCurve::new(0.0, 1.0, EaseFunction::SineIn);
324///
325/// assert_eq!(f.sample(t), c.sample(t));
326/// ```
327///
328/// [easing function]: EaseFunction
329/// [the unit interval]: Interval::UNIT
330/// [`sample`]: EasingCurve::sample
331/// [`sample_clamped`]: EasingCurve::sample_clamped
332#[derive(Clone, Debug, PartialEq)]
333#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
334#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
335pub struct EasingCurve<T> {
336    start: T,
337    end: T,
338    ease_fn: EaseFunction,
339}
340
341impl<T> EasingCurve<T> {
342    /// Given a `start` and `end` value, create a curve parametrized over [the unit interval]
343    /// that connects them, using the given [ease function] to determine the form of the
344    /// curve in between.
345    ///
346    /// [the unit interval]: Interval::UNIT
347    /// [ease function]: EaseFunction
348    pub const fn new(start: T, end: T, ease_fn: EaseFunction) -> Self {
349        Self {
350            start,
351            end,
352            ease_fn,
353        }
354    }
355}
356
357impl<T> Curve<T> for EasingCurve<T>
358where
359    T: Ease + Clone,
360{
361    #[inline]
362    fn domain(&self) -> Interval {
363        Interval::UNIT
364    }
365
366    #[inline]
367    fn sample_unchecked(&self, t: f32) -> T {
368        let remapped_t = self.ease_fn.eval(t);
369        T::interpolating_curve_unbounded(self.start.clone(), self.end.clone())
370            .sample_unchecked(remapped_t)
371    }
372}
373
374/// Configuration options for the [`EaseFunction::Steps`] curves. This closely replicates the
375/// [CSS step function specification].
376///
377/// [CSS step function specification]: https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/steps#description
378#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
379#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
380#[cfg_attr(
381    feature = "bevy_reflect",
382    derive(bevy_reflect::Reflect),
383    reflect(Clone, Default, PartialEq)
384)]
385pub enum JumpAt {
386    /// Indicates that the first step happens when the animation begins.
387    ///
388    #[doc = include_str!("../images/easefunction/StartSteps.svg")]
389    Start,
390    /// Indicates that the last step happens when the animation ends.
391    ///
392    #[doc = include_str!("../images/easefunction/EndSteps.svg")]
393    #[default]
394    End,
395    /// Indicates neither early nor late jumps happen.
396    ///
397    #[doc = include_str!("../images/easefunction/NoneSteps.svg")]
398    None,
399    /// Indicates both early and late jumps happen.
400    ///
401    #[doc = include_str!("../images/easefunction/BothSteps.svg")]
402    Both,
403}
404
405impl JumpAt {
406    #[inline]
407    pub(crate) fn eval(self, num_steps: usize, t: f32) -> f32 {
408        use bevy_math::ops;
409
410        let (a, b) = match self {
411            JumpAt::Start => (1.0, 0),
412            JumpAt::End => (0.0, 0),
413            JumpAt::None => (0.0, -1),
414            JumpAt::Both => (1.0, 1),
415        };
416
417        let current_step = ops::floor(t * num_steps as f32) + a;
418        let step_size = (num_steps as isize + b).max(1) as f32;
419
420        (current_step / step_size).clamp(0.0, 1.0)
421    }
422}
423
424/// Curve functions over the [unit interval], commonly used for easing transitions.
425///
426/// `EaseFunction` can be used on its own to interpolate between `0.0` and `1.0`.
427/// It can also be combined with [`EasingCurve`] to interpolate between other
428/// intervals and types, including vectors and rotations.
429///
430/// # Example
431///
432/// [`sample`] the smoothstep function at various points. This will return `None`
433/// if the parameter is outside the unit interval.
434///
435/// ```
436/// # use bevy_math::prelude::*;
437/// # use bevy_curve::prelude::*;
438/// let f = EaseFunction::SmoothStep;
439///
440/// assert_eq!(f.sample(-1.0), None);
441/// assert_eq!(f.sample(0.0), Some(0.0));
442/// assert_eq!(f.sample(0.5), Some(0.5));
443/// assert_eq!(f.sample(1.0), Some(1.0));
444/// assert_eq!(f.sample(2.0), None);
445/// ```
446///
447/// [`sample_clamped`] will clamp the parameter to the unit interval, so it
448/// always returns a value.
449///
450/// ```
451/// # use bevy_math::prelude::*;
452/// # use bevy_curve::prelude::*;
453/// # let f = EaseFunction::SmoothStep;
454/// assert_eq!(f.sample_clamped(-1.0), 0.0);
455/// assert_eq!(f.sample_clamped(0.0), 0.0);
456/// assert_eq!(f.sample_clamped(0.5), 0.5);
457/// assert_eq!(f.sample_clamped(1.0), 1.0);
458/// assert_eq!(f.sample_clamped(2.0), 1.0);
459/// ```
460///
461/// [`sample`]: EaseFunction::sample
462/// [`sample_clamped`]: EaseFunction::sample_clamped
463/// [unit interval]: `Interval::UNIT`
464#[non_exhaustive]
465#[derive(Debug, Copy, Clone, PartialEq)]
466#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
467#[cfg_attr(
468    feature = "bevy_reflect",
469    derive(bevy_reflect::Reflect),
470    reflect(Clone, PartialEq)
471)]
472// Note: Graphs are auto-generated via `tools/build-easefunction-graphs`.
473pub enum EaseFunction {
474    /// `f(t) = t`
475    ///
476    #[doc = include_str!("../images/easefunction/Linear.svg")]
477    Linear,
478
479    /// `f(t) = t²`
480    ///
481    /// This is the Hermite interpolator for
482    /// - f(0) = 0
483    /// - f(1) = 1
484    /// - f′(0) = 0
485    ///
486    #[doc = include_str!("../images/easefunction/QuadraticIn.svg")]
487    QuadraticIn,
488    /// `f(t) = -(t * (t - 2.0))`
489    ///
490    /// This is the Hermite interpolator for
491    /// - f(0) = 0
492    /// - f(1) = 1
493    /// - f′(1) = 0
494    ///
495    #[doc = include_str!("../images/easefunction/QuadraticOut.svg")]
496    QuadraticOut,
497    /// Behaves as `EaseFunction::QuadraticIn` for t < 0.5 and as `EaseFunction::QuadraticOut` for t >= 0.5
498    ///
499    /// A quadratic has too low of a degree to be both an `InOut` and C²,
500    /// so consider using at least a cubic (such as [`EaseFunction::SmoothStep`])
501    /// if you want the acceleration to be continuous.
502    ///
503    #[doc = include_str!("../images/easefunction/QuadraticInOut.svg")]
504    QuadraticInOut,
505
506    /// `f(t) = t³`
507    ///
508    /// This is the Hermite interpolator for
509    /// - f(0) = 0
510    /// - f(1) = 1
511    /// - f′(0) = 0
512    /// - f″(0) = 0
513    ///
514    #[doc = include_str!("../images/easefunction/CubicIn.svg")]
515    CubicIn,
516    /// `f(t) = (t - 1.0)³ + 1.0`
517    ///
518    #[doc = include_str!("../images/easefunction/CubicOut.svg")]
519    CubicOut,
520    /// Behaves as `EaseFunction::CubicIn` for t < 0.5 and as `EaseFunction::CubicOut` for t >= 0.5
521    ///
522    /// Due to this piecewise definition, this is only C¹ despite being a cubic:
523    /// the acceleration jumps from +12 to -12 at t = ½.
524    ///
525    /// Consider using [`EaseFunction::SmoothStep`] instead, which is also cubic,
526    /// or [`EaseFunction::SmootherStep`] if you picked this because you wanted
527    /// the acceleration at the endpoints to also be zero.
528    ///
529    #[doc = include_str!("../images/easefunction/CubicInOut.svg")]
530    CubicInOut,
531
532    /// `f(t) = t⁴`
533    ///
534    #[doc = include_str!("../images/easefunction/QuarticIn.svg")]
535    QuarticIn,
536    /// `f(t) = 1.0 - (1.0 - t)⁴`
537    ///
538    #[doc = include_str!("../images/easefunction/QuarticOut.svg")]
539    QuarticOut,
540    /// Behaves as `EaseFunction::QuarticIn` for t < 0.5 and as `EaseFunction::QuarticOut` for t >= 0.5
541    ///
542    #[doc = include_str!("../images/easefunction/QuarticInOut.svg")]
543    QuarticInOut,
544
545    /// `f(t) = t⁵`
546    ///
547    #[doc = include_str!("../images/easefunction/QuinticIn.svg")]
548    QuinticIn,
549    /// `f(t) = (t - 1.0)⁵ + 1.0`
550    ///
551    #[doc = include_str!("../images/easefunction/QuinticOut.svg")]
552    QuinticOut,
553    /// Behaves as `EaseFunction::QuinticIn` for t < 0.5 and as `EaseFunction::QuinticOut` for t >= 0.5
554    ///
555    /// Due to this piecewise definition, this is only C¹ despite being a quintic:
556    /// the acceleration jumps from +40 to -40 at t = ½.
557    ///
558    /// Consider using [`EaseFunction::SmootherStep`] instead, which is also quintic.
559    ///
560    #[doc = include_str!("../images/easefunction/QuinticInOut.svg")]
561    QuinticInOut,
562
563    /// Behaves as the first half of [`EaseFunction::SmoothStep`].
564    ///
565    /// This has f″(1) = 0, unlike [`EaseFunction::QuadraticIn`] which starts similarly.
566    ///
567    #[doc = include_str!("../images/easefunction/SmoothStepIn.svg")]
568    SmoothStepIn,
569    /// Behaves as the second half of [`EaseFunction::SmoothStep`].
570    ///
571    /// This has f″(0) = 0, unlike [`EaseFunction::QuadraticOut`] which ends similarly.
572    ///
573    #[doc = include_str!("../images/easefunction/SmoothStepOut.svg")]
574    SmoothStepOut,
575    /// `f(t) = 3t² - 2t³`
576    ///
577    /// This is the Hermite interpolator for
578    /// - f(0) = 0
579    /// - f(1) = 1
580    /// - f′(0) = 0
581    /// - f′(1) = 0
582    ///
583    /// See also [`smoothstep` in GLSL][glss].
584    ///
585    /// [glss]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml
586    ///
587    #[doc = include_str!("../images/easefunction/SmoothStep.svg")]
588    SmoothStep,
589
590    /// Behaves as the first half of [`EaseFunction::SmootherStep`].
591    ///
592    /// This has f″(1) = 0, unlike [`EaseFunction::CubicIn`] which starts similarly.
593    ///
594    #[doc = include_str!("../images/easefunction/SmootherStepIn.svg")]
595    SmootherStepIn,
596    /// Behaves as the second half of [`EaseFunction::SmootherStep`].
597    ///
598    /// This has f″(0) = 0, unlike [`EaseFunction::CubicOut`] which ends similarly.
599    ///
600    #[doc = include_str!("../images/easefunction/SmootherStepOut.svg")]
601    SmootherStepOut,
602    /// `f(t) = 6t⁵ - 15t⁴ + 10t³`
603    ///
604    /// This is the Hermite interpolator for
605    /// - f(0) = 0
606    /// - f(1) = 1
607    /// - f′(0) = 0
608    /// - f′(1) = 0
609    /// - f″(0) = 0
610    /// - f″(1) = 0
611    ///
612    #[doc = include_str!("../images/easefunction/SmootherStep.svg")]
613    SmootherStep,
614
615    /// `f(t) = 1.0 - cos(t * π / 2.0)`
616    ///
617    #[doc = include_str!("../images/easefunction/SineIn.svg")]
618    SineIn,
619    /// `f(t) = sin(t * π / 2.0)`
620    ///
621    #[doc = include_str!("../images/easefunction/SineOut.svg")]
622    SineOut,
623    /// Behaves as `EaseFunction::SineIn` for t < 0.5 and as `EaseFunction::SineOut` for t >= 0.5
624    ///
625    #[doc = include_str!("../images/easefunction/SineInOut.svg")]
626    SineInOut,
627
628    /// `f(t) = 1.0 - sqrt(1.0 - t²)`
629    ///
630    #[doc = include_str!("../images/easefunction/CircularIn.svg")]
631    CircularIn,
632    /// `f(t) = sqrt((2.0 - t) * t)`
633    ///
634    #[doc = include_str!("../images/easefunction/CircularOut.svg")]
635    CircularOut,
636    /// Behaves as `EaseFunction::CircularIn` for t < 0.5 and as `EaseFunction::CircularOut` for t >= 0.5
637    ///
638    #[doc = include_str!("../images/easefunction/CircularInOut.svg")]
639    CircularInOut,
640
641    /// `f(t) ≈ 2.0^(10.0 * (t - 1.0))`
642    ///
643    /// The precise definition adjusts it slightly so it hits both `(0, 0)` and `(1, 1)`:
644    /// `f(t) = 2.0^(10.0 * t - A) - B`, where A = log₂(2¹⁰-1) and B = 1/(2¹⁰-1).
645    ///
646    #[doc = include_str!("../images/easefunction/ExponentialIn.svg")]
647    ExponentialIn,
648    /// `f(t) ≈ 1.0 - 2.0^(-10.0 * t)`
649    ///
650    /// As with `EaseFunction::ExponentialIn`, the precise definition adjusts it slightly
651    /// so it hits both `(0, 0)` and `(1, 1)`.
652    ///
653    #[doc = include_str!("../images/easefunction/ExponentialOut.svg")]
654    ExponentialOut,
655    /// Behaves as `EaseFunction::ExponentialIn` for t < 0.5 and as `EaseFunction::ExponentialOut` for t >= 0.5
656    ///
657    #[doc = include_str!("../images/easefunction/ExponentialInOut.svg")]
658    ExponentialInOut,
659
660    /// `f(t) = -2.0^(10.0 * t - 10.0) * sin((t * 10.0 - 10.75) * 2.0 * π / 3.0)`
661    ///
662    #[doc = include_str!("../images/easefunction/ElasticIn.svg")]
663    ElasticIn,
664    /// `f(t) = 2.0^(-10.0 * t) * sin((t * 10.0 - 0.75) * 2.0 * π / 3.0) + 1.0`
665    ///
666    #[doc = include_str!("../images/easefunction/ElasticOut.svg")]
667    ElasticOut,
668    /// Behaves as `EaseFunction::ElasticIn` for t < 0.5 and as `EaseFunction::ElasticOut` for t >= 0.5
669    ///
670    #[doc = include_str!("../images/easefunction/ElasticInOut.svg")]
671    ElasticInOut,
672
673    /// `f(t) = 2.70158 * t³ - 1.70158 * t²`
674    ///
675    #[doc = include_str!("../images/easefunction/BackIn.svg")]
676    BackIn,
677    /// `f(t) = 1.0 + 2.70158 * (t - 1.0)³ + 1.70158 * (t - 1.0)²`
678    ///
679    #[doc = include_str!("../images/easefunction/BackOut.svg")]
680    BackOut,
681    /// Behaves as `EaseFunction::BackIn` for t < 0.5 and as `EaseFunction::BackOut` for t >= 0.5
682    ///
683    #[doc = include_str!("../images/easefunction/BackInOut.svg")]
684    BackInOut,
685
686    /// bouncy at the start!
687    ///
688    #[doc = include_str!("../images/easefunction/BounceIn.svg")]
689    BounceIn,
690    /// bouncy at the end!
691    ///
692    #[doc = include_str!("../images/easefunction/BounceOut.svg")]
693    BounceOut,
694    /// Behaves as `EaseFunction::BounceIn` for t < 0.5 and as `EaseFunction::BounceOut` for t >= 0.5
695    ///
696    #[doc = include_str!("../images/easefunction/BounceInOut.svg")]
697    BounceInOut,
698
699    /// `n` steps connecting the start and the end. Jumping behavior is customizable via
700    /// [`JumpAt`]. See [`JumpAt`] for all the options and visual examples.
701    Steps(usize, JumpAt),
702
703    /// `f(omega,t) = 1 - (1 - t)²(2sin(omega * t) / omega + cos(omega * t))`, parametrized by `omega`
704    ///
705    #[doc = include_str!("../images/easefunction/Elastic.svg")]
706    Elastic(f32),
707}
708
709/// `f(t) = t`
710///
711#[doc = include_str!("../images/easefunction/Linear.svg")]
712#[derive(Copy, Clone)]
713pub struct LinearCurve;
714
715/// `f(t) = t²`
716///
717/// This is the Hermite interpolator for
718/// - f(0) = 0
719/// - f(1) = 1
720/// - f′(0) = 0
721///
722#[doc = include_str!("../images/easefunction/QuadraticIn.svg")]
723#[derive(Copy, Clone)]
724pub struct QuadraticInCurve;
725
726/// `f(t) = -(t * (t - 2.0))`
727///
728/// This is the Hermite interpolator for
729/// - f(0) = 0
730/// - f(1) = 1
731/// - f′(1) = 0
732///
733#[doc = include_str!("../images/easefunction/QuadraticOut.svg")]
734#[derive(Copy, Clone)]
735pub struct QuadraticOutCurve;
736
737/// Behaves as `QuadraticIn` for t < 0.5 and as `QuadraticOut` for t >= 0.5
738///
739/// A quadratic has too low of a degree to be both an `InOut` and C²,
740/// so consider using at least a cubic (such as [`SmoothStepCurve`])
741/// if you want the acceleration to be continuous.
742///
743#[doc = include_str!("../images/easefunction/QuadraticInOut.svg")]
744#[derive(Copy, Clone)]
745pub struct QuadraticInOutCurve;
746
747/// `f(t) = t³`
748///
749/// This is the Hermite interpolator for
750/// - f(0) = 0
751/// - f(1) = 1
752/// - f′(0) = 0
753/// - f″(0) = 0
754///
755#[doc = include_str!("../images/easefunction/CubicIn.svg")]
756#[derive(Copy, Clone)]
757pub struct CubicInCurve;
758
759/// `f(t) = (t - 1.0)³ + 1.0`
760///
761#[doc = include_str!("../images/easefunction/CubicOut.svg")]
762#[derive(Copy, Clone)]
763pub struct CubicOutCurve;
764
765/// Behaves as `CubicIn` for t < 0.5 and as `CubicOut` for t >= 0.5
766///
767/// Due to this piecewise definition, this is only C¹ despite being a cubic:
768/// the acceleration jumps from +12 to -12 at t = ½.
769///
770/// Consider using [`SmoothStepCurve`] instead, which is also cubic,
771/// or [`SmootherStepCurve`] if you picked this because you wanted
772/// the acceleration at the endpoints to also be zero.
773///
774#[doc = include_str!("../images/easefunction/CubicInOut.svg")]
775#[derive(Copy, Clone)]
776pub struct CubicInOutCurve;
777
778/// `f(t) = t⁴`
779///
780#[doc = include_str!("../images/easefunction/QuarticIn.svg")]
781#[derive(Copy, Clone)]
782pub struct QuarticInCurve;
783
784/// `f(t) = 1.0 - (1.0 - t)⁴`
785///
786#[doc = include_str!("../images/easefunction/QuarticOut.svg")]
787#[derive(Copy, Clone)]
788pub struct QuarticOutCurve;
789
790/// Behaves as `QuarticIn` for t < 0.5 and as `QuarticOut` for t >= 0.5
791///
792#[doc = include_str!("../images/easefunction/QuarticInOut.svg")]
793#[derive(Copy, Clone)]
794pub struct QuarticInOutCurve;
795
796/// `f(t) = t⁵`
797///
798#[doc = include_str!("../images/easefunction/QuinticIn.svg")]
799#[derive(Copy, Clone)]
800pub struct QuinticInCurve;
801
802/// `f(t) = (t - 1.0)⁵ + 1.0`
803///
804#[doc = include_str!("../images/easefunction/QuinticOut.svg")]
805#[derive(Copy, Clone)]
806pub struct QuinticOutCurve;
807
808/// Behaves as `QuinticIn` for t < 0.5 and as `QuinticOut` for t >= 0.5
809///
810/// Due to this piecewise definition, this is only C¹ despite being a quintic:
811/// the acceleration jumps from +40 to -40 at t = ½.
812///
813/// Consider using [`SmootherStepCurve`] instead, which is also quintic.
814///
815#[doc = include_str!("../images/easefunction/QuinticInOut.svg")]
816#[derive(Copy, Clone)]
817pub struct QuinticInOutCurve;
818
819/// Behaves as the first half of [`SmoothStepCurve`].
820///
821/// This has f″(1) = 0, unlike [`QuadraticInCurve`] which starts similarly.
822///
823#[doc = include_str!("../images/easefunction/SmoothStepIn.svg")]
824#[derive(Copy, Clone)]
825pub struct SmoothStepInCurve;
826
827/// Behaves as the second half of [`SmoothStepCurve`].
828///
829/// This has f″(0) = 0, unlike [`QuadraticOutCurve`] which ends similarly.
830///
831#[doc = include_str!("../images/easefunction/SmoothStepOut.svg")]
832#[derive(Copy, Clone)]
833pub struct SmoothStepOutCurve;
834
835/// `f(t) = 3t² - 2t³`
836///
837/// This is the Hermite interpolator for
838/// - f(0) = 0
839/// - f(1) = 1
840/// - f′(0) = 0
841/// - f′(1) = 0
842///
843/// See also [`smoothstep` in GLSL][glss].
844///
845/// [glss]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml
846///
847#[doc = include_str!("../images/easefunction/SmoothStep.svg")]
848#[derive(Copy, Clone)]
849pub struct SmoothStepCurve;
850
851/// Behaves as the first half of [`SmootherStepCurve`].
852///
853/// This has f″(1) = 0, unlike [`CubicInCurve`] which starts similarly.
854///
855#[doc = include_str!("../images/easefunction/SmootherStepIn.svg")]
856#[derive(Copy, Clone)]
857pub struct SmootherStepInCurve;
858
859/// Behaves as the second half of [`SmootherStepCurve`].
860///
861/// This has f″(0) = 0, unlike [`CubicOutCurve`] which ends similarly.
862///
863#[doc = include_str!("../images/easefunction/SmootherStepOut.svg")]
864#[derive(Copy, Clone)]
865pub struct SmootherStepOutCurve;
866
867/// `f(t) = 6t⁵ - 15t⁴ + 10t³`
868///
869/// This is the Hermite interpolator for
870/// - f(0) = 0
871/// - f(1) = 1
872/// - f′(0) = 0
873/// - f′(1) = 0
874/// - f″(0) = 0
875/// - f″(1) = 0
876///
877#[doc = include_str!("../images/easefunction/SmootherStep.svg")]
878#[derive(Copy, Clone)]
879pub struct SmootherStepCurve;
880
881/// `f(t) = 1.0 - cos(t * π / 2.0)`
882///
883#[doc = include_str!("../images/easefunction/SineIn.svg")]
884#[derive(Copy, Clone)]
885pub struct SineInCurve;
886
887/// `f(t) = sin(t * π / 2.0)`
888///
889#[doc = include_str!("../images/easefunction/SineOut.svg")]
890#[derive(Copy, Clone)]
891pub struct SineOutCurve;
892
893/// Behaves as `SineIn` for t < 0.5 and as `SineOut` for t >= 0.5
894///
895#[doc = include_str!("../images/easefunction/SineInOut.svg")]
896#[derive(Copy, Clone)]
897pub struct SineInOutCurve;
898
899/// `f(t) = 1.0 - sqrt(1.0 - t²)`
900///
901#[doc = include_str!("../images/easefunction/CircularIn.svg")]
902#[derive(Copy, Clone)]
903pub struct CircularInCurve;
904
905/// `f(t) = sqrt((2.0 - t) * t)`
906///
907#[doc = include_str!("../images/easefunction/CircularOut.svg")]
908#[derive(Copy, Clone)]
909pub struct CircularOutCurve;
910
911/// Behaves as `CircularIn` for t < 0.5 and as `CircularOut` for t >= 0.5
912///
913#[doc = include_str!("../images/easefunction/CircularInOut.svg")]
914#[derive(Copy, Clone)]
915pub struct CircularInOutCurve;
916
917/// `f(t) ≈ 2.0^(10.0 * (t - 1.0))`
918///
919/// The precise definition adjusts it slightly so it hits both `(0, 0)` and `(1, 1)`:
920/// `f(t) = 2.0^(10.0 * t - A) - B`, where A = log₂(2¹⁰-1) and B = 1/(2¹⁰-1).
921///
922#[doc = include_str!("../images/easefunction/ExponentialIn.svg")]
923#[derive(Copy, Clone)]
924pub struct ExponentialInCurve;
925
926/// `f(t) ≈ 1.0 - 2.0^(-10.0 * t)`
927///
928/// As with `ExponentialIn`, the precise definition adjusts it slightly
929// so it hits both `(0, 0)` and `(1, 1)`.
930///
931#[doc = include_str!("../images/easefunction/ExponentialOut.svg")]
932#[derive(Copy, Clone)]
933pub struct ExponentialOutCurve;
934
935/// Behaves as `ExponentialIn` for t < 0.5 and as `ExponentialOut` for t >= 0.5
936///
937#[doc = include_str!("../images/easefunction/ExponentialInOut.svg")]
938#[derive(Copy, Clone)]
939pub struct ExponentialInOutCurve;
940
941/// `f(t) = -2.0^(10.0 * t - 10.0) * sin((t * 10.0 - 10.75) * 2.0 * π / 3.0)`
942///
943#[doc = include_str!("../images/easefunction/ElasticIn.svg")]
944#[derive(Copy, Clone)]
945pub struct ElasticInCurve;
946
947/// `f(t) = 2.0^(-10.0 * t) * sin((t * 10.0 - 0.75) * 2.0 * π / 3.0) + 1.0`
948///
949#[doc = include_str!("../images/easefunction/ElasticOut.svg")]
950#[derive(Copy, Clone)]
951pub struct ElasticOutCurve;
952
953/// Behaves as `ElasticIn` for t < 0.5 and as `ElasticOut` for t >= 0.5
954///
955#[doc = include_str!("../images/easefunction/ElasticInOut.svg")]
956#[derive(Copy, Clone)]
957pub struct ElasticInOutCurve;
958
959/// `f(t) = 2.70158 * t³ - 1.70158 * t²`
960///
961#[doc = include_str!("../images/easefunction/BackIn.svg")]
962#[derive(Copy, Clone)]
963pub struct BackInCurve;
964
965/// `f(t) = 1.0 +  2.70158 * (t - 1.0)³ + 1.70158 * (t - 1.0)²`
966///
967#[doc = include_str!("../images/easefunction/BackOut.svg")]
968#[derive(Copy, Clone)]
969pub struct BackOutCurve;
970
971/// Behaves as `BackIn` for t < 0.5 and as `BackOut` for t >= 0.5
972///
973#[doc = include_str!("../images/easefunction/BackInOut.svg")]
974#[derive(Copy, Clone)]
975pub struct BackInOutCurve;
976
977/// bouncy at the start!
978///
979#[doc = include_str!("../images/easefunction/BounceIn.svg")]
980#[derive(Copy, Clone)]
981pub struct BounceInCurve;
982
983/// bouncy at the end!
984///
985#[doc = include_str!("../images/easefunction/BounceOut.svg")]
986#[derive(Copy, Clone)]
987pub struct BounceOutCurve;
988
989/// Behaves as `BounceIn` for t < 0.5 and as `BounceOut` for t >= 0.5
990///
991#[doc = include_str!("../images/easefunction/BounceInOut.svg")]
992#[derive(Copy, Clone)]
993pub struct BounceInOutCurve;
994
995/// `n` steps connecting the start and the end. Jumping behavior is customizable via
996/// [`JumpAt`]. See [`JumpAt`] for all the options and visual examples.
997#[derive(Copy, Clone)]
998pub struct StepsCurve(pub usize, pub JumpAt);
999
1000/// `f(omega,t) = 1 - (1 - t)²(2sin(omega * t) / omega + cos(omega * t))`, parametrized by `omega`
1001///
1002#[doc = include_str!("../images/easefunction/Elastic.svg")]
1003#[derive(Copy, Clone)]
1004pub struct ElasticCurve(pub f32);
1005
1006/// Implements `Curve<f32>` for a unit struct using a function in `easing_functions`.
1007macro_rules! impl_ease_unit_struct {
1008    ($ty: ty, $fn: ident) => {
1009        impl Curve<f32> for $ty {
1010            #[inline]
1011            fn domain(&self) -> Interval {
1012                Interval::UNIT
1013            }
1014
1015            #[inline]
1016            fn sample_unchecked(&self, t: f32) -> f32 {
1017                easing_functions::$fn(t)
1018            }
1019        }
1020    };
1021}
1022
1023impl_ease_unit_struct!(LinearCurve, linear);
1024impl_ease_unit_struct!(QuadraticInCurve, quadratic_in);
1025impl_ease_unit_struct!(QuadraticOutCurve, quadratic_out);
1026impl_ease_unit_struct!(QuadraticInOutCurve, quadratic_in_out);
1027impl_ease_unit_struct!(CubicInCurve, cubic_in);
1028impl_ease_unit_struct!(CubicOutCurve, cubic_out);
1029impl_ease_unit_struct!(CubicInOutCurve, cubic_in_out);
1030impl_ease_unit_struct!(QuarticInCurve, quartic_in);
1031impl_ease_unit_struct!(QuarticOutCurve, quartic_out);
1032impl_ease_unit_struct!(QuarticInOutCurve, quartic_in_out);
1033impl_ease_unit_struct!(QuinticInCurve, quintic_in);
1034impl_ease_unit_struct!(QuinticOutCurve, quintic_out);
1035impl_ease_unit_struct!(QuinticInOutCurve, quintic_in_out);
1036impl_ease_unit_struct!(SmoothStepInCurve, smoothstep_in);
1037impl_ease_unit_struct!(SmoothStepOutCurve, smoothstep_out);
1038impl_ease_unit_struct!(SmoothStepCurve, smoothstep);
1039impl_ease_unit_struct!(SmootherStepInCurve, smootherstep_in);
1040impl_ease_unit_struct!(SmootherStepOutCurve, smootherstep_out);
1041impl_ease_unit_struct!(SmootherStepCurve, smootherstep);
1042impl_ease_unit_struct!(SineInCurve, sine_in);
1043impl_ease_unit_struct!(SineOutCurve, sine_out);
1044impl_ease_unit_struct!(SineInOutCurve, sine_in_out);
1045impl_ease_unit_struct!(CircularInCurve, circular_in);
1046impl_ease_unit_struct!(CircularOutCurve, circular_out);
1047impl_ease_unit_struct!(CircularInOutCurve, circular_in_out);
1048impl_ease_unit_struct!(ExponentialInCurve, exponential_in);
1049impl_ease_unit_struct!(ExponentialOutCurve, exponential_out);
1050impl_ease_unit_struct!(ExponentialInOutCurve, exponential_in_out);
1051impl_ease_unit_struct!(ElasticInCurve, elastic_in);
1052impl_ease_unit_struct!(ElasticOutCurve, elastic_out);
1053impl_ease_unit_struct!(ElasticInOutCurve, elastic_in_out);
1054impl_ease_unit_struct!(BackInCurve, back_in);
1055impl_ease_unit_struct!(BackOutCurve, back_out);
1056impl_ease_unit_struct!(BackInOutCurve, back_in_out);
1057impl_ease_unit_struct!(BounceInCurve, bounce_in);
1058impl_ease_unit_struct!(BounceOutCurve, bounce_out);
1059impl_ease_unit_struct!(BounceInOutCurve, bounce_in_out);
1060
1061impl Curve<f32> for StepsCurve {
1062    #[inline]
1063    fn domain(&self) -> Interval {
1064        Interval::UNIT
1065    }
1066
1067    #[inline]
1068    fn sample_unchecked(&self, t: f32) -> f32 {
1069        easing_functions::steps(self.0, self.1, t)
1070    }
1071}
1072
1073impl Curve<f32> for ElasticCurve {
1074    #[inline]
1075    fn domain(&self) -> Interval {
1076        Interval::UNIT
1077    }
1078
1079    #[inline]
1080    fn sample_unchecked(&self, t: f32) -> f32 {
1081        easing_functions::elastic(self.0, t)
1082    }
1083}
1084
1085mod easing_functions {
1086    use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, PI};
1087
1088    use bevy_math::{ops, FloatPow};
1089
1090    #[inline]
1091    pub(crate) const fn linear(t: f32) -> f32 {
1092        t
1093    }
1094
1095    #[inline]
1096    pub(crate) fn quadratic_in(t: f32) -> f32 {
1097        t.squared()
1098    }
1099    #[inline]
1100    pub(crate) fn quadratic_out(t: f32) -> f32 {
1101        1.0 - (1.0 - t).squared()
1102    }
1103    #[inline]
1104    pub(crate) fn quadratic_in_out(t: f32) -> f32 {
1105        if t < 0.5 {
1106            2.0 * t.squared()
1107        } else {
1108            1.0 - (-2.0 * t + 2.0).squared() / 2.0
1109        }
1110    }
1111
1112    #[inline]
1113    pub(crate) fn cubic_in(t: f32) -> f32 {
1114        t.cubed()
1115    }
1116    #[inline]
1117    pub(crate) fn cubic_out(t: f32) -> f32 {
1118        1.0 - (1.0 - t).cubed()
1119    }
1120    #[inline]
1121    pub(crate) fn cubic_in_out(t: f32) -> f32 {
1122        if t < 0.5 {
1123            4.0 * t.cubed()
1124        } else {
1125            1.0 - (-2.0 * t + 2.0).cubed() / 2.0
1126        }
1127    }
1128
1129    #[inline]
1130    pub(crate) const fn quartic_in(t: f32) -> f32 {
1131        t * t * t * t
1132    }
1133    #[inline]
1134    pub(crate) const fn quartic_out(t: f32) -> f32 {
1135        1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
1136    }
1137    #[inline]
1138    pub(crate) const fn quartic_in_out(t: f32) -> f32 {
1139        if t < 0.5 {
1140            8.0 * t * t * t * t
1141        } else {
1142            1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
1143        }
1144    }
1145
1146    #[inline]
1147    pub(crate) const fn quintic_in(t: f32) -> f32 {
1148        t * t * t * t * t
1149    }
1150    #[inline]
1151    pub(crate) const fn quintic_out(t: f32) -> f32 {
1152        1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
1153    }
1154    #[inline]
1155    pub(crate) const fn quintic_in_out(t: f32) -> f32 {
1156        if t < 0.5 {
1157            16.0 * t * t * t * t * t
1158        } else {
1159            1.0 - (-2.0 * t + 2.0)
1160                * (-2.0 * t + 2.0)
1161                * (-2.0 * t + 2.0)
1162                * (-2.0 * t + 2.0)
1163                * (-2.0 * t + 2.0)
1164                / 2.0
1165        }
1166    }
1167
1168    #[inline]
1169    pub(crate) const fn smoothstep_in(t: f32) -> f32 {
1170        ((1.5 - 0.5 * t) * t) * t
1171    }
1172
1173    #[inline]
1174    pub(crate) const fn smoothstep_out(t: f32) -> f32 {
1175        (1.5 + (-0.5 * t) * t) * t
1176    }
1177
1178    #[inline]
1179    pub(crate) const fn smoothstep(t: f32) -> f32 {
1180        ((3.0 - 2.0 * t) * t) * t
1181    }
1182
1183    #[inline]
1184    pub(crate) const fn smootherstep_in(t: f32) -> f32 {
1185        (((2.5 + (-1.875 + 0.375 * t) * t) * t) * t) * t
1186    }
1187
1188    #[inline]
1189    pub(crate) const fn smootherstep_out(t: f32) -> f32 {
1190        (1.875 + ((-1.25 + (0.375 * t) * t) * t) * t) * t
1191    }
1192
1193    #[inline]
1194    pub(crate) const fn smootherstep(t: f32) -> f32 {
1195        (((10.0 + (-15.0 + 6.0 * t) * t) * t) * t) * t
1196    }
1197
1198    #[inline]
1199    pub(crate) fn sine_in(t: f32) -> f32 {
1200        1.0 - ops::cos(t * FRAC_PI_2)
1201    }
1202    #[inline]
1203    pub(crate) fn sine_out(t: f32) -> f32 {
1204        ops::sin(t * FRAC_PI_2)
1205    }
1206    #[inline]
1207    pub(crate) fn sine_in_out(t: f32) -> f32 {
1208        -(ops::cos(PI * t) - 1.0) / 2.0
1209    }
1210
1211    #[inline]
1212    pub(crate) fn circular_in(t: f32) -> f32 {
1213        1.0 - ops::sqrt(1.0 - t.squared())
1214    }
1215    #[inline]
1216    pub(crate) fn circular_out(t: f32) -> f32 {
1217        ops::sqrt(1.0 - (t - 1.0).squared())
1218    }
1219    #[inline]
1220    pub(crate) fn circular_in_out(t: f32) -> f32 {
1221        if t < 0.5 {
1222            (1.0 - ops::sqrt(1.0 - (2.0 * t).squared())) / 2.0
1223        } else {
1224            (ops::sqrt(1.0 - (-2.0 * t + 2.0).squared()) + 1.0) / 2.0
1225        }
1226    }
1227
1228    // These are copied from a high precision calculator; I'd rather show them
1229    // with blatantly more digits than needed (since rust will round them to the
1230    // nearest representable value anyway) rather than make it seem like the
1231    // truncated value is somehow carefully chosen.
1232    #[expect(
1233        clippy::excessive_precision,
1234        reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
1235    )]
1236    const LOG2_1023: f32 = 9.998590429745328646459226;
1237    #[expect(
1238        clippy::excessive_precision,
1239        reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
1240    )]
1241    const FRAC_1_1023: f32 = 0.00097751710654936461388074291;
1242    #[inline]
1243    pub(crate) fn exponential_in(t: f32) -> f32 {
1244        // Derived from a rescaled exponential formula `(2^(10*t) - 1) / (2^10 - 1)`
1245        // See <https://www.wolframalpha.com/input?i=solve+over+the+reals%3A+pow%282%2C+10-A%29+-+pow%282%2C+-A%29%3D+1>
1246        ops::exp2(10.0 * t - LOG2_1023) - FRAC_1_1023
1247    }
1248    #[inline]
1249    pub(crate) fn exponential_out(t: f32) -> f32 {
1250        (FRAC_1_1023 + 1.0) - ops::exp2(-10.0 * t - (LOG2_1023 - 10.0))
1251    }
1252    #[inline]
1253    pub(crate) fn exponential_in_out(t: f32) -> f32 {
1254        if t < 0.5 {
1255            ops::exp2(20.0 * t - (LOG2_1023 + 1.0)) - (FRAC_1_1023 / 2.0)
1256        } else {
1257            (FRAC_1_1023 / 2.0 + 1.0) - ops::exp2(-20.0 * t - (LOG2_1023 - 19.0))
1258        }
1259    }
1260
1261    #[inline]
1262    pub(crate) fn back_in(t: f32) -> f32 {
1263        let c = 1.70158;
1264
1265        (c + 1.0) * t.cubed() - c * t.squared()
1266    }
1267    #[inline]
1268    pub(crate) fn back_out(t: f32) -> f32 {
1269        let c = 1.70158;
1270
1271        1.0 + (c + 1.0) * (t - 1.0).cubed() + c * (t - 1.0).squared()
1272    }
1273    #[inline]
1274    pub(crate) fn back_in_out(t: f32) -> f32 {
1275        let c1 = 1.70158;
1276        let c2 = c1 + 1.525;
1277
1278        if t < 0.5 {
1279            (2.0 * t).squared() * ((c2 + 1.0) * 2.0 * t - c2) / 2.0
1280        } else {
1281            ((2.0 * t - 2.0).squared() * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
1282        }
1283    }
1284
1285    #[inline]
1286    pub(crate) fn elastic_in(t: f32) -> f32 {
1287        -ops::powf(2.0, 10.0 * t - 10.0) * ops::sin((t * 10.0 - 10.75) * 2.0 * FRAC_PI_3)
1288    }
1289    #[inline]
1290    pub(crate) fn elastic_out(t: f32) -> f32 {
1291        ops::powf(2.0, -10.0 * t) * ops::sin((t * 10.0 - 0.75) * 2.0 * FRAC_PI_3) + 1.0
1292    }
1293    #[inline]
1294    pub(crate) fn elastic_in_out(t: f32) -> f32 {
1295        let c = (2.0 * PI) / 4.5;
1296
1297        if t < 0.5 {
1298            -ops::powf(2.0, 20.0 * t - 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0
1299        } else {
1300            ops::powf(2.0, -20.0 * t + 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0 + 1.0
1301        }
1302    }
1303
1304    #[inline]
1305    pub(crate) fn bounce_in(t: f32) -> f32 {
1306        1.0 - bounce_out(1.0 - t)
1307    }
1308    #[inline]
1309    pub(crate) fn bounce_out(t: f32) -> f32 {
1310        if t < 4.0 / 11.0 {
1311            (121.0 * t.squared()) / 16.0
1312        } else if t < 8.0 / 11.0 {
1313            (363.0 / 40.0 * t.squared()) - (99.0 / 10.0 * t) + 17.0 / 5.0
1314        } else if t < 9.0 / 10.0 {
1315            (4356.0 / 361.0 * t.squared()) - (35442.0 / 1805.0 * t) + 16061.0 / 1805.0
1316        } else {
1317            (54.0 / 5.0 * t.squared()) - (513.0 / 25.0 * t) + 268.0 / 25.0
1318        }
1319    }
1320    #[inline]
1321    pub(crate) fn bounce_in_out(t: f32) -> f32 {
1322        if t < 0.5 {
1323            (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
1324        } else {
1325            (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
1326        }
1327    }
1328
1329    #[inline]
1330    pub(crate) fn steps(num_steps: usize, jump_at: super::JumpAt, t: f32) -> f32 {
1331        jump_at.eval(num_steps, t)
1332    }
1333
1334    #[inline]
1335    pub(crate) fn elastic(omega: f32, t: f32) -> f32 {
1336        1.0 - (1.0 - t).squared() * (2.0 * ops::sin(omega * t) / omega + ops::cos(omega * t))
1337    }
1338}
1339
1340impl EaseFunction {
1341    fn eval(&self, t: f32) -> f32 {
1342        match self {
1343            EaseFunction::Linear => easing_functions::linear(t),
1344            EaseFunction::QuadraticIn => easing_functions::quadratic_in(t),
1345            EaseFunction::QuadraticOut => easing_functions::quadratic_out(t),
1346            EaseFunction::QuadraticInOut => easing_functions::quadratic_in_out(t),
1347            EaseFunction::CubicIn => easing_functions::cubic_in(t),
1348            EaseFunction::CubicOut => easing_functions::cubic_out(t),
1349            EaseFunction::CubicInOut => easing_functions::cubic_in_out(t),
1350            EaseFunction::QuarticIn => easing_functions::quartic_in(t),
1351            EaseFunction::QuarticOut => easing_functions::quartic_out(t),
1352            EaseFunction::QuarticInOut => easing_functions::quartic_in_out(t),
1353            EaseFunction::QuinticIn => easing_functions::quintic_in(t),
1354            EaseFunction::QuinticOut => easing_functions::quintic_out(t),
1355            EaseFunction::QuinticInOut => easing_functions::quintic_in_out(t),
1356            EaseFunction::SmoothStepIn => easing_functions::smoothstep_in(t),
1357            EaseFunction::SmoothStepOut => easing_functions::smoothstep_out(t),
1358            EaseFunction::SmoothStep => easing_functions::smoothstep(t),
1359            EaseFunction::SmootherStepIn => easing_functions::smootherstep_in(t),
1360            EaseFunction::SmootherStepOut => easing_functions::smootherstep_out(t),
1361            EaseFunction::SmootherStep => easing_functions::smootherstep(t),
1362            EaseFunction::SineIn => easing_functions::sine_in(t),
1363            EaseFunction::SineOut => easing_functions::sine_out(t),
1364            EaseFunction::SineInOut => easing_functions::sine_in_out(t),
1365            EaseFunction::CircularIn => easing_functions::circular_in(t),
1366            EaseFunction::CircularOut => easing_functions::circular_out(t),
1367            EaseFunction::CircularInOut => easing_functions::circular_in_out(t),
1368            EaseFunction::ExponentialIn => easing_functions::exponential_in(t),
1369            EaseFunction::ExponentialOut => easing_functions::exponential_out(t),
1370            EaseFunction::ExponentialInOut => easing_functions::exponential_in_out(t),
1371            EaseFunction::ElasticIn => easing_functions::elastic_in(t),
1372            EaseFunction::ElasticOut => easing_functions::elastic_out(t),
1373            EaseFunction::ElasticInOut => easing_functions::elastic_in_out(t),
1374            EaseFunction::BackIn => easing_functions::back_in(t),
1375            EaseFunction::BackOut => easing_functions::back_out(t),
1376            EaseFunction::BackInOut => easing_functions::back_in_out(t),
1377            EaseFunction::BounceIn => easing_functions::bounce_in(t),
1378            EaseFunction::BounceOut => easing_functions::bounce_out(t),
1379            EaseFunction::BounceInOut => easing_functions::bounce_in_out(t),
1380            EaseFunction::Steps(num_steps, jump_at) => {
1381                easing_functions::steps(*num_steps, *jump_at, t)
1382            }
1383            EaseFunction::Elastic(omega) => easing_functions::elastic(*omega, t),
1384        }
1385    }
1386}
1387
1388impl Curve<f32> for EaseFunction {
1389    #[inline]
1390    fn domain(&self) -> Interval {
1391        Interval::UNIT
1392    }
1393
1394    #[inline]
1395    fn sample_unchecked(&self, t: f32) -> f32 {
1396        self.eval(t)
1397    }
1398}
1399
1400#[cfg(test)]
1401#[cfg(feature = "approx")]
1402mod tests {
1403
1404    use approx::assert_abs_diff_eq;
1405    use bevy_math::{Vec2, Vec3, Vec3A};
1406
1407    use super::*;
1408    const MONOTONIC_IN_OUT_INOUT: &[[EaseFunction; 3]] = {
1409        use EaseFunction::*;
1410        &[
1411            [QuadraticIn, QuadraticOut, QuadraticInOut],
1412            [CubicIn, CubicOut, CubicInOut],
1413            [QuarticIn, QuarticOut, QuarticInOut],
1414            [QuinticIn, QuinticOut, QuinticInOut],
1415            [SmoothStepIn, SmoothStepOut, SmoothStep],
1416            [SmootherStepIn, SmootherStepOut, SmootherStep],
1417            [SineIn, SineOut, SineInOut],
1418            [CircularIn, CircularOut, CircularInOut],
1419            [ExponentialIn, ExponentialOut, ExponentialInOut],
1420        ]
1421    };
1422
1423    // For easing function we don't care if eval(0) is super-tiny like 2.0e-28,
1424    // so add the same amount of error on both ends of the unit interval.
1425    const TOLERANCE: f32 = 1.0e-6;
1426    const _: () = const {
1427        assert!(1.0 - TOLERANCE != 1.0);
1428    };
1429
1430    #[test]
1431    fn ease_functions_zero_to_one() {
1432        for ef in MONOTONIC_IN_OUT_INOUT.iter().flatten() {
1433            let start = ef.eval(0.0);
1434            assert!(
1435                (0.0..=TOLERANCE).contains(&start),
1436                "EaseFunction.{ef:?}(0) was {start:?}",
1437            );
1438
1439            let finish = ef.eval(1.0);
1440            assert!(
1441                (1.0 - TOLERANCE..=1.0).contains(&finish),
1442                "EaseFunction.{ef:?}(1) was {start:?}",
1443            );
1444        }
1445    }
1446
1447    #[test]
1448    fn ease_function_inout_deciles() {
1449        // convexity gives the comparisons against the input built-in tolerances
1450        for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
1451            for x in [0.1, 0.2, 0.3, 0.4] {
1452                let y = ef_inout.eval(x);
1453                assert!(y < x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
1454
1455                let iny = ef_in.eval(2.0 * x) / 2.0;
1456                assert!(
1457                    (y - TOLERANCE..y + TOLERANCE).contains(&iny),
1458                    "EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
1459                    EaseFunction.{ef_in:?}(2 * {x:?}) / 2 was {iny:?}",
1460                );
1461            }
1462
1463            for x in [0.6, 0.7, 0.8, 0.9] {
1464                let y = ef_inout.eval(x);
1465                assert!(y > x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
1466
1467                let outy = ef_out.eval(2.0 * x - 1.0) / 2.0 + 0.5;
1468                assert!(
1469                    (y - TOLERANCE..y + TOLERANCE).contains(&outy),
1470                    "EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
1471                    EaseFunction.{ef_out:?}(2 * {x:?} - 1) / 2 + ½ was {outy:?}",
1472                );
1473            }
1474        }
1475    }
1476
1477    #[test]
1478    fn ease_function_midpoints() {
1479        for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
1480            let mid = ef_in.eval(0.5);
1481            assert!(
1482                mid < 0.5 - TOLERANCE,
1483                "EaseFunction.{ef_in:?}(½) was {mid:?}",
1484            );
1485
1486            let mid = ef_out.eval(0.5);
1487            assert!(
1488                mid > 0.5 + TOLERANCE,
1489                "EaseFunction.{ef_out:?}(½) was {mid:?}",
1490            );
1491
1492            let mid = ef_inout.eval(0.5);
1493            assert!(
1494                (0.5 - TOLERANCE..=0.5 + TOLERANCE).contains(&mid),
1495                "EaseFunction.{ef_inout:?}(½) was {mid:?}",
1496            );
1497        }
1498    }
1499
1500    #[test]
1501    fn ease_quats() {
1502        let quat_start = Quat::from_axis_angle(Vec3::Z, 0.0);
1503        let quat_end = Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians());
1504
1505        let quat_curve = Quat::interpolating_curve_unbounded(quat_start, quat_end);
1506
1507        assert_abs_diff_eq!(
1508            quat_curve.sample(0.0).unwrap(),
1509            Quat::from_axis_angle(Vec3::Z, 0.0)
1510        );
1511        {
1512            let (before_mid_axis, before_mid_angle) =
1513                quat_curve.sample(0.25).unwrap().to_axis_angle();
1514            assert_abs_diff_eq!(before_mid_axis, Vec3::Z);
1515            assert_abs_diff_eq!(before_mid_angle, 22.5_f32.to_radians());
1516        }
1517        {
1518            let (mid_axis, mid_angle) = quat_curve.sample(0.5).unwrap().to_axis_angle();
1519            assert_abs_diff_eq!(mid_axis, Vec3::Z);
1520            assert_abs_diff_eq!(mid_angle, 45.0_f32.to_radians());
1521        }
1522        {
1523            let (after_mid_axis, after_mid_angle) =
1524                quat_curve.sample(0.75).unwrap().to_axis_angle();
1525            assert_abs_diff_eq!(after_mid_axis, Vec3::Z);
1526            assert_abs_diff_eq!(after_mid_angle, 67.5_f32.to_radians());
1527        }
1528        assert_abs_diff_eq!(
1529            quat_curve.sample(1.0).unwrap(),
1530            Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians())
1531        );
1532    }
1533
1534    #[test]
1535    fn ease_isometries_2d() {
1536        let angle = 90.0;
1537        let iso_2d_start = Isometry2d::new(Vec2::ZERO, Rot2::degrees(0.0));
1538        let iso_2d_end = Isometry2d::new(Vec2::ONE, Rot2::degrees(angle));
1539
1540        let iso_2d_curve = Isometry2d::interpolating_curve_unbounded(iso_2d_start, iso_2d_end);
1541
1542        [-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
1543            assert_abs_diff_eq!(
1544                iso_2d_curve.sample(t).unwrap(),
1545                Isometry2d::new(Vec2::ONE * t, Rot2::degrees(angle * t))
1546            );
1547        });
1548    }
1549
1550    #[test]
1551    fn ease_isometries_3d() {
1552        let angle = 90.0_f32.to_radians();
1553        let iso_3d_start = Isometry3d::new(Vec3A::ZERO, Quat::from_axis_angle(Vec3::Z, 0.0));
1554        let iso_3d_end = Isometry3d::new(Vec3A::ONE, Quat::from_axis_angle(Vec3::Z, angle));
1555
1556        let iso_3d_curve = Isometry3d::interpolating_curve_unbounded(iso_3d_start, iso_3d_end);
1557
1558        [-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
1559            assert_abs_diff_eq!(
1560                iso_3d_curve.sample(t).unwrap(),
1561                Isometry3d::new(Vec3A::ONE * t, Quat::from_axis_angle(Vec3::Z, angle * t))
1562            );
1563        });
1564    }
1565
1566    #[test]
1567    fn jump_at_start() {
1568        let jump_at = JumpAt::Start;
1569        let num_steps = 4;
1570
1571        [
1572            (0.0, 0.25),
1573            (0.249, 0.25),
1574            (0.25, 0.5),
1575            (0.499, 0.5),
1576            (0.5, 0.75),
1577            (0.749, 0.75),
1578            (0.75, 1.0),
1579            (1.0, 1.0),
1580        ]
1581        .into_iter()
1582        .for_each(|(t, expected)| {
1583            assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1584        });
1585    }
1586
1587    #[test]
1588    fn jump_at_end() {
1589        let jump_at = JumpAt::End;
1590        let num_steps = 4;
1591
1592        [
1593            (0.0, 0.0),
1594            (0.249, 0.0),
1595            (0.25, 0.25),
1596            (0.499, 0.25),
1597            (0.5, 0.5),
1598            (0.749, 0.5),
1599            (0.75, 0.75),
1600            (0.999, 0.75),
1601            (1.0, 1.0),
1602        ]
1603        .into_iter()
1604        .for_each(|(t, expected)| {
1605            assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1606        });
1607    }
1608
1609    #[test]
1610    fn jump_at_none() {
1611        let jump_at = JumpAt::None;
1612        let num_steps = 5;
1613
1614        [
1615            (0.0, 0.0),
1616            (0.199, 0.0),
1617            (0.2, 0.25),
1618            (0.399, 0.25),
1619            (0.4, 0.5),
1620            (0.599, 0.5),
1621            (0.6, 0.75),
1622            (0.799, 0.75),
1623            (0.8, 1.0),
1624            (0.999, 1.0),
1625            (1.0, 1.0),
1626        ]
1627        .into_iter()
1628        .for_each(|(t, expected)| {
1629            assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1630        });
1631    }
1632
1633    #[test]
1634    fn jump_at_both() {
1635        let jump_at = JumpAt::Both;
1636        let num_steps = 4;
1637
1638        [
1639            (0.0, 0.2),
1640            (0.249, 0.2),
1641            (0.25, 0.4),
1642            (0.499, 0.4),
1643            (0.5, 0.6),
1644            (0.749, 0.6),
1645            (0.75, 0.8),
1646            (0.999, 0.8),
1647            (1.0, 1.0),
1648        ]
1649        .into_iter()
1650        .for_each(|(t, expected)| {
1651            assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1652        });
1653    }
1654
1655    #[test]
1656    fn ease_function_curve() {
1657        // Test that the various ways to build an ease function are all
1658        // equivalent.
1659
1660        let f0 = SmoothStepCurve;
1661        let f1 = EaseFunction::SmoothStep;
1662        let f2 = EasingCurve::new(0.0, 1.0, EaseFunction::SmoothStep);
1663
1664        assert_eq!(f0.domain(), f1.domain());
1665        assert_eq!(f0.domain(), f2.domain());
1666
1667        [
1668            -1.0,
1669            -f32::MIN_POSITIVE,
1670            0.0,
1671            0.5,
1672            1.0,
1673            1.0 + f32::EPSILON,
1674            2.0,
1675        ]
1676        .into_iter()
1677        .for_each(|t| {
1678            assert_eq!(f0.sample(t), f1.sample(t));
1679            assert_eq!(f0.sample(t), f2.sample(t));
1680
1681            assert_eq!(f0.sample_clamped(t), f1.sample_clamped(t));
1682            assert_eq!(f0.sample_clamped(t), f2.sample_clamped(t));
1683        });
1684    }
1685
1686    #[test]
1687    fn unit_structs_match_function() {
1688        // Test that the unit structs and `EaseFunction` match each other and
1689        // implement `Curve<f32>`.
1690
1691        fn test(f1: impl Curve<f32>, f2: impl Curve<f32>, t: f32) {
1692            assert_eq!(f1.sample(t), f2.sample(t));
1693        }
1694
1695        for t in [-1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 2.0] {
1696            test(LinearCurve, EaseFunction::Linear, t);
1697            test(QuadraticInCurve, EaseFunction::QuadraticIn, t);
1698            test(QuadraticOutCurve, EaseFunction::QuadraticOut, t);
1699            test(QuadraticInOutCurve, EaseFunction::QuadraticInOut, t);
1700            test(CubicInCurve, EaseFunction::CubicIn, t);
1701            test(CubicOutCurve, EaseFunction::CubicOut, t);
1702            test(CubicInOutCurve, EaseFunction::CubicInOut, t);
1703            test(QuarticInCurve, EaseFunction::QuarticIn, t);
1704            test(QuarticOutCurve, EaseFunction::QuarticOut, t);
1705            test(QuarticInOutCurve, EaseFunction::QuarticInOut, t);
1706            test(QuinticInCurve, EaseFunction::QuinticIn, t);
1707            test(QuinticOutCurve, EaseFunction::QuinticOut, t);
1708            test(QuinticInOutCurve, EaseFunction::QuinticInOut, t);
1709            test(SmoothStepInCurve, EaseFunction::SmoothStepIn, t);
1710            test(SmoothStepOutCurve, EaseFunction::SmoothStepOut, t);
1711            test(SmoothStepCurve, EaseFunction::SmoothStep, t);
1712            test(SmootherStepInCurve, EaseFunction::SmootherStepIn, t);
1713            test(SmootherStepOutCurve, EaseFunction::SmootherStepOut, t);
1714            test(SmootherStepCurve, EaseFunction::SmootherStep, t);
1715            test(SineInCurve, EaseFunction::SineIn, t);
1716            test(SineOutCurve, EaseFunction::SineOut, t);
1717            test(SineInOutCurve, EaseFunction::SineInOut, t);
1718            test(CircularInCurve, EaseFunction::CircularIn, t);
1719            test(CircularOutCurve, EaseFunction::CircularOut, t);
1720            test(CircularInOutCurve, EaseFunction::CircularInOut, t);
1721            test(ExponentialInCurve, EaseFunction::ExponentialIn, t);
1722            test(ExponentialOutCurve, EaseFunction::ExponentialOut, t);
1723            test(ExponentialInOutCurve, EaseFunction::ExponentialInOut, t);
1724            test(ElasticInCurve, EaseFunction::ElasticIn, t);
1725            test(ElasticOutCurve, EaseFunction::ElasticOut, t);
1726            test(ElasticInOutCurve, EaseFunction::ElasticInOut, t);
1727            test(BackInCurve, EaseFunction::BackIn, t);
1728            test(BackOutCurve, EaseFunction::BackOut, t);
1729            test(BackInOutCurve, EaseFunction::BackInOut, t);
1730            test(BounceInCurve, EaseFunction::BounceIn, t);
1731            test(BounceOutCurve, EaseFunction::BounceOut, t);
1732            test(BounceInOutCurve, EaseFunction::BounceInOut, t);
1733
1734            test(
1735                StepsCurve(4, JumpAt::Start),
1736                EaseFunction::Steps(4, JumpAt::Start),
1737                t,
1738            );
1739
1740            test(ElasticCurve(50.0), EaseFunction::Elastic(50.0), t);
1741        }
1742    }
1743}