From 97c430226478faab95cc55ee242a7e6d52815ac5 Mon Sep 17 00:00:00 2001 From: Manuel Brea Carreras Date: Sun, 17 Mar 2024 15:15:59 +0000 Subject: [PATCH] Sample curves eagerly (#41) Since the start, I have been using lazy/delayed sampling for the animation graph. This means that the pose data passed around the graph was in the form of curve segments (`ValueFrame` types), and we sample them only when needed. While this allowed a couple of cool things (e.g. extremely flexible animation chaining), the complexity introduced in many parts of the codebase was getting out of hand. The industry standard way of doing this is to sample animations as early as possible, and only worked with pose values throughout the graph. This PR switches to that approach. This is part of an effort to clean up technical debt from the codebase, and make development faster and more approachable. ### Breaking changes There are changes in how chaining and looping works that required a new parameter being introduced. Chain and loop nodes now have a `interpolation_period` f32 parameter that determines how long should be spent blending back to the starting pose (for looping) or blending to the second animation (for chaining). You should manually edit graphs files to change the Loop and Chain nodes to a struct (change `Loop` to `Loop()` and `Chain` to `Chain()`), and when opening the editor the nodes should be loaded with default values. See the updated examples for how for how to do similar tasks in the new system. ### Follow-up work - Update space conversion nodes --- assets/animation_graphs/fox.animgraph.ron | 6 +- assets/animation_graphs/human.animgraph.ron | 130 +-- .../animation_graphs/human_ik.animgraph.ron | 140 ++-- .../src/bin/show_graph.rs | 72 -- .../bevy_animation_graph/src/chaining/mod.rs | 186 ----- .../src/core/animation_graph/core.rs | 88 +- .../src/core/animation_graph/dot_output.rs | 490 ----------- .../src/core/animation_graph/loader.rs | 8 +- .../src/core/animation_graph/mod.rs | 2 - .../src/core/animation_graph/serial.rs | 20 +- .../src/core/animation_node.rs | 10 +- .../bevy_animation_graph/src/core/caches.rs | 37 - .../src/core/context/deferred_gizmos.rs | 38 +- .../src/core/context/graph_context.rs | 234 +++--- .../src/core/context/pass_context.rs | 44 +- crates/bevy_animation_graph/src/core/frame.rs | 590 -------------- crates/bevy_animation_graph/src/core/mod.rs | 2 - .../bevy_animation_graph/src/core/plugin.rs | 2 +- crates/bevy_animation_graph/src/core/pose.rs | 49 +- .../src/core/space_conversion.rs | 762 +++++++++--------- .../bevy_animation_graph/src/flipping/mod.rs | 43 +- .../src/interpolation/linear.rs | 185 +---- crates/bevy_animation_graph/src/lib.rs | 4 - .../src/nodes/blend_node.rs | 7 +- .../src/nodes/chain_node.rs | 54 +- .../src/nodes/clip_node.rs | 204 +++-- .../src/nodes/flip_lr_node.rs | 27 +- .../src/nodes/graph_node.rs | 8 +- .../src/nodes/loop_node.rs | 33 +- .../src/nodes/rotation_node.rs | 62 +- .../nodes/space_conversion/extend_skeleton.rs | 11 +- .../src/nodes/space_conversion/into_bone.rs | 28 +- .../nodes/space_conversion/into_character.rs | 28 +- .../src/nodes/space_conversion/into_global.rs | 28 +- .../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 - .../src/egui_inspector_impls.rs | 2 +- .../src/graph_show.rs | 2 +- 40 files changed, 1123 insertions(+), 2682 deletions(-) delete mode 100644 crates/bevy_animation_graph/src/bin/show_graph.rs delete mode 100644 crates/bevy_animation_graph/src/chaining/mod.rs delete mode 100644 crates/bevy_animation_graph/src/core/animation_graph/dot_output.rs delete mode 100644 crates/bevy_animation_graph/src/core/caches.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/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.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/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 +) 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/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 a9bd126..d5c2771 100644 --- a/crates/bevy_animation_graph/src/core/animation_graph/core.rs +++ b/crates/bevy_animation_graph/src/core/animation_graph/core.rs @@ -4,14 +4,13 @@ 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}, + utils::ordered_map::OrderedMap, }; use bevy::{ prelude::*, @@ -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 { @@ -662,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(), ); @@ -719,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() @@ -742,13 +751,15 @@ 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)); }; - if let Some(val) = ctx.context().get_pose(source_pin) { - return Ok(val.clone()); + if !ctx.temp_cache { + if let Some(val) = ctx.context().get_pose(source_pin) { + return Ok(val.clone()); + } } let source_value = match source_pin { @@ -768,8 +779,14 @@ impl AnimationGraph { )? .unwrap(); - 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 } @@ -785,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, @@ -817,7 +871,7 @@ impl AnimationGraph { deferred_gizmos: &mut DeferredGizmos, ) -> Result { context.push_caches(); - let out = self.get_pose( + self.get_pose( time_update, TargetPin::OutputPose, PassContext::new( @@ -828,11 +882,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/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/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/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_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/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/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/deferred_gizmos.rs b/crates/bevy_animation_graph/src/core/context/deferred_gizmos.rs index 5bdc6c1..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,5 +1,8 @@ use super::PassContext; -use crate::core::{frame::InnerPoseFrame, pose::BoneId, space_conversion::SpaceConversion}; +use crate::core::{ + pose::{BoneId, Pose}, + space_conversion::SpaceConversion, +}; use bevy::{ gizmos::gizmos::Gizmos, math::{Quat, Vec3}, @@ -104,13 +107,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 +139,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..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,13 +1,12 @@ +use super::pass_context::GraphContextRef; use crate::{ core::{ animation_graph::{SourcePin, TimeUpdate}, duration_data::DurationData, - frame::PoseFrame, + pose::Pose, }, prelude::ParamValue, }; - -use super::pass_context::GraphContextRef; use bevy::{reflect::prelude::*, utils::HashMap}; #[derive(Reflect, Debug, Default)] @@ -15,73 +14,85 @@ pub struct OutputCache { pub parameters: HashMap, pub durations: HashMap, pub time_updates: HashMap, - pub poses: 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] - } - - pub fn push(&mut self) { - self.caches[self.other_cache()].clear(); - self.flip(); - } - - pub fn get_paramereter(&self, source_pin: &SourcePin) -> Option<&ParamValue> { - self.get_cache().parameters.get(source_pin) - } - - 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() + &mut self.primary_caches[self.current_cache] } - 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_temp_cache(&self) -> &OutputCache { + &self.temp_cache } - pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&PoseFrame> { - self.get_cache().poses.get(source_pin) + pub fn get_temp_cache_mut(&mut self) -> &mut OutputCache { + &mut self.temp_cache } - pub fn set_pose(&mut self, source_pin: SourcePin, value: PoseFrame) -> 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<&PoseFrame> { - self.outputs.get_pose(source_pin) + pub fn get_pose(&self, source_pin: &SourcePin) -> Option<&Pose> { + 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: PoseFrame) -> 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 4ea2b08..7a00ba7 100644 --- a/crates/bevy_animation_graph/src/core/context/pass_context.rs +++ b/crates/bevy_animation_graph/src/core/context/pass_context.rs @@ -5,13 +5,12 @@ use crate::{ animation_graph::{InputOverlay, NodeId, PinId, SourcePin, TargetPin, TimeUpdate}, duration_data::DurationData, errors::GraphError, - frame::PoseFrame, - pose::BoneId, + pose::{BoneId, Pose}, }, 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> { @@ -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 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, } @@ -51,6 +53,7 @@ impl<'a> PassContext<'a> { root_entity, entity_map, deferred_gizmos: deferred_gizmos.into(), + temp_cache: false, 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(), + temp_cache: self.temp_cache, 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(), + temp_cache: self.temp_cache, 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(), + temp_cache: self.temp_cache, 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(), + temp_cache: self.temp_cache, should_debug: self.should_debug, } } @@ -137,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(); @@ -158,7 +173,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 @@ -166,6 +181,19 @@ impl<'a> PassContext<'a> { .get_pose(time_update, target_pin, self.without_node()) } + /// Request an input pose. + pub fn temp_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.temp_cache = true; + 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(); @@ -184,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/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..7741a48 100644 --- a/crates/bevy_animation_graph/src/core/mod.rs +++ b/crates/bevy_animation_graph/src/core/mod.rs @@ -3,11 +3,9 @@ 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; -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..8450c9a 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 bevy::{ecs::entity::Entity, transform::components::Transform}; 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..c55d4a2 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,44 +11,37 @@ 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 { - rotation: self.rotation.clone().map(|v| v.flipped(config)), - translation: self.translation.clone().map(|v| v.flipped(config)), - scale: self.scale.clone(), + BonePose { + rotation: self.rotation.map(|v| v.flipped(config)), + translation: self.translation.map(|v| v.flipped(config)), + scale: self.scale, weights: self.weights.clone(), } } } -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..6ab481a 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(); @@ -58,8 +48,8 @@ impl InterpolateLinear for BoneFrame { 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) { @@ -67,8 +57,8 @@ impl InterpolateLinear for BoneFrame { 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) { @@ -76,8 +66,8 @@ impl InterpolateLinear for BoneFrame { 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) { @@ -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) { @@ -115,159 +105,8 @@ impl InterpolateLinear for InnerPoseFrame { result.add_bone(other.bones[*bone_id].clone(), path.clone()); } - 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) - ) - } - } - } -} + result.timestamp = self.timestamp; -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); + result } } diff --git a/crates/bevy_animation_graph/src/lib.rs b/crates/bevy_animation_graph/src/lib.rs index 250a989..b9c67b4 100644 --- a/crates/bevy_animation_graph/src/lib.rs +++ b/crates/bevy_animation_graph/src/lib.rs @@ -226,20 +226,16 @@ //! [`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; pub mod nodes; -pub mod sampling; mod utils; pub mod prelude { - pub use super::chaining::*; pub use super::core::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..dea8ea1 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 { @@ -31,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, @@ -44,28 +51,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..71b3479 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,26 @@ 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 { + timestamp: time, + ..Pose::default() + }; + + let time = time.clamp(0., clip_duration); + 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(); @@ -87,6 +92,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]; @@ -96,76 +124,21 @@ impl NodeLike for ClipNode { next_timestamp += clip_duration; } - // 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; - } - - frame.rotation = Some(ValueFrame { - prev, - prev_timestamp, - next, - next_timestamp, - prev_is_wrapped, - next_is_wrapped, - }); - } - 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, - }); - } - - 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, - }); - } - - 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, - }); - } - } + sample_two_keyframes( + step_start, + step_end, + prev_timestamp, + next_timestamp, + time, + keyframe_count, + &curve.keyframes, + &mut bone_pose, + ); } - 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 { @@ -176,3 +149,84 @@ impl NodeLike for ClipNode { "⏵ Animation Clip".into() } } + +#[allow(clippy::too_many_arguments)] +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/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..94aa668 100644 --- a/crates/bevy_animation_graph/src/nodes/loop_node.rs +++ b/crates/bevy_animation_graph/src/nodes/loop_node.rs @@ -2,21 +2,26 @@ 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::interpolation::prelude::InterpolateLinear; 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 { + 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 { @@ -37,20 +42,22 @@ 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 { 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) @@ -61,8 +68,16 @@ 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); + if t > duration && t < full_duration { + 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; + } + + 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/src/nodes/rotation_node.rs b/crates/bevy_animation_graph/src/nodes/rotation_node.rs index 0b8438c..60748a0 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,13 @@ 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 time = pose.timestamp; - let mut pose: BonePoseFrame = pose.data.unwrap(); - let inner_pose = pose.inner_mut(); + let mut pose = ctx.pose_back(Self::INPUT, input)?; - 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 +118,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 +135,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(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..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 @@ -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}, }; @@ -34,18 +33,19 @@ impl NodeLike for IntoBoneSpaceNode { fn pose_pass( &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), - }), - })) + _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), + // }), + // })) + 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..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 @@ -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}, }; @@ -34,18 +33,19 @@ impl NodeLike for IntoCharacterSpaceNode { fn pose_pass( &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), - }), - })) + _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), + // }), + // })) + 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..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 @@ -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}, }; @@ -34,18 +33,19 @@ impl NodeLike for IntoGlobalSpaceNode { fn pose_pass( &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(), - }), - })) + _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(), + // }), + // })) + 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::*; -} 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;