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:
parent
a6e13977d5
commit
af10aa38aa
@ -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",
|
||||
|
@ -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
|
||||
})
|
||||
};
|
||||
}
|
||||
|
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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`].
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
>(
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user