use core::{
fmt::Write,
iter,
ops::{Index, IndexMut, Range},
};
use std::io;
use bevy_asset::{
io::Reader, Asset, AssetEvent, AssetId, AssetLoader, AssetPath, Assets, Handle, LoadContext,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
message::MessageReader,
reflect::ReflectComponent,
resource::Resource,
system::{Local, Res, ResMut},
template::FromTemplate,
};
use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
use bitvec::vec::BitVec;
use derive_more::derive::From;
use petgraph::{
graph::{DiGraph, NodeIndex},
Direction,
};
use ron::de::SpannedError;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use thiserror::Error;
use crate::{AnimationClip, AnimationTargetId};
#[derive(Asset, Reflect, Clone, Debug)]
#[reflect(Debug, Clone)]
pub struct AnimationGraph {
pub graph: AnimationDiGraph,
pub root: NodeIndex,
pub mask_groups: HashMap<AnimationTargetId, AnimationMask>,
}
#[derive(
Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From, FromTemplate,
)]
#[reflect(Component, Default, Clone)]
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()
}
}
pub type AnimationDiGraph = DiGraph<AnimationGraphNode, (), u32>;
pub type AnimationNodeIndex = NodeIndex<u32>;
#[derive(Clone, Reflect, Debug)]
#[reflect(Clone)]
pub struct AnimationGraphNode {
pub node_type: AnimationNodeType,
pub mask: AnimationMask,
pub weight: f32,
}
#[derive(Clone, Default, Reflect, Debug)]
#[reflect(Clone)]
pub enum AnimationNodeType {
Clip(Handle<AnimationClip>),
#[default]
Blend,
Add,
}
#[derive(Default, TypePath)]
pub struct AnimationGraphAssetLoader;
#[derive(Error, Debug)]
pub enum AnimationGraphSaveError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Ron(#[from] ron::Error),
#[error(transparent)]
ConvertToSerialized(#[from] NonPathHandleError),
}
#[derive(Error, Debug)]
pub enum AnimationGraphLoadError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Ron(#[from] ron::Error),
#[error(transparent)]
SpannedRon(#[from] SpannedError),
#[error(
"The deserialized AnimationGraph contained an AnimationClip referenced by an AssetId, \
which is no longer supported. Consider manually deserializing the SerializedAnimationGraph \
type and determine how to migrate any SerializedAnimationClip::AssetId animation clips"
)]
GraphContainsLegacyAssetId,
}
#[derive(Default, Reflect, Resource)]
pub struct ThreadedAnimationGraphs {
pub(crate) threaded_graphs: HashMap<AssetId<AnimationGraph>, ThreadedAnimationGraph>,
clip_to_graphs: HashMap<AssetId<AnimationClip>, HashSet<AssetId<AnimationGraph>>>,
}
#[derive(Default, Reflect)]
pub struct ThreadedAnimationGraph {
pub threaded_graph: Vec<AnimationNodeIndex>,
pub sorted_edge_list_ranges: Vec<Range<u32>>,
pub sorted_edges: Vec<AnimationNodeIndex>,
pub computed_masks: Vec<u64>,
pub animation_clips: HashSet<AssetId<AnimationClip>>,
pub animation_target_to_threaded_subgraph:
HashMap<AnimationTargetId, ThreadedAnimationSubgraph>,
}
#[derive(Default, Reflect)]
pub struct ThreadedAnimationSubgraph {
pub threaded_graph: Vec<AnimationNodeIndex>,
pub sorted_edge_list_offsets: Vec<u32>,
pub sorted_edges: Vec<AnimationNodeIndex>,
}
#[derive(Serialize, Deserialize)]
pub struct SerializedAnimationGraph {
pub graph: DiGraph<SerializedAnimationGraphNode, (), u32>,
pub root: NodeIndex,
pub mask_groups: HashMap<AnimationTargetId, AnimationMask>,
}
#[derive(Serialize, Deserialize)]
pub struct SerializedAnimationGraphNode {
pub node_type: SerializedAnimationNodeType,
pub mask: AnimationMask,
pub weight: f32,
}
#[derive(Serialize, Deserialize)]
pub enum SerializedAnimationNodeType {
Clip(AssetPath<'static>),
Blend,
Add,
}
pub type AnimationMask = u64;
impl AnimationGraph {
pub fn new() -> Self {
let mut graph = DiGraph::default();
let root = graph.add_node(AnimationGraphNode::default());
Self {
graph,
root,
mask_groups: HashMap::default(),
}
}
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)
}
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)
}
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
}
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
}
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))
}
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
}
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
}
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
}
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
}
pub fn add_edge(&mut self, from: NodeIndex, to: NodeIndex) {
self.graph.add_edge(from, to, ());
}
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()
}
pub fn get(&self, animation: AnimationNodeIndex) -> Option<&AnimationGraphNode> {
self.graph.node_weight(animation)
}
pub fn get_mut(&mut self, animation: AnimationNodeIndex) -> Option<&mut AnimationGraphNode> {
self.graph.node_weight_mut(animation)
}
pub fn nodes(&self) -> impl Iterator<Item = AnimationNodeIndex> {
self.graph.node_indices()
}
pub fn save<W>(&self, writer: &mut W) -> Result<(), AnimationGraphSaveError>
where
W: Write,
{
let mut ron_serializer = ron::ser::Serializer::new(writer, None)?;
let serialized_graph: SerializedAnimationGraph = self.clone().try_into()?;
Ok(serialized_graph.serialize(&mut ron_serializer)?)
}
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 {
pub fn add_mask(&mut self, mask: AnimationMask) -> &mut Self {
self.mask |= mask;
self
}
pub fn remove_mask(&mut self, mask: AnimationMask) -> &mut Self {
self.mask &= !mask;
self
}
pub fn add_mask_group(&mut self, group: u32) -> &mut Self {
self.add_mask(1 << group)
}
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?;
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
let serialized_animation_graph = SerializedAnimationGraph::deserialize(&mut deserializer)
.map_err(|err| deserializer.span_error(err))?;
let mut animation_graph = DiGraph::with_capacity(
serialized_animation_graph.graph.node_count(),
serialized_animation_graph.graph.edge_count(),
);
for serialized_node in serialized_animation_graph.graph.node_weights() {
animation_graph.add_node(AnimationGraphNode {
node_type: match serialized_node.node_type {
SerializedAnimationNodeType::Clip(ref path) => {
AnimationNodeType::Clip(load_context.load(path.clone()))
}
SerializedAnimationNodeType::Blend => AnimationNodeType::Blend,
SerializedAnimationNodeType::Add => AnimationNodeType::Add,
},
mask: serialized_node.mask,
weight: serialized_node.weight,
});
}
for edge in serialized_animation_graph.graph.raw_edges() {
animation_graph.add_edge(edge.source(), edge.target(), ());
}
Ok(AnimationGraph {
graph: animation_graph,
root: serialized_animation_graph.root,
mask_groups: serialized_animation_graph.mask_groups,
})
}
fn extensions(&self) -> &[&str] {
&["animgraph", "animgraph.ron"]
}
}
impl TryFrom<AnimationGraph> for SerializedAnimationGraph {
type Error = NonPathHandleError;
fn try_from(animation_graph: AnimationGraph) -> Result<Self, NonPathHandleError> {
let mut serialized_graph = DiGraph::with_capacity(
animation_graph.graph.node_count(),
animation_graph.graph.edge_count(),
);
for node in animation_graph.graph.node_weights() {
serialized_graph.add_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(path.clone()),
None => return Err(NonPathHandleError),
},
AnimationNodeType::Blend => SerializedAnimationNodeType::Blend,
AnimationNodeType::Add => SerializedAnimationNodeType::Add,
},
});
}
for edge in animation_graph.graph.raw_edges() {
serialized_graph.add_edge(edge.source(), edge.target(), ());
}
Ok(Self {
graph: serialized_graph,
root: animation_graph.root,
mask_groups: animation_graph.mask_groups,
})
}
}
#[derive(Error, Debug)]
#[error("AnimationGraph contains a handle to an AnimationClip that does not correspond to an asset path")]
pub struct NonPathHandleError;
pub(crate) fn thread_animation_graphs(
threaded_animation_graphs: ResMut<ThreadedAnimationGraphs>,
animation_graphs: Res<Assets<AnimationGraph>>,
animation_clips: Res<Assets<AnimationClip>>,
mut animation_graph_asset_events: MessageReader<AssetEvent<AnimationGraph>>,
mut animation_clip_asset_events: MessageReader<AssetEvent<AnimationClip>>,
mut animation_target_graphs_rebuilt_this_frame: Local<HashSet<AssetId<AnimationGraph>>>,
) {
if animation_graph_asset_events.is_empty() && animation_clip_asset_events.is_empty() {
return;
}
let threaded_animation_graphs = threaded_animation_graphs.into_inner();
animation_target_graphs_rebuilt_this_frame.clear();
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 } => {
let Some(animation_graph) = animation_graphs.get(id) else {
continue;
};
let mut threaded_animation_graph =
match threaded_animation_graphs.threaded_graphs.remove(&id) {
None => ThreadedAnimationGraph::default(),
Some(mut existing_threaded_animation_graph) => {
existing_threaded_animation_graph.remove_from_clip_to_graphs_table(
id,
&mut threaded_animation_graphs.clip_to_graphs,
);
existing_threaded_animation_graph.clear();
existing_threaded_animation_graph
}
};
threaded_animation_graph.init(animation_graph);
threaded_animation_graph.populate_clip_to_graphs_table(
id,
&mut threaded_animation_graphs.clip_to_graphs,
);
threaded_animation_graph.build_from(
&animation_graph.graph,
animation_graph.root,
0,
);
threaded_animation_graph
.rebuild_target_subgraphs(&animation_graph.graph, &animation_clips);
animation_target_graphs_rebuilt_this_frame.insert(id);
threaded_animation_graphs
.threaded_graphs
.insert(id, threaded_animation_graph);
}
AssetEvent::Removed { id } => {
if let Some(threaded_animation_graph) =
threaded_animation_graphs.threaded_graphs.remove(&id)
{
threaded_animation_graph.remove_from_clip_to_graphs_table(
id,
&mut threaded_animation_graphs.clip_to_graphs,
);
}
}
AssetEvent::Unused { .. } => {}
}
}
for animation_clip_asset_event in animation_clip_asset_events.read() {
match *animation_clip_asset_event {
AssetEvent::Added { id }
| AssetEvent::Modified { id }
| AssetEvent::LoadedWithDependencies { id } => {
let Some(graph_ids) = threaded_animation_graphs.clip_to_graphs.get(&id) else {
continue;
};
for graph_id in graph_ids {
if animation_target_graphs_rebuilt_this_frame.insert(*graph_id)
&& let Some(threaded_animation_graph) =
threaded_animation_graphs.threaded_graphs.get_mut(graph_id)
&& let Some(animation_graph) = animation_graphs.get(*graph_id)
{
threaded_animation_graph
.rebuild_target_subgraphs(&animation_graph.graph, &animation_clips);
}
}
}
AssetEvent::Unused { .. } | AssetEvent::Removed { .. } => {}
}
}
}
impl ThreadedAnimationGraph {
fn clear(&mut self) {
self.threaded_graph.clear();
self.sorted_edge_list_ranges.clear();
self.sorted_edges.clear();
self.animation_clips.clear();
self.animation_target_to_threaded_subgraph.clear();
}
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_list_ranges.clear();
self.sorted_edge_list_ranges
.extend(iter::repeat_n(0..0, node_count));
self.computed_masks.clear();
self.computed_masks.extend(iter::repeat_n(0, node_count));
self.animation_clips.clear();
for node in animation_graph.graph.node_weights() {
match &node.node_type {
AnimationNodeType::Clip(clip_handle) => {
self.animation_clips.insert(clip_handle.id());
}
AnimationNodeType::Blend | AnimationNodeType::Add => {}
}
}
}
fn build_from(
&mut self,
graph: &AnimationDiGraph,
node_index: AnimationNodeIndex,
mut mask: u64,
) {
mask |= graph.node_weight(node_index).unwrap().mask;
self.computed_masks[node_index.index()] = mask;
let mut kids: SmallVec<[AnimationNodeIndex; 8]> = graph
.neighbors_directed(node_index, Direction::Outgoing)
.collect();
kids.sort_unstable();
self.sorted_edge_list_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);
for kid in kids.into_iter().rev() {
self.build_from(graph, kid, mask);
}
self.threaded_graph.push(node_index);
}
fn rebuild_target_subgraphs(
&mut self,
graph: &AnimationDiGraph,
clips: &Assets<AnimationClip>,
) {
self.animation_target_to_threaded_subgraph.clear();
let mut seen_animation_clips = HashSet::new();
let mut node_is_relevant: BitVec = iter::repeat_n(false, graph.node_count()).collect();
for node in graph.node_weights() {
let animation_clip = match &node.node_type {
AnimationNodeType::Clip(animation_clip_id) => {
if !seen_animation_clips.insert(animation_clip_id) {
continue;
}
let Some(animation_clip) = clips.get(animation_clip_id) else {
continue;
};
animation_clip
}
AnimationNodeType::Blend | AnimationNodeType::Add => continue,
};
for animation_target_id in animation_clip.curves().keys() {
if self
.animation_target_to_threaded_subgraph
.contains_key(animation_target_id)
{
continue;
};
let threaded_subgraph = self.create_target_subgraph(
*animation_target_id,
graph,
clips,
&mut node_is_relevant,
);
self.animation_target_to_threaded_subgraph
.insert(*animation_target_id, threaded_subgraph);
}
}
}
fn create_target_subgraph(
&self,
animation_target_id: AnimationTargetId,
graph: &AnimationDiGraph,
clips: &Assets<AnimationClip>,
node_is_relevant: &mut BitVec,
) -> ThreadedAnimationSubgraph {
node_is_relevant.fill(false);
let mut threaded_subgraph = ThreadedAnimationSubgraph::default();
for node_index in &self.threaded_graph {
let Some(node) = graph.node_weight(*node_index) else {
continue;
};
match &node.node_type {
AnimationNodeType::Clip(clip_handle) => {
if clips
.get(clip_handle)
.is_some_and(|clip| clip.is_relevant_to_target(animation_target_id))
{
threaded_subgraph.add_node(
*node_index,
&self.sorted_edge_list_ranges,
&self.sorted_edges,
node_is_relevant,
);
}
}
AnimationNodeType::Add | AnimationNodeType::Blend => {
let sorted_edge_range =
self.sorted_edge_list_ranges[node_index.index()].clone();
if sorted_edge_range.into_iter().any(|sorted_edge_index| {
node_is_relevant[self.sorted_edges[sorted_edge_index as usize].index()]
}) {
threaded_subgraph.add_node(
*node_index,
&self.sorted_edge_list_ranges,
&self.sorted_edges,
node_is_relevant,
);
}
}
}
}
threaded_subgraph
}
fn populate_clip_to_graphs_table(
&self,
graph_id: AssetId<AnimationGraph>,
clip_to_graphs: &mut HashMap<AssetId<AnimationClip>, HashSet<AssetId<AnimationGraph>>>,
) {
for clip_id in &self.animation_clips {
clip_to_graphs.entry(*clip_id).or_default().insert(graph_id);
}
}
fn remove_from_clip_to_graphs_table(
&self,
graph_id: AssetId<AnimationGraph>,
clip_to_graphs: &mut HashMap<AssetId<AnimationClip>, HashSet<AssetId<AnimationGraph>>>,
) {
for clip_id in &self.animation_clips {
let Entry::Occupied(mut graphs) = clip_to_graphs.entry(*clip_id) else {
continue;
};
graphs.get_mut().remove(&graph_id);
if graphs.get().is_empty() {
graphs.remove();
}
}
}
}
impl ThreadedAnimationSubgraph {
fn add_node(
&mut self,
node_index: NodeIndex<u32>,
original_sorted_edge_list_ranges: &[Range<u32>],
original_sorted_edges: &[NodeIndex<u32>],
node_is_relevant: &mut BitVec,
) {
node_is_relevant.set(node_index.index(), true);
self.threaded_graph.push(node_index);
let sorted_edge_range_start = self.sorted_edges.len() as u32;
let original_sorted_edge_list_range =
original_sorted_edge_list_ranges[node_index.index()].clone();
for original_sorted_edge_index in original_sorted_edge_list_range {
let edge_dest = original_sorted_edges[original_sorted_edge_index as usize];
if node_is_relevant[edge_dest.index()] {
self.sorted_edges.push(edge_dest);
}
}
self.sorted_edge_list_offsets.push(sorted_edge_range_start);
}
}
#[cfg(test)]
mod tests {
use std::array;
use bevy_asset::Assets;
use bevy_curve::{ConstantCurve, Interval};
use bevy_ecs::name::Name;
use bevy_math::Vec3;
use bevy_transform::components::Transform;
use itertools::Itertools;
use petgraph::graph::NodeIndex;
use crate::{
animated_field,
animation_curves::AnimatableCurve,
graph::{AnimationGraph, ThreadedAnimationGraph},
AnimationClip, AnimationTargetId,
};
#[test]
fn subgraph_prunes_irrelevant_clips() {
let (mut graph, mut clips) = (AnimationGraph::new(), Assets::<AnimationClip>::default());
let target_ids_and_nodes = ["A", "B"].map(|target_name| {
let target_id = AnimationTargetId::from_name(&Name::new(target_name));
let clip = clips.add(create_animation_clip_for_target(target_id));
let clip_node = graph.add_clip(clip, 1.0, graph.root);
(target_id, clip_node)
});
let threaded_graph = create_threaded_graph_from_animation_graph(&graph, &clips);
assert_eq!(
threaded_graph.animation_target_to_threaded_subgraph.len(),
2
);
for (target_id, clip_node) in target_ids_and_nodes {
let subgraph = &threaded_graph.animation_target_to_threaded_subgraph[&target_id];
assert_eq!(
subgraph.threaded_graph.iter().sorted().collect::<Vec<_>>(),
[graph.root, clip_node].iter().sorted().collect::<Vec<_>>()
);
}
}
#[test]
fn subgraph_prunes_irrelevant_blend_nodes() {
let (mut graph, mut clips) = (AnimationGraph::new(), Assets::<AnimationClip>::default());
let target_ids_and_nodes = ["A", "B"].map(|target_name| {
let target_id = AnimationTargetId::from_name(&Name::new(target_name));
let blend_node = graph.add_blend(1.0, graph.root);
let clip = clips.add(create_animation_clip_for_target(target_id));
let clip_node = graph.add_clip(clip, 1.0, blend_node);
(target_id, blend_node, clip_node)
});
let threaded_graph = create_threaded_graph_from_animation_graph(&graph, &clips);
assert_eq!(
threaded_graph.animation_target_to_threaded_subgraph.len(),
2
);
for (target_id, blend_node, clip_node) in target_ids_and_nodes {
let subgraph = &threaded_graph.animation_target_to_threaded_subgraph[&target_id];
assert_eq!(
subgraph.threaded_graph.iter().sorted().collect::<Vec<_>>(),
[graph.root, blend_node, clip_node]
.iter()
.sorted()
.collect::<Vec<_>>()
);
}
}
#[test]
fn subgraph_includes_all_clips_for_a_target() {
let (mut graph, mut clips) = (AnimationGraph::new(), Assets::<AnimationClip>::default());
let blend_node = graph.add_blend(1.0, graph.root);
let target_id = AnimationTargetId::from_name(&Name::new("MyTarget"));
let clip_nodes: [_; 2] = array::from_fn(|_| {
let clip = clips.add(create_animation_clip_for_target(target_id));
graph.add_clip(clip, 1.0, blend_node)
});
let threaded_graph = create_threaded_graph_from_animation_graph(&graph, &clips);
assert_eq!(
threaded_graph.animation_target_to_threaded_subgraph.len(),
1
);
let subgraph = &threaded_graph.animation_target_to_threaded_subgraph[&target_id];
assert_eq!(
subgraph.threaded_graph.iter().sorted().collect::<Vec<_>>(),
[graph.root, blend_node, clip_nodes[0], clip_nodes[1]]
.iter()
.sorted()
.collect::<Vec<_>>()
);
}
#[test]
fn subgraphs_are_correct_with_arbitrarily_ordered_nodes() {
let (mut graph, mut clips) = (AnimationGraph::new(), Assets::<AnimationClip>::default());
let blend_nodes: [NodeIndex; 2] = array::from_fn(|_| graph.add_blend(1.0, graph.root));
let target_ids_and_clip_nodes: [(AnimationTargetId, NodeIndex); 2] =
array::from_fn(|index| {
let target_id = AnimationTargetId::from_name(&Name::new(["A", "B"][index]));
let clip = clips.add(create_animation_clip_for_target(target_id));
let clip_node = graph.add_clip(clip, 1.0, blend_nodes[index]);
(target_id, clip_node)
});
let threaded_graph = create_threaded_graph_from_animation_graph(&graph, &clips);
assert_eq!(
threaded_graph.animation_target_to_threaded_subgraph.len(),
2
);
for (blend_node, (target_id, clip_node)) in
blend_nodes.iter().zip(target_ids_and_clip_nodes.iter())
{
let subgraph = &threaded_graph.animation_target_to_threaded_subgraph[target_id];
assert_eq!(
subgraph.threaded_graph.iter().sorted().collect::<Vec<_>>(),
[graph.root, *blend_node, *clip_node]
.iter()
.sorted()
.collect::<Vec<_>>()
);
}
}
fn create_threaded_graph_from_animation_graph(
animation_graph: &AnimationGraph,
animation_clips: &Assets<AnimationClip>,
) -> ThreadedAnimationGraph {
let mut threaded_animation_graph = ThreadedAnimationGraph::default();
threaded_animation_graph.init(animation_graph);
threaded_animation_graph.build_from(&animation_graph.graph, animation_graph.root, 0);
threaded_animation_graph.rebuild_target_subgraphs(&animation_graph.graph, animation_clips);
threaded_animation_graph
}
fn create_animation_clip_for_target(animation_target_id: AnimationTargetId) -> AnimationClip {
let mut animation_clip = AnimationClip::default();
let animatable_curve = AnimatableCurve::new(
animated_field!(Transform::translation),
ConstantCurve::new(Interval::EVERYWHERE, Vec3::ONE),
);
animation_clip.add_curve_to_target(animation_target_id, animatable_curve);
animation_clip
}
}