mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Curve-based animation (#15434)
# Objective This PR extends and reworks the material from #15282 by allowing arbitrary curves to be used by the animation system to animate arbitrary properties. The goals of this work are to: - Allow far greater flexibility in how animations are allowed to be defined in order to be used with `bevy_animation`. - Delegate responsibility over keyframe interpolation to `bevy_math` and the `Curve` libraries and reduce reliance on keyframes in animation definitions generally. - Move away from allowing the glTF spec to completely define animations on a mechanical level. ## Solution ### Overview At a high level, curves have been incorporated into the animation system using the `AnimationCurve` trait (closely related to what was `Keyframes`). From the top down: 1. In `animate_targets`, animations are driven by `VariableCurve`, which is now a thin wrapper around a `Box<dyn AnimationCurve>`. 2. `AnimationCurve` is something built out of a `Curve`, and it tells the animation system how to use the curve's output to actually mutate component properties. The trait looks like this: ```rust /// A low-level trait that provides control over how curves are actually applied to entities /// by the animation system. /// /// Typically, this will not need to be implemented manually, since it is automatically /// implemented by [`AnimatableCurve`] and other curves used by the animation system /// (e.g. those that animate parts of transforms or morph weights). However, this can be /// implemented manually when `AnimatableCurve` is not sufficiently expressive. /// /// In many respects, this behaves like a type-erased form of [`Curve`], where the output /// type of the curve is remembered only in the components that are mutated in the /// implementation of [`apply`]. /// /// [`apply`]: AnimationCurve::apply pub trait AnimationCurve: Reflect + Debug + Send + Sync { /// Returns a boxed clone of this value. fn clone_value(&self) -> Box<dyn AnimationCurve>; /// The range of times for which this animation is defined. fn domain(&self) -> Interval; /// Write the value of sampling this curve at time `t` into `transform` or `entity`, /// as appropriate, interpolating between the existing value and the sampled value /// using the given `weight`. fn apply<'a>( &self, t: f32, transform: Option<Mut<'a, Transform>>, entity: EntityMutExcept<'a, (Transform, AnimationPlayer, Handle<AnimationGraph>)>, weight: f32, ) -> Result<(), AnimationEvaluationError>; } ``` 3. The conversion process from a `Curve` to an `AnimationCurve` involves using wrappers which communicate the intent to animate a particular property. For example, here is `TranslationCurve`, which wraps a `Curve<Vec3>` and uses it to animate `Transform::translation`: ```rust /// This type allows a curve valued in `Vec3` to become an [`AnimationCurve`] that animates /// the translation component of a transform. pub struct TranslationCurve<C>(pub C); ``` ### Animatable Properties The `AnimatableProperty` trait survives in the transition, and it can be used to allow curves to animate arbitrary component properties. The updated documentation for `AnimatableProperty` explains this process: <details> <summary>Expand AnimatableProperty example</summary An `AnimatableProperty` is a value on a component that Bevy can animate. You can implement this trait on a unit struct in order to support animating custom components other than transforms and morph weights. Use that type in conjunction with `AnimatableCurve` (and perhaps `AnimatableKeyframeCurve` to define the animation itself). For example, in order to animate font size of a text section from 24 pt. to 80 pt., you might use: ```rust #[derive(Reflect)] struct FontSizeProperty; impl AnimatableProperty for FontSizeProperty { type Component = Text; type Property = f32; fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { Some(&mut component.sections.get_mut(0)?.style.font_size) } } ``` You can then create an `AnimationClip` to animate this property like so: ```rust let mut animation_clip = AnimationClip::default(); animation_clip.add_curve_to_target( animation_target_id, AnimatableKeyframeCurve::new( [ (0.0, 24.0), (1.0, 80.0), ] ) .map(AnimatableCurve::<FontSizeProperty, _>::from_curve) .expect("Failed to create font size curve") ); ``` Here, the use of `AnimatableKeyframeCurve` creates a curve out of the given keyframe time-value pairs, using the `Animatable` implementation of `f32` to interpolate between them. The invocation of `AnimatableCurve::from_curve` with `FontSizeProperty` indicates that the `f32` output from that curve is to be used to animate the font size of a `Text` component (as configured above). </details> ### glTF Loading glTF animations are now loaded into `Curve` types of various kinds, depending on what is being animated and what interpolation mode is being used. Those types get wrapped into and converted into `Box<dyn AnimationCurve>` and shoved inside of a `VariableCurve` just like everybody else. ### Morph Weights There is an `IterableCurve` abstraction which allows sampling these from a contiguous buffer without allocating. Its only reason for existing is that Rust disallows you from naming function types, otherwise we would just use `Curve` with an iterator output type. (The iterator involves `Map`, and the name of the function type would have to be able to be named, but it is not.) A `WeightsCurve` adaptor turns an `IterableCurve` into an `AnimationCurve`, so it behaves like everything else in that regard. ## Testing Tested by running existing animation examples. Interpolation logic has had additional tests added within the `Curve` API to replace the tests in `bevy_animation`. Some kinds of out-of-bounds errors have become impossible. Performance testing on `many_foxes` (`animate_targets`) suggests that performance is very similar to the existing implementation. Here are a couple trace histograms across different runs (yellow is this branch, red is main). <img width="669" alt="Screenshot 2024-09-27 at 9 41 50 AM" src="https://github.com/user-attachments/assets/5ba4e9ac-3aea-452e-aaf8-1492acc2d7fc"> <img width="673" alt="Screenshot 2024-09-27 at 9 45 18 AM" src="https://github.com/user-attachments/assets/8982538b-04cf-46b5-97b2-164c6bc8162e"> --- ## Migration Guide Most user code that does not directly deal with `AnimationClip` and `VariableCurve` will not need to be changed. On the other hand, `VariableCurve` has been completely overhauled. If you were previously defining animation curves in code using keyframes, you will need to migrate that code to use curve constructors instead. For example, a rotation animation defined using keyframes and added to an animation clip like this: ```rust animation_clip.add_curve_to_target( animation_target_id, VariableCurve { keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0], keyframes: Keyframes::Rotation(vec![ Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::IDENTITY, ]), interpolation: Interpolation::Linear, }, ); ``` would now be added like this: ```rust animation_clip.add_curve_to_target( animation_target_id, AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([ Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::IDENTITY, ])) .map(RotationCurve) .expect("Failed to build rotation curve"), ); ``` Note that the interface of `AnimationClip::add_curve_to_target` has also changed (as this example shows, if subtly), and now takes its curve input as an `impl AnimationCurve`. If you need to add a `VariableCurve` directly, a new method `add_variable_curve_to_target` accommodates that (and serves as a one-to-one migration in this regard). ### For reviewers The diff is pretty big, and the structure of some of the changes might not be super-obvious: - `keyframes.rs` became `animation_curves.rs`, and `AnimationCurve` is based heavily on `Keyframes`, with the adaptors also largely following suite. - The Curve API adaptor structs were moved from `bevy_math::curve::mod` into their own module `adaptors`. There are no functional changes to how these adaptors work; this is just to make room for the specialized reflection implementations since `mod.rs` was getting kind of cramped. - The new module `gltf_curves` holds the additional curve constructions that are needed by the glTF loader. Note that the loader uses a mix of these and off-the-shelf `bevy_math` curve stuff. - `animatable.rs` no longer holds logic related to keyframe interpolation, which is now delegated to the existing abstractions in `bevy_math::curve::cores`. --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
This commit is contained in:
parent
f3e8ae03cd
commit
429987ebf8
12 changed files with 2023 additions and 1472 deletions
|
@ -1,6 +1,6 @@
|
|||
//! Traits and type for interpolating between values.
|
||||
|
||||
use crate::{util, AnimationEvaluationError, Interpolation};
|
||||
use crate::util;
|
||||
use bevy_color::{Laba, LinearRgba, Oklaba, Srgba, Xyza};
|
||||
use bevy_math::*;
|
||||
use bevy_reflect::Reflect;
|
||||
|
@ -188,93 +188,11 @@ impl Animatable for Quat {
|
|||
}
|
||||
}
|
||||
|
||||
/// An abstraction over a list of keyframes.
|
||||
///
|
||||
/// Using this abstraction instead of `Vec<T>` enables more flexibility in how
|
||||
/// keyframes are stored. In particular, morph weights use this trait in order
|
||||
/// to flatten the keyframes for all morph weights into a single vector instead
|
||||
/// of nesting vectors.
|
||||
pub(crate) trait GetKeyframe {
|
||||
/// The type of the property to be animated.
|
||||
type Output;
|
||||
/// Retrieves the value of the keyframe at the given index.
|
||||
fn get_keyframe(&self, index: usize) -> Option<&Self::Output>;
|
||||
}
|
||||
|
||||
/// Interpolates between keyframes and stores the result in `dest`.
|
||||
///
|
||||
/// This is factored out so that it can be shared between implementations of
|
||||
/// [`crate::keyframes::Keyframes`].
|
||||
pub(crate) fn interpolate_keyframes<T>(
|
||||
dest: &mut T,
|
||||
keyframes: &(impl GetKeyframe<Output = T> + ?Sized),
|
||||
interpolation: Interpolation,
|
||||
step_start: usize,
|
||||
time: f32,
|
||||
weight: f32,
|
||||
duration: f32,
|
||||
) -> Result<(), AnimationEvaluationError>
|
||||
where
|
||||
T: Animatable + Clone,
|
||||
{
|
||||
let value = match interpolation {
|
||||
Interpolation::Step => {
|
||||
let Some(start_keyframe) = keyframes.get_keyframe(step_start) else {
|
||||
return Err(AnimationEvaluationError::KeyframeNotPresent(step_start));
|
||||
};
|
||||
(*start_keyframe).clone()
|
||||
}
|
||||
|
||||
Interpolation::Linear => {
|
||||
let (Some(start_keyframe), Some(end_keyframe)) = (
|
||||
keyframes.get_keyframe(step_start),
|
||||
keyframes.get_keyframe(step_start + 1),
|
||||
) else {
|
||||
return Err(AnimationEvaluationError::KeyframeNotPresent(step_start + 1));
|
||||
};
|
||||
|
||||
T::interpolate(start_keyframe, end_keyframe, time)
|
||||
}
|
||||
|
||||
Interpolation::CubicSpline => {
|
||||
let (
|
||||
Some(start_keyframe),
|
||||
Some(start_tangent_keyframe),
|
||||
Some(end_tangent_keyframe),
|
||||
Some(end_keyframe),
|
||||
) = (
|
||||
keyframes.get_keyframe(step_start * 3 + 1),
|
||||
keyframes.get_keyframe(step_start * 3 + 2),
|
||||
keyframes.get_keyframe(step_start * 3 + 3),
|
||||
keyframes.get_keyframe(step_start * 3 + 4),
|
||||
)
|
||||
else {
|
||||
return Err(AnimationEvaluationError::KeyframeNotPresent(
|
||||
step_start * 3 + 4,
|
||||
));
|
||||
};
|
||||
|
||||
interpolate_with_cubic_bezier(
|
||||
start_keyframe,
|
||||
start_tangent_keyframe,
|
||||
end_tangent_keyframe,
|
||||
end_keyframe,
|
||||
time,
|
||||
duration,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
*dest = T::interpolate(dest, &value, weight);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Evaluates a cubic Bézier curve at a value `t`, given two endpoints and the
|
||||
/// derivatives at those endpoints.
|
||||
///
|
||||
/// The derivatives are linearly scaled by `duration`.
|
||||
fn interpolate_with_cubic_bezier<T>(p0: &T, d0: &T, d3: &T, p3: &T, t: f32, duration: f32) -> T
|
||||
pub fn interpolate_with_cubic_bezier<T>(p0: &T, d0: &T, d3: &T, p3: &T, t: f32, duration: f32) -> T
|
||||
where
|
||||
T: Animatable + Clone,
|
||||
{
|
||||
|
|
538
crates/bevy_animation/src/animation_curves.rs
Normal file
538
crates/bevy_animation/src/animation_curves.rs
Normal file
|
@ -0,0 +1,538 @@
|
|||
//! The [`AnimationCurve`] trait and adaptors that allow curves to implement it.
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! The flow of curves into the animation system generally begins with something that
|
||||
//! implements the [`Curve`] trait. Let's imagine, for example, that we have some
|
||||
//! `Curve<Vec3>` that we want to use to animate something. That could be defined in
|
||||
//! a number of different ways, but let's imagine that we've defined it [using a function]:
|
||||
//!
|
||||
//! # use bevy_math::curve::{Curve, Interval, function_curve};
|
||||
//! # use bevy_math::vec3;
|
||||
//! let wobble_curve = function_curve(
|
||||
//! Interval::UNIT,
|
||||
//! |t| { vec3(t.cos(), 0.0, 0.0) },
|
||||
//! );
|
||||
//!
|
||||
//! Okay, so we have a curve, but the animation system also needs to know, in some way,
|
||||
//! how the values from this curve should actually be used. That is, it needs to know what
|
||||
//! to animate! That's what [`AnimationCurve`] is for. In particular, what we need to do
|
||||
//! is take our curve and turn it into an `AnimationCurve` which will be usable by the
|
||||
//! animation system.
|
||||
//!
|
||||
//! For instance, let's imagine that we want to use the `Vec3` output
|
||||
//! from our curve to animate the [translation component of a `Transform`]. For this, there is
|
||||
//! the adaptor [`TranslationCurve`], which wraps any `Curve<Vec3>` and turns it into an
|
||||
//! [`AnimationCurve`] that will use the given curve to animate the entity's translation:
|
||||
//!
|
||||
//! # use bevy_math::curve::{Curve, Interval, function_curve};
|
||||
//! # use bevy_math::vec3;
|
||||
//! # use bevy_animation::animation_curves::*;
|
||||
//! # let wobble_curve = function_curve(
|
||||
//! # Interval::UNIT,
|
||||
//! # |t| vec3(t.cos(), 0.0, 0.0)
|
||||
//! # );
|
||||
//! let wobble_animation = TranslationCurve(wobble_curve);
|
||||
//!
|
||||
//! And finally, this `AnimationCurve` needs to be added to an [`AnimationClip`] in order to
|
||||
//! actually animate something. This is what that looks like:
|
||||
//!
|
||||
//! # use bevy_math::curve::{Curve, Interval, function_curve};
|
||||
//! # use bevy_animation::{AnimationClip, AnimationTargetId, animation_curves::*};
|
||||
//! # use bevy_core::Name;
|
||||
//! # use bevy_math::vec3;
|
||||
//! # let wobble_curve = function_curve(
|
||||
//! # Interval::UNIT,
|
||||
//! # |t| { vec3(t.cos(), 0.0, 0.0) },
|
||||
//! # );
|
||||
//! # let wobble_animation = TranslationCurve(wobble_curve);
|
||||
//! # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
//! let mut animation_clip = AnimationClip::default();
|
||||
//! animation_clip.add_curve_to_target(
|
||||
//! animation_target_id,
|
||||
//! wobble_animation,
|
||||
//! );
|
||||
//!
|
||||
//! # Making animation curves
|
||||
//!
|
||||
//! The overview showed one example, but in general there are a few different ways of going from
|
||||
//! a [`Curve`], which produces time-related data of some kind, to an [`AnimationCurve`], which
|
||||
//! knows how to apply that data to an entity.
|
||||
//!
|
||||
//! ## `Transform`
|
||||
//!
|
||||
//! [`Transform`] is special and has its own adaptors:
|
||||
//! - [`TranslationCurve`], which uses `Vec3` output to animate [`Transform::translation`]
|
||||
//! - [`RotationCurve`], which uses `Quat` output to animate [`Transform::rotation`]
|
||||
//! - [`ScaleCurve`], which uses `Vec3` output to animate [`Transform::scale`]
|
||||
//! - [`TransformCurve`], which uses `Transform` output to animate the entire `Transform`
|
||||
//!
|
||||
//! ## Animatable properties
|
||||
//!
|
||||
//! Animation of arbitrary components can be accomplished using [`AnimatableProperty`] in
|
||||
//! conjunction with [`AnimatableCurve`]. See the documentation [there] for details.
|
||||
//!
|
||||
//! [using a function]: bevy_math::curve::function_curve
|
||||
//! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation
|
||||
//! [`AnimationClip`]: crate::AnimationClip
|
||||
//! [there]: AnimatableProperty
|
||||
|
||||
use core::{
|
||||
any::TypeId,
|
||||
fmt::{self, Debug, Formatter},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use bevy_ecs::{component::Component, world::Mut};
|
||||
use bevy_math::{
|
||||
curve::{
|
||||
cores::{UnevenCore, UnevenCoreError},
|
||||
iterable::IterableCurve,
|
||||
Curve, Interval,
|
||||
},
|
||||
FloatExt, Quat, Vec3,
|
||||
};
|
||||
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypePath};
|
||||
use bevy_render::mesh::morph::MorphWeights;
|
||||
use bevy_transform::prelude::Transform;
|
||||
|
||||
use crate::{prelude::Animatable, AnimationEntityMut, AnimationEvaluationError};
|
||||
|
||||
/// A value on a component that Bevy can animate.
|
||||
///
|
||||
/// You can implement this trait on a unit struct in order to support animating
|
||||
/// custom components other than transforms and morph weights. Use that type in
|
||||
/// conjunction with [`AnimatableCurve`] (and perhaps [`AnimatableKeyframeCurve`]
|
||||
/// to define the animation itself). For example, in order to animate font size of a
|
||||
/// text section from 24 pt. to 80 pt., you might use:
|
||||
///
|
||||
/// # use bevy_animation::prelude::AnimatableProperty;
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use bevy_text::Text;
|
||||
/// #[derive(Reflect)]
|
||||
/// struct FontSizeProperty;
|
||||
///
|
||||
/// impl AnimatableProperty for FontSizeProperty {
|
||||
/// type Component = Text;
|
||||
/// type Property = f32;
|
||||
/// fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
||||
/// Some(&mut component.sections.get_mut(0)?.style.font_size)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// You can then create an [`AnimationClip`] to animate this property like so:
|
||||
///
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve};
|
||||
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
|
||||
/// # use bevy_core::Name;
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use bevy_text::Text;
|
||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
/// # #[derive(Reflect)]
|
||||
/// # struct FontSizeProperty;
|
||||
/// # impl AnimatableProperty for FontSizeProperty {
|
||||
/// # type Component = Text;
|
||||
/// # type Property = f32;
|
||||
/// # fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
||||
/// # Some(&mut component.sections.get_mut(0)?.style.font_size)
|
||||
/// # }
|
||||
/// # }
|
||||
/// let mut animation_clip = AnimationClip::default();
|
||||
/// animation_clip.add_curve_to_target(
|
||||
/// animation_target_id,
|
||||
/// AnimatableKeyframeCurve::new(
|
||||
/// [
|
||||
/// (0.0, 24.0),
|
||||
/// (1.0, 80.0),
|
||||
/// ]
|
||||
/// )
|
||||
/// .map(AnimatableCurve::<FontSizeProperty, _>::from_curve)
|
||||
/// .expect("Failed to create font size curve")
|
||||
/// );
|
||||
///
|
||||
/// Here, the use of [`AnimatableKeyframeCurve`] creates a curve out of the given keyframe time-value
|
||||
/// pairs, using the [`Animatable`] implementation of `f32` to interpolate between them. The
|
||||
/// invocation of [`AnimatableCurve::from_curve`] with `FontSizeProperty` indicates that the `f32`
|
||||
/// output from that curve is to be used to animate the font size of a `Text` component (as
|
||||
/// configured above).
|
||||
///
|
||||
/// [`AnimationClip`]: crate::AnimationClip
|
||||
pub trait AnimatableProperty: Reflect + TypePath {
|
||||
/// The type of the component that the property lives on.
|
||||
type Component: Component;
|
||||
|
||||
/// The type of the property to be animated.
|
||||
type Property: Animatable + FromReflect + Reflectable + Clone + Sync + Debug;
|
||||
|
||||
/// Given a reference to the component, returns a reference to the property.
|
||||
///
|
||||
/// If the property couldn't be found, returns `None`.
|
||||
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property>;
|
||||
}
|
||||
|
||||
/// This trait collects the additional requirements on top of [`Curve<T>`] needed for a
|
||||
/// curve to be used as an [`AnimationCurve`].
|
||||
pub trait AnimationCompatibleCurve<T>: Curve<T> + Debug + Clone + Reflectable {}
|
||||
|
||||
impl<T, C> AnimationCompatibleCurve<T> for C where C: Curve<T> + Debug + Clone + Reflectable {}
|
||||
|
||||
/// This type allows the conversion of a [curve] valued in the [property type] of an
|
||||
/// [`AnimatableProperty`] into an [`AnimationCurve`] which animates that property.
|
||||
///
|
||||
/// [curve]: Curve
|
||||
/// [property type]: AnimatableProperty::Property
|
||||
#[derive(Reflect, FromReflect)]
|
||||
#[reflect(from_reflect = false)]
|
||||
pub struct AnimatableCurve<P, C> {
|
||||
curve: C,
|
||||
#[reflect(ignore)]
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P, C> AnimatableCurve<P, C>
|
||||
where
|
||||
P: AnimatableProperty,
|
||||
C: AnimationCompatibleCurve<P::Property>,
|
||||
{
|
||||
/// Create an [`AnimatableCurve`] (and thus an [`AnimationCurve`]) from a curve
|
||||
/// valued in an [animatable property].
|
||||
///
|
||||
/// [animatable property]: AnimatableProperty::Property
|
||||
pub fn from_curve(curve: C) -> Self {
|
||||
Self {
|
||||
curve,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, C> Clone for AnimatableCurve<P, C>
|
||||
where
|
||||
C: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
curve: self.curve.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, C> Debug for AnimatableCurve<P, C>
|
||||
where
|
||||
C: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AnimatableCurve")
|
||||
.field("curve", &self.curve)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, C> AnimationCurve for AnimatableCurve<P, C>
|
||||
where
|
||||
P: AnimatableProperty,
|
||||
C: AnimationCompatibleCurve<P::Property>,
|
||||
{
|
||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn domain(&self) -> Interval {
|
||||
self.curve.domain()
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&self,
|
||||
t: f32,
|
||||
_transform: Option<Mut<'a, Transform>>,
|
||||
mut entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = entity.get_mut::<P::Component>().ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<P::Component>())
|
||||
})?;
|
||||
let property = P::get_mut(&mut component)
|
||||
.ok_or_else(|| AnimationEvaluationError::PropertyNotPresent(TypeId::of::<P>()))?;
|
||||
let value = self.curve.sample_clamped(t);
|
||||
*property = <P::Property>::interpolate(property, &value, weight);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows a [curve] valued in `Vec3` to become an [`AnimationCurve`] that animates
|
||||
/// the translation component of a transform.
|
||||
///
|
||||
/// [curve]: Curve
|
||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
||||
#[reflect(from_reflect = false)]
|
||||
pub struct TranslationCurve<C>(pub C);
|
||||
|
||||
impl<C> AnimationCurve for TranslationCurve<C>
|
||||
where
|
||||
C: AnimationCompatibleCurve<Vec3>,
|
||||
{
|
||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn domain(&self) -> Interval {
|
||||
self.0.domain()
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&self,
|
||||
t: f32,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
let new_value = self.0.sample_clamped(t);
|
||||
component.translation =
|
||||
<Vec3 as Animatable>::interpolate(&component.translation, &new_value, weight);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows a [curve] valued in `Quat` to become an [`AnimationCurve`] that animates
|
||||
/// the rotation component of a transform.
|
||||
///
|
||||
/// [curve]: Curve
|
||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
||||
#[reflect(from_reflect = false)]
|
||||
pub struct RotationCurve<C>(pub C);
|
||||
|
||||
impl<C> AnimationCurve for RotationCurve<C>
|
||||
where
|
||||
C: AnimationCompatibleCurve<Quat>,
|
||||
{
|
||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn domain(&self) -> Interval {
|
||||
self.0.domain()
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&self,
|
||||
t: f32,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
let new_value = self.0.sample_clamped(t);
|
||||
component.rotation =
|
||||
<Quat as Animatable>::interpolate(&component.rotation, &new_value, weight);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows a [curve] valued in `Vec3` to become an [`AnimationCurve`] that animates
|
||||
/// the scale component of a transform.
|
||||
///
|
||||
/// [curve]: Curve
|
||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
||||
#[reflect(from_reflect = false)]
|
||||
pub struct ScaleCurve<C>(pub C);
|
||||
|
||||
impl<C> AnimationCurve for ScaleCurve<C>
|
||||
where
|
||||
C: AnimationCompatibleCurve<Vec3>,
|
||||
{
|
||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn domain(&self) -> Interval {
|
||||
self.0.domain()
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&self,
|
||||
t: f32,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
let new_value = self.0.sample_clamped(t);
|
||||
component.scale = <Vec3 as Animatable>::interpolate(&component.scale, &new_value, weight);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows a [curve] valued in `Transform` to become an [`AnimationCurve`] that animates
|
||||
/// a transform.
|
||||
///
|
||||
/// This exists primarily as a convenience to animate entities using the entire transform at once
|
||||
/// instead of splitting it into pieces and animating each part (translation, rotation, scale).
|
||||
///
|
||||
/// [curve]: Curve
|
||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
||||
#[reflect(from_reflect = false)]
|
||||
pub struct TransformCurve<C>(pub C);
|
||||
|
||||
impl<C> AnimationCurve for TransformCurve<C>
|
||||
where
|
||||
C: AnimationCompatibleCurve<Transform>,
|
||||
{
|
||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn domain(&self) -> Interval {
|
||||
self.0.domain()
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&self,
|
||||
t: f32,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
let new_value = self.0.sample_clamped(t);
|
||||
*component = <Transform as Animatable>::interpolate(&component, &new_value, weight);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows an [`IterableCurve`] valued in `f32` to be used as an [`AnimationCurve`]
|
||||
/// that animates [morph weights].
|
||||
///
|
||||
/// [morph weights]: MorphWeights
|
||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
||||
#[reflect(from_reflect = false)]
|
||||
pub struct WeightsCurve<C>(pub C);
|
||||
|
||||
impl<C> AnimationCurve for WeightsCurve<C>
|
||||
where
|
||||
C: IterableCurve<f32> + Debug + Clone + Reflectable,
|
||||
{
|
||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn domain(&self) -> Interval {
|
||||
self.0.domain()
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&self,
|
||||
t: f32,
|
||||
_transform: Option<Mut<'a, Transform>>,
|
||||
mut entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut dest = entity.get_mut::<MorphWeights>().ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<MorphWeights>())
|
||||
})?;
|
||||
lerp_morph_weights(dest.weights_mut(), self.0.sample_iter_clamped(t), weight);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Update `morph_weights` based on weights in `incoming_weights` with a linear interpolation
|
||||
/// on `lerp_weight`.
|
||||
fn lerp_morph_weights(
|
||||
morph_weights: &mut [f32],
|
||||
incoming_weights: impl Iterator<Item = f32>,
|
||||
lerp_weight: f32,
|
||||
) {
|
||||
let zipped = morph_weights.iter_mut().zip(incoming_weights);
|
||||
for (morph_weight, incoming_weights) in zipped {
|
||||
*morph_weight = morph_weight.lerp(incoming_weights, lerp_weight);
|
||||
}
|
||||
}
|
||||
|
||||
/// A low-level trait that provides control over how curves are actually applied to entities
|
||||
/// by the animation system.
|
||||
///
|
||||
/// Typically, this will not need to be implemented manually, since it is automatically
|
||||
/// implemented by [`AnimatableCurve`] and other curves used by the animation system
|
||||
/// (e.g. those that animate parts of transforms or morph weights). However, this can be
|
||||
/// implemented manually when `AnimatableCurve` is not sufficiently expressive.
|
||||
///
|
||||
/// In many respects, this behaves like a type-erased form of [`Curve`], where the output
|
||||
/// type of the curve is remembered only in the components that are mutated in the
|
||||
/// implementation of [`apply`].
|
||||
///
|
||||
/// [`apply`]: AnimationCurve::apply
|
||||
pub trait AnimationCurve: Reflect + Debug + Send + Sync {
|
||||
/// Returns a boxed clone of this value.
|
||||
fn clone_value(&self) -> Box<dyn AnimationCurve>;
|
||||
|
||||
/// The range of times for which this animation is defined.
|
||||
fn domain(&self) -> Interval;
|
||||
|
||||
/// Write the value of sampling this curve at time `t` into `transform` or `entity`,
|
||||
/// as appropriate, interpolating between the existing value and the sampled value
|
||||
/// using the given `weight`.
|
||||
fn apply<'a>(
|
||||
&self,
|
||||
t: f32,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError>;
|
||||
}
|
||||
|
||||
/// A [curve] defined by keyframes with values in an [animatable] type.
|
||||
///
|
||||
/// The keyframes are interpolated using the type's [`Animatable::interpolate`] implementation.
|
||||
///
|
||||
/// [curve]: Curve
|
||||
/// [animatable]: Animatable
|
||||
#[derive(Debug, Clone, Reflect)]
|
||||
pub struct AnimatableKeyframeCurve<T> {
|
||||
core: UnevenCore<T>,
|
||||
}
|
||||
|
||||
impl<T> Curve<T> for AnimatableKeyframeCurve<T>
|
||||
where
|
||||
T: Animatable + Clone,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.core.domain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
self.core.sample_with(t, <T as Animatable>::interpolate)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_clamped(&self, t: f32) -> T {
|
||||
// Sampling by keyframes is automatically clamped to the keyframe bounds.
|
||||
self.sample_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AnimatableKeyframeCurve<T>
|
||||
where
|
||||
T: Animatable,
|
||||
{
|
||||
/// Create a new [`AnimatableKeyframeCurve`] from the given `keyframes`. The values of this
|
||||
/// curve are interpolated from the keyframes using the output type's implementation of
|
||||
/// [`Animatable::interpolate`].
|
||||
///
|
||||
/// There must be at least two samples in order for this method to succeed.
|
||||
pub fn new(keyframes: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
|
||||
Ok(Self {
|
||||
core: UnevenCore::new(keyframes)?,
|
||||
})
|
||||
}
|
||||
}
|
422
crates/bevy_animation/src/gltf_curves.rs
Normal file
422
crates/bevy_animation/src/gltf_curves.rs
Normal file
|
@ -0,0 +1,422 @@
|
|||
//! 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 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_unchecked(&self, t: f32) -> T {
|
||||
self.core
|
||||
.sample_with(t, |x, y, t| if t >= 1.0 { y.clone() } else { x.clone() })
|
||||
}
|
||||
}
|
||||
|
||||
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_unchecked(&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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_unchecked(&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())
|
||||
}
|
||||
}
|
||||
|
||||
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_unchecked(&self, t: f32) -> impl Iterator<Item = T> {
|
||||
match self.core.sample_interp(t) {
|
||||
InterpolationDatum::Exact(v)
|
||||
| InterpolationDatum::LeftTail(v)
|
||||
| InterpolationDatum::RightTail(v) => TwoIterators::Left(v.iter().copied()),
|
||||
|
||||
InterpolationDatum::Between(u, v, s) => {
|
||||
let interpolated = u.iter().zip(v.iter()).map(move |(x, y)| x.lerp(*y, s));
|
||||
TwoIterators::Right(interpolated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_unchecked(&self, t: f32) -> impl Iterator<Item = T> {
|
||||
match self.core.sample_interp(t) {
|
||||
InterpolationDatum::Exact(v)
|
||||
| InterpolationDatum::LeftTail(v)
|
||||
| InterpolationDatum::RightTail(v) => TwoIterators::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() });
|
||||
TwoIterators::Right(interpolated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_unchecked(&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();
|
||||
TwoIterators::Left(v[width..(width * 2)].iter().copied())
|
||||
}
|
||||
|
||||
InterpolationDatum::Between((t0, u), (t1, v), s) => TwoIterators::Right(
|
||||
cubic_spline_interpolate_slices(self.core.width() / 3, u, v, s, t1 - t0),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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 //
|
||||
//---------//
|
||||
|
||||
enum TwoIterators<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
|
||||
impl<A, B, T> Iterator for TwoIterators<A, B>
|
||||
where
|
||||
A: Iterator<Item = T>,
|
||||
B: Iterator<Item = T>,
|
||||
{
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
TwoIterators::Left(a) => a.next(),
|
||||
TwoIterators::Right(b) => b.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
)
|
||||
})
|
||||
}
|
|
@ -1,584 +0,0 @@
|
|||
//! Keyframes of animation clips.
|
||||
|
||||
use core::{
|
||||
any::TypeId,
|
||||
fmt::{self, Debug, Formatter},
|
||||
};
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{component::Component, world::Mut};
|
||||
use bevy_math::{Quat, Vec3};
|
||||
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypePath};
|
||||
use bevy_render::mesh::morph::MorphWeights;
|
||||
use bevy_transform::prelude::Transform;
|
||||
|
||||
use crate::{
|
||||
animatable,
|
||||
prelude::{Animatable, GetKeyframe},
|
||||
AnimationEntityMut, AnimationEvaluationError, Interpolation,
|
||||
};
|
||||
|
||||
/// A value on a component that Bevy can animate.
|
||||
///
|
||||
/// You can implement this trait on a unit struct in order to support animating
|
||||
/// custom components other than transforms and morph weights. Use that type in
|
||||
/// conjunction with [`AnimatablePropertyKeyframes`]. For example, in order to
|
||||
/// animate font size of a text section from 24 pt. to 80 pt., you might use:
|
||||
///
|
||||
/// # use bevy_animation::prelude::AnimatableProperty;
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use bevy_text::Text;
|
||||
/// #[derive(Reflect)]
|
||||
/// struct FontSizeProperty;
|
||||
///
|
||||
/// impl AnimatableProperty for FontSizeProperty {
|
||||
/// type Component = Text;
|
||||
/// type Property = f32;
|
||||
/// fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
||||
/// Some(&mut component.sections.get_mut(0)?.style.font_size)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// You can then create a [`crate::AnimationClip`] to animate this property like so:
|
||||
///
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, Interpolation, VariableCurve};
|
||||
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatablePropertyKeyframes};
|
||||
/// # use bevy_core::Name;
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use bevy_text::Text;
|
||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
/// # #[derive(Reflect)]
|
||||
/// # struct FontSizeProperty;
|
||||
/// # impl AnimatableProperty for FontSizeProperty {
|
||||
/// # type Component = Text;
|
||||
/// # type Property = f32;
|
||||
/// # fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
||||
/// # Some(&mut component.sections.get_mut(0)?.style.font_size)
|
||||
/// # }
|
||||
/// # }
|
||||
/// let mut animation_clip = AnimationClip::default();
|
||||
/// animation_clip.add_curve_to_target(
|
||||
/// animation_target_id,
|
||||
/// VariableCurve::linear::<AnimatablePropertyKeyframes<FontSizeProperty>>(
|
||||
/// [0.0, 1.0],
|
||||
/// [24.0, 80.0],
|
||||
/// ),
|
||||
/// );
|
||||
pub trait AnimatableProperty: Reflect + TypePath + 'static {
|
||||
/// The type of the component that the property lives on.
|
||||
type Component: Component;
|
||||
|
||||
/// The type of the property to be animated.
|
||||
type Property: Animatable + FromReflect + Reflectable + Clone + Sync + Debug + 'static;
|
||||
|
||||
/// Given a reference to the component, returns a reference to the property.
|
||||
///
|
||||
/// If the property couldn't be found, returns `None`.
|
||||
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property>;
|
||||
}
|
||||
|
||||
/// Keyframes in a [`crate::VariableCurve`] that animate an
|
||||
/// [`AnimatableProperty`].
|
||||
///
|
||||
/// This is the a generic type of [`Keyframes`] that can animate any
|
||||
/// [`AnimatableProperty`]. See the documentation for [`AnimatableProperty`] for
|
||||
/// more information as to how to use this type.
|
||||
///
|
||||
/// If you're animating scale, rotation, or translation of a [`Transform`],
|
||||
/// [`ScaleKeyframes`], [`RotationKeyframes`], and [`TranslationKeyframes`] are
|
||||
/// faster, and simpler, alternatives to this type.
|
||||
#[derive(Reflect, Deref, DerefMut)]
|
||||
pub struct AnimatablePropertyKeyframes<P>(pub Vec<P::Property>)
|
||||
where
|
||||
P: AnimatableProperty;
|
||||
|
||||
impl<P> Clone for AnimatablePropertyKeyframes<P>
|
||||
where
|
||||
P: AnimatableProperty,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Debug for AnimatablePropertyKeyframes<P>
|
||||
where
|
||||
P: AnimatableProperty,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("AnimatablePropertyKeyframes")
|
||||
.field(&self.0)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A low-level trait for use in [`crate::VariableCurve`] that provides fine
|
||||
/// control over how animations are evaluated.
|
||||
///
|
||||
/// You can implement this trait when the generic
|
||||
/// [`AnimatablePropertyKeyframes`] isn't sufficiently-expressive for your
|
||||
/// needs. For example, [`MorphWeights`] implements this trait instead of using
|
||||
/// [`AnimatablePropertyKeyframes`] because it needs to animate arbitrarily many
|
||||
/// weights at once, which can't be done with [`Animatable`] as that works on
|
||||
/// fixed-size values only.
|
||||
pub trait Keyframes: Reflect + Debug + Send + Sync {
|
||||
/// Returns a boxed clone of this value.
|
||||
fn clone_value(&self) -> Box<dyn Keyframes>;
|
||||
|
||||
/// Interpolates between the existing value and the value of the first
|
||||
/// keyframe, and writes the value into `transform` and/or `entity` as
|
||||
/// appropriate.
|
||||
///
|
||||
/// Arguments:
|
||||
///
|
||||
/// * `transform`: The transform of the entity, if present.
|
||||
///
|
||||
/// * `entity`: Allows access to the rest of the components of the entity.
|
||||
///
|
||||
/// * `weight`: The blend weight between the existing component value (0.0)
|
||||
/// and the one computed from the keyframes (1.0).
|
||||
fn apply_single_keyframe<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError>;
|
||||
|
||||
/// Interpolates between the existing value and the value of the two nearest
|
||||
/// keyframes, and writes the value into `transform` and/or `entity` as
|
||||
/// appropriate.
|
||||
///
|
||||
/// Arguments:
|
||||
///
|
||||
/// * `transform`: The transform of the entity, if present.
|
||||
///
|
||||
/// * `entity`: Allows access to the rest of the components of the entity.
|
||||
///
|
||||
/// * `interpolation`: The type of interpolation to use.
|
||||
///
|
||||
/// * `step_start`: The index of the first keyframe.
|
||||
///
|
||||
/// * `time`: The blend weight between the first keyframe (0.0) and the next
|
||||
/// keyframe (1.0).
|
||||
///
|
||||
/// * `weight`: The blend weight between the existing component value (0.0)
|
||||
/// and the one computed from the keyframes (1.0).
|
||||
///
|
||||
/// If `interpolation` is `Interpolation::Linear`, then pseudocode for this
|
||||
/// function could be `property = lerp(property, lerp(keyframes[step_start],
|
||||
/// keyframes[step_start + 1], time), weight)`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn apply_tweened_keyframes<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
entity: AnimationEntityMut<'a>,
|
||||
interpolation: Interpolation,
|
||||
step_start: usize,
|
||||
time: f32,
|
||||
weight: f32,
|
||||
duration: f32,
|
||||
) -> Result<(), AnimationEvaluationError>;
|
||||
}
|
||||
|
||||
/// Keyframes for animating [`Transform::translation`].
|
||||
///
|
||||
/// An example of a [`crate::AnimationClip`] that animates translation:
|
||||
///
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, Interpolation};
|
||||
/// # use bevy_animation::{VariableCurve, prelude::TranslationKeyframes};
|
||||
/// # use bevy_core::Name;
|
||||
/// # use bevy_math::Vec3;
|
||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
/// let mut animation_clip = AnimationClip::default();
|
||||
/// animation_clip.add_curve_to_target(
|
||||
/// animation_target_id,
|
||||
/// VariableCurve::linear::<TranslationKeyframes>(
|
||||
/// [0.0, 1.0],
|
||||
/// [Vec3::ZERO, Vec3::ONE],
|
||||
/// ),
|
||||
/// );
|
||||
#[derive(Clone, Reflect, Debug, Deref, DerefMut)]
|
||||
pub struct TranslationKeyframes(pub Vec<Vec3>);
|
||||
|
||||
/// Keyframes for animating [`Transform::scale`].
|
||||
///
|
||||
/// An example of a [`crate::AnimationClip`] that animates translation:
|
||||
///
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, Interpolation};
|
||||
/// # use bevy_animation::{VariableCurve, prelude::ScaleKeyframes};
|
||||
/// # use bevy_core::Name;
|
||||
/// # use bevy_math::Vec3;
|
||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
/// let mut animation_clip = AnimationClip::default();
|
||||
/// animation_clip.add_curve_to_target(
|
||||
/// animation_target_id,
|
||||
/// VariableCurve::linear::<ScaleKeyframes>(
|
||||
/// [0.0, 1.0],
|
||||
/// [Vec3::ONE, Vec3::splat(2.0)],
|
||||
/// ),
|
||||
/// );
|
||||
#[derive(Clone, Reflect, Debug, Deref, DerefMut)]
|
||||
pub struct ScaleKeyframes(pub Vec<Vec3>);
|
||||
|
||||
/// Keyframes for animating [`Transform::rotation`].
|
||||
///
|
||||
/// An example of a [`crate::AnimationClip`] that animates translation:
|
||||
///
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, Interpolation};
|
||||
/// # use bevy_animation::{VariableCurve, prelude::RotationKeyframes};
|
||||
/// # use bevy_core::Name;
|
||||
/// # use bevy_math::Quat;
|
||||
/// # use std::f32::consts::FRAC_PI_2;
|
||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
/// let mut animation_clip = AnimationClip::default();
|
||||
/// animation_clip.add_curve_to_target(
|
||||
/// animation_target_id,
|
||||
/// VariableCurve::linear::<RotationKeyframes>(
|
||||
/// [0.0, 1.0],
|
||||
/// [Quat::from_rotation_x(FRAC_PI_2), Quat::from_rotation_y(FRAC_PI_2)],
|
||||
/// ),
|
||||
/// );
|
||||
#[derive(Clone, Reflect, Debug, Deref, DerefMut)]
|
||||
pub struct RotationKeyframes(pub Vec<Quat>);
|
||||
|
||||
/// Keyframes for animating [`MorphWeights`].
|
||||
#[derive(Clone, Debug, Reflect)]
|
||||
pub struct MorphWeightsKeyframes {
|
||||
/// The total number of morph weights.
|
||||
pub morph_target_count: usize,
|
||||
|
||||
/// The morph weights.
|
||||
///
|
||||
/// The length of this vector should be the total number of morph weights
|
||||
/// times the number of keyframes.
|
||||
pub weights: Vec<f32>,
|
||||
}
|
||||
|
||||
impl<T> From<T> for TranslationKeyframes
|
||||
where
|
||||
T: Into<Vec<Vec3>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Keyframes for TranslationKeyframes {
|
||||
fn clone_value(&self) -> Box<dyn Keyframes> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn apply_single_keyframe<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
let value = self
|
||||
.first()
|
||||
.ok_or(AnimationEvaluationError::KeyframeNotPresent(0))?;
|
||||
component.translation = Animatable::interpolate(&component.translation, value, weight);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_tweened_keyframes<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_: AnimationEntityMut<'a>,
|
||||
interpolation: Interpolation,
|
||||
step_start: usize,
|
||||
time: f32,
|
||||
weight: f32,
|
||||
duration: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
animatable::interpolate_keyframes(
|
||||
&mut component.translation,
|
||||
&(*self)[..],
|
||||
interpolation,
|
||||
step_start,
|
||||
time,
|
||||
weight,
|
||||
duration,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for ScaleKeyframes
|
||||
where
|
||||
T: Into<Vec<Vec3>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Keyframes for ScaleKeyframes {
|
||||
fn clone_value(&self) -> Box<dyn Keyframes> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn apply_single_keyframe<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
let value = self
|
||||
.first()
|
||||
.ok_or(AnimationEvaluationError::KeyframeNotPresent(0))?;
|
||||
component.scale = Animatable::interpolate(&component.scale, value, weight);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_tweened_keyframes<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_: AnimationEntityMut<'a>,
|
||||
interpolation: Interpolation,
|
||||
step_start: usize,
|
||||
time: f32,
|
||||
weight: f32,
|
||||
duration: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
animatable::interpolate_keyframes(
|
||||
&mut component.scale,
|
||||
&(*self)[..],
|
||||
interpolation,
|
||||
step_start,
|
||||
time,
|
||||
weight,
|
||||
duration,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for RotationKeyframes
|
||||
where
|
||||
T: Into<Vec<Quat>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Keyframes for RotationKeyframes {
|
||||
fn clone_value(&self) -> Box<dyn Keyframes> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn apply_single_keyframe<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
let value = self
|
||||
.first()
|
||||
.ok_or(AnimationEvaluationError::KeyframeNotPresent(0))?;
|
||||
component.rotation = Animatable::interpolate(&component.rotation, value, weight);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_tweened_keyframes<'a>(
|
||||
&self,
|
||||
transform: Option<Mut<'a, Transform>>,
|
||||
_: AnimationEntityMut<'a>,
|
||||
interpolation: Interpolation,
|
||||
step_start: usize,
|
||||
time: f32,
|
||||
weight: f32,
|
||||
duration: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = transform.ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
||||
})?;
|
||||
animatable::interpolate_keyframes(
|
||||
&mut component.rotation,
|
||||
&(*self)[..],
|
||||
interpolation,
|
||||
step_start,
|
||||
time,
|
||||
weight,
|
||||
duration,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> From<T> for AnimatablePropertyKeyframes<P>
|
||||
where
|
||||
P: AnimatableProperty,
|
||||
T: Into<Vec<P::Property>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Keyframes for AnimatablePropertyKeyframes<P>
|
||||
where
|
||||
P: AnimatableProperty,
|
||||
{
|
||||
fn clone_value(&self) -> Box<dyn Keyframes> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn apply_single_keyframe<'a>(
|
||||
&self,
|
||||
_: Option<Mut<'a, Transform>>,
|
||||
mut entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = entity.get_mut::<P::Component>().ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<P::Component>())
|
||||
})?;
|
||||
let property = P::get_mut(&mut component)
|
||||
.ok_or_else(|| AnimationEvaluationError::PropertyNotPresent(TypeId::of::<P>()))?;
|
||||
let value = self
|
||||
.first()
|
||||
.ok_or(AnimationEvaluationError::KeyframeNotPresent(0))?;
|
||||
<P::Property>::interpolate(property, value, weight);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_tweened_keyframes<'a>(
|
||||
&self,
|
||||
_: Option<Mut<'a, Transform>>,
|
||||
mut entity: AnimationEntityMut<'a>,
|
||||
interpolation: Interpolation,
|
||||
step_start: usize,
|
||||
time: f32,
|
||||
weight: f32,
|
||||
duration: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut component = entity.get_mut::<P::Component>().ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<P::Component>())
|
||||
})?;
|
||||
let property = P::get_mut(&mut component)
|
||||
.ok_or_else(|| AnimationEvaluationError::PropertyNotPresent(TypeId::of::<P>()))?;
|
||||
animatable::interpolate_keyframes(
|
||||
property,
|
||||
self,
|
||||
interpolation,
|
||||
step_start,
|
||||
time,
|
||||
weight,
|
||||
duration,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> GetKeyframe for [A]
|
||||
where
|
||||
A: Animatable,
|
||||
{
|
||||
type Output = A;
|
||||
|
||||
fn get_keyframe(&self, index: usize) -> Option<&Self::Output> {
|
||||
self.get(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> GetKeyframe for AnimatablePropertyKeyframes<P>
|
||||
where
|
||||
P: AnimatableProperty,
|
||||
{
|
||||
type Output = P::Property;
|
||||
|
||||
fn get_keyframe(&self, index: usize) -> Option<&Self::Output> {
|
||||
self.get(index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information needed to look up morph weight values in the flattened morph
|
||||
/// weight keyframes vector.
|
||||
struct GetMorphWeightKeyframe<'k> {
|
||||
/// The morph weights keyframe structure that we're animating.
|
||||
keyframes: &'k MorphWeightsKeyframes,
|
||||
/// The index of the morph target in that structure.
|
||||
morph_target_index: usize,
|
||||
}
|
||||
|
||||
impl Keyframes for MorphWeightsKeyframes {
|
||||
fn clone_value(&self) -> Box<dyn Keyframes> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn apply_single_keyframe<'a>(
|
||||
&self,
|
||||
_: Option<Mut<'a, Transform>>,
|
||||
mut entity: AnimationEntityMut<'a>,
|
||||
weight: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut dest = entity.get_mut::<MorphWeights>().ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<MorphWeights>())
|
||||
})?;
|
||||
|
||||
// TODO: Go 4 weights at a time to make better use of SIMD.
|
||||
for (morph_target_index, morph_weight) in dest.weights_mut().iter_mut().enumerate() {
|
||||
*morph_weight =
|
||||
f32::interpolate(morph_weight, &self.weights[morph_target_index], weight);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_tweened_keyframes<'a>(
|
||||
&self,
|
||||
_: Option<Mut<'a, Transform>>,
|
||||
mut entity: AnimationEntityMut<'a>,
|
||||
interpolation: Interpolation,
|
||||
step_start: usize,
|
||||
time: f32,
|
||||
weight: f32,
|
||||
duration: f32,
|
||||
) -> Result<(), AnimationEvaluationError> {
|
||||
let mut dest = entity.get_mut::<MorphWeights>().ok_or_else(|| {
|
||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<MorphWeights>())
|
||||
})?;
|
||||
|
||||
// TODO: Go 4 weights at a time to make better use of SIMD.
|
||||
for (morph_target_index, morph_weight) in dest.weights_mut().iter_mut().enumerate() {
|
||||
animatable::interpolate_keyframes(
|
||||
morph_weight,
|
||||
&GetMorphWeightKeyframe {
|
||||
keyframes: self,
|
||||
morph_target_index,
|
||||
},
|
||||
interpolation,
|
||||
step_start,
|
||||
time,
|
||||
weight,
|
||||
duration,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GetKeyframe for GetMorphWeightKeyframe<'_> {
|
||||
type Output = f32;
|
||||
|
||||
fn get_keyframe(&self, keyframe_index: usize) -> Option<&Self::Output> {
|
||||
self.keyframes
|
||||
.weights
|
||||
.as_slice()
|
||||
.get(keyframe_index * self.keyframes.morph_target_count + self.morph_target_index)
|
||||
}
|
||||
}
|
|
@ -10,8 +10,9 @@
|
|||
extern crate alloc;
|
||||
|
||||
pub mod animatable;
|
||||
pub mod animation_curves;
|
||||
pub mod gltf_curves;
|
||||
pub mod graph;
|
||||
pub mod keyframes;
|
||||
pub mod transition;
|
||||
mod util;
|
||||
|
||||
|
@ -33,12 +34,11 @@ use bevy_ecs::{
|
|||
reflect::{ReflectMapEntities, ReflectVisitEntities, ReflectVisitEntitiesMut},
|
||||
world::EntityMutExcept,
|
||||
};
|
||||
use bevy_math::FloatExt;
|
||||
use bevy_reflect::{
|
||||
prelude::ReflectDefault, utility::NonGenericTypeInfoCell, ApplyError, DynamicStruct, FieldIter,
|
||||
FromReflect, FromType, GetTypeRegistration, NamedField, PartialReflect, Reflect,
|
||||
ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Struct, StructInfo,
|
||||
TypeInfo, TypePath, TypeRegistration, Typed,
|
||||
prelude::ReflectDefault, utility::NonGenericTypeInfoCell, ApplyError, DynamicTupleStruct,
|
||||
FromReflect, FromType, GetTypeRegistration, PartialReflect, Reflect, ReflectFromPtr,
|
||||
ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TupleStruct, TupleStructFieldIter,
|
||||
TupleStructInfo, TypeInfo, TypePath, TypeRegistration, Typed, UnnamedField,
|
||||
};
|
||||
use bevy_time::Time;
|
||||
use bevy_transform::{prelude::Transform, TransformSystem};
|
||||
|
@ -61,14 +61,14 @@ use uuid::Uuid;
|
|||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
animatable::*, graph::*, keyframes::*, transition::*, AnimationClip, AnimationPlayer,
|
||||
AnimationPlugin, Interpolation, VariableCurve,
|
||||
animatable::*, animation_curves::*, graph::*, transition::*, AnimationClip,
|
||||
AnimationPlayer, AnimationPlugin, VariableCurve,
|
||||
};
|
||||
}
|
||||
|
||||
use crate::{
|
||||
animation_curves::AnimationCurve,
|
||||
graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex},
|
||||
keyframes::Keyframes,
|
||||
transition::{advance_transitions, expire_completed_transitions, AnimationTransitions},
|
||||
};
|
||||
|
||||
|
@ -77,167 +77,29 @@ use crate::{
|
|||
/// [UUID namespace]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)
|
||||
pub static ANIMATION_TARGET_NAMESPACE: Uuid = Uuid::from_u128(0x3179f519d9274ff2b5966fd077023911);
|
||||
|
||||
/// Describes how an attribute of a [`Transform`] or
|
||||
/// [`bevy_render::mesh::morph::MorphWeights`] should be animated.
|
||||
/// Contains an [animation curve] which is used to animate entities.
|
||||
///
|
||||
/// `keyframe_timestamps` and `keyframes` should have the same length.
|
||||
/// [animation curve]: AnimationCurve
|
||||
#[derive(Debug, TypePath)]
|
||||
pub struct VariableCurve {
|
||||
/// Timestamp for each of the keyframes.
|
||||
pub keyframe_timestamps: Vec<f32>,
|
||||
/// List of the keyframes.
|
||||
///
|
||||
/// The representation will depend on the interpolation type of this curve:
|
||||
///
|
||||
/// - for `Interpolation::Step` and `Interpolation::Linear`, each keyframe is a single value
|
||||
/// - for `Interpolation::CubicSpline`, each keyframe is made of three values for `tangent_in`,
|
||||
/// `keyframe_value` and `tangent_out`
|
||||
pub keyframes: Box<dyn Keyframes>,
|
||||
/// Interpolation method to use between keyframes.
|
||||
pub interpolation: Interpolation,
|
||||
}
|
||||
pub struct VariableCurve(pub Box<dyn AnimationCurve>);
|
||||
|
||||
impl Clone for VariableCurve {
|
||||
fn clone(&self) -> Self {
|
||||
VariableCurve {
|
||||
keyframe_timestamps: self.keyframe_timestamps.clone(),
|
||||
keyframes: Keyframes::clone_value(&*self.keyframes),
|
||||
interpolation: self.interpolation,
|
||||
}
|
||||
Self(AnimationCurve::clone_value(&*self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl VariableCurve {
|
||||
/// Creates a new curve from timestamps, keyframes, and interpolation type.
|
||||
/// Create a new [`VariableCurve`] from an [animation curve].
|
||||
///
|
||||
/// The two arrays must have the same length.
|
||||
pub fn new<K>(
|
||||
keyframe_timestamps: Vec<f32>,
|
||||
keyframes: impl Into<K>,
|
||||
interpolation: Interpolation,
|
||||
) -> VariableCurve
|
||||
where
|
||||
K: Keyframes,
|
||||
{
|
||||
VariableCurve {
|
||||
keyframe_timestamps,
|
||||
keyframes: Box::new(keyframes.into()),
|
||||
interpolation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new curve from timestamps and keyframes with no interpolation.
|
||||
///
|
||||
/// The two arrays must have the same length.
|
||||
pub fn step<K>(
|
||||
keyframe_timestamps: impl Into<Vec<f32>>,
|
||||
keyframes: impl Into<K>,
|
||||
) -> VariableCurve
|
||||
where
|
||||
K: Keyframes,
|
||||
{
|
||||
VariableCurve::new(keyframe_timestamps.into(), keyframes, Interpolation::Step)
|
||||
}
|
||||
|
||||
/// Creates a new curve from timestamps and keyframes with linear
|
||||
/// interpolation.
|
||||
///
|
||||
/// The two arrays must have the same length.
|
||||
pub fn linear<K>(
|
||||
keyframe_timestamps: impl Into<Vec<f32>>,
|
||||
keyframes: impl Into<K>,
|
||||
) -> VariableCurve
|
||||
where
|
||||
K: Keyframes,
|
||||
{
|
||||
VariableCurve::new(keyframe_timestamps.into(), keyframes, Interpolation::Linear)
|
||||
}
|
||||
|
||||
/// Creates a new curve from timestamps and keyframes with no interpolation.
|
||||
///
|
||||
/// The two arrays must have the same length.
|
||||
pub fn cubic_spline<K>(
|
||||
keyframe_timestamps: impl Into<Vec<f32>>,
|
||||
keyframes: impl Into<K>,
|
||||
) -> VariableCurve
|
||||
where
|
||||
K: Keyframes,
|
||||
{
|
||||
VariableCurve::new(
|
||||
keyframe_timestamps.into(),
|
||||
keyframes,
|
||||
Interpolation::CubicSpline,
|
||||
)
|
||||
}
|
||||
|
||||
/// Find the index of the keyframe at or before the current time.
|
||||
///
|
||||
/// Returns [`None`] if the curve is finished or not yet started.
|
||||
/// To be more precise, this returns [`None`] if the frame is at or past the last keyframe:
|
||||
/// we cannot get the *next* keyframe to interpolate to in that case.
|
||||
pub fn find_current_keyframe(&self, seek_time: f32) -> Option<usize> {
|
||||
// An Ok(keyframe_index) result means an exact result was found by binary search
|
||||
// An Err result means the keyframe was not found, and the index is the keyframe
|
||||
// PERF: finding the current keyframe can be optimised
|
||||
let search_result = self
|
||||
.keyframe_timestamps
|
||||
.binary_search_by(|probe| probe.partial_cmp(&seek_time).unwrap());
|
||||
|
||||
// Subtract one for zero indexing!
|
||||
let last_keyframe = self.keyframe_timestamps.len() - 1;
|
||||
|
||||
// We want to find the index of the keyframe before the current time
|
||||
// If the keyframe is past the second-to-last keyframe, the animation cannot be interpolated.
|
||||
let step_start = match search_result {
|
||||
// An exact match was found, and it is the last keyframe (or something has gone terribly wrong).
|
||||
// This means that the curve is finished.
|
||||
Ok(n) if n >= last_keyframe => return None,
|
||||
// An exact match was found, and it is not the last keyframe.
|
||||
Ok(i) => i,
|
||||
// No exact match was found, and the seek_time is before the start of the animation.
|
||||
// This occurs because the binary search returns the index of where we could insert a value
|
||||
// without disrupting the order of the vector.
|
||||
// If the value is less than the first element, the index will be 0.
|
||||
Err(0) => return None,
|
||||
// No exact match was found, and it was after the last keyframe.
|
||||
// The curve is finished.
|
||||
Err(n) if n > last_keyframe => return None,
|
||||
// No exact match was found, so return the previous keyframe to interpolate from.
|
||||
Err(i) => i - 1,
|
||||
};
|
||||
|
||||
// Consumers need to be able to interpolate between the return keyframe and the next
|
||||
assert!(step_start < self.keyframe_timestamps.len());
|
||||
|
||||
Some(step_start)
|
||||
}
|
||||
|
||||
/// Find the index of the keyframe at or before the current time.
|
||||
///
|
||||
/// Returns the first keyframe if the `seek_time` is before the first keyframe, and
|
||||
/// the second-to-last keyframe if the `seek_time` is after the last keyframe.
|
||||
/// Panics if there are less than 2 keyframes.
|
||||
pub fn find_interpolation_start_keyframe(&self, seek_time: f32) -> usize {
|
||||
// An Ok(keyframe_index) result means an exact result was found by binary search
|
||||
// An Err result means the keyframe was not found, and the index is the keyframe
|
||||
// PERF: finding the current keyframe can be optimised
|
||||
let search_result = self
|
||||
.keyframe_timestamps
|
||||
.binary_search_by(|probe| probe.partial_cmp(&seek_time).unwrap());
|
||||
|
||||
// We want to find the index of the keyframe before the current time
|
||||
// If the keyframe is past the second-to-last keyframe, the animation cannot be interpolated.
|
||||
match search_result {
|
||||
// An exact match was found
|
||||
Ok(i) => i.clamp(0, self.keyframe_timestamps.len() - 2),
|
||||
// No exact match was found, so return the previous keyframe to interpolate from.
|
||||
Err(i) => (i.saturating_sub(1)).clamp(0, self.keyframe_timestamps.len() - 2),
|
||||
}
|
||||
/// [animation curve]: AnimationCurve
|
||||
pub fn new(animation_curve: impl AnimationCurve) -> Self {
|
||||
Self(Box::new(animation_curve))
|
||||
}
|
||||
}
|
||||
|
||||
// We have to implement `PartialReflect` manually because of the embedded
|
||||
// `Box<dyn Keyframes>`, which can't be automatically derived yet.
|
||||
// `Box<dyn AnimationCurve>`, which can't be automatically derived yet.
|
||||
impl PartialReflect for VariableCurve {
|
||||
#[inline]
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
|
@ -274,32 +136,31 @@ impl PartialReflect for VariableCurve {
|
|||
}
|
||||
|
||||
fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> {
|
||||
if let ReflectRef::Struct(struct_value) = value.reflect_ref() {
|
||||
for (i, value) in struct_value.iter_fields().enumerate() {
|
||||
let name = struct_value.name_at(i).unwrap();
|
||||
if let Some(v) = self.field_mut(name) {
|
||||
if let ReflectRef::TupleStruct(tuple_value) = value.reflect_ref() {
|
||||
for (i, value) in tuple_value.iter_fields().enumerate() {
|
||||
if let Some(v) = self.field_mut(i) {
|
||||
v.try_apply(value)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ApplyError::MismatchedKinds {
|
||||
from_kind: value.reflect_kind(),
|
||||
to_kind: ReflectKind::Struct,
|
||||
to_kind: ReflectKind::TupleStruct,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reflect_ref(&self) -> ReflectRef {
|
||||
ReflectRef::Struct(self)
|
||||
ReflectRef::TupleStruct(self)
|
||||
}
|
||||
|
||||
fn reflect_mut(&mut self) -> ReflectMut {
|
||||
ReflectMut::Struct(self)
|
||||
ReflectMut::TupleStruct(self)
|
||||
}
|
||||
|
||||
fn reflect_owned(self: Box<Self>) -> ReflectOwned {
|
||||
ReflectOwned::Struct(self)
|
||||
ReflectOwned::TupleStruct(self)
|
||||
}
|
||||
|
||||
fn clone_value(&self) -> Box<dyn PartialReflect> {
|
||||
|
@ -308,7 +169,7 @@ impl PartialReflect for VariableCurve {
|
|||
}
|
||||
|
||||
// We have to implement `Reflect` manually because of the embedded `Box<dyn
|
||||
// Keyframes>`, which can't be automatically derived yet.
|
||||
// AnimationCurve>`, which can't be automatically derived yet.
|
||||
impl Reflect for VariableCurve {
|
||||
#[inline]
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
|
@ -347,79 +208,38 @@ impl Reflect for VariableCurve {
|
|||
}
|
||||
}
|
||||
|
||||
// We have to implement `Struct` manually because of the embedded `Box<dyn
|
||||
// Keyframes>`, which can't be automatically derived yet.
|
||||
impl Struct for VariableCurve {
|
||||
fn field(&self, name: &str) -> Option<&dyn PartialReflect> {
|
||||
match name {
|
||||
"keyframe_timestamps" => Some(&self.keyframe_timestamps),
|
||||
"keyframes" => Some(self.keyframes.as_partial_reflect()),
|
||||
"interpolation" => Some(&self.interpolation),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn field_mut(&mut self, name: &str) -> Option<&mut dyn PartialReflect> {
|
||||
match name {
|
||||
"keyframe_timestamps" => Some(&mut self.keyframe_timestamps),
|
||||
"keyframes" => Some(self.keyframes.as_partial_reflect_mut()),
|
||||
"interpolation" => Some(&mut self.interpolation),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn field_at(&self, index: usize) -> Option<&dyn PartialReflect> {
|
||||
// We have to implement `TupleStruct` manually because of the embedded `Box<dyn
|
||||
// AnimationCurve>`, which can't be automatically derived yet.
|
||||
impl TupleStruct for VariableCurve {
|
||||
fn field(&self, index: usize) -> Option<&dyn PartialReflect> {
|
||||
match index {
|
||||
0 => Some(&self.keyframe_timestamps),
|
||||
1 => Some(self.keyframes.as_partial_reflect()),
|
||||
2 => Some(&self.interpolation),
|
||||
0 => Some(self.0.as_partial_reflect()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> {
|
||||
fn field_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> {
|
||||
match index {
|
||||
0 => Some(&mut self.keyframe_timestamps),
|
||||
1 => Some(self.keyframes.as_partial_reflect_mut()),
|
||||
2 => Some(&mut self.interpolation),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn name_at(&self, index: usize) -> Option<&str> {
|
||||
match index {
|
||||
0 => Some("keyframe_timestamps"),
|
||||
1 => Some("keyframes"),
|
||||
2 => Some("interpolation"),
|
||||
0 => Some(self.0.as_partial_reflect_mut()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn field_len(&self) -> usize {
|
||||
3
|
||||
1
|
||||
}
|
||||
|
||||
fn iter_fields(&self) -> FieldIter {
|
||||
FieldIter::new(self)
|
||||
fn iter_fields(&self) -> TupleStructFieldIter {
|
||||
TupleStructFieldIter::new(self)
|
||||
}
|
||||
|
||||
fn clone_dynamic(&self) -> DynamicStruct {
|
||||
DynamicStruct::from_iter([
|
||||
(
|
||||
"keyframe_timestamps",
|
||||
Box::new(self.keyframe_timestamps.clone()) as Box<dyn PartialReflect>,
|
||||
),
|
||||
("keyframes", PartialReflect::clone_value(&*self.keyframes)),
|
||||
(
|
||||
"interpolation",
|
||||
Box::new(self.interpolation) as Box<dyn PartialReflect>,
|
||||
),
|
||||
])
|
||||
fn clone_dynamic(&self) -> DynamicTupleStruct {
|
||||
DynamicTupleStruct::from_iter([PartialReflect::clone_value(&*self.0)])
|
||||
}
|
||||
}
|
||||
|
||||
// We have to implement `FromReflect` manually because of the embedded `Box<dyn
|
||||
// Keyframes>`, which can't be automatically derived yet.
|
||||
// AnimationCurve>`, which can't be automatically derived yet.
|
||||
impl FromReflect for VariableCurve {
|
||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
||||
Some(reflect.try_downcast_ref::<VariableCurve>()?.clone())
|
||||
|
@ -427,7 +247,7 @@ impl FromReflect for VariableCurve {
|
|||
}
|
||||
|
||||
// We have to implement `GetTypeRegistration` manually because of the embedded
|
||||
// `Box<dyn Keyframes>`, which can't be automatically derived yet.
|
||||
// `Box<dyn AnimationCurve>`, which can't be automatically derived yet.
|
||||
impl GetTypeRegistration for VariableCurve {
|
||||
fn get_type_registration() -> TypeRegistration {
|
||||
let mut registration = TypeRegistration::of::<Self>();
|
||||
|
@ -437,32 +257,16 @@ impl GetTypeRegistration for VariableCurve {
|
|||
}
|
||||
|
||||
// We have to implement `Typed` manually because of the embedded `Box<dyn
|
||||
// Keyframes>`, which can't be automatically derived yet.
|
||||
// AnimationCurve>`, which can't be automatically derived yet.
|
||||
impl Typed for VariableCurve {
|
||||
fn type_info() -> &'static TypeInfo {
|
||||
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
||||
CELL.get_or_set(|| {
|
||||
TypeInfo::Struct(StructInfo::new::<Self>(&[
|
||||
NamedField::new::<Vec<f32>>("keyframe_timestamps"),
|
||||
NamedField::new::<()>("keyframes"),
|
||||
NamedField::new::<Interpolation>("interpolation"),
|
||||
]))
|
||||
TypeInfo::TupleStruct(TupleStructInfo::new::<Self>(&[UnnamedField::new::<()>(0)]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpolation method to use between keyframes.
|
||||
#[derive(Reflect, Clone, Copy, Debug)]
|
||||
pub enum Interpolation {
|
||||
/// Linear interpolation between the two closest keyframes.
|
||||
Linear,
|
||||
/// Step interpolation, the value of the start keyframe is used.
|
||||
Step,
|
||||
/// Cubic spline interpolation. The value of the two closest keyframes is used, with the out
|
||||
/// tangent of the start keyframe and the in tangent of the end keyframe.
|
||||
CubicSpline,
|
||||
}
|
||||
|
||||
/// A list of [`VariableCurve`]s and the [`AnimationTargetId`]s to which they
|
||||
/// apply.
|
||||
///
|
||||
|
@ -590,18 +394,46 @@ impl AnimationClip {
|
|||
self.duration = duration_sec;
|
||||
}
|
||||
|
||||
/// Adds a [`VariableCurve`] to an [`AnimationTarget`] named by an
|
||||
/// Adds an [`AnimationCurve`] to an [`AnimationTarget`] named by an
|
||||
/// [`AnimationTargetId`].
|
||||
///
|
||||
/// If the curve extends beyond the current duration of this clip, this
|
||||
/// method lengthens this clip to include the entire time span that the
|
||||
/// curve covers.
|
||||
pub fn add_curve_to_target(&mut self, target_id: AnimationTargetId, curve: VariableCurve) {
|
||||
pub fn add_curve_to_target(
|
||||
&mut self,
|
||||
target_id: AnimationTargetId,
|
||||
curve: impl AnimationCurve,
|
||||
) {
|
||||
// Update the duration of the animation by this curve duration if it's longer
|
||||
self.duration = self
|
||||
.duration
|
||||
.max(*curve.keyframe_timestamps.last().unwrap_or(&0.0));
|
||||
self.curves.entry(target_id).or_default().push(curve);
|
||||
let end = curve.domain().end();
|
||||
if end.is_finite() {
|
||||
self.duration = self.duration.max(end);
|
||||
}
|
||||
self.curves
|
||||
.entry(target_id)
|
||||
.or_default()
|
||||
.push(VariableCurve::new(curve));
|
||||
}
|
||||
|
||||
/// Like [`add_curve_to_target`], but adding a [`VariableCurve`] directly.
|
||||
///
|
||||
/// Under normal circumstances, that method is generally more convenient.
|
||||
///
|
||||
/// [`add_curve_to_target`]: AnimationClip::add_curve_to_target
|
||||
pub fn add_variable_curve_to_target(
|
||||
&mut self,
|
||||
target_id: AnimationTargetId,
|
||||
variable_curve: VariableCurve,
|
||||
) {
|
||||
let end = variable_curve.0.domain().end();
|
||||
if end.is_finite() {
|
||||
self.duration = self.duration.max(end);
|
||||
}
|
||||
self.curves
|
||||
.entry(target_id)
|
||||
.or_default()
|
||||
.push(variable_curve);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -620,15 +452,6 @@ pub enum RepeatAnimation {
|
|||
/// Why Bevy failed to evaluate an animation.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AnimationEvaluationError {
|
||||
/// The `keyframes` array is too small.
|
||||
///
|
||||
/// For curves with `Interpolation::Step` or `Interpolation::Linear`, the
|
||||
/// `keyframes` array must have at least as many elements as keyframe
|
||||
/// timestamps. For curves with `Interpolation::CubicBezier`, the
|
||||
/// `keyframes` array must have at least 3× the number of elements as
|
||||
/// keyframe timestamps, in order to account for the tangents.
|
||||
KeyframeNotPresent(usize),
|
||||
|
||||
/// The component to be animated isn't present on the animation target.
|
||||
///
|
||||
/// To fix this error, make sure the entity to be animated contains all
|
||||
|
@ -1199,36 +1022,11 @@ pub fn animate_targets(
|
|||
let seek_time = active_animation.seek_time;
|
||||
|
||||
for curve in curves {
|
||||
// Some curves have only one keyframe used to set a transform
|
||||
if curve.keyframe_timestamps.len() == 1 {
|
||||
if let Err(err) = curve.keyframes.apply_single_keyframe(
|
||||
transform.as_mut().map(|transform| transform.reborrow()),
|
||||
entity_mut.reborrow(),
|
||||
weight,
|
||||
) {
|
||||
warn!("Animation application failed: {:?}", err);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the best keyframe to interpolate from
|
||||
let step_start = curve.find_interpolation_start_keyframe(seek_time);
|
||||
|
||||
let timestamp_start = curve.keyframe_timestamps[step_start];
|
||||
let timestamp_end = curve.keyframe_timestamps[step_start + 1];
|
||||
// Compute how far we are through the keyframe, normalized to [0, 1]
|
||||
let lerp = f32::inverse_lerp(timestamp_start, timestamp_end, seek_time)
|
||||
.clamp(0.0, 1.0);
|
||||
|
||||
if let Err(err) = curve.keyframes.apply_tweened_keyframes(
|
||||
if let Err(err) = curve.0.apply(
|
||||
seek_time,
|
||||
transform.as_mut().map(|transform| transform.reborrow()),
|
||||
entity_mut.reborrow(),
|
||||
curve.interpolation,
|
||||
step_start,
|
||||
lerp,
|
||||
weight,
|
||||
timestamp_end - timestamp_start,
|
||||
) {
|
||||
warn!("Animation application failed: {:?}", err);
|
||||
}
|
||||
|
@ -1316,153 +1114,3 @@ impl AnimationGraphEvaluator {
|
|||
.extend(iter::repeat(EvaluatedAnimationGraphNode::default()).take(node_count));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{prelude::TranslationKeyframes, VariableCurve};
|
||||
use bevy_math::Vec3;
|
||||
|
||||
// Returns the curve and the keyframe count.
|
||||
fn test_variable_curve() -> (VariableCurve, usize) {
|
||||
let keyframe_timestamps = vec![1.0, 2.0, 3.0, 4.0];
|
||||
let keyframes = vec![
|
||||
Vec3::ONE * 0.0,
|
||||
Vec3::ONE * 3.0,
|
||||
Vec3::ONE * 6.0,
|
||||
Vec3::ONE * 9.0,
|
||||
];
|
||||
let interpolation = crate::Interpolation::Linear;
|
||||
|
||||
assert_eq!(keyframe_timestamps.len(), keyframes.len());
|
||||
let keyframe_count = keyframes.len();
|
||||
|
||||
let variable_curve = VariableCurve::new::<TranslationKeyframes>(
|
||||
keyframe_timestamps,
|
||||
keyframes,
|
||||
interpolation,
|
||||
);
|
||||
|
||||
// f32 doesn't impl Ord so we can't easily sort it
|
||||
let mut maybe_last_timestamp = None;
|
||||
for current_timestamp in &variable_curve.keyframe_timestamps {
|
||||
assert!(current_timestamp.is_finite());
|
||||
|
||||
if let Some(last_timestamp) = maybe_last_timestamp {
|
||||
assert!(current_timestamp > last_timestamp);
|
||||
}
|
||||
maybe_last_timestamp = Some(current_timestamp);
|
||||
}
|
||||
|
||||
(variable_curve, keyframe_count)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_current_keyframe_is_in_bounds() {
|
||||
let curve = test_variable_curve().0;
|
||||
let min_time = *curve.keyframe_timestamps.first().unwrap();
|
||||
// We will always get none at times at or past the second last keyframe
|
||||
let second_last_keyframe = curve.keyframe_timestamps.len() - 2;
|
||||
let max_time = curve.keyframe_timestamps[second_last_keyframe];
|
||||
let elapsed_time = max_time - min_time;
|
||||
|
||||
let n_keyframes = curve.keyframe_timestamps.len();
|
||||
let n_test_points = 5;
|
||||
|
||||
for i in 0..=n_test_points {
|
||||
// Get a value between 0 and 1
|
||||
let normalized_time = i as f32 / n_test_points as f32;
|
||||
let seek_time = min_time + normalized_time * elapsed_time;
|
||||
assert!(seek_time >= min_time);
|
||||
assert!(seek_time <= max_time);
|
||||
|
||||
let maybe_current_keyframe = curve.find_current_keyframe(seek_time);
|
||||
assert!(
|
||||
maybe_current_keyframe.is_some(),
|
||||
"Seek time: {seek_time}, Min time: {min_time}, Max time: {max_time}"
|
||||
);
|
||||
|
||||
// We cannot return the last keyframe,
|
||||
// because we want to interpolate between the current and next keyframe
|
||||
assert!(maybe_current_keyframe.unwrap() < n_keyframes);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_current_keyframe_returns_none_on_unstarted_animations() {
|
||||
let curve = test_variable_curve().0;
|
||||
let min_time = *curve.keyframe_timestamps.first().unwrap();
|
||||
let seek_time = 0.0;
|
||||
assert!(seek_time < min_time);
|
||||
|
||||
let maybe_keyframe = curve.find_current_keyframe(seek_time);
|
||||
assert!(
|
||||
maybe_keyframe.is_none(),
|
||||
"Seek time: {seek_time}, Minimum time: {min_time}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_current_keyframe_returns_none_on_finished_animation() {
|
||||
let curve = test_variable_curve().0;
|
||||
let max_time = *curve.keyframe_timestamps.last().unwrap();
|
||||
|
||||
assert!(max_time < f32::INFINITY);
|
||||
let maybe_keyframe = curve.find_current_keyframe(f32::INFINITY);
|
||||
assert!(maybe_keyframe.is_none());
|
||||
|
||||
let maybe_keyframe = curve.find_current_keyframe(max_time);
|
||||
assert!(maybe_keyframe.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_last_keyframe_is_found_correctly() {
|
||||
let curve = test_variable_curve().0;
|
||||
|
||||
// Exact time match
|
||||
let second_last_keyframe = curve.keyframe_timestamps.len() - 2;
|
||||
let second_last_time = curve.keyframe_timestamps[second_last_keyframe];
|
||||
let maybe_keyframe = curve.find_current_keyframe(second_last_time);
|
||||
assert!(maybe_keyframe.unwrap() == second_last_keyframe);
|
||||
|
||||
// Inexact match, between the last and second last frames
|
||||
let seek_time = second_last_time + 0.001;
|
||||
let last_time = curve.keyframe_timestamps[second_last_keyframe + 1];
|
||||
assert!(seek_time < last_time);
|
||||
|
||||
let maybe_keyframe = curve.find_current_keyframe(seek_time);
|
||||
assert!(maybe_keyframe.unwrap() == second_last_keyframe);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_keyframe_matches_are_found_correctly() {
|
||||
let (curve, keyframe_count) = test_variable_curve();
|
||||
let second_last_keyframe = keyframe_count - 2;
|
||||
|
||||
for i in 0..=second_last_keyframe {
|
||||
let seek_time = curve.keyframe_timestamps[i];
|
||||
|
||||
let keyframe = curve.find_current_keyframe(seek_time).unwrap();
|
||||
assert!(keyframe == i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_and_inexact_keyframes_correspond() {
|
||||
let (curve, keyframe_count) = test_variable_curve();
|
||||
let second_last_keyframe = keyframe_count - 2;
|
||||
|
||||
for i in 0..=second_last_keyframe {
|
||||
let seek_time = curve.keyframe_timestamps[i];
|
||||
|
||||
let exact_keyframe = curve.find_current_keyframe(seek_time).unwrap();
|
||||
|
||||
let inexact_seek_time = seek_time + 0.0001;
|
||||
let final_time = *curve.keyframe_timestamps.last().unwrap();
|
||||
assert!(inexact_seek_time < final_time);
|
||||
|
||||
let inexact_keyframe = curve.find_current_keyframe(inexact_seek_time).unwrap();
|
||||
|
||||
assert!(exact_keyframe == inexact_keyframe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -268,7 +268,9 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
let (animations, named_animations, animation_roots) = {
|
||||
use bevy_animation::Interpolation;
|
||||
use bevy_animation::{animation_curves::*, gltf_curves::*, VariableCurve};
|
||||
use bevy_math::curve::{constant_curve, Interval, UnevenSampleAutoCurve};
|
||||
use bevy_math::{Quat, Vec4};
|
||||
use gltf::animation::util::ReadOutputs;
|
||||
let mut animations = vec![];
|
||||
let mut named_animations = HashMap::default();
|
||||
|
@ -276,12 +278,8 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||
for animation in gltf.animations() {
|
||||
let mut animation_clip = AnimationClip::default();
|
||||
for channel in animation.channels() {
|
||||
let interpolation = match channel.sampler().interpolation() {
|
||||
gltf::animation::Interpolation::Linear => Interpolation::Linear,
|
||||
gltf::animation::Interpolation::Step => Interpolation::Step,
|
||||
gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline,
|
||||
};
|
||||
let node = channel.target().node();
|
||||
let interpolation = channel.sampler().interpolation();
|
||||
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
||||
let keyframe_timestamps: Vec<f32> = if let Some(inputs) = reader.read_inputs() {
|
||||
match inputs {
|
||||
|
@ -296,26 +294,150 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||
return Err(GltfError::MissingAnimationSampler(animation.index()));
|
||||
};
|
||||
|
||||
let keyframes = if let Some(outputs) = reader.read_outputs() {
|
||||
if keyframe_timestamps.is_empty() {
|
||||
warn!("Tried to load animation with no keyframe timestamps");
|
||||
continue;
|
||||
}
|
||||
|
||||
let maybe_curve: Option<VariableCurve> = if let Some(outputs) =
|
||||
reader.read_outputs()
|
||||
{
|
||||
match outputs {
|
||||
ReadOutputs::Translations(tr) => {
|
||||
Box::new(TranslationKeyframes(tr.map(Vec3::from).collect()))
|
||||
as Box<dyn Keyframes>
|
||||
let translations: Vec<Vec3> = tr.map(Vec3::from).collect();
|
||||
if keyframe_timestamps.len() == 1 {
|
||||
#[allow(clippy::unnecessary_map_on_constructor)]
|
||||
Some(constant_curve(Interval::EVERYWHERE, translations[0]))
|
||||
.map(TranslationCurve)
|
||||
.map(VariableCurve::new)
|
||||
} else {
|
||||
match interpolation {
|
||||
gltf::animation::Interpolation::Linear => {
|
||||
UnevenSampleAutoCurve::new(
|
||||
keyframe_timestamps.into_iter().zip(translations),
|
||||
)
|
||||
.ok()
|
||||
.map(TranslationCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::Step => {
|
||||
SteppedKeyframeCurve::new(
|
||||
keyframe_timestamps.into_iter().zip(translations),
|
||||
)
|
||||
.ok()
|
||||
.map(TranslationCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::CubicSpline => {
|
||||
CubicKeyframeCurve::new(keyframe_timestamps, translations)
|
||||
.ok()
|
||||
.map(TranslationCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ReadOutputs::Rotations(rots) => {
|
||||
let rotations: Vec<Quat> =
|
||||
rots.into_f32().map(Quat::from_array).collect();
|
||||
if keyframe_timestamps.len() == 1 {
|
||||
#[allow(clippy::unnecessary_map_on_constructor)]
|
||||
Some(constant_curve(Interval::EVERYWHERE, rotations[0]))
|
||||
.map(RotationCurve)
|
||||
.map(VariableCurve::new)
|
||||
} else {
|
||||
match interpolation {
|
||||
gltf::animation::Interpolation::Linear => {
|
||||
UnevenSampleAutoCurve::new(
|
||||
keyframe_timestamps.into_iter().zip(rotations),
|
||||
)
|
||||
.ok()
|
||||
.map(RotationCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::Step => {
|
||||
SteppedKeyframeCurve::new(
|
||||
keyframe_timestamps.into_iter().zip(rotations),
|
||||
)
|
||||
.ok()
|
||||
.map(RotationCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::CubicSpline => {
|
||||
CubicRotationCurve::new(
|
||||
keyframe_timestamps,
|
||||
rotations.into_iter().map(Vec4::from),
|
||||
)
|
||||
.ok()
|
||||
.map(RotationCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ReadOutputs::Rotations(rots) => Box::new(RotationKeyframes(
|
||||
rots.into_f32().map(bevy_math::Quat::from_array).collect(),
|
||||
))
|
||||
as Box<dyn Keyframes>,
|
||||
ReadOutputs::Scales(scale) => {
|
||||
Box::new(ScaleKeyframes(scale.map(Vec3::from).collect()))
|
||||
as Box<dyn Keyframes>
|
||||
let scales: Vec<Vec3> = scale.map(Vec3::from).collect();
|
||||
if keyframe_timestamps.len() == 1 {
|
||||
#[allow(clippy::unnecessary_map_on_constructor)]
|
||||
Some(constant_curve(Interval::EVERYWHERE, scales[0]))
|
||||
.map(ScaleCurve)
|
||||
.map(VariableCurve::new)
|
||||
} else {
|
||||
match interpolation {
|
||||
gltf::animation::Interpolation::Linear => {
|
||||
UnevenSampleAutoCurve::new(
|
||||
keyframe_timestamps.into_iter().zip(scales),
|
||||
)
|
||||
.ok()
|
||||
.map(ScaleCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::Step => {
|
||||
SteppedKeyframeCurve::new(
|
||||
keyframe_timestamps.into_iter().zip(scales),
|
||||
)
|
||||
.ok()
|
||||
.map(ScaleCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::CubicSpline => {
|
||||
CubicKeyframeCurve::new(keyframe_timestamps, scales)
|
||||
.ok()
|
||||
.map(ScaleCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ReadOutputs::MorphTargetWeights(weights) => {
|
||||
let weights: Vec<_> = weights.into_f32().collect();
|
||||
Box::new(MorphWeightsKeyframes {
|
||||
morph_target_count: weights.len() / keyframe_timestamps.len(),
|
||||
weights,
|
||||
}) as Box<dyn Keyframes>
|
||||
let weights: Vec<f32> = weights.into_f32().collect();
|
||||
if keyframe_timestamps.len() == 1 {
|
||||
#[allow(clippy::unnecessary_map_on_constructor)]
|
||||
Some(constant_curve(Interval::EVERYWHERE, weights))
|
||||
.map(WeightsCurve)
|
||||
.map(VariableCurve::new)
|
||||
} else {
|
||||
match interpolation {
|
||||
gltf::animation::Interpolation::Linear => {
|
||||
WideLinearKeyframeCurve::new(keyframe_timestamps, weights)
|
||||
.ok()
|
||||
.map(WeightsCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::Step => {
|
||||
WideSteppedKeyframeCurve::new(keyframe_timestamps, weights)
|
||||
.ok()
|
||||
.map(WeightsCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
gltf::animation::Interpolation::CubicSpline => {
|
||||
WideCubicKeyframeCurve::new(keyframe_timestamps, weights)
|
||||
.ok()
|
||||
.map(WeightsCurve)
|
||||
.map(VariableCurve::new)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -323,15 +445,19 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||
return Err(GltfError::MissingAnimationSampler(animation.index()));
|
||||
};
|
||||
|
||||
let Some(curve) = maybe_curve else {
|
||||
warn!(
|
||||
"Invalid keyframe data for node {}; curve could not be constructed",
|
||||
node.index()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some((root_index, path)) = paths.get(&node.index()) {
|
||||
animation_roots.insert(*root_index);
|
||||
animation_clip.add_curve_to_target(
|
||||
animation_clip.add_variable_curve_to_target(
|
||||
AnimationTargetId::from_names(path.iter()),
|
||||
VariableCurve {
|
||||
keyframe_timestamps,
|
||||
keyframes,
|
||||
interpolation,
|
||||
},
|
||||
curve,
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
|
|
|
@ -1,27 +1,504 @@
|
|||
//! A module containing utility helper structs to transform a [`Curve`] into another. This is useful
|
||||
//! for building up complex curves from simple segments.
|
||||
use core::marker::PhantomData;
|
||||
//! Adaptors used by the Curve API for transforming and combining curves together.
|
||||
|
||||
use super::interval::*;
|
||||
use super::Curve;
|
||||
|
||||
use crate::VectorSpace;
|
||||
use core::any::type_name;
|
||||
use core::fmt::{self, Debug};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::{Curve, Interval};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::{utility::GenericTypePathCell, Reflect, TypePath};
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
mod paths {
|
||||
pub(super) const THIS_MODULE: &str = "bevy_math::curve::adaptors";
|
||||
pub(super) const THIS_CRATE: &str = "bevy_math";
|
||||
}
|
||||
|
||||
// NOTE ON REFLECTION:
|
||||
//
|
||||
// Function members of structs pose an obstacle for reflection, because they don't implement
|
||||
// reflection traits themselves. Some of these are more problematic than others; for example,
|
||||
// `FromReflect` is basically hopeless for function members regardless, so function-containing
|
||||
// adaptors will just never be `FromReflect` (at least until function item types implement
|
||||
// Default, if that ever happens). Similarly, they do not implement `TypePath`, and as a result,
|
||||
// those adaptors also need custom `TypePath` adaptors which use `type_name` instead.
|
||||
//
|
||||
// The sum total weirdness of the `Reflect` implementations amounts to this; those adaptors:
|
||||
// - are currently never `FromReflect`;
|
||||
// - have custom `TypePath` implementations which are not fully stable;
|
||||
// - have custom `Debug` implementations which display the function only by type name.
|
||||
|
||||
/// A curve with a constant value over its domain.
|
||||
///
|
||||
/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct ConstantCurve<T> {
|
||||
pub(crate) domain: Interval,
|
||||
pub(crate) value: T,
|
||||
}
|
||||
|
||||
impl<T> ConstantCurve<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
/// Create a constant curve, which has the given `domain` and always produces the given `value`
|
||||
/// when sampled.
|
||||
pub fn new(domain: Interval, value: T) -> Self {
|
||||
Self { domain, value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Curve<T> for ConstantCurve<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, _t: f32) -> T {
|
||||
self.value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve defined by a function together with a fixed domain.
|
||||
///
|
||||
/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
|
||||
/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(where T: TypePath),
|
||||
reflect(from_reflect = false, type_path = false),
|
||||
)]
|
||||
pub struct FunctionCurve<T, F> {
|
||||
pub(crate) domain: Interval,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) f: F,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, F> Debug for FunctionCurve<T, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FunctionCurve")
|
||||
.field("domain", &self.domain)
|
||||
.field("f", &type_name::<F>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
|
||||
/// for function members.
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
impl<T, F> TypePath for FunctionCurve<T, F>
|
||||
where
|
||||
T: TypePath,
|
||||
F: 'static,
|
||||
{
|
||||
fn type_path() -> &'static str {
|
||||
static CELL: GenericTypePathCell = GenericTypePathCell::new();
|
||||
CELL.get_or_insert::<Self, _>(|| {
|
||||
format!(
|
||||
"{}::FunctionCurve<{},{}>",
|
||||
paths::THIS_MODULE,
|
||||
T::type_path(),
|
||||
type_name::<F>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn short_type_path() -> &'static str {
|
||||
static CELL: GenericTypePathCell = GenericTypePathCell::new();
|
||||
CELL.get_or_insert::<Self, _>(|| {
|
||||
format!(
|
||||
"FunctionCurve<{},{}>",
|
||||
T::short_type_path(),
|
||||
type_name::<F>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn type_ident() -> Option<&'static str> {
|
||||
Some("FunctionCurve")
|
||||
}
|
||||
|
||||
fn crate_name() -> Option<&'static str> {
|
||||
Some(paths::THIS_CRATE)
|
||||
}
|
||||
|
||||
fn module_path() -> Option<&'static str> {
|
||||
Some(paths::THIS_MODULE)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> FunctionCurve<T, F>
|
||||
where
|
||||
F: Fn(f32) -> T,
|
||||
{
|
||||
/// Create a new curve with the given `domain` from the given `function`. When sampled, the
|
||||
/// `function` is evaluated at the sample time to compute the output.
|
||||
pub fn new(domain: Interval, function: F) -> Self {
|
||||
FunctionCurve {
|
||||
domain,
|
||||
f: function,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> Curve<T> for FunctionCurve<T, F>
|
||||
where
|
||||
F: Fn(f32) -> T,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
(self.f)(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve whose samples are defined by mapping samples from another curve through a
|
||||
/// given function. Curves of this type are produced by [`Curve::map`].
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(where S: TypePath, T: TypePath, C: TypePath),
|
||||
reflect(from_reflect = false, type_path = false),
|
||||
)]
|
||||
pub struct MapCurve<S, T, C, F> {
|
||||
pub(crate) preimage: C,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) f: F,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<(S, T)>,
|
||||
}
|
||||
|
||||
impl<S, T, C, F> Debug for MapCurve<S, T, C, F>
|
||||
where
|
||||
C: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MapCurve")
|
||||
.field("preimage", &self.preimage)
|
||||
.field("f", &type_name::<F>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
|
||||
/// for function members.
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
impl<S, T, C, F> TypePath for MapCurve<S, T, C, F>
|
||||
where
|
||||
S: TypePath,
|
||||
T: TypePath,
|
||||
C: TypePath,
|
||||
F: 'static,
|
||||
{
|
||||
fn type_path() -> &'static str {
|
||||
static CELL: GenericTypePathCell = GenericTypePathCell::new();
|
||||
CELL.get_or_insert::<Self, _>(|| {
|
||||
format!(
|
||||
"{}::MapCurve<{},{},{},{}>",
|
||||
paths::THIS_MODULE,
|
||||
S::type_path(),
|
||||
T::type_path(),
|
||||
C::type_path(),
|
||||
type_name::<F>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn short_type_path() -> &'static str {
|
||||
static CELL: GenericTypePathCell = GenericTypePathCell::new();
|
||||
CELL.get_or_insert::<Self, _>(|| {
|
||||
format!(
|
||||
"MapCurve<{},{},{},{}>",
|
||||
S::type_path(),
|
||||
T::type_path(),
|
||||
C::type_path(),
|
||||
type_name::<F>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn type_ident() -> Option<&'static str> {
|
||||
Some("MapCurve")
|
||||
}
|
||||
|
||||
fn crate_name() -> Option<&'static str> {
|
||||
Some(paths::THIS_CRATE)
|
||||
}
|
||||
|
||||
fn module_path() -> Option<&'static str> {
|
||||
Some(paths::THIS_MODULE)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
|
||||
where
|
||||
C: Curve<S>,
|
||||
F: Fn(S) -> T,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.preimage.domain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
(self.f)(self.preimage.sample_unchecked(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve whose sample space is mapped onto that of some base curve's before sampling.
|
||||
/// Curves of this type are produced by [`Curve::reparametrize`].
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(where T: TypePath, C: TypePath),
|
||||
reflect(from_reflect = false, type_path = false),
|
||||
)]
|
||||
pub struct ReparamCurve<T, C, F> {
|
||||
pub(crate) domain: Interval,
|
||||
pub(crate) base: C,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) f: F,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C, F> Debug for ReparamCurve<T, C, F>
|
||||
where
|
||||
C: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ReparamCurve")
|
||||
.field("domain", &self.domain)
|
||||
.field("base", &self.base)
|
||||
.field("f", &type_name::<F>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
|
||||
/// for function members.
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
impl<T, C, F> TypePath for ReparamCurve<T, C, F>
|
||||
where
|
||||
T: TypePath,
|
||||
C: TypePath,
|
||||
F: 'static,
|
||||
{
|
||||
fn type_path() -> &'static str {
|
||||
static CELL: GenericTypePathCell = GenericTypePathCell::new();
|
||||
CELL.get_or_insert::<Self, _>(|| {
|
||||
format!(
|
||||
"{}::ReparamCurve<{},{},{}>",
|
||||
paths::THIS_MODULE,
|
||||
T::type_path(),
|
||||
C::type_path(),
|
||||
type_name::<F>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn short_type_path() -> &'static str {
|
||||
static CELL: GenericTypePathCell = GenericTypePathCell::new();
|
||||
CELL.get_or_insert::<Self, _>(|| {
|
||||
format!(
|
||||
"ReparamCurve<{},{},{}>",
|
||||
T::type_path(),
|
||||
C::type_path(),
|
||||
type_name::<F>()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn type_ident() -> Option<&'static str> {
|
||||
Some("ReparamCurve")
|
||||
}
|
||||
|
||||
fn crate_name() -> Option<&'static str> {
|
||||
Some(paths::THIS_CRATE)
|
||||
}
|
||||
|
||||
fn module_path() -> Option<&'static str> {
|
||||
Some(paths::THIS_MODULE)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
|
||||
where
|
||||
C: Curve<T>,
|
||||
F: Fn(f32) -> f32,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
self.base.sample_unchecked((self.f)(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that has had its domain changed by a linear reparametrization (stretching and scaling).
|
||||
/// Curves of this type are produced by [`Curve::reparametrize_linear`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(from_reflect = false)
|
||||
)]
|
||||
pub struct LinearReparamCurve<T, C> {
|
||||
/// Invariants: The domain of this curve must always be bounded.
|
||||
pub(crate) base: C,
|
||||
/// Invariants: This interval must always be bounded.
|
||||
pub(crate) new_domain: Interval,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<T> for LinearReparamCurve<T, C>
|
||||
where
|
||||
C: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.new_domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
// The invariants imply this unwrap always succeeds.
|
||||
let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
|
||||
self.base.sample_unchecked(f(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that has been reparametrized by another curve, using that curve to transform the
|
||||
/// sample times before sampling. Curves of this type are produced by [`Curve::reparametrize_by_curve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct CurveReparamCurve<T, C, D> {
|
||||
pub(crate) base: C,
|
||||
pub(crate) reparam_curve: D,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
|
||||
where
|
||||
C: Curve<T>,
|
||||
D: Curve<f32>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.reparam_curve.domain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
let sample_time = self.reparam_curve.sample_unchecked(t);
|
||||
self.base.sample_unchecked(sample_time)
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that is the graph of another curve over its parameter space. Curves of this type are
|
||||
/// produced by [`Curve::graph`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct GraphCurve<T, C> {
|
||||
pub(crate) base: C,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
|
||||
where
|
||||
C: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.base.domain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> (f32, T) {
|
||||
(t, self.base.sample_unchecked(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that combines the output data from two constituent curves into a tuple output. Curves
|
||||
/// of this type are produced by [`Curve::zip`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct ZipCurve<S, T, C, D> {
|
||||
pub(crate) domain: Interval,
|
||||
pub(crate) first: C,
|
||||
pub(crate) second: D,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<(S, T)>,
|
||||
}
|
||||
|
||||
impl<S, T, C, D> Curve<(S, T)> for ZipCurve<S, T, C, D>
|
||||
where
|
||||
C: Curve<S>,
|
||||
D: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> (S, T) {
|
||||
(
|
||||
self.first.sample_unchecked(t),
|
||||
self.second.sample_unchecked(t),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The curve that results from chaining one curve with another. The second curve is
|
||||
/// effectively reparametrized so that its start is at the end of the first.
|
||||
///
|
||||
/// For this to be well-formed, the first curve's domain must be right-finite and the second's
|
||||
/// must be left-finite.
|
||||
///
|
||||
/// Curves of this type are produced by [`Curve::chain`].
|
||||
///
|
||||
/// # Domain
|
||||
///
|
||||
/// The first curve's domain must be right-finite and the second's must be left-finite to get a
|
||||
/// valid [`ChainCurve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct ChainCurve<T, C, D> {
|
||||
pub(super) first: C,
|
||||
pub(super) second: D,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
pub(crate) first: C,
|
||||
pub(crate) second: D,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C, D> Curve<T> for ChainCurve<T, C, D>
|
||||
|
@ -62,10 +539,11 @@ where
|
|||
/// The original curve's domain must be bounded to get a valid [`ReverseCurve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct ReverseCurve<T, C> {
|
||||
pub(super) curve: C,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
pub(crate) curve: C,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<T> for ReverseCurve<T, C>
|
||||
|
@ -98,11 +576,12 @@ where
|
|||
/// The original curve's domain must be bounded to get a valid [`RepeatCurve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct RepeatCurve<T, C> {
|
||||
pub(super) domain: Interval,
|
||||
pub(super) curve: C,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
pub(crate) domain: Interval,
|
||||
pub(crate) curve: C,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<T> for RepeatCurve<T, C>
|
||||
|
@ -142,10 +621,11 @@ where
|
|||
/// The original curve's domain must be bounded to get a valid [`ForeverCurve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct ForeverCurve<T, C> {
|
||||
pub(super) curve: C,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
pub(crate) curve: C,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<T> for ForeverCurve<T, C>
|
||||
|
@ -181,10 +661,11 @@ where
|
|||
/// The original curve's domain must be right-finite to get a valid [`PingPongCurve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct PingPongCurve<T, C> {
|
||||
pub(super) curve: C,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
pub(crate) curve: C,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<T> for PingPongCurve<T, C>
|
||||
|
@ -230,13 +711,14 @@ where
|
|||
/// valid [`ContinuationCurve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
pub struct ContinuationCurve<T, C, D> {
|
||||
pub(super) first: C,
|
||||
pub(super) second: D,
|
||||
pub(crate) first: C,
|
||||
pub(crate) second: D,
|
||||
// cache the offset in the curve directly to prevent triple sampling for every sample we make
|
||||
pub(super) offset: T,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
pub(crate) offset: T,
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C, D> Curve<T> for ContinuationCurve<T, C, D>
|
||||
|
|
|
@ -496,6 +496,15 @@ pub enum ChunkedUnevenCoreError {
|
|||
/// The actual length of the value buffer.
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// Tried to infer the width, but the ratio of lengths wasn't an integer, so no such length exists.
|
||||
#[error("The length of the list of values ({values_len}) was not divisible by that of the list of times ({times_len})")]
|
||||
NonDivisibleLengths {
|
||||
/// The length of the value buffer.
|
||||
values_len: usize,
|
||||
/// The length of the time buffer.
|
||||
times_len: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> ChunkedUnevenCore<T> {
|
||||
|
@ -504,17 +513,17 @@ impl<T> ChunkedUnevenCore<T> {
|
|||
///
|
||||
/// Produces an error in any of the following circumstances:
|
||||
/// - `width` is zero.
|
||||
/// - `times` has less than `2` valid unique entries.
|
||||
/// - `times` has less than `2` unique valid entries.
|
||||
/// - `values` has the incorrect length relative to `times`.
|
||||
///
|
||||
/// [type-level documentation]: ChunkedUnevenCore
|
||||
pub fn new(
|
||||
times: impl Into<Vec<f32>>,
|
||||
values: impl Into<Vec<T>>,
|
||||
times: impl IntoIterator<Item = f32>,
|
||||
values: impl IntoIterator<Item = T>,
|
||||
width: usize,
|
||||
) -> Result<Self, ChunkedUnevenCoreError> {
|
||||
let times: Vec<f32> = times.into();
|
||||
let values: Vec<T> = values.into();
|
||||
let times = times.into_iter().collect_vec();
|
||||
let values = values.into_iter().collect_vec();
|
||||
|
||||
if width == 0 {
|
||||
return Err(ChunkedUnevenCoreError::ZeroWidth);
|
||||
|
@ -538,6 +547,52 @@ impl<T> ChunkedUnevenCore<T> {
|
|||
Ok(Self { times, values })
|
||||
}
|
||||
|
||||
/// Create a new [`ChunkedUnevenCore`], inferring the width from the sizes of the inputs.
|
||||
/// The given `times` are sorted, filtered to finite times, and deduplicated. See the
|
||||
/// [type-level documentation] for more information about this type. Prefer using [`new`]
|
||||
/// if possible, since that constructor has richer error checking.
|
||||
///
|
||||
/// Produces an error in any of the following circumstances:
|
||||
/// - `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).
|
||||
///
|
||||
/// The [width] is implicitly taken to be the length of `values` divided by that of `times`
|
||||
/// (once sorted, filtered, and deduplicated).
|
||||
///
|
||||
/// [type-level documentation]: ChunkedUnevenCore
|
||||
/// [`new`]: ChunkedUnevenCore::new
|
||||
/// [width]: ChunkedUnevenCore::width
|
||||
pub fn new_width_inferred(
|
||||
times: impl IntoIterator<Item = f32>,
|
||||
values: impl IntoIterator<Item = T>,
|
||||
) -> Result<Self, ChunkedUnevenCoreError> {
|
||||
let times = times.into_iter().collect_vec();
|
||||
let values = values.into_iter().collect_vec();
|
||||
|
||||
let times = filter_sort_dedup_times(times);
|
||||
|
||||
if times.len() < 2 {
|
||||
return Err(ChunkedUnevenCoreError::NotEnoughSamples {
|
||||
samples: times.len(),
|
||||
});
|
||||
}
|
||||
|
||||
if values.len() % times.len() != 0 {
|
||||
return Err(ChunkedUnevenCoreError::NonDivisibleLengths {
|
||||
values_len: values.len(),
|
||||
times_len: times.len(),
|
||||
});
|
||||
}
|
||||
|
||||
if values.is_empty() {
|
||||
return Err(ChunkedUnevenCoreError::ZeroWidth);
|
||||
}
|
||||
|
||||
Ok(Self { times, values })
|
||||
}
|
||||
|
||||
/// The domain of the curve derived from this core.
|
||||
///
|
||||
/// # Panics
|
||||
|
@ -626,3 +681,134 @@ pub fn uneven_interp(times: &[f32], t: f32) -> InterpolationDatum<usize> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ChunkedUnevenCore, EvenCore, UnevenCore};
|
||||
use crate::curve::{cores::InterpolationDatum, interval};
|
||||
use approx::{assert_abs_diff_eq, AbsDiffEq};
|
||||
|
||||
fn approx_between<T>(datum: InterpolationDatum<T>, start: T, end: T, p: f32) -> bool
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
if let InterpolationDatum::Between(m_start, m_end, m_p) = datum {
|
||||
m_start == start && m_end == end && m_p.abs_diff_eq(&p, 1e-6)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_left_tail<T>(datum: InterpolationDatum<T>) -> bool {
|
||||
matches!(datum, InterpolationDatum::LeftTail(_))
|
||||
}
|
||||
|
||||
fn is_right_tail<T>(datum: InterpolationDatum<T>) -> bool {
|
||||
matches!(datum, InterpolationDatum::RightTail(_))
|
||||
}
|
||||
|
||||
fn is_exact<T>(datum: InterpolationDatum<T>, target: T) -> bool
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
if let InterpolationDatum::Exact(v) = datum {
|
||||
v == target
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn even_sample_interp() {
|
||||
let even_core = EvenCore::<f32>::new(
|
||||
interval(0.0, 1.0).unwrap(),
|
||||
// 11 entries -> 10 segments
|
||||
vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
|
||||
)
|
||||
.expect("Failed to construct test core");
|
||||
|
||||
let datum = even_core.sample_interp(-1.0);
|
||||
assert!(is_left_tail(datum));
|
||||
let datum = even_core.sample_interp(0.0);
|
||||
assert!(is_left_tail(datum));
|
||||
let datum = even_core.sample_interp(1.0);
|
||||
assert!(is_right_tail(datum));
|
||||
let datum = even_core.sample_interp(2.0);
|
||||
assert!(is_right_tail(datum));
|
||||
|
||||
let datum = even_core.sample_interp(0.05);
|
||||
let InterpolationDatum::Between(0.0, 1.0, p) = datum else {
|
||||
panic!("Sample did not lie in the correct subinterval")
|
||||
};
|
||||
assert_abs_diff_eq!(p, 0.5);
|
||||
|
||||
let datum = even_core.sample_interp(0.05);
|
||||
assert!(approx_between(datum, &0.0, &1.0, 0.5));
|
||||
let datum = even_core.sample_interp(0.33);
|
||||
assert!(approx_between(datum, &3.0, &4.0, 0.3));
|
||||
let datum = even_core.sample_interp(0.78);
|
||||
assert!(approx_between(datum, &7.0, &8.0, 0.8));
|
||||
|
||||
let datum = even_core.sample_interp(0.5);
|
||||
assert!(approx_between(datum, &4.0, &5.0, 1.0) || approx_between(datum, &5.0, &6.0, 0.0));
|
||||
let datum = even_core.sample_interp(0.7);
|
||||
assert!(approx_between(datum, &6.0, &7.0, 1.0) || approx_between(datum, &7.0, &8.0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uneven_sample_interp() {
|
||||
let uneven_core = UnevenCore::<f32>::new(vec![
|
||||
(0.0, 0.0),
|
||||
(1.0, 3.0),
|
||||
(2.0, 9.0),
|
||||
(4.0, 10.0),
|
||||
(8.0, -5.0),
|
||||
])
|
||||
.expect("Failed to construct test core");
|
||||
|
||||
let datum = uneven_core.sample_interp(-1.0);
|
||||
assert!(is_left_tail(datum));
|
||||
let datum = uneven_core.sample_interp(0.0);
|
||||
assert!(is_exact(datum, &0.0));
|
||||
let datum = uneven_core.sample_interp(8.0);
|
||||
assert!(is_exact(datum, &(-5.0)));
|
||||
let datum = uneven_core.sample_interp(9.0);
|
||||
assert!(is_right_tail(datum));
|
||||
|
||||
let datum = uneven_core.sample_interp(0.5);
|
||||
assert!(approx_between(datum, &0.0, &3.0, 0.5));
|
||||
let datum = uneven_core.sample_interp(2.5);
|
||||
assert!(approx_between(datum, &9.0, &10.0, 0.25));
|
||||
let datum = uneven_core.sample_interp(7.0);
|
||||
assert!(approx_between(datum, &10.0, &(-5.0), 0.75));
|
||||
|
||||
let datum = uneven_core.sample_interp(2.0);
|
||||
assert!(is_exact(datum, &9.0));
|
||||
let datum = uneven_core.sample_interp(4.0);
|
||||
assert!(is_exact(datum, &10.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chunked_uneven_sample_interp() {
|
||||
let core =
|
||||
ChunkedUnevenCore::new(vec![0.0, 2.0, 8.0], vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 2)
|
||||
.expect("Failed to construct test core");
|
||||
|
||||
let datum = core.sample_interp(-1.0);
|
||||
assert!(is_left_tail(datum));
|
||||
let datum = core.sample_interp(0.0);
|
||||
assert!(is_exact(datum, &[0.0, 1.0]));
|
||||
let datum = core.sample_interp(8.0);
|
||||
assert!(is_exact(datum, &[4.0, 5.0]));
|
||||
let datum = core.sample_interp(10.0);
|
||||
assert!(is_right_tail(datum));
|
||||
|
||||
let datum = core.sample_interp(1.0);
|
||||
assert!(approx_between(datum, &[0.0, 1.0], &[2.0, 3.0], 0.5));
|
||||
let datum = core.sample_interp(3.0);
|
||||
assert!(approx_between(datum, &[2.0, 3.0], &[4.0, 5.0], 1.0 / 6.0));
|
||||
|
||||
let datum = core.sample_interp(2.0);
|
||||
assert!(is_exact(datum, &[2.0, 3.0]));
|
||||
}
|
||||
}
|
||||
|
|
53
crates/bevy_math/src/curve/iterable.rs
Normal file
53
crates/bevy_math/src/curve/iterable.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
//! Iterable curves, which sample in the form of an iterator in order to support `Vec`-like
|
||||
//! output whose length cannot be known statically.
|
||||
|
||||
use super::{ConstantCurve, Interval};
|
||||
|
||||
/// A curve which provides samples in the form of [`Iterator`]s.
|
||||
///
|
||||
/// This is an abstraction that provides an interface for curves which look like `Curve<Vec<T>>`
|
||||
/// but side-stepping issues with allocation on sampling. This happens when the size of an output
|
||||
/// array cannot be known statically.
|
||||
pub trait IterableCurve<T> {
|
||||
/// The interval over which this curve is parametrized.
|
||||
fn domain(&self) -> Interval;
|
||||
|
||||
/// Sample a point on this curve at the parameter value `t`, producing an iterator over values.
|
||||
/// This is the unchecked version of sampling, which should only be used if the sample time `t`
|
||||
/// is already known to lie within the curve's domain.
|
||||
///
|
||||
/// Values sampled from outside of a curve's domain are generally considered invalid; data which
|
||||
/// is nonsensical or otherwise useless may be returned in such a circumstance, and extrapolation
|
||||
/// beyond a curve's domain should not be relied upon.
|
||||
fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T>;
|
||||
|
||||
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
|
||||
/// The parameter `t` is clamped to the domain of the curve.
|
||||
fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
|
||||
let t_clamped = self.domain().clamp(t);
|
||||
self.sample_iter_unchecked(t_clamped)
|
||||
}
|
||||
|
||||
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
|
||||
/// If the parameter `t` does not lie in the curve's domain, `None` is returned.
|
||||
fn sample_iter(&self, t: f32) -> Option<impl Iterator<Item = T>> {
|
||||
if self.domain().contains(t) {
|
||||
Some(self.sample_iter_unchecked(t))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IterableCurve<T> for ConstantCurve<Vec<T>>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
fn sample_iter_unchecked(&self, _t: f32) -> impl Iterator<Item = T> {
|
||||
self.value.iter().cloned()
|
||||
}
|
||||
}
|
|
@ -5,13 +5,14 @@
|
|||
pub mod adaptors;
|
||||
pub mod cores;
|
||||
pub mod interval;
|
||||
pub mod iterable;
|
||||
pub mod sample_curves;
|
||||
|
||||
// bevy_math::curve re-exports all commonly-needed curve-related items.
|
||||
pub use adaptors::*;
|
||||
pub use interval::{interval, Interval};
|
||||
pub use sample_curves::*;
|
||||
|
||||
use adaptors::*;
|
||||
use cores::{EvenCore, UnevenCore};
|
||||
|
||||
use crate::{StableInterpolate, VectorSpace};
|
||||
|
@ -232,13 +233,13 @@ pub trait Curve<T> {
|
|||
/// time `t` and `y` is the sample of `other` at time `t`. The domain of the new curve is the
|
||||
/// intersection of the domains of its constituents. If the domain intersection would be empty,
|
||||
/// an error is returned.
|
||||
fn zip<S, C>(self, other: C) -> Result<ProductCurve<T, S, Self, C>, InvalidIntervalError>
|
||||
fn zip<S, C>(self, other: C) -> Result<ZipCurve<T, S, Self, C>, InvalidIntervalError>
|
||||
where
|
||||
Self: Sized,
|
||||
C: Curve<S> + Sized,
|
||||
{
|
||||
let domain = self.domain().intersect(other.domain())?;
|
||||
Ok(ProductCurve {
|
||||
Ok(ZipCurve {
|
||||
domain,
|
||||
first: self,
|
||||
second: other,
|
||||
|
@ -691,255 +692,6 @@ pub enum ResamplingError {
|
|||
UnboundedDomain,
|
||||
}
|
||||
|
||||
/// A curve with a constant value over its domain.
|
||||
///
|
||||
/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct ConstantCurve<T> {
|
||||
domain: Interval,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T> ConstantCurve<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
/// Create a constant curve, which has the given `domain` and always produces the given `value`
|
||||
/// when sampled.
|
||||
pub fn new(domain: Interval, value: T) -> Self {
|
||||
Self { domain, value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Curve<T> for ConstantCurve<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, _t: f32) -> T {
|
||||
self.value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve defined by a function together with a fixed domain.
|
||||
///
|
||||
/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
|
||||
/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct FunctionCurve<T, F> {
|
||||
domain: Interval,
|
||||
f: F,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, F> FunctionCurve<T, F>
|
||||
where
|
||||
F: Fn(f32) -> T,
|
||||
{
|
||||
/// Create a new curve with the given `domain` from the given `function`. When sampled, the
|
||||
/// `function` is evaluated at the sample time to compute the output.
|
||||
pub fn new(domain: Interval, function: F) -> Self {
|
||||
FunctionCurve {
|
||||
domain,
|
||||
f: function,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> Curve<T> for FunctionCurve<T, F>
|
||||
where
|
||||
F: Fn(f32) -> T,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
(self.f)(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve whose samples are defined by mapping samples from another curve through a
|
||||
/// given function. Curves of this type are produced by [`Curve::map`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct MapCurve<S, T, C, F> {
|
||||
preimage: C,
|
||||
f: F,
|
||||
_phantom: PhantomData<(S, T)>,
|
||||
}
|
||||
|
||||
impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
|
||||
where
|
||||
C: Curve<S>,
|
||||
F: Fn(S) -> T,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.preimage.domain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
(self.f)(self.preimage.sample_unchecked(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve whose sample space is mapped onto that of some base curve's before sampling.
|
||||
/// Curves of this type are produced by [`Curve::reparametrize`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct ReparamCurve<T, C, F> {
|
||||
domain: Interval,
|
||||
base: C,
|
||||
f: F,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
|
||||
where
|
||||
C: Curve<T>,
|
||||
F: Fn(f32) -> f32,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
self.base.sample_unchecked((self.f)(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that has had its domain changed by a linear reparametrization (stretching and scaling).
|
||||
/// Curves of this type are produced by [`Curve::reparametrize_linear`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct LinearReparamCurve<T, C> {
|
||||
/// Invariants: The domain of the inner curve must always be bounded.
|
||||
base: C,
|
||||
/// Invariants: This interval must always be bounded.
|
||||
new_domain: Interval,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<T> for LinearReparamCurve<T, C>
|
||||
where
|
||||
C: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.new_domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
// The invariants imply this unwrap always succeeds.
|
||||
let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
|
||||
self.base.sample_unchecked(f(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that has been reparametrized by another curve, using that curve to transform the
|
||||
/// sample times before sampling. Curves of this type are produced by [`Curve::reparametrize_by_curve`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct CurveReparamCurve<T, C, D> {
|
||||
base: C,
|
||||
reparam_curve: D,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
|
||||
where
|
||||
C: Curve<T>,
|
||||
D: Curve<f32>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.reparam_curve.domain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
let sample_time = self.reparam_curve.sample_unchecked(t);
|
||||
self.base.sample_unchecked(sample_time)
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that is the graph of another curve over its parameter space. Curves of this type are
|
||||
/// produced by [`Curve::graph`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct GraphCurve<T, C> {
|
||||
base: C,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
|
||||
where
|
||||
C: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.base.domain()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> (f32, T) {
|
||||
(t, self.base.sample_unchecked(t))
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that combines the output data from two constituent curves into a tuple output. Curves
|
||||
/// of this type are produced by [`Curve::zip`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
pub struct ProductCurve<S, T, C, D> {
|
||||
domain: Interval,
|
||||
first: C,
|
||||
second: D,
|
||||
_phantom: PhantomData<(S, T)>,
|
||||
}
|
||||
|
||||
impl<S, T, C, D> Curve<(S, T)> for ProductCurve<S, T, C, D>
|
||||
where
|
||||
C: Curve<S>,
|
||||
D: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
self.domain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> (S, T) {
|
||||
(
|
||||
self.first.sample_unchecked(t),
|
||||
self.second.sample_unchecked(t),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Curve`] that constantly takes the given `value` over the given `domain`.
|
||||
pub fn constant_curve<T: Clone>(domain: Interval, value: T) -> ConstantCurve<T> {
|
||||
ConstantCurve { domain, value }
|
||||
|
|
|
@ -49,79 +49,79 @@ fn setup(
|
|||
|
||||
// Creating the animation
|
||||
let mut animation = AnimationClip::default();
|
||||
// A curve can modify a single part of a transform, here the translation
|
||||
// A curve can modify a single part of a transform: here, the translation.
|
||||
let planet_animation_target_id = AnimationTargetId::from_name(&planet);
|
||||
animation.add_curve_to_target(
|
||||
planet_animation_target_id,
|
||||
VariableCurve::linear::<TranslationKeyframes>(
|
||||
[0.0, 1.0, 2.0, 3.0, 4.0],
|
||||
[
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(-1.0, 0.0, 1.0),
|
||||
Vec3::new(-1.0, 0.0, -1.0),
|
||||
Vec3::new(1.0, 0.0, -1.0),
|
||||
// in case seamless looping is wanted, the last keyframe should
|
||||
// be the same as the first one
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
],
|
||||
),
|
||||
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(-1.0, 0.0, 1.0),
|
||||
Vec3::new(-1.0, 0.0, -1.0),
|
||||
Vec3::new(1.0, 0.0, -1.0),
|
||||
// in case seamless looping is wanted, the last keyframe should
|
||||
// be the same as the first one
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
]))
|
||||
.map(TranslationCurve)
|
||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
||||
);
|
||||
// Or it can modify the rotation of the transform.
|
||||
// To find the entity to modify, the hierarchy will be traversed looking for
|
||||
// an entity with the right name at each level
|
||||
// an entity with the right name at each level.
|
||||
let orbit_controller_animation_target_id =
|
||||
AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter());
|
||||
animation.add_curve_to_target(
|
||||
orbit_controller_animation_target_id,
|
||||
VariableCurve::linear::<RotationKeyframes>(
|
||||
[0.0, 1.0, 2.0, 3.0, 4.0],
|
||||
[
|
||||
Quat::IDENTITY,
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||
Quat::IDENTITY,
|
||||
],
|
||||
),
|
||||
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
||||
Quat::IDENTITY,
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||
Quat::IDENTITY,
|
||||
]))
|
||||
.map(RotationCurve)
|
||||
.expect("Failed to build rotation curve"),
|
||||
);
|
||||
// If a curve in an animation is shorter than the other, it will not repeat
|
||||
// until all other curves are finished. In that case, another animation should
|
||||
// be created for each part that would have a different duration / period
|
||||
// be created for each part that would have a different duration / period.
|
||||
let satellite_animation_target_id = AnimationTargetId::from_names(
|
||||
[planet.clone(), orbit_controller.clone(), satellite.clone()].iter(),
|
||||
);
|
||||
animation.add_curve_to_target(
|
||||
satellite_animation_target_id,
|
||||
VariableCurve::linear::<ScaleKeyframes>(
|
||||
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0],
|
||||
[
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
],
|
||||
),
|
||||
UnevenSampleAutoCurve::new(
|
||||
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]
|
||||
.into_iter()
|
||||
.zip([
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
Vec3::splat(1.2),
|
||||
Vec3::splat(0.8),
|
||||
]),
|
||||
)
|
||||
.map(ScaleCurve)
|
||||
.expect("Failed to build scale curve"),
|
||||
);
|
||||
// There can be more than one curve targeting the same entity path
|
||||
// There can be more than one curve targeting the same entity path.
|
||||
animation.add_curve_to_target(
|
||||
AnimationTargetId::from_names(
|
||||
[planet.clone(), orbit_controller.clone(), satellite.clone()].iter(),
|
||||
),
|
||||
VariableCurve::linear::<RotationKeyframes>(
|
||||
[0.0, 1.0, 2.0, 3.0, 4.0],
|
||||
[
|
||||
Quat::IDENTITY,
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||
Quat::IDENTITY,
|
||||
],
|
||||
),
|
||||
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
||||
Quat::IDENTITY,
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||
Quat::IDENTITY,
|
||||
]))
|
||||
.map(RotationCurve)
|
||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
||||
);
|
||||
|
||||
// Create the animation graph
|
||||
|
|
|
@ -77,24 +77,34 @@ impl AnimationInfo {
|
|||
|
||||
// Create a curve that animates font size.
|
||||
//
|
||||
// `VariableCurve::linear` is just a convenience constructor; it's also
|
||||
// possible to initialize the structure manually.
|
||||
// The curve itself is a `Curve<f32>`, and `f32` is `FontSizeProperty::Property`,
|
||||
// which is required by `AnimatableCurve::from_curve`.
|
||||
animation_clip.add_curve_to_target(
|
||||
animation_target_id,
|
||||
VariableCurve::linear::<AnimatablePropertyKeyframes<FontSizeProperty>>(
|
||||
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0],
|
||||
[24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0],
|
||||
),
|
||||
AnimatableKeyframeCurve::new(
|
||||
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
|
||||
.into_iter()
|
||||
.zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]),
|
||||
)
|
||||
.map(AnimatableCurve::<FontSizeProperty, _>::from_curve)
|
||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
||||
);
|
||||
|
||||
// Create a curve that animates font color. Note that this should have
|
||||
// the same time duration as the previous curve.
|
||||
//
|
||||
// Similar to the above, the curve itself is a `Curve<Srgba>`, and `Srgba` is
|
||||
// `TextColorProperty::Property`, which is required by the `from_curve` method.
|
||||
animation_clip.add_curve_to_target(
|
||||
animation_target_id,
|
||||
VariableCurve::linear::<AnimatablePropertyKeyframes<TextColorProperty>>(
|
||||
[0.0, 1.0, 2.0, 3.0],
|
||||
[Srgba::RED, Srgba::GREEN, Srgba::BLUE, Srgba::RED],
|
||||
),
|
||||
AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([
|
||||
Srgba::RED,
|
||||
Srgba::GREEN,
|
||||
Srgba::BLUE,
|
||||
Srgba::RED,
|
||||
]))
|
||||
.map(AnimatableCurve::<TextColorProperty, _>::from_curve)
|
||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
||||
);
|
||||
|
||||
// Save our animation clip as an asset.
|
||||
|
|
Loading…
Reference in a new issue