//! Concrete curve structures used to load glTF curves into the animation system. use bevy_math::{ curve::{cores::*, iterable::IterableCurve, *}, vec4, Quat, Vec4, VectorSpace, }; use bevy_reflect::Reflect; use either::Either; use thiserror::Error; /// A keyframe-defined curve that "interpolates" by stepping at `t = 1.0` to the next keyframe. #[derive(Debug, Clone, Reflect)] pub struct SteppedKeyframeCurve { core: UnevenCore, } impl Curve for SteppedKeyframeCurve where T: Clone, { #[inline] fn domain(&self) -> Interval { self.core.domain() } #[inline] fn sample_clamped(&self, t: f32) -> T { self.core .sample_with(t, |x, y, t| if t >= 1.0 { y.clone() } else { x.clone() }) } #[inline] fn sample_unchecked(&self, t: f32) -> T { self.sample_clamped(t) } } impl SteppedKeyframeCurve { /// Create a new [`SteppedKeyframeCurve`]. If the curve could not be constructed from the /// given data, an error is returned. #[inline] pub fn new(timed_samples: impl IntoIterator) -> Result { Ok(Self { core: UnevenCore::new(timed_samples)?, }) } } /// A keyframe-defined curve that uses cubic spline interpolation, backed by a contiguous buffer. #[derive(Debug, Clone, Reflect)] pub struct CubicKeyframeCurve { // Note: the sample width here should be 3. core: ChunkedUnevenCore, } impl Curve for CubicKeyframeCurve where V: VectorSpace, { #[inline] fn domain(&self) -> Interval { self.core.domain() } #[inline] fn sample_clamped(&self, t: f32) -> V { match self.core.sample_interp_timed(t) { // In all the cases where only one frame matters, defer to the position within it. InterpolationDatum::Exact((_, v)) | InterpolationDatum::LeftTail((_, v)) | InterpolationDatum::RightTail((_, v)) => v[1], InterpolationDatum::Between((t0, u), (t1, v), s) => { cubic_spline_interpolation(u[1], u[2], v[0], v[1], s, t1 - t0) } } } #[inline] fn sample_unchecked(&self, t: f32) -> V { self.sample_clamped(t) } } impl CubicKeyframeCurve { /// Create a new [`CubicKeyframeCurve`] from keyframe `times` and their associated `values`. /// Because 3 values are needed to perform cubic interpolation, `values` must have triple the /// length of `times` — each consecutive triple `a_k, v_k, b_k` associated to time `t_k` /// consists of: /// - The in-tangent `a_k` for the sample at time `t_k` /// - The actual value `v_k` for the sample at time `t_k` /// - The out-tangent `b_k` for the sample at time `t_k` /// /// For example, for a curve built from two keyframes, the inputs would have the following form: /// - `times`: `[t_0, t_1]` /// - `values`: `[a_0, v_0, b_0, a_1, v_1, b_1]` #[inline] pub fn new( times: impl IntoIterator, values: impl IntoIterator, ) -> Result { Ok(Self { core: ChunkedUnevenCore::new(times, values, 3)?, }) } } // NOTE: We can probably delete `CubicRotationCurve` once we improve the `Reflect` implementations // for the `Curve` API adaptors; this is basically a `CubicKeyframeCurve` composed with `map`. /// A keyframe-defined curve that uses cubic spline interpolation, special-cased for quaternions /// since it uses `Vec4` internally. #[derive(Debug, Clone, Reflect)] #[reflect(Clone)] pub struct CubicRotationCurve { // Note: The sample width here should be 3. core: ChunkedUnevenCore, } impl Curve for CubicRotationCurve { #[inline] fn domain(&self) -> Interval { self.core.domain() } #[inline] fn sample_clamped(&self, t: f32) -> Quat { let vec = match self.core.sample_interp_timed(t) { // In all the cases where only one frame matters, defer to the position within it. InterpolationDatum::Exact((_, v)) | InterpolationDatum::LeftTail((_, v)) | InterpolationDatum::RightTail((_, v)) => v[1], InterpolationDatum::Between((t0, u), (t1, v), s) => { cubic_spline_interpolation(u[1], u[2], v[0], v[1], s, t1 - t0) } }; Quat::from_vec4(vec.normalize()) } #[inline] fn sample_unchecked(&self, t: f32) -> Quat { self.sample_clamped(t) } } impl CubicRotationCurve { /// Create a new [`CubicRotationCurve`] from keyframe `times` and their associated `values`. /// Because 3 values are needed to perform cubic interpolation, `values` must have triple the /// length of `times` — each consecutive triple `a_k, v_k, b_k` associated to time `t_k` /// consists of: /// - The in-tangent `a_k` for the sample at time `t_k` /// - The actual value `v_k` for the sample at time `t_k` /// - The out-tangent `b_k` for the sample at time `t_k` /// /// For example, for a curve built from two keyframes, the inputs would have the following form: /// - `times`: `[t_0, t_1]` /// - `values`: `[a_0, v_0, b_0, a_1, v_1, b_1]` /// /// To sample quaternions from this curve, the resulting interpolated `Vec4` output is normalized /// and interpreted as a quaternion. pub fn new( times: impl IntoIterator, values: impl IntoIterator, ) -> Result { Ok(Self { core: ChunkedUnevenCore::new(times, values, 3)?, }) } } /// A keyframe-defined curve that uses linear interpolation over many samples at once, backed /// by a contiguous buffer. #[derive(Debug, Clone, Reflect)] pub struct WideLinearKeyframeCurve { // Here the sample width is the number of things to simultaneously interpolate. core: ChunkedUnevenCore, } impl IterableCurve for WideLinearKeyframeCurve where T: VectorSpace, { #[inline] fn domain(&self) -> Interval { self.core.domain() } #[inline] fn sample_iter_clamped(&self, t: f32) -> impl Iterator { match self.core.sample_interp(t) { InterpolationDatum::Exact(v) | InterpolationDatum::LeftTail(v) | InterpolationDatum::RightTail(v) => Either::Left(v.iter().copied()), InterpolationDatum::Between(u, v, s) => { let interpolated = u.iter().zip(v.iter()).map(move |(x, y)| x.lerp(*y, s)); Either::Right(interpolated) } } } #[inline] fn sample_iter_unchecked(&self, t: f32) -> impl Iterator { self.sample_iter_clamped(t) } } impl WideLinearKeyframeCurve { /// Create a new [`WideLinearKeyframeCurve`]. An error will be returned if: /// - `values` has length zero. /// - `times` has less than `2` unique valid entries. /// - The length of `values` is not divisible by that of `times` (once sorted, filtered, /// and deduplicated). #[inline] pub fn new( times: impl IntoIterator, values: impl IntoIterator, ) -> Result { Ok(Self { core: ChunkedUnevenCore::new_width_inferred(times, values)?, }) } } /// A keyframe-defined curve that uses stepped "interpolation" over many samples at once, backed /// by a contiguous buffer. #[derive(Debug, Clone, Reflect)] pub struct WideSteppedKeyframeCurve { // Here the sample width is the number of things to simultaneously interpolate. core: ChunkedUnevenCore, } impl IterableCurve for WideSteppedKeyframeCurve where T: Clone, { #[inline] fn domain(&self) -> Interval { self.core.domain() } #[inline] fn sample_iter_clamped(&self, t: f32) -> impl Iterator { match self.core.sample_interp(t) { InterpolationDatum::Exact(v) | InterpolationDatum::LeftTail(v) | InterpolationDatum::RightTail(v) => Either::Left(v.iter().cloned()), InterpolationDatum::Between(u, v, s) => { let interpolated = u.iter() .zip(v.iter()) .map(move |(x, y)| if s >= 1.0 { y.clone() } else { x.clone() }); Either::Right(interpolated) } } } #[inline] fn sample_iter_unchecked(&self, t: f32) -> impl Iterator { self.sample_iter_clamped(t) } } impl WideSteppedKeyframeCurve { /// Create a new [`WideSteppedKeyframeCurve`]. An error will be returned if: /// - `values` has length zero. /// - `times` has less than `2` unique valid entries. /// - The length of `values` is not divisible by that of `times` (once sorted, filtered, /// and deduplicated). #[inline] pub fn new( times: impl IntoIterator, values: impl IntoIterator, ) -> Result { Ok(Self { core: ChunkedUnevenCore::new_width_inferred(times, values)?, }) } } /// A keyframe-defined curve that uses cubic interpolation over many samples at once, backed by a /// contiguous buffer. #[derive(Debug, Clone, Reflect)] pub struct WideCubicKeyframeCurve { core: ChunkedUnevenCore, } impl IterableCurve for WideCubicKeyframeCurve where T: VectorSpace, { #[inline] fn domain(&self) -> Interval { self.core.domain() } fn sample_iter_clamped(&self, t: f32) -> impl Iterator { match self.core.sample_interp_timed(t) { InterpolationDatum::Exact((_, v)) | InterpolationDatum::LeftTail((_, v)) | InterpolationDatum::RightTail((_, v)) => { // Pick out the part of this that actually represents the position (instead of tangents), // which is the middle third. let width = self.core.width(); Either::Left(v[width..(width * 2)].iter().copied()) } InterpolationDatum::Between((t0, u), (t1, v), s) => Either::Right( cubic_spline_interpolate_slices(self.core.width() / 3, u, v, s, t1 - t0), ), } } #[inline] fn sample_iter_unchecked(&self, t: f32) -> impl Iterator { self.sample_iter_clamped(t) } } /// An error indicating that a multisampling keyframe curve could not be constructed. #[derive(Debug, Error)] #[error("unable to construct a curve using this data")] pub enum WideKeyframeCurveError { /// The number of given values was not divisible by a multiple of the number of keyframes. #[error("number of values ({values_given}) is not divisible by {divisor}")] LengthMismatch { /// The number of values given. values_given: usize, /// The number that `values_given` was supposed to be divisible by. divisor: usize, }, /// An error was returned by the internal core constructor. #[error(transparent)] CoreError(#[from] ChunkedUnevenCoreError), } impl WideCubicKeyframeCurve { /// Create a new [`WideCubicKeyframeCurve`]. /// /// An error will be returned if: /// - `values` has length zero. /// - `times` has less than `2` unique valid entries. /// - The length of `values` is not divisible by three times that of `times` (once sorted, /// filtered, and deduplicated). #[inline] pub fn new( times: impl IntoIterator, values: impl IntoIterator, ) -> Result { let times: Vec = times.into_iter().collect(); let values: Vec = values.into_iter().collect(); let divisor = times.len() * 3; if values.len() % divisor != 0 { return Err(WideKeyframeCurveError::LengthMismatch { values_given: values.len(), divisor, }); } Ok(Self { core: ChunkedUnevenCore::new_width_inferred(times, values)?, }) } } /// A curve specifying the [`MorphWeights`] for a mesh in animation. The variants are broken /// down by interpolation mode (with the exception of `Constant`, which never interpolates). /// /// This type is, itself, a `Curve>`; however, in order to avoid allocation, it is /// recommended to use its implementation of the [`IterableCurve`] trait, which allows iterating /// directly over information derived from the curve without allocating. /// /// [`MorphWeights`]: bevy_mesh::morph::MorphWeights #[derive(Debug, Clone, Reflect)] #[reflect(Clone)] pub enum WeightsCurve { /// A curve which takes a constant value over its domain. Notably, this is how animations with /// only a single keyframe are interpreted. Constant(ConstantCurve>), /// A curve which interpolates weights linearly between keyframes. Linear(WideLinearKeyframeCurve), /// A curve which interpolates weights between keyframes in steps. Step(WideSteppedKeyframeCurve), /// A curve which interpolates between keyframes by using auxiliary tangent data to join /// adjacent keyframes with a cubic Hermite spline, which is then sampled. CubicSpline(WideCubicKeyframeCurve), } //---------// // HELPERS // //---------// /// Helper function for cubic spline interpolation. fn cubic_spline_interpolation( value_start: T, tangent_out_start: T, tangent_in_end: T, value_end: T, lerp: f32, step_duration: f32, ) -> T where T: VectorSpace, { let coeffs = (vec4(2.0, 1.0, -2.0, 1.0) * lerp + vec4(-3.0, -2.0, 3.0, -1.0)) * lerp; value_start * (coeffs.x * lerp + 1.0) + tangent_out_start * step_duration * lerp * (coeffs.y + 1.0) + value_end * lerp * coeffs.z + tangent_in_end * step_duration * lerp * coeffs.w } fn cubic_spline_interpolate_slices<'a, T: VectorSpace>( width: usize, first: &'a [T], second: &'a [T], s: f32, step_between: f32, ) -> impl Iterator + 'a { (0..width).map(move |idx| { cubic_spline_interpolation( first[idx + width], first[idx + (width * 2)], second[idx + width], second[idx], s, step_between, ) }) }