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"
serde = "1"
blake3 = { version = "1.0" }
downcast-rs = "1.2.0"
derive_more = { version = "1", default-features = false, features = [
"error",
"from",

View File

@ -22,30 +22,32 @@
//!
//! 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:
//! 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 property:
//!
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # 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(
//! # Interval::UNIT,
//! # |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:
//!
//! # 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_math::vec3;
//! # let wobble_curve = FunctionCurve::new(
//! # Interval::UNIT,
//! # |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 mut animation_clip = AnimationClip::default();
//! animation_clip.add_curve_to_target(
@ -59,22 +61,27 @@
//! a [`Curve`], which produces time-related data of some kind, to an [`AnimationCurve`], which
//! knows how to apply that data to an entity.
//!
//! ## `Transform`
//! ## Animated Fields
//!
//! [`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`]
//! The [`animated_field`] macro (which returns an [`AnimatedField`]), in combination with [`AnimatableCurve`]
//! is the easiest way to make an animation curve (see the example above).
//!
//! ## 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.
//!
//! ## 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
//! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation
//! [`AnimationClip`]: crate::AnimationClip
//! [there]: AnimatableProperty
//! [`animated_field`]: crate::animated_field
use core::{
any::TypeId,
@ -82,24 +89,22 @@ use core::{
marker::PhantomData,
};
use bevy_ecs::{component::Component, world::Mut};
use bevy_math::{
curve::{
cores::{UnevenCore, UnevenCoreError},
iterable::IterableCurve,
Curve, Interval,
},
Quat, Vec3,
use bevy_ecs::component::Component;
use bevy_math::curve::{
cores::{UnevenCore, UnevenCoreError},
iterable::IterableCurve,
Curve, Interval,
};
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypePath};
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed};
use bevy_render::mesh::morph::MorphWeights;
use bevy_transform::prelude::Transform;
use crate::{
graph::AnimationNodeIndex,
prelude::{Animatable, BlendInput},
AnimationEntityMut, AnimationEvaluationError,
};
use bevy_utils::Hashed;
use downcast_rs::{impl_downcast, Downcast};
/// A value on a component that Bevy can animate.
///
@ -109,68 +114,154 @@ use crate::{
/// to define the animation itself).
/// 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 std::any::TypeId;
/// # use bevy_render::camera::PerspectiveProjection;
/// #[derive(Reflect)]
/// struct FieldOfViewProperty;
///
/// impl AnimatableProperty for FieldOfViewProperty {
/// type Component = PerspectiveProjection;
/// type Property = f32;
/// fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
/// Some(&mut component.fov)
/// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
/// 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:
///
/// # 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_core::Name;
/// # use bevy_reflect::Reflect;
/// # use bevy_render::camera::PerspectiveProjection;
/// # use std::any::TypeId;
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
/// # #[derive(Reflect)]
/// # #[derive(Reflect, Clone)]
/// # struct FieldOfViewProperty;
/// # impl AnimatableProperty for FieldOfViewProperty {
/// # type Component = PerspectiveProjection;
/// # type Property = f32;
/// # fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
/// # Some(&mut component.fov)
/// # }
/// # type Property = f32;
/// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
/// # 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>())
/// # }
/// # }
/// let mut animation_clip = AnimationClip::default();
/// animation_clip.add_curve_to_target(
/// animation_target_id,
/// AnimatableKeyframeCurve::new(
/// [
/// AnimatableCurve::new(
/// FieldOfViewProperty,
/// AnimatableKeyframeCurve::new([
/// (0.0, core::f32::consts::PI / 4.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
/// 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
/// configured above).
///
/// [`AnimationClip`]: crate::AnimationClip
pub trait AnimatableProperty: Reflect + TypePath {
/// The type of the component that the property lives on.
type Component: Component;
pub trait AnimatableProperty: Send + Sync + 'static {
/// The animated property type.
type Property: Animatable;
/// The type of the property to be animated.
type Property: Animatable + FromReflect + Reflectable + Clone + Sync + Debug;
/// Retrieves the property from the given `entity`.
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`.
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property>;
/// # Panics
/// 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
@ -187,12 +278,14 @@ impl<T, C> AnimationCompatibleCurve<T> for C where C: Curve<T> + Debug + Clone +
#[derive(Reflect, FromReflect)]
#[reflect(from_reflect = false)]
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.
///
/// [curve]: Curve
pub curve: C,
#[reflect(ignore)]
_phantom: PhantomData<P>,
}
/// 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
/// will automatically do so when you use an [`AnimatableCurve`] instance.
#[derive(Reflect)]
pub struct AnimatableCurveEvaluator<P>
where
P: AnimatableProperty,
{
evaluator: BasicAnimationCurveEvaluator<P::Property>,
#[reflect(ignore)]
phantom: PhantomData<P>,
pub struct AnimatableCurveEvaluator<A: Animatable> {
evaluator: BasicAnimationCurveEvaluator<A>,
property: Box<dyn AnimatableProperty<Property = A>>,
}
impl<P, C> AnimatableCurve<P, C>
@ -218,22 +307,20 @@ where
/// valued in an [animatable property].
///
/// [animatable property]: AnimatableProperty::Property
pub fn from_curve(curve: C) -> Self {
Self {
curve,
_phantom: PhantomData,
}
pub fn new(property: P, curve: C) -> Self {
Self { property, curve }
}
}
impl<P, C> Clone for AnimatableCurve<P, C>
where
C: Clone,
P: Clone,
{
fn clone(&self) -> Self {
Self {
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
P: AnimatableProperty,
C: AnimationCompatibleCurve<P::Property>,
P: AnimatableProperty + Clone,
C: AnimationCompatibleCurve<P::Property> + Clone,
{
fn clone_value(&self) -> Box<dyn AnimationCurve> {
Box::new(self.clone())
@ -262,14 +349,14 @@ where
self.curve.domain()
}
fn evaluator_type(&self) -> TypeId {
TypeId::of::<AnimatableCurveEvaluator<P>>()
fn evaluator_id(&self) -> EvaluatorId {
self.property.evaluator_id()
}
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
Box::new(AnimatableCurveEvaluator {
Box::new(AnimatableCurveEvaluator::<P::Property> {
evaluator: BasicAnimationCurveEvaluator::default(),
phantom: PhantomData::<P>,
property: Box::new(self.property.clone()),
})
}
@ -280,8 +367,8 @@ where
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
.downcast_mut::<AnimatableCurveEvaluator<P>>()
let curve_evaluator = curve_evaluator
.downcast_mut::<AnimatableCurveEvaluator<P::Property>>()
.unwrap();
let value = self.curve.sample_clamped(t);
curve_evaluator
@ -296,10 +383,7 @@ where
}
}
impl<P> AnimationCurveEvaluator for AnimatableCurveEvaluator<P>
where
P: AnimatableProperty,
{
impl<A: Animatable> AnimationCurveEvaluator for AnimatableCurveEvaluator<A> {
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
self.evaluator.combine(graph_node, /*additive=*/ false)
}
@ -318,310 +402,14 @@ where
fn commit<'a>(
&mut self,
_: Option<Mut<'a, Transform>>,
mut entity: AnimationEntityMut<'a>,
) -> 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 property = self.property.get_mut(&mut entity)?;
*property = self
.evaluator
.stack
.pop()
.ok_or_else(inconsistent::<AnimatableCurveEvaluator<P>>)?
.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>)?
.ok_or_else(inconsistent::<AnimatableCurveEvaluator<A>>)?
.value;
Ok(())
}
@ -683,8 +471,8 @@ where
self.0.domain()
}
fn evaluator_type(&self) -> TypeId {
TypeId::of::<WeightsCurveEvaluator>()
fn evaluator_id(&self) -> EvaluatorId {
EvaluatorId::Type(TypeId::of::<WeightsCurveEvaluator>())
}
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
@ -704,7 +492,7 @@ where
weight: f32,
graph_node: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
let curve_evaluator = curve_evaluator
.downcast_mut::<WeightsCurveEvaluator>()
.unwrap();
@ -802,7 +590,6 @@ impl AnimationCurveEvaluator for WeightsCurveEvaluator {
fn commit<'a>(
&mut self,
_: Option<Mut<'a, Transform>>,
mut entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> {
if self.stack_morph_target_weights.is_empty() {
@ -968,7 +755,7 @@ where
/// mutated in the implementation of [`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.
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
/// 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
/// this curve.
///
/// All curve types must return the same type of
/// [`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>;
/// 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
/// AnimationCurveEvaluator`. Typically, implementations of [`Self::apply`]
/// 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.
///
/// 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>;
}
/// 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
/// 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
/// evaluating the [`crate::graph::AnimationGraph`], while the blend register
/// 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.
///
/// The semantics of this method are as follows:
@ -1094,11 +899,12 @@ pub trait AnimationCurveEvaluator: Reflect {
/// the stack, not blended with it.
fn commit<'a>(
&mut self,
transform: Option<Mut<'a, Transform>>,
entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError>;
}
impl_downcast!(AnimationCurveEvaluator);
/// A [curve] defined by keyframes with values in an [animatable] type.
///
/// The keyframes are interpolated using the type's [`Animatable::interpolate`] implementation.
@ -1153,3 +959,28 @@ where
{
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;
use core::{
any::{Any, TypeId},
any::TypeId,
cell::RefCell,
fmt::Debug,
hash::{Hash, Hasher},
@ -26,7 +26,10 @@ use core::{
use graph::AnimationNodeType;
use prelude::AnimationCurveEvaluator;
use crate::graph::{AnimationGraphHandle, ThreadedAnimationGraphs};
use crate::{
graph::{AnimationGraphHandle, ThreadedAnimationGraphs},
prelude::EvaluatorId,
};
use bevy_app::{Animation, App, Plugin, PostUpdate};
use bevy_asset::{Asset, AssetApp, Assets};
@ -38,18 +41,13 @@ use bevy_ecs::{
world::EntityMutExcept,
};
use bevy_math::FloatOrd;
use bevy_reflect::{
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_reflect::{prelude::ReflectDefault, Reflect, TypePath};
use bevy_time::Time;
use bevy_transform::{prelude::Transform, TransformSystem};
use bevy_transform::TransformSystem;
use bevy_utils::{
hashbrown::HashMap,
tracing::{trace, warn},
NoOpHash, TypeIdMap,
NoOpHash, PreHashMap, PreHashMapExt, TypeIdMap,
};
use petgraph::graph::NodeIndex;
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
/// apply.
///
@ -276,6 +105,8 @@ impl Typed for VariableCurve {
/// [`AnimationTarget`] with that ID.
#[derive(Asset, Reflect, Clone, Debug, Default)]
pub struct AnimationClip {
// This field is ignored by reflection because AnimationCurves can contain things that are not reflect-able
#[reflect(ignore)]
curves: AnimationCurves,
events: AnimationEvents,
duration: f32,
@ -878,23 +709,106 @@ pub struct AnimationEvaluationState {
/// Stores all [`AnimationCurveEvaluator`]s corresponding to properties that
/// 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.
///
/// For efficiency's sake, the [`AnimationCurveEvaluator`]s are cached from
/// frame to frame and animation target to animation target. Therefore,
/// there may be entries in this list corresponding to properties that the
/// current [`AnimationPlayer`] doesn't animate. To iterate only over the
/// properties that are currently being animated, consult the
/// [`Self::current_curve_evaluator_types`] set.
curve_evaluators: TypeIdMap<Box<dyn AnimationCurveEvaluator>>,
/// [`Self::current_evaluators`] set.
evaluators: AnimationCurveEvaluators,
/// The set of [`AnimationCurveEvaluator`] types that the current
/// [`AnimationPlayer`] is animating.
///
/// This is built up as new curve evaluators are encountered during graph
/// 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 {
@ -1117,15 +1031,8 @@ pub fn advance_animations(
}
/// A type alias for [`EntityMutExcept`] as used in animation.
pub type AnimationEntityMut<'w> = EntityMutExcept<
'w,
(
AnimationTarget,
Transform,
AnimationPlayer,
AnimationGraphHandle,
),
>;
pub type AnimationEntityMut<'w> =
EntityMutExcept<'w, (AnimationTarget, AnimationPlayer, AnimationGraphHandle)>;
/// A system that modifies animation targets (e.g. bones in a skinned mesh)
/// according to the currently-playing animations.
@ -1135,18 +1042,13 @@ pub fn animate_targets(
graphs: Res<Assets<AnimationGraph>>,
threaded_animation_graphs: Res<ThreadedAnimationGraphs>,
players: Query<(&AnimationPlayer, &AnimationGraphHandle)>,
mut targets: Query<(
Entity,
&AnimationTarget,
Option<&mut Transform>,
AnimationEntityMut,
)>,
mut targets: Query<(Entity, &AnimationTarget, AnimationEntityMut)>,
animation_evaluation_state: Local<ThreadLocal<RefCell<AnimationEvaluationState>>>,
) {
// Evaluate all animation targets in parallel.
targets
.par_iter_mut()
.for_each(|(entity, target, transform, entity_mut)| {
.for_each(|(entity, target, entity_mut)| {
let &AnimationTarget {
id: target_id,
player: player_id,
@ -1300,19 +1202,20 @@ pub fn animate_targets(
// will both yield a `RotationCurveEvaluator` and
// therefore will share the same evaluator in this
// table.
let curve_evaluator_type_id = (*curve.0).evaluator_type();
let curve_evaluator_id = (*curve.0).evaluator_id();
let curve_evaluator = evaluation_state
.curve_evaluators
.entry(curve_evaluator_type_id)
.or_insert_with(|| curve.0.create_evaluator());
.evaluators
.get_or_insert_with(curve_evaluator_id.clone(), || {
curve.0.create_evaluator()
});
evaluation_state
.current_curve_evaluator_types
.insert(curve_evaluator_type_id, ());
.current_evaluators
.insert(curve_evaluator_id);
if let Err(err) = AnimationCurve::apply(
&*curve.0,
&mut **curve_evaluator,
curve_evaluator,
seek_time,
weight,
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);
}
});
@ -1425,8 +1328,8 @@ impl AnimationEvaluationState {
&mut self,
node_index: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
self.curve_evaluators
for curve_evaluator_type in self.current_evaluators.keys() {
self.evaluators
.get_mut(curve_evaluator_type)
.unwrap()
.blend(node_index)?;
@ -1439,8 +1342,8 @@ impl AnimationEvaluationState {
///
/// The given `node_index` is the node that we're evaluating.
fn add_all(&mut self, node_index: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
self.curve_evaluators
for curve_evaluator_type in self.current_evaluators.keys() {
self.evaluators
.get_mut(curve_evaluator_type)
.unwrap()
.add(node_index)?;
@ -1459,8 +1362,8 @@ impl AnimationEvaluationState {
weight: f32,
node_index: AnimationNodeIndex,
) -> Result<(), AnimationEvaluationError> {
for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
self.curve_evaluators
for curve_evaluator_type in self.current_evaluators.keys() {
self.evaluators
.get_mut(curve_evaluator_type)
.unwrap()
.push_blend_register(weight, node_index)?;
@ -1475,19 +1378,14 @@ impl AnimationEvaluationState {
/// components being animated.
fn commit_all(
&mut self,
mut transform: Option<Mut<Transform>>,
mut entity_mut: AnimationEntityMut,
) -> Result<(), AnimationEvaluationError> {
for (curve_evaluator_type, _) in self.current_curve_evaluator_types.drain() {
self.curve_evaluators
.get_mut(&curve_evaluator_type)
self.current_evaluators.clear(|id| {
self.evaluators
.get_mut(id)
.unwrap()
.commit(
transform.as_mut().map(|transform| transform.reborrow()),
entity_mut.reborrow(),
)?;
}
Ok(())
.commit(entity_mut.reborrow())
})
}
}

View File

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

View File

@ -3,7 +3,7 @@
use std::f32::consts::PI;
use bevy::{
animation::{AnimationTarget, AnimationTargetId},
animation::{animated_field, AnimationTarget, AnimationTargetId},
prelude::*,
};
@ -52,17 +52,19 @@ fn setup(
let planet_animation_target_id = AnimationTargetId::from_name(&planet);
animation.add_curve_to_target(
planet_animation_target_id,
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"),
AnimatableCurve::new(
animated_field!(Transform::translation),
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),
]))
.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
@ -71,15 +73,17 @@ fn setup(
AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter());
animation.add_curve_to_target(
orbit_controller_animation_target_id,
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"),
AnimatableCurve::new(
animated_field!(Transform::rotation),
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,
]))
.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
@ -89,38 +93,42 @@ fn setup(
);
animation.add_curve_to_target(
satellite_animation_target_id,
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"),
AnimatableCurve::new(
animated_field!(Transform::scale),
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),
]),
)
.expect("Failed to build scale curve"),
),
);
// 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(),
),
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"),
AnimatableCurve::new(
animated_field!(Transform::rotation),
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,
]))
.expect("should be able to build translation curve because we pass in valid samples"),
),
);
// Create the animation graph

View File

@ -1,21 +1,13 @@
//! Shows how to use animation clips to animate UI properties.
use bevy::{
animation::{AnimationTarget, AnimationTargetId},
animation::{
animated_field, AnimationEntityMut, AnimationEvaluationError, AnimationTarget,
AnimationTargetId,
},
prelude::*,
};
// 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;
use std::any::TypeId;
// Holds information about the animation we programmatically create.
struct AnimationInfo {
@ -39,29 +31,6 @@ fn main() {
.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 {
// Programmatically creates the UI animation.
fn create(
@ -76,35 +45,40 @@ impl AnimationInfo {
let mut animation_clip = AnimationClip::default();
// 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_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"),
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",
),
),
);
// 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.
// This time we use a "custom property", which in this case animates TextColor under the assumption
// that it is in the "srgba" format.
animation_clip.add_curve_to_target(
animation_target_id,
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"),
AnimatableCurve::new(
TextColorProperty,
AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([
Srgba::RED,
Srgba::GREEN,
Srgba::BLUE,
Srgba::RED,
]))
.expect(
"should be able to build translation curve because we pass in valid samples",
),
),
);
// Save our animation clip as an asset.
@ -187,3 +161,37 @@ fn setup(
.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 bevy::{
animation::{AnimationTarget, AnimationTargetId},
animation::{animated_field, AnimationTarget, AnimationTargetId},
color::palettes::css::{ORANGE, SILVER},
math::vec3,
prelude::*,
@ -127,9 +127,14 @@ impl AnimationInfo {
.reparametrize_linear(interval(0.0, 4.0).unwrap())
.expect("this curve has bounded domain, so this should never fail");
animation_clip
.add_curve_to_target(animation_target_id, TranslationCurve(translation_curve));
animation_clip.add_curve_to_target(animation_target_id, RotationCurve(rotation_curve));
animation_clip.add_curve_to_target(
animation_target_id,
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.
let animation_clip_handle = animation_clips.add(animation_clip);