bevy/crates/bevy_animation/src/graph.rs
Matty e3be8f13d8
Incorporate all node weights in additive blending (#16279)
# Objective

In the existing implementation, additive blending effectively treats the
node with least index specially by basically forcing its weight to be
`1.0` regardless of what its computed weight would be (based on the
weights in the `AnimationGraph` and `AnimationPlayer`).

Arguably this makes some amount of sense, because the "base" animation
is often one which was not authored to be used additively, meaning that
its sampled values are interpreted absolutely rather than as deltas.
However, this also leads to strange behavior with respect to animation
masks: if the "base" animation is masked out on some target, then the
next node is treated as the "base" animation, despite the fact that it
would normally be interpreted additively, and the weight of that
animation is thrown away as a result.

This is all kind of weird and revolves around special treatment (if the
behavior is even really intentional in the first place). From a
mathematical standpoint, there is nothing special about how the "base"
animation must be treated other than having a weight of 1.0 under an
`Add` node, which is something that the user can do without relying on
some bizarre corner-case behavior of the animation system — this is the
only present situation under which weights are discarded.

This PR changes this behavior so that the weight of every node is
incorporated. In other words, for an animation graph that looks like
this:
```text
┌───────────────┐                                 
│Base clip      ┼──┐                              
│      0.5      │  │                              
└───────────────┘  │                              
┌───────────────┐  │  ┌───────────────┐     ┌────┐
│Additive clip 1┼──┼─►┤Additive blend ┼────►│Root│
│      0.1      │  │  │      1.0      │     └────┘
└───────────────┘  │  └───────────────┘           
┌───────────────┐  │                              
│Additive clip 2┼──┘                              
│      0.2      │                                 
└───────────────┘                                 
```

Previously, the result would have been
```text
base_clip + 0.1 * additive_clip_1 + 0.2 * additive_clip_2
```

whereas now it would be
```text
0.5 * base_clip + 0.1 * additive_clip_1 + 0.2 * additive_clip_2
```

and in the scenario where `base_clip` is masked out:
```text
additive_clip_1 + 0.2 * additive_clip_2
```
vs.
```text
0.1 * additive_clip_1 + 0.2 * additive_clip_2
```

## Solution

For background, the way that the additive blending procedure works is
something like this:
- During graph traversal, the node values and weights of the children
are pushed onto the evaluator `stack`. The traversal order guarantees
that the item with least node index will be on top.
- Once we reach the `Add` node itself, we start popping off the `stack`
and into the evaluator's `blend_register`, which is an accumulator
holding up to one weight-value pair:
- If the `blend_register` is empty, it is filled using data from the top
of the `stack`.
- Otherwise, the `blend_register` is combined with data popped from the
`stack` and updated.

In the example above, the additive blending steps would look like this
(with the pre-existing implementation):
1. The `blend_register` is empty, so we pop `(base_clip, 0.5)` from the
top of the `stack` and put it in. Now the value of the `blend_register`
is `(base_clip, 0.5)`.
2. The `blend_register` is non-empty: we pop `(additive_clip_1, 0.1)`
from the top of the `stack` and combine it additively with the value in
the `blend_register`, forming `(base_clip + 0.1 * additive_clip_1, 0.6)`
in the `blend_register` (the carried weight value goes unused).
3. The `blend_register` is non-empty: we pop `(additive_clip_2, 0.2)`
from the top of the `stack` and combine it additively with the value in
the `blend_register`, forming `(base_clip + 0.1 * additive_clip_1 + 0.2
* additive_clip_2, 0.8)` in the `blend_register`.

The solution in this PR changes step 1: the `base_clip` is multiplied by
its weight as it is added to the `blend_register` in the first place,
yielding `0.5 * base_clip + 0.1 * additive_clip_1 + 0.2 *
additive_clip_2` as the final result.

### Note for reviewers

It might be tempting to look at the code, which contains a segment that
looks like this:
```rust
if additive {
    current_value = A::blend(
        [
            BlendInput {
                weight: 1.0, // <--
                value: current_value,
                additive: true,
            },
            BlendInput {
                weight: weight_to_blend,
                value: value_to_blend,
                additive: true,
            },
        ]
        .into_iter(),
    );
} 
```
and conclude that the explicit value of `1.0` is responsible for
overwriting the weight of the base animation. This is incorrect.

Rather, this additive blend has to be written this way because it is
multiplying the *existing value in the blend register* by 1 (i.e. not
doing anything) before adding the next value to it. Changing this to
another quantity (e.g. the existing weight) would cause the value in the
blend register to be spuriously multiplied down.

## Testing

Tested on `animation_masks` example. Checked `morph_weights` example as
well.

## Migration Guide

I will write a migration guide later if this change is not included in
0.15.
2024-11-11 21:50:47 +01:00

925 lines
36 KiB
Rust

//! The animation graph, which allows animations to be blended together.
use core::iter;
use core::ops::{Index, IndexMut, Range};
use std::io::{self, Write};
use bevy_asset::{
io::Reader, Asset, AssetEvent, AssetId, AssetLoader, AssetPath, Assets, Handle, LoadContext,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
event::EventReader,
reflect::ReflectComponent,
system::{Res, ResMut, Resource},
};
use bevy_reflect::{prelude::ReflectDefault, Reflect, ReflectSerialize};
use bevy_utils::HashMap;
use derive_more::derive::{Display, Error, From};
use petgraph::{
graph::{DiGraph, NodeIndex},
Direction,
};
use ron::de::SpannedError;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::{AnimationClip, AnimationTargetId};
/// A graph structure that describes how animation clips are to be blended
/// together.
///
/// Applications frequently want to be able to play multiple animations at once
/// and to fine-tune the influence that animations have on a skinned mesh. Bevy
/// uses an *animation graph* to store this information. Animation graphs are a
/// directed acyclic graph (DAG) that describes how animations are to be
/// weighted and combined together. Every frame, Bevy evaluates the graph from
/// the root and blends the animations together in a bottom-up fashion to
/// produce the final pose.
///
/// There are three types of nodes: *blend nodes*, *add nodes*, and *clip
/// nodes*, all of which can have an associated weight. Blend nodes and add
/// nodes have no associated animation clip and combine the animations of their
/// children according to those children's weights. Clip nodes specify an
/// animation clip to play. When a graph is created, it starts with only a
/// single blend node, the root node.
///
/// For example, consider the following graph:
///
/// ```text
/// ┌────────────┐
/// │ │
/// │ Idle ├─────────────────────┐
/// │ │ │
/// └────────────┘ │
/// │
/// ┌────────────┐ │ ┌────────────┐
/// │ │ │ │ │
/// │ Run ├──┐ ├──┤ Root │
/// │ │ │ ┌────────────┐ │ │ │
/// └────────────┘ │ │ Blend │ │ └────────────┘
/// ├──┤ ├──┘
/// ┌────────────┐ │ │ 0.5 │
/// │ │ │ └────────────┘
/// │ Walk ├──┘
/// │ │
/// └────────────┘
/// ```
///
/// In this case, assuming that Idle, Run, and Walk are all playing with weight
/// 1.0, the Run and Walk animations will be equally blended together, then
/// their weights will be halved and finally blended with the Idle animation.
/// Thus the weight of Run and Walk are effectively half of the weight of Idle.
///
/// Nodes can optionally have a *mask*, a bitfield that restricts the set of
/// animation targets that the node and its descendants affect. Each bit in the
/// mask corresponds to a *mask group*, which is a set of animation targets
/// (bones). An animation target can belong to any number of mask groups within
/// the context of an animation graph.
///
/// When the appropriate bit is set in a node's mask, neither the node nor its
/// descendants will animate any animation targets belonging to that mask group.
/// That is, setting a mask bit to 1 *disables* the animation targets in that
/// group. If an animation target belongs to multiple mask groups, masking any
/// one of the mask groups that it belongs to will mask that animation target.
/// (Thus an animation target will only be animated if *all* of its mask groups
/// are unmasked.)
///
/// A common use of masks is to allow characters to hold objects. For this, the
/// typical workflow is to assign each character's hand to a mask group. Then,
/// when the character picks up an object, the application masks out the hand
/// that the object is held in for the character's animation set, then positions
/// the hand's digits as necessary to grasp the object. The character's
/// animations will continue to play but will not affect the hand, which will
/// continue to be depicted as holding the object.
///
/// Animation graphs are assets and can be serialized to and loaded from [RON]
/// files. Canonically, such files have an `.animgraph.ron` extension.
///
/// The animation graph implements [RFC 51]. See that document for more
/// information.
///
/// [RON]: https://github.com/ron-rs/ron
///
/// [RFC 51]: https://github.com/bevyengine/rfcs/blob/main/rfcs/51-animation-composition.md
#[derive(Asset, Reflect, Clone, Debug, Serialize)]
#[reflect(Serialize, Debug)]
#[serde(into = "SerializedAnimationGraph")]
pub struct AnimationGraph {
/// The `petgraph` data structure that defines the animation graph.
pub graph: AnimationDiGraph,
/// The index of the root node in the animation graph.
pub root: NodeIndex,
/// The mask groups that each animation target (bone) belongs to.
///
/// Each value in this map is a bitfield, in which 0 in bit position N
/// indicates that the animation target doesn't belong to mask group N, and
/// a 1 in position N indicates that the animation target does belong to
/// mask group N.
///
/// Animation targets not in this collection are treated as though they
/// don't belong to any mask groups.
pub mask_groups: HashMap<AnimationTargetId, AnimationMask>,
}
/// A [`Handle`] to the [`AnimationGraph`] to be used by the [`AnimationPlayer`](crate::AnimationPlayer) on the same entity.
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default)]
pub struct AnimationGraphHandle(pub Handle<AnimationGraph>);
impl From<AnimationGraphHandle> for AssetId<AnimationGraph> {
fn from(handle: AnimationGraphHandle) -> Self {
handle.id()
}
}
impl From<&AnimationGraphHandle> for AssetId<AnimationGraph> {
fn from(handle: &AnimationGraphHandle) -> Self {
handle.id()
}
}
/// A type alias for the `petgraph` data structure that defines the animation
/// graph.
pub type AnimationDiGraph = DiGraph<AnimationGraphNode, (), u32>;
/// The index of either an animation or blend node in the animation graph.
///
/// These indices are the way that [animation players] identify each animation.
///
/// [animation players]: crate::AnimationPlayer
pub type AnimationNodeIndex = NodeIndex<u32>;
/// An individual node within an animation graph.
///
/// The [`AnimationGraphNode::node_type`] field specifies the type of node: one
/// of a *clip node*, a *blend node*, or an *add node*. Clip nodes, the leaves
/// of the graph, contain animation clips to play. Blend and add nodes describe
/// how to combine their children to produce a final animation.
#[derive(Clone, Reflect, Debug)]
pub struct AnimationGraphNode {
/// Animation node data specific to the type of node (clip, blend, or add).
///
/// In the case of clip nodes, this contains the actual animation clip
/// associated with the node.
pub node_type: AnimationNodeType,
/// A bitfield specifying the mask groups that this node and its descendants
/// will not affect.
///
/// A 0 in bit N indicates that this node and its descendants *can* animate
/// animation targets in mask group N, while a 1 in bit N indicates that
/// this node and its descendants *cannot* animate mask group N.
pub mask: AnimationMask,
/// The weight of this node, which signifies its contribution in blending.
///
/// Note that this does not propagate down the graph hierarchy; rather,
/// each [Blend] and [Add] node uses the weights of its children to determine
/// the total animation that is accumulated at that node. The parent node's
/// weight is used only to determine the contribution of that total animation
/// in *further* blending.
///
/// In other words, it is as if the blend node is replaced by a single clip
/// node consisting of the blended animation with the weight specified at the
/// blend node.
///
/// For animation clips, this weight is also multiplied by the [active animation weight]
/// before being applied.
///
/// [Blend]: AnimationNodeType::Blend
/// [Add]: AnimationNodeType::Add
/// [active animation weight]: crate::ActiveAnimation::weight
pub weight: f32,
}
/// Animation node data specific to the type of node (clip, blend, or add).
///
/// In the case of clip nodes, this contains the actual animation clip
/// associated with the node.
#[derive(Clone, Default, Reflect, Debug)]
pub enum AnimationNodeType {
/// A *clip node*, which plays an animation clip.
///
/// These are always the leaves of the graph.
Clip(Handle<AnimationClip>),
/// A *blend node*, which blends its children according to their weights.
///
/// The weights of all the children of this node are normalized to 1.0.
#[default]
Blend,
/// An *additive blend node*, which combines the animations of its children
/// additively.
///
/// The weights of all the children of this node are *not* normalized to
/// 1.0. Rather, each child is multiplied by its respective weight and
/// added in sequence.
///
/// Add nodes are primarily useful for superimposing an animation for a
/// portion of a rig on top of the main animation. For example, an add node
/// could superimpose a weapon attack animation for a character's limb on
/// top of a running animation to produce an animation of a character
/// attacking while running.
Add,
}
/// An [`AssetLoader`] that can load [`AnimationGraph`]s as assets.
///
/// The canonical extension for [`AnimationGraph`]s is `.animgraph.ron`. Plain
/// `.animgraph` is supported as well.
#[derive(Default)]
pub struct AnimationGraphAssetLoader;
/// Various errors that can occur when serializing or deserializing animation
/// graphs to and from RON, respectively.
#[derive(Error, Display, Debug, From)]
pub enum AnimationGraphLoadError {
/// An I/O error occurred.
#[display("I/O")]
Io(io::Error),
/// An error occurred in RON serialization or deserialization.
#[display("RON serialization")]
Ron(ron::Error),
/// An error occurred in RON deserialization, and the location of the error
/// is supplied.
#[display("RON serialization")]
SpannedRon(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<AssetId<AnimationGraph>, 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<AnimationNodeIndex>,
/// 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<Range<u32>>,
/// A list of the children of each node, sorted in ascending order.
pub sorted_edges: Vec<AnimationNodeIndex>,
/// 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<u64>,
}
/// A version of [`AnimationGraph`] suitable for serializing as an asset.
///
/// Animation nodes can refer to external animation clips, and the [`AssetId`]
/// is typically not sufficient to identify the clips, since the
/// [`bevy_asset::AssetServer`] assigns IDs in unpredictable ways. That fact
/// motivates this type, which replaces the `Handle<AnimationClip>` with an
/// asset path. Loading an animation graph via the [`bevy_asset::AssetServer`]
/// actually loads a serialized instance of this type, as does serializing an
/// [`AnimationGraph`] through `serde`.
#[derive(Serialize, Deserialize)]
pub struct SerializedAnimationGraph {
/// Corresponds to the `graph` field on [`AnimationGraph`].
pub graph: DiGraph<SerializedAnimationGraphNode, (), u32>,
/// Corresponds to the `root` field on [`AnimationGraph`].
pub root: NodeIndex,
/// Corresponds to the `mask_groups` field on [`AnimationGraph`].
pub mask_groups: HashMap<AnimationTargetId, AnimationMask>,
}
/// A version of [`AnimationGraphNode`] suitable for serializing as an asset.
///
/// See the comments in [`SerializedAnimationGraph`] for more information.
#[derive(Serialize, Deserialize)]
pub struct SerializedAnimationGraphNode {
/// Corresponds to the `node_type` field on [`AnimationGraphNode`].
pub node_type: SerializedAnimationNodeType,
/// Corresponds to the `mask` field on [`AnimationGraphNode`].
pub mask: AnimationMask,
/// Corresponds to the `weight` field on [`AnimationGraphNode`].
pub weight: f32,
}
/// A version of [`AnimationNodeType`] suitable for serializing as part of a
/// [`SerializedAnimationGraphNode`] asset.
#[derive(Serialize, Deserialize)]
pub enum SerializedAnimationNodeType {
/// Corresponds to [`AnimationNodeType::Clip`].
Clip(SerializedAnimationClip),
/// Corresponds to [`AnimationNodeType::Blend`].
Blend,
/// Corresponds to [`AnimationNodeType::Add`].
Add,
}
/// A version of `Handle<AnimationClip>` suitable for serializing as an asset.
///
/// This replaces any handle that has a path with an [`AssetPath`]. Failing
/// that, the asset ID is serialized directly.
#[derive(Serialize, Deserialize)]
pub enum SerializedAnimationClip {
/// Records an asset path.
AssetPath(AssetPath<'static>),
/// The fallback that records an asset ID.
///
/// Because asset IDs can change, this should not be relied upon. Prefer to
/// use asset paths where possible.
AssetId(AssetId<AnimationClip>),
}
/// The type of an animation mask bitfield.
///
/// Bit N corresponds to mask group N.
///
/// Because this is a 64-bit value, there is currently a limitation of 64 mask
/// groups per animation graph.
pub type AnimationMask = u64;
impl AnimationGraph {
/// Creates a new animation graph with a root node and no other nodes.
pub fn new() -> Self {
let mut graph = DiGraph::default();
let root = graph.add_node(AnimationGraphNode::default());
Self {
graph,
root,
mask_groups: HashMap::new(),
}
}
/// A convenience function for creating an [`AnimationGraph`] from a single
/// [`AnimationClip`].
///
/// The clip will be a direct child of the root with weight 1.0. Both the
/// graph and the index of the added node are returned as a tuple.
pub fn from_clip(clip: Handle<AnimationClip>) -> (Self, AnimationNodeIndex) {
let mut graph = Self::new();
let node_index = graph.add_clip(clip, 1.0, graph.root);
(graph, node_index)
}
/// A convenience method to create an [`AnimationGraph`]s with an iterator
/// of clips.
///
/// All of the animation clips will be direct children of the root with
/// weight 1.0.
///
/// Returns the graph and indices of the new nodes.
pub fn from_clips<'a, I>(clips: I) -> (Self, Vec<AnimationNodeIndex>)
where
I: IntoIterator<Item = Handle<AnimationClip>>,
<I as IntoIterator>::IntoIter: 'a,
{
let mut graph = Self::new();
let indices = graph.add_clips(clips, 1.0, graph.root).collect();
(graph, indices)
}
/// Adds an [`AnimationClip`] to the animation graph with the given weight
/// and returns its index.
///
/// The animation clip will be the child of the given parent. The resulting
/// node will have no mask.
pub fn add_clip(
&mut self,
clip: Handle<AnimationClip>,
weight: f32,
parent: AnimationNodeIndex,
) -> AnimationNodeIndex {
let node_index = self.graph.add_node(AnimationGraphNode {
node_type: AnimationNodeType::Clip(clip),
mask: 0,
weight,
});
self.graph.add_edge(parent, node_index, ());
node_index
}
/// Adds an [`AnimationClip`] to the animation graph with the given weight
/// and mask, and returns its index.
///
/// The animation clip will be the child of the given parent.
pub fn add_clip_with_mask(
&mut self,
clip: Handle<AnimationClip>,
mask: AnimationMask,
weight: f32,
parent: AnimationNodeIndex,
) -> AnimationNodeIndex {
let node_index = self.graph.add_node(AnimationGraphNode {
node_type: AnimationNodeType::Clip(clip),
mask,
weight,
});
self.graph.add_edge(parent, node_index, ());
node_index
}
/// A convenience method to add multiple [`AnimationClip`]s to the animation
/// graph.
///
/// All of the animation clips will have the same weight and will be
/// parented to the same node.
///
/// Returns the indices of the new nodes.
pub fn add_clips<'a, I>(
&'a mut self,
clips: I,
weight: f32,
parent: AnimationNodeIndex,
) -> impl Iterator<Item = AnimationNodeIndex> + 'a
where
I: IntoIterator<Item = Handle<AnimationClip>>,
<I as IntoIterator>::IntoIter: 'a,
{
clips
.into_iter()
.map(move |clip| self.add_clip(clip, weight, parent))
}
/// Adds a blend node to the animation graph with the given weight and
/// returns its index.
///
/// The blend node will be placed under the supplied `parent` node. During
/// animation evaluation, the descendants of this blend node will have their
/// weights multiplied by the weight of the blend. The blend node will have
/// no mask.
pub fn add_blend(&mut self, weight: f32, parent: AnimationNodeIndex) -> AnimationNodeIndex {
let node_index = self.graph.add_node(AnimationGraphNode {
node_type: AnimationNodeType::Blend,
mask: 0,
weight,
});
self.graph.add_edge(parent, node_index, ());
node_index
}
/// Adds a blend node to the animation graph with the given weight and
/// returns its index.
///
/// The blend node will be placed under the supplied `parent` node. During
/// animation evaluation, the descendants of this blend node will have their
/// weights multiplied by the weight of the blend. Neither this node nor its
/// descendants will affect animation targets that belong to mask groups not
/// in the given `mask`.
pub fn add_blend_with_mask(
&mut self,
mask: AnimationMask,
weight: f32,
parent: AnimationNodeIndex,
) -> AnimationNodeIndex {
let node_index = self.graph.add_node(AnimationGraphNode {
node_type: AnimationNodeType::Blend,
mask,
weight,
});
self.graph.add_edge(parent, node_index, ());
node_index
}
/// Adds a blend node to the animation graph with the given weight and
/// returns its index.
///
/// The blend node will be placed under the supplied `parent` node. During
/// animation evaluation, the descendants of this blend node will have their
/// weights multiplied by the weight of the blend. The blend node will have
/// no mask.
pub fn add_additive_blend(
&mut self,
weight: f32,
parent: AnimationNodeIndex,
) -> AnimationNodeIndex {
let node_index = self.graph.add_node(AnimationGraphNode {
node_type: AnimationNodeType::Add,
mask: 0,
weight,
});
self.graph.add_edge(parent, node_index, ());
node_index
}
/// Adds a blend node to the animation graph with the given weight and
/// returns its index.
///
/// The blend node will be placed under the supplied `parent` node. During
/// animation evaluation, the descendants of this blend node will have their
/// weights multiplied by the weight of the blend. Neither this node nor its
/// descendants will affect animation targets that belong to mask groups not
/// in the given `mask`.
pub fn add_additive_blend_with_mask(
&mut self,
mask: AnimationMask,
weight: f32,
parent: AnimationNodeIndex,
) -> AnimationNodeIndex {
let node_index = self.graph.add_node(AnimationGraphNode {
node_type: AnimationNodeType::Add,
mask,
weight,
});
self.graph.add_edge(parent, node_index, ());
node_index
}
/// Adds an edge from the edge `from` to `to`, making `to` a child of
/// `from`.
///
/// The behavior is unspecified if adding this produces a cycle in the
/// graph.
pub fn add_edge(&mut self, from: NodeIndex, to: NodeIndex) {
self.graph.add_edge(from, to, ());
}
/// Removes an edge between `from` and `to` if it exists.
///
/// Returns true if the edge was successfully removed or false if no such
/// edge existed.
pub fn remove_edge(&mut self, from: NodeIndex, to: NodeIndex) -> bool {
self.graph
.find_edge(from, to)
.map(|edge| self.graph.remove_edge(edge))
.is_some()
}
/// Returns the [`AnimationGraphNode`] associated with the given index.
///
/// If no node with the given index exists, returns `None`.
pub fn get(&self, animation: AnimationNodeIndex) -> Option<&AnimationGraphNode> {
self.graph.node_weight(animation)
}
/// Returns a mutable reference to the [`AnimationGraphNode`] associated
/// with the given index.
///
/// If no node with the given index exists, returns `None`.
pub fn get_mut(&mut self, animation: AnimationNodeIndex) -> Option<&mut AnimationGraphNode> {
self.graph.node_weight_mut(animation)
}
/// Returns an iterator over the [`AnimationGraphNode`]s in this graph.
pub fn nodes(&self) -> impl Iterator<Item = AnimationNodeIndex> {
self.graph.node_indices()
}
/// Serializes the animation graph to the given [`Write`]r in RON format.
///
/// If writing to a file, it can later be loaded with the
/// [`AnimationGraphAssetLoader`] to reconstruct the graph.
pub fn save<W>(&self, writer: &mut W) -> Result<(), AnimationGraphLoadError>
where
W: Write,
{
let mut ron_serializer = ron::ser::Serializer::new(writer, None)?;
Ok(self.serialize(&mut ron_serializer)?)
}
/// Adds an animation target (bone) to the mask group with the given ID.
///
/// Calling this method multiple times with the same animation target but
/// different mask groups will result in that target being added to all of
/// the specified groups.
pub fn add_target_to_mask_group(&mut self, target: AnimationTargetId, mask_group: u32) {
*self.mask_groups.entry(target).or_default() |= 1 << mask_group;
}
}
impl AnimationGraphNode {
/// Masks out the mask groups specified by the given `mask` bitfield.
///
/// A 1 in bit position N causes this function to mask out mask group N, and
/// thus neither this node nor its descendants will animate any animation
/// targets that belong to group N.
pub fn add_mask(&mut self, mask: AnimationMask) -> &mut Self {
self.mask |= mask;
self
}
/// Unmasks the mask groups specified by the given `mask` bitfield.
///
/// A 1 in bit position N causes this function to unmask mask group N, and
/// thus this node and its descendants will be allowed to animate animation
/// targets that belong to group N, unless another mask masks those targets
/// out.
pub fn remove_mask(&mut self, mask: AnimationMask) -> &mut Self {
self.mask &= !mask;
self
}
/// Masks out the single mask group specified by `group`.
///
/// After calling this function, neither this node nor its descendants will
/// animate any animation targets that belong to the given `group`.
pub fn add_mask_group(&mut self, group: u32) -> &mut Self {
self.add_mask(1 << group)
}
/// Unmasks the single mask group specified by `group`.
///
/// After calling this function, this node and its descendants will be
/// allowed to animate animation targets that belong to the given `group`,
/// unless another mask masks those targets out.
pub fn remove_mask_group(&mut self, group: u32) -> &mut Self {
self.remove_mask(1 << group)
}
}
impl Index<AnimationNodeIndex> for AnimationGraph {
type Output = AnimationGraphNode;
fn index(&self, index: AnimationNodeIndex) -> &Self::Output {
&self.graph[index]
}
}
impl IndexMut<AnimationNodeIndex> for AnimationGraph {
fn index_mut(&mut self, index: AnimationNodeIndex) -> &mut Self::Output {
&mut self.graph[index]
}
}
impl Default for AnimationGraphNode {
fn default() -> Self {
Self {
node_type: Default::default(),
mask: 0,
weight: 1.0,
}
}
}
impl Default for AnimationGraph {
fn default() -> Self {
Self::new()
}
}
impl AssetLoader for AnimationGraphAssetLoader {
type Asset = AnimationGraph;
type Settings = ();
type Error = AnimationGraphLoadError;
async fn load(
&self,
reader: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
// Deserialize a `SerializedAnimationGraph` directly, so that we can
// get the list of the animation clips it refers to and load them.
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
let serialized_animation_graph = SerializedAnimationGraph::deserialize(&mut deserializer)
.map_err(|err| deserializer.span_error(err))?;
// Load all `AssetPath`s to convert from a
// `SerializedAnimationGraph` to a real `AnimationGraph`.
Ok(AnimationGraph {
graph: serialized_animation_graph.graph.map(
|_, serialized_node| AnimationGraphNode {
node_type: match serialized_node.node_type {
SerializedAnimationNodeType::Clip(ref clip) => match clip {
SerializedAnimationClip::AssetId(asset_id) => {
AnimationNodeType::Clip(Handle::Weak(*asset_id))
}
SerializedAnimationClip::AssetPath(asset_path) => {
AnimationNodeType::Clip(load_context.load(asset_path))
}
},
SerializedAnimationNodeType::Blend => AnimationNodeType::Blend,
SerializedAnimationNodeType::Add => AnimationNodeType::Add,
},
mask: serialized_node.mask,
weight: serialized_node.weight,
},
|_, _| (),
),
root: serialized_animation_graph.root,
mask_groups: serialized_animation_graph.mask_groups,
})
}
fn extensions(&self) -> &[&str] {
&["animgraph", "animgraph.ron"]
}
}
impl From<AnimationGraph> for SerializedAnimationGraph {
fn from(animation_graph: AnimationGraph) -> Self {
// If any of the animation clips have paths, then serialize them as
// `SerializedAnimationClip::AssetPath` so that the
// `AnimationGraphAssetLoader` can load them.
Self {
graph: animation_graph.graph.map(
|_, node| SerializedAnimationGraphNode {
weight: node.weight,
mask: node.mask,
node_type: match node.node_type {
AnimationNodeType::Clip(ref clip) => match clip.path() {
Some(path) => SerializedAnimationNodeType::Clip(
SerializedAnimationClip::AssetPath(path.clone()),
),
None => SerializedAnimationNodeType::Clip(
SerializedAnimationClip::AssetId(clip.id()),
),
},
AnimationNodeType::Blend => SerializedAnimationNodeType::Blend,
AnimationNodeType::Add => SerializedAnimationNodeType::Add,
},
},
|_, _| (),
),
root: animation_graph.root,
mask_groups: animation_graph.mask_groups,
}
}
}
/// 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<ThreadedAnimationGraphs>,
animation_graphs: Res<Assets<AnimationGraph>>,
mut animation_graph_asset_events: EventReader<AssetEvent<AnimationGraph>>,
) {
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[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);
}
}