diff --git a/CMakeLists.txt b/CMakeLists.txt index 23804a9..cbb0da7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ add_library(AnimTestbedCode OBJECT src/AnimNode.cc src/AnimNodes/AnimSamplerNode.cc src/AnimNodes/SpeedScaleNode.cc + src/AnimNodes/BlendSpace1D.cc src/AnimNodes/BlendNode.cc src/AnimNodes/LockTranslationNode.cc src/AnimationController.cc diff --git a/src/AnimNodes/BlendSpace1D.cc b/src/AnimNodes/BlendSpace1D.cc new file mode 100644 index 0000000..5905fa6 --- /dev/null +++ b/src/AnimNodes/BlendSpace1D.cc @@ -0,0 +1,63 @@ +// +// Created by martin on 19.11.21. +// + +#include "BlendSpace1D.h" + +#include +#include + +#include "../SkinnedMesh.h" + +BlendSpace1D::BlendSpace1D(AnimationController* animation_controller) + : AnimNode(animation_controller), + m_input_0(nullptr), + m_weight_0(0.f), + m_input_1(nullptr), + m_weight_1(0.f), + m_weight(0.f), + m_sync_inputs(false) { + m_anim_node_type = AnimNodeType::Blend; + const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh; + const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints(); + const int num_joints = skinned_mesh->m_skeleton.num_joints(); + m_local_matrices_0.resize(num_soa_joints); + m_local_matrices_1.resize(num_soa_joints); +} + +void BlendSpace1D::Evaluate(ozz::vector* local_matrices) { + const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh; + + m_input_0->Evaluate(&m_local_matrices_0); + m_input_1->Evaluate(&m_local_matrices_1); + + // perform blend + ozz::animation::BlendingJob::Layer layers[2]; + layers[0].transform = make_span(m_local_matrices_0); + layers[0].weight = (1.0f - m_normalized_weight); + + layers[1].transform = make_span(m_local_matrices_1); + layers[1].weight = (m_normalized_weight); + + ozz::animation::BlendingJob blend_job; + blend_job.threshold = ozz::animation::BlendingJob().threshold; + blend_job.layers = layers; + blend_job.bind_pose = skinned_mesh->m_skeleton.joint_bind_poses(); + blend_job.output = make_span(*local_matrices); + + if (!blend_job.Run()) { + ozz::log::Err() << "Error blending animations." << std::endl; + } +} + +void BlendSpace1D::DrawDebugUi() { + float min_weight = m_input_weights[0]; + float max_weight = m_input_weights.back(); + if (ImGui::SliderFloat("Weight", &m_weight, min_weight, max_weight)) { + m_sync_track = SyncTrack::Blend(m_weight, m_input_0->m_sync_track, m_input_1->m_sync_track); + } + ImGui::Checkbox("Sync Inputs", &m_sync_inputs); + + ImGui::Text("SyncTrack"); + m_sync_track.DrawDebugUi(); +} diff --git a/src/AnimNodes/BlendSpace1D.h b/src/AnimNodes/BlendSpace1D.h new file mode 100644 index 0000000..827124a --- /dev/null +++ b/src/AnimNodes/BlendSpace1D.h @@ -0,0 +1,99 @@ +// +// Created by martin on 19.11.21. +// + +#ifndef ANIMTESTBED_BLENDSPACE1D_H +#define ANIMTESTBED_BLENDSPACE1D_H + +#include "../AnimNode.h" + +struct BlendSpace1D : public AnimNode { + BlendSpace1D(AnimationController* animation_controller); + + virtual ~BlendSpace1D() {} + + int m_num_inputs; + std::vector m_input_weights; + std::vector m_inputs; + float m_weight; + bool m_sync_inputs; + + AnimNode* m_input_0; + AnimNode* m_input_1; + + float m_normalized_weight; + float m_weight_0; + float m_weight_1; + ozz::vector m_local_matrices_0; + ozz::vector m_local_matrices_1; + + virtual void Reset() { + m_current_time = 0.f; + } + + virtual void UpdateIsSynced(bool is_synced) override { + m_is_time_synced = is_synced; + + if (m_sync_inputs) { + m_is_time_synced = true; + } + + assert (m_input_weights.size() > 0); + assert (m_weight >= m_input_weights[0] && m_weight <= m_input_weights[m_input_weights.size() - 1]); + + int prev_idx = 0; + for (int next_idx = 1; next_idx < m_input_weights.size(); next_idx++) { + if (m_input_weights[prev_idx] <= m_weight && m_input_weights[next_idx] >= m_weight) { + m_input_0 = m_inputs[prev_idx]; + m_weight_0 = m_input_weights[prev_idx]; + + m_input_1 = m_inputs[next_idx]; + m_weight_1 = m_input_weights[next_idx]; + + break; + } + prev_idx = next_idx; + } + + m_input_0->UpdateIsSynced(m_is_time_synced); + m_input_1->UpdateIsSynced(m_is_time_synced); + + m_normalized_weight = (m_weight - m_weight_0) / (m_weight_1 - m_weight_0); + } + + virtual void UpdateSyncTrack() override { + if (m_is_time_synced) { + m_sync_track = SyncTrack::Blend(m_normalized_weight, m_input_0->m_sync_track, m_input_1->m_sync_track); + } else { + assert (false); + } + } + + virtual void UpdateTime(float dt) { + if (m_is_time_synced) { + m_current_time = fmodf(m_current_time + dt, m_sync_track.m_duration); + float current_sync_time = m_sync_track.CalcSyncFromAbsTime(m_current_time); + + m_input_0->UpdateTime(current_sync_time - m_input_0->m_current_time); + m_input_1->UpdateTime(current_sync_time - m_input_1->m_current_time); + } else { + m_current_time += dt; + m_input_0->UpdateTime(dt); + m_input_1->UpdateTime(dt); + } + } + + virtual void Evaluate( + ozz::vector* local_matrices) override; + + virtual void GetInputNodes(std::vector& input_nodes) const override { + for (int i = 0; i < m_inputs.size(); i++) { + input_nodes.push_back(m_inputs[i]); + } + }; + + virtual void DrawDebugUi(); +}; + + +#endif //ANIMTESTBED_BLENDSPACE1D_H diff --git a/src/AnimationController.cc b/src/AnimationController.cc index cb266d3..77f8757 100644 --- a/src/AnimationController.cc +++ b/src/AnimationController.cc @@ -12,6 +12,7 @@ #include "AnimNodes/AnimSamplerNode.h" #include "AnimNodes/BlendNode.h" +#include "AnimNodes/BlendSpace1D.h" #include "AnimNodes/LockTranslationNode.h" #include "AnimNodes/SpeedScaleNode.h" #include "SkinnedMesh.h" @@ -35,21 +36,37 @@ AnimationController::AnimationController(SkinnedMesh* skinned_mesh) sampler_node1->SetAnimation(skinned_mesh->m_animations[2], skinned_mesh->m_animation_sync_track[2]); m_anim_nodes.push_back(sampler_node1); -// SpeedScaleNode* speed_node = new SpeedScaleNode(this); -// speed_node->m_name = "SpeedNode0"; -// speed_node->m_input_node = sampler_node0; -// m_anim_nodes.push_back(speed_node); + AnimSamplerNode* sampler_node2 = new AnimSamplerNode(this); + sampler_node2->m_name = "AnimSampler2"; + sampler_node2->SetAnimation(skinned_mesh->m_animations[3], skinned_mesh->m_animation_sync_track[3]); + m_anim_nodes.push_back(sampler_node2); + + BlendSpace1D* blend_space = new BlendSpace1D(this); + blend_space->m_name = "BlendSpace0"; + blend_space->m_weight = 0.f; + blend_space->m_inputs.push_back(sampler_node0); + blend_space->m_input_weights.push_back(-1.f); + blend_space->m_inputs.push_back(sampler_node1); + blend_space->m_input_weights.push_back(0.f); + blend_space->m_inputs.push_back(sampler_node2); + blend_space->m_input_weights.push_back(1.f); + m_anim_nodes.push_back(blend_space); + + SpeedScaleNode* speed_node = new SpeedScaleNode(this); + speed_node->m_name = "SpeedNode0"; + speed_node->m_input_node = sampler_node0; + m_anim_nodes.push_back(speed_node); BlendNode* blend_node = new BlendNode(this); blend_node->m_name = "Blend0"; - blend_node->m_input_A = sampler_node0; + blend_node->m_input_A = speed_node; blend_node->m_input_B = sampler_node1; blend_node->m_sync_inputs = true; m_anim_nodes.push_back(blend_node); SpeedScaleNode* speed_node1 = new SpeedScaleNode(this); speed_node1->m_name = "SpeedNode1"; - speed_node1->m_input_node = blend_node; + speed_node1->m_input_node = blend_space; m_anim_nodes.push_back(speed_node1); LockTranslationNode* lock_node = new LockTranslationNode(this); @@ -100,13 +117,17 @@ void AnimationController::UpdateOrderedNodes() { } void AnimationController::UpdateTime(float dt) { - if (m_paused || m_output_node == nullptr) { + if (m_output_node == nullptr) { return; } // Mark all nodes that evaluate time using sync tracks. m_output_node->UpdateIsSynced(false); + if (m_paused) { + return; + } + // For all synced nodes calculate their current sync track durations for (int i = m_ordered_nodes.size() - 1; i >= 0; i--) { AnimNode* node = m_ordered_nodes[i];