AnimatedField and Rework Evaluators (#16484)

# Objective

Animating component fields requires too much boilerplate at the moment:

```rust
#[derive(Reflect)]
struct FontSizeProperty;

impl AnimatableProperty for FontSizeProperty {
    type Component = TextFont;

    type Property = f32;

    fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
        Some(&mut component.font_size)
    }
}

animation_clip.add_curve_to_target(
    animation_target_id,
    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"),
);
```

## Solution

This adds `AnimatedField` and an `animated_field!` macro, enabling the
following:

```rust
animation_clip.add_curve_to_target(
    animation_target_id,
    AnimatableCurve::new(
        animated_field!(TextFont::font_size),
        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]),
        )
        .expect(
            "should be able to build translation curve because we pass in valid samples",
        ),
    ),
);
```

This required reworking the internals a bit, namely stripping out a lot
of the `Reflect` usage, as that implementation was fundamentally
incompatible with the `AnimatedField` pattern. `Reflect` was being used
in this context just to downcast traits. But we can get downcasting
behavior without the `Reflect` requirement by implementing `Downcast`
for `AnimationCurveEvaluator`.

This also reworks "evaluator identity" to support either a (Component /
Field) pair, or a TypeId. This allows properties to reuse evaluators,
even if they have different accessor methods. The "contract" here is
that for a given (Component / Field) pair, the accessor will return the
same value. Fields are identified by their Reflect-ed field index. The
(TypeId, usize) is prehashed and cached to optimize for lookup speed.

This removes the built-in hard-coded TranslationCurve / RotationCurve /
ScaleCurve in favor of AnimatableField.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
Carter Anderson 2024-11-27 14:19:55 -08:00 committed by GitHub
parent a6e13977d5
commit af10aa38aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 539 additions and 746 deletions

View File

@ -33,6 +33,7 @@ petgraph = { version = "0.6", features = ["serde-1"] }
ron = "0.8" ron = "0.8"
serde = "1" serde = "1"
blake3 = { version = "1.0" } blake3 = { version = "1.0" }
downcast-rs = "1.2.0"
derive_more = { version = "1", default-features = false, features = [ derive_more = { version = "1", default-features = false, features = [
"error", "error",
"from", "from",

View File

@ -22,30 +22,32 @@
//! //!
//! For instance, let's imagine that we want to use the `Vec3` output //! 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 //! 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 //! the adaptor [`AnimatableCurve`], which wraps any [`Curve`] and [`AnimatableProperty`] and turns it into an
//! [`AnimationCurve`] that will use the given curve to animate the entity's translation: //! [`AnimationCurve`] that will use the given curve to animate the entity's property:
//! //!
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve}; //! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_math::vec3; //! # use bevy_math::vec3;
//! # use bevy_animation::animation_curves::*; //! # use bevy_transform::components::Transform;
//! # use bevy_animation::{animated_field, animation_curves::*};
//! # let wobble_curve = FunctionCurve::new( //! # let wobble_curve = FunctionCurve::new(
//! # Interval::UNIT, //! # Interval::UNIT,
//! # |t| vec3(t.cos(), 0.0, 0.0) //! # |t| vec3(t.cos(), 0.0, 0.0)
//! # ); //! # );
//! let wobble_animation = TranslationCurve(wobble_curve); //! let wobble_animation = AnimatableCurve::new(animated_field!(Transform::translation), wobble_curve);
//! //!
//! And finally, this `AnimationCurve` needs to be added to an [`AnimationClip`] in order to //! And finally, this [`AnimationCurve`] needs to be added to an [`AnimationClip`] in order to
//! actually animate something. This is what that looks like: //! actually animate something. This is what that looks like:
//! //!
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve}; //! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_animation::{AnimationClip, AnimationTargetId, animation_curves::*}; //! # use bevy_animation::{AnimationClip, AnimationTargetId, animated_field, animation_curves::*};
//! # use bevy_transform::components::Transform;
//! # use bevy_core::Name; //! # use bevy_core::Name;
//! # use bevy_math::vec3; //! # use bevy_math::vec3;
//! # let wobble_curve = FunctionCurve::new( //! # let wobble_curve = FunctionCurve::new(
//! # Interval::UNIT, //! # Interval::UNIT,
//! # |t| { vec3(t.cos(), 0.0, 0.0) }, //! # |t| { vec3(t.cos(), 0.0, 0.0) },
//! # ); //! # );
//! # let wobble_animation = TranslationCurve(wobble_curve); //! # let wobble_animation = AnimatableCurve::new(animated_field!(Transform::translation), wobble_curve);
//! # let animation_target_id = AnimationTargetId::from(&Name::new("Test")); //! # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
//! let mut animation_clip = AnimationClip::default(); //! let mut animation_clip = AnimationClip::default();
//! animation_clip.add_curve_to_target( //! animation_clip.add_curve_to_target(
@ -59,22 +61,27 @@
//! a [`Curve`], which produces time-related data of some kind, to an [`AnimationCurve`], which //! a [`Curve`], which produces time-related data of some kind, to an [`AnimationCurve`], which
//! knows how to apply that data to an entity. //! knows how to apply that data to an entity.
//! //!
//! ## `Transform` //! ## Animated Fields
//! //!
//! [`Transform`] is special and has its own adaptors: //! The [`animated_field`] macro (which returns an [`AnimatedField`]), in combination with [`AnimatableCurve`]
//! - [`TranslationCurve`], which uses `Vec3` output to animate [`Transform::translation`] //! is the easiest way to make an animation curve (see the example above).
//! - [`RotationCurve`], which uses `Quat` output to animate [`Transform::rotation`]
//! - [`ScaleCurve`], which uses `Vec3` output to animate [`Transform::scale`]
//! //!
//! ## Animatable properties //! This will select a field on a component and pass it to a [`Curve`] with a type that matches the field.
//! //!
//! Animation of arbitrary components can be accomplished using [`AnimatableProperty`] in //! ## Animatable Properties
//!
//! Animation of arbitrary aspects of entities can be accomplished using [`AnimatableProperty`] in
//! conjunction with [`AnimatableCurve`]. See the documentation [there] for details. //! conjunction with [`AnimatableCurve`]. See the documentation [there] for details.
//! //!
//! ## Custom [`AnimationCurve`] and [`AnimationCurveEvaluator`]
//!
//! This is the lowest-level option with the most control, but it is also the most complicated.
//!
//! [using a function]: bevy_math::curve::FunctionCurve //! [using a function]: bevy_math::curve::FunctionCurve
//! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation //! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation
//! [`AnimationClip`]: crate::AnimationClip //! [`AnimationClip`]: crate::AnimationClip
//! [there]: AnimatableProperty //! [there]: AnimatableProperty
//! [`animated_field`]: crate::animated_field
use core::{ use core::{
any::TypeId, any::TypeId,
@ -82,24 +89,22 @@ use core::{
marker::PhantomData, marker::PhantomData,
}; };
use bevy_ecs::{component::Component, world::Mut}; use bevy_ecs::component::Component;
use bevy_math::{ use bevy_math::curve::{
curve::{ cores::{UnevenCore, UnevenCoreError},
cores::{UnevenCore, UnevenCoreError}, iterable::IterableCurve,
iterable::IterableCurve, Curve, Interval,
Curve, Interval,
},
Quat, Vec3,
}; };
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypePath}; use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed};
use bevy_render::mesh::morph::MorphWeights; use bevy_render::mesh::morph::MorphWeights;
use bevy_transform::prelude::Transform;
use crate::{ use crate::{
graph::AnimationNodeIndex, graph::AnimationNodeIndex,
prelude::{Animatable, BlendInput}, prelude::{Animatable, BlendInput},
AnimationEntityMut, AnimationEvaluationError, AnimationEntityMut, AnimationEvaluationError,
}; };
use bevy_utils::Hashed;
use downcast_rs::{impl_downcast, Downcast};
/// A value on a component that Bevy can animate. /// A value on a component that Bevy can animate.
/// ///
@ -109,68 +114,154 @@ use crate::{
/// to define the animation itself). /// to define the animation itself).
/// For example, in order to animate field of view, you might use: /// For example, in order to animate field of view, you might use:
/// ///
/// # use bevy_animation::prelude::AnimatableProperty; /// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
/// # use bevy_reflect::Reflect; /// # use bevy_reflect::Reflect;
/// # use std::any::TypeId;
/// # use bevy_render::camera::PerspectiveProjection; /// # use bevy_render::camera::PerspectiveProjection;
/// #[derive(Reflect)] /// #[derive(Reflect)]
/// struct FieldOfViewProperty; /// struct FieldOfViewProperty;
/// ///
/// impl AnimatableProperty for FieldOfViewProperty { /// impl AnimatableProperty for FieldOfViewProperty {
/// type Component = PerspectiveProjection;
/// type Property = f32; /// type Property = f32;
/// fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { /// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
/// Some(&mut component.fov) /// let component = entity
/// .get_mut::<PerspectiveProjection>()
/// .ok_or(
/// AnimationEvaluationError::ComponentNotPresent(
/// TypeId::of::<PerspectiveProjection>()
/// )
/// )?
/// .into_inner();
/// Ok(&mut component.fov)
/// }
///
/// fn evaluator_id(&self) -> EvaluatorId {
/// EvaluatorId::Type(TypeId::of::<Self>())
/// } /// }
/// } /// }
/// ///
/// You can then create an [`AnimationClip`] to animate this property like so: /// You can then create an [`AnimationClip`] to animate this property like so:
/// ///
/// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve}; /// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve}; /// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
/// # use bevy_core::Name; /// # use bevy_core::Name;
/// # use bevy_reflect::Reflect; /// # use bevy_reflect::Reflect;
/// # use bevy_render::camera::PerspectiveProjection; /// # use bevy_render::camera::PerspectiveProjection;
/// # use std::any::TypeId;
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test")); /// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
/// # #[derive(Reflect)] /// # #[derive(Reflect, Clone)]
/// # struct FieldOfViewProperty; /// # struct FieldOfViewProperty;
/// # impl AnimatableProperty for FieldOfViewProperty { /// # impl AnimatableProperty for FieldOfViewProperty {
/// # type Component = PerspectiveProjection; /// # type Property = f32;
/// # type Property = f32; /// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
/// # fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { /// # let component = entity
/// # Some(&mut component.fov) /// # .get_mut::<PerspectiveProjection>()
/// # } /// # .ok_or(
/// # AnimationEvaluationError::ComponentNotPresent(
/// # TypeId::of::<PerspectiveProjection>()
/// # )
/// # )?
/// # .into_inner();
/// # Ok(&mut component.fov)
/// # }
/// # fn evaluator_id(&self) -> EvaluatorId {
/// # EvaluatorId::Type(TypeId::of::<Self>())
/// # }
/// # } /// # }
/// let mut animation_clip = AnimationClip::default(); /// let mut animation_clip = AnimationClip::default();
/// animation_clip.add_curve_to_target( /// animation_clip.add_curve_to_target(
/// animation_target_id, /// animation_target_id,
/// AnimatableKeyframeCurve::new( /// AnimatableCurve::new(
/// [ /// FieldOfViewProperty,
/// AnimatableKeyframeCurve::new([
/// (0.0, core::f32::consts::PI / 4.0), /// (0.0, core::f32::consts::PI / 4.0),
/// (1.0, core::f32::consts::PI / 3.0), /// (1.0, core::f32::consts::PI / 3.0),
/// ] /// ]).expect("Failed to create font size curve")
/// ) /// )
/// .map(AnimatableCurve::<FieldOfViewProperty, _>::from_curve)
/// .expect("Failed to create font size curve")
/// ); /// );
/// ///
/// Here, the use of [`AnimatableKeyframeCurve`] creates a curve out of the given keyframe time-value /// 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 /// pairs, using the [`Animatable`] implementation of `f32` to interpolate between them. The
/// invocation of [`AnimatableCurve::from_curve`] with `FieldOfViewProperty` indicates that the `f32` /// invocation of [`AnimatableCurve::new`] with `FieldOfViewProperty` indicates that the `f32`
/// output from that curve is to be used to animate the font size of a `PerspectiveProjection` component (as /// output from that curve is to be used to animate the font size of a `PerspectiveProjection` component (as
/// configured above). /// configured above).
/// ///
/// [`AnimationClip`]: crate::AnimationClip /// [`AnimationClip`]: crate::AnimationClip
pub trait AnimatableProperty: Reflect + TypePath { pub trait AnimatableProperty: Send + Sync + 'static {
/// The type of the component that the property lives on. /// The animated property type.
type Component: Component; type Property: Animatable;
/// The type of the property to be animated. /// Retrieves the property from the given `entity`.
type Property: Animatable + FromReflect + Reflectable + Clone + Sync + Debug; fn get_mut<'a>(
&self,
entity: &'a mut AnimationEntityMut,
) -> Result<&'a mut Self::Property, AnimationEvaluationError>;
/// Given a reference to the component, returns a reference to the property. /// The [`EvaluatorId`] used to look up the [`AnimationCurveEvaluator`] for this [`AnimatableProperty`].
/// For a given animated property, this ID should always be the same to allow things like animation blending to occur.
fn evaluator_id(&self) -> EvaluatorId;
}
/// A [`Component`] field that can be animated, defined by a function that reads the component and returns
/// the accessed field / property.
///
/// The best way to create an instance of this type is via the [`animated_field`] macro.
///
/// `C` is the component being animated, `A` is the type of the [`Animatable`] field on the component, and `F` is an accessor
/// function that accepts a reference to `C` and retrieves the field `A`.
///
/// [`animated_field`]: crate::animated_field
#[derive(Clone)]
pub struct AnimatedField<C, A, F: Fn(&mut C) -> &mut A> {
func: F,
/// A pre-hashed (component-type-id, reflected-field-index) pair, uniquely identifying a component field
evaluator_id: Hashed<(TypeId, usize)>,
marker: PhantomData<(C, A)>,
}
impl<C, A, F> AnimatableProperty for AnimatedField<C, A, F>
where
C: Component,
A: Animatable + Clone + Sync + Debug,
F: Fn(&mut C) -> &mut A + Send + Sync + 'static,
{
type Property = A;
fn get_mut<'a>(
&self,
entity: &'a mut AnimationEntityMut,
) -> Result<&'a mut A, AnimationEvaluationError> {
let c = entity
.get_mut::<C>()
.ok_or_else(|| AnimationEvaluationError::ComponentNotPresent(TypeId::of::<C>()))?;
Ok((self.func)(c.into_inner()))
}
fn evaluator_id(&self) -> EvaluatorId {
EvaluatorId::ComponentField(&self.evaluator_id)
}
}
impl<C: Typed, P, F: Fn(&mut C) -> &mut P + 'static> AnimatedField<C, P, F> {
/// Creates a new instance of [`AnimatedField`]. This operates under the assumption that
/// `C` is a reflect-able struct with named fields, and that `field_name` is a valid field on that struct.
/// ///
/// If the property couldn't be found, returns `None`. /// # Panics
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property>; /// If the type of `C` is not a struct with named fields or if the `field_name` does not exist.
pub fn new_unchecked(field_name: &str, func: F) -> Self {
let TypeInfo::Struct(struct_info) = C::type_info() else {
panic!("Only structs are supported in `AnimatedField::new_unchecked`")
};
let field_index = struct_info
.index_of(field_name)
.expect("Field name should exist");
Self {
func,
evaluator_id: Hashed::new((TypeId::of::<C>(), field_index)),
marker: PhantomData,
}
}
} }
/// This trait collects the additional requirements on top of [`Curve<T>`] needed for a /// This trait collects the additional requirements on top of [`Curve<T>`] needed for a
@ -187,12 +278,14 @@ impl<T, C> AnimationCompatibleCurve<T> for C where C: Curve<T> + Debug + Clone +
#[derive(Reflect, FromReflect)] #[derive(Reflect, FromReflect)]
#[reflect(from_reflect = false)] #[reflect(from_reflect = false)]
pub struct AnimatableCurve<P, C> { pub struct AnimatableCurve<P, C> {
/// The property selector, which defines what component to access and how to access
/// a property on that component.
pub property: P,
/// The inner [curve] whose values are used to animate the property. /// The inner [curve] whose values are used to animate the property.
/// ///
/// [curve]: Curve /// [curve]: Curve
pub curve: C, pub curve: C,
#[reflect(ignore)]
_phantom: PhantomData<P>,
} }
/// An [`AnimatableCurveEvaluator`] for [`AnimatableProperty`] instances. /// An [`AnimatableCurveEvaluator`] for [`AnimatableProperty`] instances.
@ -200,13 +293,9 @@ pub struct AnimatableCurve<P, C> {
/// You shouldn't ordinarily need to instantiate one of these manually. Bevy /// You shouldn't ordinarily need to instantiate one of these manually. Bevy
/// will automatically do so when you use an [`AnimatableCurve`] instance. /// will automatically do so when you use an [`AnimatableCurve`] instance.
#[derive(Reflect)] #[derive(Reflect)]
pub struct AnimatableCurveEvaluator<P> pub struct AnimatableCurveEvaluator<A: Animatable> {
where evaluator: BasicAnimationCurveEvaluator<A>,
P: AnimatableProperty, property: Box<dyn AnimatableProperty<Property = A>>,
{
evaluator: BasicAnimationCurveEvaluator<P::Property>,
#[reflect(ignore)]
phantom: PhantomData<P>,
} }
impl<P, C> AnimatableCurve<P, C> impl<P, C> AnimatableCurve<P, C>
@ -218,22 +307,20 @@ where
/// valued in an [animatable property]. /// valued in an [animatable property].
/// ///
/// [animatable property]: AnimatableProperty::Property /// [animatable property]: AnimatableProperty::Property
pub fn from_curve(curve: C) -> Self { pub fn new(property: P, curve: C) -> Self {
Self { Self { property, curve }
curve,
_phantom: PhantomData,
}
} }
} }
impl<P, C> Clone for AnimatableCurve<P, C> impl<P, C> Clone for AnimatableCurve<P, C>
where where
C: Clone, C: Clone,
P: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
curve: self.curve.clone(), curve: self.curve.clone(),
_phantom: PhantomData, property: self.property.clone(),
} }
} }
} }
@ -249,10 +336,10 @@ where
} }
} }
impl<P, C> AnimationCurve for AnimatableCurve<P, C> impl<P: Send + Sync + 'static, C> AnimationCurve for AnimatableCurve<P, C>
where where
P: AnimatableProperty, P: AnimatableProperty + Clone,
C: AnimationCompatibleCurve<P::Property>, C: AnimationCompatibleCurve<P::Property> + Clone,
{ {
fn clone_value(&self) -> Box<dyn AnimationCurve> { fn clone_value(&self) -> Box<dyn AnimationCurve> {
Box::new(self.clone()) Box::new(self.clone())
@ -262,14 +349,14 @@ where
self.curve.domain() self.curve.domain()
} }
fn evaluator_type(&self) -> TypeId { fn evaluator_id(&self) -> EvaluatorId {
TypeId::of::<AnimatableCurveEvaluator<P>>() self.property.evaluator_id()
} }
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> { fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
Box::new(AnimatableCurveEvaluator { Box::new(AnimatableCurveEvaluator::<P::Property> {
evaluator: BasicAnimationCurveEvaluator::default(), evaluator: BasicAnimationCurveEvaluator::default(),
phantom: PhantomData::<P>, property: Box::new(self.property.clone()),
}) })
} }
@ -280,8 +367,8 @@ where
weight: f32, weight: f32,
graph_node: AnimationNodeIndex, graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> { ) -> Result<(), AnimationEvaluationError> {
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator)) let curve_evaluator = curve_evaluator
.downcast_mut::<AnimatableCurveEvaluator<P>>() .downcast_mut::<AnimatableCurveEvaluator<P::Property>>()
.unwrap(); .unwrap();
let value = self.curve.sample_clamped(t); let value = self.curve.sample_clamped(t);
curve_evaluator curve_evaluator
@ -296,10 +383,7 @@ where
} }
} }
impl<P> AnimationCurveEvaluator for AnimatableCurveEvaluator<P> impl<A: Animatable> AnimationCurveEvaluator for AnimatableCurveEvaluator<A> {
where
P: AnimatableProperty,
{
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> { fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ false) self.evaluator.combine(graph_node, /*additive=*/ false)
} }
@ -318,310 +402,14 @@ where
fn commit<'a>( fn commit<'a>(
&mut self, &mut self,
_: Option<Mut<'a, Transform>>,
mut entity: AnimationEntityMut<'a>, mut entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> { ) -> Result<(), AnimationEvaluationError> {
let mut component = entity.get_mut::<P::Component>().ok_or_else(|| { let property = self.property.get_mut(&mut entity)?;
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<P::Component>())
})?;
let property = P::get_mut(&mut component)
.ok_or_else(|| AnimationEvaluationError::PropertyNotPresent(TypeId::of::<P>()))?;
*property = self *property = self
.evaluator .evaluator
.stack .stack
.pop() .pop()
.ok_or_else(inconsistent::<AnimatableCurveEvaluator<P>>)? .ok_or_else(inconsistent::<AnimatableCurveEvaluator<A>>)?
.value;
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);
/// An [`AnimationCurveEvaluator`] for use with [`TranslationCurve`]s.
///
/// You shouldn't need to instantiate this manually; Bevy will automatically do
/// so.
#[derive(Reflect)]
pub struct TranslationCurveEvaluator {
evaluator: BasicAnimationCurveEvaluator<Vec3>,
}
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 evaluator_type(&self) -> TypeId {
TypeId::of::<TranslationCurveEvaluator>()
}
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
Box::new(TranslationCurveEvaluator {
evaluator: BasicAnimationCurveEvaluator::default(),
})
}
fn apply(
&self,
curve_evaluator: &mut dyn AnimationCurveEvaluator,
t: f32,
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
.downcast_mut::<TranslationCurveEvaluator>()
.unwrap();
let value = self.0.sample_clamped(t);
curve_evaluator
.evaluator
.stack
.push(BasicAnimationCurveEvaluatorStackElement {
value,
weight,
graph_node,
});
Ok(())
}
}
impl AnimationCurveEvaluator for TranslationCurveEvaluator {
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ false)
}
fn add(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ true)
}
fn push_blend_register(
&mut self,
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
self.evaluator.push_blend_register(weight, graph_node)
}
fn commit<'a>(
&mut self,
transform: Option<Mut<'a, Transform>>,
_: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> {
let mut component = transform.ok_or_else(|| {
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
})?;
component.translation = self
.evaluator
.stack
.pop()
.ok_or_else(inconsistent::<TranslationCurveEvaluator>)?
.value;
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);
/// An [`AnimationCurveEvaluator`] for use with [`RotationCurve`]s.
///
/// You shouldn't need to instantiate this manually; Bevy will automatically do
/// so.
#[derive(Reflect)]
pub struct RotationCurveEvaluator {
evaluator: BasicAnimationCurveEvaluator<Quat>,
}
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 evaluator_type(&self) -> TypeId {
TypeId::of::<RotationCurveEvaluator>()
}
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
Box::new(RotationCurveEvaluator {
evaluator: BasicAnimationCurveEvaluator::default(),
})
}
fn apply(
&self,
curve_evaluator: &mut dyn AnimationCurveEvaluator,
t: f32,
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
.downcast_mut::<RotationCurveEvaluator>()
.unwrap();
let value = self.0.sample_clamped(t);
curve_evaluator
.evaluator
.stack
.push(BasicAnimationCurveEvaluatorStackElement {
value,
weight,
graph_node,
});
Ok(())
}
}
impl AnimationCurveEvaluator for RotationCurveEvaluator {
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ false)
}
fn add(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ true)
}
fn push_blend_register(
&mut self,
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
self.evaluator.push_blend_register(weight, graph_node)
}
fn commit<'a>(
&mut self,
transform: Option<Mut<'a, Transform>>,
_: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> {
let mut component = transform.ok_or_else(|| {
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
})?;
component.rotation = self
.evaluator
.stack
.pop()
.ok_or_else(inconsistent::<RotationCurveEvaluator>)?
.value;
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);
/// An [`AnimationCurveEvaluator`] for use with [`ScaleCurve`]s.
///
/// You shouldn't need to instantiate this manually; Bevy will automatically do
/// so.
#[derive(Reflect)]
pub struct ScaleCurveEvaluator {
evaluator: BasicAnimationCurveEvaluator<Vec3>,
}
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 evaluator_type(&self) -> TypeId {
TypeId::of::<ScaleCurveEvaluator>()
}
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
Box::new(ScaleCurveEvaluator {
evaluator: BasicAnimationCurveEvaluator::default(),
})
}
fn apply(
&self,
curve_evaluator: &mut dyn AnimationCurveEvaluator,
t: f32,
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
.downcast_mut::<ScaleCurveEvaluator>()
.unwrap();
let value = self.0.sample_clamped(t);
curve_evaluator
.evaluator
.stack
.push(BasicAnimationCurveEvaluatorStackElement {
value,
weight,
graph_node,
});
Ok(())
}
}
impl AnimationCurveEvaluator for ScaleCurveEvaluator {
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ false)
}
fn add(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ true)
}
fn push_blend_register(
&mut self,
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
self.evaluator.push_blend_register(weight, graph_node)
}
fn commit<'a>(
&mut self,
transform: Option<Mut<'a, Transform>>,
_: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> {
let mut component = transform.ok_or_else(|| {
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
})?;
component.scale = self
.evaluator
.stack
.pop()
.ok_or_else(inconsistent::<ScaleCurveEvaluator>)?
.value; .value;
Ok(()) Ok(())
} }
@ -683,8 +471,8 @@ where
self.0.domain() self.0.domain()
} }
fn evaluator_type(&self) -> TypeId { fn evaluator_id(&self) -> EvaluatorId {
TypeId::of::<WeightsCurveEvaluator>() EvaluatorId::Type(TypeId::of::<WeightsCurveEvaluator>())
} }
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> { fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
@ -704,7 +492,7 @@ where
weight: f32, weight: f32,
graph_node: AnimationNodeIndex, graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> { ) -> Result<(), AnimationEvaluationError> {
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator)) let curve_evaluator = curve_evaluator
.downcast_mut::<WeightsCurveEvaluator>() .downcast_mut::<WeightsCurveEvaluator>()
.unwrap(); .unwrap();
@ -802,7 +590,6 @@ impl AnimationCurveEvaluator for WeightsCurveEvaluator {
fn commit<'a>( fn commit<'a>(
&mut self, &mut self,
_: Option<Mut<'a, Transform>>,
mut entity: AnimationEntityMut<'a>, mut entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> { ) -> Result<(), AnimationEvaluationError> {
if self.stack_morph_target_weights.is_empty() { if self.stack_morph_target_weights.is_empty() {
@ -968,7 +755,7 @@ where
/// mutated in the implementation of [`apply`]. /// mutated in the implementation of [`apply`].
/// ///
/// [`apply`]: AnimationCurve::apply /// [`apply`]: AnimationCurve::apply
pub trait AnimationCurve: Reflect + Debug + Send + Sync { pub trait AnimationCurve: Debug + Send + Sync + 'static {
/// Returns a boxed clone of this value. /// Returns a boxed clone of this value.
fn clone_value(&self) -> Box<dyn AnimationCurve>; fn clone_value(&self) -> Box<dyn AnimationCurve>;
@ -979,14 +766,14 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
/// ///
/// This must match the type returned by [`Self::create_evaluator`]. It must /// This must match the type returned by [`Self::create_evaluator`]. It must
/// be a single type that doesn't depend on the type of the curve. /// be a single type that doesn't depend on the type of the curve.
fn evaluator_type(&self) -> TypeId; fn evaluator_id(&self) -> EvaluatorId;
/// Returns a newly-instantiated [`AnimationCurveEvaluator`] for use with /// Returns a newly-instantiated [`AnimationCurveEvaluator`] for use with
/// this curve. /// this curve.
/// ///
/// All curve types must return the same type of /// All curve types must return the same type of
/// [`AnimationCurveEvaluator`]. The returned value must match the type /// [`AnimationCurveEvaluator`]. The returned value must match the type
/// returned by [`Self::evaluator_type`]. /// returned by [`Self::evaluator_id`].
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator>; fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator>;
/// Samples the curve at the given time `t`, and pushes the sampled value /// Samples the curve at the given time `t`, and pushes the sampled value
@ -996,7 +783,7 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
/// [`Self::create_evaluator`], upcast to an `&mut dyn /// [`Self::create_evaluator`], upcast to an `&mut dyn
/// AnimationCurveEvaluator`. Typically, implementations of [`Self::apply`] /// AnimationCurveEvaluator`. Typically, implementations of [`Self::apply`]
/// will want to downcast the `curve_evaluator` parameter to the concrete /// will want to downcast the `curve_evaluator` parameter to the concrete
/// type [`Self::evaluator_type`] in order to push values of the appropriate /// type [`Self::evaluator_id`] in order to push values of the appropriate
/// type onto its evaluation stack. /// type onto its evaluation stack.
/// ///
/// Be sure not to confuse the `t` and `weight` values. The former /// Be sure not to confuse the `t` and `weight` values. The former
@ -1012,6 +799,22 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
) -> Result<(), AnimationEvaluationError>; ) -> Result<(), AnimationEvaluationError>;
} }
/// The [`EvaluatorId`] is used to look up the [`AnimationCurveEvaluator`] for an [`AnimatableProperty`].
/// For a given animated property, this ID should always be the same to allow things like animation blending to occur.
#[derive(Clone)]
pub enum EvaluatorId<'a> {
/// Corresponds to a specific field on a specific component type.
/// The `TypeId` should correspond to the component type, and the `usize`
/// should correspond to the Reflect-ed field index of the field.
//
// IMPLEMENTATION NOTE: The Hashed<(TypeId, usize) is intentionally cheap to clone, as it will be cloned per frame by the evaluator
// Switching the field index `usize` for something like a field name `String` would probably be too expensive to justify
ComponentField(&'a Hashed<(TypeId, usize)>),
/// Corresponds to a custom property of a given type. This should be the [`TypeId`]
/// of the custom [`AnimatableProperty`].
Type(TypeId),
}
/// A low-level trait for use in [`crate::VariableCurve`] that provides fine /// A low-level trait for use in [`crate::VariableCurve`] that provides fine
/// control over how animations are evaluated. /// control over how animations are evaluated.
/// ///
@ -1031,7 +834,9 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
/// translation keyframes. The stack stores intermediate values generated while /// translation keyframes. The stack stores intermediate values generated while
/// evaluating the [`crate::graph::AnimationGraph`], while the blend register /// evaluating the [`crate::graph::AnimationGraph`], while the blend register
/// stores the result of a blend operation. /// stores the result of a blend operation.
pub trait AnimationCurveEvaluator: Reflect { ///
/// [`Vec3`]: bevy_math::Vec3
pub trait AnimationCurveEvaluator: Downcast + Send + Sync + 'static {
/// Blends the top element of the stack with the blend register. /// Blends the top element of the stack with the blend register.
/// ///
/// The semantics of this method are as follows: /// The semantics of this method are as follows:
@ -1094,11 +899,12 @@ pub trait AnimationCurveEvaluator: Reflect {
/// the stack, not blended with it. /// the stack, not blended with it.
fn commit<'a>( fn commit<'a>(
&mut self, &mut self,
transform: Option<Mut<'a, Transform>>,
entity: AnimationEntityMut<'a>, entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError>; ) -> Result<(), AnimationEvaluationError>;
} }
impl_downcast!(AnimationCurveEvaluator);
/// A [curve] defined by keyframes with values in an [animatable] type. /// A [curve] defined by keyframes with values in an [animatable] type.
/// ///
/// The keyframes are interpolated using the type's [`Animatable::interpolate`] implementation. /// The keyframes are interpolated using the type's [`Animatable::interpolate`] implementation.
@ -1153,3 +959,28 @@ where
{ {
AnimationEvaluationError::InconsistentEvaluatorImplementation(TypeId::of::<P>()) AnimationEvaluationError::InconsistentEvaluatorImplementation(TypeId::of::<P>())
} }
/// Returns an [`AnimatedField`] with a given `$component` and `$field`.
///
/// This can be used in the following way:
///
/// ```
/// # use bevy_animation::{animation_curves::AnimatedField, animated_field};
/// # use bevy_ecs::component::Component;
/// # use bevy_math::Vec3;
/// # use bevy_reflect::Reflect;
/// #[derive(Component, Reflect)]
/// struct Transform {
/// translation: Vec3,
/// }
///
/// let field = animated_field!(Transform::translation);
/// ```
#[macro_export]
macro_rules! animated_field {
($component:ident::$field:ident) => {
AnimatedField::new_unchecked(stringify!($field), |component: &mut $component| {
&mut component.$field
})
};
}

View File

@ -17,7 +17,7 @@ pub mod transition;
mod util; mod util;
use core::{ use core::{
any::{Any, TypeId}, any::TypeId,
cell::RefCell, cell::RefCell,
fmt::Debug, fmt::Debug,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
@ -26,7 +26,10 @@ use core::{
use graph::AnimationNodeType; use graph::AnimationNodeType;
use prelude::AnimationCurveEvaluator; use prelude::AnimationCurveEvaluator;
use crate::graph::{AnimationGraphHandle, ThreadedAnimationGraphs}; use crate::{
graph::{AnimationGraphHandle, ThreadedAnimationGraphs},
prelude::EvaluatorId,
};
use bevy_app::{Animation, App, Plugin, PostUpdate}; use bevy_app::{Animation, App, Plugin, PostUpdate};
use bevy_asset::{Asset, AssetApp, Assets}; use bevy_asset::{Asset, AssetApp, Assets};
@ -38,18 +41,13 @@ use bevy_ecs::{
world::EntityMutExcept, world::EntityMutExcept,
}; };
use bevy_math::FloatOrd; use bevy_math::FloatOrd;
use bevy_reflect::{ use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
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_time::Time;
use bevy_transform::{prelude::Transform, TransformSystem}; use bevy_transform::TransformSystem;
use bevy_utils::{ use bevy_utils::{
hashbrown::HashMap, hashbrown::HashMap,
tracing::{trace, warn}, tracing::{trace, warn},
NoOpHash, TypeIdMap, NoOpHash, PreHashMap, PreHashMapExt, TypeIdMap,
}; };
use petgraph::graph::NodeIndex; use petgraph::graph::NodeIndex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -100,175 +98,6 @@ impl VariableCurve {
} }
} }
// We have to implement `PartialReflect` manually because of the embedded
// `Box<dyn AnimationCurve>`, which can't be automatically derived yet.
impl PartialReflect for VariableCurve {
#[inline]
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
}
#[inline]
fn into_partial_reflect(self: Box<Self>) -> Box<dyn PartialReflect> {
self
}
#[inline]
fn as_partial_reflect(&self) -> &dyn PartialReflect {
self
}
#[inline]
fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect {
self
}
fn try_into_reflect(self: Box<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>> {
Ok(self)
}
#[inline]
fn try_as_reflect(&self) -> Option<&dyn Reflect> {
Some(self)
}
#[inline]
fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> {
Some(self)
}
fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> {
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::TupleStruct,
});
}
Ok(())
}
fn reflect_ref(&self) -> ReflectRef {
ReflectRef::TupleStruct(self)
}
fn reflect_mut(&mut self) -> ReflectMut {
ReflectMut::TupleStruct(self)
}
fn reflect_owned(self: Box<Self>) -> ReflectOwned {
ReflectOwned::TupleStruct(self)
}
fn clone_value(&self) -> Box<dyn PartialReflect> {
Box::new((*self).clone())
}
}
// We have to implement `Reflect` manually because of the embedded `Box<dyn
// AnimationCurve>`, which can't be automatically derived yet.
impl Reflect for VariableCurve {
#[inline]
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
#[inline]
fn as_any(&self) -> &dyn Any {
self
}
#[inline]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline]
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> {
self
}
#[inline]
fn as_reflect(&self) -> &dyn Reflect {
self
}
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
self
}
#[inline]
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
}
}
// 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.0.as_partial_reflect()),
_ => None,
}
}
fn field_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> {
match index {
0 => Some(self.0.as_partial_reflect_mut()),
_ => None,
}
}
fn field_len(&self) -> usize {
1
}
fn iter_fields(&self) -> TupleStructFieldIter {
TupleStructFieldIter::new(self)
}
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
// 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())
}
}
// We have to implement `GetTypeRegistration` manually because of the embedded
// `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>();
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
registration
}
}
// We have to implement `Typed` manually because of the embedded `Box<dyn
// 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::TupleStruct(TupleStructInfo::new::<Self>(&[UnnamedField::new::<()>(0)]))
})
}
}
/// A list of [`VariableCurve`]s and the [`AnimationTargetId`]s to which they /// A list of [`VariableCurve`]s and the [`AnimationTargetId`]s to which they
/// apply. /// apply.
/// ///
@ -276,6 +105,8 @@ impl Typed for VariableCurve {
/// [`AnimationTarget`] with that ID. /// [`AnimationTarget`] with that ID.
#[derive(Asset, Reflect, Clone, Debug, Default)] #[derive(Asset, Reflect, Clone, Debug, Default)]
pub struct AnimationClip { pub struct AnimationClip {
// This field is ignored by reflection because AnimationCurves can contain things that are not reflect-able
#[reflect(ignore)]
curves: AnimationCurves, curves: AnimationCurves,
events: AnimationEvents, events: AnimationEvents,
duration: f32, duration: f32,
@ -878,23 +709,106 @@ pub struct AnimationEvaluationState {
/// Stores all [`AnimationCurveEvaluator`]s corresponding to properties that /// Stores all [`AnimationCurveEvaluator`]s corresponding to properties that
/// we've seen so far. /// we've seen so far.
/// ///
/// This is a mapping from the type ID of an animation curve evaluator to /// This is a mapping from the id of an animation curve evaluator to
/// the animation curve evaluator itself. /// the animation curve evaluator itself.
///
/// For efficiency's sake, the [`AnimationCurveEvaluator`]s are cached from /// For efficiency's sake, the [`AnimationCurveEvaluator`]s are cached from
/// frame to frame and animation target to animation target. Therefore, /// frame to frame and animation target to animation target. Therefore,
/// there may be entries in this list corresponding to properties that the /// there may be entries in this list corresponding to properties that the
/// current [`AnimationPlayer`] doesn't animate. To iterate only over the /// current [`AnimationPlayer`] doesn't animate. To iterate only over the
/// properties that are currently being animated, consult the /// properties that are currently being animated, consult the
/// [`Self::current_curve_evaluator_types`] set. /// [`Self::current_evaluators`] set.
curve_evaluators: TypeIdMap<Box<dyn AnimationCurveEvaluator>>, evaluators: AnimationCurveEvaluators,
/// The set of [`AnimationCurveEvaluator`] types that the current /// The set of [`AnimationCurveEvaluator`] types that the current
/// [`AnimationPlayer`] is animating. /// [`AnimationPlayer`] is animating.
/// ///
/// This is built up as new curve evaluators are encountered during graph /// This is built up as new curve evaluators are encountered during graph
/// traversal. /// traversal.
current_curve_evaluator_types: TypeIdMap<()>, current_evaluators: CurrentEvaluators,
}
#[derive(Default)]
struct AnimationCurveEvaluators {
component_property_curve_evaluators:
PreHashMap<(TypeId, usize), Box<dyn AnimationCurveEvaluator>>,
type_id_curve_evaluators: TypeIdMap<Box<dyn AnimationCurveEvaluator>>,
}
impl AnimationCurveEvaluators {
#[inline]
pub(crate) fn get_mut(&mut self, id: EvaluatorId) -> Option<&mut dyn AnimationCurveEvaluator> {
match id {
EvaluatorId::ComponentField(component_property) => self
.component_property_curve_evaluators
.get_mut(component_property),
EvaluatorId::Type(type_id) => self.type_id_curve_evaluators.get_mut(&type_id),
}
.map(|e| &mut **e)
}
#[inline]
pub(crate) fn get_or_insert_with(
&mut self,
id: EvaluatorId,
func: impl FnOnce() -> Box<dyn AnimationCurveEvaluator>,
) -> &mut dyn AnimationCurveEvaluator {
match id {
EvaluatorId::ComponentField(component_property) => &mut **self
.component_property_curve_evaluators
.get_or_insert_with(component_property, func),
EvaluatorId::Type(type_id) => match self.type_id_curve_evaluators.entry(type_id) {
bevy_utils::hashbrown::hash_map::Entry::Occupied(occupied_entry) => {
&mut **occupied_entry.into_mut()
}
bevy_utils::hashbrown::hash_map::Entry::Vacant(vacant_entry) => {
&mut **vacant_entry.insert(func())
}
},
}
}
}
#[derive(Default)]
struct CurrentEvaluators {
component_properties: PreHashMap<(TypeId, usize), ()>,
type_ids: TypeIdMap<()>,
}
impl CurrentEvaluators {
pub(crate) fn keys(&self) -> impl Iterator<Item = EvaluatorId> {
self.component_properties
.keys()
.map(EvaluatorId::ComponentField)
.chain(self.type_ids.keys().copied().map(EvaluatorId::Type))
}
pub(crate) fn clear(
&mut self,
mut visit: impl FnMut(EvaluatorId) -> Result<(), AnimationEvaluationError>,
) -> Result<(), AnimationEvaluationError> {
for (key, _) in self.component_properties.drain() {
(visit)(EvaluatorId::ComponentField(&key))?;
}
for (key, _) in self.type_ids.drain() {
(visit)(EvaluatorId::Type(key))?;
}
Ok(())
}
#[inline]
pub(crate) fn insert(&mut self, id: EvaluatorId) {
match id {
EvaluatorId::ComponentField(component_property) => {
self.component_properties.insert(*component_property, ());
}
EvaluatorId::Type(type_id) => {
self.type_ids.insert(type_id, ());
}
}
}
} }
impl AnimationPlayer { impl AnimationPlayer {
@ -1117,15 +1031,8 @@ pub fn advance_animations(
} }
/// A type alias for [`EntityMutExcept`] as used in animation. /// A type alias for [`EntityMutExcept`] as used in animation.
pub type AnimationEntityMut<'w> = EntityMutExcept< pub type AnimationEntityMut<'w> =
'w, EntityMutExcept<'w, (AnimationTarget, AnimationPlayer, AnimationGraphHandle)>;
(
AnimationTarget,
Transform,
AnimationPlayer,
AnimationGraphHandle,
),
>;
/// A system that modifies animation targets (e.g. bones in a skinned mesh) /// A system that modifies animation targets (e.g. bones in a skinned mesh)
/// according to the currently-playing animations. /// according to the currently-playing animations.
@ -1135,18 +1042,13 @@ pub fn animate_targets(
graphs: Res<Assets<AnimationGraph>>, graphs: Res<Assets<AnimationGraph>>,
threaded_animation_graphs: Res<ThreadedAnimationGraphs>, threaded_animation_graphs: Res<ThreadedAnimationGraphs>,
players: Query<(&AnimationPlayer, &AnimationGraphHandle)>, players: Query<(&AnimationPlayer, &AnimationGraphHandle)>,
mut targets: Query<( mut targets: Query<(Entity, &AnimationTarget, AnimationEntityMut)>,
Entity,
&AnimationTarget,
Option<&mut Transform>,
AnimationEntityMut,
)>,
animation_evaluation_state: Local<ThreadLocal<RefCell<AnimationEvaluationState>>>, animation_evaluation_state: Local<ThreadLocal<RefCell<AnimationEvaluationState>>>,
) { ) {
// Evaluate all animation targets in parallel. // Evaluate all animation targets in parallel.
targets targets
.par_iter_mut() .par_iter_mut()
.for_each(|(entity, target, transform, entity_mut)| { .for_each(|(entity, target, entity_mut)| {
let &AnimationTarget { let &AnimationTarget {
id: target_id, id: target_id,
player: player_id, player: player_id,
@ -1300,19 +1202,20 @@ pub fn animate_targets(
// will both yield a `RotationCurveEvaluator` and // will both yield a `RotationCurveEvaluator` and
// therefore will share the same evaluator in this // therefore will share the same evaluator in this
// table. // table.
let curve_evaluator_type_id = (*curve.0).evaluator_type(); let curve_evaluator_id = (*curve.0).evaluator_id();
let curve_evaluator = evaluation_state let curve_evaluator = evaluation_state
.curve_evaluators .evaluators
.entry(curve_evaluator_type_id) .get_or_insert_with(curve_evaluator_id.clone(), || {
.or_insert_with(|| curve.0.create_evaluator()); curve.0.create_evaluator()
});
evaluation_state evaluation_state
.current_curve_evaluator_types .current_evaluators
.insert(curve_evaluator_type_id, ()); .insert(curve_evaluator_id);
if let Err(err) = AnimationCurve::apply( if let Err(err) = AnimationCurve::apply(
&*curve.0, &*curve.0,
&mut **curve_evaluator, curve_evaluator,
seek_time, seek_time,
weight, weight,
animation_graph_node_index, animation_graph_node_index,
@ -1324,7 +1227,7 @@ pub fn animate_targets(
} }
} }
if let Err(err) = evaluation_state.commit_all(transform, entity_mut) { if let Err(err) = evaluation_state.commit_all(entity_mut) {
warn!("Animation application failed: {:?}", err); warn!("Animation application failed: {:?}", err);
} }
}); });
@ -1425,8 +1328,8 @@ impl AnimationEvaluationState {
&mut self, &mut self,
node_index: AnimationNodeIndex, node_index: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> { ) -> Result<(), AnimationEvaluationError> {
for curve_evaluator_type in self.current_curve_evaluator_types.keys() { for curve_evaluator_type in self.current_evaluators.keys() {
self.curve_evaluators self.evaluators
.get_mut(curve_evaluator_type) .get_mut(curve_evaluator_type)
.unwrap() .unwrap()
.blend(node_index)?; .blend(node_index)?;
@ -1439,8 +1342,8 @@ impl AnimationEvaluationState {
/// ///
/// The given `node_index` is the node that we're evaluating. /// The given `node_index` is the node that we're evaluating.
fn add_all(&mut self, node_index: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> { fn add_all(&mut self, node_index: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
for curve_evaluator_type in self.current_curve_evaluator_types.keys() { for curve_evaluator_type in self.current_evaluators.keys() {
self.curve_evaluators self.evaluators
.get_mut(curve_evaluator_type) .get_mut(curve_evaluator_type)
.unwrap() .unwrap()
.add(node_index)?; .add(node_index)?;
@ -1459,8 +1362,8 @@ impl AnimationEvaluationState {
weight: f32, weight: f32,
node_index: AnimationNodeIndex, node_index: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> { ) -> Result<(), AnimationEvaluationError> {
for curve_evaluator_type in self.current_curve_evaluator_types.keys() { for curve_evaluator_type in self.current_evaluators.keys() {
self.curve_evaluators self.evaluators
.get_mut(curve_evaluator_type) .get_mut(curve_evaluator_type)
.unwrap() .unwrap()
.push_blend_register(weight, node_index)?; .push_blend_register(weight, node_index)?;
@ -1475,19 +1378,14 @@ impl AnimationEvaluationState {
/// components being animated. /// components being animated.
fn commit_all( fn commit_all(
&mut self, &mut self,
mut transform: Option<Mut<Transform>>,
mut entity_mut: AnimationEntityMut, mut entity_mut: AnimationEntityMut,
) -> Result<(), AnimationEvaluationError> { ) -> Result<(), AnimationEvaluationError> {
for (curve_evaluator_type, _) in self.current_curve_evaluator_types.drain() { self.current_evaluators.clear(|id| {
self.curve_evaluators self.evaluators
.get_mut(&curve_evaluator_type) .get_mut(id)
.unwrap() .unwrap()
.commit( .commit(entity_mut.reborrow())
transform.as_mut().map(|transform| transform.reborrow()), })
entity_mut.reborrow(),
)?;
}
Ok(())
} }
} }

View File

@ -4,6 +4,7 @@ use crate::{
}; };
use alloc::collections::VecDeque; use alloc::collections::VecDeque;
use bevy_animation::animated_field;
use bevy_asset::{ use bevy_asset::{
io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError, io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError,
}; };
@ -310,12 +311,13 @@ async fn load_gltf<'a, 'b, 'c>(
{ {
match outputs { match outputs {
ReadOutputs::Translations(tr) => { ReadOutputs::Translations(tr) => {
let translation_property = animated_field!(Transform::translation);
let translations: Vec<Vec3> = tr.map(Vec3::from).collect(); let translations: Vec<Vec3> = tr.map(Vec3::from).collect();
if keyframe_timestamps.len() == 1 { if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)] Some(VariableCurve::new(AnimatableCurve::new(
Some(ConstantCurve::new(Interval::EVERYWHERE, translations[0])) translation_property,
.map(TranslationCurve) ConstantCurve::new(Interval::EVERYWHERE, translations[0]),
.map(VariableCurve::new) )))
} else { } else {
match interpolation { match interpolation {
gltf::animation::Interpolation::Linear => { gltf::animation::Interpolation::Linear => {
@ -323,34 +325,47 @@ async fn load_gltf<'a, 'b, 'c>(
keyframe_timestamps.into_iter().zip(translations), keyframe_timestamps.into_iter().zip(translations),
) )
.ok() .ok()
.map(TranslationCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
translation_property,
curve,
))
})
} }
gltf::animation::Interpolation::Step => { gltf::animation::Interpolation::Step => {
SteppedKeyframeCurve::new( SteppedKeyframeCurve::new(
keyframe_timestamps.into_iter().zip(translations), keyframe_timestamps.into_iter().zip(translations),
) )
.ok() .ok()
.map(TranslationCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
translation_property,
curve,
))
})
} }
gltf::animation::Interpolation::CubicSpline => { gltf::animation::Interpolation::CubicSpline => {
CubicKeyframeCurve::new(keyframe_timestamps, translations) CubicKeyframeCurve::new(keyframe_timestamps, translations)
.ok() .ok()
.map(TranslationCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
translation_property,
curve,
))
})
} }
} }
} }
} }
ReadOutputs::Rotations(rots) => { ReadOutputs::Rotations(rots) => {
let rotation_property = animated_field!(Transform::rotation);
let rotations: Vec<Quat> = let rotations: Vec<Quat> =
rots.into_f32().map(Quat::from_array).collect(); rots.into_f32().map(Quat::from_array).collect();
if keyframe_timestamps.len() == 1 { if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)] Some(VariableCurve::new(AnimatableCurve::new(
Some(ConstantCurve::new(Interval::EVERYWHERE, rotations[0])) rotation_property,
.map(RotationCurve) ConstantCurve::new(Interval::EVERYWHERE, rotations[0]),
.map(VariableCurve::new) )))
} else { } else {
match interpolation { match interpolation {
gltf::animation::Interpolation::Linear => { gltf::animation::Interpolation::Linear => {
@ -358,16 +373,24 @@ async fn load_gltf<'a, 'b, 'c>(
keyframe_timestamps.into_iter().zip(rotations), keyframe_timestamps.into_iter().zip(rotations),
) )
.ok() .ok()
.map(RotationCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
rotation_property,
curve,
))
})
} }
gltf::animation::Interpolation::Step => { gltf::animation::Interpolation::Step => {
SteppedKeyframeCurve::new( SteppedKeyframeCurve::new(
keyframe_timestamps.into_iter().zip(rotations), keyframe_timestamps.into_iter().zip(rotations),
) )
.ok() .ok()
.map(RotationCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
rotation_property,
curve,
))
})
} }
gltf::animation::Interpolation::CubicSpline => { gltf::animation::Interpolation::CubicSpline => {
CubicRotationCurve::new( CubicRotationCurve::new(
@ -375,19 +398,24 @@ async fn load_gltf<'a, 'b, 'c>(
rotations.into_iter().map(Vec4::from), rotations.into_iter().map(Vec4::from),
) )
.ok() .ok()
.map(RotationCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
rotation_property,
curve,
))
})
} }
} }
} }
} }
ReadOutputs::Scales(scale) => { ReadOutputs::Scales(scale) => {
let scale_property = animated_field!(Transform::scale);
let scales: Vec<Vec3> = scale.map(Vec3::from).collect(); let scales: Vec<Vec3> = scale.map(Vec3::from).collect();
if keyframe_timestamps.len() == 1 { if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)] Some(VariableCurve::new(AnimatableCurve::new(
Some(ConstantCurve::new(Interval::EVERYWHERE, scales[0])) scale_property,
.map(ScaleCurve) ConstantCurve::new(Interval::EVERYWHERE, scales[0]),
.map(VariableCurve::new) )))
} else { } else {
match interpolation { match interpolation {
gltf::animation::Interpolation::Linear => { gltf::animation::Interpolation::Linear => {
@ -395,22 +423,34 @@ async fn load_gltf<'a, 'b, 'c>(
keyframe_timestamps.into_iter().zip(scales), keyframe_timestamps.into_iter().zip(scales),
) )
.ok() .ok()
.map(ScaleCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
scale_property,
curve,
))
})
} }
gltf::animation::Interpolation::Step => { gltf::animation::Interpolation::Step => {
SteppedKeyframeCurve::new( SteppedKeyframeCurve::new(
keyframe_timestamps.into_iter().zip(scales), keyframe_timestamps.into_iter().zip(scales),
) )
.ok() .ok()
.map(ScaleCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
scale_property,
curve,
))
})
} }
gltf::animation::Interpolation::CubicSpline => { gltf::animation::Interpolation::CubicSpline => {
CubicKeyframeCurve::new(keyframe_timestamps, scales) CubicKeyframeCurve::new(keyframe_timestamps, scales)
.ok() .ok()
.map(ScaleCurve) .map(|curve| {
.map(VariableCurve::new) VariableCurve::new(AnimatableCurve::new(
scale_property,
curve,
))
})
} }
} }
} }

View File

@ -223,6 +223,8 @@ impl<V: Clone, H> Clone for Hashed<V, H> {
} }
} }
impl<V: Copy, H> Copy for Hashed<V, H> {}
impl<V: Eq, H> Eq for Hashed<V, H> {} impl<V: Eq, H> Eq for Hashed<V, H> {}
/// A [`BuildHasher`] that results in a [`PassHasher`]. /// A [`BuildHasher`] that results in a [`PassHasher`].

View File

@ -3,7 +3,7 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use bevy::{ use bevy::{
animation::{AnimationTarget, AnimationTargetId}, animation::{animated_field, AnimationTarget, AnimationTargetId},
prelude::*, prelude::*,
}; };
@ -52,17 +52,19 @@ fn setup(
let planet_animation_target_id = AnimationTargetId::from_name(&planet); let planet_animation_target_id = AnimationTargetId::from_name(&planet);
animation.add_curve_to_target( animation.add_curve_to_target(
planet_animation_target_id, planet_animation_target_id,
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([ AnimatableCurve::new(
Vec3::new(1.0, 0.0, 1.0), animated_field!(Transform::translation),
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 Vec3::new(-1.0, 0.0, -1.0),
// be the same as the first one 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
.map(TranslationCurve) Vec3::new(1.0, 0.0, 1.0),
.expect("should be able to build translation curve because we pass in valid samples"), ]))
.expect("should be able to build translation curve because we pass in valid samples"),
),
); );
// Or it can modify the rotation of the transform. // Or it can modify the rotation of the transform.
// To find the entity to modify, the hierarchy will be traversed looking for // To find the entity to modify, the hierarchy will be traversed looking for
@ -71,15 +73,17 @@ fn setup(
AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter()); AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter());
animation.add_curve_to_target( animation.add_curve_to_target(
orbit_controller_animation_target_id, orbit_controller_animation_target_id,
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([ AnimatableCurve::new(
Quat::IDENTITY, animated_field!(Transform::rotation),
Quat::from_axis_angle(Vec3::Y, PI / 2.), UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::IDENTITY,
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::from_axis_angle(Vec3::Y, PI / 2.),
Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
])) Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
.map(RotationCurve) Quat::IDENTITY,
.expect("Failed to build rotation curve"), ]))
.expect("Failed to build rotation curve"),
),
); );
// If a curve in an animation is shorter than the other, it will not repeat // 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 // until all other curves are finished. In that case, another animation should
@ -89,38 +93,42 @@ fn setup(
); );
animation.add_curve_to_target( animation.add_curve_to_target(
satellite_animation_target_id, satellite_animation_target_id,
UnevenSampleAutoCurve::new( AnimatableCurve::new(
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0] animated_field!(Transform::scale),
.into_iter() UnevenSampleAutoCurve::new(
.zip([ [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]
Vec3::splat(0.8), .into_iter()
Vec3::splat(1.2), .zip([
Vec3::splat(0.8), Vec3::splat(0.8),
Vec3::splat(1.2), Vec3::splat(1.2),
Vec3::splat(0.8), Vec3::splat(0.8),
Vec3::splat(1.2), Vec3::splat(1.2),
Vec3::splat(0.8), Vec3::splat(0.8),
Vec3::splat(1.2), Vec3::splat(1.2),
Vec3::splat(0.8), Vec3::splat(0.8),
]), Vec3::splat(1.2),
) Vec3::splat(0.8),
.map(ScaleCurve) ]),
.expect("Failed to build scale curve"), )
.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( animation.add_curve_to_target(
AnimationTargetId::from_names( AnimationTargetId::from_names(
[planet.clone(), orbit_controller.clone(), satellite.clone()].iter(), [planet.clone(), orbit_controller.clone(), satellite.clone()].iter(),
), ),
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([ AnimatableCurve::new(
Quat::IDENTITY, animated_field!(Transform::rotation),
Quat::from_axis_angle(Vec3::Y, PI / 2.), UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::IDENTITY,
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::from_axis_angle(Vec3::Y, PI / 2.),
Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
])) Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
.map(RotationCurve) Quat::IDENTITY,
.expect("should be able to build translation curve because we pass in valid samples"), ]))
.expect("should be able to build translation curve because we pass in valid samples"),
),
); );
// Create the animation graph // Create the animation graph

View File

@ -1,21 +1,13 @@
//! Shows how to use animation clips to animate UI properties. //! Shows how to use animation clips to animate UI properties.
use bevy::{ use bevy::{
animation::{AnimationTarget, AnimationTargetId}, animation::{
animated_field, AnimationEntityMut, AnimationEvaluationError, AnimationTarget,
AnimationTargetId,
},
prelude::*, prelude::*,
}; };
use std::any::TypeId;
// A type that represents the font size of the first text section.
//
// We implement `AnimatableProperty` on this.
#[derive(Reflect)]
struct FontSizeProperty;
// A type that represents the color of the first text section.
//
// We implement `AnimatableProperty` on this.
#[derive(Reflect)]
struct TextColorProperty;
// Holds information about the animation we programmatically create. // Holds information about the animation we programmatically create.
struct AnimationInfo { struct AnimationInfo {
@ -39,29 +31,6 @@ fn main() {
.run(); .run();
} }
impl AnimatableProperty for FontSizeProperty {
type Component = TextFont;
type Property = f32;
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
Some(&mut component.font_size)
}
}
impl AnimatableProperty for TextColorProperty {
type Component = TextColor;
type Property = Srgba;
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
match component.0 {
Color::Srgba(ref mut color) => Some(color),
_ => None,
}
}
}
impl AnimationInfo { impl AnimationInfo {
// Programmatically creates the UI animation. // Programmatically creates the UI animation.
fn create( fn create(
@ -76,35 +45,40 @@ impl AnimationInfo {
let mut animation_clip = AnimationClip::default(); let mut animation_clip = AnimationClip::default();
// Create a curve that animates font size. // Create a curve that animates font size.
//
// 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_clip.add_curve_to_target(
animation_target_id, animation_target_id,
AnimatableKeyframeCurve::new( AnimatableCurve::new(
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] animated_field!(TextFont::font_size),
.into_iter() AnimatableKeyframeCurve::new(
.zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]), [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
) .into_iter()
.map(AnimatableCurve::<FontSizeProperty, _>::from_curve) .zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]),
.expect("should be able to build translation curve because we pass in valid samples"), )
.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 // Create a curve that animates font color. Note that this should have
// the same time duration as the previous curve. // the same time duration as the previous curve.
// //
// Similar to the above, the curve itself is a `Curve<Srgba>`, and `Srgba` is // This time we use a "custom property", which in this case animates TextColor under the assumption
// `TextColorProperty::Property`, which is required by the `from_curve` method. // that it is in the "srgba" format.
animation_clip.add_curve_to_target( animation_clip.add_curve_to_target(
animation_target_id, animation_target_id,
AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([ AnimatableCurve::new(
Srgba::RED, TextColorProperty,
Srgba::GREEN, AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([
Srgba::BLUE, Srgba::RED,
Srgba::RED, Srgba::GREEN,
])) Srgba::BLUE,
.map(AnimatableCurve::<TextColorProperty, _>::from_curve) Srgba::RED,
.expect("should be able to build translation curve because we pass in valid samples"), ]))
.expect(
"should be able to build translation curve because we pass in valid samples",
),
),
); );
// Save our animation clip as an asset. // Save our animation clip as an asset.
@ -187,3 +161,37 @@ fn setup(
.insert(animation_target_name); .insert(animation_target_name);
}); });
} }
// A type that represents the color of the first text section.
//
// We implement `AnimatableProperty` on this to define custom property accessor logic
#[derive(Clone)]
struct TextColorProperty;
impl AnimatableProperty for TextColorProperty {
type Property = Srgba;
fn evaluator_id(&self) -> EvaluatorId {
EvaluatorId::Type(TypeId::of::<Self>())
}
fn get_mut<'a>(
&self,
entity: &'a mut AnimationEntityMut,
) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
let text_color = entity
.get_mut::<TextColor>()
.ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
TextColor,
>(
)))?
.into_inner();
match text_color.0 {
Color::Srgba(ref mut color) => Ok(color),
_ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
Srgba,
>(
))),
}
}
}

View File

@ -3,7 +3,7 @@
use std::f32::consts::FRAC_PI_2; use std::f32::consts::FRAC_PI_2;
use bevy::{ use bevy::{
animation::{AnimationTarget, AnimationTargetId}, animation::{animated_field, AnimationTarget, AnimationTargetId},
color::palettes::css::{ORANGE, SILVER}, color::palettes::css::{ORANGE, SILVER},
math::vec3, math::vec3,
prelude::*, prelude::*,
@ -127,9 +127,14 @@ impl AnimationInfo {
.reparametrize_linear(interval(0.0, 4.0).unwrap()) .reparametrize_linear(interval(0.0, 4.0).unwrap())
.expect("this curve has bounded domain, so this should never fail"); .expect("this curve has bounded domain, so this should never fail");
animation_clip animation_clip.add_curve_to_target(
.add_curve_to_target(animation_target_id, TranslationCurve(translation_curve)); animation_target_id,
animation_clip.add_curve_to_target(animation_target_id, RotationCurve(rotation_curve)); AnimatableCurve::new(animated_field!(Transform::translation), translation_curve),
);
animation_clip.add_curve_to_target(
animation_target_id,
AnimatableCurve::new(animated_field!(Transform::rotation), rotation_curve),
);
// Save our animation clip as an asset. // Save our animation clip as an asset.
let animation_clip_handle = animation_clips.add(animation_clip); let animation_clip_handle = animation_clips.add(animation_clip);