From 96adde5e15cfd0b5198241f9d8122440bb8dbdd5 Mon Sep 17 00:00:00 2001 From: Manuel Brea Date: Sat, 16 Mar 2024 20:32:10 +0000 Subject: [PATCH 1/4] Started with it --- .../src/bin/show_graph.rs | 72 -- .../src/core/animation_graph/core.rs | 9 +- .../src/core/animation_graph/dot_output.rs | 490 ----------- .../src/core/animation_graph/mod.rs | 2 - .../src/core/animation_node.rs | 10 +- .../src/core/context/deferred_gizmos.rs | 39 +- .../src/core/context/graph_context.rs | 12 +- .../src/core/context/pass_context.rs | 4 +- crates/bevy_animation_graph/src/core/frame.rs | 590 -------------- crates/bevy_animation_graph/src/core/mod.rs | 1 - .../bevy_animation_graph/src/core/plugin.rs | 2 +- crates/bevy_animation_graph/src/core/pose.rs | 49 +- .../src/core/space_conversion.rs | 760 +++++++++--------- .../bevy_animation_graph/src/flipping/mod.rs | 37 +- .../src/interpolation/linear.rs | 171 +--- crates/bevy_animation_graph/src/lib.rs | 2 - .../src/nodes/blend_node.rs | 7 +- .../src/nodes/chain_node.rs | 50 +- .../src/nodes/clip_node.rs | 70 +- .../src/nodes/flip_lr_node.rs | 27 +- .../src/nodes/graph_node.rs | 8 +- .../src/nodes/loop_node.rs | 10 +- .../src/nodes/rotation_node.rs | 61 +- .../nodes/space_conversion/extend_skeleton.rs | 11 +- .../src/nodes/space_conversion/into_bone.rs | 24 +- .../nodes/space_conversion/into_character.rs | 24 +- .../src/nodes/space_conversion/into_global.rs | 24 +- .../src/nodes/speed_node.rs | 10 +- .../src/nodes/twoboneik_node.rs | 85 +- .../src/sampling/linear.rs | 69 -- .../bevy_animation_graph/src/sampling/mod.rs | 5 - 31 files changed, 611 insertions(+), 2124 deletions(-) delete mode 100644 crates/bevy_animation_graph/src/bin/show_graph.rs delete mode 100644 crates/bevy_animation_graph/src/core/animation_graph/dot_output.rs delete mode 100644 crates/bevy_animation_graph/src/core/frame.rs delete mode 100644 crates/bevy_animation_graph/src/sampling/linear.rs delete mode 100644 crates/bevy_animation_graph/src/sampling/mod.rs diff --git a/crates/bevy_animation_graph/src/bin/show_graph.rs b/crates/bevy_animation_graph/src/bin/show_graph.rs deleted file mode 100644 index bef0c47..0000000 --- a/crates/bevy_animation_graph/src/bin/show_graph.rs +++ /dev/null @@ -1,72 +0,0 @@ -use bevy::{app::AppExit, prelude::*}; -use bevy_animation_graph::{core::animation_graph::ToDot, prelude::*}; -use std::env; - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() != 3 { - panic!("Usage: show_graph "); - } - - App::new() - .add_plugins(( - // TODO: Figure out the minimal set of plugins needed - // to make this work - DefaultPlugins, - AnimationGraphPlugin, - )) - .insert_resource(TargetGraph { - name: args[1].clone(), - output_path: args[2].clone(), - handle: None, - }) - .add_systems(Startup, load_graph) - .add_systems(Update, show_graph) - .run() -} - -#[derive(Resource)] -struct TargetGraph { - name: String, - output_path: String, - handle: Option>, -} - -fn load_graph(mut target_graph: ResMut, asset_server: Res) { - let handle: Handle = asset_server.load(&target_graph.name); - target_graph.handle = Some(handle); -} - -fn show_graph( - target_graph: Res, - animation_graph_assets: Res>, - _graph_clip_assets: Res>, - asset_server: Res, - mut exit: EventWriter, - sysres: SystemResources, -) { - match asset_server.recursive_dependency_load_state(target_graph.handle.as_ref().unwrap()) { - bevy::asset::RecursiveDependencyLoadState::NotLoaded => {} - bevy::asset::RecursiveDependencyLoadState::Loading => {} - bevy::asset::RecursiveDependencyLoadState::Loaded => { - info!("Graph {} loaded", target_graph.name); - let graph = animation_graph_assets - .get(target_graph.handle.as_ref().unwrap()) - .unwrap(); - - if target_graph.output_path == "-" { - graph.dot_to_stdout(None, &sysres).unwrap(); - } else { - graph - .dot_to_file(&target_graph.output_path, None, &sysres) - .unwrap(); - } - - exit.send(AppExit); - } - bevy::asset::RecursiveDependencyLoadState::Failed => { - panic!("Failed to load graph {}", target_graph.name) - } - }; -} diff --git a/crates/bevy_animation_graph/src/core/animation_graph/core.rs b/crates/bevy_animation_graph/src/core/animation_graph/core.rs index a9bd126..60f3b89 100644 --- a/crates/bevy_animation_graph/src/core/animation_graph/core.rs +++ b/crates/bevy_animation_graph/src/core/animation_graph/core.rs @@ -4,12 +4,11 @@ use crate::{ animation_node::{AnimationNode, NodeLike}, duration_data::DurationData, errors::{GraphError, GraphValidationError}, - frame::{BonePoseFrame, PoseFrame, PoseSpec}, - pose::{BoneId, Pose}, + pose::{BoneId, Pose, PoseSpec}, }, prelude::{ DeferredGizmos, GraphContext, OptParamSpec, ParamSpec, ParamValue, PassContext, - SampleLinearAt, SpecContext, SystemResources, + SpecContext, SystemResources, }, utils::{ordered_map::OrderedMap, unwrap::Unwrap}, }; @@ -104,7 +103,7 @@ impl UpdateTime> for TimeState { pub struct InputOverlay { pub parameters: HashMap, pub durations: HashMap, - pub poses: HashMap, + pub poses: HashMap, } impl InputOverlay { @@ -742,7 +741,7 @@ impl AnimationGraph { time_update: TimeUpdate, target_pin: TargetPin, mut ctx: PassContext, - ) -> Result { + ) -> Result { let Some(source_pin) = self.edges.get(&target_pin) else { return Err(GraphError::MissingInputEdge(target_pin)); }; diff --git a/crates/bevy_animation_graph/src/core/animation_graph/dot_output.rs b/crates/bevy_animation_graph/src/core/animation_graph/dot_output.rs deleted file mode 100644 index 02be74c..0000000 --- a/crates/bevy_animation_graph/src/core/animation_graph/dot_output.rs +++ /dev/null @@ -1,490 +0,0 @@ -use super::{AnimationGraph, SourcePin, TimeState, TimeUpdate}; -use crate::{ - core::{ - animation_node::NodeLike, - frame::{BoneFrame, InnerPoseFrame, PoseFrame, ValueFrame}, - }, - nodes::{ClipNode, GraphNode}, - prelude::{GraphContext, OptParamSpec, ParamSpec, ParamValue, SpecContext, SystemResources}, -}; -use bevy::{ - reflect::{FromReflect, TypePath}, - utils::HashMap, -}; -use std::{ - fs::File, - io::BufWriter, - process::{Command, Stdio}, -}; - -pub trait ToDot { - fn to_dot( - &self, - f: &mut impl std::io::Write, - context: Option<&mut GraphContext>, - context_tmp: &SystemResources, - ) -> std::io::Result<()>; - - fn preview_dot( - &self, - context: Option<&mut GraphContext>, - context_tmp: &SystemResources, - ) -> std::io::Result<()> { - let dir = std::env::temp_dir(); - let path = dir.join("bevy_animation_graph_dot.dot"); - - let file = File::create(&path)?; - let mut writer = BufWriter::new(file); - - self.to_dot(&mut writer, context, context_tmp)?; - writer.get_mut().sync_all()?; - - let dot = Command::new("dot") - .args([path.to_str().unwrap(), "-Tpdf", "-O"]) - .stdout(Stdio::piped()) - .spawn()?; - Command::new("zathura") - .args(["-"]) - .stdin(Stdio::from(dot.stdout.unwrap())) - .spawn()?; - - Ok(()) - } - - fn dot_to_tmp_file_and_open( - &self, - context: Option<&mut GraphContext>, - context_tmp: &SystemResources, - ) -> std::io::Result<()> { - self.dot_to_tmp_file(context, context_tmp)?; - - Command::new("zathura") - .args(["/tmp/bevy_animation_graph_dot.dot.pdf"]) - .spawn()?; - - Ok(()) - } - - fn dot_to_file( - &self, - path: &str, - context: Option<&mut GraphContext>, - context_tmp: &SystemResources, - ) -> std::io::Result<()> { - { - let file = File::create(path)?; - let mut writer = BufWriter::new(file); - self.to_dot(&mut writer, context, context_tmp)?; - } - - Ok(()) - } - - fn dot_to_stdout( - &self, - context: Option<&mut GraphContext>, - context_tmp: &SystemResources, - ) -> std::io::Result<()> { - { - let mut stdout = std::io::stdout(); - self.to_dot(&mut stdout, context, context_tmp)?; - } - - Ok(()) - } - - fn dot_to_tmp_file( - &self, - context: Option<&mut GraphContext>, - context_tmp: &SystemResources, - ) -> std::io::Result<()> { - let path = "/tmp/bevy_animation_graph_dot.dot"; - let pdf_path = "/tmp/bevy_animation_graph_dot.dot.pdf"; - let pdf_path_alt = "/tmp/bevy_animation_graph_dot.dot.pdf_alt"; - - { - let file = File::create(path)?; - let mut writer = BufWriter::new(file); - self.to_dot(&mut writer, context, context_tmp)?; - } - - { - let pdf_file_alt = File::create(pdf_path_alt)?; - Command::new("dot") - .args([path, "-Tpdf"]) - .stdout(pdf_file_alt) - .status()?; - - std::fs::rename(pdf_path_alt, pdf_path)?; - } - - Ok(()) - } -} - -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.spec { - ParamSpec::F32 => String::from(""), - ParamSpec::BoneMask => String::from("󰚌"), - ParamSpec::Quat => String::from("󰑵"), - ParamSpec::Vec3 => String::from("󰵉"), - ParamSpec::EntityPath => String::from("EntityPath"), - }; - - write!( - f, - "", - param_name, icon, param_name - )?; - } - write!(f, "
{} {}
")?; - } - 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, -) -> std::io::Result<()> { - write!(f, "")?; - write!(f, "")?; - write_col(f, left)?; - write!(f, "")?; - write!(f, "")?; - write_col(f, right)?; - write!(f, "")?; - write!(f, "")?; - Ok(()) -} - -fn write_rows_pose( - f: &mut impl std::io::Write, - left: HashMap, - right: HashMap, -) -> std::io::Result<()> { - write!(f, "")?; - write!(f, "")?; - write_col_pose(f, left)?; - write!(f, "")?; - write!(f, "")?; - write_col_pose(f, right)?; - write!(f, "")?; - write!(f, "")?; - Ok(()) -} - -fn write_debug_info(f: &mut impl std::io::Write, pose: PoseFrame) -> std::io::Result<()> { - write!(f, "")?; - write!(f, "")?; - if pose.verify_timestamps_in_order() { - write!(f, " Timestamps not in order
")?; - } - if pose.verify_timestamp_in_range() { - write!(f, " Timestamp not in range
")?; - } - write!(f, "")?; - write!(f, "")?; - Ok(()) -} - -pub trait AsDotLabel { - fn as_dot_label(&self) -> String; -} - -impl AsDotLabel for ParamValue { - fn as_dot_label(&self) -> String { - match self { - ParamValue::F32(f) => format!("{:.3}", f), - ParamValue::Quat(q) => format!("{}", q), - ParamValue::BoneMask(_) => "Bone Mask".to_string(), - ParamValue::Vec3(v) => format!("{}", v), - ParamValue::EntityPath(_) => "EntityPath".to_string(), - } - } -} - -impl AsDotLabel for InnerPoseFrame { - fn as_dot_label(&self) -> String { - self.bones - .iter() - .map(|b| b.as_dot_label()) - .collect::>() - .join("
") - } -} - -impl AsDotLabel for BoneFrame { - fn as_dot_label(&self) -> String { - self.rotation - .as_ref() - .map_or("".into(), |r| r.as_dot_label()) - } -} - -impl AsDotLabel for ValueFrame { - fn as_dot_label(&self) -> String { - format!("{:.3}<->{:.3}", self.prev_timestamp, self.next_timestamp) - } -} - -impl AsDotLabel for Option { - fn as_dot_label(&self) -> String { - format!("{:?}", self) - } -} - -impl AsDotLabel for f32 { - fn as_dot_label(&self) -> String { - format!("{:.3}", self) - } -} - -impl AsDotLabel for TimeUpdate { - fn as_dot_label(&self) -> String { - match self { - TimeUpdate::Delta(dt) => format!("Δt({:.3})", dt), - TimeUpdate::Absolute(t) => format!("t🡠{:.3}", t), - } - } -} - -impl AsDotLabel for TimeState { - fn as_dot_label(&self) -> String { - format!("{:.3} after {}", self.time, self.update.as_dot_label()) - } -} - -impl ToDot for AnimationGraph { - fn to_dot( - &self, - f: &mut impl std::io::Write, - mut context: Option<&mut GraphContext>, - context_tmp: &SystemResources, - ) -> 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 ctx = if let Some(context) = &mut context { - context - } else { - &mut default_graph_context - }; - - for (name, node) in self.nodes.iter() { - write!( - f, - "\t\"{}\" [label=<", - name - )?; - write!( - f, - "",)?; - - let in_param = - node.parameter_input_spec(SpecContext::new(&context_tmp.animation_graph_assets)); - let out_param = - node.parameter_output_spec(SpecContext::new(&context_tmp.animation_graph_assets)); - - let in_td = node.pose_input_spec(SpecContext::new(&context_tmp.animation_graph_assets)); - let out_td = - node.pose_output_spec(SpecContext::new(&context_tmp.animation_graph_assets)); - - write_rows( - f, - in_param.into_iter().collect(), - out_param.into_iter().map(|(k, v)| (k, v.into())).collect(), - )?; - - let mut right = HashMap::new(); - if out_td.is_some() { - right.insert("POSE".into(), ()); - } - - write_rows_pose(f, in_td.into_iter().map(|(k, _)| (k, ())).collect(), right)?; - - if let Some(frame) = ctx.get_pose(&SourcePin::NodePose(name.clone())) { - write_debug_info(f, frame.clone())?; - } - - writeln!(f, "
{}
{}", - name, - node.display_name() - )?; - - match &node.node { - crate::core::animation_node::AnimationNodeType::Clip(ClipNode { clip, .. }) => { - write!( - f, - "
{}

", - clip.path().unwrap() - )?; - } - crate::core::animation_node::AnimationNodeType::Graph(GraphNode { - graph, .. - }) => { - write!( - f, - "
{}

", - graph.path().unwrap() - )?; - } - _ => {} - }; - write!(f, "
>]")?; - } - - // --- Input parameters node - // -------------------------------------------------------- - let name = "INPUT PARAMETERS"; - write!( - f, - "\t\"{}\" [label=<", - name - )?; - write!(f, "",)?; - let out_param = self - .default_parameters - .iter() - .map(|(k, v)| (k.into(), v.into())) - .collect(); - 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.is_some() { - out.insert("POSE".into(), ()); - } - write_rows_pose(f, out, HashMap::new())?; - writeln!(f, "
{}", name)?; - write!(f, "
>]")?; - // -------------------------------------------------------- - - for (target_pin, source_pin) in self.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!( - f, - "\t\"{}\":\"{}\" -> \"{}\":\"{}\" [color={}", - start_node, start_edge, end_node, end_edge, color - )?; - - if let Some(context) = context.as_ref() { - let time = context.get_prev_time(source_pin); - - writeln!(f, "label=\"{}\"", time)?; - } - - writeln!(f, "];")?; - } - - writeln!(f, "}}")?; - - Ok(()) - } -} diff --git a/crates/bevy_animation_graph/src/core/animation_graph/mod.rs b/crates/bevy_animation_graph/src/core/animation_graph/mod.rs index 14c4f76..06795a8 100644 --- a/crates/bevy_animation_graph/src/core/animation_graph/mod.rs +++ b/crates/bevy_animation_graph/src/core/animation_graph/mod.rs @@ -1,8 +1,6 @@ mod core; -mod dot_output; pub mod loader; mod pin; pub mod serial; pub use core::*; -pub use dot_output::*; diff --git a/crates/bevy_animation_graph/src/core/animation_node.rs b/crates/bevy_animation_graph/src/core/animation_node.rs index 9fcc053..a842d33 100644 --- a/crates/bevy_animation_graph/src/core/animation_node.rs +++ b/crates/bevy_animation_graph/src/core/animation_node.rs @@ -2,8 +2,8 @@ use super::{ animation_graph::{PinId, PinMap, TimeUpdate}, duration_data::DurationData, errors::GraphError, - frame::{PoseFrame, PoseSpec}, parameters::{OptParamSpec, ParamSpec, ParamValue}, + pose::{Pose, PoseSpec}, }; use crate::{ nodes::{ @@ -33,7 +33,7 @@ pub trait NodeLike: Send + Sync + Reflect { &self, _time_update: TimeUpdate, _ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { Ok(None) } @@ -112,11 +112,7 @@ impl NodeLike for AnimationNode { self.node.map(|n| n.duration_pass(ctx)) } - fn pose_pass( - &self, - input: TimeUpdate, - ctx: PassContext, - ) -> Result, GraphError> { + fn pose_pass(&self, input: TimeUpdate, ctx: PassContext) -> Result, GraphError> { self.node.map(|n| n.pose_pass(input, ctx)) } diff --git a/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs b/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs index 5bdc6c1..fc7c737 100644 --- a/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs +++ b/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs @@ -1,5 +1,9 @@ use super::PassContext; -use crate::core::{frame::InnerPoseFrame, pose::BoneId, space_conversion::SpaceConversion}; +use crate::core::{ + frame::InnerPoseFrame, + pose::{BoneId, Pose}, + space_conversion::SpaceConversion, +}; use bevy::{ gizmos::gizmos::Gizmos, math::{Quat, Vec3}, @@ -104,13 +108,8 @@ pub trait BoneDebugGizmos { fn will_draw(&self) -> bool; fn gizmo(&mut self, gizmo: DeferredGizmoCommand); - fn pose_bone_gizmos(&mut self, color: Color, inner_pose: &InnerPoseFrame, timestamp: f32); - fn bone_gizmo( - &mut self, - bone_id: BoneId, - color: Color, - inner_pose: Option<(&InnerPoseFrame, f32)>, - ); + fn pose_bone_gizmos(&mut self, color: Color, pose: &Pose); + fn bone_gizmo(&mut self, bone_id: BoneId, color: Color, pose: Option<&Pose>); fn bone_sphere(&mut self, bone_id: BoneId, radius: f32, color: Color); fn bone_rays(&mut self, bone_id: BoneId); fn sphere_in_parent_bone_space( @@ -141,37 +140,29 @@ impl BoneDebugGizmos for PassContext<'_> { } } - fn pose_bone_gizmos(&mut self, color: Color, inner_pose: &InnerPoseFrame, timestamp: f32) { + fn pose_bone_gizmos(&mut self, color: Color, pose: &Pose) { if !self.will_draw() { return; } - for bone_path in inner_pose.paths.keys() { - self.bone_gizmo(bone_path.clone(), color, Some((inner_pose, timestamp))); + for bone_path in pose.paths.keys() { + self.bone_gizmo(bone_path.clone(), color, Some(pose)); } } - fn bone_gizmo( - &mut self, - bone_id: BoneId, - color: Color, - inner_pose: Option<(&InnerPoseFrame, f32)>, - ) { + fn bone_gizmo(&mut self, bone_id: BoneId, color: Color, pose: Option<&Pose>) { if !self.will_draw() { return; } - let default_pose = InnerPoseFrame::default(); - let (inner_pose, timestamp) = match inner_pose { - Some((pose, time)) => (pose, time), - None => (&default_pose, 0.), - }; + let default_pose = Pose::default(); + let pose = pose.unwrap_or(&default_pose); let Some(parent_id) = bone_id.parent() else { return; }; - let global_bone_transform = self.global_transform_of_bone(inner_pose, bone_id, timestamp); - let parent_bone_transform = self.global_transform_of_bone(inner_pose, parent_id, timestamp); + let global_bone_transform = self.global_transform_of_bone(pose, bone_id); + let parent_bone_transform = self.global_transform_of_bone(pose, parent_id); self.gizmo(DeferredGizmoCommand::Bone( parent_bone_transform.translation, global_bone_transform.translation, diff --git a/crates/bevy_animation_graph/src/core/context/graph_context.rs b/crates/bevy_animation_graph/src/core/context/graph_context.rs index 4c85fff..5c57cfa 100644 --- a/crates/bevy_animation_graph/src/core/context/graph_context.rs +++ b/crates/bevy_animation_graph/src/core/context/graph_context.rs @@ -2,7 +2,7 @@ use crate::{ core::{ animation_graph::{SourcePin, TimeUpdate}, duration_data::DurationData, - frame::PoseFrame, + pose::Pose, }, prelude::ParamValue, }; @@ -15,7 +15,7 @@ pub struct OutputCache { pub parameters: HashMap, pub durations: HashMap, pub time_updates: HashMap, - pub poses: HashMap, + pub poses: HashMap, } #[derive(Reflect, Debug, Default)] @@ -147,11 +147,11 @@ impl OutputCaches { self.get_cache_mut().time_updates.insert(source_pin, value) } - pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&PoseFrame> { + pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&Pose> { self.get_cache().poses.get(source_pin) } - pub fn set_pose(&mut self, source_pin: SourcePin, value: PoseFrame) -> Option { + pub fn set_pose(&mut self, source_pin: SourcePin, value: Pose) -> Option { self.get_cache_mut().poses.insert(source_pin, value) } } @@ -218,11 +218,11 @@ impl GraphContext { self.times.set_curr(source_pin, value); } - pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&PoseFrame> { + pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&Pose> { self.outputs.get_pose(source_pin) } - pub fn set_pose(&mut self, source_pin: SourcePin, value: PoseFrame) -> Option { + pub fn set_pose(&mut self, source_pin: SourcePin, value: Pose) -> Option { self.outputs.set_pose(source_pin, value) } diff --git a/crates/bevy_animation_graph/src/core/context/pass_context.rs b/crates/bevy_animation_graph/src/core/context/pass_context.rs index 4ea2b08..3bd9792 100644 --- a/crates/bevy_animation_graph/src/core/context/pass_context.rs +++ b/crates/bevy_animation_graph/src/core/context/pass_context.rs @@ -6,7 +6,7 @@ use crate::{ duration_data::DurationData, errors::GraphError, frame::PoseFrame, - pose::BoneId, + pose::{BoneId, Pose}, }, prelude::{AnimationGraph, ParamValue}, }; @@ -158,7 +158,7 @@ impl<'a> PassContext<'a> { &mut self, pin_id: impl Into, time_update: TimeUpdate, - ) -> Result { + ) -> Result { let node_ctx = self.node_context.unwrap(); let target_pin = TargetPin::NodePose(node_ctx.node_id.clone(), pin_id.into()); node_ctx diff --git a/crates/bevy_animation_graph/src/core/frame.rs b/crates/bevy_animation_graph/src/core/frame.rs deleted file mode 100644 index 1da5433..0000000 --- a/crates/bevy_animation_graph/src/core/frame.rs +++ /dev/null @@ -1,590 +0,0 @@ -use super::animation_clip::EntityPath; -use crate::{ - prelude::{InterpolateLinear, SampleLinearAt}, - utils::unwrap::Unwrap, -}; -use bevy::{ - asset::prelude::*, math::prelude::*, reflect::prelude::*, transform::components::Transform, - utils::HashMap, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Asset, Reflect, Clone, Default, PartialEq)] -pub struct ValueFrame { - pub(crate) prev: T, - pub(crate) prev_timestamp: f32, - pub(crate) next: T, - pub(crate) next_timestamp: f32, - pub(crate) prev_is_wrapped: bool, - pub(crate) next_is_wrapped: bool, -} - -impl ValueFrame { - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - self.prev_timestamp = f(self.prev_timestamp); - self.next_timestamp = f(self.next_timestamp); - } - - /// Maps the `prev` and `next` values of the frame - /// using the given function - pub fn map(&self, f: F) -> ValueFrame - where - Q: FromReflect + TypePath, - F: Fn(&T) -> Q, - { - ValueFrame { - prev: f(&self.prev), - prev_timestamp: self.prev_timestamp, - next: f(&self.next), - next_timestamp: self.next_timestamp, - prev_is_wrapped: self.prev_is_wrapped, - next_is_wrapped: self.next_is_wrapped, - } - } - - /// Mutates the `prev` and `next` values of the frame - /// using the given function - pub fn map_mut(&mut self, f: F) - where - F: Fn(&T) -> T, - { - self.prev = f(&self.prev); - self.next = f(&self.next); - } - - /// Returns a new frame where `prev_timestamp` is the maximum of `self.prev_timestamp` - /// and `other.prev_timestamp`, and `next_timestamp` is the minimum of `self.next_timestamp` - /// and `other.next_timestamp`. Both frames are sampled at the chosen timestamps for either - /// end using the given sampler and combined using the given combiner function. - pub fn merge( - &self, - other: &ValueFrame, - sampler_left: SLeft, - sampler_right: SRight, - combiner: F, - ) -> ValueFrame - where - B: FromReflect + TypePath, - C: FromReflect + TypePath, - SLeft: Fn(&Self, f32) -> T, - SRight: Fn(&ValueFrame, f32) -> B, - F: Fn(&T, &B) -> C, - { - let (prev_timestamp, prev, prev_is_wrapped) = if self.prev_timestamp >= other.prev_timestamp - { - let ts = self.prev_timestamp; - let other_prev = sampler_right(other, ts); - ( - ts, - combiner(&self.prev, &other_prev), - self.prev_is_wrapped && other.prev_is_wrapped, - ) - } else { - let ts = other.prev_timestamp; - let self_prev = sampler_left(self, ts); - ( - ts, - combiner(&self_prev, &other.prev), - self.prev_is_wrapped && other.prev_is_wrapped, - ) - }; - - let (next_timestamp, next, next_is_wrapped) = if self.next_timestamp <= other.next_timestamp - { - let ts = self.next_timestamp; - let other_next = sampler_right(other, ts); - ( - ts, - combiner(&self.next, &other_next), - self.next_is_wrapped && other.next_is_wrapped, - ) - } else { - let ts = other.next_timestamp; - let self_next = sampler_left(self, ts); - ( - ts, - combiner(&self_next, &other.next), - self.next_is_wrapped && other.next_is_wrapped, - ) - }; - - ValueFrame { - prev, - prev_timestamp, - next, - next_timestamp, - prev_is_wrapped, - next_is_wrapped, - } - } - - /// Returns a new frame where `prev_timestamp` is the maximum of `self.prev_timestamp` - /// and `other.prev_timestamp`, and `next_timestamp` is the minimum of `self.next_timestamp` - /// and `other.next_timestamp`. Both frames are sampled linearly at the chosen timestamps for either - /// end and combined using the given combiner function. - pub fn merge_linear(&self, other: &ValueFrame, combiner: F) -> ValueFrame - where - T: InterpolateLinear, - B: FromReflect + TypePath + InterpolateLinear, - C: FromReflect + TypePath, - F: Fn(&T, &B) -> C, - { - self.merge( - other, - ValueFrame::::sample_linear_at, - ValueFrame::::sample_linear_at, - combiner, - ) - } -} - -#[derive(Asset, Reflect, Clone, Default)] -pub struct BoneFrame { - pub(crate) rotation: Option>, - pub(crate) translation: Option>, - pub(crate) scale: Option>, - pub(crate) weights: Option>>, -} - -impl BoneFrame { - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - if let Some(v) = self.rotation.as_mut() { - v.map_ts(&f) - }; - if let Some(v) = self.translation.as_mut() { - v.map_ts(&f) - }; - if let Some(v) = self.scale.as_mut() { - v.map_ts(&f) - }; - if let Some(v) = self.weights.as_mut() { - v.map_ts(&f) - }; - } - - pub fn to_transform_frame_linear(&self) -> ValueFrame { - let transform_frame = ValueFrame { - prev: Transform::IDENTITY, - prev_timestamp: f32::MIN, - next: Transform::IDENTITY, - next_timestamp: f32::MAX, - prev_is_wrapped: true, - next_is_wrapped: true, - }; - - self.to_transform_frame_linear_with_base_frame(transform_frame) - } - - pub fn to_transform_frame_linear_with_base_frame( - &self, - base_frame: ValueFrame, - ) -> ValueFrame { - let mut transform_frame = base_frame; - if let Some(translation_frame) = &self.translation { - transform_frame = - transform_frame.merge_linear(translation_frame, |transform, translation| { - Transform { - translation: *translation, - ..*transform - } - }); - } - - if let Some(rotation_frame) = &self.rotation { - transform_frame = - transform_frame.merge_linear(rotation_frame, |transform, rotation| Transform { - rotation: *rotation, - ..*transform - }); - } - - if let Some(scale_frame) = &self.scale { - transform_frame = - transform_frame.merge_linear(scale_frame, |transform, scale| Transform { - scale: *scale, - ..*transform - }); - } - - transform_frame - } - - pub fn to_transform_frame_linear_with_base(&self, base: Transform) -> ValueFrame { - let transform_frame = ValueFrame { - prev: base, - prev_timestamp: f32::MIN, - next: base, - next_timestamp: f32::MAX, - prev_is_wrapped: true, - next_is_wrapped: true, - }; - - self.to_transform_frame_linear_with_base_frame(transform_frame) - } - - pub fn to_transform_linear_with_base(&self, mut base: Transform, timestamp: f32) -> Transform { - if let Some(translation_frame) = &self.translation { - base.translation = translation_frame.sample_linear_at(timestamp); - } - if let Some(rotation_frame) = &self.rotation { - base.rotation = rotation_frame.sample_linear_at(timestamp); - } - - if let Some(scale_frame) = &self.scale { - base.scale = scale_frame.sample_linear_at(timestamp); - } - - base - } -} - -#[derive(Asset, Reflect, Clone, Default)] -pub struct InnerPoseFrame { - pub(crate) bones: Vec, - pub(crate) paths: HashMap, -} - -/// Pose frame where each transform is local with respect to the parent bone -// TODO: Verify that transforms are wrt parent bone -#[derive(Reflect, Clone, Default, Debug)] -pub struct BonePoseFrame(pub(crate) InnerPoseFrame); -/// Pose frame where each transform is relative to the root of the skeleton -#[derive(Reflect, Clone, Default, Debug)] -pub struct CharacterPoseFrame(pub(crate) InnerPoseFrame); -/// Pose frame where each transform is in world/global space -#[derive(Reflect, Clone, Default, Debug)] -pub struct GlobalPoseFrame(pub(crate) InnerPoseFrame); - -impl From for BonePoseFrame { - fn from(value: InnerPoseFrame) -> Self { - Self(value) - } -} - -impl From for CharacterPoseFrame { - fn from(value: InnerPoseFrame) -> Self { - Self(value) - } -} - -impl From for GlobalPoseFrame { - fn from(value: InnerPoseFrame) -> Self { - Self(value) - } -} - -impl BonePoseFrame { - pub fn inner_ref(&self) -> &InnerPoseFrame { - &self.0 - } - - pub fn inner_mut(&mut self) -> &mut InnerPoseFrame { - &mut self.0 - } - - pub fn inner(self) -> InnerPoseFrame { - self.0 - } - - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - self.inner_mut().map_ts(f) - } -} - -impl CharacterPoseFrame { - pub fn inner_ref(&self) -> &InnerPoseFrame { - &self.0 - } - - pub fn inner_mut(&mut self) -> &mut InnerPoseFrame { - &mut self.0 - } - - pub fn inner(self) -> InnerPoseFrame { - self.0 - } - - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - self.inner_mut().map_ts(f) - } -} - -impl GlobalPoseFrame { - pub fn inner_ref(&self) -> &InnerPoseFrame { - &self.0 - } - - pub fn inner_mut(&mut self) -> &mut InnerPoseFrame { - &mut self.0 - } - - pub fn inner(self) -> InnerPoseFrame { - self.0 - } - - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - self.inner_mut().map_ts(f) - } -} - -impl InnerPoseFrame { - /// Adds a new bone frame to the pose frame, possibly replacing an existing bone frame. - pub(crate) fn add_bone(&mut self, frame: BoneFrame, path: EntityPath) { - let id = self.bones.len(); - self.bones.insert(id, frame); - self.paths.insert(path, id); - } - - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - self.bones.iter_mut().for_each(|v| v.map_ts(&f)); - } - - pub(crate) fn verify_timestamp_in_range(&self, timestamp: f32) -> bool { - let mut failed = false; - - for bone in self.bones.iter() { - if let Some(v) = &bone.translation { - if !(v.prev_timestamp <= timestamp && timestamp <= v.next_timestamp) { - failed = true; - } - } - if let Some(v) = &bone.rotation { - if !(v.prev_timestamp <= timestamp && timestamp <= v.next_timestamp) { - failed = true; - } - } - if let Some(v) = &bone.scale { - if !(v.prev_timestamp <= timestamp && timestamp <= v.next_timestamp) { - failed = true; - } - } - if let Some(v) = &bone.weights { - if !(v.prev_timestamp <= timestamp && timestamp <= v.next_timestamp) { - failed = true; - } - } - } - - failed - } - - pub(crate) fn verify_timestamps_in_order(&self) -> bool { - let mut failed = false; - - for bone in self.bones.iter() { - if let Some(v) = &bone.translation { - if v.prev_timestamp > v.next_timestamp { - failed = true; - } - } - if let Some(v) = &bone.rotation { - if v.prev_timestamp > v.next_timestamp { - failed = true; - } - } - if let Some(v) = &bone.scale { - if v.prev_timestamp > v.next_timestamp { - failed = true; - } - } - if let Some(v) = &bone.weights { - if v.prev_timestamp > v.next_timestamp { - failed = true; - } - } - } - - failed - } -} - -impl std::fmt::Debug for ValueFrame { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{:?} <--> {:?}", - self.prev_timestamp, self.next_timestamp - ) - } -} - -impl std::fmt::Debug for BoneFrame { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "\tBone:")?; - if let Some(v) = &self.translation { - writeln!(f, "\t\ttranslation: {:?}", v)?; - } - if let Some(v) = &self.rotation { - writeln!(f, "\t\trotation: {:?}", v)?; - } - if let Some(v) = &self.scale { - writeln!(f, "\t\tscale: {:?}", v)?; - } - if let Some(v) = &self.weights { - writeln!(f, "\t\tweight: {:?}", v)?; - } - - Ok(()) - } -} - -impl std::fmt::Debug for InnerPoseFrame { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for bone in self.bones.iter() { - write!(f, "{:?}", bone)?; - } - Ok(()) - } -} - -#[derive(Clone, Reflect, Debug, Default)] -pub struct PoseFrame { - pub data: PoseFrameData, - pub timestamp: f32, -} - -#[derive(Clone, Reflect, Debug)] -pub enum PoseFrameData { - BoneSpace(BonePoseFrame), - CharacterSpace(CharacterPoseFrame), - GlobalSpace(GlobalPoseFrame), -} - -impl Default for PoseFrameData { - fn default() -> Self { - Self::BoneSpace(BonePoseFrame::default()) - } -} - -impl PoseFrameData { - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - match self { - PoseFrameData::BoneSpace(data) => data.map_ts(f), - PoseFrameData::CharacterSpace(data) => data.map_ts(f), - PoseFrameData::GlobalSpace(data) => data.map_ts(f), - } - } -} - -impl PoseFrame { - pub fn map_ts(&mut self, f: F) - where - F: Fn(f32) -> f32, - { - self.data.map_ts(&f); - self.timestamp = f(self.timestamp); - } - - pub(crate) fn verify_timestamp_in_range(&self) -> bool { - let inner = match &self.data { - PoseFrameData::BoneSpace(data) => data.inner_ref(), - PoseFrameData::CharacterSpace(data) => data.inner_ref(), - PoseFrameData::GlobalSpace(data) => data.inner_ref(), - }; - - inner.verify_timestamp_in_range(self.timestamp) - } - - pub(crate) fn verify_timestamps_in_order(&self) -> bool { - let inner = match &self.data { - PoseFrameData::BoneSpace(data) => data.inner_ref(), - PoseFrameData::CharacterSpace(data) => data.inner_ref(), - PoseFrameData::GlobalSpace(data) => data.inner_ref(), - }; - - inner.verify_timestamps_in_order() - } -} - -#[derive(Clone, Copy, Debug, Reflect, Default, Serialize, Deserialize, PartialEq, Eq)] -#[reflect(Default)] -pub enum PoseSpec { - #[default] - BoneSpace, - CharacterSpace, - GlobalSpace, - Any, -} - -impl PoseSpec { - pub fn compatible(&self, other: &Self) -> bool { - if self == other { - true - } else { - matches!((self, other), (Self::Any, _) | (_, Self::Any)) - } - } -} - -impl From<&PoseFrameData> for PoseSpec { - fn from(value: &PoseFrameData) -> Self { - match value { - PoseFrameData::BoneSpace(_) => PoseSpec::BoneSpace, - PoseFrameData::CharacterSpace(_) => PoseSpec::CharacterSpace, - PoseFrameData::GlobalSpace(_) => PoseSpec::GlobalSpace, - } - } -} - -impl From<&PoseFrame> for PoseSpec { - fn from(value: &PoseFrame) -> Self { - (&value.data).into() - } -} - -impl Unwrap for PoseFrameData { - fn unwrap(self) -> BonePoseFrame { - match self { - PoseFrameData::BoneSpace(b) => b, - x => panic!( - "Found {:?}, expected pose in bone space", - PoseSpec::from(&x) - ), - } - } -} - -impl Unwrap for PoseFrameData { - fn unwrap(self) -> CharacterPoseFrame { - match self { - PoseFrameData::CharacterSpace(b) => b, - x => panic!( - "Found {:?}, expected pose in character space", - PoseSpec::from(&x) - ), - } - } -} - -impl Unwrap for PoseFrameData { - fn unwrap(self) -> GlobalPoseFrame { - match self { - PoseFrameData::GlobalSpace(b) => b, - x => panic!( - "Found {:?}, expected pose in character space", - PoseSpec::from(&x) - ), - } - } -} diff --git a/crates/bevy_animation_graph/src/core/mod.rs b/crates/bevy_animation_graph/src/core/mod.rs index 3302c37..3c0d877 100644 --- a/crates/bevy_animation_graph/src/core/mod.rs +++ b/crates/bevy_animation_graph/src/core/mod.rs @@ -7,7 +7,6 @@ pub mod caches; pub mod context; pub mod duration_data; pub mod errors; -pub mod frame; pub mod parameters; pub mod plugin; pub mod pose; diff --git a/crates/bevy_animation_graph/src/core/plugin.rs b/crates/bevy_animation_graph/src/core/plugin.rs index 4bb7662..6e0d96c 100644 --- a/crates/bevy_animation_graph/src/core/plugin.rs +++ b/crates/bevy_animation_graph/src/core/plugin.rs @@ -3,8 +3,8 @@ use super::{ process_animated_scenes, spawn_animated_scenes, AnimatedScene, AnimatedSceneLoader, }, animation_graph::loader::{AnimationGraphLoader, GraphClipLoader}, - frame::PoseSpec, parameters::{BoneMask, ParamSpec, ParamValue}, + pose::PoseSpec, systems::{animation_player, animation_player_deferred_gizmos}, }; use crate::prelude::{ diff --git a/crates/bevy_animation_graph/src/core/pose.rs b/crates/bevy_animation_graph/src/core/pose.rs index 20dbe49..263778b 100644 --- a/crates/bevy_animation_graph/src/core/pose.rs +++ b/crates/bevy_animation_graph/src/core/pose.rs @@ -1,6 +1,8 @@ -use bevy::{asset::prelude::*, math::prelude::*, reflect::prelude::*, utils::HashMap}; - use super::animation_clip::EntityPath; +use bevy::{ + asset::prelude::*, math::prelude::*, reflect::prelude::*, transform::prelude::*, utils::HashMap, +}; +use serde::{Deserialize, Serialize}; // PERF: Bone ids should become integers eventually pub type BoneId = EntityPath; @@ -17,6 +19,28 @@ pub struct BonePose { pub(crate) weights: Option>, } +impl BonePose { + pub fn to_transform(&self) -> Transform { + self.to_transform_with_base(Transform::default()) + } + + pub fn to_transform_with_base(&self, mut base: Transform) -> Transform { + if let Some(translation) = &self.translation { + base.translation = *translation; + } + + if let Some(rotation) = &self.rotation { + base.rotation = *rotation; + } + + if let Some(scale) = &self.scale { + base.scale = *scale; + } + + base + } +} + /// Vertical slice of an [`GraphClip`] /// /// [`GraphClip`]: crate::prelude::GraphClip @@ -24,6 +48,7 @@ pub struct BonePose { pub struct Pose { pub(crate) bones: Vec, pub(crate) paths: HashMap, + pub(crate) timestamp: f32, } impl Pose { @@ -33,3 +58,23 @@ impl Pose { self.paths.insert(path, id); } } + +#[derive(Clone, Copy, Debug, Reflect, Default, Serialize, Deserialize, PartialEq, Eq)] +#[reflect(Default)] +pub enum PoseSpec { + #[default] + BoneSpace, + CharacterSpace, + GlobalSpace, + Any, +} + +impl PoseSpec { + pub fn compatible(&self, other: &Self) -> bool { + if self == other { + true + } else { + matches!((self, other), (Self::Any, _) | (_, Self::Any)) + } + } +} diff --git a/crates/bevy_animation_graph/src/core/space_conversion.rs b/crates/bevy_animation_graph/src/core/space_conversion.rs index 9527b69..f7940cf 100644 --- a/crates/bevy_animation_graph/src/core/space_conversion.rs +++ b/crates/bevy_animation_graph/src/core/space_conversion.rs @@ -1,21 +1,18 @@ use super::{ animation_clip::EntityPath, context::PassContext, - frame::{ - BoneFrame, BonePoseFrame, CharacterPoseFrame, GlobalPoseFrame, InnerPoseFrame, ValueFrame, - }, - pose::BoneId, + pose::{BoneId, BonePose, Pose}, }; use bevy::{ecs::entity::Entity, transform::components::Transform, utils::HashMap}; use std::collections::VecDeque; pub trait SpaceConversion { - fn bone_to_character(&self, data: &BonePoseFrame) -> CharacterPoseFrame; - fn bone_to_global(&self, data: &BonePoseFrame) -> GlobalPoseFrame; - fn character_to_bone(&self, data: &CharacterPoseFrame) -> BonePoseFrame; - fn character_to_global(&self, data: &CharacterPoseFrame) -> GlobalPoseFrame; - fn global_to_bone(&self, data: &GlobalPoseFrame) -> BonePoseFrame; - fn global_to_character(&self, data: &GlobalPoseFrame) -> CharacterPoseFrame; + // fn bone_to_character(&self, data: &BonePoseFrame) -> CharacterPoseFrame; + // fn bone_to_global(&self, data: &BonePoseFrame) -> GlobalPoseFrame; + // fn character_to_bone(&self, data: &CharacterPoseFrame) -> BonePoseFrame; + // fn character_to_global(&self, data: &CharacterPoseFrame) -> GlobalPoseFrame; + // fn global_to_bone(&self, data: &GlobalPoseFrame) -> BonePoseFrame; + // fn global_to_character(&self, data: &GlobalPoseFrame) -> CharacterPoseFrame; /// Given a transform in a space relative to a given bone, convert it into a space /// relative to a descendant bone. @@ -27,10 +24,9 @@ pub trait SpaceConversion { fn change_bone_space_down( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space source: BoneId, target: BoneId, - timestamp: f32, ) -> Transform; /// Given a transform in a space relative to a given bone, convert it into a space @@ -43,10 +39,9 @@ pub trait SpaceConversion { fn change_bone_space_up( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space source: BoneId, target: BoneId, - timestamp: f32, ) -> Transform; /// Given a transform in a space relative to the root bone, convert it into a space @@ -59,17 +54,15 @@ pub trait SpaceConversion { fn root_to_bone_space( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space target: BoneId, - timestamp: f32, ) -> Transform; fn global_to_bone_space( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space target: BoneId, - timestamp: f32, ) -> Transform; fn transform_global_to_character(&self, transform: Transform) -> Transform; @@ -77,362 +70,362 @@ pub trait SpaceConversion { /// Returns transform of bone in character space fn character_transform_of_bone( &self, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space target: BoneId, - timestamp: f32, ) -> Transform; /// Returns transform of bone in character space fn global_transform_of_bone( &self, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space target: BoneId, - timestamp: f32, ) -> Transform; - fn extend_skeleton_bone(&self, data: &BonePoseFrame) -> BonePoseFrame; + /// Extends the skeleton to include all bones in the hierarchy + /// + /// ### Important + /// **The pose should be in bone space** + fn extend_skeleton_bone(&self, data: &Pose) -> Pose; } impl SpaceConversion for PassContext<'_> { - fn bone_to_character(&self, data: &BonePoseFrame) -> CharacterPoseFrame { - let root_name = self.resources.names_query.get(self.root_entity).unwrap(); - let root_path = EntityPath { - parts: vec![root_name.clone()], - }; - let root_transform_frame = ValueFrame { - prev: Transform::IDENTITY, - prev_timestamp: f32::MIN, - next: Transform::IDENTITY, - next_timestamp: f32::MAX, - prev_is_wrapped: true, - next_is_wrapped: true, - }; - - let root_children = self.resources.children_query.get(self.root_entity).unwrap(); - - let mut character_transforms: HashMap> = HashMap::new(); - let mut queue: VecDeque<(Entity, EntityPath, ValueFrame)> = VecDeque::new(); - - for child in root_children { - queue.push_back((*child, root_path.clone(), root_transform_frame.clone())); - } - - while !queue.is_empty() { - let (entity, parent_path, parent_transform_frame) = queue.pop_front().unwrap(); - // --- Compute the updated transform frame - // ------------------------------------------------------- - // First, build the entity path for the current entity - let entity_name = self.resources.names_query.get(entity).unwrap(); - let entity_path = parent_path.child(entity_name.clone()); - - // Get the entity's current local transform - let (entity_transform, _) = self.resources.transform_query.get(entity).unwrap(); - let inner_data = data.inner_ref(); - // Get the corresponding bone frame - let bone_frame: BoneFrame = if inner_data.paths.contains_key(&entity_path) { - let bone_id = inner_data.paths.get(&entity_path).unwrap(); - inner_data.bones[*bone_id].clone() - } else { - BoneFrame::default() - }; - - // Obtain a merged local transform frame - let local_transform_frame = ValueFrame { - prev: *entity_transform, - prev_timestamp: f32::MIN, - next: *entity_transform, - next_timestamp: f32::MAX, - prev_is_wrapped: true, - next_is_wrapped: true, - }; - let local_transform_frame = - bone_frame.to_transform_frame_linear_with_base_frame(local_transform_frame); - - let character_transform_frame = parent_transform_frame - .merge_linear(&local_transform_frame, |parent, child| *child * *parent); - character_transforms.insert(entity_path.clone(), character_transform_frame.clone()); - - if let Ok(children) = self.resources.children_query.get(entity) { - for child in children { - queue.push_back(( - *child, - entity_path.clone(), - character_transform_frame.clone(), - )); - } - } - // ------------------------------------------------------- - } - - // --- Build character pose frame - // --- - // --- This involves building a bone frame for each bone - // --- frame in the existing data using the computed - // --- character transforms - // ------------------------------------------------------- - let mut final_pose_frame = CharacterPoseFrame::default(); - let inner_character_frame = final_pose_frame.inner_mut(); - - for (path, bone_id) in data.inner_ref().paths.iter() { - let local_bone_frame = &data.inner_ref().bones[*bone_id]; - let character_transform_frame = character_transforms.get(path).unwrap(); - let character_translation_frame = character_transform_frame.map(|t| t.translation); - let character_rotation_frame = character_transform_frame.map(|t| t.rotation); - let character_scale_frame = character_transform_frame.map(|t| t.scale); - - let character_bone_frame = BoneFrame { - rotation: Some(character_rotation_frame), - translation: Some(character_translation_frame), - scale: Some(character_scale_frame), - weights: local_bone_frame.weights.clone(), - }; - - inner_character_frame.add_bone(character_bone_frame, path.clone()); - } - // ------------------------------------------------------- - - final_pose_frame - } - - fn bone_to_global(&self, data: &BonePoseFrame) -> GlobalPoseFrame { - let character_pose_frame = self.bone_to_character(data); - self.character_to_global(&character_pose_frame) - } - - fn character_to_bone(&self, data: &CharacterPoseFrame) -> BonePoseFrame { - let root_name = self.resources.names_query.get(self.root_entity).unwrap(); - let root_path = EntityPath { - parts: vec![root_name.clone()], - }; - let root_transform_frame = ValueFrame { - prev: Transform::IDENTITY, - prev_timestamp: f32::MIN, - next: Transform::IDENTITY, - next_timestamp: f32::MAX, - prev_is_wrapped: true, - next_is_wrapped: true, - }; - - let root_children = self.resources.children_query.get(self.root_entity).unwrap(); - - let mut bone_transforms: HashMap> = HashMap::new(); - let mut queue: VecDeque<( - Entity, - EntityPath, - ValueFrame, - ValueFrame, - )> = VecDeque::new(); - - for child in root_children { - queue.push_back(( - *child, - root_path.clone(), - root_transform_frame.clone(), - root_transform_frame.clone(), - )); - } - - while !queue.is_empty() { - let (entity, parent_path, parent_transform_frame, parent_inverse_transform_frame) = - queue.pop_front().unwrap(); - // --- Compute the updated transform frame - // ------------------------------------------------------- - // First, build the entity path for the current entity - let entity_name = self.resources.names_query.get(entity).unwrap(); - let entity_path = parent_path.child(entity_name.clone()); - - // Get the entity's current local transform (in parent bone space) - let (entity_transform, _) = self.resources.transform_query.get(entity).unwrap(); - let inner_data = data.inner_ref(); - // Get the corresponding bone frame in character space - let bone_frame: BoneFrame = if inner_data.paths.contains_key(&entity_path) { - let bone_id = inner_data.paths.get(&entity_path).unwrap(); - inner_data.bones[*bone_id].clone() - } else { - BoneFrame { - translation: Some(parent_transform_frame.map(|t| t.translation)), - rotation: Some(parent_transform_frame.map(|t| t.rotation)), - scale: Some(parent_transform_frame.map(|t| t.scale)), - ..Default::default() - } - }; - - // Obtain a merged character transform frame - let character_transform_frame = ValueFrame { - prev: *entity_transform, - prev_timestamp: f32::MIN, - next: *entity_transform, - next_timestamp: f32::MAX, - prev_is_wrapped: true, - next_is_wrapped: true, - } - .merge_linear(&parent_transform_frame, |child, parent| *child * *parent); - - let character_transform_frame = - bone_frame.to_transform_frame_linear_with_base_frame(character_transform_frame); - - let bone_transform_frame = parent_inverse_transform_frame - .merge_linear(&character_transform_frame, |parent, child| *child * *parent); - bone_transforms.insert(entity_path.clone(), bone_transform_frame.clone()); - - if let Ok(children) = self.resources.children_query.get(entity) { - for child in children { - queue.push_back(( - *child, - entity_path.clone(), - character_transform_frame.clone(), - character_transform_frame - .map(|t| Transform::from_matrix(t.compute_matrix().inverse())), - )); - } - } - // ------------------------------------------------------- - } - - // --- Build character pose frame - // --- - // --- This involves building a bone frame for each bone - // --- frame in the existing data using the computed - // --- character transforms - // ------------------------------------------------------- - let mut final_pose_frame = BonePoseFrame::default(); - let inner_character_frame = final_pose_frame.inner_mut(); - - for (path, bone_id) in data.inner_ref().paths.iter() { - let local_bone_frame = &data.inner_ref().bones[*bone_id]; - let character_transform_frame = bone_transforms.get(path).unwrap(); - let character_translation_frame = character_transform_frame.map(|t| t.translation); - let character_rotation_frame = character_transform_frame.map(|t| t.rotation); - let character_scale_frame = character_transform_frame.map(|t| t.scale); - - let character_bone_frame = BoneFrame { - rotation: Some(character_rotation_frame), - translation: Some(character_translation_frame), - scale: Some(character_scale_frame), - weights: local_bone_frame.weights.clone(), - }; - - inner_character_frame.add_bone(character_bone_frame, path.clone()); - } - // ------------------------------------------------------- - - final_pose_frame - } - - fn character_to_global(&self, data: &CharacterPoseFrame) -> GlobalPoseFrame { - let (_, root_global_transform) = self - .resources - .transform_query - .get(self.root_entity) - .unwrap(); - let root_global_transform = root_global_transform.compute_transform(); - - // --- Build character pose frame - // --- - // --- This involves building a bone frame for each bone - // --- frame in the existing data using the computed - // --- inverse root transform - // ------------------------------------------------------- - let mut final_pose_frame = GlobalPoseFrame::default(); - let inner_global_frame = final_pose_frame.inner_mut(); - - for (path, bone_id) in data.inner_ref().paths.iter() { - let global_bone_frame = &data.inner_ref().bones[*bone_id]; - - let global_bone_frame = BoneFrame { - rotation: global_bone_frame - .rotation - .as_ref() - .map(|frame| frame.map(|r| root_global_transform.rotation * *r)), - translation: global_bone_frame - .translation - .as_ref() - .map(|frame| frame.map(|t| root_global_transform * *t)), - scale: global_bone_frame - .scale - .as_ref() - .map(|frame| frame.map(|s| root_global_transform.scale * *s)), - weights: global_bone_frame.weights.clone(), - }; - - inner_global_frame.add_bone(global_bone_frame, path.clone()); - } - // ------------------------------------------------------- - - final_pose_frame - } - - fn global_to_bone(&self, data: &GlobalPoseFrame) -> BonePoseFrame { - let character_pose_frame = self.global_to_character(data); - self.character_to_bone(&character_pose_frame) - } - - fn global_to_character(&self, data: &GlobalPoseFrame) -> CharacterPoseFrame { - let (_, root_global_transform) = self - .resources - .transform_query - .get(self.root_entity) - .unwrap(); - let inverse_global_transform = - Transform::from_matrix(root_global_transform.compute_matrix().inverse()); - - // --- Build character pose frame - // --- - // --- This involves building a bone frame for each bone - // --- frame in the existing data using the computed - // --- inverse root transform - // ------------------------------------------------------- - let mut final_pose_frame = CharacterPoseFrame::default(); - let inner_character_frame = final_pose_frame.inner_mut(); - - for (path, bone_id) in data.inner_ref().paths.iter() { - let global_bone_frame = &data.inner_ref().bones[*bone_id]; - - let character_bone_frame = BoneFrame { - rotation: global_bone_frame - .rotation - .as_ref() - .map(|frame| frame.map(|r| inverse_global_transform.rotation * *r)), - translation: global_bone_frame - .translation - .as_ref() - .map(|frame| frame.map(|t| inverse_global_transform * *t)), - scale: global_bone_frame - .scale - .as_ref() - .map(|frame| frame.map(|s| inverse_global_transform.scale * *s)), - weights: global_bone_frame.weights.clone(), - }; - - inner_character_frame.add_bone(character_bone_frame, path.clone()); - } - // ------------------------------------------------------- - - final_pose_frame - } + // fn bone_to_character(&self, data: &BonePoseFrame) -> CharacterPoseFrame { + // let root_name = self.resources.names_query.get(self.root_entity).unwrap(); + // let root_path = EntityPath { + // parts: vec![root_name.clone()], + // }; + // let root_transform_frame = ValueFrame { + // prev: Transform::IDENTITY, + // prev_timestamp: f32::MIN, + // next: Transform::IDENTITY, + // next_timestamp: f32::MAX, + // prev_is_wrapped: true, + // next_is_wrapped: true, + // }; + + // let root_children = self.resources.children_query.get(self.root_entity).unwrap(); + + // let mut character_transforms: HashMap> = HashMap::new(); + // let mut queue: VecDeque<(Entity, EntityPath, ValueFrame)> = VecDeque::new(); + + // for child in root_children { + // queue.push_back((*child, root_path.clone(), root_transform_frame.clone())); + // } + + // while !queue.is_empty() { + // let (entity, parent_path, parent_transform_frame) = queue.pop_front().unwrap(); + // // --- Compute the updated transform frame + // // ------------------------------------------------------- + // // First, build the entity path for the current entity + // let entity_name = self.resources.names_query.get(entity).unwrap(); + // let entity_path = parent_path.child(entity_name.clone()); + + // // Get the entity's current local transform + // let (entity_transform, _) = self.resources.transform_query.get(entity).unwrap(); + // let inner_data = data.inner_ref(); + // // Get the corresponding bone frame + // let bone_frame: BoneFrame = if inner_data.paths.contains_key(&entity_path) { + // let bone_id = inner_data.paths.get(&entity_path).unwrap(); + // inner_data.bones[*bone_id].clone() + // } else { + // BoneFrame::default() + // }; + + // // Obtain a merged local transform frame + // let local_transform_frame = ValueFrame { + // prev: *entity_transform, + // prev_timestamp: f32::MIN, + // next: *entity_transform, + // next_timestamp: f32::MAX, + // prev_is_wrapped: true, + // next_is_wrapped: true, + // }; + // let local_transform_frame = + // bone_frame.to_transform_frame_linear_with_base_frame(local_transform_frame); + + // let character_transform_frame = parent_transform_frame + // .merge_linear(&local_transform_frame, |parent, child| *child * *parent); + // character_transforms.insert(entity_path.clone(), character_transform_frame.clone()); + + // if let Ok(children) = self.resources.children_query.get(entity) { + // for child in children { + // queue.push_back(( + // *child, + // entity_path.clone(), + // character_transform_frame.clone(), + // )); + // } + // } + // // ------------------------------------------------------- + // } + + // // --- Build character pose frame + // // --- + // // --- This involves building a bone frame for each bone + // // --- frame in the existing data using the computed + // // --- character transforms + // // ------------------------------------------------------- + // let mut final_pose_frame = CharacterPoseFrame::default(); + // let inner_character_frame = final_pose_frame.inner_mut(); + + // for (path, bone_id) in data.inner_ref().paths.iter() { + // let local_bone_frame = &data.inner_ref().bones[*bone_id]; + // let character_transform_frame = character_transforms.get(path).unwrap(); + // let character_translation_frame = character_transform_frame.map(|t| t.translation); + // let character_rotation_frame = character_transform_frame.map(|t| t.rotation); + // let character_scale_frame = character_transform_frame.map(|t| t.scale); + + // let character_bone_frame = BoneFrame { + // rotation: Some(character_rotation_frame), + // translation: Some(character_translation_frame), + // scale: Some(character_scale_frame), + // weights: local_bone_frame.weights.clone(), + // }; + + // inner_character_frame.add_bone(character_bone_frame, path.clone()); + // } + // // ------------------------------------------------------- + + // final_pose_frame + // } + + // fn bone_to_global(&self, data: &BonePoseFrame) -> GlobalPoseFrame { + // let character_pose_frame = self.bone_to_character(data); + // self.character_to_global(&character_pose_frame) + // } + + // fn character_to_bone(&self, data: &CharacterPoseFrame) -> BonePoseFrame { + // let root_name = self.resources.names_query.get(self.root_entity).unwrap(); + // let root_path = EntityPath { + // parts: vec![root_name.clone()], + // }; + // let root_transform_frame = ValueFrame { + // prev: Transform::IDENTITY, + // prev_timestamp: f32::MIN, + // next: Transform::IDENTITY, + // next_timestamp: f32::MAX, + // prev_is_wrapped: true, + // next_is_wrapped: true, + // }; + + // let root_children = self.resources.children_query.get(self.root_entity).unwrap(); + + // let mut bone_transforms: HashMap> = HashMap::new(); + // let mut queue: VecDeque<( + // Entity, + // EntityPath, + // ValueFrame, + // ValueFrame, + // )> = VecDeque::new(); + + // for child in root_children { + // queue.push_back(( + // *child, + // root_path.clone(), + // root_transform_frame.clone(), + // root_transform_frame.clone(), + // )); + // } + + // while !queue.is_empty() { + // let (entity, parent_path, parent_transform_frame, parent_inverse_transform_frame) = + // queue.pop_front().unwrap(); + // // --- Compute the updated transform frame + // // ------------------------------------------------------- + // // First, build the entity path for the current entity + // let entity_name = self.resources.names_query.get(entity).unwrap(); + // let entity_path = parent_path.child(entity_name.clone()); + + // // Get the entity's current local transform (in parent bone space) + // let (entity_transform, _) = self.resources.transform_query.get(entity).unwrap(); + // let inner_data = data.inner_ref(); + // // Get the corresponding bone frame in character space + // let bone_frame: BoneFrame = if inner_data.paths.contains_key(&entity_path) { + // let bone_id = inner_data.paths.get(&entity_path).unwrap(); + // inner_data.bones[*bone_id].clone() + // } else { + // BoneFrame { + // translation: Some(parent_transform_frame.map(|t| t.translation)), + // rotation: Some(parent_transform_frame.map(|t| t.rotation)), + // scale: Some(parent_transform_frame.map(|t| t.scale)), + // ..Default::default() + // } + // }; + + // // Obtain a merged character transform frame + // let character_transform_frame = ValueFrame { + // prev: *entity_transform, + // prev_timestamp: f32::MIN, + // next: *entity_transform, + // next_timestamp: f32::MAX, + // prev_is_wrapped: true, + // next_is_wrapped: true, + // } + // .merge_linear(&parent_transform_frame, |child, parent| *child * *parent); + + // let character_transform_frame = + // bone_frame.to_transform_frame_linear_with_base_frame(character_transform_frame); + + // let bone_transform_frame = parent_inverse_transform_frame + // .merge_linear(&character_transform_frame, |parent, child| *child * *parent); + // bone_transforms.insert(entity_path.clone(), bone_transform_frame.clone()); + + // if let Ok(children) = self.resources.children_query.get(entity) { + // for child in children { + // queue.push_back(( + // *child, + // entity_path.clone(), + // character_transform_frame.clone(), + // character_transform_frame + // .map(|t| Transform::from_matrix(t.compute_matrix().inverse())), + // )); + // } + // } + // // ------------------------------------------------------- + // } + + // // --- Build character pose frame + // // --- + // // --- This involves building a bone frame for each bone + // // --- frame in the existing data using the computed + // // --- character transforms + // // ------------------------------------------------------- + // let mut final_pose_frame = BonePoseFrame::default(); + // let inner_character_frame = final_pose_frame.inner_mut(); + + // for (path, bone_id) in data.inner_ref().paths.iter() { + // let local_bone_frame = &data.inner_ref().bones[*bone_id]; + // let character_transform_frame = bone_transforms.get(path).unwrap(); + // let character_translation_frame = character_transform_frame.map(|t| t.translation); + // let character_rotation_frame = character_transform_frame.map(|t| t.rotation); + // let character_scale_frame = character_transform_frame.map(|t| t.scale); + + // let character_bone_frame = BoneFrame { + // rotation: Some(character_rotation_frame), + // translation: Some(character_translation_frame), + // scale: Some(character_scale_frame), + // weights: local_bone_frame.weights.clone(), + // }; + + // inner_character_frame.add_bone(character_bone_frame, path.clone()); + // } + // // ------------------------------------------------------- + + // final_pose_frame + // } + + // fn character_to_global(&self, data: &CharacterPoseFrame) -> GlobalPoseFrame { + // let (_, root_global_transform) = self + // .resources + // .transform_query + // .get(self.root_entity) + // .unwrap(); + // let root_global_transform = root_global_transform.compute_transform(); + + // // --- Build character pose frame + // // --- + // // --- This involves building a bone frame for each bone + // // --- frame in the existing data using the computed + // // --- inverse root transform + // // ------------------------------------------------------- + // let mut final_pose_frame = GlobalPoseFrame::default(); + // let inner_global_frame = final_pose_frame.inner_mut(); + + // for (path, bone_id) in data.inner_ref().paths.iter() { + // let global_bone_frame = &data.inner_ref().bones[*bone_id]; + + // let global_bone_frame = BoneFrame { + // rotation: global_bone_frame + // .rotation + // .as_ref() + // .map(|frame| frame.map(|r| root_global_transform.rotation * *r)), + // translation: global_bone_frame + // .translation + // .as_ref() + // .map(|frame| frame.map(|t| root_global_transform * *t)), + // scale: global_bone_frame + // .scale + // .as_ref() + // .map(|frame| frame.map(|s| root_global_transform.scale * *s)), + // weights: global_bone_frame.weights.clone(), + // }; + + // inner_global_frame.add_bone(global_bone_frame, path.clone()); + // } + // // ------------------------------------------------------- + + // final_pose_frame + // } + + // fn global_to_bone(&self, data: &GlobalPoseFrame) -> BonePoseFrame { + // let character_pose_frame = self.global_to_character(data); + // self.character_to_bone(&character_pose_frame) + // } + + // fn global_to_character(&self, data: &GlobalPoseFrame) -> CharacterPoseFrame { + // let (_, root_global_transform) = self + // .resources + // .transform_query + // .get(self.root_entity) + // .unwrap(); + // let inverse_global_transform = + // Transform::from_matrix(root_global_transform.compute_matrix().inverse()); + + // // --- Build character pose frame + // // --- + // // --- This involves building a bone frame for each bone + // // --- frame in the existing data using the computed + // // --- inverse root transform + // // ------------------------------------------------------- + // let mut final_pose_frame = CharacterPoseFrame::default(); + // let inner_character_frame = final_pose_frame.inner_mut(); + + // for (path, bone_id) in data.inner_ref().paths.iter() { + // let global_bone_frame = &data.inner_ref().bones[*bone_id]; + + // let character_bone_frame = BoneFrame { + // rotation: global_bone_frame + // .rotation + // .as_ref() + // .map(|frame| frame.map(|r| inverse_global_transform.rotation * *r)), + // translation: global_bone_frame + // .translation + // .as_ref() + // .map(|frame| frame.map(|t| inverse_global_transform * *t)), + // scale: global_bone_frame + // .scale + // .as_ref() + // .map(|frame| frame.map(|s| inverse_global_transform.scale * *s)), + // weights: global_bone_frame.weights.clone(), + // }; + + // inner_character_frame.add_bone(character_bone_frame, path.clone()); + // } + // // ------------------------------------------------------- + + // final_pose_frame + // } fn change_bone_space_down( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space source: BoneId, target: BoneId, - timestamp: f32, ) -> Transform { let mut curr_path = target; let mut curr_transform = Transform::IDENTITY; while curr_path != source { - let bone_frame: BoneFrame = if data.paths.contains_key(&curr_path) { + let bone_frame = if data.paths.contains_key(&curr_path) { let bone_id = data.paths.get(&curr_path).unwrap(); data.bones[*bone_id].clone() } else { - BoneFrame::default() + BonePose::default() }; let curr_entity = self.entity_map.get(&curr_path).unwrap(); let curr_local_transform = self.resources.transform_query.get(*curr_entity).unwrap().0; - let merged_local_transform = - bone_frame.to_transform_linear_with_base(*curr_local_transform, timestamp); + let merged_local_transform = bone_frame.to_transform_with_base(*curr_local_transform); curr_transform = merged_local_transform * curr_transform; curr_path = curr_path.parent().unwrap(); @@ -444,25 +437,23 @@ impl SpaceConversion for PassContext<'_> { fn change_bone_space_up( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space source: BoneId, target: BoneId, - timestamp: f32, ) -> Transform { let mut curr_path = source; let mut curr_transform = Transform::IDENTITY; while curr_path != target { - let bone_frame: BoneFrame = if data.paths.contains_key(&curr_path) { + let bone_pose: BonePose = if data.paths.contains_key(&curr_path) { let bone_id = data.paths.get(&curr_path).unwrap(); data.bones[*bone_id].clone() } else { - BoneFrame::default() + BonePose::default() }; let curr_entity = self.entity_map.get(&curr_path).unwrap(); let curr_local_transform = self.resources.transform_query.get(*curr_entity).unwrap().0; - let merged_local_transform = - bone_frame.to_transform_linear_with_base(*curr_local_transform, timestamp); + let merged_local_transform = bone_pose.to_transform_with_base(*curr_local_transform); curr_transform = merged_local_transform * curr_transform; curr_path = curr_path.parent().unwrap(); @@ -474,27 +465,25 @@ impl SpaceConversion for PassContext<'_> { fn root_to_bone_space( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space target: BoneId, - timestamp: f32, ) -> Transform { let root_name = self.resources.names_query.get(self.root_entity).unwrap(); let root_path = EntityPath { parts: vec![root_name.clone()], }; - self.change_bone_space_down(transform, data, root_path, target, timestamp) + self.change_bone_space_down(transform, data, root_path, target) } fn global_to_bone_space( &self, transform: Transform, - data: &InnerPoseFrame, // Should be in bone space + data: &Pose, // Should be in bone space target: BoneId, - timestamp: f32, ) -> Transform { let character_transform = self.transform_global_to_character(transform); - self.root_to_bone_space(character_transform, data, target, timestamp) + self.root_to_bone_space(character_transform, data, target) } fn transform_global_to_character(&self, transform: Transform) -> Transform { @@ -508,38 +497,26 @@ impl SpaceConversion for PassContext<'_> { inverse_global_transform * transform } - fn character_transform_of_bone( - &self, - data: &InnerPoseFrame, - target: BoneId, - timestamp: f32, - ) -> Transform { + fn character_transform_of_bone(&self, data: &Pose, target: BoneId) -> Transform { let root_name = self.resources.names_query.get(self.root_entity).unwrap(); let root_path = EntityPath { parts: vec![root_name.clone()], }; - self.change_bone_space_up(Transform::IDENTITY, data, target, root_path, timestamp) + self.change_bone_space_up(Transform::IDENTITY, data, target, root_path) } - fn global_transform_of_bone( - &self, - data: &InnerPoseFrame, - target: BoneId, - timestamp: f32, - ) -> Transform { + fn global_transform_of_bone(&self, data: &Pose, target: BoneId) -> Transform { let (_, root_transform_global) = self .resources .transform_query .get(self.root_entity) .unwrap(); - root_transform_global.compute_transform() - * self.character_transform_of_bone(data, target, timestamp) + root_transform_global.compute_transform() * self.character_transform_of_bone(data, target) } - fn extend_skeleton_bone(&self, data: &BonePoseFrame) -> BonePoseFrame { - let mut new_frame = data.clone(); - let new_frame_inner = new_frame.inner_mut(); + fn extend_skeleton_bone(&self, data: &Pose) -> Pose { + let mut new_pose = data.clone(); let root_name = self.resources.names_query.get(self.root_entity).unwrap(); let root_path = EntityPath { @@ -564,38 +541,29 @@ impl SpaceConversion for PassContext<'_> { // Get the entity's current local transform let (entity_transform, _) = self.resources.transform_query.get(entity).unwrap(); - let inner_data = data.inner_ref(); // Get the corresponding bone frame - let mut bone_frame: BoneFrame = if inner_data.paths.contains_key(&entity_path) { - let bone_id = inner_data.paths.get(&entity_path).unwrap(); - inner_data.bones[*bone_id].clone() + let mut bone_pose = if new_pose.paths.contains_key(&entity_path) { + let bone_id = new_pose.paths.get(&entity_path).unwrap(); + new_pose.bones[*bone_id].clone() } else { - BoneFrame::default() + BonePose::default() }; // Obtain a merged local transform frame - let local_transform_frame = ValueFrame { - prev: *entity_transform, - prev_timestamp: f32::MIN, - next: *entity_transform, - next_timestamp: f32::MAX, - prev_is_wrapped: true, - next_is_wrapped: true, - }; - if bone_frame.translation.is_none() { - bone_frame.translation = Some(local_transform_frame.map(|t| t.translation)); + if bone_pose.translation.is_none() { + bone_pose.translation = Some(entity_transform.translation); } - if bone_frame.rotation.is_none() { - bone_frame.rotation = Some(local_transform_frame.map(|t| t.rotation)); + if bone_pose.rotation.is_none() { + bone_pose.rotation = Some(entity_transform.rotation); } - if bone_frame.scale.is_none() { - bone_frame.scale = Some(local_transform_frame.map(|t| t.scale)); + if bone_pose.scale.is_none() { + bone_pose.scale = Some(entity_transform.scale); } - new_frame_inner.add_bone(bone_frame, entity_path); + new_pose.add_bone(bone_pose, entity_path); } - new_frame + new_pose } } diff --git a/crates/bevy_animation_graph/src/flipping/mod.rs b/crates/bevy_animation_graph/src/flipping/mod.rs index e05d092..1f2847e 100644 --- a/crates/bevy_animation_graph/src/flipping/mod.rs +++ b/crates/bevy_animation_graph/src/flipping/mod.rs @@ -3,7 +3,7 @@ pub mod config; use self::config::FlipConfig; use crate::core::{ animation_clip::EntityPath, - frame::{BoneFrame, BonePoseFrame, InnerPoseFrame, ValueFrame}, + pose::{BonePose, Pose}, }; use bevy::math::prelude::*; @@ -11,33 +11,26 @@ pub trait FlipXBySuffix { fn flipped(&self, config: &FlipConfig) -> Self; } -impl FlipXBySuffix for ValueFrame { +impl FlipXBySuffix for Vec3 { fn flipped(&self, _: &FlipConfig) -> Self { - let mut out = self.clone(); - - out.prev.x *= -1.; - out.next.x *= -1.; - + let mut out = *self; + out.x *= -1.; out } } -impl FlipXBySuffix for ValueFrame { +impl FlipXBySuffix for Quat { fn flipped(&self, _: &FlipConfig) -> Self { - let mut out = self.clone(); - - out.prev.x *= -1.; - out.prev.w *= -1.; - out.next.x *= -1.; - out.next.w *= -1.; - + let mut out = *self; + out.x *= -1.; + out.w *= -1.; out } } -impl FlipXBySuffix for BoneFrame { +impl FlipXBySuffix for BonePose { fn flipped(&self, config: &FlipConfig) -> Self { - BoneFrame { + BonePose { rotation: self.rotation.clone().map(|v| v.flipped(config)), translation: self.translation.clone().map(|v| v.flipped(config)), scale: self.scale.clone(), @@ -46,9 +39,9 @@ impl FlipXBySuffix for BoneFrame { } } -impl FlipXBySuffix for InnerPoseFrame { +impl FlipXBySuffix for Pose { fn flipped(&self, config: &FlipConfig) -> Self { - let mut out = InnerPoseFrame::default(); + let mut out = Pose::default(); for (path, bone_id) in self.paths.iter() { let channel = self.bones[*bone_id].flipped(config); let new_path = EntityPath { @@ -70,9 +63,3 @@ impl FlipXBySuffix for InnerPoseFrame { out } } - -impl FlipXBySuffix for BonePoseFrame { - fn flipped(&self, config: &FlipConfig) -> Self { - BonePoseFrame(self.0.flipped(config)) - } -} diff --git a/crates/bevy_animation_graph/src/interpolation/linear.rs b/crates/bevy_animation_graph/src/interpolation/linear.rs index ea83d07..3e5b4b2 100644 --- a/crates/bevy_animation_graph/src/interpolation/linear.rs +++ b/crates/bevy_animation_graph/src/interpolation/linear.rs @@ -1,6 +1,4 @@ -use crate::core::frame::{ - BoneFrame, InnerPoseFrame, PoseFrame, PoseFrameData, PoseSpec, ValueFrame, -}; +use crate::core::pose::{BonePose, Pose}; use bevy::prelude::*; pub trait InterpolateLinear { @@ -39,15 +37,7 @@ impl InterpolateLinear for Transform { } } -impl InterpolateLinear - for ValueFrame -{ - fn interpolate_linear(&self, other: &Self, f: f32) -> Self { - self.merge_linear(other, |l, r| l.interpolate_linear(r, f)) - } -} - -impl InterpolateLinear for BoneFrame { +impl InterpolateLinear for BonePose { fn interpolate_linear(&self, other: &Self, f: f32) -> Self { let mut result = Self::default(); @@ -93,9 +83,9 @@ impl InterpolateLinear for BoneFrame { } } -impl InterpolateLinear for InnerPoseFrame { +impl InterpolateLinear for Pose { fn interpolate_linear(&self, other: &Self, f: f32) -> Self { - let mut result = InnerPoseFrame::default(); + let mut result = Pose::default(); for (path, bone_id) in self.paths.iter() { if let Some(other_bone_id) = other.paths.get(path) { @@ -118,156 +108,3 @@ impl InterpolateLinear for InnerPoseFrame { result } } - -impl InterpolateLinear for PoseFrameData { - fn interpolate_linear(&self, other: &Self, f: f32) -> Self { - match (self, other) { - (PoseFrameData::BoneSpace(f1), PoseFrameData::BoneSpace(f2)) => { - PoseFrameData::BoneSpace( - f1.inner_ref().interpolate_linear(f2.inner_ref(), f).into(), - ) - } - (PoseFrameData::CharacterSpace(f1), PoseFrameData::CharacterSpace(f2)) => { - PoseFrameData::CharacterSpace( - f1.inner_ref().interpolate_linear(f2.inner_ref(), f).into(), - ) - } - (PoseFrameData::GlobalSpace(f1), PoseFrameData::GlobalSpace(f2)) => { - PoseFrameData::GlobalSpace( - f1.inner_ref().interpolate_linear(f2.inner_ref(), f).into(), - ) - } - _ => { - panic!( - "Tried to chain {:?} with {:?}", - PoseSpec::from(self), - PoseSpec::from(other) - ) - } - } - } -} - -impl InterpolateLinear for PoseFrame { - fn interpolate_linear(&self, other: &Self, f: f32) -> Self { - Self { - data: self.data.interpolate_linear(&other.data, f), - timestamp: self.timestamp, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_interpolate_value_frame_nest_1() { - let frame_1 = ValueFrame { - prev: Vec3::new(0., 0., 0.), - prev_timestamp: 0., - next: Vec3::new(1., 1., 1.), - next_timestamp: 1., - next_is_wrapped: false, - prev_is_wrapped: false, - }; - let frame_2 = ValueFrame { - prev: Vec3::new(0., 0., 0.), - prev_timestamp: 0.2, - next: Vec3::new(1., 1., 1.), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - let interpolated_0 = frame_1.interpolate_linear(&frame_2, 0.); - let interpolated_half = frame_1.interpolate_linear(&frame_2, 0.5); - let interpolated_1 = frame_1.interpolate_linear(&frame_2, 1.); - - let expected_0 = ValueFrame { - prev: Vec3::new(0.2, 0.2, 0.2), - prev_timestamp: 0.2, - next: Vec3::new(0.8, 0.8, 0.8), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - let expected_half = ValueFrame { - prev: Vec3::new(0.1, 0.1, 0.1), - prev_timestamp: 0.2, - next: Vec3::new(0.9, 0.9, 0.9), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - let expected_1 = ValueFrame { - prev: Vec3::new(0.0, 0.0, 0.0), - prev_timestamp: 0.2, - next: Vec3::new(1.0, 1.0, 1.0), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - assert_eq!(expected_0, interpolated_0); - assert_eq!(expected_1, interpolated_1); - assert_eq!(expected_half, interpolated_half); - } - - #[test] - fn test_interpolate_value_frame_nest_2() { - let frame_2 = ValueFrame { - prev: Vec3::new(0., 0., 0.), - prev_timestamp: 0., - next: Vec3::new(1., 1., 1.), - next_timestamp: 1., - next_is_wrapped: false, - prev_is_wrapped: false, - }; - let frame_1 = ValueFrame { - prev: Vec3::new(0., 0., 0.), - prev_timestamp: 0.2, - next: Vec3::new(1., 1., 1.), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - let interpolated_1 = frame_1.interpolate_linear(&frame_2, 0.); - let interpolated_half = frame_1.interpolate_linear(&frame_2, 0.5); - let interpolated_0 = frame_1.interpolate_linear(&frame_2, 1.); - - let expected_0 = ValueFrame { - prev: Vec3::new(0.2, 0.2, 0.2), - prev_timestamp: 0.2, - next: Vec3::new(0.8, 0.8, 0.8), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - let expected_half = ValueFrame { - prev: Vec3::new(0.1, 0.1, 0.1), - prev_timestamp: 0.2, - next: Vec3::new(0.9, 0.9, 0.9), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - let expected_1 = ValueFrame { - prev: Vec3::new(0.0, 0.0, 0.0), - prev_timestamp: 0.2, - next: Vec3::new(1.0, 1.0, 1.0), - next_timestamp: 0.8, - next_is_wrapped: false, - prev_is_wrapped: false, - }; - - assert_eq!(expected_0, interpolated_0); - assert_eq!(expected_1, interpolated_1); - assert_eq!(expected_half, interpolated_half); - } -} diff --git a/crates/bevy_animation_graph/src/lib.rs b/crates/bevy_animation_graph/src/lib.rs index 250a989..0b71ce4 100644 --- a/crates/bevy_animation_graph/src/lib.rs +++ b/crates/bevy_animation_graph/src/lib.rs @@ -231,7 +231,6 @@ pub mod core; pub mod flipping; pub mod interpolation; pub mod nodes; -pub mod sampling; mod utils; pub mod prelude { @@ -240,6 +239,5 @@ pub mod prelude { pub use super::flipping::*; pub use super::interpolation::linear::*; pub use super::nodes::*; - pub use super::sampling::prelude::*; pub use super::utils::ordered_map::OrderedMap; } diff --git a/crates/bevy_animation_graph/src/nodes/blend_node.rs b/crates/bevy_animation_graph/src/nodes/blend_node.rs index 781ce5d..5aa436b 100644 --- a/crates/bevy_animation_graph/src/nodes/blend_node.rs +++ b/crates/bevy_animation_graph/src/nodes/blend_node.rs @@ -2,9 +2,8 @@ use crate::core::animation_graph::{PinMap, TimeUpdate}; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{PoseFrame, PoseSpec}; -use crate::interpolation::linear::InterpolateLinear; -use crate::prelude::{OptParamSpec, ParamSpec, PassContext, SpecContext}; +use crate::core::pose::{Pose, PoseSpec}; +use crate::prelude::{InterpolateLinear, OptParamSpec, ParamSpec, PassContext, SpecContext}; use bevy::prelude::*; #[derive(Reflect, Clone, Debug, Default)] @@ -45,7 +44,7 @@ impl NodeLike for BlendNode { &self, input: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let in_frame_1 = ctx.pose_back(Self::INPUT_1, input)?; let in_frame_2 = ctx.pose_back(Self::INPUT_2, input)?; diff --git a/crates/bevy_animation_graph/src/nodes/chain_node.rs b/crates/bevy_animation_graph/src/nodes/chain_node.rs index d626d59..be50756 100644 --- a/crates/bevy_animation_graph/src/nodes/chain_node.rs +++ b/crates/bevy_animation_graph/src/nodes/chain_node.rs @@ -1,23 +1,28 @@ -use crate::chaining::Chainable; use crate::core::animation_graph::{PinMap, TimeUpdate}; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{PoseFrame, PoseSpec}; -use crate::prelude::{PassContext, SpecContext}; +use crate::core::pose::{Pose, PoseSpec}; +use crate::prelude::{InterpolateLinear, PassContext, SpecContext}; use bevy::prelude::*; #[derive(Reflect, Clone, Debug, Default)] #[reflect(Default)] -pub struct ChainNode {} +pub struct ChainNode { + /// Time in-between animations where the output should interpolate between the last pose of the + /// first animation and the first pose of the second + pub interpolation_period: f32, +} impl ChainNode { pub const INPUT_1: &'static str = "Pose In 1"; pub const INPUT_2: &'static str = "Pose In 2"; pub const OUTPUT: &'static str = "Pose Out"; - pub fn new() -> Self { - Self {} + pub fn new(interpolation_period: f32) -> Self { + Self { + interpolation_period, + } } pub fn wrapped(self, name: impl Into) -> AnimationNode { @@ -44,28 +49,33 @@ impl NodeLike for ChainNode { &self, input: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { 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 Ok(Some(ctx.pose_back(Self::INPUT_1, input)?)); }; - let pose_1 = ctx.pose_back(Self::INPUT_1, input)?; let curr_time = pose_1.timestamp; - let pose_2 = ctx.pose_back(Self::INPUT_2, TimeUpdate::Absolute(curr_time - duration_1))?; - - let duration_2 = ctx.duration_back(Self::INPUT_2)?; - - let out_pose = pose_1.chain( - &pose_2, - duration_1, - duration_2.unwrap_or(f32::MAX), - curr_time, - ); - - Ok(Some(out_pose)) + if curr_time < duration_1 { + Ok(Some(pose_1)) + } else if curr_time - duration_1 - self.interpolation_period >= 0. { + let mut pose_2 = ctx.pose_back( + Self::INPUT_2, + TimeUpdate::Absolute(curr_time - duration_1 - self.interpolation_period), + )?; + pose_2.timestamp = curr_time; + Ok(Some(pose_2)) + } else { + let pose_2 = ctx.pose_back(Self::INPUT_2, TimeUpdate::Absolute(0.0))?; + let mut out_pose = pose_1.interpolate_linear( + &pose_2, + (curr_time - duration_1) / self.interpolation_period, + ); + out_pose.timestamp = curr_time; + Ok(Some(out_pose)) + } } fn pose_input_spec(&self, _: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/clip_node.rs b/crates/bevy_animation_graph/src/nodes/clip_node.rs index 2075003..d63f854 100644 --- a/crates/bevy_animation_graph/src/nodes/clip_node.rs +++ b/crates/bevy_animation_graph/src/nodes/clip_node.rs @@ -3,11 +3,9 @@ use crate::core::animation_graph::TimeUpdate; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{ - BoneFrame, InnerPoseFrame, PoseFrame, PoseFrameData, PoseSpec, ValueFrame, -}; +use crate::core::pose::{BonePose, Pose, PoseSpec}; use crate::core::systems::get_keyframe; -use crate::prelude::{PassContext, SpecContext}; +use crate::prelude::{InterpolateLinear, PassContext, SpecContext}; use bevy::asset::Handle; use bevy::reflect::prelude::*; @@ -54,19 +52,22 @@ impl NodeLike for ClipNode { &self, time_update: TimeUpdate, ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let clip_duration = self.clip_duration(&ctx); + let Some(clip) = ctx.resources.graph_clip_assets.get(&self.clip) else { - return Ok(Some(PoseFrame::default())); + return Ok(Some(Pose::default())); }; let prev_time = ctx.prev_time_fwd(); let time = time_update.apply(prev_time); - let mut inner_frame = InnerPoseFrame::default(); + let mut out_pose = Pose::default(); + out_pose.timestamp = time; + for (path, bone_id) in &clip.paths { let curves = clip.get_curves(*bone_id).unwrap(); - let mut frame = BoneFrame::default(); + let mut bone_pose = BonePose::default(); for curve in curves { // Some curves have only one keyframe used to set a transform let keyframe_count = curve.keyframe_timestamps.len(); @@ -96,6 +97,8 @@ impl NodeLike for ClipNode { next_timestamp += clip_duration; } + let lerp = (time - prev_timestamp) / (next_timestamp - prev_timestamp); + // Apply the keyframe match &curve.keyframes { Keyframes::Rotation(keyframes) => { @@ -106,66 +109,35 @@ impl NodeLike for ClipNode { next = -next; } - frame.rotation = Some(ValueFrame { - prev, - prev_timestamp, - next, - next_timestamp, - prev_is_wrapped, - next_is_wrapped, - }); + bone_pose.rotation = Some(prev.interpolate_linear(&next, lerp)); } Keyframes::Translation(keyframes) => { let prev = keyframes[step_start]; let next = keyframes[step_end]; - frame.translation = Some(ValueFrame { - prev, - prev_timestamp, - next, - next_timestamp, - prev_is_wrapped, - next_is_wrapped, - }); + bone_pose.translation = Some(prev.interpolate_linear(&next, lerp)); } Keyframes::Scale(keyframes) => { let prev = keyframes[step_start]; let next = keyframes[step_end]; - frame.scale = Some(ValueFrame { - prev, - prev_timestamp, - next, - next_timestamp, - prev_is_wrapped, - next_is_wrapped, - }); + bone_pose.scale = Some(prev.interpolate_linear(&next, lerp)); } Keyframes::Weights(keyframes) => { let target_count = keyframes.len() / keyframe_count; - let morph_start = get_keyframe(target_count, keyframes, step_start); - let morph_end = get_keyframe(target_count, keyframes, step_end); - frame.weights = Some(ValueFrame { - prev: morph_start.into(), - prev_timestamp, - next: morph_end.into(), - next_timestamp, - prev_is_wrapped, - next_is_wrapped, - }); + let morph_start: Vec = + get_keyframe(target_count, keyframes, step_start).into(); + let morph_end: Vec = + get_keyframe(target_count, keyframes, step_end).into(); + bone_pose.weights = Some(morph_end.interpolate_linear(&morph_end, lerp)); } } } - inner_frame.add_bone(frame, path.clone()); + out_pose.add_bone(bone_pose, path.clone()); } - let pose_frame = PoseFrame { - data: PoseFrameData::BoneSpace(inner_frame.into()), - timestamp: time, - }; - - Ok(Some(pose_frame)) + Ok(Some(out_pose)) } fn pose_output_spec(&self, _: SpecContext) -> Option { diff --git a/crates/bevy_animation_graph/src/nodes/flip_lr_node.rs b/crates/bevy_animation_graph/src/nodes/flip_lr_node.rs index 34df0c7..e514145 100644 --- a/crates/bevy_animation_graph/src/nodes/flip_lr_node.rs +++ b/crates/bevy_animation_graph/src/nodes/flip_lr_node.rs @@ -2,11 +2,10 @@ use crate::core::animation_graph::{PinMap, TimeUpdate}; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{BonePoseFrame, PoseFrame, PoseFrameData, PoseSpec}; +use crate::core::pose::{Pose, PoseSpec}; use crate::flipping::FlipXBySuffix; use crate::prelude::config::FlipConfig; use crate::prelude::{BoneDebugGizmos, PassContext, SpecContext}; -use crate::utils::unwrap::Unwrap; use bevy::prelude::*; #[derive(Reflect, Clone, Debug)] @@ -43,24 +42,12 @@ impl NodeLike for FlipLRNode { &self, input: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { - let in_pose_frame = ctx.pose_back(Self::INPUT, input)?; - let bone_frame: BonePoseFrame = in_pose_frame.data.unwrap(); - - ctx.pose_bone_gizmos(Color::RED, bone_frame.inner_ref(), in_pose_frame.timestamp); - - let flipped_pose_frame = bone_frame.flipped(&self.config); - - ctx.pose_bone_gizmos( - Color::BLUE, - flipped_pose_frame.inner_ref(), - in_pose_frame.timestamp, - ); - - Ok(Some(PoseFrame { - data: PoseFrameData::BoneSpace(flipped_pose_frame), - timestamp: in_pose_frame.timestamp, - })) + ) -> Result, GraphError> { + let in_pose = ctx.pose_back(Self::INPUT, input)?; + ctx.pose_bone_gizmos(Color::RED, &in_pose); + let flipped_pose = in_pose.flipped(&self.config); + ctx.pose_bone_gizmos(Color::BLUE, &flipped_pose); + Ok(Some(flipped_pose)) } fn pose_input_spec(&self, _: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/graph_node.rs b/crates/bevy_animation_graph/src/nodes/graph_node.rs index 591350f..78c5c6a 100644 --- a/crates/bevy_animation_graph/src/nodes/graph_node.rs +++ b/crates/bevy_animation_graph/src/nodes/graph_node.rs @@ -4,7 +4,7 @@ use crate::core::animation_graph::{ use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{PoseFrame, PoseSpec}; +use crate::core::pose::{Pose, PoseSpec}; use crate::prelude::{OptParamSpec, ParamSpec, ParamValue, PassContext, SpecContext}; use bevy::prelude::*; use bevy::utils::HashMap; @@ -64,11 +64,7 @@ impl NodeLike for GraphNode { } } - fn pose_pass( - &self, - input: TimeUpdate, - ctx: PassContext, - ) -> Result, GraphError> { + fn pose_pass(&self, input: TimeUpdate, ctx: PassContext) -> Result, GraphError> { let graph = ctx .resources .animation_graph_assets diff --git a/crates/bevy_animation_graph/src/nodes/loop_node.rs b/crates/bevy_animation_graph/src/nodes/loop_node.rs index fc058d5..effe7f8 100644 --- a/crates/bevy_animation_graph/src/nodes/loop_node.rs +++ b/crates/bevy_animation_graph/src/nodes/loop_node.rs @@ -2,14 +2,16 @@ use crate::core::animation_graph::{PinId, PinMap, TimeUpdate}; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{PoseFrame, PoseSpec}; +use crate::core::pose::{Pose, PoseSpec}; use crate::prelude::{ParamValue, PassContext, SpecContext}; use bevy::prelude::*; use bevy::utils::HashMap; #[derive(Reflect, Clone, Debug, Default)] #[reflect(Default)] -pub struct LoopNode {} +pub struct LoopNode { + // TODO: Interpolation period, like in chain node +} impl LoopNode { pub const INPUT: &'static str = "Pose In"; @@ -37,7 +39,7 @@ impl NodeLike for LoopNode { &self, input: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let duration = ctx.duration_back(Self::INPUT)?; let Some(duration) = duration else { @@ -62,7 +64,7 @@ impl NodeLike for LoopNode { let mut pose = ctx.pose_back(Self::INPUT, fw_upd)?; let t_extra = curr_time.div_euclid(duration) * duration; - pose.map_ts(|t| t + t_extra); + pose.timestamp += t_extra; Ok(Some(pose)) } diff --git a/crates/bevy_animation_graph/src/nodes/rotation_node.rs b/crates/bevy_animation_graph/src/nodes/rotation_node.rs index 0b8438c..79123ab 100644 --- a/crates/bevy_animation_graph/src/nodes/rotation_node.rs +++ b/crates/bevy_animation_graph/src/nodes/rotation_node.rs @@ -3,11 +3,9 @@ use crate::core::animation_graph::{PinMap, TimeUpdate}; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{ - BoneFrame, BonePoseFrame, PoseFrame, PoseFrameData, PoseSpec, ValueFrame, -}; +use crate::core::pose::{BonePose, Pose, PoseSpec}; use crate::core::space_conversion::SpaceConversion; -use crate::prelude::{OptParamSpec, ParamSpec, PassContext, SampleLinearAt, SpecContext}; +use crate::prelude::{OptParamSpec, ParamSpec, PassContext, SpecContext}; use crate::utils::unwrap::Unwrap; use bevy::math::Quat; use bevy::reflect::std_traits::ReflectDefault; @@ -94,16 +92,14 @@ impl NodeLike for RotationNode { &self, input: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let mut target: EntityPath = ctx.parameter_back(Self::TARGET)?.unwrap(); let rotation: Quat = ctx.parameter_back(Self::ROTATION)?.unwrap(); - let pose = ctx.pose_back(Self::INPUT, input)?; + let mut pose = ctx.pose_back(Self::INPUT, input)?; let time = pose.timestamp; - let mut pose: BonePoseFrame = pose.data.unwrap(); - let inner_pose = pose.inner_mut(); - if !inner_pose.paths.contains_key(&target) { - inner_pose.add_bone(BoneFrame::default(), target.clone()); + if !pose.paths.contains_key(&target) { + pose.add_bone(BonePose::default(), target.clone()); } // build bone chain @@ -123,26 +119,16 @@ impl NodeLike for RotationNode { RotationSpace::Local => rotation, RotationSpace::Character => { if let Some(parent) = target.parent() { - ctx.root_to_bone_space( - Transform::from_rotation(rotation), - inner_pose, - parent, - time, - ) - .rotation + ctx.root_to_bone_space(Transform::from_rotation(rotation), &pose, parent) + .rotation } else { rotation } } RotationSpace::Global => { if let Some(parent) = target.parent() { - ctx.global_to_bone_space( - Transform::from_rotation(rotation), - inner_pose, - parent, - time, - ) - .rotation + ctx.global_to_bone_space(Transform::from_rotation(rotation), &pose, parent) + .rotation } else { ctx.transform_global_to_character(Transform::from_rotation(rotation)) .rotation @@ -150,41 +136,28 @@ impl NodeLike for RotationNode { } }; - let mut bone_frame = inner_pose + let mut bone_pose = pose .paths .get(&target) - .and_then(|bone_id| inner_pose.bones.get_mut(*bone_id).cloned()) + .and_then(|bone_id| pose.bones.get_mut(*bone_id).cloned()) .unwrap_or_default(); - if let Some(mut rot_frame) = bone_frame.rotation { - let rot = rot_frame.sample_linear_at(time); + if let Some(mut rot) = bone_pose.rotation { let rotation = match self.application_mode { RotationMode::Blend => rot.slerp(rotation_bone_space, percent), RotationMode::Compose => { Quat::IDENTITY.slerp(rotation_bone_space, percent) * rot } }; - rot_frame.prev = rotation; - rot_frame.next = rotation; - bone_frame.rotation = Some(rot_frame); + bone_pose.rotation = Some(rotation); } else { - bone_frame.rotation = Some(ValueFrame { - prev: rotation_bone_space, - prev_timestamp: time - 0.1, - next: rotation_bone_space, - next_timestamp: time + 0.1, - prev_is_wrapped: false, - next_is_wrapped: false, - }); + bone_pose.rotation = Some(rotation_bone_space); } - inner_pose.add_bone(bone_frame, target); + pose.add_bone(bone_pose, target); } - Ok(Some(PoseFrame { - data: PoseFrameData::BoneSpace(pose), - timestamp: time, - })) + Ok(Some(pose)) } fn parameter_input_spec(&self, _ctx: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/space_conversion/extend_skeleton.rs b/crates/bevy_animation_graph/src/nodes/space_conversion/extend_skeleton.rs index b06ea37..46747fe 100644 --- a/crates/bevy_animation_graph/src/nodes/space_conversion/extend_skeleton.rs +++ b/crates/bevy_animation_graph/src/nodes/space_conversion/extend_skeleton.rs @@ -4,11 +4,10 @@ use crate::{ animation_node::{AnimationNode, AnimationNodeType, NodeLike}, duration_data::DurationData, errors::GraphError, - frame::{BonePoseFrame, PoseFrame, PoseFrameData, PoseSpec}, + pose::{Pose, PoseSpec}, space_conversion::SpaceConversion, }, prelude::{PassContext, SpecContext}, - utils::unwrap::Unwrap, }; use bevy::reflect::{std_traits::ReflectDefault, Reflect}; @@ -37,14 +36,10 @@ impl NodeLike for ExtendSkeleton { &self, time_update: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; - let bone_pose_frame: BonePoseFrame = in_pose.data.unwrap(); - Ok(Some(PoseFrame { - data: PoseFrameData::BoneSpace(ctx.extend_skeleton_bone(&bone_pose_frame)), - ..in_pose - })) + Ok(Some(ctx.extend_skeleton_bone(&in_pose))) } fn pose_input_spec(&self, _ctx: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs b/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs index 8593c81..9092ff2 100644 --- a/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs +++ b/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs @@ -4,8 +4,7 @@ use crate::{ animation_node::{AnimationNode, AnimationNodeType, NodeLike}, duration_data::DurationData, errors::GraphError, - frame::{PoseFrame, PoseFrameData, PoseSpec}, - space_conversion::SpaceConversion, + pose::{Pose, PoseSpec}, }, prelude::{PassContext, SpecContext}, }; @@ -36,16 +35,17 @@ impl NodeLike for IntoBoneSpaceNode { &self, time_update: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { - let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; - Ok(Some(PoseFrame { - timestamp: in_pose.timestamp, - data: PoseFrameData::BoneSpace(match &in_pose.data { - PoseFrameData::BoneSpace(data) => data.clone(), - PoseFrameData::CharacterSpace(data) => ctx.character_to_bone(data), - PoseFrameData::GlobalSpace(data) => ctx.global_to_bone(data), - }), - })) + ) -> Result, GraphError> { + // let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; + // Ok(Some(PoseFrame { + // timestamp: in_pose.timestamp, + // data: PoseFrameData::BoneSpace(match &in_pose.data { + // PoseFrameData::BoneSpace(data) => data.clone(), + // PoseFrameData::CharacterSpace(data) => ctx.character_to_bone(data), + // PoseFrameData::GlobalSpace(data) => ctx.global_to_bone(data), + // }), + // })) + todo!() } fn pose_input_spec(&self, _ctx: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs b/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs index 789f28b..1f2253c 100644 --- a/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs +++ b/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs @@ -4,8 +4,7 @@ use crate::{ animation_node::{AnimationNode, AnimationNodeType, NodeLike}, duration_data::DurationData, errors::GraphError, - frame::{PoseFrame, PoseFrameData, PoseSpec}, - space_conversion::SpaceConversion, + pose::{Pose, PoseSpec}, }, prelude::{PassContext, SpecContext}, }; @@ -36,16 +35,17 @@ impl NodeLike for IntoCharacterSpaceNode { &self, time_update: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { - let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; - Ok(Some(PoseFrame { - timestamp: in_pose.timestamp, - data: PoseFrameData::CharacterSpace(match &in_pose.data { - PoseFrameData::BoneSpace(data) => ctx.bone_to_character(data), - PoseFrameData::CharacterSpace(data) => data.clone(), - PoseFrameData::GlobalSpace(data) => ctx.global_to_character(data), - }), - })) + ) -> Result, GraphError> { + // let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; + // Ok(Some(PoseFrame { + // timestamp: in_pose.timestamp, + // data: PoseFrameData::CharacterSpace(match &in_pose.data { + // PoseFrameData::BoneSpace(data) => ctx.bone_to_character(data), + // PoseFrameData::CharacterSpace(data) => data.clone(), + // PoseFrameData::GlobalSpace(data) => ctx.global_to_character(data), + // }), + // })) + todo!() } fn pose_input_spec(&self, _ctx: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs b/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs index a96574a..4d8d66d 100644 --- a/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs +++ b/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs @@ -4,8 +4,7 @@ use crate::{ animation_node::{AnimationNode, AnimationNodeType, NodeLike}, duration_data::DurationData, errors::GraphError, - frame::{PoseFrame, PoseFrameData, PoseSpec}, - space_conversion::SpaceConversion, + pose::{Pose, PoseSpec}, }, prelude::{PassContext, SpecContext}, }; @@ -36,16 +35,17 @@ impl NodeLike for IntoGlobalSpaceNode { &self, time_update: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { - let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; - Ok(Some(PoseFrame { - timestamp: in_pose.timestamp, - data: PoseFrameData::GlobalSpace(match &in_pose.data { - PoseFrameData::BoneSpace(data) => ctx.bone_to_global(data), - PoseFrameData::CharacterSpace(data) => ctx.character_to_global(data), - PoseFrameData::GlobalSpace(data) => data.clone(), - }), - })) + ) -> Result, GraphError> { + // let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; + // Ok(Some(PoseFrame { + // timestamp: in_pose.timestamp, + // data: PoseFrameData::GlobalSpace(match &in_pose.data { + // PoseFrameData::BoneSpace(data) => ctx.bone_to_global(data), + // PoseFrameData::CharacterSpace(data) => ctx.character_to_global(data), + // PoseFrameData::GlobalSpace(data) => data.clone(), + // }), + // })) + todo!() } fn pose_input_spec(&self, _ctx: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/speed_node.rs b/crates/bevy_animation_graph/src/nodes/speed_node.rs index 264d162..f9e8351 100644 --- a/crates/bevy_animation_graph/src/nodes/speed_node.rs +++ b/crates/bevy_animation_graph/src/nodes/speed_node.rs @@ -2,7 +2,7 @@ use crate::core::animation_graph::{PinMap, TimeUpdate}; use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; -use crate::core::frame::{PoseFrame, PoseSpec}; +use crate::core::pose::{Pose, PoseSpec}; use crate::prelude::{OptParamSpec, ParamSpec, PassContext, SpecContext}; use bevy::reflect::std_traits::ReflectDefault; use bevy::reflect::Reflect; @@ -43,19 +43,19 @@ impl NodeLike for SpeedNode { &self, input: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let speed = ctx.parameter_back(Self::SPEED)?.unwrap_f32(); let fw_upd = match input { TimeUpdate::Delta(dt) => TimeUpdate::Delta(dt * speed), TimeUpdate::Absolute(t) => TimeUpdate::Absolute(t * speed), }; - let mut in_pose_frame = ctx.pose_back(Self::INPUT, fw_upd)?; + let mut in_pose = ctx.pose_back(Self::INPUT, fw_upd)?; if speed != 0. { - in_pose_frame.map_ts(|t| t / speed.abs()); + in_pose.timestamp /= speed.abs(); } - Ok(Some(in_pose_frame)) + Ok(Some(in_pose)) } fn parameter_input_spec(&self, _: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/nodes/twoboneik_node.rs b/crates/bevy_animation_graph/src/nodes/twoboneik_node.rs index 5adca6a..f59d63e 100644 --- a/crates/bevy_animation_graph/src/nodes/twoboneik_node.rs +++ b/crates/bevy_animation_graph/src/nodes/twoboneik_node.rs @@ -12,10 +12,10 @@ use crate::{ animation_node::{AnimationNode, AnimationNodeType, NodeLike}, duration_data::DurationData, errors::GraphError, - frame::{BonePoseFrame, PoseFrame, PoseFrameData, PoseSpec}, + pose::{Pose, PoseSpec}, space_conversion::SpaceConversion, }, - prelude::{BoneDebugGizmos, OptParamSpec, ParamSpec, PassContext, SampleLinearAt, SpecContext}, + prelude::{BoneDebugGizmos, OptParamSpec, ParamSpec, PassContext, SpecContext}, utils::unwrap::Unwrap, }; @@ -46,55 +46,41 @@ impl NodeLike for TwoBoneIKNode { &self, input: TimeUpdate, mut ctx: PassContext, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let target: EntityPath = ctx.parameter_back(Self::TARGETBONE)?.unwrap(); let target_pos_char: Vec3 = ctx.parameter_back(Self::TARGETPOS)?.unwrap(); //let targetrotation: Quat = ctx.parameter_back(Self::TARGETROT).unwrap(); - let pose = ctx.pose_back(Self::INPUT, input)?; - let mut bone_pose_data: BonePoseFrame = pose.data.unwrap(); - let inner_pose_data = bone_pose_data.inner_mut(); + let mut pose = ctx.pose_back(Self::INPUT, input)?; if let (Some(bone_id), Some(parent_path), Some(grandparent_path)) = ( - inner_pose_data.paths.get(&target), + pose.paths.get(&target), target.parent(), target.parent().and_then(|p| p.parent()), ) { // Debug render (if enabled) - ctx.bone_gizmo( - target.clone(), - Color::RED, - Some((&inner_pose_data, pose.timestamp)), - ); - ctx.bone_gizmo( - parent_path.clone(), - Color::RED, - Some((&inner_pose_data, pose.timestamp)), - ); + ctx.bone_gizmo(target.clone(), Color::RED, Some(&pose)); + ctx.bone_gizmo(parent_path.clone(), Color::RED, Some(&pose)); - let bone = inner_pose_data.bones[*bone_id].clone(); + let bone = pose.bones[*bone_id].clone(); let target_gp = ctx.root_to_bone_space( Transform::from_translation(target_pos_char), - inner_pose_data, + &pose, grandparent_path.parent().unwrap().clone(), - pose.timestamp, ); let target_pos_gp = target_gp.translation; - let parent_id = inner_pose_data.paths.get(&parent_path).unwrap(); - let parent_frame = { - let parent_bone = inner_pose_data.bones.get_mut(*parent_id).unwrap(); - parent_bone.to_transform_frame_linear() + let parent_id = pose.paths.get(&parent_path).unwrap(); + let parent_transform = { + let parent_bone = pose.bones.get_mut(*parent_id).unwrap(); + parent_bone.to_transform() }; - let parent_transform = parent_frame.sample_linear_at(pose.timestamp); - let grandparent_id = inner_pose_data.paths.get(&grandparent_path).unwrap(); - let grandparent_bone = inner_pose_data.bones.get_mut(*grandparent_id).unwrap(); - let grandparent_frame = grandparent_bone.to_transform_frame_linear(); - let grandparent_transform = grandparent_frame.sample_linear_at(pose.timestamp); + let grandparent_id = pose.paths.get(&grandparent_path).unwrap(); + let grandparent_bone = pose.bones.get_mut(*grandparent_id).unwrap(); + let grandparent_transform = grandparent_bone.to_transform(); - let bone_frame = bone.to_transform_frame_linear(); - let bone_transform = bone_frame.sample_linear_at(pose.timestamp); + let bone_transform = bone.to_transform(); let parent_gp_transform = grandparent_transform * parent_transform; let bone_gp_transform = parent_gp_transform * bone_transform; @@ -113,41 +99,16 @@ impl NodeLike for TwoBoneIKNode { Transform::from_matrix(parent_gp_transform.compute_matrix().inverse()) * bone_gp_transform; - inner_pose_data.bones[*grandparent_id] - .rotation - .as_mut() - .unwrap() - .map_mut(|_| grandparent_transform.rotation); - - inner_pose_data.bones[*parent_id] - .rotation - .as_mut() - .unwrap() - .map_mut(|_| parent_transform.rotation); - - inner_pose_data.bones[*bone_id] - .rotation - .as_mut() - .unwrap() - .map_mut(|_| bone_transform.rotation); + pose.bones[*grandparent_id].rotation = Some(grandparent_transform.rotation); + pose.bones[*parent_id].rotation = Some(parent_transform.rotation); + pose.bones[*bone_id].rotation = Some(bone_transform.rotation); // Debug render (if enabled) - ctx.bone_gizmo( - target.clone(), - Color::BLUE, - Some((&inner_pose_data, pose.timestamp)), - ); - ctx.bone_gizmo( - parent_path.clone(), - Color::BLUE, - Some((&inner_pose_data, pose.timestamp)), - ); + ctx.bone_gizmo(target.clone(), Color::BLUE, Some(&pose)); + ctx.bone_gizmo(parent_path.clone(), Color::BLUE, Some(&pose)); } - Ok(Some(PoseFrame { - data: PoseFrameData::BoneSpace(bone_pose_data), - timestamp: pose.timestamp, - })) + Ok(Some(pose)) } fn parameter_input_spec(&self, _: SpecContext) -> PinMap { diff --git a/crates/bevy_animation_graph/src/sampling/linear.rs b/crates/bevy_animation_graph/src/sampling/linear.rs deleted file mode 100644 index 5af7ac0..0000000 --- a/crates/bevy_animation_graph/src/sampling/linear.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::{ - core::{ - frame::{BoneFrame, BonePoseFrame, InnerPoseFrame, ValueFrame}, - pose::{BonePose, Pose}, - }, - interpolation::linear::InterpolateLinear, -}; -use bevy::prelude::*; - -pub trait SampleLinearAt { - type Output; - fn sample_linear_at(&self, time: f32) -> Self::Output; -} - -impl SampleLinearAt for ValueFrame { - type Output = T; - - fn sample_linear_at(&self, time: f32) -> Self::Output { - let time = time.clamp( - self.prev_timestamp, - // In order to prevent a silly crash - // TODO: A better solution must be found - self.next_timestamp.max(self.prev_timestamp), - ); - let f = if self.prev_timestamp == self.next_timestamp { - 0. - } else { - (time - self.prev_timestamp) / (self.next_timestamp - self.prev_timestamp) - }; - - self.prev.interpolate_linear(&self.next, f) - } -} - -impl SampleLinearAt for BoneFrame { - type Output = BonePose; - - fn sample_linear_at(&self, time: f32) -> Self::Output { - BonePose { - rotation: self.rotation.as_ref().map(|v| v.sample_linear_at(time)), - translation: self.translation.as_ref().map(|v| v.sample_linear_at(time)), - scale: self.scale.as_ref().map(|v| v.sample_linear_at(time)), - weights: self.weights.as_ref().map(|v| v.sample_linear_at(time)), - } - } -} - -impl SampleLinearAt for InnerPoseFrame { - type Output = Pose; - - fn sample_linear_at(&self, time: f32) -> Self::Output { - Pose { - paths: self.paths.clone(), - bones: self - .bones - .iter() - .map(|b| b.sample_linear_at(time)) - .collect(), - } - } -} - -impl SampleLinearAt for BonePoseFrame { - type Output = Pose; - - fn sample_linear_at(&self, time: f32) -> Self::Output { - self.inner_ref().sample_linear_at(time) - } -} diff --git a/crates/bevy_animation_graph/src/sampling/mod.rs b/crates/bevy_animation_graph/src/sampling/mod.rs deleted file mode 100644 index 5ae701a..0000000 --- a/crates/bevy_animation_graph/src/sampling/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod linear; - -pub mod prelude { - pub use super::linear::*; -} From 5192891f0fcad77ad4bc3bb0ee169b7efee871ef Mon Sep 17 00:00:00 2001 From: Manuel Brea Date: Sat, 16 Mar 2024 23:09:58 +0000 Subject: [PATCH 2/4] Somewhat working version of eager sampling --- assets/animation_graphs/human.animgraph.ron | 130 ++++++------ .../bevy_animation_graph/src/chaining/mod.rs | 186 ------------------ .../src/core/animation_graph/core.rs | 20 +- .../src/core/animation_graph/loader.rs | 8 +- .../src/core/animation_graph/serial.rs | 20 +- .../src/core/context/deferred_gizmos.rs | 1 - .../src/core/context/pass_context.rs | 22 ++- .../src/interpolation/linear.rs | 2 + crates/bevy_animation_graph/src/lib.rs | 2 - .../src/nodes/chain_node.rs | 4 +- .../src/nodes/clip_node.rs | 151 ++++++++++---- .../src/nodes/loop_node.rs | 24 ++- .../src/egui_inspector_impls.rs | 2 +- .../src/graph_show.rs | 2 +- 14 files changed, 260 insertions(+), 314 deletions(-) delete mode 100644 crates/bevy_animation_graph/src/chaining/mod.rs diff --git a/assets/animation_graphs/human.animgraph.ron b/assets/animation_graphs/human.animgraph.ron index d7312b2..58a3108 100644 --- a/assets/animation_graphs/human.animgraph.ron +++ b/assets/animation_graphs/human.animgraph.ron @@ -1,37 +1,50 @@ ( nodes: [ ( - name: "Run Clip 2", - node: Clip("animations/human_run.anim.ron", Some(1.0)), + name: "Blend", + node: Blend, ), ( - name: "Walk Chain", - node: Chain, + name: "Walk Clip", + node: Clip("animations/human_walk.anim.ron", None), ), ( - name: "Run Flip LR", - node: FlipLR( - config: ( - name_mapper: Pattern(( - key_1: "L", - key_2: "R", - pattern_before: "^.*", - pattern_after: "$", - )), - ), + name: "Run Chain", + node: Chain( + interpolation_period: 0.5, ), ), ( - name: "Run Chain", - node: Chain, + name: "Walk Clip 2", + node: Clip("animations/human_walk.anim.ron", None), + ), + ( + name: "Rotate", + node: Rotation(Compose, Character, Linear, 1, 1.0), + ), + ( + name: "Walk Chain", + node: Chain( + interpolation_period: 0.5, + ), ), ( name: "Run Clip", - node: Clip("animations/human_run.anim.ron", Some(1.0)), + node: Clip("animations/human_run.anim.ron", None), ), ( - name: "Blend", - node: Blend, + name: "Make Rotation", + node: RotationArc, + ), + ( + name: "Loop", + node: Loop( + interpolation_period: 0.5, + ), + ), + ( + name: "Run Clip 2", + node: Clip("animations/human_run.anim.ron", None), ), ( name: "Walk Flip LR", @@ -46,10 +59,6 @@ ), ), ), - ( - name: "Loop", - node: Loop, - ), ( name: "Speed", node: Speed, @@ -59,45 +68,42 @@ node: Graph("animation_graphs/velocity_to_params.animgraph.ron"), ), ( - name: "Make Rotation", - node: RotationArc, - ), - ( - name: "Walk Clip 2", - node: Clip("animations/human_walk.anim.ron", Some(1.0)), - ), - ( - name: "Rotate", - node: Rotation(Compose, Character, Linear, 1, 1.0), - ), - ( - name: "Walk Clip", - node: Clip("animations/human_walk.anim.ron", Some(1.0)), + name: "Run Flip LR", + node: FlipLR( + config: ( + name_mapper: Pattern(( + key_1: "L", + key_2: "R", + pattern_before: "^.*", + pattern_after: "$", + )), + ), + ), ), ], edges_inverted: { - NodeParameter("Make Rotation", "Vec3 In 1"): InputParameter("Z"), - NodeParameter("Blend", "Factor"): NodeParameter("Param graph", "blend_fac"), NodePose("Loop", "Pose In"): NodePose("Blend"), - NodePose("Rotate", "Pose In"): NodePose("Speed"), - NodePose("Walk Flip LR", "Pose In"): NodePose("Walk Clip 2"), - NodePose("Run Chain", "Pose In 2"): NodePose("Run Flip LR"), + NodeParameter("Make Rotation", "Vec3 In 1"): InputParameter("Z"), NodePose("Speed", "Pose In"): NodePose("Loop"), - NodeParameter("Param graph", "Target Speed"): InputParameter("Target Speed"), - NodePose("Blend", "Pose In 1"): NodePose("Walk Chain"), NodePose("Blend", "Pose In 2"): NodePose("Run Chain"), - NodePose("Run Flip LR", "Pose In"): NodePose("Run Clip 2"), + NodePose("Run Chain", "Pose In 2"): NodePose("Run Flip LR"), NodeParameter("Make Rotation", "Vec3 In 2"): InputParameter("Target Direction"), - NodePose("Run Chain", "Pose In 1"): NodePose("Run Clip"), + NodePose("Blend", "Pose In 1"): NodePose("Walk Chain"), + NodePose("Walk Chain", "Pose In 2"): NodePose("Walk Flip LR"), + OutputPose: NodePose("Rotate"), NodeParameter("Speed", "Speed"): NodeParameter("Param graph", "speed_fac"), - NodePose("Walk Chain", "Pose In 1"): NodePose("Walk Clip"), + NodePose("Walk Flip LR", "Pose In"): NodePose("Walk Clip 2"), NodeParameter("Rotate", "Rotation"): NodeParameter("Make Rotation", "Quat Out"), - OutputPose: NodePose("Rotate"), + NodeParameter("Blend", "Factor"): NodeParameter("Param graph", "blend_fac"), + NodePose("Run Chain", "Pose In 1"): NodePose("Run Clip"), + NodeParameter("Param graph", "Target Speed"): InputParameter("Target Speed"), NodeParameter("Rotate", "Bone Mask"): InputParameter("Rotation Mask"), - NodePose("Walk Chain", "Pose In 2"): NodePose("Walk Flip LR"), + NodePose("Walk Chain", "Pose In 1"): NodePose("Walk Clip"), + NodePose("Rotate", "Pose In"): NodePose("Speed"), + NodePose("Run Flip LR", "Pose In"): NodePose("Run Clip 2"), }, default_parameters: { - "Target Speed": F32(1.5), + "Target Speed": F32(3.0), "Target Direction": Vec3((0.0, 0.0, -1.0)), "Rotation Mask": EntityPath([ "metarig", @@ -110,22 +116,22 @@ output_pose: Some(BoneSpace), extra: ( node_positions: { - "Walk Clip": (-84.0, -288.0), - "Run Flip LR": (-77.0, -21.0), - "Blend": (238.0, -142.0), - "Walk Flip LR": (-82.0, -204.0), - "Run Clip 2": (-216.0, -10.0), + "Blend": (124.0, -240.0), + "Walk Clip": (-215.0, -365.0), + "Run Chain": (-78.0, -62.0), + "Walk Clip 2": (-351.0, -251.0), "Rotate": (692.0, -97.0), - "Run Chain": (60.0, -70.0), - "Run Clip": (-81.0, -102.0), - "Speed": (540.0, -99.0), + "Walk Chain": (-74.0, -303.0), + "Run Clip": (-284.0, -109.0), + "Make Rotation": (502.0, 69.0), "Loop": (384.0, -92.0), + "Run Clip 2": (-441.0, -16.0), + "Walk Flip LR": (-214.0, -247.0), + "Speed": (612.0, -238.0), "Param graph": (135.0, 190.0), - "Make Rotation": (502.0, 69.0), - "Walk Clip 2": (-220.0, -210.0), - "Walk Chain": (61.0, -248.0), + "Run Flip LR": (-271.0, -18.0), }, input_position: (-242.0, 134.0), - output_position: (860.0, -42.0), + output_position: (840.0, -45.0), ), ) \ No newline at end of file diff --git a/crates/bevy_animation_graph/src/chaining/mod.rs b/crates/bevy_animation_graph/src/chaining/mod.rs deleted file mode 100644 index 2eba871..0000000 --- a/crates/bevy_animation_graph/src/chaining/mod.rs +++ /dev/null @@ -1,186 +0,0 @@ -use bevy::prelude::*; - -use crate::core::frame::{ - BoneFrame, InnerPoseFrame, PoseFrame, PoseFrameData, PoseSpec, ValueFrame, -}; - -pub trait Chainable { - fn chain(&self, other: &Self, duration_first: f32, duration_second: f32, time: f32) -> Self; -} - -impl Chainable for ValueFrame { - fn chain(&self, other: &Self, duration_first: f32, duration_second: f32, time: f32) -> Self { - // Note that self and other are queried at the same (relative) time - // i.e. self is queried at `time`, whereas other is queried at `time - duration_first` - // That means that it is possible to have the time query be out of range of timestamps - - if time < duration_first { - match (self.prev_is_wrapped, self.next_is_wrapped) { - (true, false) => Self { - prev: other.prev.clone(), - prev_timestamp: other.prev_timestamp, - next: self.next.clone(), - next_timestamp: self.next_timestamp, - prev_is_wrapped: true, - // next_is_wrapped should never be true when prev_is_wrapped is true - next_is_wrapped: false, - }, - (false, true) => Self { - prev: self.prev.clone(), - prev_timestamp: self.prev_timestamp, - next: other.next.clone(), - next_timestamp: other.next_timestamp + duration_first, - prev_is_wrapped: false, - next_is_wrapped: false, - }, - (false, false) => self.clone(), - (true, true) => { - panic!("prev_is_wrapped and next_is_wrapped should never both be true!") - } - } - } else { - match (other.prev_is_wrapped, other.next_is_wrapped) { - (true, false) => Self { - prev: self.prev.clone(), - prev_timestamp: self.prev_timestamp, - next: other.next.clone(), - next_timestamp: other.next_timestamp + duration_first, - prev_is_wrapped: false, - next_is_wrapped: false, - }, - (false, true) => Self { - prev: other.prev.clone(), - prev_timestamp: other.prev_timestamp + duration_first, - next: self.next.clone(), - next_timestamp: self.next_timestamp + duration_second, - // prev_is_wrapped should never be true when next_is_wrapped is true - prev_is_wrapped: false, - next_is_wrapped: true, - }, - (false, false) => { - let mut out = other.clone(); - out.map_ts(|t| t + duration_first); - out - } - (true, true) => { - panic!("prev_is_wrapped and next_is_wrapped should never both be true!") - } - } - } - } -} - -impl Chainable for Option> { - fn chain(&self, other: &Self, duration_first: f32, duration_second: f32, time: f32) -> Self { - match (self, other) { - (Some(frame_1), Some(frame_2)) => { - Some(frame_1.chain(frame_2, duration_first, duration_second, time)) - } - (None, None) => None, - (None, Some(frame_2)) => { - let mut out = frame_2.clone(); - out.map_ts(|t| t + duration_first); - Some(out) - } - (Some(frame_1), None) => { - let mut out = frame_1.clone(); - - if out.next_is_wrapped { - out.next_timestamp += duration_second; - } - - Some(out) - } - } - } -} - -impl Chainable for BoneFrame { - fn chain(&self, other: &Self, duration_first: f32, duration_second: f32, time: f32) -> Self { - Self { - rotation: self - .rotation - .chain(&other.rotation, duration_first, duration_second, time), - translation: self.translation.chain( - &other.translation, - duration_first, - duration_second, - time, - ), - scale: self - .scale - .chain(&other.scale, duration_first, duration_second, time), - weights: self - .weights - .chain(&other.weights, duration_first, duration_second, time), - } - } -} - -impl Chainable for InnerPoseFrame { - fn chain(&self, other: &Self, duration_first: f32, duration_second: f32, time: f32) -> Self { - let mut result = InnerPoseFrame::default(); - - for (path, bone_id) in self.paths.iter() { - let Some(other_bone_id) = other.paths.get(path) else { - continue; - }; - - result.add_bone( - self.bones[*bone_id].chain( - &other.bones[*other_bone_id], - duration_first, - duration_second, - time, - ), - path.clone(), - ); - } - - result - } -} - -impl Chainable for PoseFrameData { - fn chain(&self, other: &Self, duration_first: f32, duration_second: f32, time: f32) -> Self { - match (&self, &other) { - (PoseFrameData::BoneSpace(f1), PoseFrameData::BoneSpace(f2)) => { - PoseFrameData::BoneSpace( - f1.inner_ref() - .chain(f2.inner_ref(), duration_first, duration_second, time) - .into(), - ) - } - (PoseFrameData::CharacterSpace(f1), PoseFrameData::CharacterSpace(f2)) => { - PoseFrameData::CharacterSpace( - f1.inner_ref() - .chain(f2.inner_ref(), duration_first, duration_second, time) - .into(), - ) - } - (PoseFrameData::GlobalSpace(f1), PoseFrameData::GlobalSpace(f2)) => { - PoseFrameData::GlobalSpace( - f1.inner_ref() - .chain(f2.inner_ref(), duration_first, duration_second, time) - .into(), - ) - } - _ => panic!( - "Tried to chain {:?} with {:?}", - PoseSpec::from(self), - PoseSpec::from(other) - ), - } - } -} - -impl Chainable for PoseFrame { - fn chain(&self, other: &Self, duration_first: f32, duration_second: f32, time: f32) -> Self { - Self { - data: self - .data - .chain(&other.data, duration_first, duration_second, time), - timestamp: time, - } - } -} diff --git a/crates/bevy_animation_graph/src/core/animation_graph/core.rs b/crates/bevy_animation_graph/src/core/animation_graph/core.rs index 60f3b89..a57979c 100644 --- a/crates/bevy_animation_graph/src/core/animation_graph/core.rs +++ b/crates/bevy_animation_graph/src/core/animation_graph/core.rs @@ -746,8 +746,10 @@ impl AnimationGraph { return Err(GraphError::MissingInputEdge(target_pin)); }; - if let Some(val) = ctx.context().get_pose(source_pin) { - return Ok(val.clone()); + if ctx.cached_query { + if let Some(val) = ctx.context().get_pose(source_pin) { + return Ok(val.clone()); + } } let source_value = match source_pin { @@ -767,8 +769,10 @@ impl AnimationGraph { )? .unwrap(); - ctx.context().set_pose(source_pin.clone(), output.clone()); - ctx.context().set_time(source_pin.clone(), output.timestamp); + if ctx.cached_query { + ctx.context().set_pose(source_pin.clone(), output.clone()); + ctx.context().set_time(source_pin.clone(), output.timestamp); + } output } @@ -816,7 +820,7 @@ impl AnimationGraph { deferred_gizmos: &mut DeferredGizmos, ) -> Result { context.push_caches(); - let out = self.get_pose( + Ok(self.get_pose( time_update, TargetPin::OutputPose, PassContext::new( @@ -827,11 +831,7 @@ impl AnimationGraph { entity_map, deferred_gizmos, ), - )?; - let time = out.timestamp; - let bone_frame: BonePoseFrame = out.data.unwrap(); - - Ok(bone_frame.sample_linear_at(time)) + )?) } // ---------------------------------------------------------------------------------------- } diff --git a/crates/bevy_animation_graph/src/core/animation_graph/loader.rs b/crates/bevy_animation_graph/src/core/animation_graph/loader.rs index 3f243b5..396d9b2 100644 --- a/crates/bevy_animation_graph/src/core/animation_graph/loader.rs +++ b/crates/bevy_animation_graph/src/core/animation_graph/loader.rs @@ -128,11 +128,15 @@ impl AssetLoader for AnimationGraphLoader { .wrapped(&serial_node.name) } AnimationNodeTypeSerial::Blend => BlendNode::new().wrapped(&serial_node.name), - AnimationNodeTypeSerial::Chain => ChainNode::new().wrapped(&serial_node.name), + AnimationNodeTypeSerial::Chain { + interpolation_period, + } => ChainNode::new(*interpolation_period).wrapped(&serial_node.name), AnimationNodeTypeSerial::FlipLR { config } => { FlipLRNode::new(config.clone()).wrapped(&serial_node.name) } - AnimationNodeTypeSerial::Loop => LoopNode::new().wrapped(&serial_node.name), + AnimationNodeTypeSerial::Loop { + interpolation_period, + } => LoopNode::new(*interpolation_period).wrapped(&serial_node.name), AnimationNodeTypeSerial::Speed => SpeedNode::new().wrapped(&serial_node.name), AnimationNodeTypeSerial::Rotation(mode, space, decay, length, base_weight) => { RotationNode::new(*mode, *space, *decay, *length, *base_weight) diff --git a/crates/bevy_animation_graph/src/core/animation_graph/serial.rs b/crates/bevy_animation_graph/src/core/animation_graph/serial.rs index 1ccc1da..1fcef9f 100644 --- a/crates/bevy_animation_graph/src/core/animation_graph/serial.rs +++ b/crates/bevy_animation_graph/src/core/animation_graph/serial.rs @@ -1,6 +1,6 @@ use super::{pin, AnimationGraph, Extra}; use crate::{ - core::frame::PoseSpec, + core::pose::PoseSpec, prelude::{ config::FlipConfig, AnimationNode, AnimationNodeType, ChainDecay, ParamSpec, ParamValue, RotationMode, RotationSpace, @@ -57,12 +57,18 @@ pub struct AnimationNodeSerial { pub enum AnimationNodeTypeSerial { Clip(String, Option), Blend, - Chain, + Chain { + #[serde(default)] + interpolation_period: f32, + }, FlipLR { #[serde(default)] config: FlipConfig, }, - Loop, + Loop { + #[serde(default)] + interpolation_period: f32, + }, Speed, Rotation( RotationMode, @@ -135,11 +141,15 @@ impl From<&AnimationNodeType> for AnimationNodeTypeSerial { n.override_duration, ), AnimationNodeType::Blend(_) => AnimationNodeTypeSerial::Blend, - AnimationNodeType::Chain(_) => AnimationNodeTypeSerial::Chain, + AnimationNodeType::Chain(n) => AnimationNodeTypeSerial::Chain { + interpolation_period: n.interpolation_period, + }, AnimationNodeType::FlipLR(n) => AnimationNodeTypeSerial::FlipLR { config: n.config.clone(), }, - AnimationNodeType::Loop(_) => AnimationNodeTypeSerial::Loop, + AnimationNodeType::Loop(n) => AnimationNodeTypeSerial::Loop { + interpolation_period: n.interpolation_period, + }, AnimationNodeType::Speed(_) => AnimationNodeTypeSerial::Speed, AnimationNodeType::Rotation(n) => AnimationNodeTypeSerial::Rotation( n.application_mode, diff --git a/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs b/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs index fc7c737..1c789bd 100644 --- a/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs +++ b/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs @@ -1,6 +1,5 @@ use super::PassContext; use crate::core::{ - frame::InnerPoseFrame, pose::{BoneId, Pose}, space_conversion::SpaceConversion, }; diff --git a/crates/bevy_animation_graph/src/core/context/pass_context.rs b/crates/bevy_animation_graph/src/core/context/pass_context.rs index 3bd9792..a947d29 100644 --- a/crates/bevy_animation_graph/src/core/context/pass_context.rs +++ b/crates/bevy_animation_graph/src/core/context/pass_context.rs @@ -5,7 +5,6 @@ use crate::{ animation_graph::{InputOverlay, NodeId, PinId, SourcePin, TargetPin, TimeUpdate}, duration_data::DurationData, errors::GraphError, - frame::PoseFrame, pose::{BoneId, Pose}, }, prelude::{AnimationGraph, ParamValue}, @@ -29,6 +28,9 @@ pub struct PassContext<'a> { pub root_entity: Entity, pub entity_map: &'a HashMap, pub deferred_gizmos: DeferredGizmoRef, + /// Whether this query should mutate chaches. Useful when getting a pose back but not wanting + /// to use the time query to update the times + pub cached_query: bool, pub should_debug: bool, } @@ -51,6 +53,7 @@ impl<'a> PassContext<'a> { root_entity, entity_map, deferred_gizmos: deferred_gizmos.into(), + cached_query: true, should_debug: false, } } @@ -67,6 +70,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), + cached_query: self.cached_query, should_debug: self.should_debug, } } @@ -83,6 +87,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), + cached_query: self.cached_query, should_debug: self.should_debug, } } @@ -98,6 +103,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), + cached_query: self.cached_query, should_debug, } } @@ -117,6 +123,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), + cached_query: self.cached_query, should_debug: self.should_debug, } } @@ -166,6 +173,19 @@ impl<'a> PassContext<'a> { .get_pose(time_update, target_pin, self.without_node()) } + /// Request an input pose. + pub fn uncached_pose_back( + &mut self, + pin_id: impl Into, + time_update: TimeUpdate, + ) -> Result { + let node_ctx = self.node_context.unwrap(); + let target_pin = TargetPin::NodePose(node_ctx.node_id.clone(), pin_id.into()); + let mut ctx = self.without_node(); + ctx.cached_query = false; + node_ctx.graph.get_pose(time_update, target_pin, ctx) + } + /// Request the cached time update query from the current frame pub fn time_update_fwd(&self) -> TimeUpdate { let node_ctx = self.node_context.unwrap(); diff --git a/crates/bevy_animation_graph/src/interpolation/linear.rs b/crates/bevy_animation_graph/src/interpolation/linear.rs index 3e5b4b2..54f9aaa 100644 --- a/crates/bevy_animation_graph/src/interpolation/linear.rs +++ b/crates/bevy_animation_graph/src/interpolation/linear.rs @@ -105,6 +105,8 @@ impl InterpolateLinear for Pose { result.add_bone(other.bones[*bone_id].clone(), path.clone()); } + result.timestamp = self.timestamp; + result } } diff --git a/crates/bevy_animation_graph/src/lib.rs b/crates/bevy_animation_graph/src/lib.rs index 0b71ce4..b9c67b4 100644 --- a/crates/bevy_animation_graph/src/lib.rs +++ b/crates/bevy_animation_graph/src/lib.rs @@ -226,7 +226,6 @@ //! [`AnimationGraphPlayer`]: crate::core::animation_graph_player::AnimationGraphPlayer //! [`AnimatedScene`]: crate::core::animated_scene::AnimatedScene -pub mod chaining; pub mod core; pub mod flipping; pub mod interpolation; @@ -234,7 +233,6 @@ pub mod nodes; mod utils; pub mod prelude { - pub use super::chaining::*; pub use super::core::prelude::*; pub use super::flipping::*; pub use super::interpolation::linear::*; diff --git a/crates/bevy_animation_graph/src/nodes/chain_node.rs b/crates/bevy_animation_graph/src/nodes/chain_node.rs index be50756..dea8ea1 100644 --- a/crates/bevy_animation_graph/src/nodes/chain_node.rs +++ b/crates/bevy_animation_graph/src/nodes/chain_node.rs @@ -36,7 +36,9 @@ impl NodeLike for ChainNode { let source_duration_2 = ctx.duration_back(Self::INPUT_2)?; let out_duration = match (source_duration_1, source_duration_2) { - (Some(duration_1), Some(duration_2)) => Some(duration_1 + duration_2), + (Some(duration_1), Some(duration_2)) => { + Some(duration_1 + duration_2 + self.interpolation_period) + } (Some(_), None) => None, (None, Some(_)) => None, (None, None) => None, diff --git a/crates/bevy_animation_graph/src/nodes/clip_node.rs b/crates/bevy_animation_graph/src/nodes/clip_node.rs index d63f854..82e7106 100644 --- a/crates/bevy_animation_graph/src/nodes/clip_node.rs +++ b/crates/bevy_animation_graph/src/nodes/clip_node.rs @@ -65,6 +65,8 @@ impl NodeLike for ClipNode { let mut out_pose = Pose::default(); out_pose.timestamp = time; + let time = time.clamp(0., clip_duration); + for (path, bone_id) in &clip.paths { let curves = clip.get_curves(*bone_id).unwrap(); let mut bone_pose = BonePose::default(); @@ -88,6 +90,29 @@ impl NodeLike for ClipNode { Err(i) => (i - 1, i, false, false), }; + if prev_is_wrapped { + sample_one_keyframe(step_end, keyframe_count, &curve.keyframes, &mut bone_pose); + continue; + } + if next_is_wrapped { + sample_one_keyframe( + step_start, + keyframe_count, + &curve.keyframes, + &mut bone_pose, + ); + continue; + } + if step_start == step_end { + sample_one_keyframe( + step_start, + keyframe_count, + &curve.keyframes, + &mut bone_pose, + ); + continue; + } + let mut prev_timestamp = curve.keyframe_timestamps[step_start]; let mut next_timestamp = curve.keyframe_timestamps[step_end]; @@ -97,42 +122,16 @@ impl NodeLike for ClipNode { next_timestamp += clip_duration; } - let lerp = (time - prev_timestamp) / (next_timestamp - prev_timestamp); - - // Apply the keyframe - match &curve.keyframes { - Keyframes::Rotation(keyframes) => { - let prev = keyframes[step_start]; - let mut next = keyframes[step_end]; - // Choose the smallest angle for the rotation - if next.dot(prev) < 0.0 { - next = -next; - } - - bone_pose.rotation = Some(prev.interpolate_linear(&next, lerp)); - } - Keyframes::Translation(keyframes) => { - let prev = keyframes[step_start]; - let next = keyframes[step_end]; - - bone_pose.translation = Some(prev.interpolate_linear(&next, lerp)); - } - - Keyframes::Scale(keyframes) => { - let prev = keyframes[step_start]; - let next = keyframes[step_end]; - bone_pose.scale = Some(prev.interpolate_linear(&next, lerp)); - } - - Keyframes::Weights(keyframes) => { - let target_count = keyframes.len() / keyframe_count; - let morph_start: Vec = - get_keyframe(target_count, keyframes, step_start).into(); - let morph_end: Vec = - get_keyframe(target_count, keyframes, step_end).into(); - bone_pose.weights = Some(morph_end.interpolate_linear(&morph_end, lerp)); - } - } + sample_two_keyframes( + step_start, + step_end, + prev_timestamp, + next_timestamp, + time, + keyframe_count, + &curve.keyframes, + &mut bone_pose, + ); } out_pose.add_bone(bone_pose, path.clone()); } @@ -148,3 +147,83 @@ impl NodeLike for ClipNode { "⏵ Animation Clip".into() } } + +fn sample_two_keyframes( + step_start: usize, + step_end: usize, + prev_timestamp: f32, + next_timestamp: f32, + time: f32, + keyframe_count: usize, + keyframes: &Keyframes, + bone_pose: &mut BonePose, +) { + let lerp = if next_timestamp == prev_timestamp { + 1. + } else { + (time - prev_timestamp) / (next_timestamp - prev_timestamp) + }; + + // Apply the keyframe + match keyframes { + Keyframes::Rotation(keyframes) => { + let prev = keyframes[step_start]; + let mut next = keyframes[step_end]; + // Choose the smallest angle for the rotation + if next.dot(prev) < 0.0 { + next = -next; + } + + bone_pose.rotation = Some(prev.interpolate_linear(&next, lerp)); + } + Keyframes::Translation(keyframes) => { + let prev = keyframes[step_start]; + let next = keyframes[step_end]; + + bone_pose.translation = Some(prev.interpolate_linear(&next, lerp)); + } + + Keyframes::Scale(keyframes) => { + let prev = keyframes[step_start]; + let next = keyframes[step_end]; + bone_pose.scale = Some(prev.interpolate_linear(&next, lerp)); + } + + Keyframes::Weights(keyframes) => { + let target_count = keyframes.len() / keyframe_count; + let morph_start: Vec = get_keyframe(target_count, keyframes, step_start).into(); + let morph_end: Vec = get_keyframe(target_count, keyframes, step_end).into(); + bone_pose.weights = Some(morph_start.interpolate_linear(&morph_end, lerp)); + } + } +} + +fn sample_one_keyframe( + step: usize, + keyframe_count: usize, + keyframes: &Keyframes, + bone_pose: &mut BonePose, +) { + match keyframes { + Keyframes::Rotation(keyframes) => { + let frame = keyframes[step]; + + bone_pose.rotation = Some(frame); + } + Keyframes::Translation(keyframes) => { + let frame = keyframes[step]; + bone_pose.translation = Some(frame); + } + + Keyframes::Scale(keyframes) => { + let frame = keyframes[step]; + bone_pose.scale = Some(frame); + } + + Keyframes::Weights(keyframes) => { + let target_count = keyframes.len() / keyframe_count; + let morph_start: Vec = get_keyframe(target_count, keyframes, step).into(); + bone_pose.weights = Some(morph_start); + } + } +} diff --git a/crates/bevy_animation_graph/src/nodes/loop_node.rs b/crates/bevy_animation_graph/src/nodes/loop_node.rs index effe7f8..066739f 100644 --- a/crates/bevy_animation_graph/src/nodes/loop_node.rs +++ b/crates/bevy_animation_graph/src/nodes/loop_node.rs @@ -3,6 +3,7 @@ use crate::core::animation_node::{AnimationNode, AnimationNodeType, NodeLike}; use crate::core::duration_data::DurationData; use crate::core::errors::GraphError; use crate::core::pose::{Pose, PoseSpec}; +use crate::interpolation::prelude::InterpolateLinear; use crate::prelude::{ParamValue, PassContext, SpecContext}; use bevy::prelude::*; use bevy::utils::HashMap; @@ -10,15 +11,17 @@ use bevy::utils::HashMap; #[derive(Reflect, Clone, Debug, Default)] #[reflect(Default)] pub struct LoopNode { - // TODO: Interpolation period, like in chain node + pub interpolation_period: f32, } impl LoopNode { pub const INPUT: &'static str = "Pose In"; pub const OUTPUT: &'static str = "Pose Out"; - pub fn new() -> Self { - Self {} + pub fn new(interpolation_period: f32) -> Self { + Self { + interpolation_period, + } } pub fn wrapped(self, name: impl Into) -> AnimationNode { @@ -46,13 +49,15 @@ impl NodeLike for LoopNode { return Ok(Some(ctx.pose_back(Self::INPUT, input)?)); }; + let full_duration = duration + self.interpolation_period; + let prev_time = ctx.prev_time_fwd(); let curr_time = input.apply(prev_time); - let t = curr_time.rem_euclid(duration); + let t = curr_time.rem_euclid(full_duration); let fw_upd = match input { TimeUpdate::Delta(dt) => { - if prev_time.div_euclid(duration) != curr_time.div_euclid(duration) { + if prev_time.div_euclid(full_duration) != curr_time.div_euclid(full_duration) { TimeUpdate::Absolute(t) } else { TimeUpdate::Delta(dt) @@ -63,7 +68,14 @@ impl NodeLike for LoopNode { let mut pose = ctx.pose_back(Self::INPUT, fw_upd)?; - let t_extra = curr_time.div_euclid(duration) * duration; + if t > duration && t < full_duration { + let start_pose = ctx.uncached_pose_back(Self::INPUT, TimeUpdate::Absolute(0.))?; + let old_time = pose.timestamp; + pose = pose.interpolate_linear(&start_pose, (t - duration) / self.interpolation_period); + pose.timestamp = old_time; + } + + let t_extra = curr_time.div_euclid(full_duration) * full_duration; pose.timestamp += t_extra; Ok(Some(pose)) diff --git a/crates/bevy_animation_graph_editor/src/egui_inspector_impls.rs b/crates/bevy_animation_graph_editor/src/egui_inspector_impls.rs index 8ec7a25..83affc1 100644 --- a/crates/bevy_animation_graph_editor/src/egui_inspector_impls.rs +++ b/crates/bevy_animation_graph_editor/src/egui_inspector_impls.rs @@ -10,8 +10,8 @@ use bevy_animation_graph::{ core::{ animation_clip::{EntityPath, GraphClip}, animation_graph::{AnimationGraph, PinId}, - frame::PoseSpec, parameters::{BoneMask, ParamSpec, ParamValue}, + pose::PoseSpec, }, flipping::config::{PatternMapper, PatternMapperSerial}, prelude::OrderedMap, diff --git a/crates/bevy_animation_graph_editor/src/graph_show.rs b/crates/bevy_animation_graph_editor/src/graph_show.rs index 332fb7b..c9f1e53 100644 --- a/crates/bevy_animation_graph_editor/src/graph_show.rs +++ b/crates/bevy_animation_graph_editor/src/graph_show.rs @@ -9,8 +9,8 @@ use bevy_animation_graph::core::{ animation_graph::{AnimationGraph, SourcePin, TargetPin}, animation_node::NodeLike, context::SpecContext, - frame::PoseSpec, parameters::ParamSpec, + pose::PoseSpec, }; use bevy_inspector_egui::egui::Color32; From 6ca1deada70dc8d82681fa46a9171dff802cce3d Mon Sep 17 00:00:00 2001 From: Manuel Brea Date: Sun, 17 Mar 2024 13:57:34 +0000 Subject: [PATCH 3/4] Update rest of examples --- assets/animation_graphs/fox.animgraph.ron | 6 +- .../animation_graphs/human_ik.animgraph.ron | 140 +++++++++--------- 2 files changed, 76 insertions(+), 70 deletions(-) diff --git a/assets/animation_graphs/fox.animgraph.ron b/assets/animation_graphs/fox.animgraph.ron index b2a293a..c15d58d 100644 --- a/assets/animation_graphs/fox.animgraph.ron +++ b/assets/animation_graphs/fox.animgraph.ron @@ -10,7 +10,7 @@ ), ( name: "Loop Walk", - node: Loop, + node: Loop(), ), ( name: "Run Clip", @@ -26,7 +26,7 @@ ), ( name: "Loop Run", - node: Loop, + node: Loop(), ), ], edges_inverted: { @@ -63,4 +63,4 @@ input_position: (-381.0, 260.0), output_position: (269.0, 173.0), ), -) \ No newline at end of file +) diff --git a/assets/animation_graphs/human_ik.animgraph.ron b/assets/animation_graphs/human_ik.animgraph.ron index 8de5a32..9804cb0 100644 --- a/assets/animation_graphs/human_ik.animgraph.ron +++ b/assets/animation_graphs/human_ik.animgraph.ron @@ -1,52 +1,67 @@ ( nodes: [ ( - name: "Blend", - node: Blend, + name: "Walk Chain", + node: Chain( + interpolation_period: 0.5, + ), ), ( - name: "Rotate", - node: Rotation(Compose, Character, Linear, 1, 1.0), + name: "Loop", + node: Loop( + interpolation_period: 0.5, + ), ), ( - name: "Facing rotation", - node: RotationArc, + name: "Run Clip", + node: Clip("animations/human_run.anim.ron", None), ), ( - name: "IK left hand", - node: TwoBoneIK, + name: "Facing rotation", + node: RotationArc, ), ( - name: "Run Clip", - node: Clip("animations/human_run.anim.ron", Some(1.0)), + name: "Run Flip LR", + node: FlipLR( + config: ( + name_mapper: Pattern(( + key_1: "L", + key_2: "R", + pattern_before: "^.*", + pattern_after: "$", + )), + ), + ), ), ( name: "Run Clip 2", - node: Clip("animations/human_run.anim.ron", Some(1.0)), + node: Clip("animations/human_run.anim.ron", None), ), ( - name: "Param graph", - node: Graph("animation_graphs/velocity_to_params.animgraph.ron"), + name: "Extend to skeleton", + node: ExtendSkeleton, ), ( - name: "Walk Chain", - node: Chain, + name: "Rotate Test", + node: Rotation(Blend, Character, Linear, 5, 1.0), ), ( name: "Walk Clip 2", - node: Clip("animations/human_walk.anim.ron", Some(1.0)), + node: Clip("animations/human_walk.anim.ron", None), ), ( - name: "Extend to skeleton", - node: ExtendSkeleton, + name: "IK left hand", + node: TwoBoneIK, ), ( - name: "Walk Clip", - node: Clip("animations/human_walk.anim.ron", Some(1.0)), + name: "Run Chain", + node: Chain( + interpolation_period: 0.5, + ), ), ( - name: "Speed", - node: Speed, + name: "Rotate", + node: Rotation(Compose, Character, Linear, 1, 1.0), ), ( name: "Walk Flip LR", @@ -62,58 +77,49 @@ ), ), ( - name: "Rotate Test", - node: Rotation(Blend, Character, Linear, 5, 1.0), + name: "Param graph", + node: Graph("animation_graphs/velocity_to_params.animgraph.ron"), ), ( - name: "Run Chain", - node: Chain, + name: "Blend", + node: Blend, ), ( - name: "Run Flip LR", - node: FlipLR( - config: ( - name_mapper: Pattern(( - key_1: "L", - key_2: "R", - pattern_before: "^.*", - pattern_after: "$", - )), - ), - ), + name: "Walk Clip", + node: Clip("animations/human_walk.anim.ron", None), ), ( - name: "Loop", - node: Loop, + name: "Speed", + node: Speed, ), ], edges_inverted: { + NodeParameter("IK left hand", "Target Position"): InputParameter("Target Position"), + NodeParameter("Rotate Test", "Bone Mask"): InputParameter("Rotate Test Target"), + NodePose("Rotate Test", "Pose In"): NodePose("Extend to skeleton"), + NodePose("Run Chain", "Pose In 2"): NodePose("Run Flip LR"), + NodeParameter("Speed", "Speed"): NodeParameter("Param graph", "speed_fac"), + NodeParameter("Facing rotation", "Vec3 In 1"): InputParameter("Z"), + NodePose("Speed", "Pose In"): NodePose("Loop"), + NodeParameter("Param graph", "Target Speed"): InputParameter("Target Speed"), NodeParameter("Rotate", "Rotation"): NodeParameter("Facing rotation", "Quat Out"), - NodeParameter("Blend", "Factor"): NodeParameter("Param graph", "blend_fac"), - NodeParameter("Facing rotation", "Vec3 In 2"): InputParameter("Target Direction"), NodeParameter("IK left hand", "Target Path"): InputParameter("Target Path"), - NodePose("Blend", "Pose In 1"): NodePose("Walk Chain"), - NodeParameter("IK left hand", "Target Position"): InputParameter("Target Position"), + NodePose("Rotate", "Pose In"): NodePose("Speed"), + NodeParameter("Facing rotation", "Vec3 In 2"): InputParameter("Target Direction"), NodePose("Walk Chain", "Pose In 2"): NodePose("Walk Flip LR"), - NodeParameter("Facing rotation", "Vec3 In 1"): InputParameter("Z"), NodeParameter("Rotate Test", "Rotation"): InputParameter("Rotate Test Quat"), - NodeParameter("Speed", "Speed"): NodeParameter("Param graph", "speed_fac"), - NodePose("IK left hand", "Pose In"): NodePose("Rotate Test"), - NodePose("Walk Flip LR", "Pose In"): NodePose("Walk Clip 2"), - NodePose("Run Chain", "Pose In 2"): NodePose("Run Flip LR"), - NodeParameter("Param graph", "Target Speed"): InputParameter("Target Speed"), - OutputPose: NodePose("IK left hand"), - NodeParameter("Rotate Test", "Bone Mask"): InputParameter("Rotate Test Target"), + NodePose("Blend", "Pose In 1"): NodePose("Walk Chain"), NodePose("Loop", "Pose In"): NodePose("Blend"), - NodePose("Rotate", "Pose In"): NodePose("Speed"), NodePose("Run Flip LR", "Pose In"): NodePose("Run Clip 2"), - NodePose("Speed", "Pose In"): NodePose("Loop"), NodePose("Blend", "Pose In 2"): NodePose("Run Chain"), + OutputPose: NodePose("IK left hand"), + NodePose("Walk Flip LR", "Pose In"): NodePose("Walk Clip 2"), NodePose("Extend to skeleton", "Pose In"): NodePose("Rotate"), NodePose("Walk Chain", "Pose In 1"): NodePose("Walk Clip"), NodeParameter("Rotate", "Bone Mask"): InputParameter("Rotation Mask"), NodePose("Run Chain", "Pose In 1"): NodePose("Run Clip"), - NodePose("Rotate Test", "Pose In"): NodePose("Extend to skeleton"), + NodePose("IK left hand", "Pose In"): NodePose("Rotate Test"), + NodeParameter("Blend", "Factor"): NodeParameter("Param graph", "blend_fac"), }, default_parameters: { "Z": Vec3((0.0, 0.0, 1.0)), @@ -152,25 +158,25 @@ output_pose: Some(BoneSpace), extra: ( node_positions: { - "Blend": (382.0, -155.0), - "Rotate": (810.0, -30.0), - "Facing rotation": (635.0, 317.0), - "IK left hand": (1230.0, 59.0), + "Walk Chain": (163.0, -191.0), + "Loop": (535.0, -26.0), "Run Clip": (19.0, -44.0), + "Facing rotation": (635.0, 317.0), + "Run Flip LR": (16.0, 47.0), "Run Clip 2": (-120.0, 52.0), - "Param graph": (235.0, 155.0), - "Walk Chain": (163.0, -191.0), - "Walk Clip 2": (-120.0, -144.0), "Extend to skeleton": (937.0, -3.0), - "Walk Clip": (20.0, -239.0), - "Speed": (670.0, -28.0), - "Walk Flip LR": (20.0, -150.0), "Rotate Test": (1073.0, 23.0), + "Walk Clip 2": (-120.0, -144.0), + "IK left hand": (1230.0, 59.0), "Run Chain": (170.0, -4.0), - "Run Flip LR": (16.0, 47.0), - "Loop": (535.0, -26.0), + "Rotate": (810.0, -30.0), + "Walk Flip LR": (20.0, -150.0), + "Param graph": (235.0, 155.0), + "Blend": (382.0, -155.0), + "Walk Clip": (20.0, -239.0), + "Speed": (670.0, -28.0), }, input_position: (69.0, 330.0), output_position: (1270.0, 258.0), ), -) \ No newline at end of file +) From fecfd07b954a9ef89738abb8e78cf6107cecb3e7 Mon Sep 17 00:00:00 2001 From: Manuel Brea Date: Sun, 17 Mar 2024 15:12:06 +0000 Subject: [PATCH 4/4] Fixed situation with temp caching, appeased clippy --- .../src/core/animation_graph/core.rs | 73 +++++- .../bevy_animation_graph/src/core/caches.rs | 37 --- .../src/core/context/graph_context.rs | 228 ++++++++---------- .../src/core/context/pass_context.rs | 38 ++- crates/bevy_animation_graph/src/core/mod.rs | 1 - .../src/core/space_conversion.rs | 2 +- .../bevy_animation_graph/src/flipping/mod.rs | 6 +- .../src/interpolation/linear.rs | 12 +- .../src/nodes/clip_node.rs | 7 +- .../src/nodes/loop_node.rs | 3 +- .../src/nodes/rotation_node.rs | 3 +- .../src/nodes/space_conversion/into_bone.rs | 4 +- .../nodes/space_conversion/into_character.rs | 4 +- .../src/nodes/space_conversion/into_global.rs | 4 +- 14 files changed, 212 insertions(+), 210 deletions(-) delete mode 100644 crates/bevy_animation_graph/src/core/caches.rs diff --git a/crates/bevy_animation_graph/src/core/animation_graph/core.rs b/crates/bevy_animation_graph/src/core/animation_graph/core.rs index a57979c..d5c2771 100644 --- a/crates/bevy_animation_graph/src/core/animation_graph/core.rs +++ b/crates/bevy_animation_graph/src/core/animation_graph/core.rs @@ -10,7 +10,7 @@ use crate::{ DeferredGizmos, GraphContext, OptParamSpec, ParamSpec, ParamValue, PassContext, SpecContext, SystemResources, }, - utils::{ordered_map::OrderedMap, unwrap::Unwrap}, + utils::ordered_map::OrderedMap, }; use bevy::{ prelude::*, @@ -661,8 +661,14 @@ impl AnimationGraph { let outputs = node.parameter_pass(ctx.with_node(node_id, self).with_debugging(should_debug))?; + let active_cache = if ctx.temp_cache { + ctx.context().caches.get_temp_cache_mut() + } else { + ctx.context().caches.get_cache_mut() + }; + for (pin_id, value) in outputs.iter() { - ctx.context().set_parameter( + active_cache.set_parameter( SourcePin::NodeParameter(node_id.clone(), pin_id.clone()), value.clone(), ); @@ -718,8 +724,12 @@ impl AnimationGraph { node.duration_pass(ctx.with_node(node_id, self).with_debugging(should_debug))?; if let Some(value) = output { - ctx.context() - .set_duration(SourcePin::NodePose(node_id.clone()), value); + let active_cache = if ctx.temp_cache { + ctx.context().caches.get_temp_cache_mut() + } else { + ctx.context().caches.get_cache_mut() + }; + active_cache.set_duration(SourcePin::NodePose(node_id.clone()), value); } output.unwrap() @@ -746,7 +756,7 @@ impl AnimationGraph { return Err(GraphError::MissingInputEdge(target_pin)); }; - if ctx.cached_query { + if !ctx.temp_cache { if let Some(val) = ctx.context().get_pose(source_pin) { return Ok(val.clone()); } @@ -769,10 +779,14 @@ impl AnimationGraph { )? .unwrap(); - if ctx.cached_query { - ctx.context().set_pose(source_pin.clone(), output.clone()); - ctx.context().set_time(source_pin.clone(), output.timestamp); - } + let active_cache = if ctx.temp_cache { + ctx.context().caches.get_temp_cache_mut() + } else { + ctx.context().caches.get_cache_mut() + }; + + active_cache.set_pose(source_pin.clone(), output.clone()); + active_cache.set_time(source_pin.clone(), output.timestamp); output } @@ -788,6 +802,43 @@ impl AnimationGraph { Ok(source_value) } + pub fn clear_temp_cache( + &self, + target_pin: TargetPin, + mut ctx: PassContext, + ) -> Result<(), GraphError> { + let Some(source_pin) = self.edges.get(&target_pin) else { + return Err(GraphError::MissingInputEdge(target_pin)); + }; + + ctx.context() + .caches + .get_temp_cache_mut() + .clear_for(source_pin); + + 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 node = &self.nodes[node_id]; + let should_debug = node.should_debug; + let mut input_spec = node.pose_input_spec(ctx.spec_context()); + for (pin_id, _) in input_spec.drain(..) { + ctx.with_node(node_id, self) + .with_debugging(should_debug) + .clear_temp_cache(pin_id); + } + } + SourcePin::InputPose(_) => {} + } + + Ok(()) + } + pub fn query( &self, time_update: TimeUpdate, @@ -820,7 +871,7 @@ impl AnimationGraph { deferred_gizmos: &mut DeferredGizmos, ) -> Result { context.push_caches(); - Ok(self.get_pose( + self.get_pose( time_update, TargetPin::OutputPose, PassContext::new( @@ -831,7 +882,7 @@ impl AnimationGraph { entity_map, deferred_gizmos, ), - )?) + ) } // ---------------------------------------------------------------------------------------- } diff --git a/crates/bevy_animation_graph/src/core/caches.rs b/crates/bevy_animation_graph/src/core/caches.rs deleted file mode 100644 index 5280984..0000000 --- a/crates/bevy_animation_graph/src/core/caches.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::{ - animation_graph::{PinId, TimeState, TimeUpdate}, - parameters::ParamValue, -}; -use bevy::{reflect::prelude::*, utils::HashMap}; - -#[derive(Reflect, Clone, Debug, Default)] -pub struct ParameterCache { - pub upstream: HashMap, - pub downstream: HashMap, -} - -#[derive(Reflect, Clone, Debug)] -pub struct DurationCache { - pub upstream: HashMap>, - pub downstream: Option>, -} - -#[derive(Reflect, Clone, Debug)] -pub struct TimeCache { - pub upstream: HashMap, - pub downstream: TimeState, -} - -#[derive(Reflect, Clone, Debug, Default)] -pub struct TimeDependentCache { - pub upstream: HashMap, - pub downstream: Option, -} - -#[derive(Reflect, Clone, Debug, Default)] -pub struct AnimationCaches { - pub parameter_cache: Option, - pub duration_cache: Option, - pub time_caches: Option, - pub time_dependent_caches: Option, -} diff --git a/crates/bevy_animation_graph/src/core/context/graph_context.rs b/crates/bevy_animation_graph/src/core/context/graph_context.rs index 5c57cfa..54ac448 100644 --- a/crates/bevy_animation_graph/src/core/context/graph_context.rs +++ b/crates/bevy_animation_graph/src/core/context/graph_context.rs @@ -1,3 +1,4 @@ +use super::pass_context::GraphContextRef; use crate::{ core::{ animation_graph::{SourcePin, TimeUpdate}, @@ -6,8 +7,6 @@ use crate::{ }, prelude::ParamValue, }; - -use super::pass_context::GraphContextRef; use bevy::{reflect::prelude::*, utils::HashMap}; #[derive(Reflect, Debug, Default)] @@ -16,72 +15,84 @@ pub struct OutputCache { pub durations: HashMap, pub time_updates: HashMap, pub poses: HashMap, + pub times: HashMap, } -#[derive(Reflect, Debug, Default)] -struct TimeCacheSingle { - /// Only set after a pose query, cannot be assumed to exist - current: Option, - /// Should always exist - prev: f32, -} +impl OutputCache { + pub fn clear(&mut self, current_times: HashMap) { + self.parameters.clear(); + self.durations.clear(); + self.time_updates.clear(); + self.poses.clear(); + self.times = current_times; + } -impl TimeCacheSingle { - pub fn push(&mut self) { - if let Some(current) = self.current { - self.prev = current; - } + pub fn clear_for(&mut self, source_pin: &SourcePin) { + self.parameters.remove(source_pin); + self.durations.remove(source_pin); + self.time_updates.remove(source_pin); + self.poses.remove(source_pin); + self.times.remove(source_pin); } - pub fn get_prev(&self) -> f32 { - self.prev + pub fn get_parameter(&self, source_pin: &SourcePin) -> Option<&ParamValue> { + self.parameters.get(source_pin) } - pub fn set_curr(&mut self, value: f32) { - self.current = Some(value); + pub fn set_parameter( + &mut self, + source_pin: SourcePin, + value: ParamValue, + ) -> Option { + self.parameters.insert(source_pin, value) } -} -#[derive(Reflect, Debug, Default)] -pub struct TimeCaches { - caches: HashMap, -} + pub fn get_duration(&self, source_pin: &SourcePin) -> Option { + self.durations.get(source_pin).cloned() + } -impl TimeCaches { - pub fn push(&mut self) { - for (_, cache) in self.caches.iter_mut() { - cache.push(); - } + pub fn set_duration( + &mut self, + source_pin: SourcePin, + value: DurationData, + ) -> Option { + self.durations.insert(source_pin, value) } - pub fn get_prev(&self, source_pin: &SourcePin) -> f32 { - self.caches.get(source_pin).map_or(0., |c| c.get_prev()) + pub fn get_time_update(&self, source_pin: &SourcePin) -> Option<&TimeUpdate> { + self.time_updates.get(source_pin) } - pub fn set_curr(&mut self, source_pin: SourcePin, value: f32) { - if let Some(cache) = self.caches.get_mut(&source_pin) { - cache.set_curr(value); - } else { - let mut new_cache = TimeCacheSingle::default(); - new_cache.set_curr(value); - self.caches.insert(source_pin, new_cache); - } + pub fn set_time_update( + &mut self, + source_pin: SourcePin, + value: TimeUpdate, + ) -> Option { + self.time_updates.insert(source_pin, value) } -} -impl OutputCache { - pub fn clear(&mut self) { - self.parameters.clear(); - self.durations.clear(); - self.time_updates.clear(); - self.poses.clear(); + pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&Pose> { + self.poses.get(source_pin) + } + + pub fn set_pose(&mut self, source_pin: SourcePin, value: Pose) -> Option { + self.poses.insert(source_pin, value) + } + + pub fn get_time(&self, source_pin: &SourcePin) -> Option { + self.times.get(source_pin).copied() + } + + pub fn set_time(&mut self, source_pin: SourcePin, value: f32) -> Option { + self.times.insert(source_pin, value) } } #[derive(Reflect, Debug, Default)] pub struct OutputCaches { /// Caches are double buffered - caches: [OutputCache; 2], + primary_caches: [OutputCache; 2], + temp_cache: OutputCache, current_cache: usize, } @@ -95,79 +106,43 @@ impl OutputCaches { } pub fn get_cache(&self) -> &OutputCache { - &self.caches[self.current_cache] + &self.primary_caches[self.current_cache] } pub fn get_other_cache(&self) -> &OutputCache { - &self.caches[self.other_cache()] + &self.primary_caches[self.other_cache()] } pub fn get_cache_mut(&mut self) -> &mut OutputCache { - &mut self.caches[self.current_cache] + &mut self.primary_caches[self.current_cache] } - pub fn push(&mut self) { - self.caches[self.other_cache()].clear(); - self.flip(); + pub fn get_temp_cache(&self) -> &OutputCache { + &self.temp_cache } - pub fn get_paramereter(&self, source_pin: &SourcePin) -> Option<&ParamValue> { - self.get_cache().parameters.get(source_pin) + pub fn get_temp_cache_mut(&mut self) -> &mut OutputCache { + &mut self.temp_cache } - pub fn set_parameter( - &mut self, - source_pin: SourcePin, - value: ParamValue, - ) -> Option { - self.get_cache_mut().parameters.insert(source_pin, value) - } - - pub fn get_duration(&self, source_pin: &SourcePin) -> Option { - self.get_cache().durations.get(source_pin).cloned() - } - - pub fn set_duration( - &mut self, - source_pin: SourcePin, - value: DurationData, - ) -> Option { - self.get_cache_mut().durations.insert(source_pin, value) - } - - pub fn get_time_update(&self, source_pin: &SourcePin) -> Option<&TimeUpdate> { - self.get_cache().time_updates.get(source_pin) - } - - pub fn set_time_update( - &mut self, - source_pin: SourcePin, - value: TimeUpdate, - ) -> Option { - self.get_cache_mut().time_updates.insert(source_pin, value) - } - - pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&Pose> { - self.get_cache().poses.get(source_pin) - } - - pub fn set_pose(&mut self, source_pin: SourcePin, value: Pose) -> Option { - self.get_cache_mut().poses.insert(source_pin, value) + pub fn push(&mut self) { + let current_times = self.primary_caches[self.current_cache].times.clone(); + self.primary_caches[self.other_cache()].clear(current_times); + self.temp_cache.clear(HashMap::default()); + self.flip(); } } #[derive(Debug, Default, Reflect)] pub struct GraphContext { - outputs: OutputCaches, - times: TimeCaches, + pub caches: OutputCaches, #[reflect(ignore)] subgraph_contexts: HashMap, } impl GraphContext { pub fn push_caches(&mut self) { - self.outputs.push(); - self.times.push(); + self.caches.push(); for (_, sub_ctx) in self.subgraph_contexts.iter_mut() { sub_ctx.push_caches(); @@ -175,55 +150,50 @@ impl GraphContext { } pub fn get_parameter(&self, source_pin: &SourcePin) -> Option<&ParamValue> { - self.outputs.get_paramereter(source_pin) - } - - pub fn set_parameter( - &mut self, - source_pin: SourcePin, - value: ParamValue, - ) -> Option { - self.outputs.set_parameter(source_pin, value) + self.caches + .get_temp_cache() + .get_parameter(source_pin) + .or_else(|| self.caches.get_cache().get_parameter(source_pin)) } pub fn get_duration(&self, source_pin: &SourcePin) -> Option { - self.outputs.get_duration(source_pin) - } - - pub fn set_duration( - &mut self, - source_pin: SourcePin, - value: DurationData, - ) -> Option { - self.outputs.set_duration(source_pin, value) + self.caches + .get_temp_cache() + .get_duration(source_pin) + .or_else(|| self.caches.get_cache().get_duration(source_pin)) } pub fn get_time_update(&self, source_pin: &SourcePin) -> Option<&TimeUpdate> { - self.outputs.get_time_update(source_pin) - } - - pub fn set_time_update( - &mut self, - source_pin: SourcePin, - value: TimeUpdate, - ) -> Option { - self.outputs.set_time_update(source_pin, value) + self.caches + .get_temp_cache() + .get_time_update(source_pin) + .or_else(|| self.caches.get_cache().get_time_update(source_pin)) } pub fn get_prev_time(&self, source_pin: &SourcePin) -> f32 { - self.times.get_prev(source_pin) + self.caches + .get_other_cache() + .get_time(source_pin) + .unwrap_or(0.) } - pub fn set_time(&mut self, source_pin: SourcePin, value: f32) { - self.times.set_curr(source_pin, value); + pub fn get_time(&self, source_pin: &SourcePin) -> f32 { + self.caches + .get_temp_cache() + .get_time(source_pin) + .or_else(|| self.caches.get_cache().get_time(source_pin)) + .unwrap_or(0.) } pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&Pose> { - self.outputs.get_pose(source_pin) + self.caches + .get_temp_cache() + .get_pose(source_pin) + .or_else(|| self.caches.get_cache().get_pose(source_pin)) } - pub fn set_pose(&mut self, source_pin: SourcePin, value: Pose) -> Option { - self.outputs.set_pose(source_pin, value) + pub fn clear_temp_cache_for(&mut self, source_pin: &SourcePin) { + self.caches.get_temp_cache_mut().clear_for(source_pin); } pub(super) fn context_for_subgraph_or_insert_default(&mut self, node: &str) -> GraphContextRef { diff --git a/crates/bevy_animation_graph/src/core/context/pass_context.rs b/crates/bevy_animation_graph/src/core/context/pass_context.rs index a947d29..7a00ba7 100644 --- a/crates/bevy_animation_graph/src/core/context/pass_context.rs +++ b/crates/bevy_animation_graph/src/core/context/pass_context.rs @@ -10,7 +10,7 @@ use crate::{ prelude::{AnimationGraph, ParamValue}, }; -use super::{deferred_gizmos::DeferredGizmoRef, GraphContext, SystemResources}; +use super::{deferred_gizmos::DeferredGizmoRef, GraphContext, SpecContext, SystemResources}; #[derive(Clone, Copy)] pub struct NodeContext<'a> { @@ -28,9 +28,9 @@ pub struct PassContext<'a> { pub root_entity: Entity, pub entity_map: &'a HashMap, pub deferred_gizmos: DeferredGizmoRef, - /// Whether this query should mutate chaches. Useful when getting a pose back but not wanting - /// to use the time query to update the times - pub cached_query: bool, + /// Whether this query should mutate the *permanent* or *temporary* chache. Useful when getting + /// a pose back but not wanting to use the time query to update the times + pub temp_cache: bool, pub should_debug: bool, } @@ -53,7 +53,7 @@ impl<'a> PassContext<'a> { root_entity, entity_map, deferred_gizmos: deferred_gizmos.into(), - cached_query: true, + temp_cache: false, should_debug: false, } } @@ -70,7 +70,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), - cached_query: self.cached_query, + temp_cache: self.temp_cache, should_debug: self.should_debug, } } @@ -87,7 +87,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), - cached_query: self.cached_query, + temp_cache: self.temp_cache, should_debug: self.should_debug, } } @@ -103,7 +103,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), - cached_query: self.cached_query, + temp_cache: self.temp_cache, should_debug, } } @@ -123,7 +123,7 @@ impl<'a> PassContext<'a> { root_entity: self.root_entity, entity_map: self.entity_map, deferred_gizmos: self.deferred_gizmos.clone(), - cached_query: self.cached_query, + temp_cache: self.temp_cache, should_debug: self.should_debug, } } @@ -144,6 +144,14 @@ impl<'a> PassContext<'a> { self.context.as_mut() } + pub fn spec_context(&'a self) -> SpecContext<'a> { + SpecContext { + graph_assets: &self.resources.animation_graph_assets, + } + } +} + +impl<'a> PassContext<'a> { /// Request an input parameter from the graph pub fn parameter_back(&mut self, pin_id: impl Into) -> Result { let node_ctx = self.node_context.unwrap(); @@ -174,7 +182,7 @@ impl<'a> PassContext<'a> { } /// Request an input pose. - pub fn uncached_pose_back( + pub fn temp_pose_back( &mut self, pin_id: impl Into, time_update: TimeUpdate, @@ -182,7 +190,7 @@ impl<'a> PassContext<'a> { let node_ctx = self.node_context.unwrap(); let target_pin = TargetPin::NodePose(node_ctx.node_id.clone(), pin_id.into()); let mut ctx = self.without_node(); - ctx.cached_query = false; + ctx.temp_cache = true; node_ctx.graph.get_pose(time_update, target_pin, ctx) } @@ -204,6 +212,14 @@ impl<'a> PassContext<'a> { let source_pin = SourcePin::NodePose(node_ctx.node_id.clone()); self.context.as_mut().get_prev_time(&source_pin) } + + pub fn clear_temp_cache(&self, pin_id: impl Into) { + let node_ctx = self.node_context.unwrap(); + let target_pin = TargetPin::NodePose(node_ctx.node_id.clone(), pin_id.into()); + let _ = node_ctx + .graph + .clear_temp_cache(target_pin, self.without_node()); + } } #[derive(Clone)] diff --git a/crates/bevy_animation_graph/src/core/mod.rs b/crates/bevy_animation_graph/src/core/mod.rs index 3c0d877..7741a48 100644 --- a/crates/bevy_animation_graph/src/core/mod.rs +++ b/crates/bevy_animation_graph/src/core/mod.rs @@ -3,7 +3,6 @@ pub mod animation_clip; pub mod animation_graph; pub mod animation_graph_player; pub mod animation_node; -pub mod caches; pub mod context; pub mod duration_data; pub mod errors; diff --git a/crates/bevy_animation_graph/src/core/space_conversion.rs b/crates/bevy_animation_graph/src/core/space_conversion.rs index f7940cf..8450c9a 100644 --- a/crates/bevy_animation_graph/src/core/space_conversion.rs +++ b/crates/bevy_animation_graph/src/core/space_conversion.rs @@ -3,7 +3,7 @@ use super::{ context::PassContext, pose::{BoneId, BonePose, Pose}, }; -use bevy::{ecs::entity::Entity, transform::components::Transform, utils::HashMap}; +use bevy::{ecs::entity::Entity, transform::components::Transform}; use std::collections::VecDeque; pub trait SpaceConversion { diff --git a/crates/bevy_animation_graph/src/flipping/mod.rs b/crates/bevy_animation_graph/src/flipping/mod.rs index 1f2847e..c55d4a2 100644 --- a/crates/bevy_animation_graph/src/flipping/mod.rs +++ b/crates/bevy_animation_graph/src/flipping/mod.rs @@ -31,9 +31,9 @@ impl FlipXBySuffix for Quat { impl FlipXBySuffix for BonePose { fn flipped(&self, config: &FlipConfig) -> Self { BonePose { - rotation: self.rotation.clone().map(|v| v.flipped(config)), - translation: self.translation.clone().map(|v| v.flipped(config)), - scale: self.scale.clone(), + rotation: self.rotation.map(|v| v.flipped(config)), + translation: self.translation.map(|v| v.flipped(config)), + scale: self.scale, weights: self.weights.clone(), } } diff --git a/crates/bevy_animation_graph/src/interpolation/linear.rs b/crates/bevy_animation_graph/src/interpolation/linear.rs index 54f9aaa..6ab481a 100644 --- a/crates/bevy_animation_graph/src/interpolation/linear.rs +++ b/crates/bevy_animation_graph/src/interpolation/linear.rs @@ -48,8 +48,8 @@ impl InterpolateLinear for BonePose { result.rotation = Some(a.interpolate_linear(b, f)); } (None, None) => {} - (None, Some(b)) => result.rotation = Some(b.clone()), - (Some(a), None) => result.rotation = Some(a.clone()), + (None, Some(b)) => result.rotation = Some(*b), + (Some(a), None) => result.rotation = Some(*a), } match (&self.translation, &other.translation) { @@ -57,8 +57,8 @@ impl InterpolateLinear for BonePose { result.translation = Some(a.interpolate_linear(b, f)); } (None, None) => {} - (None, Some(b)) => result.translation = Some(b.clone()), - (Some(a), None) => result.translation = Some(a.clone()), + (None, Some(b)) => result.translation = Some(*b), + (Some(a), None) => result.translation = Some(*a), } match (&self.scale, &other.scale) { @@ -66,8 +66,8 @@ impl InterpolateLinear for BonePose { result.scale = Some(a.interpolate_linear(b, f)); } (None, None) => {} - (None, Some(b)) => result.scale = Some(b.clone()), - (Some(a), None) => result.scale = Some(a.clone()), + (None, Some(b)) => result.scale = Some(*b), + (Some(a), None) => result.scale = Some(*a), } match (&self.weights, &other.weights) { diff --git a/crates/bevy_animation_graph/src/nodes/clip_node.rs b/crates/bevy_animation_graph/src/nodes/clip_node.rs index 82e7106..71b3479 100644 --- a/crates/bevy_animation_graph/src/nodes/clip_node.rs +++ b/crates/bevy_animation_graph/src/nodes/clip_node.rs @@ -62,8 +62,10 @@ impl NodeLike for ClipNode { let prev_time = ctx.prev_time_fwd(); let time = time_update.apply(prev_time); - let mut out_pose = Pose::default(); - out_pose.timestamp = time; + let mut out_pose = Pose { + timestamp: time, + ..Pose::default() + }; let time = time.clamp(0., clip_duration); @@ -148,6 +150,7 @@ impl NodeLike for ClipNode { } } +#[allow(clippy::too_many_arguments)] fn sample_two_keyframes( step_start: usize, step_end: usize, diff --git a/crates/bevy_animation_graph/src/nodes/loop_node.rs b/crates/bevy_animation_graph/src/nodes/loop_node.rs index 066739f..94aa668 100644 --- a/crates/bevy_animation_graph/src/nodes/loop_node.rs +++ b/crates/bevy_animation_graph/src/nodes/loop_node.rs @@ -69,7 +69,8 @@ impl NodeLike for LoopNode { let mut pose = ctx.pose_back(Self::INPUT, fw_upd)?; if t > duration && t < full_duration { - let start_pose = ctx.uncached_pose_back(Self::INPUT, TimeUpdate::Absolute(0.))?; + let start_pose = ctx.temp_pose_back(Self::INPUT, TimeUpdate::Absolute(0.))?; + ctx.clear_temp_cache(Self::INPUT); let old_time = pose.timestamp; pose = pose.interpolate_linear(&start_pose, (t - duration) / self.interpolation_period); pose.timestamp = old_time; diff --git a/crates/bevy_animation_graph/src/nodes/rotation_node.rs b/crates/bevy_animation_graph/src/nodes/rotation_node.rs index 79123ab..60748a0 100644 --- a/crates/bevy_animation_graph/src/nodes/rotation_node.rs +++ b/crates/bevy_animation_graph/src/nodes/rotation_node.rs @@ -96,7 +96,6 @@ impl NodeLike for RotationNode { let mut target: EntityPath = ctx.parameter_back(Self::TARGET)?.unwrap(); let rotation: Quat = ctx.parameter_back(Self::ROTATION)?.unwrap(); let mut pose = ctx.pose_back(Self::INPUT, input)?; - let time = pose.timestamp; if !pose.paths.contains_key(&target) { pose.add_bone(BonePose::default(), target.clone()); @@ -142,7 +141,7 @@ impl NodeLike for RotationNode { .and_then(|bone_id| pose.bones.get_mut(*bone_id).cloned()) .unwrap_or_default(); - if let Some(mut rot) = bone_pose.rotation { + if let Some(rot) = bone_pose.rotation { let rotation = match self.application_mode { RotationMode::Blend => rot.slerp(rotation_bone_space, percent), RotationMode::Compose => { diff --git a/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs b/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs index 9092ff2..2801be9 100644 --- a/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs +++ b/crates/bevy_animation_graph/src/nodes/space_conversion/into_bone.rs @@ -33,8 +33,8 @@ impl NodeLike for IntoBoneSpaceNode { fn pose_pass( &self, - time_update: TimeUpdate, - mut ctx: PassContext, + _time_update: TimeUpdate, + mut _ctx: PassContext, ) -> Result, GraphError> { // let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; // Ok(Some(PoseFrame { diff --git a/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs b/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs index 1f2253c..5268f62 100644 --- a/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs +++ b/crates/bevy_animation_graph/src/nodes/space_conversion/into_character.rs @@ -33,8 +33,8 @@ impl NodeLike for IntoCharacterSpaceNode { fn pose_pass( &self, - time_update: TimeUpdate, - mut ctx: PassContext, + _time_update: TimeUpdate, + mut _ctx: PassContext, ) -> Result, GraphError> { // let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; // Ok(Some(PoseFrame { diff --git a/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs b/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs index 4d8d66d..199a840 100644 --- a/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs +++ b/crates/bevy_animation_graph/src/nodes/space_conversion/into_global.rs @@ -33,8 +33,8 @@ impl NodeLike for IntoGlobalSpaceNode { fn pose_pass( &self, - time_update: TimeUpdate, - mut ctx: PassContext, + _time_update: TimeUpdate, + mut _ctx: PassContext, ) -> Result, GraphError> { // let in_pose = ctx.pose_back(Self::POSE_IN, time_update)?; // Ok(Some(PoseFrame {