diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index f2d5da4cd7dbf..58ca8028033bc 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -13,9 +13,11 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_asset = { path = "../bevy_asset", version = "0.12.0" } bevy_core = { path = "../bevy_core", version = "0.12.0" } +bevy_derive = { path = "../bevy_derive", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ "bevy", + "petgraph", ] } bevy_render = { path = "../bevy_render", version = "0.12.0" } bevy_time = { path = "../bevy_time", version = "0.12.0" } @@ -24,5 +26,8 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" } +# other +petgraph = "0.6" + [lints] workspace = true diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs new file mode 100644 index 0000000000000..e84294bc4bfb6 --- /dev/null +++ b/crates/bevy_animation/src/graph.rs @@ -0,0 +1,467 @@ +//! The animation graph. + +#![allow(missing_docs)] + +use std::ops::{Index, IndexMut, Mul}; + +use bevy_asset::{Assets, Handle}; +use bevy_core::Name; +use bevy_derive::Deref; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::Has, + reflect::ReflectComponent, + system::{Query, Res}, +}; +use bevy_hierarchy::{Children, Parent}; +use bevy_reflect::Reflect; +use bevy_render::mesh::morph::MorphWeights; +use bevy_time::Time; +use bevy_transform::components::Transform; +use petgraph::{ + stable_graph::{NodeIndex, StableDiGraph}, + visit::{IntoNodeReferences, NodeRef}, + Direction, +}; + +use crate::{AnimationClip, AnimationPlayer, PlayingAnimation, RepeatAnimation}; + +#[derive(Component, Reflect)] +#[reflect(Component)] +pub struct AnimationGraph { + graph: StableDiGraph, + root: AnimationNodeIndex, +} + +#[derive(Clone, Copy, Deref, Reflect)] +pub struct AnimationWeight(f32); + +#[derive(Clone, Reflect)] +pub struct AnimationNode { + animation: Option, + pub paused: bool, + pub weight: AnimationWeight, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, Reflect)] +pub struct AnimationNodeIndex(NodeIndex); + +struct AnimationContext< + 'gr, + 't, + 'ac, + 'p, + 'ppq, + 'ppa, + 'ppb, + 'ppc, + 'cq, + 'ca, + 'cb, + 'cc, + 'nq, + 'na, + 'nb, + 'nc, + 'tq, + 'ta, + 'tb, + 'tc, + 'mwq, + 'mwa, + 'mwb, + 'mwc, +> { + animation_graph: &'gr mut AnimationGraph, + time: &'t Time, + root_entity: Entity, + animation_clips: &'ac Assets, + parent: Option<&'p Parent>, + parents: &'ppq Query< + 'ppa, + 'ppb, + ( + Has, + Has, + Option<&'ppc Parent>, + ), + >, + children: &'cq Query<'ca, 'cb, &'cc Children>, + + // TODO: These should be abstracted into a list of `Animatable` properties. + names: &'nq Query<'na, 'nb, &'nc Name>, + transforms: &'tq Query<'ta, 'tb, &'tc mut Transform>, + morph_weights: &'mwq Query<'mwa, 'mwb, &'mwc mut MorphWeights>, +} + +impl< + 'gr, + 't, + 'ac, + 'p, + 'ppq, + 'ppa, + 'ppb, + 'ppc, + 'cq, + 'ca, + 'cb, + 'cc, + 'nq, + 'na, + 'nb, + 'nc, + 'tq, + 'ta, + 'tb, + 'tc, + 'mwq, + 'mwa, + 'mwb, + 'mwc, + > + AnimationContext< + 'gr, + 't, + 'ac, + 'p, + 'ppq, + 'ppa, + 'ppb, + 'ppc, + 'cq, + 'ca, + 'cb, + 'cc, + 'nq, + 'na, + 'nb, + 'nc, + 'tq, + 'ta, + 'tb, + 'tc, + 'mwq, + 'mwa, + 'mwb, + 'mwc, + > +{ + fn evaluate(&mut self) { + self.evaluate_node(self.animation_graph.root, AnimationWeight(1.0)) + } + + fn evaluate_node(&mut self, node_index: AnimationNodeIndex, weight: AnimationWeight) { + let kids: Vec<_> = self + .animation_graph + .graph + .neighbors_directed(*node_index, Direction::Outgoing) + .collect(); + for &kid in &kids { + // FIXME: Is `weight * kid_edge.weight()` right? + self.evaluate_node(kid.into(), weight * self.animation_graph.graph[kid].weight); + } + + let node = &mut self.animation_graph.graph[*node_index]; + if let Some(ref mut playing_animation) = node.animation { + crate::apply_animation( + *weight, + playing_animation, + node.paused, + self.root_entity, + self.time, + self.animation_clips, + self.names, + self.transforms, + self.morph_weights, + self.parent, + self.parents, + self.children, + ) + } + } +} + +impl From> for AnimationNodeIndex { + fn from(value: NodeIndex) -> Self { + Self(value) + } +} + +impl Into> for AnimationNodeIndex { + fn into(self) -> NodeIndex { + *self + } +} + +impl From for AnimationWeight { + fn from(value: f32) -> Self { + Self(value) + } +} + +impl Mul for AnimationWeight { + type Output = AnimationWeight; + + fn mul(self, rhs: AnimationWeight) -> Self::Output { + Self(self.0 * rhs.0) + } +} + +/// A system that updates all playing animations in animation graphs. +pub fn evaluate_animation_graphs( + time: Res