diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index a1d019ad1a..4f493ceb34 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -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", diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 6ca1ec34c4..d91f376b79 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -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` 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::() +/// .ok_or( +/// AnimationEvaluationError::ComponentNotPresent( +/// TypeId::of::() +/// ) +/// )? +/// .into_inner(); +/// Ok(&mut component.fov) +/// } +/// +/// fn evaluator_id(&self) -> EvaluatorId { +/// EvaluatorId::Type(TypeId::of::()) /// } /// } /// /// 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::() +/// # .ok_or( +/// # AnimationEvaluationError::ComponentNotPresent( +/// # TypeId::of::() +/// # ) +/// # )? +/// # .into_inner(); +/// # Ok(&mut component.fov) +/// # } +/// # fn evaluator_id(&self) -> EvaluatorId { +/// # EvaluatorId::Type(TypeId::of::()) +/// # } /// # } /// 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::::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 &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 AnimatableProperty for AnimatedField +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::() + .ok_or_else(|| AnimationEvaluationError::ComponentNotPresent(TypeId::of::()))?; + Ok((self.func)(c.into_inner())) + } + + fn evaluator_id(&self) -> EvaluatorId { + EvaluatorId::ComponentField(&self.evaluator_id) + } +} + +impl &mut P + 'static> AnimatedField { + /// 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::(), field_index)), + marker: PhantomData, + } + } } /// This trait collects the additional requirements on top of [`Curve`] needed for a @@ -187,12 +278,14 @@ impl AnimationCompatibleCurve for C where C: Curve + Debug + Clone + #[derive(Reflect, FromReflect)] #[reflect(from_reflect = false)] pub struct AnimatableCurve { + /// 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

, } /// An [`AnimatableCurveEvaluator`] for [`AnimatableProperty`] instances. @@ -200,13 +293,9 @@ pub struct AnimatableCurve { /// 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

-where - P: AnimatableProperty, -{ - evaluator: BasicAnimationCurveEvaluator, - #[reflect(ignore)] - phantom: PhantomData

, +pub struct AnimatableCurveEvaluator { + evaluator: BasicAnimationCurveEvaluator, + property: Box>, } impl AnimatableCurve @@ -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 Clone for AnimatableCurve 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 AnimationCurve for AnimatableCurve +impl AnimationCurve for AnimatableCurve where - P: AnimatableProperty, - C: AnimationCompatibleCurve, + P: AnimatableProperty + Clone, + C: AnimationCompatibleCurve + Clone, { fn clone_value(&self) -> Box { Box::new(self.clone()) @@ -262,14 +349,14 @@ where self.curve.domain() } - fn evaluator_type(&self) -> TypeId { - TypeId::of::>() + fn evaluator_id(&self) -> EvaluatorId { + self.property.evaluator_id() } fn create_evaluator(&self) -> Box { - Box::new(AnimatableCurveEvaluator { + Box::new(AnimatableCurveEvaluator:: { evaluator: BasicAnimationCurveEvaluator::default(), - phantom: PhantomData::

, + 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::>() + let curve_evaluator = curve_evaluator + .downcast_mut::>() .unwrap(); let value = self.curve.sample_clamped(t); curve_evaluator @@ -296,10 +383,7 @@ where } } -impl

AnimationCurveEvaluator for AnimatableCurveEvaluator

-where - P: AnimatableProperty, -{ +impl AnimationCurveEvaluator for AnimatableCurveEvaluator { 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 entity: AnimationEntityMut<'a>, ) -> Result<(), AnimationEvaluationError> { - let mut component = entity.get_mut::().ok_or_else(|| { - AnimationEvaluationError::ComponentNotPresent(TypeId::of::()) - })?; - let property = P::get_mut(&mut component) - .ok_or_else(|| AnimationEvaluationError::PropertyNotPresent(TypeId::of::

()))?; + let property = self.property.get_mut(&mut entity)?; *property = self .evaluator .stack .pop() - .ok_or_else(inconsistent::>)? - .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(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, -} - -impl AnimationCurve for TranslationCurve -where - C: AnimationCompatibleCurve, -{ - fn clone_value(&self) -> Box { - Box::new(self.clone()) - } - - fn domain(&self) -> Interval { - self.0.domain() - } - - fn evaluator_type(&self) -> TypeId { - TypeId::of::() - } - - fn create_evaluator(&self) -> Box { - 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::() - .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>, - _: AnimationEntityMut<'a>, - ) -> Result<(), AnimationEvaluationError> { - let mut component = transform.ok_or_else(|| { - AnimationEvaluationError::ComponentNotPresent(TypeId::of::()) - })?; - component.translation = self - .evaluator - .stack - .pop() - .ok_or_else(inconsistent::)? - .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(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, -} - -impl AnimationCurve for RotationCurve -where - C: AnimationCompatibleCurve, -{ - fn clone_value(&self) -> Box { - Box::new(self.clone()) - } - - fn domain(&self) -> Interval { - self.0.domain() - } - - fn evaluator_type(&self) -> TypeId { - TypeId::of::() - } - - fn create_evaluator(&self) -> Box { - 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::() - .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>, - _: AnimationEntityMut<'a>, - ) -> Result<(), AnimationEvaluationError> { - let mut component = transform.ok_or_else(|| { - AnimationEvaluationError::ComponentNotPresent(TypeId::of::()) - })?; - component.rotation = self - .evaluator - .stack - .pop() - .ok_or_else(inconsistent::)? - .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(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, -} - -impl AnimationCurve for ScaleCurve -where - C: AnimationCompatibleCurve, -{ - fn clone_value(&self) -> Box { - Box::new(self.clone()) - } - - fn domain(&self) -> Interval { - self.0.domain() - } - - fn evaluator_type(&self) -> TypeId { - TypeId::of::() - } - - fn create_evaluator(&self) -> Box { - 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::() - .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>, - _: AnimationEntityMut<'a>, - ) -> Result<(), AnimationEvaluationError> { - let mut component = transform.ok_or_else(|| { - AnimationEvaluationError::ComponentNotPresent(TypeId::of::()) - })?; - component.scale = self - .evaluator - .stack - .pop() - .ok_or_else(inconsistent::)? + .ok_or_else(inconsistent::>)? .value; Ok(()) } @@ -683,8 +471,8 @@ where self.0.domain() } - fn evaluator_type(&self) -> TypeId { - TypeId::of::() + fn evaluator_id(&self) -> EvaluatorId { + EvaluatorId::Type(TypeId::of::()) } fn create_evaluator(&self) -> Box { @@ -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::() .unwrap(); @@ -802,7 +590,6 @@ impl AnimationCurveEvaluator for WeightsCurveEvaluator { fn commit<'a>( &mut self, - _: Option>, 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; @@ -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; /// 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>, 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::

()) } + +/// 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 + }) + }; +} diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 9fe1d89f25..83ba659d08 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -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`, which can't be automatically derived yet. -impl PartialReflect for VariableCurve { - #[inline] - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - 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) -> Result, Box> { - 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) -> ReflectOwned { - ReflectOwned::TupleStruct(self) - } - - fn clone_value(&self) -> Box { - Box::new((*self).clone()) - } -} - -// We have to implement `Reflect` manually because of the embedded `Box`, which can't be automatically derived yet. -impl Reflect for VariableCurve { - #[inline] - fn into_any(self: Box) -> Box { - 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) -> Box { - 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) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } -} - -// We have to implement `TupleStruct` manually because of the embedded `Box`, 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`, which can't be automatically derived yet. -impl FromReflect for VariableCurve { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - Some(reflect.try_downcast_ref::()?.clone()) - } -} - -// We have to implement `GetTypeRegistration` manually because of the embedded -// `Box`, which can't be automatically derived yet. -impl GetTypeRegistration for VariableCurve { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration - } -} - -// We have to implement `Typed` manually because of the embedded `Box`, 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::(&[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>, + /// [`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>, + type_id_curve_evaluators: TypeIdMap>, +} + +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, + ) -> &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 { + 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>, threaded_animation_graphs: Res, players: Query<(&AnimationPlayer, &AnimationGraphHandle)>, - mut targets: Query<( - Entity, - &AnimationTarget, - Option<&mut Transform>, - AnimationEntityMut, - )>, + mut targets: Query<(Entity, &AnimationTarget, AnimationEntityMut)>, animation_evaluation_state: Local>>, ) { // 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 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()) + }) } } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 04e8f4a48e..77b1e9de89 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -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 = 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 = 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 = 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, + )) + }) } } } diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 694c341465..32cf483251 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -223,6 +223,8 @@ impl Clone for Hashed { } } +impl Copy for Hashed {} + impl Eq for Hashed {} /// A [`BuildHasher`] that results in a [`PassHasher`]. diff --git a/examples/animation/animated_transform.rs b/examples/animation/animated_transform.rs index 7fa9a9e98b..21b85c5489 100644 --- a/examples/animation/animated_transform.rs +++ b/examples/animation/animated_transform.rs @@ -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 diff --git a/examples/animation/animated_ui.rs b/examples/animation/animated_ui.rs index e40afed616..3dc80f3eba 100644 --- a/examples/animation/animated_ui.rs +++ b/examples/animation/animated_ui.rs @@ -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`, 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::::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`, 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::::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::()) + } + + fn get_mut<'a>( + &self, + entity: &'a mut AnimationEntityMut, + ) -> Result<&'a mut Self::Property, AnimationEvaluationError> { + let text_color = entity + .get_mut::() + .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, + >( + ))), + } + } +} diff --git a/examples/animation/eased_motion.rs b/examples/animation/eased_motion.rs index 11220e92ad..f6254cd65d 100644 --- a/examples/animation/eased_motion.rs +++ b/examples/animation/eased_motion.rs @@ -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);