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:
Matty 2024-09-30 15:56:55 -04:00 committed by GitHub
parent f3e8ae03cd
commit 429987ebf8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 2023 additions and 1472 deletions

View file

@ -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,
{

View 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)?,
})
}
}

View 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,
)
})
}

View file

@ -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)
}
}

View file

@ -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);
}
}
}

View file

@ -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!(

View file

@ -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>

View file

@ -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]));
}
}

View 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()
}
}

View file

@ -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 }

View file

@ -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

View file

@ -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.