keyframe/
easing.rs

1use core::borrow::Borrow;
2use core::mem::MaybeUninit;
3
4pub(crate) use crate::*;
5
6#[cfg(feature = "mint_types")]
7pub use mint_type_impls::*;
8
9/// Implementation of a 2D curve function for easing between two points
10pub trait EasingFunction {
11	/// For an X position on the curve, calculate the Y position.
12	/// 0.0-1.0 is start and end on both axes but values can go out of bounds.
13	///
14	/// # Note
15	///
16	/// Because this method has a `&self` argument this trait can be used to both implement a "static" curve function (e.g. a linear interpolation)
17	/// or a "dynamic" curve function (e.g. a bezier curve with user defined inputs).
18	///
19	/// Since a static curve function will have zero size the size of a `dyn EasingFunction` will be the same size as a vtable.
20	/// This also means you can specify a static curve function with only the name of the type (e.g. `ease(EaseInOut, 0.0, 1.0, 0.5)`).
21	fn y(&self, x: f64) -> f64;
22}
23
24/// Type that can be used with an easing function
25pub trait CanTween {
26	/// Returns the interpolated value between `from` and `to` at the specified time.
27	///
28	/// # Note
29	/// This function will always create a new value, so calling it on very large structures is not a good idea.
30	fn ease(from: Self, to: Self, time: impl Float) -> Self;
31}
32
33impl CanTween for f32 {
34	#[inline]
35	fn ease(from: Self, to: Self, time: impl Float) -> Self {
36		as_t(as_f64(from) + as_f64(to - from) * as_f64(time))
37	}
38}
39
40impl CanTween for f64 {
41	#[inline]
42	fn ease(from: Self, to: Self, time: impl Float) -> Self {
43		as_t(as_f64(from) + as_f64(to - from) * as_f64(time))
44	}
45}
46
47impl<T: CanTween, const N: usize> CanTween for [T; N] {
48	fn ease(from: Self, to: Self, time: impl Float) -> Self {
49		// This is safe, see: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html#initializing-an-array-element-by-element
50		let mut result_uninit: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
51
52		for (i, (f, t)) in IntoIterator::into_iter(from)
53			.zip(IntoIterator::into_iter(to))
54			.enumerate()
55		{
56			// Initialize the array while moving elements out of from and to...
57			result_uninit[i].write(T::ease(f, t, time));
58		}
59
60		unsafe {
61			// MaybeUninit<T> is guaranteed to have the same size, alignment, and ABI as T.
62			let ptr = result_uninit.as_mut_ptr() as *mut [T; N];
63			let result = ptr.read();
64			core::mem::forget(result_uninit);
65
66			result
67		}
68	}
69}
70
71/// Returns the value at a specified X position on the curve between point A and point B.
72/// The time argument is expected to stay within a range of 0.0 to 1.0 but bounds checking is not enforced.
73#[inline]
74pub fn ease_with_unbounded_time<V: CanTween, F: EasingFunction>(
75	function: impl Borrow<F>,
76	from: V,
77	to: V,
78	time: impl Float,
79) -> V {
80	V::ease(from, to, function.borrow().y(as_f64(time)))
81}
82
83/// Returns the value at a specified X position on the curve between point A and point B.
84/// Time is limited to a range between 0.0 and 1.0.
85#[inline]
86pub fn ease<V: CanTween, T: Float, F: EasingFunction>(function: impl Borrow<F>, from: V, to: V, time: T) -> V {
87	ease_with_unbounded_time(
88		function,
89		from,
90		to,
91		match time {
92			_ if time < T::zero() => T::zero(),
93			_ if time > T::one() => T::one(),
94			_ => time,
95		},
96	)
97}
98
99/// Returns the value at a specified X position on the curve between point A and point B.
100/// Time is limited to a range between 0.0 and `max_time`.
101#[inline]
102pub fn ease_with_scaled_time<V: CanTween, T: Float, F: EasingFunction>(
103	function: impl Borrow<F>,
104	from: V,
105	to: V,
106	time: T,
107	max_time: T,
108) -> V {
109	ease(
110		function,
111		from,
112		to,
113		match time {
114			_ if time < T::zero() => T::zero(),
115			_ if time > max_time => T::one(),
116			_ => time / max_time,
117		},
118	)
119}
120
121#[cfg(feature = "mint_types")]
122mod mint_type_impls {
123	use crate::easing::*;
124
125	impl<V: CanTween> CanTween for Vector2<V> {
126		#[inline]
127		fn ease(from: Self, to: Self, time: impl Float) -> Self {
128			Self {
129				x: V::ease(from.x, to.x, time),
130				y: V::ease(from.y, to.y, time),
131			}
132		}
133	}
134
135	impl<V: CanTween> CanTween for Vector3<V> {
136		#[inline]
137		fn ease(from: Self, to: Self, time: impl Float) -> Self {
138			Self {
139				x: V::ease(from.x, to.x, time),
140				y: V::ease(from.y, to.y, time),
141				z: V::ease(from.z, to.z, time),
142			}
143		}
144	}
145
146	impl<V: CanTween> CanTween for Vector4<V> {
147		#[inline]
148		fn ease(from: Self, to: Self, time: impl Float) -> Self {
149			Self {
150				x: V::ease(from.x, to.x, time),
151				y: V::ease(from.y, to.y, time),
152				z: V::ease(from.z, to.z, time),
153				w: V::ease(from.w, to.w, time),
154			}
155		}
156	}
157
158	impl<V: CanTween> CanTween for Point2<V> {
159		#[inline]
160		fn ease(from: Self, to: Self, time: impl Float) -> Self {
161			Self {
162				x: V::ease(from.x, to.x, time),
163				y: V::ease(from.y, to.y, time),
164			}
165		}
166	}
167
168	impl<V: CanTween> CanTween for Point3<V> {
169		#[inline]
170		fn ease(from: Self, to: Self, time: impl Float) -> Self {
171			Self {
172				x: V::ease(from.x, to.x, time),
173				y: V::ease(from.y, to.y, time),
174				z: V::ease(from.z, to.z, time),
175			}
176		}
177	}
178}