use crate::{
curve::{Curve, CurveExt, FunctionCurve, Interval},
Dir2, Dir3, Dir3A, Isometry2d, Isometry3d, Quat, Rot2, VectorSpace,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::std_traits::ReflectDefault;
use variadics_please::all_tuples_enumerated;
pub trait Ease: Sized {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self>;
}
impl<V: VectorSpace<Scalar = f32>> Ease for V {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
FunctionCurve::new(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
}
}
impl Ease for Rot2 {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
FunctionCurve::new(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
}
}
impl Ease for Quat {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
let dot = start.dot(end);
let end_adjusted = if dot < 0.0 { -end } else { end };
let difference = end_adjusted * start.inverse();
let (axis, angle) = difference.to_axis_angle();
FunctionCurve::new(Interval::EVERYWHERE, move |s| {
Quat::from_axis_angle(axis, angle * s) * start
})
}
}
impl Ease for Dir2 {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
FunctionCurve::new(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
}
}
impl Ease for Dir3 {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
let difference_quat = Quat::from_rotation_arc(start.as_vec3(), end.as_vec3());
Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
}
}
impl Ease for Dir3A {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
let difference_quat =
Quat::from_rotation_arc(start.as_vec3a().into(), end.as_vec3a().into());
Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
}
}
impl Ease for Isometry3d {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
FunctionCurve::new(Interval::EVERYWHERE, move |t| {
Isometry3d {
rotation: Quat::interpolating_curve_unbounded(start.rotation, end.rotation)
.sample_unchecked(t),
translation: crate::Vec3A::interpolating_curve_unbounded(
start.translation,
end.translation,
)
.sample_unchecked(t),
}
})
}
}
impl Ease for Isometry2d {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
FunctionCurve::new(Interval::EVERYWHERE, move |t| {
Isometry2d {
rotation: Rot2::interpolating_curve_unbounded(start.rotation, end.rotation)
.sample_unchecked(t),
translation: crate::Vec2::interpolating_curve_unbounded(
start.translation,
end.translation,
)
.sample_unchecked(t),
}
})
}
}
macro_rules! impl_ease_tuple {
($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => {
$(#[$meta])*
impl<$($T: Ease),*> Ease for ($($T,)*) {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
let curve_tuple =
(
$(
<$T as Ease>::interpolating_curve_unbounded(start.$n, end.$n),
)*
);
FunctionCurve::new(Interval::EVERYWHERE, move |t|
(
$(
curve_tuple.$n.sample_unchecked(t),
)*
)
)
}
}
};
}
all_tuples_enumerated!(
#[doc(fake_variadic)]
impl_ease_tuple,
1,
11,
T
);
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct EasingCurve<T> {
start: T,
end: T,
ease_fn: EaseFunction,
}
impl<T> EasingCurve<T> {
pub fn new(start: T, end: T, ease_fn: EaseFunction) -> Self {
Self {
start,
end,
ease_fn,
}
}
}
impl<T> Curve<T> for EasingCurve<T>
where
T: Ease + Clone,
{
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let remapped_t = self.ease_fn.eval(t);
T::interpolating_curve_unbounded(self.start.clone(), self.end.clone())
.sample_unchecked(remapped_t)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Clone, Default, PartialEq)
)]
pub enum JumpAt {
#[doc = include_str!("../../images/easefunction/StartSteps.svg")]
Start,
#[doc = include_str!("../../images/easefunction/EndSteps.svg")]
#[default]
End,
#[doc = include_str!("../../images/easefunction/NoneSteps.svg")]
None,
#[doc = include_str!("../../images/easefunction/BothSteps.svg")]
Both,
}
impl JumpAt {
#[inline]
pub(crate) fn eval(self, num_steps: usize, t: f32) -> f32 {
use crate::ops;
let (a, b) = match self {
JumpAt::Start => (1.0, 0),
JumpAt::End => (0.0, 0),
JumpAt::None => (0.0, -1),
JumpAt::Both => (1.0, 1),
};
let current_step = ops::floor(t * num_steps as f32) + a;
let step_size = (num_steps as isize + b).max(1) as f32;
(current_step / step_size).clamp(0.0, 1.0)
}
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(bevy_reflect::Reflect),
reflect(Clone, PartialEq)
)]
pub enum EaseFunction {
#[doc = include_str!("../../images/easefunction/Linear.svg")]
Linear,
#[doc = include_str!("../../images/easefunction/QuadraticIn.svg")]
QuadraticIn,
#[doc = include_str!("../../images/easefunction/QuadraticOut.svg")]
QuadraticOut,
#[doc = include_str!("../../images/easefunction/QuadraticInOut.svg")]
QuadraticInOut,
#[doc = include_str!("../../images/easefunction/CubicIn.svg")]
CubicIn,
#[doc = include_str!("../../images/easefunction/CubicOut.svg")]
CubicOut,
#[doc = include_str!("../../images/easefunction/CubicInOut.svg")]
CubicInOut,
#[doc = include_str!("../../images/easefunction/QuarticIn.svg")]
QuarticIn,
#[doc = include_str!("../../images/easefunction/QuarticOut.svg")]
QuarticOut,
#[doc = include_str!("../../images/easefunction/QuarticInOut.svg")]
QuarticInOut,
#[doc = include_str!("../../images/easefunction/QuinticIn.svg")]
QuinticIn,
#[doc = include_str!("../../images/easefunction/QuinticOut.svg")]
QuinticOut,
#[doc = include_str!("../../images/easefunction/QuinticInOut.svg")]
QuinticInOut,
#[doc = include_str!("../../images/easefunction/SmoothStepIn.svg")]
SmoothStepIn,
#[doc = include_str!("../../images/easefunction/SmoothStepOut.svg")]
SmoothStepOut,
#[doc = include_str!("../../images/easefunction/SmoothStep.svg")]
SmoothStep,
#[doc = include_str!("../../images/easefunction/SmootherStepIn.svg")]
SmootherStepIn,
#[doc = include_str!("../../images/easefunction/SmootherStepOut.svg")]
SmootherStepOut,
#[doc = include_str!("../../images/easefunction/SmootherStep.svg")]
SmootherStep,
#[doc = include_str!("../../images/easefunction/SineIn.svg")]
SineIn,
#[doc = include_str!("../../images/easefunction/SineOut.svg")]
SineOut,
#[doc = include_str!("../../images/easefunction/SineInOut.svg")]
SineInOut,
#[doc = include_str!("../../images/easefunction/CircularIn.svg")]
CircularIn,
#[doc = include_str!("../../images/easefunction/CircularOut.svg")]
CircularOut,
#[doc = include_str!("../../images/easefunction/CircularInOut.svg")]
CircularInOut,
#[doc = include_str!("../../images/easefunction/ExponentialIn.svg")]
ExponentialIn,
#[doc = include_str!("../../images/easefunction/ExponentialOut.svg")]
ExponentialOut,
#[doc = include_str!("../../images/easefunction/ExponentialInOut.svg")]
ExponentialInOut,
#[doc = include_str!("../../images/easefunction/ElasticIn.svg")]
ElasticIn,
#[doc = include_str!("../../images/easefunction/ElasticOut.svg")]
ElasticOut,
#[doc = include_str!("../../images/easefunction/ElasticInOut.svg")]
ElasticInOut,
#[doc = include_str!("../../images/easefunction/BackIn.svg")]
BackIn,
#[doc = include_str!("../../images/easefunction/BackOut.svg")]
BackOut,
#[doc = include_str!("../../images/easefunction/BackInOut.svg")]
BackInOut,
#[doc = include_str!("../../images/easefunction/BounceIn.svg")]
BounceIn,
#[doc = include_str!("../../images/easefunction/BounceOut.svg")]
BounceOut,
#[doc = include_str!("../../images/easefunction/BounceInOut.svg")]
BounceInOut,
Steps(usize, JumpAt),
#[doc = include_str!("../../images/easefunction/Elastic.svg")]
Elastic(f32),
}
#[doc = include_str!("../../images/easefunction/Linear.svg")]
#[derive(Copy, Clone)]
pub struct LinearCurve;
#[doc = include_str!("../../images/easefunction/QuadraticIn.svg")]
#[derive(Copy, Clone)]
pub struct QuadraticInCurve;
#[doc = include_str!("../../images/easefunction/QuadraticOut.svg")]
#[derive(Copy, Clone)]
pub struct QuadraticOutCurve;
#[doc = include_str!("../../images/easefunction/QuadraticInOut.svg")]
#[derive(Copy, Clone)]
pub struct QuadraticInOutCurve;
#[doc = include_str!("../../images/easefunction/CubicIn.svg")]
#[derive(Copy, Clone)]
pub struct CubicInCurve;
#[doc = include_str!("../../images/easefunction/CubicOut.svg")]
#[derive(Copy, Clone)]
pub struct CubicOutCurve;
#[doc = include_str!("../../images/easefunction/CubicInOut.svg")]
#[derive(Copy, Clone)]
pub struct CubicInOutCurve;
#[doc = include_str!("../../images/easefunction/QuarticIn.svg")]
#[derive(Copy, Clone)]
pub struct QuarticInCurve;
#[doc = include_str!("../../images/easefunction/QuarticOut.svg")]
#[derive(Copy, Clone)]
pub struct QuarticOutCurve;
#[doc = include_str!("../../images/easefunction/QuarticInOut.svg")]
#[derive(Copy, Clone)]
pub struct QuarticInOutCurve;
#[doc = include_str!("../../images/easefunction/QuinticIn.svg")]
#[derive(Copy, Clone)]
pub struct QuinticInCurve;
#[doc = include_str!("../../images/easefunction/QuinticOut.svg")]
#[derive(Copy, Clone)]
pub struct QuinticOutCurve;
#[doc = include_str!("../../images/easefunction/QuinticInOut.svg")]
#[derive(Copy, Clone)]
pub struct QuinticInOutCurve;
#[doc = include_str!("../../images/easefunction/SmoothStepIn.svg")]
#[derive(Copy, Clone)]
pub struct SmoothStepInCurve;
#[doc = include_str!("../../images/easefunction/SmoothStepOut.svg")]
#[derive(Copy, Clone)]
pub struct SmoothStepOutCurve;
#[doc = include_str!("../../images/easefunction/SmoothStep.svg")]
#[derive(Copy, Clone)]
pub struct SmoothStepCurve;
#[doc = include_str!("../../images/easefunction/SmootherStepIn.svg")]
#[derive(Copy, Clone)]
pub struct SmootherStepInCurve;
#[doc = include_str!("../../images/easefunction/SmootherStepOut.svg")]
#[derive(Copy, Clone)]
pub struct SmootherStepOutCurve;
#[doc = include_str!("../../images/easefunction/SmootherStep.svg")]
#[derive(Copy, Clone)]
pub struct SmootherStepCurve;
#[doc = include_str!("../../images/easefunction/SineIn.svg")]
#[derive(Copy, Clone)]
pub struct SineInCurve;
#[doc = include_str!("../../images/easefunction/SineOut.svg")]
#[derive(Copy, Clone)]
pub struct SineOutCurve;
#[doc = include_str!("../../images/easefunction/SineInOut.svg")]
#[derive(Copy, Clone)]
pub struct SineInOutCurve;
#[doc = include_str!("../../images/easefunction/CircularIn.svg")]
#[derive(Copy, Clone)]
pub struct CircularInCurve;
#[doc = include_str!("../../images/easefunction/CircularOut.svg")]
#[derive(Copy, Clone)]
pub struct CircularOutCurve;
#[doc = include_str!("../../images/easefunction/CircularInOut.svg")]
#[derive(Copy, Clone)]
pub struct CircularInOutCurve;
#[doc = include_str!("../../images/easefunction/ExponentialIn.svg")]
#[derive(Copy, Clone)]
pub struct ExponentialInCurve;
#[doc = include_str!("../../images/easefunction/ExponentialOut.svg")]
#[derive(Copy, Clone)]
pub struct ExponentialOutCurve;
#[doc = include_str!("../../images/easefunction/ExponentialInOut.svg")]
#[derive(Copy, Clone)]
pub struct ExponentialInOutCurve;
#[doc = include_str!("../../images/easefunction/ElasticIn.svg")]
#[derive(Copy, Clone)]
pub struct ElasticInCurve;
#[doc = include_str!("../../images/easefunction/ElasticOut.svg")]
#[derive(Copy, Clone)]
pub struct ElasticOutCurve;
#[doc = include_str!("../../images/easefunction/ElasticInOut.svg")]
#[derive(Copy, Clone)]
pub struct ElasticInOutCurve;
#[doc = include_str!("../../images/easefunction/BackIn.svg")]
#[derive(Copy, Clone)]
pub struct BackInCurve;
#[doc = include_str!("../../images/easefunction/BackOut.svg")]
#[derive(Copy, Clone)]
pub struct BackOutCurve;
#[doc = include_str!("../../images/easefunction/BackInOut.svg")]
#[derive(Copy, Clone)]
pub struct BackInOutCurve;
#[doc = include_str!("../../images/easefunction/BounceIn.svg")]
#[derive(Copy, Clone)]
pub struct BounceInCurve;
#[doc = include_str!("../../images/easefunction/BounceOut.svg")]
#[derive(Copy, Clone)]
pub struct BounceOutCurve;
#[doc = include_str!("../../images/easefunction/BounceInOut.svg")]
#[derive(Copy, Clone)]
pub struct BounceInOutCurve;
#[derive(Copy, Clone)]
pub struct StepsCurve(pub usize, pub JumpAt);
#[doc = include_str!("../../images/easefunction/Elastic.svg")]
#[derive(Copy, Clone)]
pub struct ElasticCurve(pub f32);
macro_rules! impl_ease_unit_struct {
($ty: ty, $fn: ident) => {
impl Curve<f32> for $ty {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> f32 {
easing_functions::$fn(t)
}
}
};
}
impl_ease_unit_struct!(LinearCurve, linear);
impl_ease_unit_struct!(QuadraticInCurve, quadratic_in);
impl_ease_unit_struct!(QuadraticOutCurve, quadratic_out);
impl_ease_unit_struct!(QuadraticInOutCurve, quadratic_in_out);
impl_ease_unit_struct!(CubicInCurve, cubic_in);
impl_ease_unit_struct!(CubicOutCurve, cubic_out);
impl_ease_unit_struct!(CubicInOutCurve, cubic_in_out);
impl_ease_unit_struct!(QuarticInCurve, quartic_in);
impl_ease_unit_struct!(QuarticOutCurve, quartic_out);
impl_ease_unit_struct!(QuarticInOutCurve, quartic_in_out);
impl_ease_unit_struct!(QuinticInCurve, quintic_in);
impl_ease_unit_struct!(QuinticOutCurve, quintic_out);
impl_ease_unit_struct!(QuinticInOutCurve, quintic_in_out);
impl_ease_unit_struct!(SmoothStepInCurve, smoothstep_in);
impl_ease_unit_struct!(SmoothStepOutCurve, smoothstep_out);
impl_ease_unit_struct!(SmoothStepCurve, smoothstep);
impl_ease_unit_struct!(SmootherStepInCurve, smootherstep_in);
impl_ease_unit_struct!(SmootherStepOutCurve, smootherstep_out);
impl_ease_unit_struct!(SmootherStepCurve, smootherstep);
impl_ease_unit_struct!(SineInCurve, sine_in);
impl_ease_unit_struct!(SineOutCurve, sine_out);
impl_ease_unit_struct!(SineInOutCurve, sine_in_out);
impl_ease_unit_struct!(CircularInCurve, circular_in);
impl_ease_unit_struct!(CircularOutCurve, circular_out);
impl_ease_unit_struct!(CircularInOutCurve, circular_in_out);
impl_ease_unit_struct!(ExponentialInCurve, exponential_in);
impl_ease_unit_struct!(ExponentialOutCurve, exponential_out);
impl_ease_unit_struct!(ExponentialInOutCurve, exponential_in_out);
impl_ease_unit_struct!(ElasticInCurve, elastic_in);
impl_ease_unit_struct!(ElasticOutCurve, elastic_out);
impl_ease_unit_struct!(ElasticInOutCurve, elastic_in_out);
impl_ease_unit_struct!(BackInCurve, back_in);
impl_ease_unit_struct!(BackOutCurve, back_out);
impl_ease_unit_struct!(BackInOutCurve, back_in_out);
impl_ease_unit_struct!(BounceInCurve, bounce_in);
impl_ease_unit_struct!(BounceOutCurve, bounce_out);
impl_ease_unit_struct!(BounceInOutCurve, bounce_in_out);
impl Curve<f32> for StepsCurve {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> f32 {
easing_functions::steps(self.0, self.1, t)
}
}
impl Curve<f32> for ElasticCurve {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> f32 {
easing_functions::elastic(self.0, t)
}
}
mod easing_functions {
use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, PI};
use crate::{ops, FloatPow};
#[inline]
pub(crate) fn linear(t: f32) -> f32 {
t
}
#[inline]
pub(crate) fn quadratic_in(t: f32) -> f32 {
t.squared()
}
#[inline]
pub(crate) fn quadratic_out(t: f32) -> f32 {
1.0 - (1.0 - t).squared()
}
#[inline]
pub(crate) fn quadratic_in_out(t: f32) -> f32 {
if t < 0.5 {
2.0 * t.squared()
} else {
1.0 - (-2.0 * t + 2.0).squared() / 2.0
}
}
#[inline]
pub(crate) fn cubic_in(t: f32) -> f32 {
t.cubed()
}
#[inline]
pub(crate) fn cubic_out(t: f32) -> f32 {
1.0 - (1.0 - t).cubed()
}
#[inline]
pub(crate) fn cubic_in_out(t: f32) -> f32 {
if t < 0.5 {
4.0 * t.cubed()
} else {
1.0 - (-2.0 * t + 2.0).cubed() / 2.0
}
}
#[inline]
pub(crate) fn quartic_in(t: f32) -> f32 {
t * t * t * t
}
#[inline]
pub(crate) fn quartic_out(t: f32) -> f32 {
1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
}
#[inline]
pub(crate) fn quartic_in_out(t: f32) -> f32 {
if t < 0.5 {
8.0 * t * t * t * t
} else {
1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
}
}
#[inline]
pub(crate) fn quintic_in(t: f32) -> f32 {
t * t * t * t * t
}
#[inline]
pub(crate) fn quintic_out(t: f32) -> f32 {
1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
}
#[inline]
pub(crate) fn quintic_in_out(t: f32) -> f32 {
if t < 0.5 {
16.0 * t * t * t * t * t
} else {
1.0 - (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
/ 2.0
}
}
#[inline]
pub(crate) fn smoothstep_in(t: f32) -> f32 {
((1.5 - 0.5 * t) * t) * t
}
#[inline]
pub(crate) fn smoothstep_out(t: f32) -> f32 {
(1.5 + (-0.5 * t) * t) * t
}
#[inline]
pub(crate) fn smoothstep(t: f32) -> f32 {
((3.0 - 2.0 * t) * t) * t
}
#[inline]
pub(crate) fn smootherstep_in(t: f32) -> f32 {
(((2.5 + (-1.875 + 0.375 * t) * t) * t) * t) * t
}
#[inline]
pub(crate) fn smootherstep_out(t: f32) -> f32 {
(1.875 + ((-1.25 + (0.375 * t) * t) * t) * t) * t
}
#[inline]
pub(crate) fn smootherstep(t: f32) -> f32 {
(((10.0 + (-15.0 + 6.0 * t) * t) * t) * t) * t
}
#[inline]
pub(crate) fn sine_in(t: f32) -> f32 {
1.0 - ops::cos(t * FRAC_PI_2)
}
#[inline]
pub(crate) fn sine_out(t: f32) -> f32 {
ops::sin(t * FRAC_PI_2)
}
#[inline]
pub(crate) fn sine_in_out(t: f32) -> f32 {
-(ops::cos(PI * t) - 1.0) / 2.0
}
#[inline]
pub(crate) fn circular_in(t: f32) -> f32 {
1.0 - ops::sqrt(1.0 - t.squared())
}
#[inline]
pub(crate) fn circular_out(t: f32) -> f32 {
ops::sqrt(1.0 - (t - 1.0).squared())
}
#[inline]
pub(crate) fn circular_in_out(t: f32) -> f32 {
if t < 0.5 {
(1.0 - ops::sqrt(1.0 - (2.0 * t).squared())) / 2.0
} else {
(ops::sqrt(1.0 - (-2.0 * t + 2.0).squared()) + 1.0) / 2.0
}
}
#[expect(
clippy::excessive_precision,
reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
)]
const LOG2_1023: f32 = 9.998590429745328646459226;
#[expect(
clippy::excessive_precision,
reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
)]
const FRAC_1_1023: f32 = 0.00097751710654936461388074291;
#[inline]
pub(crate) fn exponential_in(t: f32) -> f32 {
ops::exp2(10.0 * t - LOG2_1023) - FRAC_1_1023
}
#[inline]
pub(crate) fn exponential_out(t: f32) -> f32 {
(FRAC_1_1023 + 1.0) - ops::exp2(-10.0 * t - (LOG2_1023 - 10.0))
}
#[inline]
pub(crate) fn exponential_in_out(t: f32) -> f32 {
if t < 0.5 {
ops::exp2(20.0 * t - (LOG2_1023 + 1.0)) - (FRAC_1_1023 / 2.0)
} else {
(FRAC_1_1023 / 2.0 + 1.0) - ops::exp2(-20.0 * t - (LOG2_1023 - 19.0))
}
}
#[inline]
pub(crate) fn back_in(t: f32) -> f32 {
let c = 1.70158;
(c + 1.0) * t.cubed() - c * t.squared()
}
#[inline]
pub(crate) fn back_out(t: f32) -> f32 {
let c = 1.70158;
1.0 + (c + 1.0) * (t - 1.0).cubed() + c * (t - 1.0).squared()
}
#[inline]
pub(crate) fn back_in_out(t: f32) -> f32 {
let c1 = 1.70158;
let c2 = c1 + 1.525;
if t < 0.5 {
(2.0 * t).squared() * ((c2 + 1.0) * 2.0 * t - c2) / 2.0
} else {
((2.0 * t - 2.0).squared() * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
}
}
#[inline]
pub(crate) fn elastic_in(t: f32) -> f32 {
-ops::powf(2.0, 10.0 * t - 10.0) * ops::sin((t * 10.0 - 10.75) * 2.0 * FRAC_PI_3)
}
#[inline]
pub(crate) fn elastic_out(t: f32) -> f32 {
ops::powf(2.0, -10.0 * t) * ops::sin((t * 10.0 - 0.75) * 2.0 * FRAC_PI_3) + 1.0
}
#[inline]
pub(crate) fn elastic_in_out(t: f32) -> f32 {
let c = (2.0 * PI) / 4.5;
if t < 0.5 {
-ops::powf(2.0, 20.0 * t - 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0
} else {
ops::powf(2.0, -20.0 * t + 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0 + 1.0
}
}
#[inline]
pub(crate) fn bounce_in(t: f32) -> f32 {
1.0 - bounce_out(1.0 - t)
}
#[inline]
pub(crate) fn bounce_out(t: f32) -> f32 {
if t < 4.0 / 11.0 {
(121.0 * t.squared()) / 16.0
} else if t < 8.0 / 11.0 {
(363.0 / 40.0 * t.squared()) - (99.0 / 10.0 * t) + 17.0 / 5.0
} else if t < 9.0 / 10.0 {
(4356.0 / 361.0 * t.squared()) - (35442.0 / 1805.0 * t) + 16061.0 / 1805.0
} else {
(54.0 / 5.0 * t.squared()) - (513.0 / 25.0 * t) + 268.0 / 25.0
}
}
#[inline]
pub(crate) fn bounce_in_out(t: f32) -> f32 {
if t < 0.5 {
(1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
} else {
(1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
}
}
#[inline]
pub(crate) fn steps(num_steps: usize, jump_at: super::JumpAt, t: f32) -> f32 {
jump_at.eval(num_steps, t)
}
#[inline]
pub(crate) fn elastic(omega: f32, t: f32) -> f32 {
1.0 - (1.0 - t).squared() * (2.0 * ops::sin(omega * t) / omega + ops::cos(omega * t))
}
}
impl EaseFunction {
fn eval(&self, t: f32) -> f32 {
match self {
EaseFunction::Linear => easing_functions::linear(t),
EaseFunction::QuadraticIn => easing_functions::quadratic_in(t),
EaseFunction::QuadraticOut => easing_functions::quadratic_out(t),
EaseFunction::QuadraticInOut => easing_functions::quadratic_in_out(t),
EaseFunction::CubicIn => easing_functions::cubic_in(t),
EaseFunction::CubicOut => easing_functions::cubic_out(t),
EaseFunction::CubicInOut => easing_functions::cubic_in_out(t),
EaseFunction::QuarticIn => easing_functions::quartic_in(t),
EaseFunction::QuarticOut => easing_functions::quartic_out(t),
EaseFunction::QuarticInOut => easing_functions::quartic_in_out(t),
EaseFunction::QuinticIn => easing_functions::quintic_in(t),
EaseFunction::QuinticOut => easing_functions::quintic_out(t),
EaseFunction::QuinticInOut => easing_functions::quintic_in_out(t),
EaseFunction::SmoothStepIn => easing_functions::smoothstep_in(t),
EaseFunction::SmoothStepOut => easing_functions::smoothstep_out(t),
EaseFunction::SmoothStep => easing_functions::smoothstep(t),
EaseFunction::SmootherStepIn => easing_functions::smootherstep_in(t),
EaseFunction::SmootherStepOut => easing_functions::smootherstep_out(t),
EaseFunction::SmootherStep => easing_functions::smootherstep(t),
EaseFunction::SineIn => easing_functions::sine_in(t),
EaseFunction::SineOut => easing_functions::sine_out(t),
EaseFunction::SineInOut => easing_functions::sine_in_out(t),
EaseFunction::CircularIn => easing_functions::circular_in(t),
EaseFunction::CircularOut => easing_functions::circular_out(t),
EaseFunction::CircularInOut => easing_functions::circular_in_out(t),
EaseFunction::ExponentialIn => easing_functions::exponential_in(t),
EaseFunction::ExponentialOut => easing_functions::exponential_out(t),
EaseFunction::ExponentialInOut => easing_functions::exponential_in_out(t),
EaseFunction::ElasticIn => easing_functions::elastic_in(t),
EaseFunction::ElasticOut => easing_functions::elastic_out(t),
EaseFunction::ElasticInOut => easing_functions::elastic_in_out(t),
EaseFunction::BackIn => easing_functions::back_in(t),
EaseFunction::BackOut => easing_functions::back_out(t),
EaseFunction::BackInOut => easing_functions::back_in_out(t),
EaseFunction::BounceIn => easing_functions::bounce_in(t),
EaseFunction::BounceOut => easing_functions::bounce_out(t),
EaseFunction::BounceInOut => easing_functions::bounce_in_out(t),
EaseFunction::Steps(num_steps, jump_at) => {
easing_functions::steps(*num_steps, *jump_at, t)
}
EaseFunction::Elastic(omega) => easing_functions::elastic(*omega, t),
}
}
}
impl Curve<f32> for EaseFunction {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> f32 {
self.eval(t)
}
}
#[cfg(test)]
#[cfg(feature = "approx")]
mod tests {
use crate::{Vec2, Vec3, Vec3A};
use approx::assert_abs_diff_eq;
use super::*;
const MONOTONIC_IN_OUT_INOUT: &[[EaseFunction; 3]] = {
use EaseFunction::*;
&[
[QuadraticIn, QuadraticOut, QuadraticInOut],
[CubicIn, CubicOut, CubicInOut],
[QuarticIn, QuarticOut, QuarticInOut],
[QuinticIn, QuinticOut, QuinticInOut],
[SmoothStepIn, SmoothStepOut, SmoothStep],
[SmootherStepIn, SmootherStepOut, SmootherStep],
[SineIn, SineOut, SineInOut],
[CircularIn, CircularOut, CircularInOut],
[ExponentialIn, ExponentialOut, ExponentialInOut],
]
};
const TOLERANCE: f32 = 1.0e-6;
const _: () = const {
assert!(1.0 - TOLERANCE != 1.0);
};
#[test]
fn ease_functions_zero_to_one() {
for ef in MONOTONIC_IN_OUT_INOUT.iter().flatten() {
let start = ef.eval(0.0);
assert!(
(0.0..=TOLERANCE).contains(&start),
"EaseFunction.{ef:?}(0) was {start:?}",
);
let finish = ef.eval(1.0);
assert!(
(1.0 - TOLERANCE..=1.0).contains(&finish),
"EaseFunction.{ef:?}(1) was {start:?}",
);
}
}
#[test]
fn ease_function_inout_deciles() {
for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
for x in [0.1, 0.2, 0.3, 0.4] {
let y = ef_inout.eval(x);
assert!(y < x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
let iny = ef_in.eval(2.0 * x) / 2.0;
assert!(
(y - TOLERANCE..y + TOLERANCE).contains(&iny),
"EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
EaseFunction.{ef_in:?}(2 * {x:?}) / 2 was {iny:?}",
);
}
for x in [0.6, 0.7, 0.8, 0.9] {
let y = ef_inout.eval(x);
assert!(y > x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
let outy = ef_out.eval(2.0 * x - 1.0) / 2.0 + 0.5;
assert!(
(y - TOLERANCE..y + TOLERANCE).contains(&outy),
"EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
EaseFunction.{ef_out:?}(2 * {x:?} - 1) / 2 + ½ was {outy:?}",
);
}
}
}
#[test]
fn ease_function_midpoints() {
for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
let mid = ef_in.eval(0.5);
assert!(
mid < 0.5 - TOLERANCE,
"EaseFunction.{ef_in:?}(½) was {mid:?}",
);
let mid = ef_out.eval(0.5);
assert!(
mid > 0.5 + TOLERANCE,
"EaseFunction.{ef_out:?}(½) was {mid:?}",
);
let mid = ef_inout.eval(0.5);
assert!(
(0.5 - TOLERANCE..=0.5 + TOLERANCE).contains(&mid),
"EaseFunction.{ef_inout:?}(½) was {mid:?}",
);
}
}
#[test]
fn ease_quats() {
let quat_start = Quat::from_axis_angle(Vec3::Z, 0.0);
let quat_end = Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians());
let quat_curve = Quat::interpolating_curve_unbounded(quat_start, quat_end);
assert_abs_diff_eq!(
quat_curve.sample(0.0).unwrap(),
Quat::from_axis_angle(Vec3::Z, 0.0)
);
{
let (before_mid_axis, before_mid_angle) =
quat_curve.sample(0.25).unwrap().to_axis_angle();
assert_abs_diff_eq!(before_mid_axis, Vec3::Z);
assert_abs_diff_eq!(before_mid_angle, 22.5_f32.to_radians());
}
{
let (mid_axis, mid_angle) = quat_curve.sample(0.5).unwrap().to_axis_angle();
assert_abs_diff_eq!(mid_axis, Vec3::Z);
assert_abs_diff_eq!(mid_angle, 45.0_f32.to_radians());
}
{
let (after_mid_axis, after_mid_angle) =
quat_curve.sample(0.75).unwrap().to_axis_angle();
assert_abs_diff_eq!(after_mid_axis, Vec3::Z);
assert_abs_diff_eq!(after_mid_angle, 67.5_f32.to_radians());
}
assert_abs_diff_eq!(
quat_curve.sample(1.0).unwrap(),
Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians())
);
}
#[test]
fn ease_isometries_2d() {
let angle = 90.0;
let iso_2d_start = Isometry2d::new(Vec2::ZERO, Rot2::degrees(0.0));
let iso_2d_end = Isometry2d::new(Vec2::ONE, Rot2::degrees(angle));
let iso_2d_curve = Isometry2d::interpolating_curve_unbounded(iso_2d_start, iso_2d_end);
[-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
assert_abs_diff_eq!(
iso_2d_curve.sample(t).unwrap(),
Isometry2d::new(Vec2::ONE * t, Rot2::degrees(angle * t))
);
});
}
#[test]
fn ease_isometries_3d() {
let angle = 90.0_f32.to_radians();
let iso_3d_start = Isometry3d::new(Vec3A::ZERO, Quat::from_axis_angle(Vec3::Z, 0.0));
let iso_3d_end = Isometry3d::new(Vec3A::ONE, Quat::from_axis_angle(Vec3::Z, angle));
let iso_3d_curve = Isometry3d::interpolating_curve_unbounded(iso_3d_start, iso_3d_end);
[-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
assert_abs_diff_eq!(
iso_3d_curve.sample(t).unwrap(),
Isometry3d::new(Vec3A::ONE * t, Quat::from_axis_angle(Vec3::Z, angle * t))
);
});
}
#[test]
fn jump_at_start() {
let jump_at = JumpAt::Start;
let num_steps = 4;
[
(0.0, 0.25),
(0.249, 0.25),
(0.25, 0.5),
(0.499, 0.5),
(0.5, 0.75),
(0.749, 0.75),
(0.75, 1.0),
(1.0, 1.0),
]
.into_iter()
.for_each(|(t, expected)| {
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
});
}
#[test]
fn jump_at_end() {
let jump_at = JumpAt::End;
let num_steps = 4;
[
(0.0, 0.0),
(0.249, 0.0),
(0.25, 0.25),
(0.499, 0.25),
(0.5, 0.5),
(0.749, 0.5),
(0.75, 0.75),
(0.999, 0.75),
(1.0, 1.0),
]
.into_iter()
.for_each(|(t, expected)| {
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
});
}
#[test]
fn jump_at_none() {
let jump_at = JumpAt::None;
let num_steps = 5;
[
(0.0, 0.0),
(0.199, 0.0),
(0.2, 0.25),
(0.399, 0.25),
(0.4, 0.5),
(0.599, 0.5),
(0.6, 0.75),
(0.799, 0.75),
(0.8, 1.0),
(0.999, 1.0),
(1.0, 1.0),
]
.into_iter()
.for_each(|(t, expected)| {
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
});
}
#[test]
fn jump_at_both() {
let jump_at = JumpAt::Both;
let num_steps = 4;
[
(0.0, 0.2),
(0.249, 0.2),
(0.25, 0.4),
(0.499, 0.4),
(0.5, 0.6),
(0.749, 0.6),
(0.75, 0.8),
(0.999, 0.8),
(1.0, 1.0),
]
.into_iter()
.for_each(|(t, expected)| {
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
});
}
#[test]
fn ease_function_curve() {
let f0 = SmoothStepCurve;
let f1 = EaseFunction::SmoothStep;
let f2 = EasingCurve::new(0.0, 1.0, EaseFunction::SmoothStep);
assert_eq!(f0.domain(), f1.domain());
assert_eq!(f0.domain(), f2.domain());
[
-1.0,
-f32::MIN_POSITIVE,
0.0,
0.5,
1.0,
1.0 + f32::EPSILON,
2.0,
]
.into_iter()
.for_each(|t| {
assert_eq!(f0.sample(t), f1.sample(t));
assert_eq!(f0.sample(t), f2.sample(t));
assert_eq!(f0.sample_clamped(t), f1.sample_clamped(t));
assert_eq!(f0.sample_clamped(t), f2.sample_clamped(t));
});
}
#[test]
fn unit_structs_match_function() {
fn test(f1: impl Curve<f32>, f2: impl Curve<f32>, t: f32) {
assert_eq!(f1.sample(t), f2.sample(t));
}
for t in [-1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 2.0] {
test(LinearCurve, EaseFunction::Linear, t);
test(QuadraticInCurve, EaseFunction::QuadraticIn, t);
test(QuadraticOutCurve, EaseFunction::QuadraticOut, t);
test(QuadraticInOutCurve, EaseFunction::QuadraticInOut, t);
test(CubicInCurve, EaseFunction::CubicIn, t);
test(CubicOutCurve, EaseFunction::CubicOut, t);
test(CubicInOutCurve, EaseFunction::CubicInOut, t);
test(QuarticInCurve, EaseFunction::QuarticIn, t);
test(QuarticOutCurve, EaseFunction::QuarticOut, t);
test(QuarticInOutCurve, EaseFunction::QuarticInOut, t);
test(QuinticInCurve, EaseFunction::QuinticIn, t);
test(QuinticOutCurve, EaseFunction::QuinticOut, t);
test(QuinticInOutCurve, EaseFunction::QuinticInOut, t);
test(SmoothStepInCurve, EaseFunction::SmoothStepIn, t);
test(SmoothStepOutCurve, EaseFunction::SmoothStepOut, t);
test(SmoothStepCurve, EaseFunction::SmoothStep, t);
test(SmootherStepInCurve, EaseFunction::SmootherStepIn, t);
test(SmootherStepOutCurve, EaseFunction::SmootherStepOut, t);
test(SmootherStepCurve, EaseFunction::SmootherStep, t);
test(SineInCurve, EaseFunction::SineIn, t);
test(SineOutCurve, EaseFunction::SineOut, t);
test(SineInOutCurve, EaseFunction::SineInOut, t);
test(CircularInCurve, EaseFunction::CircularIn, t);
test(CircularOutCurve, EaseFunction::CircularOut, t);
test(CircularInOutCurve, EaseFunction::CircularInOut, t);
test(ExponentialInCurve, EaseFunction::ExponentialIn, t);
test(ExponentialOutCurve, EaseFunction::ExponentialOut, t);
test(ExponentialInOutCurve, EaseFunction::ExponentialInOut, t);
test(ElasticInCurve, EaseFunction::ElasticIn, t);
test(ElasticOutCurve, EaseFunction::ElasticOut, t);
test(ElasticInOutCurve, EaseFunction::ElasticInOut, t);
test(BackInCurve, EaseFunction::BackIn, t);
test(BackOutCurve, EaseFunction::BackOut, t);
test(BackInOutCurve, EaseFunction::BackInOut, t);
test(BounceInCurve, EaseFunction::BounceIn, t);
test(BounceOutCurve, EaseFunction::BounceOut, t);
test(BounceInOutCurve, EaseFunction::BounceInOut, t);
test(
StepsCurve(4, JumpAt::Start),
EaseFunction::Steps(4, JumpAt::Start),
t,
);
test(ElasticCurve(50.0), EaseFunction::Elastic(50.0), t);
}
}
}