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"
|
ron = "0.8"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
blake3 = { version = "1.0" }
|
blake3 = { version = "1.0" }
|
||||||
|
downcast-rs = "1.2.0"
|
||||||
derive_more = { version = "1", default-features = false, features = [
|
derive_more = { version = "1", default-features = false, features = [
|
||||||
"error",
|
"error",
|
||||||
"from",
|
"from",
|
||||||
|
@ -22,30 +22,32 @@
|
|||||||
//!
|
//!
|
||||||
//! For instance, let's imagine that we want to use the `Vec3` output
|
//! For instance, let's imagine that we want to use the `Vec3` output
|
||||||
//! from our curve to animate the [translation component of a `Transform`]. For this, there is
|
//! from our curve to animate the [translation component of a `Transform`]. For this, there is
|
||||||
//! the adaptor [`TranslationCurve`], which wraps any `Curve<Vec3>` and turns it into an
|
//! the adaptor [`AnimatableCurve`], which wraps any [`Curve`] and [`AnimatableProperty`] and turns it into an
|
||||||
//! [`AnimationCurve`] that will use the given curve to animate the entity's translation:
|
//! [`AnimationCurve`] that will use the given curve to animate the entity's property:
|
||||||
//!
|
//!
|
||||||
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
|
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
|
||||||
//! # use bevy_math::vec3;
|
//! # use bevy_math::vec3;
|
||||||
//! # use bevy_animation::animation_curves::*;
|
//! # use bevy_transform::components::Transform;
|
||||||
|
//! # use bevy_animation::{animated_field, animation_curves::*};
|
||||||
//! # let wobble_curve = FunctionCurve::new(
|
//! # let wobble_curve = FunctionCurve::new(
|
||||||
//! # Interval::UNIT,
|
//! # Interval::UNIT,
|
||||||
//! # |t| vec3(t.cos(), 0.0, 0.0)
|
//! # |t| vec3(t.cos(), 0.0, 0.0)
|
||||||
//! # );
|
//! # );
|
||||||
//! let wobble_animation = TranslationCurve(wobble_curve);
|
//! let wobble_animation = AnimatableCurve::new(animated_field!(Transform::translation), wobble_curve);
|
||||||
//!
|
//!
|
||||||
//! And finally, this `AnimationCurve` needs to be added to an [`AnimationClip`] in order to
|
//! And finally, this [`AnimationCurve`] needs to be added to an [`AnimationClip`] in order to
|
||||||
//! actually animate something. This is what that looks like:
|
//! actually animate something. This is what that looks like:
|
||||||
//!
|
//!
|
||||||
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
|
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
|
||||||
//! # use bevy_animation::{AnimationClip, AnimationTargetId, animation_curves::*};
|
//! # use bevy_animation::{AnimationClip, AnimationTargetId, animated_field, animation_curves::*};
|
||||||
|
//! # use bevy_transform::components::Transform;
|
||||||
//! # use bevy_core::Name;
|
//! # use bevy_core::Name;
|
||||||
//! # use bevy_math::vec3;
|
//! # use bevy_math::vec3;
|
||||||
//! # let wobble_curve = FunctionCurve::new(
|
//! # let wobble_curve = FunctionCurve::new(
|
||||||
//! # Interval::UNIT,
|
//! # Interval::UNIT,
|
||||||
//! # |t| { vec3(t.cos(), 0.0, 0.0) },
|
//! # |t| { vec3(t.cos(), 0.0, 0.0) },
|
||||||
//! # );
|
//! # );
|
||||||
//! # let wobble_animation = TranslationCurve(wobble_curve);
|
//! # let wobble_animation = AnimatableCurve::new(animated_field!(Transform::translation), wobble_curve);
|
||||||
//! # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
//! # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||||
//! let mut animation_clip = AnimationClip::default();
|
//! let mut animation_clip = AnimationClip::default();
|
||||||
//! animation_clip.add_curve_to_target(
|
//! animation_clip.add_curve_to_target(
|
||||||
@ -59,22 +61,27 @@
|
|||||||
//! a [`Curve`], which produces time-related data of some kind, to an [`AnimationCurve`], which
|
//! a [`Curve`], which produces time-related data of some kind, to an [`AnimationCurve`], which
|
||||||
//! knows how to apply that data to an entity.
|
//! knows how to apply that data to an entity.
|
||||||
//!
|
//!
|
||||||
//! ## `Transform`
|
//! ## Animated Fields
|
||||||
//!
|
//!
|
||||||
//! [`Transform`] is special and has its own adaptors:
|
//! The [`animated_field`] macro (which returns an [`AnimatedField`]), in combination with [`AnimatableCurve`]
|
||||||
//! - [`TranslationCurve`], which uses `Vec3` output to animate [`Transform::translation`]
|
//! is the easiest way to make an animation curve (see the example above).
|
||||||
//! - [`RotationCurve`], which uses `Quat` output to animate [`Transform::rotation`]
|
|
||||||
//! - [`ScaleCurve`], which uses `Vec3` output to animate [`Transform::scale`]
|
|
||||||
//!
|
//!
|
||||||
//! ## Animatable properties
|
//! This will select a field on a component and pass it to a [`Curve`] with a type that matches the field.
|
||||||
//!
|
//!
|
||||||
//! Animation of arbitrary components can be accomplished using [`AnimatableProperty`] in
|
//! ## Animatable Properties
|
||||||
|
//!
|
||||||
|
//! Animation of arbitrary aspects of entities can be accomplished using [`AnimatableProperty`] in
|
||||||
//! conjunction with [`AnimatableCurve`]. See the documentation [there] for details.
|
//! conjunction with [`AnimatableCurve`]. See the documentation [there] for details.
|
||||||
//!
|
//!
|
||||||
|
//! ## Custom [`AnimationCurve`] and [`AnimationCurveEvaluator`]
|
||||||
|
//!
|
||||||
|
//! This is the lowest-level option with the most control, but it is also the most complicated.
|
||||||
|
//!
|
||||||
//! [using a function]: bevy_math::curve::FunctionCurve
|
//! [using a function]: bevy_math::curve::FunctionCurve
|
||||||
//! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation
|
//! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation
|
||||||
//! [`AnimationClip`]: crate::AnimationClip
|
//! [`AnimationClip`]: crate::AnimationClip
|
||||||
//! [there]: AnimatableProperty
|
//! [there]: AnimatableProperty
|
||||||
|
//! [`animated_field`]: crate::animated_field
|
||||||
|
|
||||||
use core::{
|
use core::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
@ -82,24 +89,22 @@ use core::{
|
|||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_ecs::{component::Component, world::Mut};
|
use bevy_ecs::component::Component;
|
||||||
use bevy_math::{
|
use bevy_math::curve::{
|
||||||
curve::{
|
cores::{UnevenCore, UnevenCoreError},
|
||||||
cores::{UnevenCore, UnevenCoreError},
|
iterable::IterableCurve,
|
||||||
iterable::IterableCurve,
|
Curve, Interval,
|
||||||
Curve, Interval,
|
|
||||||
},
|
|
||||||
Quat, Vec3,
|
|
||||||
};
|
};
|
||||||
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypePath};
|
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed};
|
||||||
use bevy_render::mesh::morph::MorphWeights;
|
use bevy_render::mesh::morph::MorphWeights;
|
||||||
use bevy_transform::prelude::Transform;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
graph::AnimationNodeIndex,
|
graph::AnimationNodeIndex,
|
||||||
prelude::{Animatable, BlendInput},
|
prelude::{Animatable, BlendInput},
|
||||||
AnimationEntityMut, AnimationEvaluationError,
|
AnimationEntityMut, AnimationEvaluationError,
|
||||||
};
|
};
|
||||||
|
use bevy_utils::Hashed;
|
||||||
|
use downcast_rs::{impl_downcast, Downcast};
|
||||||
|
|
||||||
/// A value on a component that Bevy can animate.
|
/// A value on a component that Bevy can animate.
|
||||||
///
|
///
|
||||||
@ -109,68 +114,154 @@ use crate::{
|
|||||||
/// to define the animation itself).
|
/// to define the animation itself).
|
||||||
/// For example, in order to animate field of view, you might use:
|
/// For example, in order to animate field of view, you might use:
|
||||||
///
|
///
|
||||||
/// # use bevy_animation::prelude::AnimatableProperty;
|
/// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||||
/// # use bevy_reflect::Reflect;
|
/// # use bevy_reflect::Reflect;
|
||||||
|
/// # use std::any::TypeId;
|
||||||
/// # use bevy_render::camera::PerspectiveProjection;
|
/// # use bevy_render::camera::PerspectiveProjection;
|
||||||
/// #[derive(Reflect)]
|
/// #[derive(Reflect)]
|
||||||
/// struct FieldOfViewProperty;
|
/// struct FieldOfViewProperty;
|
||||||
///
|
///
|
||||||
/// impl AnimatableProperty for FieldOfViewProperty {
|
/// impl AnimatableProperty for FieldOfViewProperty {
|
||||||
/// type Component = PerspectiveProjection;
|
|
||||||
/// type Property = f32;
|
/// type Property = f32;
|
||||||
/// fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
/// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||||
/// Some(&mut component.fov)
|
/// let component = entity
|
||||||
|
/// .get_mut::<PerspectiveProjection>()
|
||||||
|
/// .ok_or(
|
||||||
|
/// AnimationEvaluationError::ComponentNotPresent(
|
||||||
|
/// TypeId::of::<PerspectiveProjection>()
|
||||||
|
/// )
|
||||||
|
/// )?
|
||||||
|
/// .into_inner();
|
||||||
|
/// Ok(&mut component.fov)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn evaluator_id(&self) -> EvaluatorId {
|
||||||
|
/// EvaluatorId::Type(TypeId::of::<Self>())
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// You can then create an [`AnimationClip`] to animate this property like so:
|
/// You can then create an [`AnimationClip`] to animate this property like so:
|
||||||
///
|
///
|
||||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve};
|
/// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||||
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
|
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
|
||||||
/// # use bevy_core::Name;
|
/// # use bevy_core::Name;
|
||||||
/// # use bevy_reflect::Reflect;
|
/// # use bevy_reflect::Reflect;
|
||||||
/// # use bevy_render::camera::PerspectiveProjection;
|
/// # use bevy_render::camera::PerspectiveProjection;
|
||||||
|
/// # use std::any::TypeId;
|
||||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||||
/// # #[derive(Reflect)]
|
/// # #[derive(Reflect, Clone)]
|
||||||
/// # struct FieldOfViewProperty;
|
/// # struct FieldOfViewProperty;
|
||||||
/// # impl AnimatableProperty for FieldOfViewProperty {
|
/// # impl AnimatableProperty for FieldOfViewProperty {
|
||||||
/// # type Component = PerspectiveProjection;
|
/// # type Property = f32;
|
||||||
/// # type Property = f32;
|
/// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||||
/// # fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
/// # let component = entity
|
||||||
/// # Some(&mut component.fov)
|
/// # .get_mut::<PerspectiveProjection>()
|
||||||
/// # }
|
/// # .ok_or(
|
||||||
|
/// # AnimationEvaluationError::ComponentNotPresent(
|
||||||
|
/// # TypeId::of::<PerspectiveProjection>()
|
||||||
|
/// # )
|
||||||
|
/// # )?
|
||||||
|
/// # .into_inner();
|
||||||
|
/// # Ok(&mut component.fov)
|
||||||
|
/// # }
|
||||||
|
/// # fn evaluator_id(&self) -> EvaluatorId {
|
||||||
|
/// # EvaluatorId::Type(TypeId::of::<Self>())
|
||||||
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// let mut animation_clip = AnimationClip::default();
|
/// let mut animation_clip = AnimationClip::default();
|
||||||
/// animation_clip.add_curve_to_target(
|
/// animation_clip.add_curve_to_target(
|
||||||
/// animation_target_id,
|
/// animation_target_id,
|
||||||
/// AnimatableKeyframeCurve::new(
|
/// AnimatableCurve::new(
|
||||||
/// [
|
/// FieldOfViewProperty,
|
||||||
|
/// AnimatableKeyframeCurve::new([
|
||||||
/// (0.0, core::f32::consts::PI / 4.0),
|
/// (0.0, core::f32::consts::PI / 4.0),
|
||||||
/// (1.0, core::f32::consts::PI / 3.0),
|
/// (1.0, core::f32::consts::PI / 3.0),
|
||||||
/// ]
|
/// ]).expect("Failed to create font size curve")
|
||||||
/// )
|
/// )
|
||||||
/// .map(AnimatableCurve::<FieldOfViewProperty, _>::from_curve)
|
|
||||||
/// .expect("Failed to create font size curve")
|
|
||||||
/// );
|
/// );
|
||||||
///
|
///
|
||||||
/// Here, the use of [`AnimatableKeyframeCurve`] creates a curve out of the given keyframe time-value
|
/// Here, the use of [`AnimatableKeyframeCurve`] creates a curve out of the given keyframe time-value
|
||||||
/// pairs, using the [`Animatable`] implementation of `f32` to interpolate between them. The
|
/// pairs, using the [`Animatable`] implementation of `f32` to interpolate between them. The
|
||||||
/// invocation of [`AnimatableCurve::from_curve`] with `FieldOfViewProperty` indicates that the `f32`
|
/// invocation of [`AnimatableCurve::new`] with `FieldOfViewProperty` indicates that the `f32`
|
||||||
/// output from that curve is to be used to animate the font size of a `PerspectiveProjection` component (as
|
/// output from that curve is to be used to animate the font size of a `PerspectiveProjection` component (as
|
||||||
/// configured above).
|
/// configured above).
|
||||||
///
|
///
|
||||||
/// [`AnimationClip`]: crate::AnimationClip
|
/// [`AnimationClip`]: crate::AnimationClip
|
||||||
pub trait AnimatableProperty: Reflect + TypePath {
|
pub trait AnimatableProperty: Send + Sync + 'static {
|
||||||
/// The type of the component that the property lives on.
|
/// The animated property type.
|
||||||
type Component: Component;
|
type Property: Animatable;
|
||||||
|
|
||||||
/// The type of the property to be animated.
|
/// Retrieves the property from the given `entity`.
|
||||||
type Property: Animatable + FromReflect + Reflectable + Clone + Sync + Debug;
|
fn get_mut<'a>(
|
||||||
|
&self,
|
||||||
|
entity: &'a mut AnimationEntityMut,
|
||||||
|
) -> Result<&'a mut Self::Property, AnimationEvaluationError>;
|
||||||
|
|
||||||
/// Given a reference to the component, returns a reference to the property.
|
/// The [`EvaluatorId`] used to look up the [`AnimationCurveEvaluator`] for this [`AnimatableProperty`].
|
||||||
|
/// For a given animated property, this ID should always be the same to allow things like animation blending to occur.
|
||||||
|
fn evaluator_id(&self) -> EvaluatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Component`] field that can be animated, defined by a function that reads the component and returns
|
||||||
|
/// the accessed field / property.
|
||||||
|
///
|
||||||
|
/// The best way to create an instance of this type is via the [`animated_field`] macro.
|
||||||
|
///
|
||||||
|
/// `C` is the component being animated, `A` is the type of the [`Animatable`] field on the component, and `F` is an accessor
|
||||||
|
/// function that accepts a reference to `C` and retrieves the field `A`.
|
||||||
|
///
|
||||||
|
/// [`animated_field`]: crate::animated_field
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AnimatedField<C, A, F: Fn(&mut C) -> &mut A> {
|
||||||
|
func: F,
|
||||||
|
/// A pre-hashed (component-type-id, reflected-field-index) pair, uniquely identifying a component field
|
||||||
|
evaluator_id: Hashed<(TypeId, usize)>,
|
||||||
|
marker: PhantomData<(C, A)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, A, F> AnimatableProperty for AnimatedField<C, A, F>
|
||||||
|
where
|
||||||
|
C: Component,
|
||||||
|
A: Animatable + Clone + Sync + Debug,
|
||||||
|
F: Fn(&mut C) -> &mut A + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
type Property = A;
|
||||||
|
fn get_mut<'a>(
|
||||||
|
&self,
|
||||||
|
entity: &'a mut AnimationEntityMut,
|
||||||
|
) -> Result<&'a mut A, AnimationEvaluationError> {
|
||||||
|
let c = entity
|
||||||
|
.get_mut::<C>()
|
||||||
|
.ok_or_else(|| AnimationEvaluationError::ComponentNotPresent(TypeId::of::<C>()))?;
|
||||||
|
Ok((self.func)(c.into_inner()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluator_id(&self) -> EvaluatorId {
|
||||||
|
EvaluatorId::ComponentField(&self.evaluator_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Typed, P, F: Fn(&mut C) -> &mut P + 'static> AnimatedField<C, P, F> {
|
||||||
|
/// Creates a new instance of [`AnimatedField`]. This operates under the assumption that
|
||||||
|
/// `C` is a reflect-able struct with named fields, and that `field_name` is a valid field on that struct.
|
||||||
///
|
///
|
||||||
/// If the property couldn't be found, returns `None`.
|
/// # Panics
|
||||||
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property>;
|
/// If the type of `C` is not a struct with named fields or if the `field_name` does not exist.
|
||||||
|
pub fn new_unchecked(field_name: &str, func: F) -> Self {
|
||||||
|
let TypeInfo::Struct(struct_info) = C::type_info() else {
|
||||||
|
panic!("Only structs are supported in `AnimatedField::new_unchecked`")
|
||||||
|
};
|
||||||
|
|
||||||
|
let field_index = struct_info
|
||||||
|
.index_of(field_name)
|
||||||
|
.expect("Field name should exist");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
func,
|
||||||
|
evaluator_id: Hashed::new((TypeId::of::<C>(), field_index)),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait collects the additional requirements on top of [`Curve<T>`] needed for a
|
/// This trait collects the additional requirements on top of [`Curve<T>`] needed for a
|
||||||
@ -187,12 +278,14 @@ impl<T, C> AnimationCompatibleCurve<T> for C where C: Curve<T> + Debug + Clone +
|
|||||||
#[derive(Reflect, FromReflect)]
|
#[derive(Reflect, FromReflect)]
|
||||||
#[reflect(from_reflect = false)]
|
#[reflect(from_reflect = false)]
|
||||||
pub struct AnimatableCurve<P, C> {
|
pub struct AnimatableCurve<P, C> {
|
||||||
|
/// The property selector, which defines what component to access and how to access
|
||||||
|
/// a property on that component.
|
||||||
|
pub property: P,
|
||||||
|
|
||||||
/// The inner [curve] whose values are used to animate the property.
|
/// The inner [curve] whose values are used to animate the property.
|
||||||
///
|
///
|
||||||
/// [curve]: Curve
|
/// [curve]: Curve
|
||||||
pub curve: C,
|
pub curve: C,
|
||||||
#[reflect(ignore)]
|
|
||||||
_phantom: PhantomData<P>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`AnimatableCurveEvaluator`] for [`AnimatableProperty`] instances.
|
/// An [`AnimatableCurveEvaluator`] for [`AnimatableProperty`] instances.
|
||||||
@ -200,13 +293,9 @@ pub struct AnimatableCurve<P, C> {
|
|||||||
/// You shouldn't ordinarily need to instantiate one of these manually. Bevy
|
/// You shouldn't ordinarily need to instantiate one of these manually. Bevy
|
||||||
/// will automatically do so when you use an [`AnimatableCurve`] instance.
|
/// will automatically do so when you use an [`AnimatableCurve`] instance.
|
||||||
#[derive(Reflect)]
|
#[derive(Reflect)]
|
||||||
pub struct AnimatableCurveEvaluator<P>
|
pub struct AnimatableCurveEvaluator<A: Animatable> {
|
||||||
where
|
evaluator: BasicAnimationCurveEvaluator<A>,
|
||||||
P: AnimatableProperty,
|
property: Box<dyn AnimatableProperty<Property = A>>,
|
||||||
{
|
|
||||||
evaluator: BasicAnimationCurveEvaluator<P::Property>,
|
|
||||||
#[reflect(ignore)]
|
|
||||||
phantom: PhantomData<P>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, C> AnimatableCurve<P, C>
|
impl<P, C> AnimatableCurve<P, C>
|
||||||
@ -218,22 +307,20 @@ where
|
|||||||
/// valued in an [animatable property].
|
/// valued in an [animatable property].
|
||||||
///
|
///
|
||||||
/// [animatable property]: AnimatableProperty::Property
|
/// [animatable property]: AnimatableProperty::Property
|
||||||
pub fn from_curve(curve: C) -> Self {
|
pub fn new(property: P, curve: C) -> Self {
|
||||||
Self {
|
Self { property, curve }
|
||||||
curve,
|
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, C> Clone for AnimatableCurve<P, C>
|
impl<P, C> Clone for AnimatableCurve<P, C>
|
||||||
where
|
where
|
||||||
C: Clone,
|
C: Clone,
|
||||||
|
P: Clone,
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
curve: self.curve.clone(),
|
curve: self.curve.clone(),
|
||||||
_phantom: PhantomData,
|
property: self.property.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,10 +336,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, C> AnimationCurve for AnimatableCurve<P, C>
|
impl<P: Send + Sync + 'static, C> AnimationCurve for AnimatableCurve<P, C>
|
||||||
where
|
where
|
||||||
P: AnimatableProperty,
|
P: AnimatableProperty + Clone,
|
||||||
C: AnimationCompatibleCurve<P::Property>,
|
C: AnimationCompatibleCurve<P::Property> + Clone,
|
||||||
{
|
{
|
||||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
@ -262,14 +349,14 @@ where
|
|||||||
self.curve.domain()
|
self.curve.domain()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluator_type(&self) -> TypeId {
|
fn evaluator_id(&self) -> EvaluatorId {
|
||||||
TypeId::of::<AnimatableCurveEvaluator<P>>()
|
self.property.evaluator_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
|
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
|
||||||
Box::new(AnimatableCurveEvaluator {
|
Box::new(AnimatableCurveEvaluator::<P::Property> {
|
||||||
evaluator: BasicAnimationCurveEvaluator::default(),
|
evaluator: BasicAnimationCurveEvaluator::default(),
|
||||||
phantom: PhantomData::<P>,
|
property: Box::new(self.property.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,8 +367,8 @@ where
|
|||||||
weight: f32,
|
weight: f32,
|
||||||
graph_node: AnimationNodeIndex,
|
graph_node: AnimationNodeIndex,
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
|
let curve_evaluator = curve_evaluator
|
||||||
.downcast_mut::<AnimatableCurveEvaluator<P>>()
|
.downcast_mut::<AnimatableCurveEvaluator<P::Property>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let value = self.curve.sample_clamped(t);
|
let value = self.curve.sample_clamped(t);
|
||||||
curve_evaluator
|
curve_evaluator
|
||||||
@ -296,10 +383,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> AnimationCurveEvaluator for AnimatableCurveEvaluator<P>
|
impl<A: Animatable> AnimationCurveEvaluator for AnimatableCurveEvaluator<A> {
|
||||||
where
|
|
||||||
P: AnimatableProperty,
|
|
||||||
{
|
|
||||||
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
||||||
self.evaluator.combine(graph_node, /*additive=*/ false)
|
self.evaluator.combine(graph_node, /*additive=*/ false)
|
||||||
}
|
}
|
||||||
@ -318,310 +402,14 @@ where
|
|||||||
|
|
||||||
fn commit<'a>(
|
fn commit<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Mut<'a, Transform>>,
|
|
||||||
mut entity: AnimationEntityMut<'a>,
|
mut entity: AnimationEntityMut<'a>,
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
let mut component = entity.get_mut::<P::Component>().ok_or_else(|| {
|
let property = self.property.get_mut(&mut entity)?;
|
||||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<P::Component>())
|
|
||||||
})?;
|
|
||||||
let property = P::get_mut(&mut component)
|
|
||||||
.ok_or_else(|| AnimationEvaluationError::PropertyNotPresent(TypeId::of::<P>()))?;
|
|
||||||
*property = self
|
*property = self
|
||||||
.evaluator
|
.evaluator
|
||||||
.stack
|
.stack
|
||||||
.pop()
|
.pop()
|
||||||
.ok_or_else(inconsistent::<AnimatableCurveEvaluator<P>>)?
|
.ok_or_else(inconsistent::<AnimatableCurveEvaluator<A>>)?
|
||||||
.value;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This type allows a [curve] valued in `Vec3` to become an [`AnimationCurve`] that animates
|
|
||||||
/// the translation component of a transform.
|
|
||||||
///
|
|
||||||
/// [curve]: Curve
|
|
||||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
|
||||||
#[reflect(from_reflect = false)]
|
|
||||||
pub struct TranslationCurve<C>(pub C);
|
|
||||||
|
|
||||||
/// An [`AnimationCurveEvaluator`] for use with [`TranslationCurve`]s.
|
|
||||||
///
|
|
||||||
/// You shouldn't need to instantiate this manually; Bevy will automatically do
|
|
||||||
/// so.
|
|
||||||
#[derive(Reflect)]
|
|
||||||
pub struct TranslationCurveEvaluator {
|
|
||||||
evaluator: BasicAnimationCurveEvaluator<Vec3>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> AnimationCurve for TranslationCurve<C>
|
|
||||||
where
|
|
||||||
C: AnimationCompatibleCurve<Vec3>,
|
|
||||||
{
|
|
||||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn domain(&self) -> Interval {
|
|
||||||
self.0.domain()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluator_type(&self) -> TypeId {
|
|
||||||
TypeId::of::<TranslationCurveEvaluator>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
|
|
||||||
Box::new(TranslationCurveEvaluator {
|
|
||||||
evaluator: BasicAnimationCurveEvaluator::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply(
|
|
||||||
&self,
|
|
||||||
curve_evaluator: &mut dyn AnimationCurveEvaluator,
|
|
||||||
t: f32,
|
|
||||||
weight: f32,
|
|
||||||
graph_node: AnimationNodeIndex,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
|
|
||||||
.downcast_mut::<TranslationCurveEvaluator>()
|
|
||||||
.unwrap();
|
|
||||||
let value = self.0.sample_clamped(t);
|
|
||||||
curve_evaluator
|
|
||||||
.evaluator
|
|
||||||
.stack
|
|
||||||
.push(BasicAnimationCurveEvaluatorStackElement {
|
|
||||||
value,
|
|
||||||
weight,
|
|
||||||
graph_node,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimationCurveEvaluator for TranslationCurveEvaluator {
|
|
||||||
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.combine(graph_node, /*additive=*/ false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.combine(graph_node, /*additive=*/ true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_blend_register(
|
|
||||||
&mut self,
|
|
||||||
weight: f32,
|
|
||||||
graph_node: AnimationNodeIndex,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.push_blend_register(weight, graph_node)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit<'a>(
|
|
||||||
&mut self,
|
|
||||||
transform: Option<Mut<'a, Transform>>,
|
|
||||||
_: AnimationEntityMut<'a>,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
let mut component = transform.ok_or_else(|| {
|
|
||||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
|
||||||
})?;
|
|
||||||
component.translation = self
|
|
||||||
.evaluator
|
|
||||||
.stack
|
|
||||||
.pop()
|
|
||||||
.ok_or_else(inconsistent::<TranslationCurveEvaluator>)?
|
|
||||||
.value;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This type allows a [curve] valued in `Quat` to become an [`AnimationCurve`] that animates
|
|
||||||
/// the rotation component of a transform.
|
|
||||||
///
|
|
||||||
/// [curve]: Curve
|
|
||||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
|
||||||
#[reflect(from_reflect = false)]
|
|
||||||
pub struct RotationCurve<C>(pub C);
|
|
||||||
|
|
||||||
/// An [`AnimationCurveEvaluator`] for use with [`RotationCurve`]s.
|
|
||||||
///
|
|
||||||
/// You shouldn't need to instantiate this manually; Bevy will automatically do
|
|
||||||
/// so.
|
|
||||||
#[derive(Reflect)]
|
|
||||||
pub struct RotationCurveEvaluator {
|
|
||||||
evaluator: BasicAnimationCurveEvaluator<Quat>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> AnimationCurve for RotationCurve<C>
|
|
||||||
where
|
|
||||||
C: AnimationCompatibleCurve<Quat>,
|
|
||||||
{
|
|
||||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn domain(&self) -> Interval {
|
|
||||||
self.0.domain()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluator_type(&self) -> TypeId {
|
|
||||||
TypeId::of::<RotationCurveEvaluator>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
|
|
||||||
Box::new(RotationCurveEvaluator {
|
|
||||||
evaluator: BasicAnimationCurveEvaluator::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply(
|
|
||||||
&self,
|
|
||||||
curve_evaluator: &mut dyn AnimationCurveEvaluator,
|
|
||||||
t: f32,
|
|
||||||
weight: f32,
|
|
||||||
graph_node: AnimationNodeIndex,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
|
|
||||||
.downcast_mut::<RotationCurveEvaluator>()
|
|
||||||
.unwrap();
|
|
||||||
let value = self.0.sample_clamped(t);
|
|
||||||
curve_evaluator
|
|
||||||
.evaluator
|
|
||||||
.stack
|
|
||||||
.push(BasicAnimationCurveEvaluatorStackElement {
|
|
||||||
value,
|
|
||||||
weight,
|
|
||||||
graph_node,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimationCurveEvaluator for RotationCurveEvaluator {
|
|
||||||
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.combine(graph_node, /*additive=*/ false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.combine(graph_node, /*additive=*/ true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_blend_register(
|
|
||||||
&mut self,
|
|
||||||
weight: f32,
|
|
||||||
graph_node: AnimationNodeIndex,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.push_blend_register(weight, graph_node)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit<'a>(
|
|
||||||
&mut self,
|
|
||||||
transform: Option<Mut<'a, Transform>>,
|
|
||||||
_: AnimationEntityMut<'a>,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
let mut component = transform.ok_or_else(|| {
|
|
||||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
|
||||||
})?;
|
|
||||||
component.rotation = self
|
|
||||||
.evaluator
|
|
||||||
.stack
|
|
||||||
.pop()
|
|
||||||
.ok_or_else(inconsistent::<RotationCurveEvaluator>)?
|
|
||||||
.value;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This type allows a [curve] valued in `Vec3` to become an [`AnimationCurve`] that animates
|
|
||||||
/// the scale component of a transform.
|
|
||||||
///
|
|
||||||
/// [curve]: Curve
|
|
||||||
#[derive(Debug, Clone, Reflect, FromReflect)]
|
|
||||||
#[reflect(from_reflect = false)]
|
|
||||||
pub struct ScaleCurve<C>(pub C);
|
|
||||||
|
|
||||||
/// An [`AnimationCurveEvaluator`] for use with [`ScaleCurve`]s.
|
|
||||||
///
|
|
||||||
/// You shouldn't need to instantiate this manually; Bevy will automatically do
|
|
||||||
/// so.
|
|
||||||
#[derive(Reflect)]
|
|
||||||
pub struct ScaleCurveEvaluator {
|
|
||||||
evaluator: BasicAnimationCurveEvaluator<Vec3>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> AnimationCurve for ScaleCurve<C>
|
|
||||||
where
|
|
||||||
C: AnimationCompatibleCurve<Vec3>,
|
|
||||||
{
|
|
||||||
fn clone_value(&self) -> Box<dyn AnimationCurve> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn domain(&self) -> Interval {
|
|
||||||
self.0.domain()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluator_type(&self) -> TypeId {
|
|
||||||
TypeId::of::<ScaleCurveEvaluator>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
|
|
||||||
Box::new(ScaleCurveEvaluator {
|
|
||||||
evaluator: BasicAnimationCurveEvaluator::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply(
|
|
||||||
&self,
|
|
||||||
curve_evaluator: &mut dyn AnimationCurveEvaluator,
|
|
||||||
t: f32,
|
|
||||||
weight: f32,
|
|
||||||
graph_node: AnimationNodeIndex,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
|
|
||||||
.downcast_mut::<ScaleCurveEvaluator>()
|
|
||||||
.unwrap();
|
|
||||||
let value = self.0.sample_clamped(t);
|
|
||||||
curve_evaluator
|
|
||||||
.evaluator
|
|
||||||
.stack
|
|
||||||
.push(BasicAnimationCurveEvaluatorStackElement {
|
|
||||||
value,
|
|
||||||
weight,
|
|
||||||
graph_node,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimationCurveEvaluator for ScaleCurveEvaluator {
|
|
||||||
fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.combine(graph_node, /*additive=*/ false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.combine(graph_node, /*additive=*/ true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_blend_register(
|
|
||||||
&mut self,
|
|
||||||
weight: f32,
|
|
||||||
graph_node: AnimationNodeIndex,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
self.evaluator.push_blend_register(weight, graph_node)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit<'a>(
|
|
||||||
&mut self,
|
|
||||||
transform: Option<Mut<'a, Transform>>,
|
|
||||||
_: AnimationEntityMut<'a>,
|
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
|
||||||
let mut component = transform.ok_or_else(|| {
|
|
||||||
AnimationEvaluationError::ComponentNotPresent(TypeId::of::<Transform>())
|
|
||||||
})?;
|
|
||||||
component.scale = self
|
|
||||||
.evaluator
|
|
||||||
.stack
|
|
||||||
.pop()
|
|
||||||
.ok_or_else(inconsistent::<ScaleCurveEvaluator>)?
|
|
||||||
.value;
|
.value;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -683,8 +471,8 @@ where
|
|||||||
self.0.domain()
|
self.0.domain()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluator_type(&self) -> TypeId {
|
fn evaluator_id(&self) -> EvaluatorId {
|
||||||
TypeId::of::<WeightsCurveEvaluator>()
|
EvaluatorId::Type(TypeId::of::<WeightsCurveEvaluator>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
|
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator> {
|
||||||
@ -704,7 +492,7 @@ where
|
|||||||
weight: f32,
|
weight: f32,
|
||||||
graph_node: AnimationNodeIndex,
|
graph_node: AnimationNodeIndex,
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
let curve_evaluator = (*Reflect::as_any_mut(curve_evaluator))
|
let curve_evaluator = curve_evaluator
|
||||||
.downcast_mut::<WeightsCurveEvaluator>()
|
.downcast_mut::<WeightsCurveEvaluator>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -802,7 +590,6 @@ impl AnimationCurveEvaluator for WeightsCurveEvaluator {
|
|||||||
|
|
||||||
fn commit<'a>(
|
fn commit<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Mut<'a, Transform>>,
|
|
||||||
mut entity: AnimationEntityMut<'a>,
|
mut entity: AnimationEntityMut<'a>,
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
if self.stack_morph_target_weights.is_empty() {
|
if self.stack_morph_target_weights.is_empty() {
|
||||||
@ -968,7 +755,7 @@ where
|
|||||||
/// mutated in the implementation of [`apply`].
|
/// mutated in the implementation of [`apply`].
|
||||||
///
|
///
|
||||||
/// [`apply`]: AnimationCurve::apply
|
/// [`apply`]: AnimationCurve::apply
|
||||||
pub trait AnimationCurve: Reflect + Debug + Send + Sync {
|
pub trait AnimationCurve: Debug + Send + Sync + 'static {
|
||||||
/// Returns a boxed clone of this value.
|
/// Returns a boxed clone of this value.
|
||||||
fn clone_value(&self) -> Box<dyn AnimationCurve>;
|
fn clone_value(&self) -> Box<dyn AnimationCurve>;
|
||||||
|
|
||||||
@ -979,14 +766,14 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
|
|||||||
///
|
///
|
||||||
/// This must match the type returned by [`Self::create_evaluator`]. It must
|
/// This must match the type returned by [`Self::create_evaluator`]. It must
|
||||||
/// be a single type that doesn't depend on the type of the curve.
|
/// be a single type that doesn't depend on the type of the curve.
|
||||||
fn evaluator_type(&self) -> TypeId;
|
fn evaluator_id(&self) -> EvaluatorId;
|
||||||
|
|
||||||
/// Returns a newly-instantiated [`AnimationCurveEvaluator`] for use with
|
/// Returns a newly-instantiated [`AnimationCurveEvaluator`] for use with
|
||||||
/// this curve.
|
/// this curve.
|
||||||
///
|
///
|
||||||
/// All curve types must return the same type of
|
/// All curve types must return the same type of
|
||||||
/// [`AnimationCurveEvaluator`]. The returned value must match the type
|
/// [`AnimationCurveEvaluator`]. The returned value must match the type
|
||||||
/// returned by [`Self::evaluator_type`].
|
/// returned by [`Self::evaluator_id`].
|
||||||
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator>;
|
fn create_evaluator(&self) -> Box<dyn AnimationCurveEvaluator>;
|
||||||
|
|
||||||
/// Samples the curve at the given time `t`, and pushes the sampled value
|
/// Samples the curve at the given time `t`, and pushes the sampled value
|
||||||
@ -996,7 +783,7 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
|
|||||||
/// [`Self::create_evaluator`], upcast to an `&mut dyn
|
/// [`Self::create_evaluator`], upcast to an `&mut dyn
|
||||||
/// AnimationCurveEvaluator`. Typically, implementations of [`Self::apply`]
|
/// AnimationCurveEvaluator`. Typically, implementations of [`Self::apply`]
|
||||||
/// will want to downcast the `curve_evaluator` parameter to the concrete
|
/// will want to downcast the `curve_evaluator` parameter to the concrete
|
||||||
/// type [`Self::evaluator_type`] in order to push values of the appropriate
|
/// type [`Self::evaluator_id`] in order to push values of the appropriate
|
||||||
/// type onto its evaluation stack.
|
/// type onto its evaluation stack.
|
||||||
///
|
///
|
||||||
/// Be sure not to confuse the `t` and `weight` values. The former
|
/// Be sure not to confuse the `t` and `weight` values. The former
|
||||||
@ -1012,6 +799,22 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
|
|||||||
) -> Result<(), AnimationEvaluationError>;
|
) -> Result<(), AnimationEvaluationError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The [`EvaluatorId`] is used to look up the [`AnimationCurveEvaluator`] for an [`AnimatableProperty`].
|
||||||
|
/// For a given animated property, this ID should always be the same to allow things like animation blending to occur.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum EvaluatorId<'a> {
|
||||||
|
/// Corresponds to a specific field on a specific component type.
|
||||||
|
/// The `TypeId` should correspond to the component type, and the `usize`
|
||||||
|
/// should correspond to the Reflect-ed field index of the field.
|
||||||
|
//
|
||||||
|
// IMPLEMENTATION NOTE: The Hashed<(TypeId, usize) is intentionally cheap to clone, as it will be cloned per frame by the evaluator
|
||||||
|
// Switching the field index `usize` for something like a field name `String` would probably be too expensive to justify
|
||||||
|
ComponentField(&'a Hashed<(TypeId, usize)>),
|
||||||
|
/// Corresponds to a custom property of a given type. This should be the [`TypeId`]
|
||||||
|
/// of the custom [`AnimatableProperty`].
|
||||||
|
Type(TypeId),
|
||||||
|
}
|
||||||
|
|
||||||
/// A low-level trait for use in [`crate::VariableCurve`] that provides fine
|
/// A low-level trait for use in [`crate::VariableCurve`] that provides fine
|
||||||
/// control over how animations are evaluated.
|
/// control over how animations are evaluated.
|
||||||
///
|
///
|
||||||
@ -1031,7 +834,9 @@ pub trait AnimationCurve: Reflect + Debug + Send + Sync {
|
|||||||
/// translation keyframes. The stack stores intermediate values generated while
|
/// translation keyframes. The stack stores intermediate values generated while
|
||||||
/// evaluating the [`crate::graph::AnimationGraph`], while the blend register
|
/// evaluating the [`crate::graph::AnimationGraph`], while the blend register
|
||||||
/// stores the result of a blend operation.
|
/// stores the result of a blend operation.
|
||||||
pub trait AnimationCurveEvaluator: Reflect {
|
///
|
||||||
|
/// [`Vec3`]: bevy_math::Vec3
|
||||||
|
pub trait AnimationCurveEvaluator: Downcast + Send + Sync + 'static {
|
||||||
/// Blends the top element of the stack with the blend register.
|
/// Blends the top element of the stack with the blend register.
|
||||||
///
|
///
|
||||||
/// The semantics of this method are as follows:
|
/// The semantics of this method are as follows:
|
||||||
@ -1094,11 +899,12 @@ pub trait AnimationCurveEvaluator: Reflect {
|
|||||||
/// the stack, not blended with it.
|
/// the stack, not blended with it.
|
||||||
fn commit<'a>(
|
fn commit<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
transform: Option<Mut<'a, Transform>>,
|
|
||||||
entity: AnimationEntityMut<'a>,
|
entity: AnimationEntityMut<'a>,
|
||||||
) -> Result<(), AnimationEvaluationError>;
|
) -> Result<(), AnimationEvaluationError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_downcast!(AnimationCurveEvaluator);
|
||||||
|
|
||||||
/// A [curve] defined by keyframes with values in an [animatable] type.
|
/// A [curve] defined by keyframes with values in an [animatable] type.
|
||||||
///
|
///
|
||||||
/// The keyframes are interpolated using the type's [`Animatable::interpolate`] implementation.
|
/// The keyframes are interpolated using the type's [`Animatable::interpolate`] implementation.
|
||||||
@ -1153,3 +959,28 @@ where
|
|||||||
{
|
{
|
||||||
AnimationEvaluationError::InconsistentEvaluatorImplementation(TypeId::of::<P>())
|
AnimationEvaluationError::InconsistentEvaluatorImplementation(TypeId::of::<P>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an [`AnimatedField`] with a given `$component` and `$field`.
|
||||||
|
///
|
||||||
|
/// This can be used in the following way:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_animation::{animation_curves::AnimatedField, animated_field};
|
||||||
|
/// # use bevy_ecs::component::Component;
|
||||||
|
/// # use bevy_math::Vec3;
|
||||||
|
/// # use bevy_reflect::Reflect;
|
||||||
|
/// #[derive(Component, Reflect)]
|
||||||
|
/// struct Transform {
|
||||||
|
/// translation: Vec3,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let field = animated_field!(Transform::translation);
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! animated_field {
|
||||||
|
($component:ident::$field:ident) => {
|
||||||
|
AnimatedField::new_unchecked(stringify!($field), |component: &mut $component| {
|
||||||
|
&mut component.$field
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -17,7 +17,7 @@ pub mod transition;
|
|||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use core::{
|
use core::{
|
||||||
any::{Any, TypeId},
|
any::TypeId,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
@ -26,7 +26,10 @@ use core::{
|
|||||||
use graph::AnimationNodeType;
|
use graph::AnimationNodeType;
|
||||||
use prelude::AnimationCurveEvaluator;
|
use prelude::AnimationCurveEvaluator;
|
||||||
|
|
||||||
use crate::graph::{AnimationGraphHandle, ThreadedAnimationGraphs};
|
use crate::{
|
||||||
|
graph::{AnimationGraphHandle, ThreadedAnimationGraphs},
|
||||||
|
prelude::EvaluatorId,
|
||||||
|
};
|
||||||
|
|
||||||
use bevy_app::{Animation, App, Plugin, PostUpdate};
|
use bevy_app::{Animation, App, Plugin, PostUpdate};
|
||||||
use bevy_asset::{Asset, AssetApp, Assets};
|
use bevy_asset::{Asset, AssetApp, Assets};
|
||||||
@ -38,18 +41,13 @@ use bevy_ecs::{
|
|||||||
world::EntityMutExcept,
|
world::EntityMutExcept,
|
||||||
};
|
};
|
||||||
use bevy_math::FloatOrd;
|
use bevy_math::FloatOrd;
|
||||||
use bevy_reflect::{
|
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
|
||||||
prelude::ReflectDefault, utility::NonGenericTypeInfoCell, ApplyError, DynamicTupleStruct,
|
|
||||||
FromReflect, FromType, GetTypeRegistration, PartialReflect, Reflect, ReflectFromPtr,
|
|
||||||
ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TupleStruct, TupleStructFieldIter,
|
|
||||||
TupleStructInfo, TypeInfo, TypePath, TypeRegistration, Typed, UnnamedField,
|
|
||||||
};
|
|
||||||
use bevy_time::Time;
|
use bevy_time::Time;
|
||||||
use bevy_transform::{prelude::Transform, TransformSystem};
|
use bevy_transform::TransformSystem;
|
||||||
use bevy_utils::{
|
use bevy_utils::{
|
||||||
hashbrown::HashMap,
|
hashbrown::HashMap,
|
||||||
tracing::{trace, warn},
|
tracing::{trace, warn},
|
||||||
NoOpHash, TypeIdMap,
|
NoOpHash, PreHashMap, PreHashMapExt, TypeIdMap,
|
||||||
};
|
};
|
||||||
use petgraph::graph::NodeIndex;
|
use petgraph::graph::NodeIndex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -100,175 +98,6 @@ impl VariableCurve {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have to implement `PartialReflect` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationCurve>`, which can't be automatically derived yet.
|
|
||||||
impl PartialReflect for VariableCurve {
|
|
||||||
#[inline]
|
|
||||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
|
||||||
Some(<Self as Typed>::type_info())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_partial_reflect(self: Box<Self>) -> Box<dyn PartialReflect> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_partial_reflect(&self) -> &dyn PartialReflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_into_reflect(self: Box<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_as_reflect(&self) -> Option<&dyn Reflect> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> {
|
|
||||||
if let ReflectRef::TupleStruct(tuple_value) = value.reflect_ref() {
|
|
||||||
for (i, value) in tuple_value.iter_fields().enumerate() {
|
|
||||||
if let Some(v) = self.field_mut(i) {
|
|
||||||
v.try_apply(value)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ApplyError::MismatchedKinds {
|
|
||||||
from_kind: value.reflect_kind(),
|
|
||||||
to_kind: ReflectKind::TupleStruct,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_ref(&self) -> ReflectRef {
|
|
||||||
ReflectRef::TupleStruct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_mut(&mut self) -> ReflectMut {
|
|
||||||
ReflectMut::TupleStruct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_owned(self: Box<Self>) -> ReflectOwned {
|
|
||||||
ReflectOwned::TupleStruct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_value(&self) -> Box<dyn PartialReflect> {
|
|
||||||
Box::new((*self).clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `Reflect` manually because of the embedded `Box<dyn
|
|
||||||
// AnimationCurve>`, which can't be automatically derived yet.
|
|
||||||
impl Reflect for VariableCurve {
|
|
||||||
#[inline]
|
|
||||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_reflect(&self) -> &dyn Reflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
|
|
||||||
*self = value.take()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `TupleStruct` manually because of the embedded `Box<dyn
|
|
||||||
// AnimationCurve>`, which can't be automatically derived yet.
|
|
||||||
impl TupleStruct for VariableCurve {
|
|
||||||
fn field(&self, index: usize) -> Option<&dyn PartialReflect> {
|
|
||||||
match index {
|
|
||||||
0 => Some(self.0.as_partial_reflect()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> {
|
|
||||||
match index {
|
|
||||||
0 => Some(self.0.as_partial_reflect_mut()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_len(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn iter_fields(&self) -> TupleStructFieldIter {
|
|
||||||
TupleStructFieldIter::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_dynamic(&self) -> DynamicTupleStruct {
|
|
||||||
DynamicTupleStruct::from_iter([PartialReflect::clone_value(&*self.0)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `FromReflect` manually because of the embedded `Box<dyn
|
|
||||||
// AnimationCurve>`, which can't be automatically derived yet.
|
|
||||||
impl FromReflect for VariableCurve {
|
|
||||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
|
||||||
Some(reflect.try_downcast_ref::<VariableCurve>()?.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `GetTypeRegistration` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationCurve>`, which can't be automatically derived yet.
|
|
||||||
impl GetTypeRegistration for VariableCurve {
|
|
||||||
fn get_type_registration() -> TypeRegistration {
|
|
||||||
let mut registration = TypeRegistration::of::<Self>();
|
|
||||||
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
|
||||||
registration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `Typed` manually because of the embedded `Box<dyn
|
|
||||||
// AnimationCurve>`, which can't be automatically derived yet.
|
|
||||||
impl Typed for VariableCurve {
|
|
||||||
fn type_info() -> &'static TypeInfo {
|
|
||||||
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
|
||||||
CELL.get_or_set(|| {
|
|
||||||
TypeInfo::TupleStruct(TupleStructInfo::new::<Self>(&[UnnamedField::new::<()>(0)]))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of [`VariableCurve`]s and the [`AnimationTargetId`]s to which they
|
/// A list of [`VariableCurve`]s and the [`AnimationTargetId`]s to which they
|
||||||
/// apply.
|
/// apply.
|
||||||
///
|
///
|
||||||
@ -276,6 +105,8 @@ impl Typed for VariableCurve {
|
|||||||
/// [`AnimationTarget`] with that ID.
|
/// [`AnimationTarget`] with that ID.
|
||||||
#[derive(Asset, Reflect, Clone, Debug, Default)]
|
#[derive(Asset, Reflect, Clone, Debug, Default)]
|
||||||
pub struct AnimationClip {
|
pub struct AnimationClip {
|
||||||
|
// This field is ignored by reflection because AnimationCurves can contain things that are not reflect-able
|
||||||
|
#[reflect(ignore)]
|
||||||
curves: AnimationCurves,
|
curves: AnimationCurves,
|
||||||
events: AnimationEvents,
|
events: AnimationEvents,
|
||||||
duration: f32,
|
duration: f32,
|
||||||
@ -878,23 +709,106 @@ pub struct AnimationEvaluationState {
|
|||||||
/// Stores all [`AnimationCurveEvaluator`]s corresponding to properties that
|
/// Stores all [`AnimationCurveEvaluator`]s corresponding to properties that
|
||||||
/// we've seen so far.
|
/// we've seen so far.
|
||||||
///
|
///
|
||||||
/// This is a mapping from the type ID of an animation curve evaluator to
|
/// This is a mapping from the id of an animation curve evaluator to
|
||||||
/// the animation curve evaluator itself.
|
/// the animation curve evaluator itself.
|
||||||
///
|
|
||||||
/// For efficiency's sake, the [`AnimationCurveEvaluator`]s are cached from
|
/// For efficiency's sake, the [`AnimationCurveEvaluator`]s are cached from
|
||||||
/// frame to frame and animation target to animation target. Therefore,
|
/// frame to frame and animation target to animation target. Therefore,
|
||||||
/// there may be entries in this list corresponding to properties that the
|
/// there may be entries in this list corresponding to properties that the
|
||||||
/// current [`AnimationPlayer`] doesn't animate. To iterate only over the
|
/// current [`AnimationPlayer`] doesn't animate. To iterate only over the
|
||||||
/// properties that are currently being animated, consult the
|
/// properties that are currently being animated, consult the
|
||||||
/// [`Self::current_curve_evaluator_types`] set.
|
/// [`Self::current_evaluators`] set.
|
||||||
curve_evaluators: TypeIdMap<Box<dyn AnimationCurveEvaluator>>,
|
evaluators: AnimationCurveEvaluators,
|
||||||
|
|
||||||
/// The set of [`AnimationCurveEvaluator`] types that the current
|
/// The set of [`AnimationCurveEvaluator`] types that the current
|
||||||
/// [`AnimationPlayer`] is animating.
|
/// [`AnimationPlayer`] is animating.
|
||||||
///
|
///
|
||||||
/// This is built up as new curve evaluators are encountered during graph
|
/// This is built up as new curve evaluators are encountered during graph
|
||||||
/// traversal.
|
/// traversal.
|
||||||
current_curve_evaluator_types: TypeIdMap<()>,
|
current_evaluators: CurrentEvaluators,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AnimationCurveEvaluators {
|
||||||
|
component_property_curve_evaluators:
|
||||||
|
PreHashMap<(TypeId, usize), Box<dyn AnimationCurveEvaluator>>,
|
||||||
|
type_id_curve_evaluators: TypeIdMap<Box<dyn AnimationCurveEvaluator>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationCurveEvaluators {
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn get_mut(&mut self, id: EvaluatorId) -> Option<&mut dyn AnimationCurveEvaluator> {
|
||||||
|
match id {
|
||||||
|
EvaluatorId::ComponentField(component_property) => self
|
||||||
|
.component_property_curve_evaluators
|
||||||
|
.get_mut(component_property),
|
||||||
|
EvaluatorId::Type(type_id) => self.type_id_curve_evaluators.get_mut(&type_id),
|
||||||
|
}
|
||||||
|
.map(|e| &mut **e)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn get_or_insert_with(
|
||||||
|
&mut self,
|
||||||
|
id: EvaluatorId,
|
||||||
|
func: impl FnOnce() -> Box<dyn AnimationCurveEvaluator>,
|
||||||
|
) -> &mut dyn AnimationCurveEvaluator {
|
||||||
|
match id {
|
||||||
|
EvaluatorId::ComponentField(component_property) => &mut **self
|
||||||
|
.component_property_curve_evaluators
|
||||||
|
.get_or_insert_with(component_property, func),
|
||||||
|
EvaluatorId::Type(type_id) => match self.type_id_curve_evaluators.entry(type_id) {
|
||||||
|
bevy_utils::hashbrown::hash_map::Entry::Occupied(occupied_entry) => {
|
||||||
|
&mut **occupied_entry.into_mut()
|
||||||
|
}
|
||||||
|
bevy_utils::hashbrown::hash_map::Entry::Vacant(vacant_entry) => {
|
||||||
|
&mut **vacant_entry.insert(func())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CurrentEvaluators {
|
||||||
|
component_properties: PreHashMap<(TypeId, usize), ()>,
|
||||||
|
type_ids: TypeIdMap<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CurrentEvaluators {
|
||||||
|
pub(crate) fn keys(&self) -> impl Iterator<Item = EvaluatorId> {
|
||||||
|
self.component_properties
|
||||||
|
.keys()
|
||||||
|
.map(EvaluatorId::ComponentField)
|
||||||
|
.chain(self.type_ids.keys().copied().map(EvaluatorId::Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear(
|
||||||
|
&mut self,
|
||||||
|
mut visit: impl FnMut(EvaluatorId) -> Result<(), AnimationEvaluationError>,
|
||||||
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
|
for (key, _) in self.component_properties.drain() {
|
||||||
|
(visit)(EvaluatorId::ComponentField(&key))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, _) in self.type_ids.drain() {
|
||||||
|
(visit)(EvaluatorId::Type(key))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn insert(&mut self, id: EvaluatorId) {
|
||||||
|
match id {
|
||||||
|
EvaluatorId::ComponentField(component_property) => {
|
||||||
|
self.component_properties.insert(*component_property, ());
|
||||||
|
}
|
||||||
|
EvaluatorId::Type(type_id) => {
|
||||||
|
self.type_ids.insert(type_id, ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnimationPlayer {
|
impl AnimationPlayer {
|
||||||
@ -1117,15 +1031,8 @@ pub fn advance_animations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A type alias for [`EntityMutExcept`] as used in animation.
|
/// A type alias for [`EntityMutExcept`] as used in animation.
|
||||||
pub type AnimationEntityMut<'w> = EntityMutExcept<
|
pub type AnimationEntityMut<'w> =
|
||||||
'w,
|
EntityMutExcept<'w, (AnimationTarget, AnimationPlayer, AnimationGraphHandle)>;
|
||||||
(
|
|
||||||
AnimationTarget,
|
|
||||||
Transform,
|
|
||||||
AnimationPlayer,
|
|
||||||
AnimationGraphHandle,
|
|
||||||
),
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// A system that modifies animation targets (e.g. bones in a skinned mesh)
|
/// A system that modifies animation targets (e.g. bones in a skinned mesh)
|
||||||
/// according to the currently-playing animations.
|
/// according to the currently-playing animations.
|
||||||
@ -1135,18 +1042,13 @@ pub fn animate_targets(
|
|||||||
graphs: Res<Assets<AnimationGraph>>,
|
graphs: Res<Assets<AnimationGraph>>,
|
||||||
threaded_animation_graphs: Res<ThreadedAnimationGraphs>,
|
threaded_animation_graphs: Res<ThreadedAnimationGraphs>,
|
||||||
players: Query<(&AnimationPlayer, &AnimationGraphHandle)>,
|
players: Query<(&AnimationPlayer, &AnimationGraphHandle)>,
|
||||||
mut targets: Query<(
|
mut targets: Query<(Entity, &AnimationTarget, AnimationEntityMut)>,
|
||||||
Entity,
|
|
||||||
&AnimationTarget,
|
|
||||||
Option<&mut Transform>,
|
|
||||||
AnimationEntityMut,
|
|
||||||
)>,
|
|
||||||
animation_evaluation_state: Local<ThreadLocal<RefCell<AnimationEvaluationState>>>,
|
animation_evaluation_state: Local<ThreadLocal<RefCell<AnimationEvaluationState>>>,
|
||||||
) {
|
) {
|
||||||
// Evaluate all animation targets in parallel.
|
// Evaluate all animation targets in parallel.
|
||||||
targets
|
targets
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each(|(entity, target, transform, entity_mut)| {
|
.for_each(|(entity, target, entity_mut)| {
|
||||||
let &AnimationTarget {
|
let &AnimationTarget {
|
||||||
id: target_id,
|
id: target_id,
|
||||||
player: player_id,
|
player: player_id,
|
||||||
@ -1300,19 +1202,20 @@ pub fn animate_targets(
|
|||||||
// will both yield a `RotationCurveEvaluator` and
|
// will both yield a `RotationCurveEvaluator` and
|
||||||
// therefore will share the same evaluator in this
|
// therefore will share the same evaluator in this
|
||||||
// table.
|
// table.
|
||||||
let curve_evaluator_type_id = (*curve.0).evaluator_type();
|
let curve_evaluator_id = (*curve.0).evaluator_id();
|
||||||
let curve_evaluator = evaluation_state
|
let curve_evaluator = evaluation_state
|
||||||
.curve_evaluators
|
.evaluators
|
||||||
.entry(curve_evaluator_type_id)
|
.get_or_insert_with(curve_evaluator_id.clone(), || {
|
||||||
.or_insert_with(|| curve.0.create_evaluator());
|
curve.0.create_evaluator()
|
||||||
|
});
|
||||||
|
|
||||||
evaluation_state
|
evaluation_state
|
||||||
.current_curve_evaluator_types
|
.current_evaluators
|
||||||
.insert(curve_evaluator_type_id, ());
|
.insert(curve_evaluator_id);
|
||||||
|
|
||||||
if let Err(err) = AnimationCurve::apply(
|
if let Err(err) = AnimationCurve::apply(
|
||||||
&*curve.0,
|
&*curve.0,
|
||||||
&mut **curve_evaluator,
|
curve_evaluator,
|
||||||
seek_time,
|
seek_time,
|
||||||
weight,
|
weight,
|
||||||
animation_graph_node_index,
|
animation_graph_node_index,
|
||||||
@ -1324,7 +1227,7 @@ pub fn animate_targets(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = evaluation_state.commit_all(transform, entity_mut) {
|
if let Err(err) = evaluation_state.commit_all(entity_mut) {
|
||||||
warn!("Animation application failed: {:?}", err);
|
warn!("Animation application failed: {:?}", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1425,8 +1328,8 @@ impl AnimationEvaluationState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
node_index: AnimationNodeIndex,
|
node_index: AnimationNodeIndex,
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
|
for curve_evaluator_type in self.current_evaluators.keys() {
|
||||||
self.curve_evaluators
|
self.evaluators
|
||||||
.get_mut(curve_evaluator_type)
|
.get_mut(curve_evaluator_type)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.blend(node_index)?;
|
.blend(node_index)?;
|
||||||
@ -1439,8 +1342,8 @@ impl AnimationEvaluationState {
|
|||||||
///
|
///
|
||||||
/// The given `node_index` is the node that we're evaluating.
|
/// The given `node_index` is the node that we're evaluating.
|
||||||
fn add_all(&mut self, node_index: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
fn add_all(&mut self, node_index: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> {
|
||||||
for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
|
for curve_evaluator_type in self.current_evaluators.keys() {
|
||||||
self.curve_evaluators
|
self.evaluators
|
||||||
.get_mut(curve_evaluator_type)
|
.get_mut(curve_evaluator_type)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add(node_index)?;
|
.add(node_index)?;
|
||||||
@ -1459,8 +1362,8 @@ impl AnimationEvaluationState {
|
|||||||
weight: f32,
|
weight: f32,
|
||||||
node_index: AnimationNodeIndex,
|
node_index: AnimationNodeIndex,
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
|
for curve_evaluator_type in self.current_evaluators.keys() {
|
||||||
self.curve_evaluators
|
self.evaluators
|
||||||
.get_mut(curve_evaluator_type)
|
.get_mut(curve_evaluator_type)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push_blend_register(weight, node_index)?;
|
.push_blend_register(weight, node_index)?;
|
||||||
@ -1475,19 +1378,14 @@ impl AnimationEvaluationState {
|
|||||||
/// components being animated.
|
/// components being animated.
|
||||||
fn commit_all(
|
fn commit_all(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut transform: Option<Mut<Transform>>,
|
|
||||||
mut entity_mut: AnimationEntityMut,
|
mut entity_mut: AnimationEntityMut,
|
||||||
) -> Result<(), AnimationEvaluationError> {
|
) -> Result<(), AnimationEvaluationError> {
|
||||||
for (curve_evaluator_type, _) in self.current_curve_evaluator_types.drain() {
|
self.current_evaluators.clear(|id| {
|
||||||
self.curve_evaluators
|
self.evaluators
|
||||||
.get_mut(&curve_evaluator_type)
|
.get_mut(id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.commit(
|
.commit(entity_mut.reborrow())
|
||||||
transform.as_mut().map(|transform| transform.reborrow()),
|
})
|
||||||
entity_mut.reborrow(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use alloc::collections::VecDeque;
|
use alloc::collections::VecDeque;
|
||||||
|
use bevy_animation::animated_field;
|
||||||
use bevy_asset::{
|
use bevy_asset::{
|
||||||
io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError,
|
io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError,
|
||||||
};
|
};
|
||||||
@ -310,12 +311,13 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
{
|
{
|
||||||
match outputs {
|
match outputs {
|
||||||
ReadOutputs::Translations(tr) => {
|
ReadOutputs::Translations(tr) => {
|
||||||
|
let translation_property = animated_field!(Transform::translation);
|
||||||
let translations: Vec<Vec3> = tr.map(Vec3::from).collect();
|
let translations: Vec<Vec3> = tr.map(Vec3::from).collect();
|
||||||
if keyframe_timestamps.len() == 1 {
|
if keyframe_timestamps.len() == 1 {
|
||||||
#[allow(clippy::unnecessary_map_on_constructor)]
|
Some(VariableCurve::new(AnimatableCurve::new(
|
||||||
Some(ConstantCurve::new(Interval::EVERYWHERE, translations[0]))
|
translation_property,
|
||||||
.map(TranslationCurve)
|
ConstantCurve::new(Interval::EVERYWHERE, translations[0]),
|
||||||
.map(VariableCurve::new)
|
)))
|
||||||
} else {
|
} else {
|
||||||
match interpolation {
|
match interpolation {
|
||||||
gltf::animation::Interpolation::Linear => {
|
gltf::animation::Interpolation::Linear => {
|
||||||
@ -323,34 +325,47 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
keyframe_timestamps.into_iter().zip(translations),
|
keyframe_timestamps.into_iter().zip(translations),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(TranslationCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
translation_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
gltf::animation::Interpolation::Step => {
|
gltf::animation::Interpolation::Step => {
|
||||||
SteppedKeyframeCurve::new(
|
SteppedKeyframeCurve::new(
|
||||||
keyframe_timestamps.into_iter().zip(translations),
|
keyframe_timestamps.into_iter().zip(translations),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(TranslationCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
translation_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
gltf::animation::Interpolation::CubicSpline => {
|
gltf::animation::Interpolation::CubicSpline => {
|
||||||
CubicKeyframeCurve::new(keyframe_timestamps, translations)
|
CubicKeyframeCurve::new(keyframe_timestamps, translations)
|
||||||
.ok()
|
.ok()
|
||||||
.map(TranslationCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
translation_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReadOutputs::Rotations(rots) => {
|
ReadOutputs::Rotations(rots) => {
|
||||||
|
let rotation_property = animated_field!(Transform::rotation);
|
||||||
let rotations: Vec<Quat> =
|
let rotations: Vec<Quat> =
|
||||||
rots.into_f32().map(Quat::from_array).collect();
|
rots.into_f32().map(Quat::from_array).collect();
|
||||||
if keyframe_timestamps.len() == 1 {
|
if keyframe_timestamps.len() == 1 {
|
||||||
#[allow(clippy::unnecessary_map_on_constructor)]
|
Some(VariableCurve::new(AnimatableCurve::new(
|
||||||
Some(ConstantCurve::new(Interval::EVERYWHERE, rotations[0]))
|
rotation_property,
|
||||||
.map(RotationCurve)
|
ConstantCurve::new(Interval::EVERYWHERE, rotations[0]),
|
||||||
.map(VariableCurve::new)
|
)))
|
||||||
} else {
|
} else {
|
||||||
match interpolation {
|
match interpolation {
|
||||||
gltf::animation::Interpolation::Linear => {
|
gltf::animation::Interpolation::Linear => {
|
||||||
@ -358,16 +373,24 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
keyframe_timestamps.into_iter().zip(rotations),
|
keyframe_timestamps.into_iter().zip(rotations),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(RotationCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
rotation_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
gltf::animation::Interpolation::Step => {
|
gltf::animation::Interpolation::Step => {
|
||||||
SteppedKeyframeCurve::new(
|
SteppedKeyframeCurve::new(
|
||||||
keyframe_timestamps.into_iter().zip(rotations),
|
keyframe_timestamps.into_iter().zip(rotations),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(RotationCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
rotation_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
gltf::animation::Interpolation::CubicSpline => {
|
gltf::animation::Interpolation::CubicSpline => {
|
||||||
CubicRotationCurve::new(
|
CubicRotationCurve::new(
|
||||||
@ -375,19 +398,24 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
rotations.into_iter().map(Vec4::from),
|
rotations.into_iter().map(Vec4::from),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(RotationCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
rotation_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReadOutputs::Scales(scale) => {
|
ReadOutputs::Scales(scale) => {
|
||||||
|
let scale_property = animated_field!(Transform::scale);
|
||||||
let scales: Vec<Vec3> = scale.map(Vec3::from).collect();
|
let scales: Vec<Vec3> = scale.map(Vec3::from).collect();
|
||||||
if keyframe_timestamps.len() == 1 {
|
if keyframe_timestamps.len() == 1 {
|
||||||
#[allow(clippy::unnecessary_map_on_constructor)]
|
Some(VariableCurve::new(AnimatableCurve::new(
|
||||||
Some(ConstantCurve::new(Interval::EVERYWHERE, scales[0]))
|
scale_property,
|
||||||
.map(ScaleCurve)
|
ConstantCurve::new(Interval::EVERYWHERE, scales[0]),
|
||||||
.map(VariableCurve::new)
|
)))
|
||||||
} else {
|
} else {
|
||||||
match interpolation {
|
match interpolation {
|
||||||
gltf::animation::Interpolation::Linear => {
|
gltf::animation::Interpolation::Linear => {
|
||||||
@ -395,22 +423,34 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
keyframe_timestamps.into_iter().zip(scales),
|
keyframe_timestamps.into_iter().zip(scales),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(ScaleCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
scale_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
gltf::animation::Interpolation::Step => {
|
gltf::animation::Interpolation::Step => {
|
||||||
SteppedKeyframeCurve::new(
|
SteppedKeyframeCurve::new(
|
||||||
keyframe_timestamps.into_iter().zip(scales),
|
keyframe_timestamps.into_iter().zip(scales),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(ScaleCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
scale_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
gltf::animation::Interpolation::CubicSpline => {
|
gltf::animation::Interpolation::CubicSpline => {
|
||||||
CubicKeyframeCurve::new(keyframe_timestamps, scales)
|
CubicKeyframeCurve::new(keyframe_timestamps, scales)
|
||||||
.ok()
|
.ok()
|
||||||
.map(ScaleCurve)
|
.map(|curve| {
|
||||||
.map(VariableCurve::new)
|
VariableCurve::new(AnimatableCurve::new(
|
||||||
|
scale_property,
|
||||||
|
curve,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,6 +223,8 @@ impl<V: Clone, H> Clone for Hashed<V, H> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<V: Copy, H> Copy for Hashed<V, H> {}
|
||||||
|
|
||||||
impl<V: Eq, H> Eq for Hashed<V, H> {}
|
impl<V: Eq, H> Eq for Hashed<V, H> {}
|
||||||
|
|
||||||
/// A [`BuildHasher`] that results in a [`PassHasher`].
|
/// A [`BuildHasher`] that results in a [`PassHasher`].
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
animation::{AnimationTarget, AnimationTargetId},
|
animation::{animated_field, AnimationTarget, AnimationTargetId},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,17 +52,19 @@ fn setup(
|
|||||||
let planet_animation_target_id = AnimationTargetId::from_name(&planet);
|
let planet_animation_target_id = AnimationTargetId::from_name(&planet);
|
||||||
animation.add_curve_to_target(
|
animation.add_curve_to_target(
|
||||||
planet_animation_target_id,
|
planet_animation_target_id,
|
||||||
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
AnimatableCurve::new(
|
||||||
Vec3::new(1.0, 0.0, 1.0),
|
animated_field!(Transform::translation),
|
||||||
Vec3::new(-1.0, 0.0, 1.0),
|
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
||||||
Vec3::new(-1.0, 0.0, -1.0),
|
Vec3::new(1.0, 0.0, 1.0),
|
||||||
Vec3::new(1.0, 0.0, -1.0),
|
Vec3::new(-1.0, 0.0, 1.0),
|
||||||
// in case seamless looping is wanted, the last keyframe should
|
Vec3::new(-1.0, 0.0, -1.0),
|
||||||
// be the same as the first one
|
Vec3::new(1.0, 0.0, -1.0),
|
||||||
Vec3::new(1.0, 0.0, 1.0),
|
// in case seamless looping is wanted, the last keyframe should
|
||||||
]))
|
// be the same as the first one
|
||||||
.map(TranslationCurve)
|
Vec3::new(1.0, 0.0, 1.0),
|
||||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
]))
|
||||||
|
.expect("should be able to build translation curve because we pass in valid samples"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// Or it can modify the rotation of the transform.
|
// Or it can modify the rotation of the transform.
|
||||||
// To find the entity to modify, the hierarchy will be traversed looking for
|
// To find the entity to modify, the hierarchy will be traversed looking for
|
||||||
@ -71,15 +73,17 @@ fn setup(
|
|||||||
AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter());
|
AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter());
|
||||||
animation.add_curve_to_target(
|
animation.add_curve_to_target(
|
||||||
orbit_controller_animation_target_id,
|
orbit_controller_animation_target_id,
|
||||||
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
AnimatableCurve::new(
|
||||||
Quat::IDENTITY,
|
animated_field!(Transform::rotation),
|
||||||
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
||||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
Quat::IDENTITY,
|
||||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
||||||
Quat::IDENTITY,
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
||||||
]))
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||||
.map(RotationCurve)
|
Quat::IDENTITY,
|
||||||
.expect("Failed to build rotation curve"),
|
]))
|
||||||
|
.expect("Failed to build rotation curve"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// If a curve in an animation is shorter than the other, it will not repeat
|
// If a curve in an animation is shorter than the other, it will not repeat
|
||||||
// until all other curves are finished. In that case, another animation should
|
// until all other curves are finished. In that case, another animation should
|
||||||
@ -89,38 +93,42 @@ fn setup(
|
|||||||
);
|
);
|
||||||
animation.add_curve_to_target(
|
animation.add_curve_to_target(
|
||||||
satellite_animation_target_id,
|
satellite_animation_target_id,
|
||||||
UnevenSampleAutoCurve::new(
|
AnimatableCurve::new(
|
||||||
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]
|
animated_field!(Transform::scale),
|
||||||
.into_iter()
|
UnevenSampleAutoCurve::new(
|
||||||
.zip([
|
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]
|
||||||
Vec3::splat(0.8),
|
.into_iter()
|
||||||
Vec3::splat(1.2),
|
.zip([
|
||||||
Vec3::splat(0.8),
|
Vec3::splat(0.8),
|
||||||
Vec3::splat(1.2),
|
Vec3::splat(1.2),
|
||||||
Vec3::splat(0.8),
|
Vec3::splat(0.8),
|
||||||
Vec3::splat(1.2),
|
Vec3::splat(1.2),
|
||||||
Vec3::splat(0.8),
|
Vec3::splat(0.8),
|
||||||
Vec3::splat(1.2),
|
Vec3::splat(1.2),
|
||||||
Vec3::splat(0.8),
|
Vec3::splat(0.8),
|
||||||
]),
|
Vec3::splat(1.2),
|
||||||
)
|
Vec3::splat(0.8),
|
||||||
.map(ScaleCurve)
|
]),
|
||||||
.expect("Failed to build scale curve"),
|
)
|
||||||
|
.expect("Failed to build scale curve"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// There can be more than one curve targeting the same entity path.
|
// There can be more than one curve targeting the same entity path.
|
||||||
animation.add_curve_to_target(
|
animation.add_curve_to_target(
|
||||||
AnimationTargetId::from_names(
|
AnimationTargetId::from_names(
|
||||||
[planet.clone(), orbit_controller.clone(), satellite.clone()].iter(),
|
[planet.clone(), orbit_controller.clone(), satellite.clone()].iter(),
|
||||||
),
|
),
|
||||||
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
AnimatableCurve::new(
|
||||||
Quat::IDENTITY,
|
animated_field!(Transform::rotation),
|
||||||
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
|
||||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
Quat::IDENTITY,
|
||||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
||||||
Quat::IDENTITY,
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
||||||
]))
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||||
.map(RotationCurve)
|
Quat::IDENTITY,
|
||||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
]))
|
||||||
|
.expect("should be able to build translation curve because we pass in valid samples"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the animation graph
|
// Create the animation graph
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
//! Shows how to use animation clips to animate UI properties.
|
//! Shows how to use animation clips to animate UI properties.
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
animation::{AnimationTarget, AnimationTargetId},
|
animation::{
|
||||||
|
animated_field, AnimationEntityMut, AnimationEvaluationError, AnimationTarget,
|
||||||
|
AnimationTargetId,
|
||||||
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
use std::any::TypeId;
|
||||||
// A type that represents the font size of the first text section.
|
|
||||||
//
|
|
||||||
// We implement `AnimatableProperty` on this.
|
|
||||||
#[derive(Reflect)]
|
|
||||||
struct FontSizeProperty;
|
|
||||||
|
|
||||||
// A type that represents the color of the first text section.
|
|
||||||
//
|
|
||||||
// We implement `AnimatableProperty` on this.
|
|
||||||
#[derive(Reflect)]
|
|
||||||
struct TextColorProperty;
|
|
||||||
|
|
||||||
// Holds information about the animation we programmatically create.
|
// Holds information about the animation we programmatically create.
|
||||||
struct AnimationInfo {
|
struct AnimationInfo {
|
||||||
@ -39,29 +31,6 @@ fn main() {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnimatableProperty for FontSizeProperty {
|
|
||||||
type Component = TextFont;
|
|
||||||
|
|
||||||
type Property = f32;
|
|
||||||
|
|
||||||
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
|
||||||
Some(&mut component.font_size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimatableProperty for TextColorProperty {
|
|
||||||
type Component = TextColor;
|
|
||||||
|
|
||||||
type Property = Srgba;
|
|
||||||
|
|
||||||
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
|
|
||||||
match component.0 {
|
|
||||||
Color::Srgba(ref mut color) => Some(color),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimationInfo {
|
impl AnimationInfo {
|
||||||
// Programmatically creates the UI animation.
|
// Programmatically creates the UI animation.
|
||||||
fn create(
|
fn create(
|
||||||
@ -76,35 +45,40 @@ impl AnimationInfo {
|
|||||||
let mut animation_clip = AnimationClip::default();
|
let mut animation_clip = AnimationClip::default();
|
||||||
|
|
||||||
// Create a curve that animates font size.
|
// Create a curve that animates font size.
|
||||||
//
|
|
||||||
// The curve itself is a `Curve<f32>`, and `f32` is `FontSizeProperty::Property`,
|
|
||||||
// which is required by `AnimatableCurve::from_curve`.
|
|
||||||
animation_clip.add_curve_to_target(
|
animation_clip.add_curve_to_target(
|
||||||
animation_target_id,
|
animation_target_id,
|
||||||
AnimatableKeyframeCurve::new(
|
AnimatableCurve::new(
|
||||||
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
|
animated_field!(TextFont::font_size),
|
||||||
.into_iter()
|
AnimatableKeyframeCurve::new(
|
||||||
.zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]),
|
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
|
||||||
)
|
.into_iter()
|
||||||
.map(AnimatableCurve::<FontSizeProperty, _>::from_curve)
|
.zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]),
|
||||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
)
|
||||||
|
.expect(
|
||||||
|
"should be able to build translation curve because we pass in valid samples",
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a curve that animates font color. Note that this should have
|
// Create a curve that animates font color. Note that this should have
|
||||||
// the same time duration as the previous curve.
|
// the same time duration as the previous curve.
|
||||||
//
|
//
|
||||||
// Similar to the above, the curve itself is a `Curve<Srgba>`, and `Srgba` is
|
// This time we use a "custom property", which in this case animates TextColor under the assumption
|
||||||
// `TextColorProperty::Property`, which is required by the `from_curve` method.
|
// that it is in the "srgba" format.
|
||||||
animation_clip.add_curve_to_target(
|
animation_clip.add_curve_to_target(
|
||||||
animation_target_id,
|
animation_target_id,
|
||||||
AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([
|
AnimatableCurve::new(
|
||||||
Srgba::RED,
|
TextColorProperty,
|
||||||
Srgba::GREEN,
|
AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([
|
||||||
Srgba::BLUE,
|
Srgba::RED,
|
||||||
Srgba::RED,
|
Srgba::GREEN,
|
||||||
]))
|
Srgba::BLUE,
|
||||||
.map(AnimatableCurve::<TextColorProperty, _>::from_curve)
|
Srgba::RED,
|
||||||
.expect("should be able to build translation curve because we pass in valid samples"),
|
]))
|
||||||
|
.expect(
|
||||||
|
"should be able to build translation curve because we pass in valid samples",
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save our animation clip as an asset.
|
// Save our animation clip as an asset.
|
||||||
@ -187,3 +161,37 @@ fn setup(
|
|||||||
.insert(animation_target_name);
|
.insert(animation_target_name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A type that represents the color of the first text section.
|
||||||
|
//
|
||||||
|
// We implement `AnimatableProperty` on this to define custom property accessor logic
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TextColorProperty;
|
||||||
|
|
||||||
|
impl AnimatableProperty for TextColorProperty {
|
||||||
|
type Property = Srgba;
|
||||||
|
|
||||||
|
fn evaluator_id(&self) -> EvaluatorId {
|
||||||
|
EvaluatorId::Type(TypeId::of::<Self>())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mut<'a>(
|
||||||
|
&self,
|
||||||
|
entity: &'a mut AnimationEntityMut,
|
||||||
|
) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||||
|
let text_color = entity
|
||||||
|
.get_mut::<TextColor>()
|
||||||
|
.ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
|
||||||
|
TextColor,
|
||||||
|
>(
|
||||||
|
)))?
|
||||||
|
.into_inner();
|
||||||
|
match text_color.0 {
|
||||||
|
Color::Srgba(ref mut color) => Ok(color),
|
||||||
|
_ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
|
||||||
|
Srgba,
|
||||||
|
>(
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use std::f32::consts::FRAC_PI_2;
|
use std::f32::consts::FRAC_PI_2;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
animation::{AnimationTarget, AnimationTargetId},
|
animation::{animated_field, AnimationTarget, AnimationTargetId},
|
||||||
color::palettes::css::{ORANGE, SILVER},
|
color::palettes::css::{ORANGE, SILVER},
|
||||||
math::vec3,
|
math::vec3,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -127,9 +127,14 @@ impl AnimationInfo {
|
|||||||
.reparametrize_linear(interval(0.0, 4.0).unwrap())
|
.reparametrize_linear(interval(0.0, 4.0).unwrap())
|
||||||
.expect("this curve has bounded domain, so this should never fail");
|
.expect("this curve has bounded domain, so this should never fail");
|
||||||
|
|
||||||
animation_clip
|
animation_clip.add_curve_to_target(
|
||||||
.add_curve_to_target(animation_target_id, TranslationCurve(translation_curve));
|
animation_target_id,
|
||||||
animation_clip.add_curve_to_target(animation_target_id, RotationCurve(rotation_curve));
|
AnimatableCurve::new(animated_field!(Transform::translation), translation_curve),
|
||||||
|
);
|
||||||
|
animation_clip.add_curve_to_target(
|
||||||
|
animation_target_id,
|
||||||
|
AnimatableCurve::new(animated_field!(Transform::rotation), rotation_curve),
|
||||||
|
);
|
||||||
|
|
||||||
// Save our animation clip as an asset.
|
// Save our animation clip as an asset.
|
||||||
let animation_clip_handle = animation_clips.add(animation_clip);
|
let animation_clip_handle = animation_clips.add(animation_clip);
|
||||||
|
Loading…
Reference in New Issue
Block a user