From 9a2b6dacd0057197b2368e2f419e0d60d6ccf22a Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 12 Nov 2024 19:03:09 -0800 Subject: [PATCH 01/13] AnimatedProperty and AnimatedPropertyOptional --- crates/bevy_animation/Cargo.toml | 1 + crates/bevy_animation/src/animation_curves.rs | 124 +++++++++--- crates/bevy_animation/src/lib.rs | 179 +----------------- examples/animation/animated_ui.rs | 82 +++----- 4 files changed, 127 insertions(+), 259 deletions(-) diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index a1d019ad1abae..4f493ceb34366 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 6ca1ec34c4821..66502220f38d0 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -91,7 +91,7 @@ use bevy_math::{ }, Quat, Vec3, }; -use bevy_reflect::{FromReflect, Reflect, Reflectable, TypePath}; +use bevy_reflect::{FromReflect, Reflect, Reflectable}; use bevy_render::mesh::morph::MorphWeights; use bevy_transform::prelude::Transform; @@ -100,6 +100,7 @@ use crate::{ prelude::{Animatable, BlendInput}, AnimationEntityMut, AnimationEvaluationError, }; +use downcast_rs::{impl_downcast, Downcast}; /// A value on a component that Bevy can animate. /// @@ -160,17 +161,83 @@ use crate::{ /// configured above). /// /// [`AnimationClip`]: crate::AnimationClip -pub trait AnimatableProperty: Reflect + TypePath { +pub trait AnimatableProperty { /// The type of the component that the property lives on. type Component: Component; /// The type of the property to be animated. - type Property: Animatable + FromReflect + Reflectable + Clone + Sync + Debug; + type Property: Animatable + Clone + Sync + Debug; /// Given a reference to the component, returns a reference to the property. /// /// If the property couldn't be found, returns `None`. - fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property>; + fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property>; +} + +/// A [`Component`] property that can be animated, defined by a function that reads the component and returns +/// the accessed field / property. +#[derive(Clone)] +pub struct AnimatedProperty &mut P> { + func: F, + marker: PhantomData<(C, P)>, +} + +impl AnimatableProperty for AnimatedProperty +where + C: Component, + P: Animatable + Clone + Sync + Debug, + F: Fn(&mut C) -> &mut P, +{ + type Component = C; + + type Property = P; + + fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { + Some((self.func)(component)) + } +} + +impl &mut P + 'static> AnimatedProperty { + /// Creates a new instance of [`AnimatedProperty`] + pub fn new(func: F) -> Self { + Self { + func, + marker: PhantomData, + } + } +} + +/// A [`Component`] property that can be animated, defined by a function that reads the component and returns +/// either the accessed field / property, or [`None`] if it does not exist +#[derive(Clone)] +pub struct AnimatedPropertyOptional Option<&mut P>> { + func: F, + marker: PhantomData<(C, P)>, +} + +impl AnimatableProperty for AnimatedPropertyOptional +where + C: Component, + P: Animatable + Clone + Sync + Debug, + F: Fn(&mut C) -> Option<&mut P>, +{ + type Component = C; + + type Property = P; + + fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { + (self.func)(component) + } +} + +impl Option<&mut P> + 'static> AnimatedPropertyOptional { + /// Creates a new instance of [`AnimatedPropertyOptional`] + pub fn new(func: F) -> Self { + Self { + func, + marker: PhantomData, + } + } } /// This trait collects the additional requirements on top of [`Curve`] needed for a @@ -187,12 +254,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. @@ -205,8 +274,7 @@ where P: AnimatableProperty, { evaluator: BasicAnimationCurveEvaluator, - #[reflect(ignore)] - phantom: PhantomData

, + property: P, } impl AnimatableCurve @@ -218,22 +286,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 +315,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()) @@ -269,7 +335,7 @@ where fn create_evaluator(&self) -> Box { Box::new(AnimatableCurveEvaluator { evaluator: BasicAnimationCurveEvaluator::default(), - phantom: PhantomData::

, + property: self.property.clone(), }) } @@ -280,7 +346,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(); let value = self.curve.sample_clamped(t); @@ -298,7 +364,7 @@ where impl

AnimationCurveEvaluator for AnimatableCurveEvaluator

where - P: AnimatableProperty, + P: AnimatableProperty + Send + Sync + 'static, { fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> { self.evaluator.combine(graph_node, /*additive=*/ false) @@ -324,7 +390,9 @@ where let mut component = entity.get_mut::().ok_or_else(|| { AnimationEvaluationError::ComponentNotPresent(TypeId::of::()) })?; - let property = P::get_mut(&mut component) + let property = self + .property + .get_mut(&mut component) .ok_or_else(|| AnimationEvaluationError::PropertyNotPresent(TypeId::of::

()))?; *property = self .evaluator @@ -382,7 +450,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(); let value = self.0.sample_clamped(t); @@ -479,7 +547,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(); let value = self.0.sample_clamped(t); @@ -576,7 +644,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(); let value = self.0.sample_clamped(t); @@ -704,7 +772,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(); @@ -968,7 +1036,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; @@ -1031,7 +1099,7 @@ 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 { +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: @@ -1099,6 +1167,8 @@ pub trait AnimationCurveEvaluator: Reflect { ) -> 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. diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 9fe1d89f2596b..809b534108c49 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}, @@ -38,12 +38,7 @@ 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_utils::{ @@ -100,175 +95,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 +102,7 @@ impl Typed for VariableCurve { /// [`AnimationTarget`] with that ID. #[derive(Asset, Reflect, Clone, Debug, Default)] pub struct AnimationClip { + #[reflect(ignore)] curves: AnimationCurves, events: AnimationEvents, duration: f32, diff --git a/examples/animation/animated_ui.rs b/examples/animation/animated_ui.rs index e40afed616cdf..53f96029be31c 100644 --- a/examples/animation/animated_ui.rs +++ b/examples/animation/animated_ui.rs @@ -5,18 +5,6 @@ use bevy::{ 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; - // Holds information about the animation we programmatically create. struct AnimationInfo { // The name of the animation target (in this case, the text). @@ -39,29 +27,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 +41,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( + AnimatedProperty::new(|font: &mut TextFont| &mut font.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. 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( + AnimatedPropertyOptional::new(|color: &mut TextColor| match &mut color.0 { + Color::Srgba(srgba) => Some(srgba), + _ => None, + }), + 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. From ceb1c10de719c8bcddec3df380d934a26ac51426 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 22 Nov 2024 19:26:45 -0800 Subject: [PATCH 02/13] Fix docs --- crates/bevy_animation/src/animation_curves.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 66502220f38d0..9bc02e6deca5c 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -119,7 +119,7 @@ use downcast_rs::{impl_downcast, Downcast}; /// impl AnimatableProperty for FieldOfViewProperty { /// type Component = PerspectiveProjection; /// type Property = f32; -/// fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { +/// fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { /// Some(&mut component.fov) /// } /// } @@ -132,26 +132,25 @@ use downcast_rs::{impl_downcast, Downcast}; /// # use bevy_reflect::Reflect; /// # use bevy_render::camera::PerspectiveProjection; /// # 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> { +/// # fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { /// # Some(&mut component.fov) /// # } /// # } /// 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 From a952852bb289015b29e8bdcb2da464cfd9c81137 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 22 Nov 2024 19:42:58 -0800 Subject: [PATCH 03/13] Fix doc --- crates/bevy_animation/src/animation_curves.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 9bc02e6deca5c..f9b031850128a 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -155,7 +155,7 @@ use downcast_rs::{impl_downcast, Downcast}; /// /// 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). /// From 4a9bfdb47261b92cd6c2c4291b493152186293ff Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 25 Nov 2024 20:23:26 -0800 Subject: [PATCH 04/13] Rework evaluator identity, animated_property! macro, remove built-in Transform properties --- crates/bevy_animation/src/animation_curves.rs | 404 +++--------------- crates/bevy_animation/src/lib.rs | 142 ++++-- crates/bevy_gltf/src/loader.rs | 100 +++-- crates/bevy_utils/src/lib.rs | 2 + examples/animation/animated_transform.rs | 102 +++-- examples/animation/animated_ui.rs | 36 +- examples/animation/eased_motion.rs | 16 +- 7 files changed, 350 insertions(+), 452 deletions(-) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index f9b031850128a..d3595d5071244 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -83,15 +83,12 @@ use core::{ }; use bevy_ecs::{component::Component, world::Mut}; -use bevy_math::{ - curve::{ - cores::{UnevenCore, UnevenCoreError}, - iterable::IterableCurve, - Curve, Interval, - }, - Quat, Vec3, +use bevy_math::curve::{ + cores::{UnevenCore, UnevenCoreError}, + iterable::IterableCurve, + Curve, Interval, }; -use bevy_reflect::{FromReflect, Reflect, Reflectable}; +use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed}; use bevy_render::mesh::morph::MorphWeights; use bevy_transform::prelude::Transform; @@ -100,6 +97,7 @@ use crate::{ prelude::{Animatable, BlendInput}, AnimationEntityMut, AnimationEvaluationError, }; +use bevy_utils::Hashed; use downcast_rs::{impl_downcast, Downcast}; /// A value on a component that Bevy can animate. @@ -171,6 +169,10 @@ pub trait AnimatableProperty { /// /// If the property couldn't be found, returns `None`. fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::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`] property that can be animated, defined by a function that reads the component and returns @@ -178,6 +180,8 @@ pub trait AnimatableProperty { #[derive(Clone)] pub struct AnimatedProperty &mut P> { 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, P)>, } @@ -194,46 +198,27 @@ where fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { Some((self.func)(component)) } -} -impl &mut P + 'static> AnimatedProperty { - /// Creates a new instance of [`AnimatedProperty`] - pub fn new(func: F) -> Self { - Self { - func, - marker: PhantomData, - } + fn evaluator_id(&self) -> EvaluatorId { + EvaluatorId::ComponentField(&self.evaluator_id) } } -/// A [`Component`] property that can be animated, defined by a function that reads the component and returns -/// either the accessed field / property, or [`None`] if it does not exist -#[derive(Clone)] -pub struct AnimatedPropertyOptional Option<&mut P>> { - func: F, - marker: PhantomData<(C, P)>, -} - -impl AnimatableProperty for AnimatedPropertyOptional -where - C: Component, - P: Animatable + Clone + Sync + Debug, - F: Fn(&mut C) -> Option<&mut P>, -{ - type Component = C; - - type Property = P; +impl &mut P + 'static> AnimatedProperty { + /// Creates a new instance of [`AnimatedProperty`]. 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. + 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 `AnimatedProperty::new_unchecked`") + }; - fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { - (self.func)(component) - } -} + let field_index = struct_info + .index_of(field_name) + .expect("Field name should exist"); -impl Option<&mut P> + 'static> AnimatedPropertyOptional { - /// Creates a new instance of [`AnimatedPropertyOptional`] - pub fn new(func: F) -> Self { Self { func, + evaluator_id: Hashed::new((TypeId::of::(), field_index)), marker: PhantomData, } } @@ -327,8 +312,8 @@ 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 { @@ -403,297 +388,6 @@ where } } -/// 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 = 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 = 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 = 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::)? - .value; - Ok(()) - } -} - /// This type allows an [`IterableCurve`] valued in `f32` to be used as an [`AnimationCurve`] /// that animates [morph weights]. /// @@ -750,8 +444,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 { @@ -1046,7 +740,7 @@ pub trait AnimationCurve: Debug + Send + Sync + 'static { /// /// 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. @@ -1079,6 +773,19 @@ pub trait AnimationCurve: Debug + Send + Sync + 'static { ) -> 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. + 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. /// @@ -1222,3 +929,28 @@ where { AnimationEvaluationError::InconsistentEvaluatorImplementation(TypeId::of::

()) } + +/// Returns an [`AnimatedProperty`] with a given `$component` and `$field`. +/// +/// This can be used in the following way: +/// +/// ``` +/// # use bevy_animation::{animation_curves::AnimatedProperty, animated_property}; +/// # use bevy_ecs::component::Component; +/// # use bevy_math::Vec3; +/// # use bevy_reflect::Reflect; +/// #[derive(Component, Reflect)] +/// struct Transform { +/// translation: Vec3, +/// } +/// +/// let property = animated_property!(Transform::translation); +/// ``` +#[macro_export] +macro_rules! animated_property { + ($component:ident::$field:ident) => { + AnimatedProperty::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 809b534108c49..0c88faee5f2b9 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -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}; @@ -44,7 +47,7 @@ use bevy_transform::{prelude::Transform, TransformSystem}; use bevy_utils::{ hashbrown::HashMap, tracing::{trace, warn}, - NoOpHash, TypeIdMap, + NoOpHash, PreHashMap, PreHashMapExt, TypeIdMap, }; use petgraph::graph::NodeIndex; use serde::{Deserialize, Serialize}; @@ -705,23 +708,107 @@ 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 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>, + 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.clone(), ()); + } + EvaluatorId::Type(type_id) => { + self.type_ids.insert(type_id, ()); + } + } + } } impl AnimationPlayer { @@ -1127,19 +1214,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, @@ -1252,8 +1340,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)?; @@ -1266,8 +1354,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)?; @@ -1286,8 +1374,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)?; @@ -1305,16 +1393,12 @@ impl AnimationEvaluationState { 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) - .unwrap() - .commit( - transform.as_mut().map(|transform| transform.reborrow()), - entity_mut.reborrow(), - )?; - } - Ok(()) + self.current_evaluators.clear(|id| { + self.evaluators.get_mut(id).unwrap().commit( + transform.as_mut().map(|transform| transform.reborrow()), + entity_mut.reborrow(), + ) + }) } } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 04e8f4a48e232..8cddb4cff4c41 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_property; 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_property!(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_property!(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_property!(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 694c34146504d..32cf483251b21 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 7fa9a9e98bb07..388d9f9387746 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_property, 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_property!(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_property!(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_property!(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_property!(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 53f96029be31c..8e8483b80af9a 100644 --- a/examples/animation/animated_ui.rs +++ b/examples/animation/animated_ui.rs @@ -1,9 +1,10 @@ //! Shows how to use animation clips to animate UI properties. use bevy::{ - animation::{AnimationTarget, AnimationTargetId}, + animation::{animated_property, AnimationTarget, AnimationTargetId}, prelude::*, }; +use std::any::TypeId; // Holds information about the animation we programmatically create. struct AnimationInfo { @@ -44,7 +45,7 @@ impl AnimationInfo { animation_clip.add_curve_to_target( animation_target_id, AnimatableCurve::new( - AnimatedProperty::new(|font: &mut TextFont| &mut font.font_size), + animated_property!(TextFont::font_size), AnimatableKeyframeCurve::new( [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] .into_iter() @@ -58,13 +59,13 @@ impl AnimationInfo { // Create a curve that animates font color. Note that this should have // the same time duration as the previous curve. + // + // 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, AnimatableCurve::new( - AnimatedPropertyOptional::new(|color: &mut TextColor| match &mut color.0 { - Color::Srgba(srgba) => Some(srgba), - _ => None, - }), + TextColorProperty, AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([ Srgba::RED, Srgba::GREEN, @@ -157,3 +158,26 @@ 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 Component = TextColor; + + type Property = Srgba; + + fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { + match component.0 { + Color::Srgba(ref mut color) => Some(color), + _ => None, + } + } + + fn evaluator_id(&self) -> EvaluatorId { + EvaluatorId::Type(TypeId::of::()) + } +} diff --git a/examples/animation/eased_motion.rs b/examples/animation/eased_motion.rs index 11220e92ad59e..dc3724c992fbc 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_property, AnimationTarget, AnimationTargetId}, color::palettes::css::{ORANGE, SILVER}, math::vec3, prelude::*, @@ -127,9 +127,17 @@ 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_property!(Transform::translation), + translation_curve, + ), + ); + animation_clip.add_curve_to_target( + animation_target_id, + AnimatableCurve::new(animated_property!(Transform::rotation), rotation_curve), + ); // Save our animation clip as an asset. let animation_clip_handle = animation_clips.add(animation_clip); From 008738a2bba941270a6f782c98335ceb479f494e Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 25 Nov 2024 20:44:06 -0800 Subject: [PATCH 05/13] AnimatedProperty -> AnimatedField --- crates/bevy_animation/src/animation_curves.rs | 31 +++++++++-------- crates/bevy_animation/src/lib.rs | 33 ++++++------------- crates/bevy_gltf/src/loader.rs | 8 ++--- examples/animation/animated_transform.rs | 10 +++--- examples/animation/animated_ui.rs | 4 +-- examples/animation/eased_motion.rs | 9 ++--- 6 files changed, 39 insertions(+), 56 deletions(-) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index d3595d5071244..50d26ae8db0f5 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -82,7 +82,7 @@ use core::{ marker::PhantomData, }; -use bevy_ecs::{component::Component, world::Mut}; +use bevy_ecs::component::Component; use bevy_math::curve::{ cores::{UnevenCore, UnevenCoreError}, iterable::IterableCurve, @@ -90,7 +90,6 @@ use bevy_math::curve::{ }; use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed}; use bevy_render::mesh::morph::MorphWeights; -use bevy_transform::prelude::Transform; use crate::{ graph::AnimationNodeIndex, @@ -175,17 +174,17 @@ pub trait AnimatableProperty { fn evaluator_id(&self) -> EvaluatorId; } -/// A [`Component`] property that can be animated, defined by a function that reads the component and returns +/// A [`Component`] field that can be animated, defined by a function that reads the component and returns /// the accessed field / property. #[derive(Clone)] -pub struct AnimatedProperty &mut P> { +pub struct AnimatedField &mut P> { 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, P)>, } -impl AnimatableProperty for AnimatedProperty +impl AnimatableProperty for AnimatedField where C: Component, P: Animatable + Clone + Sync + Debug, @@ -204,12 +203,12 @@ where } } -impl &mut P + 'static> AnimatedProperty { - /// Creates a new instance of [`AnimatedProperty`]. This operates under the assumption that +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. 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 `AnimatedProperty::new_unchecked`") + panic!("Only structs are supported in `AnimatedField::new_unchecked`") }; let field_index = struct_info @@ -368,7 +367,6 @@ where fn commit<'a>( &mut self, - _: Option>, mut entity: AnimationEntityMut<'a>, ) -> Result<(), AnimationEvaluationError> { let mut component = entity.get_mut::().ok_or_else(|| { @@ -563,7 +561,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() { @@ -780,6 +777,9 @@ 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`]. @@ -868,7 +868,6 @@ pub trait AnimationCurveEvaluator: Downcast + Send + Sync + 'static { /// the stack, not blended with it. fn commit<'a>( &mut self, - transform: Option>, entity: AnimationEntityMut<'a>, ) -> Result<(), AnimationEvaluationError>; } @@ -930,12 +929,12 @@ where AnimationEvaluationError::InconsistentEvaluatorImplementation(TypeId::of::

()) } -/// Returns an [`AnimatedProperty`] with a given `$component` and `$field`. +/// Returns an [`AnimatedField`] with a given `$component` and `$field`. /// /// This can be used in the following way: /// /// ``` -/// # use bevy_animation::{animation_curves::AnimatedProperty, animated_property}; +/// # use bevy_animation::{animation_curves::AnimatedField, animated_field}; /// # use bevy_ecs::component::Component; /// # use bevy_math::Vec3; /// # use bevy_reflect::Reflect; @@ -944,12 +943,12 @@ where /// translation: Vec3, /// } /// -/// let property = animated_property!(Transform::translation); +/// let field = animated_field!(Transform::translation); /// ``` #[macro_export] -macro_rules! animated_property { +macro_rules! animated_field { ($component:ident::$field:ident) => { - AnimatedProperty::new_unchecked(stringify!($field), |component: &mut $component| { + 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 0c88faee5f2b9..ffe27589355ea 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -43,7 +43,7 @@ use bevy_ecs::{ use bevy_math::FloatOrd; 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}, @@ -1031,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. @@ -1049,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, @@ -1239,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); } }); @@ -1390,14 +1378,13 @@ impl AnimationEvaluationState { /// components being animated. fn commit_all( &mut self, - mut transform: Option>, mut entity_mut: AnimationEntityMut, ) -> Result<(), AnimationEvaluationError> { self.current_evaluators.clear(|id| { - self.evaluators.get_mut(id).unwrap().commit( - transform.as_mut().map(|transform| transform.reborrow()), - entity_mut.reborrow(), - ) + self.evaluators + .get_mut(id) + .unwrap() + .commit(entity_mut.reborrow()) }) } } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 8cddb4cff4c41..77b1e9de894ae 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -4,7 +4,7 @@ use crate::{ }; use alloc::collections::VecDeque; -use bevy_animation::animated_property; +use bevy_animation::animated_field; use bevy_asset::{ io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError, }; @@ -311,7 +311,7 @@ async fn load_gltf<'a, 'b, 'c>( { match outputs { ReadOutputs::Translations(tr) => { - let translation_property = animated_property!(Transform::translation); + let translation_property = animated_field!(Transform::translation); let translations: Vec = tr.map(Vec3::from).collect(); if keyframe_timestamps.len() == 1 { Some(VariableCurve::new(AnimatableCurve::new( @@ -358,7 +358,7 @@ async fn load_gltf<'a, 'b, 'c>( } } ReadOutputs::Rotations(rots) => { - let rotation_property = animated_property!(Transform::rotation); + let rotation_property = animated_field!(Transform::rotation); let rotations: Vec = rots.into_f32().map(Quat::from_array).collect(); if keyframe_timestamps.len() == 1 { @@ -409,7 +409,7 @@ async fn load_gltf<'a, 'b, 'c>( } } ReadOutputs::Scales(scale) => { - let scale_property = animated_property!(Transform::scale); + let scale_property = animated_field!(Transform::scale); let scales: Vec = scale.map(Vec3::from).collect(); if keyframe_timestamps.len() == 1 { Some(VariableCurve::new(AnimatableCurve::new( diff --git a/examples/animation/animated_transform.rs b/examples/animation/animated_transform.rs index 388d9f9387746..21b85c5489d8b 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::{animated_property, AnimationTarget, AnimationTargetId}, + animation::{animated_field, AnimationTarget, AnimationTargetId}, prelude::*, }; @@ -53,7 +53,7 @@ fn setup( animation.add_curve_to_target( planet_animation_target_id, AnimatableCurve::new( - animated_property!(Transform::translation), + 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), @@ -74,7 +74,7 @@ fn setup( animation.add_curve_to_target( orbit_controller_animation_target_id, AnimatableCurve::new( - animated_property!(Transform::rotation), + 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.), @@ -94,7 +94,7 @@ fn setup( animation.add_curve_to_target( satellite_animation_target_id, AnimatableCurve::new( - animated_property!(Transform::scale), + 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() @@ -119,7 +119,7 @@ fn setup( [planet.clone(), orbit_controller.clone(), satellite.clone()].iter(), ), AnimatableCurve::new( - animated_property!(Transform::rotation), + 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.), diff --git a/examples/animation/animated_ui.rs b/examples/animation/animated_ui.rs index 8e8483b80af9a..ac93489ee9273 100644 --- a/examples/animation/animated_ui.rs +++ b/examples/animation/animated_ui.rs @@ -1,7 +1,7 @@ //! Shows how to use animation clips to animate UI properties. use bevy::{ - animation::{animated_property, AnimationTarget, AnimationTargetId}, + animation::{animated_field, AnimationTarget, AnimationTargetId}, prelude::*, }; use std::any::TypeId; @@ -45,7 +45,7 @@ impl AnimationInfo { animation_clip.add_curve_to_target( animation_target_id, AnimatableCurve::new( - animated_property!(TextFont::font_size), + animated_field!(TextFont::font_size), AnimatableKeyframeCurve::new( [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] .into_iter() diff --git a/examples/animation/eased_motion.rs b/examples/animation/eased_motion.rs index dc3724c992fbc..f6254cd65de35 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::{animated_property, AnimationTarget, AnimationTargetId}, + animation::{animated_field, AnimationTarget, AnimationTargetId}, color::palettes::css::{ORANGE, SILVER}, math::vec3, prelude::*, @@ -129,14 +129,11 @@ impl AnimationInfo { animation_clip.add_curve_to_target( animation_target_id, - AnimatableCurve::new( - animated_property!(Transform::translation), - translation_curve, - ), + AnimatableCurve::new(animated_field!(Transform::translation), translation_curve), ); animation_clip.add_curve_to_target( animation_target_id, - AnimatableCurve::new(animated_property!(Transform::rotation), rotation_curve), + AnimatableCurve::new(animated_field!(Transform::rotation), rotation_curve), ); // Save our animation clip as an asset. From e5dbcd291065a8c25958b29feb664220c5446f27 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 25 Nov 2024 21:51:48 -0800 Subject: [PATCH 06/13] Type erased AnimationProperty --- crates/bevy_animation/src/animation_curves.rs | 73 ++++++++----------- examples/animation/animated_ui.rs | 34 ++++++--- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 50d26ae8db0f5..7da9dd09442c7 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -157,17 +157,15 @@ use downcast_rs::{impl_downcast, Downcast}; /// configured above). /// /// [`AnimationClip`]: crate::AnimationClip -pub trait AnimatableProperty { - /// 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 + Clone + Sync + Debug; - - /// Given a reference to the component, returns a reference to the property. - /// - /// If the property couldn't be found, returns `None`. - fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property>; + /// Retrieves the property from the given `entity`. + fn get_mut<'a>( + &self, + entity: &'a mut AnimationEntityMut, + ) -> Result<&'a mut Self::Property, AnimationEvaluationError>; /// 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. @@ -177,25 +175,28 @@ pub trait AnimatableProperty { /// A [`Component`] field that can be animated, defined by a function that reads the component and returns /// the accessed field / property. #[derive(Clone)] -pub struct AnimatedField &mut P> { +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, P)>, + marker: PhantomData<(C, A)>, } -impl AnimatableProperty for AnimatedField +impl AnimatableProperty for AnimatedField where C: Component, - P: Animatable + Clone + Sync + Debug, - F: Fn(&mut C) -> &mut P, + A: Animatable + Clone + Sync + Debug, + F: Fn(&mut C) -> &mut A + Send + Sync + 'static, { - type Component = C; - - type Property = P; - - fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { - Some((self.func)(component)) + 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 { @@ -252,12 +253,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, - property: P, +pub struct AnimatableCurveEvaluator { + evaluator: BasicAnimationCurveEvaluator, + property: Box>, } impl AnimatableCurve @@ -316,9 +314,9 @@ where } fn create_evaluator(&self) -> Box { - Box::new(AnimatableCurveEvaluator { + Box::new(AnimatableCurveEvaluator:: { evaluator: BasicAnimationCurveEvaluator::default(), - property: self.property.clone(), + property: Box::new(self.property.clone()), }) } @@ -330,7 +328,7 @@ where graph_node: AnimationNodeIndex, ) -> Result<(), AnimationEvaluationError> { let curve_evaluator = curve_evaluator - .downcast_mut::>() + .downcast_mut::>() .unwrap(); let value = self.curve.sample_clamped(t); curve_evaluator @@ -345,10 +343,7 @@ where } } -impl

AnimationCurveEvaluator for AnimatableCurveEvaluator

-where - P: AnimatableProperty + Send + Sync + 'static, -{ +impl AnimationCurveEvaluator for AnimatableCurveEvaluator { fn blend(&mut self, graph_node: AnimationNodeIndex) -> Result<(), AnimationEvaluationError> { self.evaluator.combine(graph_node, /*additive=*/ false) } @@ -369,18 +364,12 @@ where &mut self, mut entity: AnimationEntityMut<'a>, ) -> Result<(), AnimationEvaluationError> { - let mut component = entity.get_mut::().ok_or_else(|| { - AnimationEvaluationError::ComponentNotPresent(TypeId::of::()) - })?; - let property = self - .property - .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::>)? + .ok_or_else(inconsistent::>)? .value; Ok(()) } diff --git a/examples/animation/animated_ui.rs b/examples/animation/animated_ui.rs index ac93489ee9273..3dc80f3eba6b0 100644 --- a/examples/animation/animated_ui.rs +++ b/examples/animation/animated_ui.rs @@ -1,7 +1,10 @@ //! Shows how to use animation clips to animate UI properties. use bevy::{ - animation::{animated_field, AnimationTarget, AnimationTargetId}, + animation::{ + animated_field, AnimationEntityMut, AnimationEvaluationError, AnimationTarget, + AnimationTargetId, + }, prelude::*, }; use std::any::TypeId; @@ -166,18 +169,29 @@ fn setup( struct TextColorProperty; impl AnimatableProperty for TextColorProperty { - type Component = TextColor; - type Property = Srgba; - fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a mut Self::Property> { - match component.0 { - Color::Srgba(ref mut color) => Some(color), - _ => None, - } - } - 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, + >( + ))), + } + } } From ff10782155285f58d746d21e7ec58231a708af8b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 25 Nov 2024 22:05:35 -0800 Subject: [PATCH 07/13] Clippy --- crates/bevy_animation/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index ffe27589355ea..12d8621d6297a 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -787,11 +787,11 @@ impl CurrentEvaluators { mut visit: impl FnMut(EvaluatorId) -> Result<(), AnimationEvaluationError>, ) -> Result<(), AnimationEvaluationError> { for (key, _) in self.component_properties.drain() { - (visit)(EvaluatorId::ComponentField(&key))? + (visit)(EvaluatorId::ComponentField(&key))?; } for (key, _) in self.type_ids.drain() { - (visit)(EvaluatorId::Type(key))? + (visit)(EvaluatorId::Type(key))?; } Ok(()) @@ -801,8 +801,7 @@ impl CurrentEvaluators { pub(crate) fn insert(&mut self, id: EvaluatorId) { match id { EvaluatorId::ComponentField(component_property) => { - self.component_properties - .insert(component_property.clone(), ()); + self.component_properties.insert(*component_property, ()); } EvaluatorId::Type(type_id) => { self.type_ids.insert(type_id, ()); From 80fedaa25aeb725a1922d45a517571e224d38452 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 26 Nov 2024 14:14:08 -0800 Subject: [PATCH 08/13] Address comments --- crates/bevy_animation/src/animation_curves.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 7da9dd09442c7..76fa71ffc0cb8 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -174,6 +174,11 @@ pub trait AnimatableProperty: Send + Sync + 'static { /// 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`. #[derive(Clone)] pub struct AnimatedField &mut A> { func: F, @@ -207,6 +212,9 @@ where 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. + /// + /// # 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`") From 613533544e7e5c5d6a1993679ab085faddd83aaf Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 27 Nov 2024 12:39:20 -0800 Subject: [PATCH 09/13] Update crates/bevy_animation/src/lib.rs Co-authored-by: Alice Cecile --- crates/bevy_animation/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 12d8621d6297a..fbe60dad3e9d0 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -708,7 +708,7 @@ pub struct AnimationEvaluationState { /// Stores all [`AnimationCurveEvaluator`]s corresponding to properties that /// we've seen so far. /// - /// This is a mapping from the id 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 From 70d11746a933a1073af7a7d79f77509bf7df4b7d Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 27 Nov 2024 12:39:33 -0800 Subject: [PATCH 10/13] Add comment --- crates/bevy_animation/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index fbe60dad3e9d0..7f1629f737eee 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -105,6 +105,7 @@ impl 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, From e5dec49cc050d8491cc8ecd76841e33bc9e9c846 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 27 Nov 2024 13:21:05 -0800 Subject: [PATCH 11/13] Fix and improve docs --- crates/bevy_animation/src/animation_curves.rs | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 76fa71ffc0cb8..146118e4df48b 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,18 +61,22 @@ //! 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 @@ -107,36 +113,59 @@ use downcast_rs::{impl_downcast, Downcast}; /// 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<'a>(&self, component: &'a mut Self::Component) -> Option<&'a 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, Clone)] /// # struct FieldOfViewProperty; /// # impl AnimatableProperty for FieldOfViewProperty { -/// # type Component = PerspectiveProjection; -/// # type Property = f32; -/// # fn get_mut<'a>(&self, component: &'a mut Self::Component) -> Option<&'a 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( From 2f8f032cba2cf579dcf29ed2bc0f26dee299a51e Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 27 Nov 2024 13:43:14 -0800 Subject: [PATCH 12/13] More doc fixes --- crates/bevy_animation/src/animation_curves.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 146118e4df48b..d91f376b7945a 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -81,6 +81,7 @@ //! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation //! [`AnimationClip`]: crate::AnimationClip //! [there]: AnimatableProperty +//! [`animated_field`]: crate::animated_field use core::{ any::TypeId, @@ -208,6 +209,8 @@ pub trait AnimatableProperty: Send + Sync + 'static { /// /// `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, @@ -770,7 +773,7 @@ pub trait AnimationCurve: Debug + Send + Sync + 'static { /// /// 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 @@ -780,7 +783,7 @@ pub trait AnimationCurve: Debug + Send + Sync + 'static { /// [`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 @@ -831,6 +834,8 @@ pub enum EvaluatorId<'a> { /// 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. +/// +/// [`Vec3`]: bevy_math::Vec3 pub trait AnimationCurveEvaluator: Downcast + Send + Sync + 'static { /// Blends the top element of the stack with the blend register. /// From 4381e67dd0d2d962daa5a77efae1266464626bd0 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 27 Nov 2024 14:07:32 -0800 Subject: [PATCH 13/13] Fix doc --- crates/bevy_animation/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 7f1629f737eee..83ba659d089e1 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -717,7 +717,7 @@ pub struct AnimationEvaluationState { /// 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. + /// [`Self::current_evaluators`] set. evaluators: AnimationCurveEvaluators, /// The set of [`AnimationCurveEvaluator`] types that the current