
# Objective Stumbled upon one of these, and set off in search of more, armed with my trusty `\b(\w+)\s+\1\b`. ## Solution Remove ~one~ one of them.
926 lines
36 KiB
Rust
926 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, the first child is used as a base, ignoring its weight,
|
|
/// while the others are multiplied by their respective weights and then
|
|
/// added in sequence to the base.
|
|
///
|
|
/// 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);
|
|
}
|
|
}
|