bevy_animation/
gltf_curves.rs

1//! Concrete curve structures used to load glTF curves into the animation system.
2
3use bevy_math::{
4    curve::{cores::*, iterable::IterableCurve, *},
5    vec4, Quat, Vec4, VectorSpace,
6};
7use bevy_reflect::Reflect;
8use either::Either;
9use thiserror::Error;
10
11/// A keyframe-defined curve that "interpolates" by stepping at `t = 1.0` to the next keyframe.
12#[derive(Debug, Clone, Reflect)]
13pub struct SteppedKeyframeCurve<T> {
14    core: UnevenCore<T>,
15}
16
17impl<T> Curve<T> for SteppedKeyframeCurve<T>
18where
19    T: Clone,
20{
21    #[inline]
22    fn domain(&self) -> Interval {
23        self.core.domain()
24    }
25
26    #[inline]
27    fn sample_clamped(&self, t: f32) -> T {
28        self.core
29            .sample_with(t, |x, y, t| if t >= 1.0 { y.clone() } else { x.clone() })
30    }
31
32    #[inline]
33    fn sample_unchecked(&self, t: f32) -> T {
34        self.sample_clamped(t)
35    }
36}
37
38impl<T> SteppedKeyframeCurve<T> {
39    /// Create a new [`SteppedKeyframeCurve`]. If the curve could not be constructed from the
40    /// given data, an error is returned.
41    #[inline]
42    pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
43        Ok(Self {
44            core: UnevenCore::new(timed_samples)?,
45        })
46    }
47}
48
49/// A keyframe-defined curve that uses cubic spline interpolation, backed by a contiguous buffer.
50#[derive(Debug, Clone, Reflect)]
51pub struct CubicKeyframeCurve<T> {
52    // Note: the sample width here should be 3.
53    core: ChunkedUnevenCore<T>,
54}
55
56impl<V> Curve<V> for CubicKeyframeCurve<V>
57where
58    V: VectorSpace<Scalar = f32>,
59{
60    #[inline]
61    fn domain(&self) -> Interval {
62        self.core.domain()
63    }
64
65    #[inline]
66    fn sample_clamped(&self, t: f32) -> V {
67        match self.core.sample_interp_timed(t) {
68            // In all the cases where only one frame matters, defer to the position within it.
69            InterpolationDatum::Exact((_, v))
70            | InterpolationDatum::LeftTail((_, v))
71            | InterpolationDatum::RightTail((_, v)) => v[1],
72
73            InterpolationDatum::Between((t0, u), (t1, v), s) => {
74                cubic_spline_interpolation(u[1], u[2], v[0], v[1], s, t1 - t0)
75            }
76        }
77    }
78
79    #[inline]
80    fn sample_unchecked(&self, t: f32) -> V {
81        self.sample_clamped(t)
82    }
83}
84
85impl<T> CubicKeyframeCurve<T> {
86    /// Create a new [`CubicKeyframeCurve`] from keyframe `times` and their associated `values`.
87    /// Because 3 values are needed to perform cubic interpolation, `values` must have triple the
88    /// length of `times` — each consecutive triple `a_k, v_k, b_k` associated to time `t_k`
89    /// consists of:
90    /// - The in-tangent `a_k` for the sample at time `t_k`
91    /// - The actual value `v_k` for the sample at time `t_k`
92    /// - The out-tangent `b_k` for the sample at time `t_k`
93    ///
94    /// For example, for a curve built from two keyframes, the inputs would have the following form:
95    /// - `times`: `[t_0, t_1]`
96    /// - `values`: `[a_0, v_0, b_0, a_1, v_1, b_1]`
97    #[inline]
98    pub fn new(
99        times: impl IntoIterator<Item = f32>,
100        values: impl IntoIterator<Item = T>,
101    ) -> Result<Self, ChunkedUnevenCoreError> {
102        Ok(Self {
103            core: ChunkedUnevenCore::new(times, values, 3)?,
104        })
105    }
106}
107
108// NOTE: We can probably delete `CubicRotationCurve` once we improve the `Reflect` implementations
109// for the `Curve` API adaptors; this is basically a `CubicKeyframeCurve` composed with `map`.
110
111/// A keyframe-defined curve that uses cubic spline interpolation, special-cased for quaternions
112/// since it uses `Vec4` internally.
113#[derive(Debug, Clone, Reflect)]
114#[reflect(Clone)]
115pub struct CubicRotationCurve {
116    // Note: The sample width here should be 3.
117    core: ChunkedUnevenCore<Vec4>,
118}
119
120impl Curve<Quat> for CubicRotationCurve {
121    #[inline]
122    fn domain(&self) -> Interval {
123        self.core.domain()
124    }
125
126    #[inline]
127    fn sample_clamped(&self, t: f32) -> Quat {
128        let vec = match self.core.sample_interp_timed(t) {
129            // In all the cases where only one frame matters, defer to the position within it.
130            InterpolationDatum::Exact((_, v))
131            | InterpolationDatum::LeftTail((_, v))
132            | InterpolationDatum::RightTail((_, v)) => v[1],
133
134            InterpolationDatum::Between((t0, u), (t1, v), s) => {
135                cubic_spline_interpolation(u[1], u[2], v[0], v[1], s, t1 - t0)
136            }
137        };
138        Quat::from_vec4(vec.normalize())
139    }
140
141    #[inline]
142    fn sample_unchecked(&self, t: f32) -> Quat {
143        self.sample_clamped(t)
144    }
145}
146
147impl CubicRotationCurve {
148    /// Create a new [`CubicRotationCurve`] from keyframe `times` and their associated `values`.
149    /// Because 3 values are needed to perform cubic interpolation, `values` must have triple the
150    /// length of `times` — each consecutive triple `a_k, v_k, b_k` associated to time `t_k`
151    /// consists of:
152    /// - The in-tangent `a_k` for the sample at time `t_k`
153    /// - The actual value `v_k` for the sample at time `t_k`
154    /// - The out-tangent `b_k` for the sample at time `t_k`
155    ///
156    /// For example, for a curve built from two keyframes, the inputs would have the following form:
157    /// - `times`: `[t_0, t_1]`
158    /// - `values`: `[a_0, v_0, b_0, a_1, v_1, b_1]`
159    ///
160    /// To sample quaternions from this curve, the resulting interpolated `Vec4` output is normalized
161    /// and interpreted as a quaternion.
162    pub fn new(
163        times: impl IntoIterator<Item = f32>,
164        values: impl IntoIterator<Item = Vec4>,
165    ) -> Result<Self, ChunkedUnevenCoreError> {
166        Ok(Self {
167            core: ChunkedUnevenCore::new(times, values, 3)?,
168        })
169    }
170}
171
172/// A keyframe-defined curve that uses linear interpolation over many samples at once, backed
173/// by a contiguous buffer.
174#[derive(Debug, Clone, Reflect)]
175pub struct WideLinearKeyframeCurve<T> {
176    // Here the sample width is the number of things to simultaneously interpolate.
177    core: ChunkedUnevenCore<T>,
178}
179
180impl<T> IterableCurve<T> for WideLinearKeyframeCurve<T>
181where
182    T: VectorSpace<Scalar = f32>,
183{
184    #[inline]
185    fn domain(&self) -> Interval {
186        self.core.domain()
187    }
188
189    #[inline]
190    fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
191        match self.core.sample_interp(t) {
192            InterpolationDatum::Exact(v)
193            | InterpolationDatum::LeftTail(v)
194            | InterpolationDatum::RightTail(v) => Either::Left(v.iter().copied()),
195
196            InterpolationDatum::Between(u, v, s) => {
197                let interpolated = u.iter().zip(v.iter()).map(move |(x, y)| x.lerp(*y, s));
198                Either::Right(interpolated)
199            }
200        }
201    }
202
203    #[inline]
204    fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T> {
205        self.sample_iter_clamped(t)
206    }
207}
208
209impl<T> WideLinearKeyframeCurve<T> {
210    /// Create a new [`WideLinearKeyframeCurve`]. An error will be returned if:
211    /// - `values` has length zero.
212    /// - `times` has less than `2` unique valid entries.
213    /// - The length of `values` is not divisible by that of `times` (once sorted, filtered,
214    ///   and deduplicated).
215    #[inline]
216    pub fn new(
217        times: impl IntoIterator<Item = f32>,
218        values: impl IntoIterator<Item = T>,
219    ) -> Result<Self, WideKeyframeCurveError> {
220        Ok(Self {
221            core: ChunkedUnevenCore::new_width_inferred(times, values)?,
222        })
223    }
224}
225
226/// A keyframe-defined curve that uses stepped "interpolation" over many samples at once, backed
227/// by a contiguous buffer.
228#[derive(Debug, Clone, Reflect)]
229pub struct WideSteppedKeyframeCurve<T> {
230    // Here the sample width is the number of things to simultaneously interpolate.
231    core: ChunkedUnevenCore<T>,
232}
233
234impl<T> IterableCurve<T> for WideSteppedKeyframeCurve<T>
235where
236    T: Clone,
237{
238    #[inline]
239    fn domain(&self) -> Interval {
240        self.core.domain()
241    }
242
243    #[inline]
244    fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
245        match self.core.sample_interp(t) {
246            InterpolationDatum::Exact(v)
247            | InterpolationDatum::LeftTail(v)
248            | InterpolationDatum::RightTail(v) => Either::Left(v.iter().cloned()),
249
250            InterpolationDatum::Between(u, v, s) => {
251                let interpolated =
252                    u.iter()
253                        .zip(v.iter())
254                        .map(move |(x, y)| if s >= 1.0 { y.clone() } else { x.clone() });
255                Either::Right(interpolated)
256            }
257        }
258    }
259
260    #[inline]
261    fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T> {
262        self.sample_iter_clamped(t)
263    }
264}
265
266impl<T> WideSteppedKeyframeCurve<T> {
267    /// Create a new [`WideSteppedKeyframeCurve`]. An error will be returned if:
268    /// - `values` has length zero.
269    /// - `times` has less than `2` unique valid entries.
270    /// - The length of `values` is not divisible by that of `times` (once sorted, filtered,
271    ///   and deduplicated).
272    #[inline]
273    pub fn new(
274        times: impl IntoIterator<Item = f32>,
275        values: impl IntoIterator<Item = T>,
276    ) -> Result<Self, WideKeyframeCurveError> {
277        Ok(Self {
278            core: ChunkedUnevenCore::new_width_inferred(times, values)?,
279        })
280    }
281}
282
283/// A keyframe-defined curve that uses cubic interpolation over many samples at once, backed by a
284/// contiguous buffer.
285#[derive(Debug, Clone, Reflect)]
286pub struct WideCubicKeyframeCurve<T> {
287    core: ChunkedUnevenCore<T>,
288}
289
290impl<T> IterableCurve<T> for WideCubicKeyframeCurve<T>
291where
292    T: VectorSpace<Scalar = f32>,
293{
294    #[inline]
295    fn domain(&self) -> Interval {
296        self.core.domain()
297    }
298
299    fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
300        match self.core.sample_interp_timed(t) {
301            InterpolationDatum::Exact((_, v))
302            | InterpolationDatum::LeftTail((_, v))
303            | InterpolationDatum::RightTail((_, v)) => {
304                // Pick out the part of this that actually represents the position (instead of tangents),
305                // which is the middle third.
306                let width = self.core.width();
307                Either::Left(v[width..(width * 2)].iter().copied())
308            }
309
310            InterpolationDatum::Between((t0, u), (t1, v), s) => Either::Right(
311                cubic_spline_interpolate_slices(self.core.width() / 3, u, v, s, t1 - t0),
312            ),
313        }
314    }
315
316    #[inline]
317    fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T> {
318        self.sample_iter_clamped(t)
319    }
320}
321
322/// An error indicating that a multisampling keyframe curve could not be constructed.
323#[derive(Debug, Error)]
324#[error("unable to construct a curve using this data")]
325pub enum WideKeyframeCurveError {
326    /// The number of given values was not divisible by a multiple of the number of keyframes.
327    #[error("number of values ({values_given}) is not divisible by {divisor}")]
328    LengthMismatch {
329        /// The number of values given.
330        values_given: usize,
331        /// The number that `values_given` was supposed to be divisible by.
332        divisor: usize,
333    },
334    /// An error was returned by the internal core constructor.
335    #[error(transparent)]
336    CoreError(#[from] ChunkedUnevenCoreError),
337}
338
339impl<T> WideCubicKeyframeCurve<T> {
340    /// Create a new [`WideCubicKeyframeCurve`].
341    ///
342    /// An error will be returned if:
343    /// - `values` has length zero.
344    /// - `times` has less than `2` unique valid entries.
345    /// - The length of `values` is not divisible by three times that of `times` (once sorted,
346    ///   filtered, and deduplicated).
347    #[inline]
348    pub fn new(
349        times: impl IntoIterator<Item = f32>,
350        values: impl IntoIterator<Item = T>,
351    ) -> Result<Self, WideKeyframeCurveError> {
352        let times: Vec<f32> = times.into_iter().collect();
353        let values: Vec<T> = values.into_iter().collect();
354        let divisor = times.len() * 3;
355
356        if !values.len().is_multiple_of(divisor) {
357            return Err(WideKeyframeCurveError::LengthMismatch {
358                values_given: values.len(),
359                divisor,
360            });
361        }
362
363        Ok(Self {
364            core: ChunkedUnevenCore::new_width_inferred(times, values)?,
365        })
366    }
367}
368
369/// A curve specifying the [`MorphWeights`] for a mesh in animation. The variants are broken
370/// down by interpolation mode (with the exception of `Constant`, which never interpolates).
371///
372/// This type is, itself, a `Curve<Vec<f32>>`; however, in order to avoid allocation, it is
373/// recommended to use its implementation of the [`IterableCurve`] trait, which allows iterating
374/// directly over information derived from the curve without allocating.
375///
376/// [`MorphWeights`]: bevy_mesh::morph::MorphWeights
377#[derive(Debug, Clone, Reflect)]
378#[reflect(Clone)]
379pub enum WeightsCurve {
380    /// A curve which takes a constant value over its domain. Notably, this is how animations with
381    /// only a single keyframe are interpreted.
382    Constant(ConstantCurve<Vec<f32>>),
383
384    /// A curve which interpolates weights linearly between keyframes.
385    Linear(WideLinearKeyframeCurve<f32>),
386
387    /// A curve which interpolates weights between keyframes in steps.
388    Step(WideSteppedKeyframeCurve<f32>),
389
390    /// A curve which interpolates between keyframes by using auxiliary tangent data to join
391    /// adjacent keyframes with a cubic Hermite spline, which is then sampled.
392    CubicSpline(WideCubicKeyframeCurve<f32>),
393}
394
395//---------//
396// HELPERS //
397//---------//
398
399/// Helper function for cubic spline interpolation.
400fn cubic_spline_interpolation<T>(
401    value_start: T,
402    tangent_out_start: T,
403    tangent_in_end: T,
404    value_end: T,
405    lerp: f32,
406    step_duration: f32,
407) -> T
408where
409    T: VectorSpace<Scalar = f32>,
410{
411    let coeffs = (vec4(2.0, 1.0, -2.0, 1.0) * lerp + vec4(-3.0, -2.0, 3.0, -1.0)) * lerp;
412    value_start * (coeffs.x * lerp + 1.0)
413        + tangent_out_start * step_duration * lerp * (coeffs.y + 1.0)
414        + value_end * lerp * coeffs.z
415        + tangent_in_end * step_duration * lerp * coeffs.w
416}
417
418fn cubic_spline_interpolate_slices<'a, T: VectorSpace<Scalar = f32>>(
419    width: usize,
420    first: &'a [T],
421    second: &'a [T],
422    s: f32,
423    step_between: f32,
424) -> impl Iterator<Item = T> + 'a {
425    (0..width).map(move |idx| {
426        cubic_spline_interpolation(
427            first[idx + width],
428            first[idx + (width * 2)],
429            second[idx + width],
430            second[idx],
431            s,
432            step_between,
433        )
434    })
435}