diff --git a/assets/animation_graphs/locomotion.animgraph.ron b/assets/animation_graphs/locomotion.animgraph.ron index 067fd1e..973bd0e 100644 --- a/assets/animation_graphs/locomotion.animgraph.ron +++ b/assets/animation_graphs/locomotion.animgraph.ron @@ -1,7 +1,9 @@ ( nodes: [ (name: "Walk Clip", node: Clip("animations/walk.anim.ron", Some(1.))), + (name: "Walk Clip 2", node: Clip("animations/walk.anim.ron", Some(1.))), (name: "Run Clip", node: Clip("animations/run.anim.ron", Some(1.))), + (name: "Run Clip 2", node: Clip("animations/run.anim.ron", Some(1.))), (name: "Walk Flip LR", node: FlipLR), (name: "Run Flip LR", node: FlipLR), (name: "Walk Chain", node: Chain), @@ -15,30 +17,25 @@ input_parameters: { "Target Speed": F32(1.5), }, - output_time_dependent_spec: { - "Pose": PoseFrame, - }, - input_edges: [ - // Alpha parameters + output_pose_spec: true, + input_parameter_edges: [ ("Target Speed", ("Param graph", "Target Speed")), ], - edges: [ + output_pose_edge: Some("Speed"), + parameter_edges: [ (("Param graph", "blend_fac"),("Blend", "Factor")), (("Param graph", "speed_fac"),("Speed", "Speed")), - - (("Walk Clip", "Pose Out"), ("Walk Flip LR", "Pose In")), - (("Walk Clip", "Pose Out"), ("Walk Chain", "Pose In 1")), - (("Walk Flip LR", "Pose Out"), ("Walk Chain", "Pose In 2")), - (("Run Clip", "Pose Out"), ("Run Chain", "Pose In 1")), - (("Run Clip", "Pose Out"), ("Run Flip LR", "Pose In")), - (("Run Flip LR", "Pose Out"), ("Run Chain", "Pose In 2")), - (("Walk Chain", "Pose Out"), ("Blend", "Pose In 1")), - (("Run Chain", "Pose Out"), ("Blend", "Pose In 2")), - (("Blend", "Pose Out"), ("Loop", "Pose In")), - (("Loop", "Pose Out"), ("Speed", "Pose In")), ], - output_edges: [ - (("Speed", "Pose Out"), "Pose"), + pose_edges: [ + ("Walk Clip", ("Walk Chain", "Pose In 1")), + ("Walk Clip 2", ("Walk Flip LR", "Pose In")), + ("Walk Flip LR", ("Walk Chain", "Pose In 2")), + ("Run Clip", ("Run Chain", "Pose In 1")), + ("Run Clip 2", ("Run Flip LR", "Pose In")), + ("Run Flip LR", ("Run Chain", "Pose In 2")), + ("Walk Chain", ("Blend", "Pose In 1")), + ("Run Chain", ("Blend", "Pose In 2")), + ("Blend", ("Loop", "Pose In")), + ("Loop", ("Speed", "Pose In")), ], - default_output: Some("Pose"), ) diff --git a/assets/animation_graphs/velocity_to_params.animgraph.ron b/assets/animation_graphs/velocity_to_params.animgraph.ron index d4926f7..6fa059c 100644 --- a/assets/animation_graphs/velocity_to_params.animgraph.ron +++ b/assets/animation_graphs/velocity_to_params.animgraph.ron @@ -19,7 +19,6 @@ "Blend End": F32(3.0), // Constant values - // TODO: Maybe there should be a better way to handle constant/defaults? "ZERO": F32(0.), "ONE": F32(1.), }, @@ -27,7 +26,7 @@ "speed_fac": F32, "blend_fac": F32, }, - input_edges: [ + input_parameter_edges: [ // Alpha clamp range ("ZERO", ("Alpha", "Min")), ("ONE", ("Alpha", "Max")), @@ -44,7 +43,7 @@ ("Run Base Speed", ("Factored run speed", "F32 In 1")), ("Target Speed", ("Speed factor", "F32 In 1")), ], - edges: [ + parameter_edges: [ // Blend alpha computation // ((target_speed - blend_start) / (blend_end - blend_start)).clamp(0., 1.); (("Alpha Tmp 1", "F32 Out"), ("Alpha Tmp 3", "F32 In 1")), @@ -60,7 +59,7 @@ (("Factored run speed", "F32 Out"), ("Blended base speed", "F32 In 2")), (("Blended base speed", "F32 Out"),("Speed factor", "F32 In 2")), ], - output_edges: [ + output_parameter_edges: [ (("Alpha", "F32 Out"), "blend_fac"), (("Speed factor", "F32 Out"), "speed_fac"), ], diff --git a/src/bin/show_graph.rs b/src/bin/show_graph.rs index f25064a..cb1f237 100644 --- a/src/bin/show_graph.rs +++ b/src/bin/show_graph.rs @@ -57,14 +57,12 @@ fn show_graph( .get(target_graph.handle.as_ref().unwrap()) .unwrap(); - let mut context_tmp = GraphContextTmp { + let context_tmp = GraphContextTmp { graph_clip_assets: &graph_clip_assets, animation_graph_assets: &animation_graph_assets, }; - graph - .dot_to_tmp_file_and_open(None, &mut context_tmp) - .unwrap(); + graph.dot_to_tmp_file_and_open(None, context_tmp).unwrap(); exit.send(AppExit); } diff --git a/src/core/animation_graph/core.rs b/src/core/animation_graph/core.rs index 492537c..3e57afe 100644 --- a/src/core/animation_graph/core.rs +++ b/src/core/animation_graph/core.rs @@ -1,66 +1,104 @@ +use std::error::Error; + use crate::{ core::{ - animation_node::{AnimationNode, GraphInputNode, GraphOutputNode, NodeLike}, - caches::{DurationCache, ParameterCache, TimeCache, TimeDependentCache}, + animation_node::{AnimationNode, NodeLike}, frame::PoseFrame, graph_context::{GraphContext, GraphContextTmp}, pose::Pose, }, + prelude::{DurationData, PassContext, SpecContext}, sampling::linear::SampleLinear, - utils::hash_map_join::HashMapJoinExt, }; -use bevy::{prelude::*, reflect::TypeUuid, utils::HashMap}; +use bevy::{ + prelude::*, + reflect::TypeUuid, + utils::{HashMap, HashSet}, +}; use serde::{Deserialize, Serialize}; #[derive(Reflect, Clone, Copy, Debug, Serialize, Deserialize)] -pub enum EdgeSpec { - PoseFrame, +pub struct OptParamSpec { + pub spec: ParamSpec, + pub optional: bool, +} + +impl OptParamSpec { + pub fn with_optional(mut self, optional: bool) -> Self { + self.optional = optional; + self + } +} + +impl From for OptParamSpec { + fn from(value: ParamSpec) -> Self { + Self { + spec: value, + optional: false, + } + } +} + +#[derive(Reflect, Clone, Copy, Debug, Serialize, Deserialize)] +pub enum ParamSpec { F32, } -pub type Edge = ((String, String), (String, String)); -pub type EdgePath = Vec; +pub type NodeId = String; +pub type PinId = String; + +#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash)] +pub enum TargetPin { + NodeParameter(NodeId, PinId), + OutputParameter(PinId), + NodePose(NodeId, PinId), + OutputPose, +} + +#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash)] +pub enum SourcePin { + NodeParameter(NodeId, PinId), + InputParameter(PinId), + NodePose(NodeId), + InputPose(PinId), +} + +#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash)] +pub struct Edge { + pub source: SourcePin, + pub target: TargetPin, +} #[derive(Reflect, Clone, Debug, Serialize, Deserialize)] -pub enum EdgeValue { - PoseFrame(#[serde(skip)] PoseFrame), +pub enum ParamValue { F32(f32), } -impl EdgeValue { - pub fn unwrap_pose_frame(self) -> PoseFrame { - match self { - Self::PoseFrame(p) => p, - _ => panic!("Edge value is not a pose frame"), - } - } - +impl ParamValue { pub fn unwrap_f32(self) -> f32 { match self { Self::F32(f) => f, - _ => panic!("Edge value is not a f32"), } } } -impl From for EdgeValue { +impl From for ParamValue { fn from(value: f32) -> Self { Self::F32(value) } } -impl From<&EdgeValue> for EdgeSpec { - fn from(value: &EdgeValue) -> Self { +impl From<&ParamValue> for OptParamSpec { + fn from(value: &ParamValue) -> Self { match value { - EdgeValue::PoseFrame(_) => Self::PoseFrame, - EdgeValue::F32(_) => Self::F32, + ParamValue::F32(_) => Self { + spec: ParamSpec::F32, + optional: false, + }, } } } -pub type NodeInput = String; -pub type NodeOutput = String; - #[derive(Reflect, Clone, Debug, Copy)] pub enum TimeUpdate { Delta(f32), @@ -120,13 +158,45 @@ impl UpdateTime> for TimeState { } } +#[derive(Debug, Clone, Reflect, Default)] +pub struct InputOverlay { + pub parameters: HashMap, + pub durations: HashMap, + pub time_dependent: HashMap, +} + +impl InputOverlay { + pub fn clear(&mut self) { + self.parameters.clear(); + self.durations.clear(); + self.time_dependent.clear(); + } +} + +#[derive(Debug, Clone, Reflect, Default)] +pub struct GraphError(String); + +impl std::fmt::Display for GraphError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Inconsistent graph: {}", self.0) + } +} + +impl Error for GraphError {} + #[derive(Debug, Clone, Asset, TypeUuid, Reflect)] #[uuid = "92411396-01ae-4528-9839-709a7a321263"] pub struct AnimationGraph { pub nodes: HashMap, /// Inverted, indexed by output node name. - pub node_edges: HashMap<(String, String), (String, String)>, + pub node_edges: HashMap, pub default_output: Option, + + pub default_parameters: HashMap, + pub input_parameters: HashMap, + pub input_poses: HashSet, + pub output_parameters: HashMap, + pub output_pose: bool, } impl Default for AnimationGraph { @@ -135,620 +205,580 @@ impl Default for AnimationGraph { } } -type SpecExtractor = - fn(&AnimationNode, &mut GraphContext, &mut GraphContextTmp) -> HashMap; -type PrepareInput = fn(&Out, &str) -> In; -type Mapper = - fn(&AnimationNode, In, &EdgePath, &mut GraphContext, &mut GraphContextTmp) -> Out; -type ShortCircuit = - fn(&AnimationNode, &EdgePath, &mut GraphContext, &mut GraphContextTmp) -> Option; - -struct UpFns { - pub prepare: PrepareInput, - pub mapper: Mapper, Out>, -} - -struct DownFns { - pub prepare: PrepareInput, - pub mapper: Mapper, -} - -impl Clone for UpFns { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for UpFns {} - -impl Clone for DownFns { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for DownFns {} - impl AnimationGraph { - pub const INPUT_NODE: &'static str = "__INPUT"; - pub const OUTPUT_NODE: &'static str = "__OUTPUT"; - pub fn new() -> Self { Self { - nodes: HashMap::from([ - ( - Self::INPUT_NODE.into(), - GraphInputNode::default().wrapped(Self::INPUT_NODE), - ), - ( - Self::OUTPUT_NODE.into(), - GraphOutputNode::default().wrapped(Self::OUTPUT_NODE), - ), - ]), + nodes: HashMap::new(), node_edges: HashMap::new(), + default_output: None, + default_parameters: HashMap::new(), + input_parameters: HashMap::new(), + input_poses: HashSet::new(), + output_parameters: HashMap::new(), + output_pose: false, } } + // --- Core graph interface: add nodes and edges + // ---------------------------------------------------------------------------------------- + /// Add a new node to the graph pub fn add_node(&mut self, node: AnimationNode) { - let node_name = node.name.clone(); - if node_name == Self::INPUT_NODE || node_name == Self::OUTPUT_NODE { - error!("Node name {node_name} is reserved"); - panic!("Node name {node_name} is reserved") - } - self.nodes.insert(node_name.clone(), node); + self.nodes.insert(node.name.clone(), node); } - pub fn set_default_output(&mut self, name: impl Into) { - self.default_output = Some(name.into()); + /// Add a new edge to the graph + pub fn add_edge(&mut self, source_pin: SourcePin, target_pin: TargetPin) { + self.node_edges.insert(target_pin, source_pin); } + // ---------------------------------------------------------------------------------------- - pub fn set_input_parameter(&mut self, parameter_name: impl Into, value: EdgeValue) { - self.nodes - .get_mut(Self::INPUT_NODE) - .unwrap() - .node - .unwrap_input_mut() - .parameters - .insert(parameter_name.into(), value); + // --- Setting graph inputs and outputs + // ---------------------------------------------------------------------------------------- + /// Sets the value for a default parameter, registering it if it wasn't yet done + pub fn set_default_parameter(&mut self, parameter_name: impl Into, value: ParamValue) { + let parameter_name = parameter_name.into(); + let mut spec = OptParamSpec::from(&value); + spec.optional = true; + self.input_parameters.insert(parameter_name.clone(), spec); + self.default_parameters + .insert(parameter_name.clone(), value); } - pub fn get_input_parameter(&mut self, parameter_name: &str) -> Option { - self.nodes - .get_mut(Self::INPUT_NODE) - .unwrap() - .node - .unwrap_input() - .parameters - .get(parameter_name) - .cloned() + /// Get the default value of an input parameter, if it exists + pub fn get_default_parameter(&mut self, parameter_name: &str) -> Option { + self.default_parameters.get(parameter_name).cloned() } - pub fn register_input_td(&mut self, input_name: impl Into, spec: EdgeSpec) { - self.nodes - .get_mut(Self::INPUT_NODE) - .unwrap() - .node - .unwrap_input_mut() - .time_dependent_spec - .insert(input_name.into(), spec); + /// Register an input pose pin for the graph + pub fn add_input_pose(&mut self, pin_id: impl Into) { + self.input_poses.insert(pin_id.into()); } - pub fn register_output_parameter(&mut self, input_name: impl Into, spec: EdgeSpec) { - self.nodes - .get_mut(Self::OUTPUT_NODE) - .unwrap() - .node - .unwrap_output_mut() - .parameters - .insert(input_name.into(), spec); + /// Register an output parameter for the graph + pub fn add_output_parameter(&mut self, pin_id: impl Into, spec: ParamSpec) { + self.output_parameters.insert(pin_id.into(), spec); } - pub fn register_output_td(&mut self, input_name: impl Into, spec: EdgeSpec) { - self.nodes - .get_mut(Self::OUTPUT_NODE) - .unwrap() - .node - .unwrap_output_mut() - .time_dependent - .insert(input_name.into(), spec); + /// Enables pose output for this graph + pub fn add_output_pose(&mut self) { + self.output_pose = true; } + // ---------------------------------------------------------------------------------------- - pub fn add_input_edge( + // --- Helper functions for adding edges + // ---------------------------------------------------------------------------------------- + pub fn add_input_parameter_edge( &mut self, - parameter_name: impl Into, - target_node: impl Into, - target_edge: impl Into, + parameter_name: impl Into, + target_node: impl Into, + target_edge: impl Into, ) { - self.add_edge(Self::INPUT_NODE, parameter_name, target_node, target_edge) + self.add_edge( + SourcePin::InputParameter(parameter_name.into()), + TargetPin::NodeParameter(target_node.into(), target_edge.into()), + ) } - pub fn add_output_edge( + pub fn add_output_parameter_edge( &mut self, - source_node: impl Into, - source_edge: impl Into, - output_name: impl Into, + source_node: impl Into, + source_edge: impl Into, + output_name: impl Into, ) { - self.add_edge(source_node, source_edge, Self::OUTPUT_NODE, output_name) + self.add_edge( + SourcePin::NodeParameter(source_node.into(), source_edge.into()), + TargetPin::OutputParameter(output_name.into()), + ) + } + + pub fn add_input_pose_edge( + &mut self, + input_name: impl Into, + target_node: impl Into, + target_edge: impl Into, + ) { + self.add_edge( + SourcePin::InputPose(input_name.into()), + TargetPin::NodePose(target_node.into(), target_edge.into()), + ) } - pub fn add_edge( + pub fn add_output_pose_edge(&mut self, source_node: impl Into) { + self.add_edge( + SourcePin::NodePose(source_node.into()), + TargetPin::OutputPose, + ) + } + + /// Adds an edge between two nodes in the graph + pub fn add_node_parameter_edge( &mut self, - source_node: impl Into, - source_edge: impl Into, - target_node: impl Into, - target_edge: impl Into, + source_node: impl Into, + source_pin: impl Into, + target_node: impl Into, + target_pin: impl Into, ) { - self.node_edges.insert( - (target_node.into(), target_edge.into()), - (source_node.into(), source_edge.into()), + self.add_edge( + SourcePin::NodeParameter(source_node.into(), source_pin.into()), + TargetPin::NodeParameter(target_node.into(), target_pin.into()), ); } - #[allow(clippy::too_many_arguments)] - fn map( - &self, - node_name: &str, - path_to_node: EdgePath, - input_spec_extractor: SpecExtractor, - recurse_spec_extractor: SpecExtractor, - short_circuiter: ShortCircuit, - up: Option>, - down: Option>, - down_input: Option, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, - ) -> OutputUp { - let node = overlay - .get(node_name) - .or_else(|| self.nodes.get(node_name)) - .unwrap(); - - if let Some(out) = short_circuiter(node, &path_to_node, context, context_tmp) { - return out; + /// Adds an edge between two node pose pins in the graph + pub fn add_node_pose_edge( + &mut self, + source_node: impl Into, + target_node: impl Into, + target_pin: impl Into, + ) { + self.add_edge( + SourcePin::NodePose(source_node.into()), + TargetPin::NodePose(target_node.into(), target_pin.into()), + ); + } + // ---------------------------------------------------------------------------------------- + + // --- Verification + // ---------------------------------------------------------------------------------------- + pub fn validate(&self) -> Result<(), GraphError> { + enum SourceType { + Parameter, + Pose, } - let in_spec = - input_spec_extractor(self.nodes.get(node_name).unwrap(), context, context_tmp); - let recurse_spec = - recurse_spec_extractor(self.nodes.get(node_name).unwrap(), context, context_tmp); - - let output_down = down.map(|down| { - (down.mapper)( - node, - down_input.expect("Have down fns but missing down input"), - &path_to_node, - context, - context_tmp, - ) - }); - - let mut input_up = up.map(|_| HashMap::new()); - - for k in recurse_spec.keys() { - let Some((in_node_name, in_edge_name)) = - self.node_edges.get(&(node_name.into(), k.into())) - else { - continue; + let mut counters = HashMap::::new(); + + for (_, source_pin) in self.node_edges.iter() { + let source_type = match source_pin { + SourcePin::NodeParameter(_, _) => SourceType::Parameter, + SourcePin::InputParameter(_) => SourceType::Parameter, + SourcePin::NodePose(_) => SourceType::Pose, + SourcePin::InputPose(_) => SourceType::Pose, }; - // Extend path to input node - let mut new_path = path_to_node.clone(); - new_path.push(( - (in_node_name.clone(), in_edge_name.clone()), - (node_name.to_string(), k.clone()), - )); - - let new_down_input = down.map(|down| (down.prepare)(output_down.as_ref().unwrap(), k)); - - let output_up = self.map( - in_node_name, - new_path, - input_spec_extractor, - recurse_spec_extractor, - short_circuiter, - up, - down, - new_down_input, - context, - context_tmp, - overlay, - ); - - if let Some(up) = up { - if in_spec.contains_key(k) { - let val = (up.prepare)(&output_up, in_edge_name); - input_up.as_mut().unwrap().insert(k.clone(), val); - } + if counters.contains_key(source_pin) { + let ex = counters.get_mut(source_pin).unwrap(); + match (ex, source_type) { + (SourceType::Parameter, SourceType::Pose) => { + return Err(GraphError( + "Inconsistent edge types connected to the same pin".into(), + )) + } + (SourceType::Pose, SourceType::Parameter) => { + return Err(GraphError( + "Inconsistent edge types connected to the same pin".into(), + )) + } + (SourceType::Pose, SourceType::Pose) => { + return Err(GraphError( + "Only one target can be connected to each pose output".into(), + )) + } + _ => (), + }; + } else { + counters.insert(source_pin.clone(), source_type); } } - - if let Some(up) = up { - (up.mapper)(node, input_up.unwrap(), &path_to_node, context, context_tmp) - } else { - OutputUp::default() - } + Ok(()) } + // ---------------------------------------------------------------------------------------- - #[allow(clippy::too_many_arguments)] - fn map_up( + // --- Computations + // ---------------------------------------------------------------------------------------- + fn parameter_map( &self, - node_name: &str, - path_to_node: EdgePath, - input_spec_extractor: SpecExtractor, - recurse_spec_extractor: SpecExtractor, - short_circuiter: ShortCircuit, - up: UpFns, + target_pin: TargetPin, + spec: OptParamSpec, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, - ) -> OutputUp { - self.map::( - node_name, - path_to_node, - input_spec_extractor, - recurse_spec_extractor, - short_circuiter, - Some(up), - None, - None, - context, - context_tmp, - overlay, - ) + context_tmp: GraphContextTmp, + overlay: &InputOverlay, + ) -> Option { + let source_pin = self.node_edges.get(&target_pin); + if spec.optional && source_pin.is_none() { + return None; + } + let source_pin = source_pin.unwrap(); + + if let Some(val) = context.get_cached_parameter(source_pin) { + return Some(val.clone()); + } + + let source_value = match source_pin { + SourcePin::NodeParameter(node_id, pin_id) => { + self.nodes[node_id] + .pose_input_spec(SpecContext::new(context, context_tmp)) + .iter() + .for_each(|pin_id| { + self.parameter_propagate( + TargetPin::NodePose(node_id.clone(), pin_id.clone()), + context, + context_tmp, + overlay, + ); + }); + let inputs = self.nodes[node_id] + .parameter_input_spec(SpecContext::new(context, context_tmp)) + .iter() + .filter_map(|(pin_id, spec)| { + self.parameter_map( + TargetPin::NodeParameter(node_id.clone(), pin_id.clone()), + *spec, + context, + context_tmp, + overlay, + ) + .map(|v| (pin_id.clone(), v)) + }) + .collect(); + + let outputs = self.nodes[node_id].parameter_pass( + inputs, + PassContext::new(node_id, context, context_tmp, &self.node_edges), + ); + + for (pin_id, value) in outputs.iter() { + context.insert_cached_parameter( + SourcePin::NodeParameter(node_id.clone(), pin_id.clone()), + value.clone(), + ); + } + + outputs[pin_id].clone() + } + SourcePin::InputParameter(pin_id) => { + if let Some(v) = overlay.parameters.get(pin_id) { + v.clone() + } else if let Some(v) = self.default_parameters.get(pin_id) { + v.clone() + } else { + panic!("Value of parameter {source_pin:?} not available") + } + } + SourcePin::NodePose(_) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + SourcePin::InputPose(_) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + }; + + Some(source_value) } - #[allow(clippy::too_many_arguments)] - fn map_down( + fn parameter_propagate( &self, - node_name: &str, - path_to_node: EdgePath, - input_spec_extractor: SpecExtractor, - recurse_spec_extractor: SpecExtractor, - short_circuiter: ShortCircuit<()>, - down: DownFns, - down_input: InputDown, + target_pin: TargetPin, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, + context_tmp: GraphContextTmp, + overlay: &InputOverlay, ) { - self.map::( - node_name, - path_to_node, - input_spec_extractor, - recurse_spec_extractor, - short_circuiter, - None, - Some(down), - Some(down_input), - context, - context_tmp, - overlay, - ) - } + let source_pin = self.node_edges.get(&target_pin).unwrap(); - fn prepare_input_index_hashmap(outputs: &HashMap, edge: &str) -> T { - outputs - .get(edge) - .unwrap_or_else(|| panic!("Edge output {} not found!", edge)) - .clone() + match source_pin { + SourcePin::NodeParameter(_, _) => { + panic!("Try using parameter_map instead: {source_pin:?} --> {target_pin:?}") + } + SourcePin::InputParameter(_) => { + panic!("Try using parameter_map instead: {source_pin:?} --> {target_pin:?}") + } + SourcePin::NodePose(node_id) => { + self.nodes[node_id] + .pose_input_spec(SpecContext::new(context, context_tmp)) + .iter() + .for_each(|pin_id| { + self.parameter_propagate( + TargetPin::NodePose(node_id.clone(), pin_id.clone()), + context, + context_tmp, + overlay, + ); + }); + self.nodes[node_id] + .parameter_input_spec(SpecContext::new(context, context_tmp)) + .iter() + .for_each(|(pin_id, spec)| { + self.parameter_map( + TargetPin::NodeParameter(node_id.clone(), pin_id.clone()), + *spec, + context, + context_tmp, + overlay, + ); + }); + } + SourcePin::InputPose(_) => {} + }; } - /// Which inputs are needed to calculate parameter output of this node - fn parameter_input_spec_extractor( - n: &AnimationNode, + fn duration_map( + &self, + target_pin: TargetPin, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - n.parameter_input_spec(context, context_tmp) - } + context_tmp: GraphContextTmp, + overlay: &InputOverlay, + ) -> Option { + let source_pin = self + .node_edges + .get(&target_pin) + .unwrap_or_else(|| panic!("Target pin {target_pin:?} is disconnected")); + + if let Some(val) = context.get_cached_duration(source_pin) { + return val; + } - /// Which inputs should parameter recalculation be triggered for (superset of input spec) - fn parameter_recurse_spec_extractor( - n: &AnimationNode, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let mut spec = n.parameter_input_spec(context, context_tmp); - spec.fill_up(&n.time_dependent_input_spec(context, context_tmp), &|v| *v); - spec - } - - /// Computes node output and caches the result for later passes - fn parameter_mapper( - n: &AnimationNode, - inputs: HashMap, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let outputs = n.parameter_pass(inputs.clone(), &n.name, path, context, context_tmp); + let source_value = match source_pin { + SourcePin::NodeParameter(_, _) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + SourcePin::InputParameter(_) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + SourcePin::NodePose(node_id) => { + let inputs = self.nodes[node_id] + .pose_input_spec(SpecContext::new(context, context_tmp)) + .iter() + .map(|pin_id| { + ( + pin_id.clone(), + self.duration_map( + TargetPin::NodePose(node_id.clone(), pin_id.clone()), + context, + context_tmp, + overlay, + ), + ) + }) + .collect(); + + let output = self.nodes[node_id].duration_pass( + inputs, + PassContext::new(node_id, context, context_tmp, &self.node_edges), + ); + + if let Some(value) = output { + context.insert_cached_duration(SourcePin::NodePose(node_id.clone()), value); + } - context - .get_node_cache_or_insert_default(&n.name) - .parameter_cache = Some(ParameterCache { - upstream: inputs, - downstream: outputs.clone(), - }); + output.unwrap() + } + SourcePin::InputPose(pin_id) => { + if let Some(v) = overlay.durations.get(pin_id) { + *v + } else { + panic!("Value of parameter {source_pin:?} not available") + } + } + }; - outputs + source_value } - fn short_circuit_parameter( - n: &AnimationNode, - _path: &EdgePath, + fn time_map( + &self, + target_pin: TargetPin, + time_update: TimeUpdate, context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> Option> { - context - .get_parameters(&n.name) - .map(|c| c.downstream.clone()) - } + context_tmp: GraphContextTmp, + _overlay: &InputOverlay, + ) { + let source_pin = self.node_edges.get(&target_pin).unwrap(); - fn short_circuit_durations( - n: &AnimationNode, - _path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> Option>> { - context.get_durations(&n.name).map(|c| c.downstream.clone()) - } + if context.get_cached_time(source_pin).is_some() { + return; + } - fn short_circuit_times( - n: &AnimationNode, - path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> Option<()> { - context.get_times(&n.name, path).map(|_| ()) - } + let old_time_state = context + .old_cached_time(source_pin) + .cloned() + .unwrap_or_default(); + let time_state = old_time_state.update(time_update); - fn short_circuit_td( - n: &AnimationNode, - path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> Option> { - context - .get_time_dependent(&n.name, path) - .map(|c| c.downstream.clone()) + // Cache the new value + context.insert_cached_time(source_pin.clone(), time_state); + + match source_pin { + SourcePin::NodeParameter(_, _) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + SourcePin::InputParameter(_) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + SourcePin::NodePose(node_id) => { + // Compute time pass + let back_target_pins = self.nodes[node_id].time_pass( + time_state, + PassContext::new(node_id, context, context_tmp, &self.node_edges), + ); + + // Propagate the time update to the back edges + for (pin_id, time_update) in back_target_pins { + self.time_map( + TargetPin::NodePose(node_id.clone(), pin_id), + time_update, + context, + context_tmp, + _overlay, + ); + } + } + SourcePin::InputPose(_) => { + // Do nothing, the value has already been cached + } + }; } - pub fn parameter_pass( + fn pose_map( &self, - node: &str, - path: EdgePath, + target_pin: TargetPin, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, - ) { - self.map_up( - node, - path, - Self::parameter_input_spec_extractor, - Self::parameter_recurse_spec_extractor, - Self::short_circuit_parameter, - UpFns { - prepare: Self::prepare_input_index_hashmap, - mapper: Self::parameter_mapper, - }, - context, - context_tmp, - overlay, - ); - } + context_tmp: GraphContextTmp, + overlay: &InputOverlay, + ) -> PoseFrame { + let source_pin = self.node_edges.get(&target_pin).unwrap(); - /// Which inputs are needed to calculate parameter output of this node - fn duration_input_spec_extractor( - n: &AnimationNode, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - n.node - .map(|n| n.time_dependent_input_spec(context, context_tmp)) - } + if let Some(val) = context.get_cached_pose(source_pin) { + return val.clone(); + } - /// Computes node output and caches the result for later passes - fn duration_mapper( - n: &AnimationNode, - inputs: HashMap>, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - let output = n.duration_pass(inputs.clone(), &n.name, path, context, context_tmp); - context - .get_node_cache_or_insert_default(&n.name) - .duration_cache = Some(DurationCache { - upstream: inputs, - downstream: output.clone(), - }); + let source_value = match source_pin { + SourcePin::NodeParameter(_, _) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + SourcePin::InputParameter(_) => { + panic!("Incompatible pins connected: {source_pin:?} --> {target_pin:?}") + } + SourcePin::NodePose(node_id) => { + let inputs = self.nodes[node_id] + .pose_input_spec(SpecContext::new(context, context_tmp)) + .iter() + .map(|pin_id| { + ( + pin_id.clone(), + self.pose_map( + TargetPin::NodePose(node_id.clone(), pin_id.clone()), + context, + context_tmp, + overlay, + ), + ) + }) + .collect(); + + let output = self.nodes[node_id].time_dependent_pass( + inputs, + PassContext::new(node_id, context, context_tmp, &self.node_edges), + ); + + if let Some(value) = &output { + context.insert_cached_pose(SourcePin::NodePose(node_id.clone()), value.clone()); + } - output + output.unwrap().clone() + } + SourcePin::InputPose(pin_id) => { + if let Some(v) = overlay.time_dependent.get(pin_id) { + v.clone() + } else { + panic!("Value of parameter {source_pin:?} not available") + } + } + }; + + source_value } - pub fn duration_pass( + pub fn parameter_pass( &self, - node: &str, - path: EdgePath, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, - ) { - self.map_up( - node, - path, - Self::duration_input_spec_extractor, - Self::duration_input_spec_extractor, - Self::short_circuit_durations, - UpFns { - prepare: Self::prepare_input_index_hashmap, - mapper: Self::duration_mapper, - }, - context, - context_tmp, - overlay, - ); - } + context_tmp: GraphContextTmp, + overlay: &InputOverlay, + ) -> HashMap { + if self.output_pose { + self.parameter_propagate(TargetPin::OutputPose, context, context_tmp, overlay); + } - fn time_spec_extractor( - n: &AnimationNode, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - n.time_dependent_input_spec(context, context_tmp) + self.output_parameters + .iter() + .map(|(pin_id, spec)| { + let target_pin = TargetPin::OutputParameter(pin_id.clone()); + let value = self + .parameter_map(target_pin, (*spec).into(), context, context_tmp, overlay) + .unwrap(); + + (pin_id.clone(), value) + }) + .collect() } - fn time_mapper( - n: &AnimationNode, - input: TimeUpdate, - path: &EdgePath, + pub fn duration_pass( + &self, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let input_state = { - let last_time_state = context - .get_other_times(&n.name, path) - .map_or(TimeState::default(), |c| c.downstream); - last_time_state.update(input) - }; - let output = n.time_pass(input_state, &n.name, path, context, context_tmp); - context - .get_node_cache_or_insert_default(&n.name) - .time_caches - .insert( - path.clone(), - TimeCache { - downstream: input_state, - upstream: output.clone(), - }, - ); + context_tmp: GraphContextTmp, + overlay: &InputOverlay, + ) -> Option { + let target_pin = TargetPin::OutputPose; - output + Some(self.duration_map(target_pin, context, context_tmp, overlay)) } pub fn time_pass( &self, - node: &str, - path: EdgePath, time_update: TimeUpdate, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, - ) { - self.map_down( - node, - path, - Self::time_spec_extractor, - Self::time_spec_extractor, - Self::short_circuit_times, - DownFns { - prepare: Self::prepare_input_index_hashmap, - mapper: Self::time_mapper, - }, - time_update, - context, - context_tmp, - overlay, - ); - } + context_tmp: GraphContextTmp, + overlay: &InputOverlay, + ) -> HashMap { + let target_pin = TargetPin::OutputPose; - /// Which inputs are needed to calculate time-dependent output of this node - fn time_dependent_input_spec_extractor( - n: &AnimationNode, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - n.time_dependent_input_spec(context, context_tmp) - } + self.time_map(target_pin, time_update, context, context_tmp, overlay); - /// Computes node output and caches the result for later passes - fn time_dependent_mapper( - n: &AnimationNode, - inputs: HashMap, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let outputs = n.time_dependent_pass(inputs.clone(), &n.name, path, context, context_tmp); - - context - .get_node_cache_or_insert_default(&n.name) - .time_dependent_caches - .insert( - path.clone(), - TimeDependentCache { - upstream: inputs, - downstream: outputs.clone(), - }, - ); + self.input_poses + .iter() + .map(|pin_id| { + let source_pin = SourcePin::InputPose(pin_id.clone()); + let state = context.get_cached_time(&source_pin).unwrap(); - outputs + (pin_id.clone(), state.update) + }) + .collect() } pub fn time_dependent_pass( &self, - node: &str, - path: EdgePath, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, - ) -> HashMap { - self.map_up( - node, - path, - Self::time_dependent_input_spec_extractor, - Self::time_dependent_input_spec_extractor, - Self::short_circuit_td, - UpFns { - prepare: Self::prepare_input_index_hashmap, - mapper: Self::time_dependent_mapper, - }, - context, - context_tmp, - overlay, - ) + context_tmp: GraphContextTmp, + overlay: &InputOverlay, + ) -> Option { + let target_pin = TargetPin::OutputPose; + + Some(self.pose_map(target_pin, context, context_tmp, overlay)) } pub fn query( &self, time_update: TimeUpdate, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, + context_tmp: GraphContextTmp, ) -> Pose { - self.query_with_overlay(time_update, context, context_tmp, &HashMap::new()) + self.query_with_overlay(time_update, context, context_tmp, &InputOverlay::default()) } pub fn query_with_overlay( &self, time_update: TimeUpdate, context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - overlay: &HashMap, + context_tmp: GraphContextTmp, + overlay: &InputOverlay, ) -> Pose { context.push_caches(); - self.parameter_pass(Self::OUTPUT_NODE, vec![], context, context_tmp, overlay); - self.duration_pass(Self::OUTPUT_NODE, vec![], context, context_tmp, overlay); - self.time_pass( - Self::OUTPUT_NODE, - vec![], - time_update, - context, - context_tmp, - overlay, - ); - self.time_dependent_pass(Self::OUTPUT_NODE, vec![], context, context_tmp, overlay); - - let output = context - .get_time_dependent(Self::OUTPUT_NODE, &vec![]) - .unwrap() - .upstream - .get(self.default_output.as_ref().unwrap()) - .unwrap() - .clone() - .unwrap_pose_frame(); + self.parameter_pass(context, context_tmp, overlay); + self.duration_pass(context, context_tmp, overlay); + self.time_pass(time_update, context, context_tmp, overlay); + let final_output = self.time_dependent_pass(context, context_tmp, overlay); - output.sample_linear() + final_output.unwrap().sample_linear() } + // ---------------------------------------------------------------------------------------- } diff --git a/src/core/animation_graph/dot_output.rs b/src/core/animation_graph/dot_output.rs index bcc04a9..8cc6f2d 100644 --- a/src/core/animation_graph/dot_output.rs +++ b/src/core/animation_graph/dot_output.rs @@ -1,12 +1,12 @@ -use super::{AnimationGraph, EdgeSpec, EdgeValue, TimeState, TimeUpdate}; +use super::{AnimationGraph, OptParamSpec, ParamSpec, ParamValue, TimeState, TimeUpdate}; use crate::{ core::{ - animation_node::{AnimationNode, NodeLike}, + animation_node::NodeLike, frame::{BoneFrame, PoseFrame, ValueFrame}, graph_context::{GraphContext, GraphContextTmp}, }, nodes::{ClipNode, GraphNode}, - utils::hash_map_join::HashMapJoinExt, + prelude::SpecContext, }; use bevy::{ reflect::{FromReflect, TypePath}, @@ -23,13 +23,13 @@ pub trait ToDot { &self, f: &mut impl std::io::Write, context: Option<&mut GraphContext>, - context_tmp: &mut GraphContextTmp, + context_tmp: GraphContextTmp, ) -> std::io::Result<()>; fn preview_dot( &self, context: Option<&mut GraphContext>, - context_tmp: &mut GraphContextTmp, + context_tmp: GraphContextTmp, ) -> std::io::Result<()> { let dir = std::env::temp_dir(); let path = dir.join("bevy_animation_graph_dot.dot"); @@ -55,7 +55,7 @@ pub trait ToDot { fn dot_to_tmp_file_and_open( &self, context: Option<&mut GraphContext>, - context_tmp: &mut GraphContextTmp, + context_tmp: GraphContextTmp, ) -> std::io::Result<()> { self.dot_to_tmp_file(context, context_tmp)?; @@ -69,7 +69,7 @@ pub trait ToDot { fn dot_to_tmp_file( &self, context: Option<&mut GraphContext>, - context_tmp: &mut GraphContextTmp, + context_tmp: GraphContextTmp, ) -> std::io::Result<()> { let path = "/tmp/bevy_animation_graph_dot.dot"; let pdf_path = "/tmp/bevy_animation_graph_dot.dot.pdf"; @@ -95,13 +95,15 @@ pub trait ToDot { } } -fn write_col(f: &mut impl std::io::Write, row: HashMap) -> std::io::Result<()> { +fn write_col( + f: &mut impl std::io::Write, + row: HashMap, +) -> std::io::Result<()> { if !row.is_empty() { write!(f, "")?; for (param_name, param_spec) in row.iter() { - let icon = match param_spec { - EdgeSpec::PoseFrame => String::from("🯅"), - EdgeSpec::F32 => String::from(""), + let icon = match param_spec.spec { + ParamSpec::F32 => String::from(""), }; write!( @@ -115,10 +117,26 @@ fn write_col(f: &mut impl std::io::Write, row: HashMap) -> std Ok(()) } +fn write_col_pose(f: &mut impl std::io::Write, row: HashMap) -> std::io::Result<()> { + if !row.is_empty() { + write!(f, "
")?; + for (param_name, _) in row.iter() { + let icon = String::from("🯅"); + write!( + f, + "", + param_name, icon, param_name + )?; + } + write!(f, "
{} {}
")?; + } + Ok(()) +} + fn write_rows( f: &mut impl std::io::Write, - left: HashMap, - right: HashMap, + left: HashMap, + right: HashMap, ) -> std::io::Result<()> { write!(f, "")?; write!(f, "")?; @@ -131,22 +149,19 @@ fn write_rows( Ok(()) } -fn write_values( +fn write_rows_pose( f: &mut impl std::io::Write, - row: &HashMap, + left: HashMap, + right: HashMap, ) -> std::io::Result<()> { - if !row.is_empty() { - write!(f, "")?; - for (param_name, param_val) in row.iter() { - write!( - f, - "", - param_name, - param_val.as_dot_label() - )?; - } - write!(f, "
{}{}
")?; - } + write!(f, "")?; + write!(f, "")?; + write_col_pose(f, left)?; + write!(f, "")?; + write!(f, "")?; + write_col_pose(f, right)?; + write!(f, "")?; + write!(f, "")?; Ok(()) } @@ -154,11 +169,10 @@ pub trait AsDotLabel { fn as_dot_label(&self) -> String; } -impl AsDotLabel for EdgeValue { +impl AsDotLabel for ParamValue { fn as_dot_label(&self) -> String { match self { - EdgeValue::PoseFrame(p) => p.as_dot_label(), - EdgeValue::F32(f) => format!("{:.3}", f), + ParamValue::F32(f) => format!("{:.3}", f), } } } @@ -217,95 +231,20 @@ impl AsDotLabel for TimeState { } } -fn write_debugdump( - f: &mut impl std::io::Write, - node: &AnimationNode, - context: &GraphContext, -) -> std::io::Result<()> { - write!(f, "DebugDump")?; - if let Some(param_cache) = context - .get_node_cache(&node.name) - .and_then(|nc| nc.parameter_cache.as_ref()) - { - write!(f, "Parameters")?; - write!(f, "")?; - write!(f, "")?; - write_values(f, ¶m_cache.upstream)?; - write!(f, "")?; - write!(f, "")?; - write_values(f, ¶m_cache.downstream)?; - write!(f, "")?; - write!(f, "")?; - } - if let Some(duration_cache) = context - .get_node_cache(&node.name) - .and_then(|nc| nc.duration_cache.as_ref()) - { - write!(f, "Durations")?; - write!(f, "")?; - write!(f, "")?; - write_values(f, &duration_cache.upstream)?; - write!(f, "")?; - write!(f, "")?; - write!(f, "{:?}", duration_cache.downstream)?; - write!(f, "")?; - write!(f, "")?; - } - - let tc = context.get_node_cache(&node.name).map(|nc| &nc.time_caches); - if let Some(time_caches) = tc { - if !time_caches.is_empty() { - write!(f, "Time queries")?; - for (_, time_cache) in time_caches.iter() { - write!(f, "")?; - write!(f, "")?; - write_values(f, &time_cache.upstream)?; - write!(f, "")?; - write!(f, "")?; - write!(f, "{}", time_cache.downstream.as_dot_label())?; - write!(f, "")?; - write!(f, "")?; - } - } - } - let tdc = context - .get_node_cache(&node.name) - .map(|nc| &nc.time_dependent_caches); - if let Some(time_dependent_caches) = tdc { - if !time_dependent_caches.is_empty() { - write!(f, "Time-dependent queries")?; - for (_, time_dependent_cache) in time_dependent_caches.iter() { - write!(f, "")?; - write!(f, "")?; - write_values(f, &time_dependent_cache.upstream)?; - write!(f, "")?; - write!(f, "")?; - write_values(f, &time_dependent_cache.downstream)?; - write!(f, "")?; - write!(f, "")?; - } - } - } - Ok(()) -} - impl ToDot for AnimationGraph { fn to_dot( &self, f: &mut impl std::io::Write, mut context: Option<&mut GraphContext>, - context_tmp: &mut GraphContextTmp, + context_tmp: GraphContextTmp, ) -> std::io::Result<()> { writeln!(f, "digraph {{")?; writeln!(f, "\trankdir=LR;")?; writeln!(f, "\tnode [style=rounded, shape=plain];")?; let mut default_graph_context = GraphContext::default(); - let mut has_ctx = false; let ctx = if let Some(context) = &mut context { - has_ctx = true; - context } else { &mut default_graph_context @@ -345,38 +284,131 @@ impl ToDot for AnimationGraph { }; write!(f, "",)?; - let in_param = node.parameter_input_spec(ctx, context_tmp); - let out_param = node.parameter_output_spec(ctx, context_tmp); + let in_param = node.parameter_input_spec(SpecContext::new(ctx, context_tmp)); + let out_param = node.parameter_output_spec(SpecContext::new(ctx, context_tmp)); - let in_td = node.time_dependent_input_spec(ctx, context_tmp); - let out_td = node.time_dependent_output_spec(ctx, context_tmp); + let in_td = node.pose_input_spec(SpecContext::new(ctx, context_tmp)); + let out_td = node.pose_output_spec(SpecContext::new(ctx, context_tmp)); - write_rows(f, in_param, out_param)?; - write_rows(f, in_td, out_td)?; + write_rows( + f, + in_param.into_iter().map(|(k, v)| (k, v)).collect(), + out_param.into_iter().map(|(k, v)| (k, v.into())).collect(), + )?; - if has_ctx { - write_debugdump(f, node, ctx)?; + let mut right = HashMap::new(); + if out_td { + right.insert("POSE".into(), ()); } + write_rows_pose(f, in_td.into_iter().map(|k| (k, ())).collect(), right)?; + writeln!(f, ">]")?; } - // TODO: Mark default output - // writeln!(f, "OUTPUT [shape=cds];")?; - // writeln!( - // f, - // "\t\"{}\":\"{}\" -> OUTPUT;", - // self.out_node, self.out_edge - // )?; - - for ((end_node, end_edge), (start_node, start_edge)) in self.node_edges.iter() { - let node = self.nodes.get(start_node).unwrap(); - let mut spec = node.parameter_output_spec(ctx, context_tmp); - spec.fill_up(&node.time_dependent_output_spec(ctx, context_tmp), &|v| *v); - let tp = spec.get(start_edge).unwrap(); - let color = match tp { - EdgeSpec::PoseFrame => "chartreuse4", - EdgeSpec::F32 => "deeppink3", + // --- Input parameters node + // -------------------------------------------------------- + let name = "INPUT PARAMETERS"; + write!( + f, + "\t\"{}\" [label=<", + name + )?; + write!(f, "",)?; + let out_param = self.input_parameters.clone(); + write_rows(f, HashMap::new(), out_param)?; + writeln!(f, "
{}", name)?; + write!(f, "
>]")?; + // -------------------------------------------------------- + + // --- Input poses node + // -------------------------------------------------------- + let name = "INPUT POSES"; + write!( + f, + "\t\"{}\" [label=<", + name + )?; + write!(f, "",)?; + let out_param = self.input_poses.clone(); + write_rows_pose( + f, + HashMap::new(), + out_param.into_iter().map(|k| (k, ())).collect(), + )?; + writeln!(f, "
{}", name)?; + write!(f, "
>]")?; + // -------------------------------------------------------- + + // --- Output parameters node + // -------------------------------------------------------- + let name = "OUTPUT PARAMETERS"; + write!( + f, + "\t\"{}\" [label=<", + name + )?; + write!(f, "",)?; + let out_param = self.output_parameters.clone(); + write_rows( + f, + out_param.into_iter().map(|(k, v)| (k, v.into())).collect(), + HashMap::new(), + )?; + writeln!(f, "
{}", name)?; + write!(f, "
>]")?; + // -------------------------------------------------------- + + // --- Output pose node + // -------------------------------------------------------- + let name = "OUTPUT POSE"; + write!( + f, + "\t\"{}\" [label=<", + name + )?; + write!(f, "",)?; + let out_param = self.output_pose; + + let mut out = HashMap::new(); + if out_param { + out.insert("POSE".into(), ()); + } + write_rows_pose(f, out, HashMap::new())?; + writeln!(f, "
{}", name)?; + write!(f, "
>]")?; + // -------------------------------------------------------- + + for (target_pin, source_pin) in self.node_edges.iter() { + let (start_node, start_edge) = match source_pin { + super::SourcePin::NodeParameter(node_id, pin_id) => { + (node_id.clone(), pin_id.clone()) + } + super::SourcePin::InputParameter(pin_id) => { + (String::from("INPUT PARAMETERS"), pin_id.clone()) + } + super::SourcePin::NodePose(node_id) => (node_id.clone(), String::from("POSE")), + super::SourcePin::InputPose(pin_id) => { + (String::from("INPUT POSES"), pin_id.clone()) + } + }; + + let (end_node, end_edge) = match target_pin { + super::TargetPin::NodeParameter(node_id, pin_id) => { + (node_id.clone(), pin_id.clone()) + } + super::TargetPin::OutputParameter(pin_id) => { + (String::from("OUTPUT PARAMETERS"), pin_id.clone()) + } + super::TargetPin::NodePose(node_id, pin_id) => (node_id.clone(), pin_id.clone()), + super::TargetPin::OutputPose => (String::from("OUTPUT POSE"), String::from("POSE")), + }; + + let color = match source_pin { + super::SourcePin::NodeParameter(_, _) => "darkblue", + super::SourcePin::InputParameter(_) => "darkblue", + super::SourcePin::NodePose(_) => "chartreuse4", + super::SourcePin::InputPose(_) => "chartreuse4", }; writeln!( diff --git a/src/core/animation_graph/loader.rs b/src/core/animation_graph/loader.rs index d3397af..5469b6b 100644 --- a/src/core/animation_graph/loader.rs +++ b/src/core/animation_graph/loader.rs @@ -1,4 +1,4 @@ -use super::{AnimationGraph, EdgeSpec, EdgeValue}; +use super::{AnimationGraph, ParamSpec, ParamValue}; use crate::{ core::animation_clip::GraphClip, nodes::{ @@ -89,23 +89,30 @@ impl AssetLoader for GraphClipLoader { pub struct AnimationGraphSerial { nodes: Vec, #[serde(default)] - input_parameters: HashMap, + input_parameters: HashMap, #[serde(default)] - input_time_dependent_spec: HashMap, + input_pose_spec: Vec, #[serde(default)] - output_parameter_spec: HashMap, + output_parameter_spec: HashMap, #[serde(default)] - output_time_dependent_spec: HashMap, + output_pose_spec: bool, /// (from_node, from_out_edge) -> (to_node, to_in_edge) /// Note that this is the opposite as [`AnimationGraph`] in order to make /// construction easier, and hand-editing of graph files more natural. - edges: Vec<((String, String), (String, String))>, - /// parameter_name -> (to_node, to_in_edge) - input_edges: Vec<(String, (String, String))>, - output_edges: Vec<((String, String), String)>, + #[serde(default)] + parameter_edges: Vec<((String, String), (String, String))>, + #[serde(default)] + pose_edges: Vec<(String, (String, String))>, + /// parameter_name -> (to_node, to_in_edge) + #[serde(default)] + input_parameter_edges: Vec<(String, (String, String))>, + #[serde(default)] + output_parameter_edges: Vec<((String, String), String)>, + #[serde(default)] + input_pose_edges: Vec<(String, (String, String))>, #[serde(default)] - default_output: Option, + output_pose_edge: Option, } #[derive(Serialize, Deserialize, Clone)] @@ -151,6 +158,8 @@ impl AssetLoader for AnimationGraphLoader { let mut graph = AnimationGraph::new(); + // --- Add nodes + // ------------------------------------------------------------------------------------ for serial_node in &serial.nodes { let node = match &serial_node.node { AnimationNodeTypeSerial::Clip(clip_name, override_duration) => { @@ -173,38 +182,54 @@ impl AssetLoader for AnimationGraphLoader { }; graph.add_node(node); } + // ------------------------------------------------------------------------------------ - for ((source_node, source_edge), (target_node, target_edge)) in &serial.edges { - graph.add_edge(source_node, source_edge, target_node, target_edge); + // --- Set up inputs and outputs + // ------------------------------------------------------------------------------------ + for (parameter_name, parameter_value) in &serial.input_parameters { + graph.set_default_parameter(parameter_name, parameter_value.clone()); + } + for td_name in &serial.input_pose_spec { + graph.add_input_pose(td_name); + } + for (p_name, p_spec) in &serial.output_parameter_spec { + graph.add_output_parameter(p_name, *p_spec); } - for (parameter_name, parameter_value) in &serial.input_parameters { - graph.set_input_parameter(parameter_name, parameter_value.clone()); + if serial.output_pose_spec { + graph.add_output_pose(); } + // ------------------------------------------------------------------------------------ - for (td_name, td_spec) in &serial.input_time_dependent_spec { - graph.register_input_td(td_name, *td_spec); + // --- Set up edges + // ------------------------------------------------------------------------------------ + for ((source_node, source_edge), (target_node, target_edge)) in &serial.parameter_edges + { + graph.add_node_parameter_edge(source_node, source_edge, target_node, target_edge); } - for (p_name, p_spec) in &serial.output_parameter_spec { - graph.register_output_parameter(p_name, *p_spec); + for (source_node, (target_node, target_pin)) in &serial.pose_edges { + graph.add_node_pose_edge(source_node, target_node, target_pin); } - for (td_name, td_spec) in &serial.output_time_dependent_spec { - graph.register_output_td(td_name, *td_spec); + for (parameter_name, (target_node, target_edge)) in &serial.input_parameter_edges { + graph.add_input_parameter_edge(parameter_name, target_node, target_edge); } - for (parameter_name, (target_node, target_edge)) in &serial.input_edges { - graph.add_input_edge(parameter_name, target_node, target_edge); + for ((source_node, source_edge), output_name) in &serial.output_parameter_edges { + graph.add_output_parameter_edge(source_node, source_edge, output_name); } - for ((source_node, source_edge), output_name) in &serial.output_edges { - graph.add_output_edge(source_node, source_edge, output_name); + for (parameter_name, (target_node, target_edge)) in &serial.input_pose_edges { + graph.add_input_pose_edge(parameter_name, target_node, target_edge); } - if let Some(def_output) = &serial.default_output { - graph.set_default_output(def_output); + if let Some(node_name) = &serial.output_pose_edge { + graph.add_output_pose_edge(node_name); } + // ------------------------------------------------------------------------------------ + + graph.validate()?; Ok(graph) }) diff --git a/src/core/animation_graph_player.rs b/src/core/animation_graph_player.rs index d49dd3d..84049f4 100644 --- a/src/core/animation_graph_player.rs +++ b/src/core/animation_graph_player.rs @@ -1,11 +1,11 @@ -use crate::{prelude::GraphContextTmp, utils::hash_map_join::HashMapOpsExt}; +use crate::prelude::GraphContextTmp; use super::{ - animation_graph::{AnimationGraph, EdgeValue, TimeState, TimeUpdate}, + animation_graph::{AnimationGraph, InputOverlay, ParamValue, TimeState, TimeUpdate}, graph_context::GraphContext, pose::Pose, }; -use bevy::{asset::prelude::*, ecs::prelude::*, reflect::prelude::*, utils::HashMap}; +use bevy::{asset::prelude::*, ecs::prelude::*, reflect::prelude::*}; /// Animation controls #[derive(Component, Default, Reflect)] @@ -17,7 +17,7 @@ pub struct AnimationGraphPlayer { pub(crate) pending_update: Option, pub(crate) context: GraphContext, - input_parameters: HashMap, + input_overlay: InputOverlay, } impl AnimationGraphPlayer { @@ -30,7 +30,7 @@ impl AnimationGraphPlayer { pending_update: None, context: GraphContext::default(), - input_parameters: HashMap::new(), + input_overlay: InputOverlay::default(), } } @@ -41,18 +41,20 @@ impl AnimationGraphPlayer { } /// Clear all input parameters for the animation graph - pub fn clear_input_parameter(&mut self) { - self.input_parameters.clear(); + pub fn clear_input_parameters(&mut self) { + self.input_overlay.clear(); } /// Configure an input parameter for the animation graph - pub fn set_input_parameter(&mut self, parameter_name: impl Into, value: EdgeValue) { - self.input_parameters.insert(parameter_name.into(), value); + pub fn set_input_parameter(&mut self, parameter_name: impl Into, value: ParamValue) { + self.input_overlay + .parameters + .insert(parameter_name.into(), value); } /// Return an input parameter for the animation graph - pub fn get_input_parameter(&self, parameter_name: &str) -> Option { - self.input_parameters.get(parameter_name).cloned() + pub fn get_input_parameter(&self, parameter_name: &str) -> Option { + self.input_overlay.parameters.get(parameter_name).cloned() } /// Start playing an animation, resetting state of the player. @@ -65,7 +67,7 @@ impl AnimationGraphPlayer { } /// Query the animation graph with the latest time update and inputs - pub(crate) fn query(&mut self, context_tmp: &mut GraphContextTmp) -> Option { + pub(crate) fn query(&mut self, context_tmp: GraphContextTmp) -> Option { let Some(graph_handle) = &self.animation else { return None; }; @@ -74,18 +76,11 @@ impl AnimationGraphPlayer { return None; }; - let mut overlay_input_node = graph.nodes.get(AnimationGraph::INPUT_NODE).unwrap().clone(); - overlay_input_node - .node - .unwrap_input_mut() - .parameters - .extend_replacing_owned(self.input_parameters.clone()); - Some(graph.query_with_overlay( self.elapsed.update, &mut self.context, context_tmp, - &HashMap::from([(AnimationGraph::INPUT_NODE.into(), overlay_input_node)]), + &self.input_overlay, )) } diff --git a/src/core/animation_node.rs b/src/core/animation_node.rs index fd3fc45..ab41e67 100644 --- a/src/core/animation_node.rs +++ b/src/core/animation_node.rs @@ -1,75 +1,48 @@ use super::{ - animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + animation_graph::{OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate}, + frame::PoseFrame, +}; +use crate::{ + nodes::{ + add_f32::AddF32, blend_node::BlendNode, chain_node::ChainNode, clamp_f32::ClampF32, + clip_node::ClipNode, dummy_node::DummyNode, flip_lr_node::FlipLRNode, loop_node::LoopNode, + speed_node::SpeedNode, sub_f32::SubF32, DivF32, GraphNode, MulF32, }, - graph_context::{GraphContext, GraphContextTmp}, + prelude::{PassContext, SpecContext}, }; -use crate::nodes::{ - add_f32::AddF32, blend_node::BlendNode, chain_node::ChainNode, clamp_f32::ClampF32, - clip_node::ClipNode, dummy_node::DummyNode, flip_lr_node::FlipLRNode, loop_node::LoopNode, - speed_node::SpeedNode, sub_f32::SubF32, DivF32, GraphNode, MulF32, +use bevy::{ + reflect::prelude::*, + utils::{HashMap, HashSet}, }; -use bevy::{reflect::prelude::*, utils::HashMap}; use std::{ ops::{Deref, DerefMut}, sync::{Arc, Mutex}, }; +pub type DurationData = Option; + pub trait NodeLike: Send + Sync { fn parameter_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap; + inputs: HashMap, + ctx: PassContext, + ) -> HashMap; fn duration_pass( &self, - inputs: HashMap>, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap>; - fn time_pass( - &self, - input: TimeState, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap; + inputs: HashMap>, + ctx: PassContext, + ) -> Option; + fn time_pass(&self, input: TimeState, ctx: PassContext) -> HashMap; fn time_dependent_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap; + inputs: HashMap, + ctx: PassContext, + ) -> Option; - fn parameter_input_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap; - fn parameter_output_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap; - - fn time_dependent_input_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap; - fn time_dependent_output_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap; + fn parameter_input_spec(&self, ctx: SpecContext) -> HashMap; + fn parameter_output_spec(&self, ctx: SpecContext) -> HashMap; + fn pose_input_spec(&self, ctx: SpecContext) -> HashSet; + fn pose_output_spec(&self, ctx: SpecContext) -> bool; fn display_name(&self) -> String; } @@ -116,86 +89,46 @@ impl AnimationNode { impl NodeLike for AnimationNode { fn parameter_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.node - .map(|n| n.parameter_pass(inputs, name, path, context, context_tmp)) + inputs: HashMap, + ctx: PassContext, + ) -> HashMap { + self.node.map(|n| n.parameter_pass(inputs, ctx)) } fn duration_pass( &self, - inputs: HashMap>, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - self.node - .map(|n| n.duration_pass(inputs, name, path, context, context_tmp)) + inputs: HashMap>, + ctx: PassContext, + ) -> Option> { + self.node.map(|n| n.duration_pass(inputs, ctx)) } - fn time_pass( - &self, - input: TimeState, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.node - .map(|n| n.time_pass(input, name, path, context, context_tmp)) + fn time_pass(&self, input: TimeState, ctx: PassContext) -> HashMap { + self.node.map(|n| n.time_pass(input, ctx)) } fn time_dependent_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.node - .map(|n| n.time_dependent_pass(inputs, name, path, context, context_tmp)) + inputs: HashMap, + ctx: PassContext, + ) -> Option { + self.node.map(|n| n.time_dependent_pass(inputs, ctx)) } - fn parameter_input_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.node - .map(|n| n.parameter_input_spec(context, context_tmp)) + fn parameter_input_spec(&self, ctx: SpecContext) -> HashMap { + self.node.map(|n| n.parameter_input_spec(ctx)) } - fn parameter_output_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.node - .map(|n| n.parameter_output_spec(context, context_tmp)) + fn parameter_output_spec(&self, ctx: SpecContext) -> HashMap { + self.node.map(|n| n.parameter_output_spec(ctx)) } - fn time_dependent_input_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.node - .map(|n| n.time_dependent_input_spec(context, context_tmp)) + fn pose_input_spec(&self, ctx: SpecContext) -> HashSet { + self.node.map(|n| n.pose_input_spec(ctx)) } - fn time_dependent_output_spec( - &self, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.node - .map(|n| n.time_dependent_output_spec(context, context_tmp)) + fn pose_output_spec(&self, ctx: SpecContext) -> bool { + self.node.map(|n| n.pose_output_spec(ctx)) } fn display_name(&self) -> String { @@ -205,9 +138,6 @@ impl NodeLike for AnimationNode { #[derive(Reflect, Clone, Debug)] pub enum AnimationNodeType { - GraphInput(GraphInputNode), - GraphOutput(GraphOutputNode), - Clip(ClipNode), Blend(BlendNode), Chain(ChainNode), @@ -232,8 +162,6 @@ impl AnimationNodeType { F: FnOnce(&dyn NodeLike) -> O, { match self { - AnimationNodeType::GraphInput(n) => f(n), - AnimationNodeType::GraphOutput(n) => f(n), AnimationNodeType::Clip(n) => f(n), AnimationNodeType::Blend(n) => f(n), AnimationNodeType::Chain(n) => f(n), @@ -255,8 +183,6 @@ impl AnimationNodeType { F: FnMut(&mut dyn NodeLike) -> O, { match self { - AnimationNodeType::GraphInput(n) => f(n), - AnimationNodeType::GraphOutput(n) => f(n), AnimationNodeType::Clip(n) => f(n), AnimationNodeType::Blend(n) => f(n), AnimationNodeType::Chain(n) => f(n), @@ -275,228 +201,4 @@ impl AnimationNodeType { } } } - - pub fn unwrap_input(&self) -> &GraphInputNode { - match self { - AnimationNodeType::GraphInput(n) => n, - _ => panic!("Node is not a parameter node"), - } - } - - pub fn unwrap_input_mut(&mut self) -> &mut GraphInputNode { - match self { - AnimationNodeType::GraphInput(n) => n, - _ => panic!("Node is not a parameter node"), - } - } - - pub fn unwrap_output(&self) -> &GraphOutputNode { - match self { - AnimationNodeType::GraphOutput(n) => n, - _ => panic!("Node is not a parameter node"), - } - } - - pub fn unwrap_output_mut(&mut self) -> &mut GraphOutputNode { - match self { - AnimationNodeType::GraphOutput(n) => n, - _ => panic!("Node is not a parameter node"), - } - } -} - -#[derive(Reflect, Default, Clone, Debug)] -pub struct GraphInputNode { - pub parameters: HashMap, - pub time_dependent_spec: HashMap, - pub time_dependent: HashMap, - pub durations: HashMap>, -} - -#[derive(Reflect, Default, Clone, Debug)] -pub struct GraphOutputNode { - pub parameters: HashMap, - pub time_dependent: HashMap, -} - -impl GraphInputNode { - pub fn wrapped(self, name: impl Into) -> AnimationNode { - AnimationNode::new_from_nodetype(name.into(), AnimationNodeType::GraphInput(self)) - } -} - -impl GraphOutputNode { - pub fn wrapped(self, name: impl Into) -> AnimationNode { - AnimationNode::new_from_nodetype(name.into(), AnimationNodeType::GraphOutput(self)) - } -} - -impl NodeLike for GraphInputNode { - fn parameter_pass( - &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.parameters.clone() - } - - fn duration_pass( - &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - self.durations.clone() - } - - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() - } - - fn time_dependent_pass( - &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() - } - - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() - } - - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.parameters - .iter() - .map(|(k, v)| (k.clone(), EdgeSpec::from(v))) - .collect() - } - - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() - } - - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.time_dependent_spec.clone() - } - - fn display_name(&self) -> String { - "ó°¿„ Input".into() - } -} - -impl NodeLike for GraphOutputNode { - fn parameter_pass( - &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - inputs.clone() - } - - fn duration_pass( - &self, - inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - inputs.clone() - } - - fn time_pass( - &self, - input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.time_dependent - .iter() - .map(|(k, _)| (k.clone(), input.update)) - .collect() - } - - fn time_dependent_pass( - &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - inputs.clone() - } - - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.parameters.clone() - } - - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.parameters.clone() - } - - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.time_dependent.clone() - } - - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - self.time_dependent.clone() - } - - fn display_name(&self) -> String { - "ó°¿… Output".into() - } } diff --git a/src/core/caches.rs b/src/core/caches.rs index ced171c..45fd7ea 100644 --- a/src/core/caches.rs +++ b/src/core/caches.rs @@ -1,69 +1,34 @@ -use super::animation_graph::{EdgePath, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate}; +use super::animation_graph::{ParamValue, PinId, TimeState, TimeUpdate}; use bevy::{reflect::prelude::*, utils::HashMap}; #[derive(Reflect, Clone, Debug, Default)] pub struct ParameterCache { - pub upstream: HashMap, - pub downstream: HashMap, + pub upstream: HashMap, + pub downstream: HashMap, } #[derive(Reflect, Clone, Debug)] pub struct DurationCache { - pub upstream: HashMap>, - pub downstream: HashMap>, + pub upstream: HashMap>, + pub downstream: Option>, } #[derive(Reflect, Clone, Debug)] pub struct TimeCache { - pub upstream: HashMap, + pub upstream: HashMap, pub downstream: TimeState, } #[derive(Reflect, Clone, Debug, Default)] pub struct TimeDependentCache { - pub upstream: HashMap, - pub downstream: HashMap, + pub upstream: HashMap, + pub downstream: Option, } -#[derive(Reflect, Clone, Debug)] +#[derive(Reflect, Clone, Debug, Default)] pub struct AnimationCaches { pub parameter_cache: Option, pub duration_cache: Option, - pub time_caches: HashMap, - pub time_dependent_caches: HashMap, -} - -#[derive(Reflect, Clone, Debug)] -pub struct EdgePathCache<'a> { - pub parameter_cache: &'a ParameterCache, - pub duration_cache: &'a DurationCache, - pub time_cache: &'a TimeCache, - pub time_dependent_cache: &'a TimeDependentCache, -} - -impl AnimationCaches { - pub fn get<'a>(&'a self, key: &EdgePath) -> Option> { - let parameter_cache = self.parameter_cache.as_ref()?; - let duration_cache = self.duration_cache.as_ref()?; - let time_cache = self.time_caches.get(key)?; - let time_dependent_cache = self.time_dependent_caches.get(key)?; - - Some(EdgePathCache { - parameter_cache, - duration_cache, - time_cache, - time_dependent_cache, - }) - } -} - -impl Default for AnimationCaches { - fn default() -> Self { - Self { - parameter_cache: None, - duration_cache: None, - time_caches: HashMap::new(), - time_dependent_caches: HashMap::new(), - } - } + pub time_caches: Option, + pub time_dependent_caches: Option, } diff --git a/src/core/graph_context.rs b/src/core/graph_context.rs index 5a66007..5a751ec 100644 --- a/src/core/graph_context.rs +++ b/src/core/graph_context.rs @@ -1,35 +1,130 @@ use super::{ animation_clip::GraphClip, - animation_graph::{AnimationGraph, EdgePath}, - caches::{AnimationCaches, DurationCache, ParameterCache, TimeCache, TimeDependentCache}, + animation_graph::{AnimationGraph, NodeId, ParamValue, PinId, SourcePin, TargetPin, TimeState}, + frame::PoseFrame, }; +use crate::prelude::DurationData; use bevy::{asset::Assets, reflect::prelude::*, utils::HashMap}; +pub struct PassContext<'a> { + pub node_id: &'a NodeId, + pub context: &'a mut GraphContext, + pub context_tmp: GraphContextTmp<'a>, + pub edges: &'a HashMap, +} + +impl<'a> PassContext<'a> { + pub fn new( + node_id: &'a NodeId, + context: &'a mut GraphContext, + context_tmp: GraphContextTmp<'a>, + edges: &'a HashMap, + ) -> Self { + Self { + node_id, + context, + context_tmp, + edges, + } + } + + pub fn parameter_back(&self, pin_id: impl Into) -> ParamValue { + let target_pin = TargetPin::NodeParameter(self.node_id.clone(), pin_id.into()); + let source_pin = self + .edges + .get(&target_pin) + .unwrap_or_else(|| panic!("Pin {target_pin:?} is disconnected!")); + + self.context + .get_cached_parameter(source_pin) + .unwrap_or_else(|| panic!("Parameter not cached at {source_pin:?}")) + .clone() + } + + pub fn duration_back(&self, pin_id: impl Into) -> DurationData { + let target_pin = TargetPin::NodePose(self.node_id.clone(), pin_id.into()); + let source_pin = self + .edges + .get(&target_pin) + .unwrap_or_else(|| panic!("Pin {target_pin:?} is disconnected!")); + + self.context + .get_cached_duration(source_pin) + .unwrap_or_else(|| panic!("Duration not cached at {source_pin:?}")) + } + + pub fn time_fwd(&self) -> TimeState { + let source_pin = SourcePin::NodePose(self.node_id.clone()); + + *self + .context + .get_cached_time(&source_pin) + .unwrap_or_else(|| panic!("Time not cached at {source_pin:?}")) + } + + pub fn prev_time_fwd_opt(&self) -> Option { + let source_pin = SourcePin::NodePose(self.node_id.clone()); + self.context.old_cached_time(&source_pin).cloned() + } +} + +pub struct SpecContext<'a> { + pub context: &'a mut GraphContext, + pub context_tmp: GraphContextTmp<'a>, +} + +impl<'a> SpecContext<'a> { + pub fn new(context: &'a mut GraphContext, context_tmp: GraphContextTmp<'a>) -> Self { + Self { + context, + context_tmp, + } + } +} + +#[derive(Reflect, Debug, Default)] +pub struct GraphCache { + pub parameters: HashMap, + pub durations: HashMap, + pub times: HashMap, + pub poses: HashMap, +} + +impl GraphCache { + pub fn clear(&mut self) { + self.parameters.clear(); + self.durations.clear(); + self.times.clear(); + self.poses.clear(); + } +} + #[derive(Reflect, Debug, Default)] pub struct GraphContext { /// Caches are double buffered - caches: [HashMap; 2], + caches: [GraphCache; 2], current_cache: usize, #[reflect(ignore)] subgraph_contexts: HashMap, } /// Contains temprary data such as references to assets, gizmos, etc. +#[derive(Clone, Copy)] pub struct GraphContextTmp<'a> { pub graph_clip_assets: &'a Assets, pub animation_graph_assets: &'a Assets, } impl GraphContext { - pub fn get_cache(&self) -> &HashMap { + pub fn get_cache(&self) -> &GraphCache { &self.caches[self.current_cache] } - pub fn get_other_cache(&self) -> &HashMap { + pub fn get_other_cache(&self) -> &GraphCache { &self.caches[self.other_cache()] } - pub fn get_cache_mut(&mut self) -> &mut HashMap { + pub fn get_cache_mut(&mut self) -> &mut GraphCache { &mut self.caches[self.current_cache] } @@ -50,69 +145,56 @@ impl GraphContext { } } - pub fn get_node_cache(&self, node: &str) -> Option<&AnimationCaches> { - self.get_cache().get(node) - } - - pub fn get_node_other_cache(&self, node: &str) -> Option<&AnimationCaches> { - self.get_other_cache().get(node) - } - - pub fn get_parameters(&self, node: &str) -> Option<&ParameterCache> { - self.get_node_cache(node) - .and_then(|c| c.parameter_cache.as_ref()) + pub fn get_cached_parameter(&self, source_pin: &SourcePin) -> Option<&ParamValue> { + self.get_cache().parameters.get(source_pin) } - pub fn get_durations(&self, node: &str) -> Option<&DurationCache> { - self.get_node_cache(node) - .and_then(|c| c.duration_cache.as_ref()) + pub fn insert_cached_parameter( + &mut self, + source_pin: SourcePin, + value: ParamValue, + ) -> Option { + self.get_cache_mut().parameters.insert(source_pin, value) } - pub fn get_times(&self, node: &str, path: &EdgePath) -> Option<&TimeCache> { - self.get_node_cache(node) - .and_then(|c| c.time_caches.get(path)) + pub fn get_cached_duration(&self, source_pin: &SourcePin) -> Option> { + self.get_cache().durations.get(source_pin).copied() } - pub fn get_time_dependent(&self, node: &str, path: &EdgePath) -> Option<&TimeDependentCache> { - self.get_node_cache(node) - .and_then(|c| c.time_dependent_caches.get(path)) + pub fn insert_cached_duration( + &mut self, + source_pin: SourcePin, + value: Option, + ) -> Option> { + self.get_cache_mut().durations.insert(source_pin, value) } - pub fn get_other_parameters(&self, node: &str) -> Option<&ParameterCache> { - self.get_node_other_cache(node) - .and_then(|c| c.parameter_cache.as_ref()) + pub fn get_cached_time(&self, source_pin: &SourcePin) -> Option<&TimeState> { + self.get_cache().times.get(source_pin) } - pub fn get_other_durations(&self, node: &str) -> Option<&DurationCache> { - self.get_node_other_cache(node) - .and_then(|c| c.duration_cache.as_ref()) + pub fn old_cached_time(&self, source_pin: &SourcePin) -> Option<&TimeState> { + self.get_other_cache().times.get(source_pin) } - pub fn get_other_times(&self, node: &str, path: &EdgePath) -> Option<&TimeCache> { - self.get_node_other_cache(node) - .and_then(|c| c.time_caches.get(path)) + pub fn insert_cached_time( + &mut self, + source_pin: SourcePin, + value: TimeState, + ) -> Option { + self.get_cache_mut().times.insert(source_pin, value) } - pub fn get_other_time_dependent( - &self, - node: &str, - path: &EdgePath, - ) -> Option<&TimeDependentCache> { - self.get_node_other_cache(node) - .and_then(|c| c.time_dependent_caches.get(path)) + pub fn get_cached_pose(&self, source_pin: &SourcePin) -> Option<&PoseFrame> { + self.get_cache().poses.get(source_pin) } - pub fn get_node_cache_mut(&mut self, node: &str) -> Option<&mut AnimationCaches> { - self.get_cache_mut().get_mut(node) - } - - pub fn get_node_cache_or_insert_default(&mut self, node: &str) -> &mut AnimationCaches { - let caches = self.get_cache_mut(); - if !caches.contains_key(node) { - caches.insert(node.to_string(), AnimationCaches::default()); - } - - caches.get_mut(node).unwrap() + pub fn insert_cached_pose( + &mut self, + source_pin: SourcePin, + value: PoseFrame, + ) -> Option { + self.get_cache_mut().poses.insert(source_pin, value) } pub fn context_for_subgraph_or_insert_default(&mut self, node: &str) -> &mut GraphContext { diff --git a/src/core/systems.rs b/src/core/systems.rs index b2b14c6..b608a72 100644 --- a/src/core/systems.rs +++ b/src/core/systems.rs @@ -135,12 +135,12 @@ pub fn run_animation_player( player.pending_update = None; - let mut context_tmp = GraphContextTmp { + let context_tmp = GraphContextTmp { graph_clip_assets: graph_clips, animation_graph_assets: graphs, }; - let Some(out_pose) = player.query(&mut context_tmp) else { + let Some(out_pose) = player.query(context_tmp) else { return; }; diff --git a/src/nodes/arithmetic/add_f32.rs b/src/nodes/arithmetic/add_f32.rs index f79e0e4..b7a9d4b 100644 --- a/src/nodes/arithmetic/add_f32.rs +++ b/src/nodes/arithmetic/add_f32.rs @@ -1,10 +1,11 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct AddF32 {} @@ -26,84 +27,52 @@ impl AddF32 { impl NodeLike for AddF32 { fn parameter_pass( &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + inputs: HashMap, + _: PassContext, + ) -> HashMap { let input_1 = inputs.get(Self::INPUT_1).unwrap().clone().unwrap_f32(); let input_2 = inputs.get(Self::INPUT_2).unwrap().clone().unwrap_f32(); - HashMap::from([(Self::OUTPUT.into(), EdgeValue::F32(input_1 + input_2))]) + HashMap::from([(Self::OUTPUT.into(), ParamValue::F32(input_1 + input_2))]) } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::new() + _inputs: HashMap>, + _: PassContext, + ) -> Option { + None } - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, _input: TimeState, _: PassContext) -> HashMap { HashMap::new() } fn time_dependent_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + _inputs: HashMap, + _: PassContext, + ) -> Option { + None } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::from([ - (Self::INPUT_1.into(), EdgeSpec::F32), - (Self::INPUT_2.into(), EdgeSpec::F32), + (Self::INPUT_1.into(), ParamSpec::F32.into()), + (Self::INPUT_2.into(), ParamSpec::F32.into()), ]) } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::F32)]) + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { + HashMap::from([(Self::OUTPUT.into(), ParamSpec::F32)]) } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::new() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_output_spec(&self, _: SpecContext) -> bool { + false } fn display_name(&self) -> String { diff --git a/src/nodes/arithmetic/clamp_f32.rs b/src/nodes/arithmetic/clamp_f32.rs index 39a561b..6f3c500 100644 --- a/src/nodes/arithmetic/clamp_f32.rs +++ b/src/nodes/arithmetic/clamp_f32.rs @@ -1,10 +1,11 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct ClampF32 {} @@ -27,86 +28,54 @@ impl ClampF32 { impl NodeLike for ClampF32 { fn parameter_pass( &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + inputs: HashMap, + _: PassContext, + ) -> HashMap { let input = inputs.get(Self::INPUT).unwrap().clone().unwrap_f32(); let min = inputs.get(Self::CLAMP_MIN).unwrap().clone().unwrap_f32(); let max = inputs.get(Self::CLAMP_MAX).unwrap().clone().unwrap_f32(); - HashMap::from([(Self::OUTPUT.into(), EdgeValue::F32(input.clamp(min, max)))]) + HashMap::from([(Self::OUTPUT.into(), ParamValue::F32(input.clamp(min, max)))]) } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::new() + _inputs: HashMap>, + _: PassContext, + ) -> Option> { + None } - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, _input: TimeState, _: PassContext) -> HashMap { HashMap::new() } fn time_dependent_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + _inputs: HashMap, + _: PassContext, + ) -> Option { + None } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::from([ - (Self::INPUT.into(), EdgeSpec::F32), - (Self::CLAMP_MIN.into(), EdgeSpec::F32), - (Self::CLAMP_MAX.into(), EdgeSpec::F32), + (Self::INPUT.into(), ParamSpec::F32.into()), + (Self::CLAMP_MIN.into(), ParamSpec::F32.into()), + (Self::CLAMP_MAX.into(), ParamSpec::F32.into()), ]) } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::F32)]) + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { + HashMap::from([(Self::OUTPUT.into(), ParamSpec::F32)]) } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::new() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_output_spec(&self, _: SpecContext) -> bool { + false } fn display_name(&self) -> String { diff --git a/src/nodes/arithmetic/div_f32.rs b/src/nodes/arithmetic/div_f32.rs index 278121d..01031dc 100644 --- a/src/nodes/arithmetic/div_f32.rs +++ b/src/nodes/arithmetic/div_f32.rs @@ -1,10 +1,11 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct DivF32 {} @@ -26,84 +27,52 @@ impl DivF32 { impl NodeLike for DivF32 { fn parameter_pass( &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + inputs: HashMap, + _: PassContext, + ) -> HashMap { let input_1 = inputs.get(Self::INPUT_1).unwrap().clone().unwrap_f32(); let input_2 = inputs.get(Self::INPUT_2).unwrap().clone().unwrap_f32(); - HashMap::from([(Self::OUTPUT.into(), EdgeValue::F32(input_1 / input_2))]) + HashMap::from([(Self::OUTPUT.into(), ParamValue::F32(input_1 / input_2))]) } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::new() + _inputs: HashMap>, + _: PassContext, + ) -> Option> { + None } - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, _input: TimeState, _: PassContext) -> HashMap { HashMap::new() } fn time_dependent_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + _inputs: HashMap, + _: PassContext, + ) -> Option { + None } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::from([ - (Self::INPUT_1.into(), EdgeSpec::F32), - (Self::INPUT_2.into(), EdgeSpec::F32), + (Self::INPUT_1.into(), ParamSpec::F32.into()), + (Self::INPUT_2.into(), ParamSpec::F32.into()), ]) } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::F32)]) + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { + HashMap::from([(Self::OUTPUT.into(), ParamSpec::F32)]) } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::new() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_output_spec(&self, _: SpecContext) -> bool { + false } fn display_name(&self) -> String { diff --git a/src/nodes/arithmetic/mul_f32.rs b/src/nodes/arithmetic/mul_f32.rs index fc46354..9ce890d 100644 --- a/src/nodes/arithmetic/mul_f32.rs +++ b/src/nodes/arithmetic/mul_f32.rs @@ -1,10 +1,11 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct MulF32 {} @@ -26,84 +27,52 @@ impl MulF32 { impl NodeLike for MulF32 { fn parameter_pass( &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + inputs: HashMap, + _: PassContext, + ) -> HashMap { let input_1 = inputs.get(Self::INPUT_1).unwrap().clone().unwrap_f32(); let input_2 = inputs.get(Self::INPUT_2).unwrap().clone().unwrap_f32(); - HashMap::from([(Self::OUTPUT.into(), EdgeValue::F32(input_1 * input_2))]) + HashMap::from([(Self::OUTPUT.into(), ParamValue::F32(input_1 * input_2))]) } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::new() + _inputs: HashMap>, + _: PassContext, + ) -> Option { + None } - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, _input: TimeState, _: PassContext) -> HashMap { HashMap::new() } fn time_dependent_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + _inputs: HashMap, + _: PassContext, + ) -> Option { + None } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::from([ - (Self::INPUT_1.into(), EdgeSpec::F32), - (Self::INPUT_2.into(), EdgeSpec::F32), + (Self::INPUT_1.into(), ParamSpec::F32.into()), + (Self::INPUT_2.into(), ParamSpec::F32.into()), ]) } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::F32)]) + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { + HashMap::from([(Self::OUTPUT.into(), ParamSpec::F32)]) } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::new() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_output_spec(&self, _: SpecContext) -> bool { + false } fn display_name(&self) -> String { diff --git a/src/nodes/arithmetic/sub_f32.rs b/src/nodes/arithmetic/sub_f32.rs index 68047dd..909364e 100644 --- a/src/nodes/arithmetic/sub_f32.rs +++ b/src/nodes/arithmetic/sub_f32.rs @@ -1,10 +1,11 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct SubF32 {} @@ -26,84 +27,52 @@ impl SubF32 { impl NodeLike for SubF32 { fn parameter_pass( &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + inputs: HashMap, + _: PassContext, + ) -> HashMap { let input_1 = inputs.get(Self::INPUT_1).unwrap().clone().unwrap_f32(); let input_2 = inputs.get(Self::INPUT_2).unwrap().clone().unwrap_f32(); - HashMap::from([(Self::OUTPUT.into(), EdgeValue::F32(input_1 - input_2))]) + HashMap::from([(Self::OUTPUT.into(), ParamValue::F32(input_1 - input_2))]) } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::new() + _inputs: HashMap>, + _: PassContext, + ) -> Option> { + None } - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, _input: TimeState, _: PassContext) -> HashMap { HashMap::new() } fn time_dependent_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + _inputs: HashMap, + _: PassContext, + ) -> Option { + None } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::from([ - (Self::INPUT_1.into(), EdgeSpec::F32), - (Self::INPUT_2.into(), EdgeSpec::F32), + (Self::INPUT_1.into(), ParamSpec::F32.into()), + (Self::INPUT_2.into(), ParamSpec::F32.into()), ]) } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::F32)]) + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { + HashMap::from([(Self::OUTPUT.into(), ParamSpec::F32)]) } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::new() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_output_spec(&self, _: SpecContext) -> bool { + false } fn display_name(&self) -> String { diff --git a/src/nodes/blend_node.rs b/src/nodes/blend_node.rs index 01f7556..9aed416 100644 --- a/src/nodes/blend_node.rs +++ b/src/nodes/blend_node.rs @@ -1,11 +1,12 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; use crate::interpolation::linear::InterpolateLinear; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct BlendNode; @@ -28,23 +29,17 @@ impl BlendNode { impl NodeLike for BlendNode { fn parameter_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + _inputs: HashMap, + _: PassContext, + ) -> HashMap { HashMap::new() } fn duration_pass( &self, - inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { + inputs: HashMap>, + _: PassContext, + ) -> Option { let duration_1 = *inputs.get(Self::INPUT_1).unwrap(); let duration_2 = *inputs.get(Self::INPUT_2).unwrap(); @@ -55,17 +50,10 @@ impl NodeLike for BlendNode { (None, None) => None, }; - HashMap::from([(Self::OUTPUT.into(), out_duration)]) + Some(out_duration) } - fn time_pass( - &self, - input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, input: TimeState, _: PassContext) -> HashMap { HashMap::from([ (Self::INPUT_1.into(), input.update), (Self::INPUT_2.into(), input.update), @@ -74,70 +62,30 @@ impl NodeLike for BlendNode { fn time_dependent_pass( &self, - inputs: HashMap, - name: &str, - _path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let in_frame_1 = inputs - .get(Self::INPUT_1) - .unwrap() - .clone() - .unwrap_pose_frame(); - let in_frame_2 = inputs - .get(Self::INPUT_2) - .unwrap() - .clone() - .unwrap_pose_frame(); - let alpha = context - .get_parameters(name) - .unwrap() - .upstream - .get(Self::FACTOR) - .unwrap() - .clone() - .unwrap_f32(); - - HashMap::from([( - Self::OUTPUT.into(), - EdgeValue::PoseFrame(in_frame_1.interpolate_linear(&in_frame_2, alpha)), - )]) + mut inputs: HashMap, + ctx: PassContext, + ) -> Option { + let in_frame_1 = inputs.remove(Self::INPUT_1).unwrap(); + let in_frame_2 = inputs.remove(Self::INPUT_2).unwrap(); + let alpha = ctx.parameter_back(Self::FACTOR).unwrap_f32(); + + Some(in_frame_1.interpolate_linear(&in_frame_2, alpha)) } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::FACTOR.into(), EdgeSpec::F32)]) + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { + HashMap::from([(Self::FACTOR.into(), ParamSpec::F32.into())]) } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([ - (Self::INPUT_1.into(), EdgeSpec::PoseFrame), - (Self::INPUT_2.into(), EdgeSpec::PoseFrame), - ]) + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::from([Self::INPUT_1.into(), Self::INPUT_2.into()]) } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_output_spec(&self, _: SpecContext) -> bool { + true } fn display_name(&self) -> String { diff --git a/src/nodes/chain_node.rs b/src/nodes/chain_node.rs index b8d6e66..af92b3f 100644 --- a/src/nodes/chain_node.rs +++ b/src/nodes/chain_node.rs @@ -1,11 +1,12 @@ use crate::chaining::Chainable; use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct ChainNode {} @@ -27,23 +28,17 @@ impl ChainNode { impl NodeLike for ChainNode { fn parameter_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + _inputs: HashMap, + _: PassContext, + ) -> HashMap { HashMap::new() } fn duration_pass( &self, - inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { + inputs: HashMap>, + _: PassContext, + ) -> Option { let source_duration_1 = *inputs.get(Self::INPUT_1).unwrap(); let source_duration_2 = *inputs.get(Self::INPUT_2).unwrap(); @@ -54,19 +49,11 @@ impl NodeLike for ChainNode { (None, None) => None, }; - HashMap::from([(Self::OUTPUT.into(), out_duration)]) + Some(out_duration) } - fn time_pass( - &self, - input: TimeState, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let durations = context.get_durations(name).unwrap(); - let duration_1 = durations.upstream.get(Self::INPUT_1).unwrap(); + fn time_pass(&self, input: TimeState, ctx: PassContext) -> HashMap { + let duration_1 = ctx.duration_back(Self::INPUT_1); let Some(duration_1) = duration_1 else { // First input is infinite, forward time update without change return HashMap::from([ @@ -75,12 +62,10 @@ impl NodeLike for ChainNode { ]); }; - let prev_time = context - .get_other_times(name, path) - .map(|c| c.downstream.time) - .unwrap_or(input.time); + let prev_time_state = ctx.prev_time_fwd_opt().unwrap_or(input); + let prev_time = prev_time_state.time; - if input.time < *duration_1 { + if input.time < duration_1 { // Current frame ends in first clip HashMap::from([ (Self::INPUT_1.into(), input.update), @@ -92,13 +77,13 @@ impl NodeLike for ChainNode { // In such cases, the given update delta will encompass the period spent in the first // frame, which will desync the clip. // subtracting the extraneous dt will counter that. - let extraneous_dt = (*duration_1 - prev_time).max(0.); + let extraneous_dt = (duration_1 - prev_time).max(0.); HashMap::from([ (Self::INPUT_1.into(), TimeUpdate::Absolute(0.)), ( Self::INPUT_2.into(), match input.update { - TimeUpdate::Absolute(t) => TimeUpdate::Absolute(t - *duration_1), + TimeUpdate::Absolute(t) => TimeUpdate::Absolute(t - duration_1), TimeUpdate::Delta(dt) => TimeUpdate::Delta(dt - extraneous_dt), }, ), @@ -108,29 +93,15 @@ impl NodeLike for ChainNode { fn time_dependent_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let in_pose_1 = inputs - .get(Self::INPUT_1) - .unwrap() - .clone() - .unwrap_pose_frame(); - let in_pose_2 = inputs - .get(Self::INPUT_2) - .unwrap() - .clone() - .unwrap_pose_frame(); - let time = context.get_times(name, path).unwrap(); - let durations = context.get_durations(name).unwrap(); - - let time = time.downstream.time; - - let duration_1 = *durations.upstream.get(Self::INPUT_1).unwrap(); - let duration_2 = *durations.upstream.get(Self::INPUT_2).unwrap(); + mut inputs: HashMap, + ctx: PassContext, + ) -> Option { + let in_pose_1 = inputs.remove(Self::INPUT_1).unwrap(); + let in_pose_2 = inputs.remove(Self::INPUT_2).unwrap(); + let time = ctx.time_fwd().time; + + let duration_1 = ctx.duration_back(Self::INPUT_1); + let duration_2 = ctx.duration_back(Self::INPUT_2); let out_pose; @@ -141,42 +112,23 @@ impl NodeLike for ChainNode { out_pose = in_pose_1; } - HashMap::from([(Self::OUTPUT.into(), EdgeValue::PoseFrame(out_pose))]) + Some(out_pose) } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([ - (Self::INPUT_1.into(), EdgeSpec::PoseFrame), - (Self::INPUT_2.into(), EdgeSpec::PoseFrame), - ]) + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::from([Self::INPUT_1.into(), Self::INPUT_2.into()]) } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_output_spec(&self, _: SpecContext) -> bool { + true } fn display_name(&self) -> String { diff --git a/src/nodes/clip_node.rs b/src/nodes/clip_node.rs index 6360b7c..003a0c7 100644 --- a/src/nodes/clip_node.rs +++ b/src/nodes/clip_node.rs @@ -1,14 +1,14 @@ use crate::core::animation_clip::{GraphClip, Keyframes}; use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::frame::{BoneFrame, PoseFrame, ValueFrame}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; use crate::core::systems::get_keyframe; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::asset::Handle; use bevy::reflect::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug)] pub struct ClipNode { @@ -30,11 +30,11 @@ impl ClipNode { } #[inline] - pub fn clip_duration(&self, context_tmp: &GraphContextTmp) -> f32 { + pub fn clip_duration(&self, ctx: &PassContext) -> f32 { if let Some(duration) = self.override_duration { duration } else { - context_tmp + ctx.context_tmp .graph_clip_assets .get(&self.clip) .unwrap() @@ -46,51 +46,35 @@ impl ClipNode { impl NodeLike for ClipNode { fn parameter_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + _inputs: HashMap, + _: PassContext, + ) -> HashMap { HashMap::new() } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::from([(Self::OUTPUT.into(), Some(self.clip_duration(context_tmp)))]) + _inputs: HashMap>, + ctx: PassContext, + ) -> Option { + Some(Some(self.clip_duration(&ctx))) } - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, _input: TimeState, _: PassContext) -> HashMap { HashMap::new() } fn time_dependent_pass( &self, - _inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let clip_duration = self.clip_duration(context_tmp); - let clip = context_tmp.graph_clip_assets.get(&self.clip).unwrap(); - - let time = context.get_times(name, path).unwrap(); - let timestamp = time.downstream.time; + _inputs: HashMap, + ctx: PassContext, + ) -> Option { + let clip_duration = self.clip_duration(&ctx); + let clip = ctx.context_tmp.graph_clip_assets.get(&self.clip).unwrap(); + + let timestamp = ctx.time_fwd().time; let time = timestamp.clamp(0., clip_duration); + let mut animation_frame = PoseFrame::default(); for (path, bone_id) in &clip.paths { let curves = clip.get_curves(*bone_id).unwrap(); @@ -194,39 +178,23 @@ impl NodeLike for ClipNode { animation_frame.add_bone(frame, path.clone()); } - HashMap::from([(Self::OUTPUT.into(), EdgeValue::PoseFrame(animation_frame))]) + Some(animation_frame) } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::new() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_output_spec(&self, _: SpecContext) -> bool { + true } fn display_name(&self) -> String { diff --git a/src/nodes/dummy_node.rs b/src/nodes/dummy_node.rs index e431023..db95dec 100644 --- a/src/nodes/dummy_node.rs +++ b/src/nodes/dummy_node.rs @@ -1,10 +1,11 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, CustomNode, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct DummyNode {} @@ -25,78 +26,46 @@ impl DummyNode { impl NodeLike for DummyNode { fn parameter_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + _inputs: HashMap, + _: PassContext, + ) -> HashMap { HashMap::new() } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::new() + _inputs: HashMap>, + _: PassContext, + ) -> Option { + None } - fn time_pass( - &self, - _input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, _input: TimeState, _: PassContext) -> HashMap { HashMap::new() } fn time_dependent_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + _inputs: HashMap, + _: PassContext, + ) -> Option { + None } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::new() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::new() + fn pose_output_spec(&self, _: SpecContext) -> bool { + false } fn display_name(&self) -> String { diff --git a/src/nodes/flip_lr_node.rs b/src/nodes/flip_lr_node.rs index 342de61..d3b343a 100644 --- a/src/nodes/flip_lr_node.rs +++ b/src/nodes/flip_lr_node.rs @@ -1,11 +1,12 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; use crate::flipping::FlipXBySuffix; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug)] pub struct FlipLRNode {} @@ -32,85 +33,50 @@ impl FlipLRNode { impl NodeLike for FlipLRNode { fn parameter_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + _inputs: HashMap, + _: PassContext, + ) -> HashMap { HashMap::new() } fn duration_pass( &self, - inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::from([(Self::OUTPUT.into(), *inputs.get(Self::INPUT).unwrap())]) + inputs: HashMap>, + _: PassContext, + ) -> Option { + Some(*inputs.get(Self::INPUT).unwrap()) } - fn time_pass( - &self, - input: TimeState, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn time_pass(&self, input: TimeState, _: PassContext) -> HashMap { // Propagate the time update without modification HashMap::from([(Self::INPUT.into(), input.update)]) } fn time_dependent_pass( &self, - inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let in_pose_frame = inputs.get(Self::INPUT).unwrap().clone().unwrap_pose_frame(); + mut inputs: HashMap, + _: PassContext, + ) -> Option { + let in_pose_frame = inputs.remove(Self::INPUT).unwrap(); let flipped_pose_frame = in_pose_frame.flipped_by_suffix(" R".into(), " L".into()); - HashMap::from([( - Self::OUTPUT.into(), - EdgeValue::PoseFrame(flipped_pose_frame), - )]) + Some(flipped_pose_frame) } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::INPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::from([Self::INPUT.into()]) } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_output_spec(&self, _: SpecContext) -> bool { + true } fn display_name(&self) -> String { diff --git a/src/nodes/graph_node.rs b/src/nodes/graph_node.rs index e16469d..6b0b4ee 100644 --- a/src/nodes/graph_node.rs +++ b/src/nodes/graph_node.rs @@ -1,11 +1,11 @@ use crate::core::animation_graph::{ - AnimationGraph, EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + AnimationGraph, InputOverlay, OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; -use crate::utils::hash_map_join::HashMapOpsExt; +use crate::core::frame::PoseFrame; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct GraphNode { @@ -25,249 +25,129 @@ impl GraphNode { impl NodeLike for GraphNode { fn parameter_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - - let mut overlay_input_node = graph.nodes.get(AnimationGraph::INPUT_NODE).unwrap().clone(); - overlay_input_node - .node - .unwrap_input_mut() - .parameters - .extend_replacing_owned(inputs); - - let sub_context = context.context_for_subgraph_or_insert_default(name); - - graph.parameter_pass( - AnimationGraph::OUTPUT_NODE, - path.clone(), - sub_context, - context_tmp, - &HashMap::from([(AnimationGraph::INPUT_NODE.into(), overlay_input_node)]), - ); - - sub_context - .get_parameters(AnimationGraph::OUTPUT_NODE) - .unwrap() - .upstream - .clone() + inputs: HashMap, + ctx: PassContext, + ) -> HashMap { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); + + let input_overlay = InputOverlay { + parameters: inputs, + ..default() + }; + + let sub_context = ctx + .context + .context_for_subgraph_or_insert_default(ctx.node_id); + + graph.parameter_pass(sub_context, ctx.context_tmp, &input_overlay) } fn duration_pass( &self, - inputs: HashMap>, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - - let params = context.get_parameters(name).unwrap().upstream.clone(); - - let mut overlay_input_node = graph.nodes.get(AnimationGraph::INPUT_NODE).unwrap().clone(); - - overlay_input_node - .node - .unwrap_input_mut() - .parameters - .extend_replacing_owned(params); - overlay_input_node - .node - .unwrap_input_mut() - .durations - .extend_replacing_owned(inputs); - - let sub_context = context.context_for_subgraph_or_insert_default(name); - - graph.duration_pass( - AnimationGraph::OUTPUT_NODE, - path.clone(), - sub_context, - context_tmp, - &HashMap::from([(AnimationGraph::INPUT_NODE.into(), overlay_input_node)]), - ); - - sub_context - .get_durations(AnimationGraph::OUTPUT_NODE) - .unwrap() - .upstream - .clone() + inputs: HashMap, + ctx: PassContext, + ) -> Option { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); + + let input_overlay = InputOverlay { + durations: inputs, + // We do not add the parameters because they should already have been cached! + // in the subgraph context + ..default() + }; + + let sub_context = ctx + .context + .context_for_subgraph_or_insert_default(ctx.node_id); + + graph.duration_pass(sub_context, ctx.context_tmp, &input_overlay) } - fn time_pass( - &self, - input: TimeState, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - - let params = context.get_parameters(name).unwrap().upstream.clone(); - let durations = context.get_durations(name).unwrap().upstream.clone(); - - let mut overlay_input_node = graph.nodes.get(AnimationGraph::INPUT_NODE).unwrap().clone(); + fn time_pass(&self, input: TimeState, ctx: PassContext) -> HashMap { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); - overlay_input_node - .node - .unwrap_input_mut() - .parameters - .extend_replacing_owned(params); - overlay_input_node - .node - .unwrap_input_mut() - .durations - .extend_replacing_owned(durations); + // We do not add the parameters and durations because they should already have + // been cached in the subgraph context + let input_overlay = InputOverlay::default(); - let sub_context = context.context_for_subgraph_or_insert_default(name); + let sub_context = ctx + .context + .context_for_subgraph_or_insert_default(ctx.node_id); - graph.time_pass( - AnimationGraph::OUTPUT_NODE, - path.clone(), - input.update, - sub_context, - context_tmp, - &HashMap::from([(AnimationGraph::INPUT_NODE.into(), overlay_input_node)]), - ); - - let update = sub_context - .get_times(AnimationGraph::INPUT_NODE, path) - .unwrap() - .downstream - .update; - - // TODO: Think whether we want nodes to receive separate time queries per time-dependent - // output - graph - .nodes - .get(AnimationGraph::INPUT_NODE) - .unwrap() - .node - .unwrap_input() - .time_dependent_spec - .iter() - .map(|(k, _)| (k.clone(), update)) - .collect() + graph.time_pass(input.update, sub_context, ctx.context_tmp, &input_overlay) } fn time_dependent_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - - let params = context.get_parameters(name).unwrap().upstream.clone(); - let durations = context.get_durations(name).unwrap().upstream.clone(); - - let mut overlay_input_node = graph.nodes.get(AnimationGraph::INPUT_NODE).unwrap().clone(); - - overlay_input_node - .node - .unwrap_input_mut() - .parameters - .extend_replacing_owned(params); - overlay_input_node - .node - .unwrap_input_mut() - .durations - .extend_replacing_owned(durations); - overlay_input_node - .node - .unwrap_input_mut() - .time_dependent - .extend_replacing_owned(inputs); - - let sub_context = context.context_for_subgraph_or_insert_default(name); - - graph.time_dependent_pass( - AnimationGraph::OUTPUT_NODE, - path.clone(), - sub_context, - context_tmp, - &HashMap::from([(AnimationGraph::INPUT_NODE.into(), overlay_input_node)]), - ); - - sub_context - .get_time_dependent(AnimationGraph::OUTPUT_NODE, path) - .unwrap() - .upstream - .clone() + inputs: HashMap, + ctx: PassContext, + ) -> Option { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); + + let input_overlay = InputOverlay { + time_dependent: inputs, + // We do not add the parameters and durations because they should already have + // been cached in the subgraph context + ..default() + }; + + let sub_context = ctx + .context + .context_for_subgraph_or_insert_default(ctx.node_id); + + graph.time_dependent_pass(sub_context, ctx.context_tmp, &input_overlay) } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - graph - .nodes - .get(AnimationGraph::INPUT_NODE) - .unwrap() - .node - .unwrap_input() - .parameters - .iter() - .map(|(k, v)| (k.clone(), EdgeSpec::from(v))) - .collect() + fn parameter_input_spec(&self, ctx: SpecContext) -> HashMap { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); + graph.input_parameters.clone() } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - graph - .nodes - .get(AnimationGraph::OUTPUT_NODE) - .unwrap() - .node - .unwrap_output() - .parameters - .clone() + fn parameter_output_spec(&self, ctx: SpecContext) -> HashMap { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); + graph.output_parameters.clone() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - graph - .nodes - .get(AnimationGraph::INPUT_NODE) - .unwrap() - .node - .unwrap_input() - .time_dependent_spec - .clone() + fn pose_input_spec(&self, ctx: SpecContext) -> HashSet { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); + graph.input_poses.clone() } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let graph = context_tmp.animation_graph_assets.get(&self.graph).unwrap(); - graph - .nodes - .get(AnimationGraph::OUTPUT_NODE) - .unwrap() - .node - .unwrap_output() - .time_dependent - .clone() + fn pose_output_spec(&self, ctx: SpecContext) -> bool { + let graph = ctx + .context_tmp + .animation_graph_assets + .get(&self.graph) + .unwrap(); + graph.output_pose } fn display_name(&self) -> String { diff --git a/src/nodes/loop_node.rs b/src/nodes/loop_node.rs index fb5bc82..67b7791 100644 --- a/src/nodes/loop_node.rs +++ b/src/nodes/loop_node.rs @@ -1,10 +1,11 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{DurationData, PassContext, SpecContext}; use bevy::prelude::*; -use bevy::utils::HashMap; +use bevy::utils::{HashMap, HashSet}; #[derive(Reflect, Clone, Debug, Default)] pub struct LoopNode {} @@ -25,36 +26,23 @@ impl LoopNode { impl NodeLike for LoopNode { fn parameter_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + _inputs: HashMap, + _: PassContext, + ) -> HashMap { HashMap::new() } fn duration_pass( &self, - _inputs: HashMap>, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - HashMap::from([(Self::OUTPUT.into(), None)]) + _inputs: HashMap>, + _: PassContext, + ) -> Option { + Some(None) } - fn time_pass( - &self, - input: TimeState, - name: &str, - _path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let durations = context.get_durations(name).unwrap(); - let duration = *durations.upstream.get(Self::INPUT).unwrap(); + fn time_pass(&self, input: TimeState, ctx: PassContext) -> HashMap { + let duration = ctx.duration_back(Self::INPUT); + let Some(duration) = duration else { return HashMap::from([(Self::INPUT.into(), input.update)]); }; @@ -78,56 +66,36 @@ impl NodeLike for LoopNode { fn time_dependent_pass( &self, - inputs: HashMap, - name: &str, - path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let time = context.get_times(name, path).unwrap(); - let durations = context.get_durations(name).unwrap(); - let mut in_pose_frame = inputs.get(Self::INPUT).unwrap().clone().unwrap_pose_frame(); - let time = time.downstream.time; - let duration = durations.upstream.get(Self::INPUT).unwrap(); + mut inputs: HashMap, + ctx: PassContext, + ) -> Option { + let time = ctx.time_fwd().time; + let duration = ctx.duration_back(Self::INPUT); + + let mut in_pose_frame = inputs.remove(Self::INPUT).unwrap(); if let Some(duration) = duration { - let t_extra = time.div_euclid(*duration) * duration; + let t_extra = time.div_euclid(duration) * duration; in_pose_frame.map_ts(|t| t + t_extra); } - HashMap::from([(Self::OUTPUT.into(), EdgeValue::PoseFrame(in_pose_frame))]) + Some(in_pose_frame) } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::INPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::from([Self::INPUT.into()]) } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_output_spec(&self, _: SpecContext) -> bool { + true } fn display_name(&self) -> String { diff --git a/src/nodes/speed_node.rs b/src/nodes/speed_node.rs index b4a7d24..d9fec39 100644 --- a/src/nodes/speed_node.rs +++ b/src/nodes/speed_node.rs @@ -1,8 +1,10 @@ use crate::core::animation_graph::{ - EdgePath, EdgeSpec, EdgeValue, NodeInput, NodeOutput, TimeState, TimeUpdate, + OptParamSpec, ParamSpec, ParamValue, PinId, TimeState, TimeUpdate, }; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; -use crate::core::graph_context::{GraphContext, GraphContextTmp}; +use crate::core::frame::PoseFrame; +use crate::prelude::{DurationData, PassContext, SpecContext}; +use bevy::utils::HashSet; use bevy::{reflect::Reflect, utils::HashMap}; #[derive(Reflect, Clone, Debug, Default)] @@ -25,30 +27,18 @@ impl SpeedNode { impl NodeLike for SpeedNode { fn parameter_pass( &self, - _inputs: HashMap, - _name: &str, - _path: &EdgePath, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + _: HashMap, + _: PassContext, + ) -> HashMap { HashMap::new() } fn duration_pass( &self, - inputs: HashMap>, - name: &str, - _path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap> { - let parameters = context.get_parameters(name).unwrap(); - let speed = parameters - .upstream - .get(Self::SPEED) - .unwrap() - .clone() - .unwrap_f32(); + inputs: HashMap>, + ctx: PassContext, + ) -> Option { + let speed = ctx.parameter_back(Self::SPEED).unwrap_f32(); let out_duration = if speed == 0. { None @@ -57,24 +47,11 @@ impl NodeLike for SpeedNode { duration.as_ref().map(|duration| duration / speed) }; - HashMap::from([(Self::OUTPUT.into(), out_duration)]) + Some(out_duration) } - fn time_pass( - &self, - input: TimeState, - name: &str, - _path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let parameters = context.get_parameters(name).unwrap(); - let speed = parameters - .upstream - .get(Self::SPEED) - .unwrap() - .clone() - .unwrap_f32(); + fn time_pass(&self, input: TimeState, ctx: PassContext) -> HashMap { + let speed = ctx.parameter_back(Self::SPEED).unwrap_f32(); let fw_upd = match input.update { TimeUpdate::Delta(dt) => TimeUpdate::Delta(dt * speed), TimeUpdate::Absolute(t) => TimeUpdate::Absolute(t * speed), @@ -84,58 +61,33 @@ impl NodeLike for SpeedNode { fn time_dependent_pass( &self, - inputs: HashMap, - name: &str, - _path: &EdgePath, - context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - let mut in_pose_frame = inputs.get(Self::INPUT).unwrap().clone().unwrap_pose_frame(); - let parameters = context.get_parameters(name).unwrap(); - let speed = parameters - .upstream - .get(Self::SPEED) - .unwrap() - .clone() - .unwrap_f32(); + mut inputs: HashMap, + ctx: PassContext, + ) -> Option { + let mut in_pose_frame = inputs.remove(Self::INPUT).unwrap(); + let speed = ctx.parameter_back(Self::SPEED).unwrap_f32(); if speed != 0. { in_pose_frame.map_ts(|t| t / speed); } - HashMap::from([(Self::OUTPUT.into(), EdgeValue::PoseFrame(in_pose_frame))]) + Some(in_pose_frame) } - fn parameter_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::SPEED.into(), EdgeSpec::F32)]) + fn parameter_input_spec(&self, _: SpecContext) -> HashMap { + HashMap::from([(Self::SPEED.into(), ParamSpec::F32.into())]) } - fn parameter_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { + fn parameter_output_spec(&self, _: SpecContext) -> HashMap { HashMap::new() } - fn time_dependent_input_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::INPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_input_spec(&self, _: SpecContext) -> HashSet { + HashSet::from([Self::INPUT.into()]) } - fn time_dependent_output_spec( - &self, - _context: &mut GraphContext, - _context_tmp: &mut GraphContextTmp, - ) -> HashMap { - HashMap::from([(Self::OUTPUT.into(), EdgeSpec::PoseFrame)]) + fn pose_output_spec(&self, _: SpecContext) -> bool { + true } fn display_name(&self) -> String { diff --git a/src/utils/asset_loader_error.rs b/src/utils/asset_loader_error.rs index dbdd3d1..9dc8e59 100644 --- a/src/utils/asset_loader_error.rs +++ b/src/utils/asset_loader_error.rs @@ -19,4 +19,6 @@ pub enum AssetLoaderError { LoadDirectError(#[from] bevy::asset::LoadDirectError), #[error("Animated scene path is incorrect: {0}")] AnimatedSceneMissingName(String), + #[error("Graph does not satisfy constraints: {0}")] + InconsistentGraphError(#[from] crate::core::animation_graph::GraphError), }