>,
entity: AnimationEntityMut<'a>,
- weight: f32,
) -> Result<(), AnimationEvaluationError>;
}
@@ -496,3 +1047,10 @@ where
})
}
}
+
+fn inconsistent() -> AnimationEvaluationError
+where
+ P: 'static + ?Sized,
+{
+ AnimationEvaluationError::InconsistentEvaluatorImplementation(TypeId::of::
())
+}
diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs
index 5264cf9a23..22c0e1a608 100644
--- a/crates/bevy_animation/src/graph.rs
+++ b/crates/bevy_animation/src/graph.rs
@@ -1,14 +1,25 @@
//! The animation graph, which allows animations to be blended together.
-use core::ops::{Index, IndexMut};
+use core::iter;
+use core::ops::{Index, IndexMut, Range};
use std::io::{self, Write};
-use bevy_asset::{io::Reader, Asset, AssetId, AssetLoader, AssetPath, Handle, LoadContext};
+use bevy_asset::{
+ io::Reader, Asset, AssetEvent, AssetId, AssetLoader, AssetPath, Assets, Handle, LoadContext,
+};
+use bevy_ecs::{
+ event::EventReader,
+ system::{Res, ResMut, Resource},
+};
use bevy_reflect::{Reflect, ReflectSerialize};
use bevy_utils::HashMap;
-use petgraph::graph::{DiGraph, NodeIndex};
+use petgraph::{
+ graph::{DiGraph, NodeIndex},
+ Direction,
+};
use ron::de::SpannedError;
use serde::{Deserialize, Serialize};
+use smallvec::SmallVec;
use thiserror::Error;
use crate::{AnimationClip, AnimationTargetId};
@@ -172,6 +183,99 @@ pub enum AnimationGraphLoadError {
SpannedRon(#[from] SpannedError),
}
+/// Acceleration structures for animation graphs that allows Bevy to evaluate
+/// them quickly.
+///
+/// These are kept up to date as [`AnimationGraph`] instances are added,
+/// modified, and removed.
+#[derive(Default, Reflect, Resource)]
+pub struct ThreadedAnimationGraphs(
+ pub(crate) HashMap, ThreadedAnimationGraph>,
+);
+
+/// An acceleration structure for an animation graph that allows Bevy to
+/// evaluate it quickly.
+///
+/// This is kept up to date as the associated [`AnimationGraph`] instance is
+/// added, modified, or removed.
+#[derive(Default, Reflect)]
+pub struct ThreadedAnimationGraph {
+ /// A cached postorder traversal of the graph.
+ ///
+ /// The node indices here are stored in postorder. Siblings are stored in
+ /// descending order. This is because the
+ /// [`crate::animation_curves::AnimationCurveEvaluator`] uses a stack for
+ /// evaluation. Consider this graph:
+ ///
+ /// ```text
+ /// ┌─────┐
+ /// │ │
+ /// │ 1 │
+ /// │ │
+ /// └──┬──┘
+ /// │
+ /// ┌───────┼───────┐
+ /// │ │ │
+ /// ▼ ▼ ▼
+ /// ┌─────┐ ┌─────┐ ┌─────┐
+ /// │ │ │ │ │ │
+ /// │ 2 │ │ 3 │ │ 4 │
+ /// │ │ │ │ │ │
+ /// └──┬──┘ └─────┘ └─────┘
+ /// │
+ /// ┌───┴───┐
+ /// │ │
+ /// ▼ ▼
+ /// ┌─────┐ ┌─────┐
+ /// │ │ │ │
+ /// │ 5 │ │ 6 │
+ /// │ │ │ │
+ /// └─────┘ └─────┘
+ /// ```
+ ///
+ /// The postorder traversal in this case will be (4, 3, 6, 5, 2, 1).
+ ///
+ /// The fact that the children of each node are sorted in reverse ensures
+ /// that, at each level, the order of blending proceeds in ascending order
+ /// by node index, as we guarantee. To illustrate this, consider the way
+ /// the graph above is evaluated. (Interpolation is represented with the ⊕
+ /// symbol.)
+ ///
+ /// | Step | Node | Operation | Stack (after operation) | Blend Register |
+ /// | ---- | ---- | ---------- | ----------------------- | -------------- |
+ /// | 1 | 4 | Push | 4 | |
+ /// | 2 | 3 | Push | 4 3 | |
+ /// | 3 | 6 | Push | 4 3 6 | |
+ /// | 4 | 5 | Push | 4 3 6 5 | |
+ /// | 5 | 2 | Blend 5 | 4 3 6 | 5 |
+ /// | 6 | 2 | Blend 6 | 4 3 | 5 ⊕ 6 |
+ /// | 7 | 2 | Push Blend | 4 3 2 | |
+ /// | 8 | 1 | Blend 2 | 4 3 | 2 |
+ /// | 9 | 1 | Blend 3 | 4 | 2 ⊕ 3 |
+ /// | 10 | 1 | Blend 4 | | 2 ⊕ 3 ⊕ 4 |
+ /// | 11 | 1 | Push Blend | 1 | |
+ /// | 12 | | Commit | | |
+ pub threaded_graph: Vec,
+
+ /// A mapping from each parent node index to the range within
+ /// [`Self::sorted_edges`].
+ ///
+ /// This allows for quick lookup of the children of each node, sorted in
+ /// ascending order of node index, without having to sort the result of the
+ /// `petgraph` traversal functions every frame.
+ pub sorted_edge_ranges: Vec>,
+
+ /// A list of the children of each node, sorted in ascending order.
+ pub sorted_edges: Vec,
+
+ /// A mapping from node index to a bitfield specifying the mask groups that
+ /// this node masks *out* (i.e. doesn't animate).
+ ///
+ /// A 1 in bit position N indicates that this node doesn't animate any
+ /// targets of mask group N.
+ pub computed_masks: Vec,
+}
+
/// A version of [`AnimationGraph`] suitable for serializing as an asset.
///
/// Animation nodes can refer to external animation clips, and the [`AssetId`]
@@ -571,3 +675,112 @@ impl From for SerializedAnimationGraph {
}
}
}
+
+/// A system that creates, updates, and removes [`ThreadedAnimationGraph`]
+/// structures for every changed [`AnimationGraph`].
+///
+/// The [`ThreadedAnimationGraph`] contains acceleration structures that allow
+/// for quick evaluation of that graph's animations.
+pub(crate) fn thread_animation_graphs(
+ mut threaded_animation_graphs: ResMut,
+ animation_graphs: Res>,
+ mut animation_graph_asset_events: EventReader>,
+) {
+ for animation_graph_asset_event in animation_graph_asset_events.read() {
+ match *animation_graph_asset_event {
+ AssetEvent::Added { id }
+ | AssetEvent::Modified { id }
+ | AssetEvent::LoadedWithDependencies { id } => {
+ // Fetch the animation graph.
+ let Some(animation_graph) = animation_graphs.get(id) else {
+ continue;
+ };
+
+ // Reuse the allocation if possible.
+ let mut threaded_animation_graph =
+ threaded_animation_graphs.0.remove(&id).unwrap_or_default();
+ threaded_animation_graph.clear();
+
+ // Recursively thread the graph in postorder.
+ threaded_animation_graph.init(animation_graph);
+ threaded_animation_graph.build_from(
+ &animation_graph.graph,
+ animation_graph.root,
+ 0,
+ );
+
+ // Write in the threaded graph.
+ threaded_animation_graphs
+ .0
+ .insert(id, threaded_animation_graph);
+ }
+
+ AssetEvent::Removed { id } => {
+ threaded_animation_graphs.0.remove(&id);
+ }
+ AssetEvent::Unused { .. } => {}
+ }
+ }
+}
+
+impl ThreadedAnimationGraph {
+ /// Removes all the data in this [`ThreadedAnimationGraph`], keeping the
+ /// memory around for later reuse.
+ fn clear(&mut self) {
+ self.threaded_graph.clear();
+ self.sorted_edge_ranges.clear();
+ self.sorted_edges.clear();
+ }
+
+ /// Prepares the [`ThreadedAnimationGraph`] for recursion.
+ fn init(&mut self, animation_graph: &AnimationGraph) {
+ let node_count = animation_graph.graph.node_count();
+ let edge_count = animation_graph.graph.edge_count();
+
+ self.threaded_graph.reserve(node_count);
+ self.sorted_edges.reserve(edge_count);
+
+ self.sorted_edge_ranges.clear();
+ self.sorted_edge_ranges
+ .extend(iter::repeat(0..0).take(node_count));
+
+ self.computed_masks.clear();
+ self.computed_masks.extend(iter::repeat(0).take(node_count));
+ }
+
+ /// Recursively constructs the [`ThreadedAnimationGraph`] for the subtree
+ /// rooted at the given node.
+ ///
+ /// `mask` specifies the computed mask of the parent node. (It could be
+ /// fetched from the [`Self::computed_masks`] field, but we pass it
+ /// explicitly as a micro-optimization.)
+ fn build_from(
+ &mut self,
+ graph: &AnimationDiGraph,
+ node_index: AnimationNodeIndex,
+ mut mask: u64,
+ ) {
+ // Accumulate the mask.
+ mask |= graph.node_weight(node_index).unwrap().mask;
+ self.computed_masks.insert(node_index.index(), mask);
+
+ // Gather up the indices of our children, and sort them.
+ let mut kids: SmallVec<[AnimationNodeIndex; 8]> = graph
+ .neighbors_directed(node_index, Direction::Outgoing)
+ .collect();
+ kids.sort_unstable();
+
+ // Write in the list of kids.
+ self.sorted_edge_ranges[node_index.index()] =
+ (self.sorted_edges.len() as u32)..((self.sorted_edges.len() + kids.len()) as u32);
+ self.sorted_edges.extend_from_slice(&kids);
+
+ // Recurse. (This is a postorder traversal.)
+ for kid in kids.into_iter().rev() {
+ self.build_from(graph, kid, mask);
+ }
+
+ // Finally, push our index.
+ self.threaded_graph.push(node_index);
+ }
+}
diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs
index 39a8349d81..2358543204 100755
--- a/crates/bevy_animation/src/lib.rs
+++ b/crates/bevy_animation/src/lib.rs
@@ -16,7 +16,6 @@ pub mod graph;
pub mod transition;
mod util;
-use alloc::collections::BTreeMap;
use core::{
any::{Any, TypeId},
cell::RefCell,
@@ -24,6 +23,9 @@ use core::{
hash::{Hash, Hasher},
iter,
};
+use prelude::AnimationCurveEvaluator;
+
+use crate::graph::ThreadedAnimationGraphs;
use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::{Asset, AssetApp, Assets, Handle};
@@ -46,11 +48,9 @@ use bevy_ui::UiSystem;
use bevy_utils::{
hashbrown::HashMap,
tracing::{trace, warn},
- NoOpHash,
+ NoOpHash, TypeIdMap,
};
-use fixedbitset::FixedBitSet;
-use graph::AnimationMask;
-use petgraph::{graph::NodeIndex, Direction};
+use petgraph::graph::NodeIndex;
use serde::{Deserialize, Serialize};
use thread_local::ThreadLocal;
use uuid::Uuid;
@@ -461,6 +461,14 @@ pub enum AnimationEvaluationError {
/// The component to be animated was present, but the property on the
/// component wasn't present.
PropertyNotPresent(TypeId),
+
+ /// An internal error occurred in the implementation of
+ /// [`AnimationCurveEvaluator`].
+ ///
+ /// You shouldn't ordinarily see this error unless you implemented
+ /// [`AnimationCurveEvaluator`] yourself. The contained [`TypeId`] is the ID
+ /// of the curve evaluator.
+ InconsistentEvaluatorImplementation(TypeId),
}
/// An animation that an [`AnimationPlayer`] is currently either playing or was
@@ -471,12 +479,8 @@ pub enum AnimationEvaluationError {
pub struct ActiveAnimation {
/// The factor by which the weight from the [`AnimationGraph`] is multiplied.
weight: f32,
- /// The actual weight of this animation this frame, taking the
- /// [`AnimationGraph`] into account.
- computed_weight: f32,
/// The mask groups that are masked out (i.e. won't be animated) this frame,
/// taking the `AnimationGraph` into account.
- computed_mask: AnimationMask,
repeat: RepeatAnimation,
speed: f32,
/// Total time the animation has been played.
@@ -497,8 +501,6 @@ impl Default for ActiveAnimation {
fn default() -> Self {
Self {
weight: 1.0,
- computed_weight: 1.0,
- computed_mask: 0,
repeat: RepeatAnimation::default(),
speed: 1.0,
elapsed: 0.0,
@@ -658,9 +660,7 @@ impl ActiveAnimation {
#[derive(Component, Default, Reflect)]
#[reflect(Component, Default)]
pub struct AnimationPlayer {
- /// We use a `BTreeMap` instead of a `HashMap` here to ensure a consistent
- /// ordering when applying the animations.
- active_animations: BTreeMap,
+ active_animations: HashMap,
blend_weights: HashMap,
}
@@ -679,27 +679,29 @@ impl Clone for AnimationPlayer {
}
}
-/// Information needed during the traversal of the animation graph in
-/// [`advance_animations`].
+/// Temporary data that the [`animate_targets`] system maintains.
#[derive(Default)]
-pub struct AnimationGraphEvaluator {
- /// The stack used for the depth-first search of the graph.
- dfs_stack: Vec,
- /// The list of visited nodes during the depth-first traversal.
- dfs_visited: FixedBitSet,
- /// Accumulated weights and masks for each node.
- nodes: Vec,
-}
+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
+ /// 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>,
-/// The accumulated weight and computed mask for a single node.
-#[derive(Clone, Copy, Default, Debug)]
-struct EvaluatedAnimationGraphNode {
- /// The weight that has been accumulated for this node, taking its
- /// ancestors' weights into account.
- weight: f32,
- /// The mask that has been computed for this node, taking its ancestors'
- /// masks into account.
- mask: AnimationMask,
+ /// 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<()>,
}
impl AnimationPlayer {
@@ -845,7 +847,6 @@ pub fn advance_animations(
animation_clips: Res>,
animation_graphs: Res>,
mut players: Query<(&mut AnimationPlayer, &Handle)>,
- animation_graph_evaluator: Local>>,
) {
let delta_seconds = time.delta_seconds();
players
@@ -856,40 +857,15 @@ pub fn advance_animations(
};
// Tick animations, and schedule them.
- //
- // We use a thread-local here so we can reuse allocations across
- // frames.
- let mut evaluator = animation_graph_evaluator.get_or_default().borrow_mut();
let AnimationPlayer {
ref mut active_animations,
- ref blend_weights,
..
} = *player;
- // Reset our state.
- evaluator.reset(animation_graph.root, animation_graph.graph.node_count());
-
- while let Some(node_index) = evaluator.dfs_stack.pop() {
- // Skip if we've already visited this node.
- if evaluator.dfs_visited.put(node_index.index()) {
- continue;
- }
-
+ for node_index in animation_graph.graph.node_indices() {
let node = &animation_graph[node_index];
- // Calculate weight and mask from the graph.
- let (mut weight, mut mask) = (node.weight, node.mask);
- for parent_index in animation_graph
- .graph
- .neighbors_directed(node_index, Direction::Incoming)
- {
- let evaluated_parent = &evaluator.nodes[parent_index.index()];
- weight *= evaluated_parent.weight;
- mask |= evaluated_parent.mask;
- }
- evaluator.nodes[node_index.index()] = EvaluatedAnimationGraphNode { weight, mask };
-
if let Some(active_animation) = active_animations.get_mut(&node_index) {
// Tick the animation if necessary.
if !active_animation.paused {
@@ -899,24 +875,7 @@ pub fn advance_animations(
}
}
}
-
- weight *= active_animation.weight;
- } else if let Some(&blend_weight) = blend_weights.get(&node_index) {
- weight *= blend_weight;
}
-
- // Write in the computed weight and mask for this node.
- if let Some(active_animation) = active_animations.get_mut(&node_index) {
- active_animation.computed_weight = weight;
- active_animation.computed_mask = mask;
- }
-
- // Push children.
- evaluator.dfs_stack.extend(
- animation_graph
- .graph
- .neighbors_directed(node_index, Direction::Outgoing),
- );
}
});
}
@@ -937,13 +896,15 @@ pub type AnimationEntityMut<'w> = EntityMutExcept<
pub fn animate_targets(
clips: Res>,
graphs: Res>,
+ threaded_animation_graphs: Res,
players: Query<(&AnimationPlayer, &Handle)>,
mut targets: Query<(&AnimationTarget, Option<&mut Transform>, AnimationEntityMut)>,
+ animation_evaluation_state: Local>>,
) {
// Evaluate all animation targets in parallel.
targets
.par_iter_mut()
- .for_each(|(target, mut transform, mut entity_mut)| {
+ .for_each(|(target, transform, entity_mut)| {
let &AnimationTarget {
id: target_id,
player: player_id,
@@ -955,7 +916,7 @@ pub fn animate_targets(
} else {
trace!(
"Either an animation player {:?} or a graph was missing for the target \
- entity {:?} ({:?}); no animations will play this frame",
+ entity {:?} ({:?}); no animations will play this frame",
player_id,
entity_mut.id(),
entity_mut.get::(),
@@ -968,6 +929,12 @@ pub fn animate_targets(
return;
};
+ let Some(threaded_animation_graph) =
+ threaded_animation_graphs.0.get(&animation_graph_id)
+ else {
+ return;
+ };
+
// Determine which mask groups this animation target belongs to.
let target_mask = animation_graph
.mask_groups
@@ -975,63 +942,104 @@ pub fn animate_targets(
.cloned()
.unwrap_or_default();
- // Apply the animations one after another. The way we accumulate
- // weights ensures that the order we apply them in doesn't matter.
- //
- // Proof: Consider three animations A₀, A₁, A₂, … with weights w₀,
- // w₁, w₂, … respectively. We seek the value:
- //
- // A₀w₀ + A₁w₁ + A₂w₂ + ⋯
- //
- // Defining lerp(a, b, t) = a + t(b - a), we have:
- //
- // ⎛ ⎛ w₁ ⎞ w₂ ⎞
- // A₀w₀ + A₁w₁ + A₂w₂ + ⋯ = ⋯ lerp⎜lerp⎜A₀, A₁, ⎯⎯⎯⎯⎯⎯⎯⎯⎟, A₂, ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎟ ⋯
- // ⎝ ⎝ w₀ + w₁⎠ w₀ + w₁ + w₂⎠
- //
- // Each step of the following loop corresponds to one of the lerp
- // operations above.
- let mut total_weight = 0.0;
- for (&animation_graph_node_index, active_animation) in
- animation_player.active_animations.iter()
- {
- // If the weight is zero or the current animation target is
- // masked out, stop here.
- if active_animation.weight == 0.0
- || (target_mask & active_animation.computed_mask) != 0
- {
- continue;
- }
+ let mut evaluation_state = animation_evaluation_state.get_or_default().borrow_mut();
+ let evaluation_state = &mut *evaluation_state;
- let Some(clip) = animation_graph
- .get(animation_graph_node_index)
- .and_then(|animation_graph_node| animation_graph_node.clip.as_ref())
- .and_then(|animation_clip_handle| clips.get(animation_clip_handle))
+ // Evaluate the graph.
+ for &animation_graph_node_index in threaded_animation_graph.threaded_graph.iter() {
+ let Some(animation_graph_node) = animation_graph.get(animation_graph_node_index)
else {
continue;
};
- let Some(curves) = clip.curves_for_target(target_id) else {
- continue;
- };
+ match animation_graph_node.clip {
+ None => {
+ // This is a blend node.
+ for edge_index in threaded_animation_graph.sorted_edge_ranges
+ [animation_graph_node_index.index()]
+ .clone()
+ {
+ if let Err(err) = evaluation_state.blend_all(
+ threaded_animation_graph.sorted_edges[edge_index as usize],
+ ) {
+ warn!("Failed to blend animation: {:?}", err);
+ }
+ }
- let weight = active_animation.computed_weight;
- total_weight += weight;
+ if let Err(err) = evaluation_state.push_blend_register_all(
+ animation_graph_node.weight,
+ animation_graph_node_index,
+ ) {
+ warn!("Animation blending failed: {:?}", err);
+ }
+ }
- let weight = weight / total_weight;
- let seek_time = active_animation.seek_time;
+ Some(ref animation_clip_handle) => {
+ // This is a clip node.
+ let Some(active_animation) = animation_player
+ .active_animations
+ .get(&animation_graph_node_index)
+ else {
+ continue;
+ };
- for curve in curves {
- if let Err(err) = curve.0.apply(
- seek_time,
- transform.as_mut().map(|transform| transform.reborrow()),
- entity_mut.reborrow(),
- weight,
- ) {
- warn!("Animation application failed: {:?}", err);
+ // If the weight is zero or the current animation target is
+ // masked out, stop here.
+ if active_animation.weight == 0.0
+ || (target_mask
+ & threaded_animation_graph.computed_masks
+ [animation_graph_node_index.index()])
+ != 0
+ {
+ continue;
+ }
+
+ let Some(clip) = clips.get(animation_clip_handle) else {
+ continue;
+ };
+
+ let Some(curves) = clip.curves_for_target(target_id) else {
+ continue;
+ };
+
+ let weight = active_animation.weight;
+ let seek_time = active_animation.seek_time;
+
+ for curve in curves {
+ // Fetch the curve evaluator. Curve evaluator types
+ // are unique to each property, but shared among all
+ // curve types. For example, given two curve types A
+ // and B, `RotationCurve` and `RotationCurve`
+ // 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 = evaluation_state
+ .curve_evaluators
+ .entry(curve_evaluator_type_id)
+ .or_insert_with(|| curve.0.create_evaluator());
+
+ evaluation_state
+ .current_curve_evaluator_types
+ .insert(curve_evaluator_type_id, ());
+
+ if let Err(err) = AnimationCurve::apply(
+ &*curve.0,
+ &mut **curve_evaluator,
+ seek_time,
+ weight,
+ animation_graph_node_index,
+ ) {
+ warn!("Animation application failed: {:?}", err);
+ }
+ }
}
}
}
+
+ if let Err(err) = evaluation_state.commit_all(transform, entity_mut) {
+ warn!("Animation application failed: {:?}", err);
+ }
});
}
@@ -1050,9 +1058,12 @@ impl Plugin for AnimationPlugin {
.register_type::()
.register_type::()
.register_type::()
+ .register_type::()
+ .init_resource::()
.add_systems(
PostUpdate,
(
+ graph::thread_animation_graphs,
advance_transitions,
advance_animations,
// TODO: `animate_targets` can animate anything, so
@@ -1100,17 +1111,63 @@ impl From<&Name> for AnimationTargetId {
}
}
-impl AnimationGraphEvaluator {
- // Starts a new depth-first search.
- fn reset(&mut self, root: AnimationNodeIndex, node_count: usize) {
- self.dfs_stack.clear();
- self.dfs_stack.push(root);
+impl AnimationEvaluationState {
+ /// Calls [`AnimationCurveEvaluator::blend`] on all curve evaluator types
+ /// that we've been building up for a single target.
+ ///
+ /// The given `node_index` is the node that we're evaluating.
+ fn blend_all(
+ &mut self,
+ node_index: AnimationNodeIndex,
+ ) -> Result<(), AnimationEvaluationError> {
+ for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
+ self.curve_evaluators
+ .get_mut(curve_evaluator_type)
+ .unwrap()
+ .blend(node_index)?;
+ }
+ Ok(())
+ }
- self.dfs_visited.grow(node_count);
- self.dfs_visited.clear();
+ /// Calls [`AnimationCurveEvaluator::push_blend_register`] on all curve
+ /// evaluator types that we've been building up for a single target.
+ ///
+ /// The `weight` parameter is the weight that should be pushed onto the
+ /// stack, while the `node_index` parameter is the node that we're
+ /// evaluating.
+ fn push_blend_register_all(
+ &mut self,
+ weight: f32,
+ node_index: AnimationNodeIndex,
+ ) -> Result<(), AnimationEvaluationError> {
+ for curve_evaluator_type in self.current_curve_evaluator_types.keys() {
+ self.curve_evaluators
+ .get_mut(curve_evaluator_type)
+ .unwrap()
+ .push_blend_register(weight, node_index)?;
+ }
+ Ok(())
+ }
- self.nodes.clear();
- self.nodes
- .extend(iter::repeat(EvaluatedAnimationGraphNode::default()).take(node_count));
+ /// Calls [`AnimationCurveEvaluator::commit`] on all curve evaluator types
+ /// that we've been building up for a single target.
+ ///
+ /// This is the call that actually writes the computed values into the
+ /// 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)
+ .unwrap()
+ .commit(
+ transform.as_mut().map(|transform| transform.reborrow()),
+ entity_mut.reborrow(),
+ )?;
+ }
+ Ok(())
}
}
diff --git a/examples/animation/animation_graph.rs b/examples/animation/animation_graph.rs
index 33a30a8879..4336151fef 100644
--- a/examples/animation/animation_graph.rs
+++ b/examples/animation/animation_graph.rs
@@ -47,24 +47,24 @@ static NODE_RECTS: [NodeRect; 5] = [
NodeRect::new(10.00, 10.00, 97.64, 48.41),
NodeRect::new(10.00, 78.41, 97.64, 48.41),
NodeRect::new(286.08, 78.41, 97.64, 48.41),
- NodeRect::new(148.04, 44.20, 97.64, 48.41),
+ NodeRect::new(148.04, 112.61, 97.64, 48.41), // was 44.20
NodeRect::new(10.00, 146.82, 97.64, 48.41),
];
/// The positions of the horizontal lines in the UI.
static HORIZONTAL_LINES: [Line; 6] = [
- Line::new(107.64, 34.21, 20.20),
+ Line::new(107.64, 34.21, 158.24),
Line::new(107.64, 102.61, 20.20),
- Line::new(107.64, 171.02, 158.24),
- Line::new(127.84, 68.41, 20.20),
- Line::new(245.68, 68.41, 20.20),
+ Line::new(107.64, 171.02, 20.20),
+ Line::new(127.84, 136.82, 20.20),
+ Line::new(245.68, 136.82, 20.20),
Line::new(265.88, 102.61, 20.20),
];
/// The positions of the vertical lines in the UI.
static VERTICAL_LINES: [Line; 2] = [
- Line::new(127.83, 34.21, 68.40),
- Line::new(265.88, 68.41, 102.61),
+ Line::new(127.83, 102.61, 68.40),
+ Line::new(265.88, 34.21, 102.61),
];
/// Initializes the app.