1use crate::{
7 curve::{Curve, CurveExt, FunctionCurve, Interval},
8 Dir2, Dir3, Dir3A, Isometry2d, Isometry3d, Quat, Rot2, VectorSpace,
9};
10
11#[cfg(feature = "bevy_reflect")]
12use bevy_reflect::std_traits::ReflectDefault;
13
14use variadics_please::all_tuples_enumerated;
15
16pub trait Ease: Sized {
25 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self>;
33}
34
35impl<V: VectorSpace> Ease for V {
36 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
37 FunctionCurve::new(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
38 }
39}
40
41impl Ease for Rot2 {
42 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
43 FunctionCurve::new(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
44 }
45}
46
47impl Ease for Quat {
48 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
49 let dot = start.dot(end);
50 let end_adjusted = if dot < 0.0 { -end } else { end };
51 let difference = end_adjusted * start.inverse();
52 let (axis, angle) = difference.to_axis_angle();
53 FunctionCurve::new(Interval::EVERYWHERE, move |s| {
54 Quat::from_axis_angle(axis, angle * s) * start
55 })
56 }
57}
58
59impl Ease for Dir2 {
60 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
61 FunctionCurve::new(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
62 }
63}
64
65impl Ease for Dir3 {
66 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
67 let difference_quat = Quat::from_rotation_arc(start.as_vec3(), end.as_vec3());
68 Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
69 }
70}
71
72impl Ease for Dir3A {
73 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
74 let difference_quat =
75 Quat::from_rotation_arc(start.as_vec3a().into(), end.as_vec3a().into());
76 Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
77 }
78}
79
80impl Ease for Isometry3d {
81 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
82 FunctionCurve::new(Interval::EVERYWHERE, move |t| {
83 Isometry3d {
86 rotation: Quat::interpolating_curve_unbounded(start.rotation, end.rotation)
87 .sample_unchecked(t),
88 translation: crate::Vec3A::interpolating_curve_unbounded(
89 start.translation,
90 end.translation,
91 )
92 .sample_unchecked(t),
93 }
94 })
95 }
96}
97
98impl Ease for Isometry2d {
99 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
100 FunctionCurve::new(Interval::EVERYWHERE, move |t| {
101 Isometry2d {
104 rotation: Rot2::interpolating_curve_unbounded(start.rotation, end.rotation)
105 .sample_unchecked(t),
106 translation: crate::Vec2::interpolating_curve_unbounded(
107 start.translation,
108 end.translation,
109 )
110 .sample_unchecked(t),
111 }
112 })
113 }
114}
115
116macro_rules! impl_ease_tuple {
117 ($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => {
118 $(#[$meta])*
119 impl<$($T: Ease),*> Ease for ($($T,)*) {
120 fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
121 let curve_tuple =
122 (
123 $(
124 <$T as Ease>::interpolating_curve_unbounded(start.$n, end.$n),
125 )*
126 );
127
128 FunctionCurve::new(Interval::EVERYWHERE, move |t|
129 (
130 $(
131 curve_tuple.$n.sample_unchecked(t),
132 )*
133 )
134 )
135 }
136 }
137 };
138}
139
140all_tuples_enumerated!(
141 #[doc(fake_variadic)]
142 impl_ease_tuple,
143 1,
144 11,
145 T
146);
147
148#[derive(Clone, Debug)]
234#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
235#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
236pub struct EasingCurve<T> {
237 start: T,
238 end: T,
239 ease_fn: EaseFunction,
240}
241
242impl<T> EasingCurve<T> {
243 pub fn new(start: T, end: T, ease_fn: EaseFunction) -> Self {
250 Self {
251 start,
252 end,
253 ease_fn,
254 }
255 }
256}
257
258impl<T> Curve<T> for EasingCurve<T>
259where
260 T: Ease + Clone,
261{
262 #[inline]
263 fn domain(&self) -> Interval {
264 Interval::UNIT
265 }
266
267 #[inline]
268 fn sample_unchecked(&self, t: f32) -> T {
269 let remapped_t = self.ease_fn.eval(t);
270 T::interpolating_curve_unbounded(self.start.clone(), self.end.clone())
271 .sample_unchecked(remapped_t)
272 }
273}
274
275#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
280#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
281#[cfg_attr(
282 feature = "bevy_reflect",
283 derive(bevy_reflect::Reflect),
284 reflect(Clone, Default, PartialEq)
285)]
286pub enum JumpAt {
287 #[doc = include_str!("../../images/easefunction/StartSteps.svg")]
290 Start,
291 #[doc = include_str!("../../images/easefunction/EndSteps.svg")]
294 #[default]
295 End,
296 #[doc = include_str!("../../images/easefunction/NoneSteps.svg")]
299 None,
300 #[doc = include_str!("../../images/easefunction/BothSteps.svg")]
303 Both,
304}
305
306impl JumpAt {
307 #[inline]
308 pub(crate) fn eval(self, num_steps: usize, t: f32) -> f32 {
309 use crate::ops;
310
311 let (a, b) = match self {
312 JumpAt::Start => (1.0, 0),
313 JumpAt::End => (0.0, 0),
314 JumpAt::None => (0.0, -1),
315 JumpAt::Both => (1.0, 1),
316 };
317
318 let current_step = ops::floor(t * num_steps as f32) + a;
319 let step_size = (num_steps as isize + b).max(1) as f32;
320
321 (current_step / step_size).clamp(0.0, 1.0)
322 }
323}
324
325#[non_exhaustive]
364#[derive(Debug, Copy, Clone, PartialEq)]
365#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
366#[cfg_attr(
367 feature = "bevy_reflect",
368 derive(bevy_reflect::Reflect),
369 reflect(Clone, PartialEq)
370)]
371pub enum EaseFunction {
373 #[doc = include_str!("../../images/easefunction/Linear.svg")]
376 Linear,
377
378 #[doc = include_str!("../../images/easefunction/QuadraticIn.svg")]
386 QuadraticIn,
387 #[doc = include_str!("../../images/easefunction/QuadraticOut.svg")]
395 QuadraticOut,
396 #[doc = include_str!("../../images/easefunction/QuadraticInOut.svg")]
403 QuadraticInOut,
404
405 #[doc = include_str!("../../images/easefunction/CubicIn.svg")]
414 CubicIn,
415 #[doc = include_str!("../../images/easefunction/CubicOut.svg")]
418 CubicOut,
419 #[doc = include_str!("../../images/easefunction/CubicInOut.svg")]
429 CubicInOut,
430
431 #[doc = include_str!("../../images/easefunction/QuarticIn.svg")]
434 QuarticIn,
435 #[doc = include_str!("../../images/easefunction/QuarticOut.svg")]
438 QuarticOut,
439 #[doc = include_str!("../../images/easefunction/QuarticInOut.svg")]
442 QuarticInOut,
443
444 #[doc = include_str!("../../images/easefunction/QuinticIn.svg")]
447 QuinticIn,
448 #[doc = include_str!("../../images/easefunction/QuinticOut.svg")]
451 QuinticOut,
452 #[doc = include_str!("../../images/easefunction/QuinticInOut.svg")]
460 QuinticInOut,
461
462 #[doc = include_str!("../../images/easefunction/SmoothStepIn.svg")]
467 SmoothStepIn,
468 #[doc = include_str!("../../images/easefunction/SmoothStepOut.svg")]
473 SmoothStepOut,
474 #[doc = include_str!("../../images/easefunction/SmoothStep.svg")]
487 SmoothStep,
488
489 #[doc = include_str!("../../images/easefunction/SmootherStepIn.svg")]
494 SmootherStepIn,
495 #[doc = include_str!("../../images/easefunction/SmootherStepOut.svg")]
500 SmootherStepOut,
501 #[doc = include_str!("../../images/easefunction/SmootherStep.svg")]
512 SmootherStep,
513
514 #[doc = include_str!("../../images/easefunction/SineIn.svg")]
517 SineIn,
518 #[doc = include_str!("../../images/easefunction/SineOut.svg")]
521 SineOut,
522 #[doc = include_str!("../../images/easefunction/SineInOut.svg")]
525 SineInOut,
526
527 #[doc = include_str!("../../images/easefunction/CircularIn.svg")]
530 CircularIn,
531 #[doc = include_str!("../../images/easefunction/CircularOut.svg")]
534 CircularOut,
535 #[doc = include_str!("../../images/easefunction/CircularInOut.svg")]
538 CircularInOut,
539
540 #[doc = include_str!("../../images/easefunction/ExponentialIn.svg")]
546 ExponentialIn,
547 #[doc = include_str!("../../images/easefunction/ExponentialOut.svg")]
553 ExponentialOut,
554 #[doc = include_str!("../../images/easefunction/ExponentialInOut.svg")]
557 ExponentialInOut,
558
559 #[doc = include_str!("../../images/easefunction/ElasticIn.svg")]
562 ElasticIn,
563 #[doc = include_str!("../../images/easefunction/ElasticOut.svg")]
566 ElasticOut,
567 #[doc = include_str!("../../images/easefunction/ElasticInOut.svg")]
570 ElasticInOut,
571
572 #[doc = include_str!("../../images/easefunction/BackIn.svg")]
575 BackIn,
576 #[doc = include_str!("../../images/easefunction/BackOut.svg")]
579 BackOut,
580 #[doc = include_str!("../../images/easefunction/BackInOut.svg")]
583 BackInOut,
584
585 #[doc = include_str!("../../images/easefunction/BounceIn.svg")]
588 BounceIn,
589 #[doc = include_str!("../../images/easefunction/BounceOut.svg")]
592 BounceOut,
593 #[doc = include_str!("../../images/easefunction/BounceInOut.svg")]
596 BounceInOut,
597
598 Steps(usize, JumpAt),
601
602 #[doc = include_str!("../../images/easefunction/Elastic.svg")]
605 Elastic(f32),
606}
607
608mod easing_functions {
609 use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, PI};
610
611 use crate::{ops, FloatPow};
612
613 #[inline]
614 pub(crate) fn linear(t: f32) -> f32 {
615 t
616 }
617
618 #[inline]
619 pub(crate) fn quadratic_in(t: f32) -> f32 {
620 t.squared()
621 }
622 #[inline]
623 pub(crate) fn quadratic_out(t: f32) -> f32 {
624 1.0 - (1.0 - t).squared()
625 }
626 #[inline]
627 pub(crate) fn quadratic_in_out(t: f32) -> f32 {
628 if t < 0.5 {
629 2.0 * t.squared()
630 } else {
631 1.0 - (-2.0 * t + 2.0).squared() / 2.0
632 }
633 }
634
635 #[inline]
636 pub(crate) fn cubic_in(t: f32) -> f32 {
637 t.cubed()
638 }
639 #[inline]
640 pub(crate) fn cubic_out(t: f32) -> f32 {
641 1.0 - (1.0 - t).cubed()
642 }
643 #[inline]
644 pub(crate) fn cubic_in_out(t: f32) -> f32 {
645 if t < 0.5 {
646 4.0 * t.cubed()
647 } else {
648 1.0 - (-2.0 * t + 2.0).cubed() / 2.0
649 }
650 }
651
652 #[inline]
653 pub(crate) fn quartic_in(t: f32) -> f32 {
654 t * t * t * t
655 }
656 #[inline]
657 pub(crate) fn quartic_out(t: f32) -> f32 {
658 1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
659 }
660 #[inline]
661 pub(crate) fn quartic_in_out(t: f32) -> f32 {
662 if t < 0.5 {
663 8.0 * t * t * t * t
664 } else {
665 1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
666 }
667 }
668
669 #[inline]
670 pub(crate) fn quintic_in(t: f32) -> f32 {
671 t * t * t * t * t
672 }
673 #[inline]
674 pub(crate) fn quintic_out(t: f32) -> f32 {
675 1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
676 }
677 #[inline]
678 pub(crate) fn quintic_in_out(t: f32) -> f32 {
679 if t < 0.5 {
680 16.0 * t * t * t * t * t
681 } else {
682 1.0 - (-2.0 * t + 2.0)
683 * (-2.0 * t + 2.0)
684 * (-2.0 * t + 2.0)
685 * (-2.0 * t + 2.0)
686 * (-2.0 * t + 2.0)
687 / 2.0
688 }
689 }
690
691 #[inline]
692 pub(crate) fn smoothstep_in(t: f32) -> f32 {
693 ((1.5 - 0.5 * t) * t) * t
694 }
695
696 #[inline]
697 pub(crate) fn smoothstep_out(t: f32) -> f32 {
698 (1.5 + (-0.5 * t) * t) * t
699 }
700
701 #[inline]
702 pub(crate) fn smoothstep(t: f32) -> f32 {
703 ((3.0 - 2.0 * t) * t) * t
704 }
705
706 #[inline]
707 pub(crate) fn smootherstep_in(t: f32) -> f32 {
708 (((2.5 + (-1.875 + 0.375 * t) * t) * t) * t) * t
709 }
710
711 #[inline]
712 pub(crate) fn smootherstep_out(t: f32) -> f32 {
713 (1.875 + ((-1.25 + (0.375 * t) * t) * t) * t) * t
714 }
715
716 #[inline]
717 pub(crate) fn smootherstep(t: f32) -> f32 {
718 (((10.0 + (-15.0 + 6.0 * t) * t) * t) * t) * t
719 }
720
721 #[inline]
722 pub(crate) fn sine_in(t: f32) -> f32 {
723 1.0 - ops::cos(t * FRAC_PI_2)
724 }
725 #[inline]
726 pub(crate) fn sine_out(t: f32) -> f32 {
727 ops::sin(t * FRAC_PI_2)
728 }
729 #[inline]
730 pub(crate) fn sine_in_out(t: f32) -> f32 {
731 -(ops::cos(PI * t) - 1.0) / 2.0
732 }
733
734 #[inline]
735 pub(crate) fn circular_in(t: f32) -> f32 {
736 1.0 - ops::sqrt(1.0 - t.squared())
737 }
738 #[inline]
739 pub(crate) fn circular_out(t: f32) -> f32 {
740 ops::sqrt(1.0 - (t - 1.0).squared())
741 }
742 #[inline]
743 pub(crate) fn circular_in_out(t: f32) -> f32 {
744 if t < 0.5 {
745 (1.0 - ops::sqrt(1.0 - (2.0 * t).squared())) / 2.0
746 } else {
747 (ops::sqrt(1.0 - (-2.0 * t + 2.0).squared()) + 1.0) / 2.0
748 }
749 }
750
751 #[expect(
756 clippy::excessive_precision,
757 reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
758 )]
759 const LOG2_1023: f32 = 9.998590429745328646459226;
760 #[expect(
761 clippy::excessive_precision,
762 reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
763 )]
764 const FRAC_1_1023: f32 = 0.00097751710654936461388074291;
765 #[inline]
766 pub(crate) fn exponential_in(t: f32) -> f32 {
767 ops::exp2(10.0 * t - LOG2_1023) - FRAC_1_1023
770 }
771 #[inline]
772 pub(crate) fn exponential_out(t: f32) -> f32 {
773 (FRAC_1_1023 + 1.0) - ops::exp2(-10.0 * t - (LOG2_1023 - 10.0))
774 }
775 #[inline]
776 pub(crate) fn exponential_in_out(t: f32) -> f32 {
777 if t < 0.5 {
778 ops::exp2(20.0 * t - (LOG2_1023 + 1.0)) - (FRAC_1_1023 / 2.0)
779 } else {
780 (FRAC_1_1023 / 2.0 + 1.0) - ops::exp2(-20.0 * t - (LOG2_1023 - 19.0))
781 }
782 }
783
784 #[inline]
785 pub(crate) fn back_in(t: f32) -> f32 {
786 let c = 1.70158;
787
788 (c + 1.0) * t.cubed() - c * t.squared()
789 }
790 #[inline]
791 pub(crate) fn back_out(t: f32) -> f32 {
792 let c = 1.70158;
793
794 1.0 + (c + 1.0) * (t - 1.0).cubed() + c * (t - 1.0).squared()
795 }
796 #[inline]
797 pub(crate) fn back_in_out(t: f32) -> f32 {
798 let c1 = 1.70158;
799 let c2 = c1 + 1.525;
800
801 if t < 0.5 {
802 (2.0 * t).squared() * ((c2 + 1.0) * 2.0 * t - c2) / 2.0
803 } else {
804 ((2.0 * t - 2.0).squared() * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
805 }
806 }
807
808 #[inline]
809 pub(crate) fn elastic_in(t: f32) -> f32 {
810 -ops::powf(2.0, 10.0 * t - 10.0) * ops::sin((t * 10.0 - 10.75) * 2.0 * FRAC_PI_3)
811 }
812 #[inline]
813 pub(crate) fn elastic_out(t: f32) -> f32 {
814 ops::powf(2.0, -10.0 * t) * ops::sin((t * 10.0 - 0.75) * 2.0 * FRAC_PI_3) + 1.0
815 }
816 #[inline]
817 pub(crate) fn elastic_in_out(t: f32) -> f32 {
818 let c = (2.0 * PI) / 4.5;
819
820 if t < 0.5 {
821 -ops::powf(2.0, 20.0 * t - 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0
822 } else {
823 ops::powf(2.0, -20.0 * t + 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0 + 1.0
824 }
825 }
826
827 #[inline]
828 pub(crate) fn bounce_in(t: f32) -> f32 {
829 1.0 - bounce_out(1.0 - t)
830 }
831 #[inline]
832 pub(crate) fn bounce_out(t: f32) -> f32 {
833 if t < 4.0 / 11.0 {
834 (121.0 * t.squared()) / 16.0
835 } else if t < 8.0 / 11.0 {
836 (363.0 / 40.0 * t.squared()) - (99.0 / 10.0 * t) + 17.0 / 5.0
837 } else if t < 9.0 / 10.0 {
838 (4356.0 / 361.0 * t.squared()) - (35442.0 / 1805.0 * t) + 16061.0 / 1805.0
839 } else {
840 (54.0 / 5.0 * t.squared()) - (513.0 / 25.0 * t) + 268.0 / 25.0
841 }
842 }
843 #[inline]
844 pub(crate) fn bounce_in_out(t: f32) -> f32 {
845 if t < 0.5 {
846 (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
847 } else {
848 (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
849 }
850 }
851
852 #[inline]
853 pub(crate) fn steps(num_steps: usize, jump_at: super::JumpAt, t: f32) -> f32 {
854 jump_at.eval(num_steps, t)
855 }
856
857 #[inline]
858 pub(crate) fn elastic(omega: f32, t: f32) -> f32 {
859 1.0 - (1.0 - t).squared() * (2.0 * ops::sin(omega * t) / omega + ops::cos(omega * t))
860 }
861}
862
863impl EaseFunction {
864 fn eval(&self, t: f32) -> f32 {
865 match self {
866 EaseFunction::Linear => easing_functions::linear(t),
867 EaseFunction::QuadraticIn => easing_functions::quadratic_in(t),
868 EaseFunction::QuadraticOut => easing_functions::quadratic_out(t),
869 EaseFunction::QuadraticInOut => easing_functions::quadratic_in_out(t),
870 EaseFunction::CubicIn => easing_functions::cubic_in(t),
871 EaseFunction::CubicOut => easing_functions::cubic_out(t),
872 EaseFunction::CubicInOut => easing_functions::cubic_in_out(t),
873 EaseFunction::QuarticIn => easing_functions::quartic_in(t),
874 EaseFunction::QuarticOut => easing_functions::quartic_out(t),
875 EaseFunction::QuarticInOut => easing_functions::quartic_in_out(t),
876 EaseFunction::QuinticIn => easing_functions::quintic_in(t),
877 EaseFunction::QuinticOut => easing_functions::quintic_out(t),
878 EaseFunction::QuinticInOut => easing_functions::quintic_in_out(t),
879 EaseFunction::SmoothStepIn => easing_functions::smoothstep_in(t),
880 EaseFunction::SmoothStepOut => easing_functions::smoothstep_out(t),
881 EaseFunction::SmoothStep => easing_functions::smoothstep(t),
882 EaseFunction::SmootherStepIn => easing_functions::smootherstep_in(t),
883 EaseFunction::SmootherStepOut => easing_functions::smootherstep_out(t),
884 EaseFunction::SmootherStep => easing_functions::smootherstep(t),
885 EaseFunction::SineIn => easing_functions::sine_in(t),
886 EaseFunction::SineOut => easing_functions::sine_out(t),
887 EaseFunction::SineInOut => easing_functions::sine_in_out(t),
888 EaseFunction::CircularIn => easing_functions::circular_in(t),
889 EaseFunction::CircularOut => easing_functions::circular_out(t),
890 EaseFunction::CircularInOut => easing_functions::circular_in_out(t),
891 EaseFunction::ExponentialIn => easing_functions::exponential_in(t),
892 EaseFunction::ExponentialOut => easing_functions::exponential_out(t),
893 EaseFunction::ExponentialInOut => easing_functions::exponential_in_out(t),
894 EaseFunction::ElasticIn => easing_functions::elastic_in(t),
895 EaseFunction::ElasticOut => easing_functions::elastic_out(t),
896 EaseFunction::ElasticInOut => easing_functions::elastic_in_out(t),
897 EaseFunction::BackIn => easing_functions::back_in(t),
898 EaseFunction::BackOut => easing_functions::back_out(t),
899 EaseFunction::BackInOut => easing_functions::back_in_out(t),
900 EaseFunction::BounceIn => easing_functions::bounce_in(t),
901 EaseFunction::BounceOut => easing_functions::bounce_out(t),
902 EaseFunction::BounceInOut => easing_functions::bounce_in_out(t),
903 EaseFunction::Steps(num_steps, jump_at) => {
904 easing_functions::steps(*num_steps, *jump_at, t)
905 }
906 EaseFunction::Elastic(omega) => easing_functions::elastic(*omega, t),
907 }
908 }
909}
910
911impl Curve<f32> for EaseFunction {
912 #[inline]
913 fn domain(&self) -> Interval {
914 Interval::UNIT
915 }
916
917 #[inline]
918 fn sample_unchecked(&self, t: f32) -> f32 {
919 self.eval(t)
920 }
921}
922
923#[cfg(test)]
924#[cfg(feature = "approx")]
925mod tests {
926
927 use crate::{Vec2, Vec3, Vec3A};
928 use approx::assert_abs_diff_eq;
929
930 use super::*;
931 const MONOTONIC_IN_OUT_INOUT: &[[EaseFunction; 3]] = {
932 use EaseFunction::*;
933 &[
934 [QuadraticIn, QuadraticOut, QuadraticInOut],
935 [CubicIn, CubicOut, CubicInOut],
936 [QuarticIn, QuarticOut, QuarticInOut],
937 [QuinticIn, QuinticOut, QuinticInOut],
938 [SmoothStepIn, SmoothStepOut, SmoothStep],
939 [SmootherStepIn, SmootherStepOut, SmootherStep],
940 [SineIn, SineOut, SineInOut],
941 [CircularIn, CircularOut, CircularInOut],
942 [ExponentialIn, ExponentialOut, ExponentialInOut],
943 ]
944 };
945
946 const TOLERANCE: f32 = 1.0e-6;
949 const _: () = const {
950 assert!(1.0 - TOLERANCE != 1.0);
951 };
952
953 #[test]
954 fn ease_functions_zero_to_one() {
955 for ef in MONOTONIC_IN_OUT_INOUT.iter().flatten() {
956 let start = ef.eval(0.0);
957 assert!(
958 (0.0..=TOLERANCE).contains(&start),
959 "EaseFunction.{ef:?}(0) was {start:?}",
960 );
961
962 let finish = ef.eval(1.0);
963 assert!(
964 (1.0 - TOLERANCE..=1.0).contains(&finish),
965 "EaseFunction.{ef:?}(1) was {start:?}",
966 );
967 }
968 }
969
970 #[test]
971 fn ease_function_inout_deciles() {
972 for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
974 for x in [0.1, 0.2, 0.3, 0.4] {
975 let y = ef_inout.eval(x);
976 assert!(y < x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
977
978 let iny = ef_in.eval(2.0 * x) / 2.0;
979 assert!(
980 (y - TOLERANCE..y + TOLERANCE).contains(&iny),
981 "EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
982 EaseFunction.{ef_in:?}(2 * {x:?}) / 2 was {iny:?}",
983 );
984 }
985
986 for x in [0.6, 0.7, 0.8, 0.9] {
987 let y = ef_inout.eval(x);
988 assert!(y > x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
989
990 let outy = ef_out.eval(2.0 * x - 1.0) / 2.0 + 0.5;
991 assert!(
992 (y - TOLERANCE..y + TOLERANCE).contains(&outy),
993 "EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
994 EaseFunction.{ef_out:?}(2 * {x:?} - 1) / 2 + ½ was {outy:?}",
995 );
996 }
997 }
998 }
999
1000 #[test]
1001 fn ease_function_midpoints() {
1002 for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
1003 let mid = ef_in.eval(0.5);
1004 assert!(
1005 mid < 0.5 - TOLERANCE,
1006 "EaseFunction.{ef_in:?}(½) was {mid:?}",
1007 );
1008
1009 let mid = ef_out.eval(0.5);
1010 assert!(
1011 mid > 0.5 + TOLERANCE,
1012 "EaseFunction.{ef_out:?}(½) was {mid:?}",
1013 );
1014
1015 let mid = ef_inout.eval(0.5);
1016 assert!(
1017 (0.5 - TOLERANCE..=0.5 + TOLERANCE).contains(&mid),
1018 "EaseFunction.{ef_inout:?}(½) was {mid:?}",
1019 );
1020 }
1021 }
1022
1023 #[test]
1024 fn ease_quats() {
1025 let quat_start = Quat::from_axis_angle(Vec3::Z, 0.0);
1026 let quat_end = Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians());
1027
1028 let quat_curve = Quat::interpolating_curve_unbounded(quat_start, quat_end);
1029
1030 assert_abs_diff_eq!(
1031 quat_curve.sample(0.0).unwrap(),
1032 Quat::from_axis_angle(Vec3::Z, 0.0)
1033 );
1034 {
1035 let (before_mid_axis, before_mid_angle) =
1036 quat_curve.sample(0.25).unwrap().to_axis_angle();
1037 assert_abs_diff_eq!(before_mid_axis, Vec3::Z);
1038 assert_abs_diff_eq!(before_mid_angle, 22.5_f32.to_radians());
1039 }
1040 {
1041 let (mid_axis, mid_angle) = quat_curve.sample(0.5).unwrap().to_axis_angle();
1042 assert_abs_diff_eq!(mid_axis, Vec3::Z);
1043 assert_abs_diff_eq!(mid_angle, 45.0_f32.to_radians());
1044 }
1045 {
1046 let (after_mid_axis, after_mid_angle) =
1047 quat_curve.sample(0.75).unwrap().to_axis_angle();
1048 assert_abs_diff_eq!(after_mid_axis, Vec3::Z);
1049 assert_abs_diff_eq!(after_mid_angle, 67.5_f32.to_radians());
1050 }
1051 assert_abs_diff_eq!(
1052 quat_curve.sample(1.0).unwrap(),
1053 Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians())
1054 );
1055 }
1056
1057 #[test]
1058 fn ease_isometries_2d() {
1059 let angle = 90.0;
1060 let iso_2d_start = Isometry2d::new(Vec2::ZERO, Rot2::degrees(0.0));
1061 let iso_2d_end = Isometry2d::new(Vec2::ONE, Rot2::degrees(angle));
1062
1063 let iso_2d_curve = Isometry2d::interpolating_curve_unbounded(iso_2d_start, iso_2d_end);
1064
1065 [-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
1066 assert_abs_diff_eq!(
1067 iso_2d_curve.sample(t).unwrap(),
1068 Isometry2d::new(Vec2::ONE * t, Rot2::degrees(angle * t))
1069 );
1070 });
1071 }
1072
1073 #[test]
1074 fn ease_isometries_3d() {
1075 let angle = 90.0_f32.to_radians();
1076 let iso_3d_start = Isometry3d::new(Vec3A::ZERO, Quat::from_axis_angle(Vec3::Z, 0.0));
1077 let iso_3d_end = Isometry3d::new(Vec3A::ONE, Quat::from_axis_angle(Vec3::Z, angle));
1078
1079 let iso_3d_curve = Isometry3d::interpolating_curve_unbounded(iso_3d_start, iso_3d_end);
1080
1081 [-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
1082 assert_abs_diff_eq!(
1083 iso_3d_curve.sample(t).unwrap(),
1084 Isometry3d::new(Vec3A::ONE * t, Quat::from_axis_angle(Vec3::Z, angle * t))
1085 );
1086 });
1087 }
1088
1089 #[test]
1090 fn jump_at_start() {
1091 let jump_at = JumpAt::Start;
1092 let num_steps = 4;
1093
1094 [
1095 (0.0, 0.25),
1096 (0.249, 0.25),
1097 (0.25, 0.5),
1098 (0.499, 0.5),
1099 (0.5, 0.75),
1100 (0.749, 0.75),
1101 (0.75, 1.0),
1102 (1.0, 1.0),
1103 ]
1104 .into_iter()
1105 .for_each(|(t, expected)| {
1106 assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1107 });
1108 }
1109
1110 #[test]
1111 fn jump_at_end() {
1112 let jump_at = JumpAt::End;
1113 let num_steps = 4;
1114
1115 [
1116 (0.0, 0.0),
1117 (0.249, 0.0),
1118 (0.25, 0.25),
1119 (0.499, 0.25),
1120 (0.5, 0.5),
1121 (0.749, 0.5),
1122 (0.75, 0.75),
1123 (0.999, 0.75),
1124 (1.0, 1.0),
1125 ]
1126 .into_iter()
1127 .for_each(|(t, expected)| {
1128 assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1129 });
1130 }
1131
1132 #[test]
1133 fn jump_at_none() {
1134 let jump_at = JumpAt::None;
1135 let num_steps = 5;
1136
1137 [
1138 (0.0, 0.0),
1139 (0.199, 0.0),
1140 (0.2, 0.25),
1141 (0.399, 0.25),
1142 (0.4, 0.5),
1143 (0.599, 0.5),
1144 (0.6, 0.75),
1145 (0.799, 0.75),
1146 (0.8, 1.0),
1147 (0.999, 1.0),
1148 (1.0, 1.0),
1149 ]
1150 .into_iter()
1151 .for_each(|(t, expected)| {
1152 assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1153 });
1154 }
1155
1156 #[test]
1157 fn jump_at_both() {
1158 let jump_at = JumpAt::Both;
1159 let num_steps = 4;
1160
1161 [
1162 (0.0, 0.2),
1163 (0.249, 0.2),
1164 (0.25, 0.4),
1165 (0.499, 0.4),
1166 (0.5, 0.6),
1167 (0.749, 0.6),
1168 (0.75, 0.8),
1169 (0.999, 0.8),
1170 (1.0, 1.0),
1171 ]
1172 .into_iter()
1173 .for_each(|(t, expected)| {
1174 assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1175 });
1176 }
1177
1178 #[test]
1179 fn ease_function_curve() {
1180 let f = EaseFunction::SmoothStep;
1183 let c = EasingCurve::new(0.0, 1.0, EaseFunction::SmoothStep);
1184
1185 assert_eq!(f.domain(), c.domain());
1186
1187 [
1188 -1.0,
1189 0.0,
1190 0.5,
1191 1.0,
1192 2.0,
1193 -f32::MIN_POSITIVE,
1194 1.0 + f32::EPSILON,
1195 ]
1196 .into_iter()
1197 .for_each(|t| {
1198 assert_eq!(f.sample(t), c.sample(t));
1199 assert_eq!(f.sample_clamped(t), c.sample_clamped(t));
1200 });
1201 }
1202}