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