
# Objective - Less code - Better iterator (implements `size_hint` for example) ## Solution - Use `either` - This change is free because `bevy_animation` depends on `bevy_asset`, which already depends on `either` ## Testing CI
433 lines
14 KiB
Rust
433 lines
14 KiB
Rust
//! 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 derive_more::derive::{Display, Error, From};
|
|
use either::Either;
|
|
|
|
/// A keyframe-defined curve that "interpolates" by stepping at `t = 1.0` to the next keyframe.
|
|
#[derive(Debug, Clone, Reflect)]
|
|
pub struct SteppedKeyframeCurve<T> {
|
|
core: UnevenCore<T>,
|
|
}
|
|
|
|
impl<T> Curve<T> for SteppedKeyframeCurve<T>
|
|
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<T> SteppedKeyframeCurve<T> {
|
|
/// 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<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
|
|
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<T> {
|
|
// Note: the sample width here should be 3.
|
|
core: ChunkedUnevenCore<T>,
|
|
}
|
|
|
|
impl<V> Curve<V> for CubicKeyframeCurve<V>
|
|
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<T> CubicKeyframeCurve<T> {
|
|
/// 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<Item = f32>,
|
|
values: impl IntoIterator<Item = T>,
|
|
) -> Result<Self, ChunkedUnevenCoreError> {
|
|
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)]
|
|
pub struct CubicRotationCurve {
|
|
// Note: The sample width here should be 3.
|
|
core: ChunkedUnevenCore<Vec4>,
|
|
}
|
|
|
|
impl Curve<Quat> 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<Item = f32>,
|
|
values: impl IntoIterator<Item = Vec4>,
|
|
) -> Result<Self, ChunkedUnevenCoreError> {
|
|
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<T> {
|
|
// Here the sample width is the number of things to simultaneously interpolate.
|
|
core: ChunkedUnevenCore<T>,
|
|
}
|
|
|
|
impl<T> IterableCurve<T> for WideLinearKeyframeCurve<T>
|
|
where
|
|
T: VectorSpace,
|
|
{
|
|
#[inline]
|
|
fn domain(&self) -> Interval {
|
|
self.core.domain()
|
|
}
|
|
|
|
#[inline]
|
|
fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
|
|
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<Item = T> {
|
|
self.sample_iter_clamped(t)
|
|
}
|
|
}
|
|
|
|
impl<T> WideLinearKeyframeCurve<T> {
|
|
/// 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<Item = f32>,
|
|
values: impl IntoIterator<Item = T>,
|
|
) -> Result<Self, WideKeyframeCurveError> {
|
|
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<T> {
|
|
// Here the sample width is the number of things to simultaneously interpolate.
|
|
core: ChunkedUnevenCore<T>,
|
|
}
|
|
|
|
impl<T> IterableCurve<T> for WideSteppedKeyframeCurve<T>
|
|
where
|
|
T: Clone,
|
|
{
|
|
#[inline]
|
|
fn domain(&self) -> Interval {
|
|
self.core.domain()
|
|
}
|
|
|
|
#[inline]
|
|
fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
|
|
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<Item = T> {
|
|
self.sample_iter_clamped(t)
|
|
}
|
|
}
|
|
|
|
impl<T> WideSteppedKeyframeCurve<T> {
|
|
/// 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<Item = f32>,
|
|
values: impl IntoIterator<Item = T>,
|
|
) -> Result<Self, WideKeyframeCurveError> {
|
|
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<T> {
|
|
core: ChunkedUnevenCore<T>,
|
|
}
|
|
|
|
impl<T> IterableCurve<T> for WideCubicKeyframeCurve<T>
|
|
where
|
|
T: VectorSpace,
|
|
{
|
|
#[inline]
|
|
fn domain(&self) -> Interval {
|
|
self.core.domain()
|
|
}
|
|
|
|
fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
|
|
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<Item = T> {
|
|
self.sample_iter_clamped(t)
|
|
}
|
|
}
|
|
|
|
/// An error indicating that a multisampling keyframe curve could not be constructed.
|
|
#[derive(Debug, Error, Display, From)]
|
|
#[display("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.
|
|
#[display("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.
|
|
CoreError(ChunkedUnevenCoreError),
|
|
}
|
|
|
|
impl<T> WideCubicKeyframeCurve<T> {
|
|
/// 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<Item = f32>,
|
|
values: impl IntoIterator<Item = T>,
|
|
) -> Result<Self, WideKeyframeCurveError> {
|
|
let times: Vec<f32> = times.into_iter().collect();
|
|
let values: Vec<T> = 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<Vec<f32>>`; 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_render::prelude::MorphWeights
|
|
#[derive(Debug, Clone, Reflect)]
|
|
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<Vec<f32>>),
|
|
|
|
/// A curve which interpolates weights linearly between keyframes.
|
|
Linear(WideLinearKeyframeCurve<f32>),
|
|
|
|
/// A curve which interpolates weights between keyframes in steps.
|
|
Step(WideSteppedKeyframeCurve<f32>),
|
|
|
|
/// 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<f32>),
|
|
}
|
|
|
|
//---------//
|
|
// HELPERS //
|
|
//---------//
|
|
|
|
/// Helper function for cubic spline interpolation.
|
|
fn cubic_spline_interpolation<T>(
|
|
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<Item = T> + 'a {
|
|
(0..width).map(move |idx| {
|
|
cubic_spline_interpolation(
|
|
first[idx + width],
|
|
first[idx + (width * 2)],
|
|
second[idx + width],
|
|
second[idx],
|
|
s,
|
|
step_between,
|
|
)
|
|
})
|
|
}
|