Path: blob/main/crates/bevy_animation/src/gltf_curves.rs
6595 views
//! Concrete curve structures used to load glTF curves into the animation system.12use bevy_math::{3curve::{cores::*, iterable::IterableCurve, *},4vec4, Quat, Vec4, VectorSpace,5};6use bevy_reflect::Reflect;7use either::Either;8use thiserror::Error;910/// A keyframe-defined curve that "interpolates" by stepping at `t = 1.0` to the next keyframe.11#[derive(Debug, Clone, Reflect)]12pub struct SteppedKeyframeCurve<T> {13core: UnevenCore<T>,14}1516impl<T> Curve<T> for SteppedKeyframeCurve<T>17where18T: Clone,19{20#[inline]21fn domain(&self) -> Interval {22self.core.domain()23}2425#[inline]26fn sample_clamped(&self, t: f32) -> T {27self.core28.sample_with(t, |x, y, t| if t >= 1.0 { y.clone() } else { x.clone() })29}3031#[inline]32fn sample_unchecked(&self, t: f32) -> T {33self.sample_clamped(t)34}35}3637impl<T> SteppedKeyframeCurve<T> {38/// Create a new [`SteppedKeyframeCurve`]. If the curve could not be constructed from the39/// given data, an error is returned.40#[inline]41pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {42Ok(Self {43core: UnevenCore::new(timed_samples)?,44})45}46}4748/// A keyframe-defined curve that uses cubic spline interpolation, backed by a contiguous buffer.49#[derive(Debug, Clone, Reflect)]50pub struct CubicKeyframeCurve<T> {51// Note: the sample width here should be 3.52core: ChunkedUnevenCore<T>,53}5455impl<V> Curve<V> for CubicKeyframeCurve<V>56where57V: VectorSpace<Scalar = f32>,58{59#[inline]60fn domain(&self) -> Interval {61self.core.domain()62}6364#[inline]65fn sample_clamped(&self, t: f32) -> V {66match self.core.sample_interp_timed(t) {67// In all the cases where only one frame matters, defer to the position within it.68InterpolationDatum::Exact((_, v))69| InterpolationDatum::LeftTail((_, v))70| InterpolationDatum::RightTail((_, v)) => v[1],7172InterpolationDatum::Between((t0, u), (t1, v), s) => {73cubic_spline_interpolation(u[1], u[2], v[0], v[1], s, t1 - t0)74}75}76}7778#[inline]79fn sample_unchecked(&self, t: f32) -> V {80self.sample_clamped(t)81}82}8384impl<T> CubicKeyframeCurve<T> {85/// Create a new [`CubicKeyframeCurve`] from keyframe `times` and their associated `values`.86/// Because 3 values are needed to perform cubic interpolation, `values` must have triple the87/// length of `times` — each consecutive triple `a_k, v_k, b_k` associated to time `t_k`88/// consists of:89/// - The in-tangent `a_k` for the sample at time `t_k`90/// - The actual value `v_k` for the sample at time `t_k`91/// - The out-tangent `b_k` for the sample at time `t_k`92///93/// For example, for a curve built from two keyframes, the inputs would have the following form:94/// - `times`: `[t_0, t_1]`95/// - `values`: `[a_0, v_0, b_0, a_1, v_1, b_1]`96#[inline]97pub fn new(98times: impl IntoIterator<Item = f32>,99values: impl IntoIterator<Item = T>,100) -> Result<Self, ChunkedUnevenCoreError> {101Ok(Self {102core: ChunkedUnevenCore::new(times, values, 3)?,103})104}105}106107// NOTE: We can probably delete `CubicRotationCurve` once we improve the `Reflect` implementations108// for the `Curve` API adaptors; this is basically a `CubicKeyframeCurve` composed with `map`.109110/// A keyframe-defined curve that uses cubic spline interpolation, special-cased for quaternions111/// since it uses `Vec4` internally.112#[derive(Debug, Clone, Reflect)]113#[reflect(Clone)]114pub struct CubicRotationCurve {115// Note: The sample width here should be 3.116core: ChunkedUnevenCore<Vec4>,117}118119impl Curve<Quat> for CubicRotationCurve {120#[inline]121fn domain(&self) -> Interval {122self.core.domain()123}124125#[inline]126fn sample_clamped(&self, t: f32) -> Quat {127let vec = match self.core.sample_interp_timed(t) {128// In all the cases where only one frame matters, defer to the position within it.129InterpolationDatum::Exact((_, v))130| InterpolationDatum::LeftTail((_, v))131| InterpolationDatum::RightTail((_, v)) => v[1],132133InterpolationDatum::Between((t0, u), (t1, v), s) => {134cubic_spline_interpolation(u[1], u[2], v[0], v[1], s, t1 - t0)135}136};137Quat::from_vec4(vec.normalize())138}139140#[inline]141fn sample_unchecked(&self, t: f32) -> Quat {142self.sample_clamped(t)143}144}145146impl CubicRotationCurve {147/// Create a new [`CubicRotationCurve`] from keyframe `times` and their associated `values`.148/// Because 3 values are needed to perform cubic interpolation, `values` must have triple the149/// length of `times` — each consecutive triple `a_k, v_k, b_k` associated to time `t_k`150/// consists of:151/// - The in-tangent `a_k` for the sample at time `t_k`152/// - The actual value `v_k` for the sample at time `t_k`153/// - The out-tangent `b_k` for the sample at time `t_k`154///155/// For example, for a curve built from two keyframes, the inputs would have the following form:156/// - `times`: `[t_0, t_1]`157/// - `values`: `[a_0, v_0, b_0, a_1, v_1, b_1]`158///159/// To sample quaternions from this curve, the resulting interpolated `Vec4` output is normalized160/// and interpreted as a quaternion.161pub fn new(162times: impl IntoIterator<Item = f32>,163values: impl IntoIterator<Item = Vec4>,164) -> Result<Self, ChunkedUnevenCoreError> {165Ok(Self {166core: ChunkedUnevenCore::new(times, values, 3)?,167})168}169}170171/// A keyframe-defined curve that uses linear interpolation over many samples at once, backed172/// by a contiguous buffer.173#[derive(Debug, Clone, Reflect)]174pub struct WideLinearKeyframeCurve<T> {175// Here the sample width is the number of things to simultaneously interpolate.176core: ChunkedUnevenCore<T>,177}178179impl<T> IterableCurve<T> for WideLinearKeyframeCurve<T>180where181T: VectorSpace<Scalar = f32>,182{183#[inline]184fn domain(&self) -> Interval {185self.core.domain()186}187188#[inline]189fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {190match self.core.sample_interp(t) {191InterpolationDatum::Exact(v)192| InterpolationDatum::LeftTail(v)193| InterpolationDatum::RightTail(v) => Either::Left(v.iter().copied()),194195InterpolationDatum::Between(u, v, s) => {196let interpolated = u.iter().zip(v.iter()).map(move |(x, y)| x.lerp(*y, s));197Either::Right(interpolated)198}199}200}201202#[inline]203fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T> {204self.sample_iter_clamped(t)205}206}207208impl<T> WideLinearKeyframeCurve<T> {209/// Create a new [`WideLinearKeyframeCurve`]. An error will be returned if:210/// - `values` has length zero.211/// - `times` has less than `2` unique valid entries.212/// - The length of `values` is not divisible by that of `times` (once sorted, filtered,213/// and deduplicated).214#[inline]215pub fn new(216times: impl IntoIterator<Item = f32>,217values: impl IntoIterator<Item = T>,218) -> Result<Self, WideKeyframeCurveError> {219Ok(Self {220core: ChunkedUnevenCore::new_width_inferred(times, values)?,221})222}223}224225/// A keyframe-defined curve that uses stepped "interpolation" over many samples at once, backed226/// by a contiguous buffer.227#[derive(Debug, Clone, Reflect)]228pub struct WideSteppedKeyframeCurve<T> {229// Here the sample width is the number of things to simultaneously interpolate.230core: ChunkedUnevenCore<T>,231}232233impl<T> IterableCurve<T> for WideSteppedKeyframeCurve<T>234where235T: Clone,236{237#[inline]238fn domain(&self) -> Interval {239self.core.domain()240}241242#[inline]243fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {244match self.core.sample_interp(t) {245InterpolationDatum::Exact(v)246| InterpolationDatum::LeftTail(v)247| InterpolationDatum::RightTail(v) => Either::Left(v.iter().cloned()),248249InterpolationDatum::Between(u, v, s) => {250let interpolated =251u.iter()252.zip(v.iter())253.map(move |(x, y)| if s >= 1.0 { y.clone() } else { x.clone() });254Either::Right(interpolated)255}256}257}258259#[inline]260fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T> {261self.sample_iter_clamped(t)262}263}264265impl<T> WideSteppedKeyframeCurve<T> {266/// Create a new [`WideSteppedKeyframeCurve`]. An error will be returned if:267/// - `values` has length zero.268/// - `times` has less than `2` unique valid entries.269/// - The length of `values` is not divisible by that of `times` (once sorted, filtered,270/// and deduplicated).271#[inline]272pub fn new(273times: impl IntoIterator<Item = f32>,274values: impl IntoIterator<Item = T>,275) -> Result<Self, WideKeyframeCurveError> {276Ok(Self {277core: ChunkedUnevenCore::new_width_inferred(times, values)?,278})279}280}281282/// A keyframe-defined curve that uses cubic interpolation over many samples at once, backed by a283/// contiguous buffer.284#[derive(Debug, Clone, Reflect)]285pub struct WideCubicKeyframeCurve<T> {286core: ChunkedUnevenCore<T>,287}288289impl<T> IterableCurve<T> for WideCubicKeyframeCurve<T>290where291T: VectorSpace<Scalar = f32>,292{293#[inline]294fn domain(&self) -> Interval {295self.core.domain()296}297298fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {299match self.core.sample_interp_timed(t) {300InterpolationDatum::Exact((_, v))301| InterpolationDatum::LeftTail((_, v))302| InterpolationDatum::RightTail((_, v)) => {303// Pick out the part of this that actually represents the position (instead of tangents),304// which is the middle third.305let width = self.core.width();306Either::Left(v[width..(width * 2)].iter().copied())307}308309InterpolationDatum::Between((t0, u), (t1, v), s) => Either::Right(310cubic_spline_interpolate_slices(self.core.width() / 3, u, v, s, t1 - t0),311),312}313}314315#[inline]316fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T> {317self.sample_iter_clamped(t)318}319}320321/// An error indicating that a multisampling keyframe curve could not be constructed.322#[derive(Debug, Error)]323#[error("unable to construct a curve using this data")]324pub enum WideKeyframeCurveError {325/// The number of given values was not divisible by a multiple of the number of keyframes.326#[error("number of values ({values_given}) is not divisible by {divisor}")]327LengthMismatch {328/// The number of values given.329values_given: usize,330/// The number that `values_given` was supposed to be divisible by.331divisor: usize,332},333/// An error was returned by the internal core constructor.334#[error(transparent)]335CoreError(#[from] ChunkedUnevenCoreError),336}337338impl<T> WideCubicKeyframeCurve<T> {339/// Create a new [`WideCubicKeyframeCurve`].340///341/// An error will be returned if:342/// - `values` has length zero.343/// - `times` has less than `2` unique valid entries.344/// - The length of `values` is not divisible by three times that of `times` (once sorted,345/// filtered, and deduplicated).346#[inline]347pub fn new(348times: impl IntoIterator<Item = f32>,349values: impl IntoIterator<Item = T>,350) -> Result<Self, WideKeyframeCurveError> {351let times: Vec<f32> = times.into_iter().collect();352let values: Vec<T> = values.into_iter().collect();353let divisor = times.len() * 3;354355if !values.len().is_multiple_of(divisor) {356return Err(WideKeyframeCurveError::LengthMismatch {357values_given: values.len(),358divisor,359});360}361362Ok(Self {363core: ChunkedUnevenCore::new_width_inferred(times, values)?,364})365}366}367368/// A curve specifying the [`MorphWeights`] for a mesh in animation. The variants are broken369/// down by interpolation mode (with the exception of `Constant`, which never interpolates).370///371/// This type is, itself, a `Curve<Vec<f32>>`; however, in order to avoid allocation, it is372/// recommended to use its implementation of the [`IterableCurve`] trait, which allows iterating373/// directly over information derived from the curve without allocating.374///375/// [`MorphWeights`]: bevy_mesh::morph::MorphWeights376#[derive(Debug, Clone, Reflect)]377#[reflect(Clone)]378pub enum WeightsCurve {379/// A curve which takes a constant value over its domain. Notably, this is how animations with380/// only a single keyframe are interpreted.381Constant(ConstantCurve<Vec<f32>>),382383/// A curve which interpolates weights linearly between keyframes.384Linear(WideLinearKeyframeCurve<f32>),385386/// A curve which interpolates weights between keyframes in steps.387Step(WideSteppedKeyframeCurve<f32>),388389/// A curve which interpolates between keyframes by using auxiliary tangent data to join390/// adjacent keyframes with a cubic Hermite spline, which is then sampled.391CubicSpline(WideCubicKeyframeCurve<f32>),392}393394//---------//395// HELPERS //396//---------//397398/// Helper function for cubic spline interpolation.399fn cubic_spline_interpolation<T>(400value_start: T,401tangent_out_start: T,402tangent_in_end: T,403value_end: T,404lerp: f32,405step_duration: f32,406) -> T407where408T: VectorSpace<Scalar = f32>,409{410let coeffs = (vec4(2.0, 1.0, -2.0, 1.0) * lerp + vec4(-3.0, -2.0, 3.0, -1.0)) * lerp;411value_start * (coeffs.x * lerp + 1.0)412+ tangent_out_start * step_duration * lerp * (coeffs.y + 1.0)413+ value_end * lerp * coeffs.z414+ tangent_in_end * step_duration * lerp * coeffs.w415}416417fn cubic_spline_interpolate_slices<'a, T: VectorSpace<Scalar = f32>>(418width: usize,419first: &'a [T],420second: &'a [T],421s: f32,422step_between: f32,423) -> impl Iterator<Item = T> + 'a {424(0..width).map(move |idx| {425cubic_spline_interpolation(426first[idx + width],427first[idx + (width * 2)],428second[idx + width],429second[idx],430s,431step_between,432)433})434}435436437