bevy/crates/bevy_animation/src/gltf_curves.rs
Zachary Harrold a6adced9ed
Deny derive_more error feature and replace it with thiserror (#16684)
# Objective

- Remove `derive_more`'s error derivation and replace it with
`thiserror`

## Solution

- Added `derive_more`'s `error` feature to `deny.toml` to prevent it
sneaking back in.
- Reverted to `thiserror` error derivation

## Notes

Merge conflicts were too numerous to revert the individual changes, so
this reversion was done manually. Please scrutinise carefully during
review.
2024-12-06 17:03:55 +00:00

434 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 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<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)]
#[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<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,
)
})
}