From db3095fa07600e53a29d805d779c7894e3a57543 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Fri, 19 Nov 2021 12:40:14 +0100 Subject: [PATCH] WIP transition to using sync track based synchronisation. --- CMakeLists.txt | 2 + src/AnimNode.h | 6 +- src/AnimNodes/AnimSamplerNode.cc | 12 ++- src/AnimNodes/AnimSamplerNode.h | 8 +- src/AnimNodes/BlendNode.cc | 3 + src/AnimNodes/BlendNode.h | 21 ++-- src/AnimNodes/LockTranslationNode.h | 4 +- src/AnimNodes/SpeedScaleNode.h | 5 +- src/AnimationController.cc | 68 +++++++------ src/AnimationController.h | 8 +- src/SkinnedMesh.cc | 60 +++++------ src/SkinnedMesh.h | 128 ++---------------------- src/SyncTrack.cc | 46 +++++++++ src/SyncTrack.h | 148 ++++++++++++++++++++++++++++ src/main.cc | 34 ++++++- tests/SyncTrackTests.cc | 2 +- 16 files changed, 345 insertions(+), 210 deletions(-) create mode 100644 src/SyncTrack.cc create mode 100644 src/SyncTrack.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 27d29a9..23804a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,8 @@ add_library(AnimTestbedCode OBJECT src/Camera.c src/SkinnedMesh.cc src/SkinnedMesh.h + src/SyncTrack.cc + src/SyncTrack.h src/AnimNode.cc src/AnimNodes/AnimSamplerNode.cc src/AnimNodes/SpeedScaleNode.cc diff --git a/src/AnimNode.h b/src/AnimNode.h index ce7544e..0ef9be5 100644 --- a/src/AnimNode.h +++ b/src/AnimNode.h @@ -16,8 +16,7 @@ struct AnimNode { AnimNode(AnimationController* animation_controller) : m_animation_controller(animation_controller), m_current_time(0.f), - m_is_time_synced(false), - m_anim_duration(0.f) {} + m_is_time_synced(false) {} virtual ~AnimNode(){}; AnimNodeType m_anim_node_type; @@ -27,7 +26,6 @@ struct AnimNode { // When synced then current time is relative to the node's anim duration.:w bool m_is_time_synced; float m_current_time; - float m_anim_duration; SyncTrack m_sync_track; virtual void Reset() { m_current_time = 0.f; } @@ -37,7 +35,7 @@ struct AnimNode { // Evaluate the animation duration of this node. All input nodes must have already evaluated // their anim durations. - virtual void EvalAnimDuration() = 0; + virtual void UpdateSyncTrack() = 0; // Evaluate current time and propagate time step to inputs. virtual void UpdateTime(float dt) = 0; diff --git a/src/AnimNodes/AnimSamplerNode.cc b/src/AnimNodes/AnimSamplerNode.cc index 13c6542..7fee075 100644 --- a/src/AnimNodes/AnimSamplerNode.cc +++ b/src/AnimNodes/AnimSamplerNode.cc @@ -8,7 +8,7 @@ #include "../SkinnedMesh.h" -void AnimSamplerNode::SetAnimation(ozz::animation::Animation* animation) { +void AnimSamplerNode::SetAnimation(ozz::animation::Animation* animation, const SyncTrack& sync_track) { m_animation = animation; const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh; @@ -16,14 +16,17 @@ void AnimSamplerNode::SetAnimation(ozz::animation::Animation* animation) { const int num_joints = skinned_mesh->m_skeleton.num_joints(); m_local_matrices.resize(num_soa_joints); m_sampling_cache.Resize(num_joints); + + m_sync_track = sync_track; } void AnimSamplerNode::Evaluate( ozz::vector* local_matrices) { ozz::animation::SamplingJob sampling_job; + float ratio = m_sync_track.CalcRatioFromSyncTime(m_anim_ratio); sampling_job.animation = m_animation; sampling_job.cache = &m_sampling_cache; - sampling_job.ratio = m_anim_ratio; + sampling_job.ratio = ratio; sampling_job.output = make_span(*local_matrices); if (!sampling_job.Run()) { ozz::log::Err() << "Error sampling animation." << std::endl; @@ -44,8 +47,13 @@ void AnimSamplerNode::DrawDebugUi() { if (ImGui::Combo("Animation", &item_current, items, anim_count)) { m_animation = skinned_mesh->m_animations[item_current]; + m_sync_track = skinned_mesh->m_animation_sync_track[item_current]; } + ImGui::Checkbox("Override", &m_override_ratio); ImGui::SameLine(); ImGui::SliderFloat("Ratio", &m_anim_ratio, 0.f, 1.f); + + ImGui::Text("SyncTrack"); + m_sync_track.DrawDebugUi(); } \ No newline at end of file diff --git a/src/AnimNodes/AnimSamplerNode.h b/src/AnimNodes/AnimSamplerNode.h index b6a81f4..615218e 100644 --- a/src/AnimNodes/AnimSamplerNode.h +++ b/src/AnimNodes/AnimSamplerNode.h @@ -25,14 +25,13 @@ struct AnimSamplerNode : public AnimNode { ozz::vector m_local_matrices; ozz::animation::SamplingCache m_sampling_cache; - void SetAnimation(ozz::animation::Animation* animation); + void SetAnimation(ozz::animation::Animation* animation, const SyncTrack& sync_track); virtual void UpdateIsSynced(bool is_synced) override { m_is_time_synced = is_synced; } - virtual void EvalAnimDuration() override { - m_anim_duration = m_animation->duration(); + virtual void UpdateSyncTrack() override { } virtual void UpdateTime(float dt) override { @@ -42,8 +41,7 @@ struct AnimSamplerNode : public AnimNode { m_anim_ratio = fmodf((float)m_current_time, 1.0f); m_current_time = m_anim_ratio; } else { - const float duration = m_animation->duration(); - m_anim_ratio = fmodf((float)m_current_time / duration, 1.0f); + m_anim_ratio = fmodf((float)m_current_time / m_sync_track.m_duration, 1.0f); } } diff --git a/src/AnimNodes/BlendNode.cc b/src/AnimNodes/BlendNode.cc index 89071b3..1dfeb63 100644 --- a/src/AnimNodes/BlendNode.cc +++ b/src/AnimNodes/BlendNode.cc @@ -51,4 +51,7 @@ void BlendNode::Evaluate(ozz::vector* local_matrices) { void BlendNode::DrawDebugUi() { ImGui::SliderFloat("Weight", &m_weight, 0.f, 1.f); ImGui::Checkbox("Sync Inputs", &m_sync_inputs); + + ImGui::Text("SyncTrack"); + m_sync_track.DrawDebugUi(); } diff --git a/src/AnimNodes/BlendNode.h b/src/AnimNodes/BlendNode.h index 20c965a..ab1d666 100644 --- a/src/AnimNodes/BlendNode.h +++ b/src/AnimNodes/BlendNode.h @@ -35,9 +35,9 @@ struct BlendNode : public AnimNode { m_input_A->UpdateIsSynced(m_is_time_synced); } - virtual void EvalAnimDuration() override { + virtual void UpdateSyncTrack() override { if (m_is_time_synced) { - m_anim_duration = (1.0f - m_weight) * m_input_A->m_anim_duration + m_weight * m_input_B->m_anim_duration; + m_sync_track = SyncTrack::Blend(m_weight, m_input_A->m_sync_track, m_input_B->m_sync_track); } else { assert (false); } @@ -45,11 +45,20 @@ struct BlendNode : public AnimNode { virtual void UpdateTime(float dt) { if (m_is_time_synced) { - float current_time_absolute = m_current_time * m_anim_duration + dt; - m_current_time = fmodf(current_time_absolute / m_anim_duration, 1.0); + float current_time_absolute = fmodf(m_current_time * m_sync_track.m_duration + dt, 1.0f); + float current_sync_time = m_sync_track.CalcSyncFromAbsTime(current_time_absolute); + m_current_time = m_sync_track.CalcRatioFromSyncTime(current_sync_time); - m_input_A->UpdateTime(m_current_time - m_input_A->m_current_time); - m_input_B->UpdateTime(m_current_time - m_input_B->m_current_time); + float time_absolute_A = fmodf (m_input_A->m_current_time * m_input_A->m_sync_track.m_duration, 1.0f); + float sync_time_A = m_input_A->m_sync_track.CalcSyncFromAbsTime(time_absolute_A); + float m_ratio_A = m_input_A->m_sync_track.CalcSyncFromAbsTime(sync_time_A); + + float time_absolute_B = fmodf (m_input_B->m_current_time * m_input_B->m_sync_track.m_duration, 1.0f); + float sync_time_B = m_input_B->m_sync_track.CalcSyncFromAbsTime(time_absolute_B); + float m_ratio_B = m_input_B->m_sync_track.CalcSyncFromAbsTime(sync_time_B); + + m_input_A->UpdateTime(current_sync_time); + m_input_B->UpdateTime(current_sync_time); } else { m_current_time += dt; m_input_A->UpdateTime(dt); diff --git a/src/AnimNodes/LockTranslationNode.h b/src/AnimNodes/LockTranslationNode.h index 0d328e9..7aa197d 100644 --- a/src/AnimNodes/LockTranslationNode.h +++ b/src/AnimNodes/LockTranslationNode.h @@ -32,8 +32,8 @@ struct LockTranslationNode : public AnimNode { m_input->UpdateIsSynced(m_is_time_synced); } - virtual void EvalAnimDuration() override { - m_anim_duration = m_input->m_anim_duration; + virtual void UpdateSyncTrack() override { + m_sync_track = m_input->m_sync_track; } virtual void UpdateTime(float dt) { m_input->UpdateTime(dt); } diff --git a/src/AnimNodes/SpeedScaleNode.h b/src/AnimNodes/SpeedScaleNode.h index e0ba4d2..e957b58 100644 --- a/src/AnimNodes/SpeedScaleNode.h +++ b/src/AnimNodes/SpeedScaleNode.h @@ -23,9 +23,10 @@ struct SpeedScaleNode : public AnimNode { m_input_node->UpdateIsSynced(is_synced); } - virtual void EvalAnimDuration() override { + virtual void UpdateSyncTrack() override { assert(fabs(m_time_scale) >= 0.01f); - m_anim_duration = fabsf(m_input_node->m_anim_duration / m_time_scale); + m_sync_track = m_input_node->m_sync_track; + m_sync_track.m_duration =fabsf(m_input_node->m_sync_track.m_duration / m_time_scale); } virtual void UpdateTime(float dt) { diff --git a/src/AnimationController.cc b/src/AnimationController.cc index 3a843cb..99f5f9a 100644 --- a/src/AnimationController.cc +++ b/src/AnimationController.cc @@ -17,36 +17,34 @@ #include "SkinnedMesh.h" AnimationController::AnimationController(SkinnedMesh* skinned_mesh) - : m_current_time(0.f), m_skinned_mesh(skinned_mesh) { + : m_current_time(0.f), m_paused(false), m_skinned_mesh(skinned_mesh) { const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints(); const int num_joints = skinned_mesh->m_skeleton.num_joints(); skinned_mesh->m_local_matrices.resize(num_soa_joints); skinned_mesh->m_model_matrices.resize(num_joints); - m_local_matrices.resize(num_soa_joints); - ResetAnims(); AnimSamplerNode* sampler_node0 = new AnimSamplerNode(this); sampler_node0->m_name = "AnimSampler0"; - sampler_node0->SetAnimation(skinned_mesh->m_animations[1]); + sampler_node0->SetAnimation(skinned_mesh->m_animations[1], skinned_mesh->m_animation_sync_track[1]); m_anim_nodes.push_back(sampler_node0); AnimSamplerNode* sampler_node1 = new AnimSamplerNode(this); sampler_node1->m_name = "AnimSampler1"; - sampler_node1->SetAnimation(skinned_mesh->m_animations[2]); + sampler_node1->SetAnimation(skinned_mesh->m_animations[2], skinned_mesh->m_animation_sync_track[1]); 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); +// 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 = speed_node; + blend_node->m_input_A = sampler_node0; blend_node->m_input_B = sampler_node1; - blend_node->m_sync_inputs = false; + blend_node->m_sync_inputs = true; m_anim_nodes.push_back(blend_node); SpeedScaleNode* speed_node1 = new SpeedScaleNode(this); @@ -54,12 +52,12 @@ AnimationController::AnimationController(SkinnedMesh* skinned_mesh) speed_node1->m_input_node = blend_node; m_anim_nodes.push_back(speed_node1); - LockTranslationNode* lock_node = new LockTranslationNode(this); - lock_node->m_name = "LockNode0"; - lock_node->m_locked_bone_index = 0; - lock_node->m_input = speed_node1; +// LockTranslationNode* lock_node = new LockTranslationNode(this); +// lock_node->m_name = "LockNode0"; +// lock_node->m_locked_bone_index = 0; +// lock_node->m_input = speed_node1; - m_output_node = lock_node; + m_output_node = m_anim_nodes.back(); UpdateOrderedNodes(); @@ -100,10 +98,8 @@ void AnimationController::UpdateOrderedNodes() { } } -void AnimationController::UpdateBlendLogic() {} - void AnimationController::UpdateTime(float dt) { - if (m_output_node == nullptr) { + if (m_paused || m_output_node == nullptr) { return; } @@ -114,7 +110,7 @@ void AnimationController::UpdateTime(float dt) { for (int i = m_ordered_nodes.size() - 1; i >= 0; i--) { AnimNode* node = m_ordered_nodes[i]; if (node->m_is_time_synced) { - node->EvalAnimDuration(); + node->UpdateSyncTrack(); } } @@ -126,14 +122,8 @@ void AnimationController::Evaluate() { if (m_output_node == nullptr) { return; } - m_output_node->Evaluate(&m_local_matrices); - // convert joint matrices from local to model space - ozz::animation::LocalToModelJob ltm_job; - ltm_job.skeleton = &m_skinned_mesh->m_skeleton; - ltm_job.input = make_span(m_local_matrices); - ltm_job.output = make_span(m_skinned_mesh->m_model_matrices); - ltm_job.Run(); + m_output_node->Evaluate(&m_skinned_mesh->m_local_matrices); }; void AnimationController::DrawDebugUi() { @@ -143,6 +133,24 @@ void AnimationController::DrawDebugUi() { if (ImGui::Button("Reset")) { ResetAnims(); } + ImGui::SameLine(); + if (m_paused) { + if (ImGui::Button("Play")) { + m_paused = false; + } + } else { + if (ImGui::Button("Pause")) { + m_paused = true; + } + } + ImGui::SameLine(); + if (ImGui::Button("Step")) { + bool was_paused = m_paused; + m_paused = false; + UpdateTime(0.1); + Evaluate(); + m_paused = was_paused; + } ImVec2 node_size(200, 100); for (int i = 0; i < m_ordered_nodes.size(); i++) { @@ -157,6 +165,8 @@ void AnimationController::DrawDebugUi() { ImGui::End(); } + ImGui::Text ("Node States"); + ImGui::Columns(4, "Node States"); // 4-ways, with border ImGui::Separator(); ImGui::Text("Name"); @@ -184,8 +194,8 @@ void AnimationController::DrawDebugUi() { ImGui::Text(node->m_is_time_synced ? "X" : "-"); ImGui::NextColumn(); - ImGui::PushID((void*)&node->m_anim_duration); - ImGui::Text("%2.3f", node->m_anim_duration); + ImGui::PushID((void*)&node->m_sync_track.m_duration); + ImGui::Text("%2.3f", node->m_sync_track.m_duration); ImGui::NextColumn(); ImGui::PopID(); diff --git a/src/AnimationController.h b/src/AnimationController.h index 8638dbe..7895ab9 100644 --- a/src/AnimationController.h +++ b/src/AnimationController.h @@ -23,8 +23,6 @@ struct AnimationController { // Creates a list of nodes where for node at index i for all inputs holds index > i. void UpdateOrderedNodes(); - void UpdateBlendLogic(); - // Updates all nodes. void UpdateTime(float dt); @@ -34,11 +32,7 @@ struct AnimationController { void DrawDebugUi(); float m_current_time; - - ozz::vector m_local_matrices; - ozz::vector m_local_matrices_blended; - - ozz::animation::SamplingCache m_sampling_cache_A; + bool m_paused; SkinnedMesh* m_skinned_mesh = nullptr; diff --git a/src/SkinnedMesh.cc b/src/SkinnedMesh.cc index 7479be4..f113cb1 100644 --- a/src/SkinnedMesh.cc +++ b/src/SkinnedMesh.cc @@ -7,7 +7,6 @@ #include #include - SkinnedMesh::~SkinnedMesh() { while (m_animations.size() > 0) { ozz::animation::Animation* animation_ptr = @@ -76,6 +75,7 @@ bool SkinnedMesh::LoadAnimation(const char* filename) { m_animations.push_back(animation_ptr); m_animation_names.push_back(filename); + m_animation_sync_track.push_back(SyncTrack()); return true; } @@ -89,20 +89,7 @@ void SkinnedMesh::SetCurrentAnimation(int index) { m_current_animation = m_animations[index]; } -//bool LoadMesh (const char* filename); - -void SkinnedMesh::EvalAnimation(float in_time) { - const float anim_duration = m_current_animation->duration(); - float anim_ratio = fmodf((float)in_time / anim_duration, 1.0f); - - // sample animation - ozz::animation::SamplingJob sampling_job; - sampling_job.animation = m_current_animation; - sampling_job.cache = &m_cache; - sampling_job.ratio = anim_ratio; - sampling_job.output = make_span(m_local_matrices); - sampling_job.Run(); - +void SkinnedMesh::CalcModelMatrices() { // convert joint matrices from local to model space ozz::animation::LocalToModelJob ltm_job; ltm_job.skeleton = &m_skeleton; @@ -111,27 +98,40 @@ void SkinnedMesh::EvalAnimation(float in_time) { ltm_job.Run(); } -void SkinnedMesh::DrawSkeleton() { - -} +void SkinnedMesh::DrawSkeleton() {} void SkinnedMesh::DrawDebugUi() { ImGui::Begin("SkinnedMesh"); -// ImGui::Checkbox("Time override", &state.time.anim_ratio_ui_override); + ImGui::Text("Animations"); - // const float anim_duration = state.ozz->animation.duration(); - // if (!state.time.anim_ratio_ui_override) { - // state.time.anim_ratio = - // fmodf((float)state.time.absolute / anim_duration, 1.0f); - // } - // ImGui::SliderFloat("Time", &state.time.anim_ratio, 0.f, 1.f); + const char* items[255] = {0}; + static int selected = -1; + for (int i = 0; i < m_animations.size(); i++) { + items[i] = m_animation_names[i].c_str(); + } -// ImGui::Text( -// "Application average %.3f ms/frame (%.1f FPS)", -// 1000.0f / ImGui::GetIO().Framerate, -// ImGui::GetIO().Framerate); + ImGui::Combo("Animation", &selected, items, m_animations.size()); + + ImGui::Text("Sync Track"); + if (selected >= 0 && selected < m_animations.size()) { + m_animation_sync_track[selected].DrawDebugUi(); + m_override_anim = selected; + + ImGui::Checkbox("Override Animation", &m_sync_track_override); + if (m_sync_track_override) { + ImGui::SliderFloat("Ratio", &m_override_ratio, 0.f, 1.f); + + ozz::animation::SamplingJob sampling_job; + sampling_job.animation = m_animations[selected]; + sampling_job.cache = &m_cache; + sampling_job.ratio = m_override_ratio; + sampling_job.output = make_span(m_local_matrices); + if (!sampling_job.Run()) { + ozz::log::Err() << "Error sampling animation." << std::endl; + } + } + } ImGui::End(); - } \ No newline at end of file diff --git a/src/SkinnedMesh.h b/src/SkinnedMesh.h index b117279..c86fff9 100644 --- a/src/SkinnedMesh.h +++ b/src/SkinnedMesh.h @@ -9,6 +9,7 @@ #include // fmodf #include // std::unique_ptr, std::make_unique +#include "SyncTrack.h" #include "ozz/animation/runtime/animation.h" #include "ozz/animation/runtime/local_to_model_job.h" #include "ozz/animation/runtime/sampling_job.h" @@ -20,127 +21,10 @@ #include "ozz/base/maths/soa_transform.h" #include "ozz/base/maths/vec_float.h" -constexpr int cSyncTrackMaxIntervals = 8; - -struct SyncTrack { - float m_duration; - int m_num_intervals; - float m_sync_markers[cSyncTrackMaxIntervals]; - float m_interval_start[cSyncTrackMaxIntervals]; - float m_interval_ratio[cSyncTrackMaxIntervals]; - - void CalcIntervals() { - int i; - - if (m_num_intervals == 0) { - m_num_intervals = 1; - m_interval_start[0] = 0.f; - m_interval_ratio[0] = 1.f; - } else { - for (i = 0; i < m_num_intervals; i++) { - assert (m_sync_markers[i] >= 0.f && m_sync_markers[i] <= 1.0f); - - int end_index = i < m_num_intervals - 1 ? i + 1 : 0; - - m_interval_start[i] = m_sync_markers[i]; - float interval_end = m_sync_markers[end_index]; - - if (interval_end < m_interval_start[i]) { - interval_end += 1.0f; - } - - m_interval_ratio[i] = interval_end - m_interval_start[i]; - } - } - } - - float CalcSyncFromAbsTime(float abs_time) { - float sync_time = fmodf(abs_time, m_duration) / m_duration; - - int interval_index = 0; - while (sync_time >= m_interval_ratio[interval_index]) { - sync_time -= m_interval_ratio[interval_index]; - interval_index++; - } - - return float(interval_index) + sync_time / m_interval_ratio[interval_index]; - } - - float CalcRatioFromSyncTime(float sync_time) { - float interval_ratio = fmodf(sync_time, 1.0f); - int interval = int(sync_time - interval_ratio); - - return fmodf( - m_interval_start[interval] - + m_interval_ratio[interval] * interval_ratio, - 1.0f); - } - - bool operator==(const SyncTrack& other) const { - bool result = m_duration == other.m_duration - && m_num_intervals == other.m_num_intervals; - - if (!result) { - return false; - } - - for (int i = 0; i < m_num_intervals; i++) { - if ((fabsf(m_interval_start[i] - other.m_interval_start[i]) > 1.0e-5) - || (fabsf(m_interval_ratio[i] - other.m_interval_ratio[i]) - > 1.0e-5)) { - return false; - } - } - - return true; - } - - static SyncTrack - Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) { - assert(track_A.m_num_intervals == track_B.m_num_intervals); - - SyncTrack result; - result.m_num_intervals = track_A.m_num_intervals; - - result.m_duration = - (1.0f - weight) * track_A.m_duration + weight * track_B.m_duration; - - float interval_0_offset = - track_B.m_interval_start[0] - track_A.m_interval_start[0]; - if (interval_0_offset > 0.5f) { - interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f); - } else if (interval_0_offset < -0.5) { - interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f); - } - - result.m_interval_start[0] = fmodf( - 1.0 + (1.0f - weight) * track_A.m_interval_start[0] - + weight * (track_A.m_interval_start[0] + interval_0_offset), - 1.0f); - result.m_sync_markers[0] = result.m_interval_start[0]; - - for (int i = 0; i < result.m_num_intervals; i++) { - float interval_duration_A = track_A.m_interval_ratio[i]; - float interval_duration_B = track_B.m_interval_ratio[i]; - result.m_interval_ratio[i] = - (1.0f - weight) * interval_duration_A + weight * interval_duration_B; - - if (i < cSyncTrackMaxIntervals) { - result.m_interval_start[i + 1] = - result.m_interval_start[i] + result.m_interval_ratio[i]; - if (result.m_interval_start[i + 1] > 1.0f) { - result.m_interval_start[i + 1] = - fmodf(result.m_interval_start[i + 1], 1.0f); - } - } - } - - return result; - } -}; - struct SkinnedMesh { + SkinnedMesh() : m_sync_track_override(false), m_override_anim(0.f) {} virtual ~SkinnedMesh(); + bool LoadSkeleton(const char* filename); bool LoadAnimation(const char* filename); //bool LoadMesh (const char* filename); @@ -153,7 +37,7 @@ struct SkinnedMesh { return m_current_animation->duration(); }; - void EvalAnimation(float in_time); + void CalcModelMatrices(); void DrawSkeleton(); void DrawJoint(int joint_index, int parent_joint_index); @@ -168,6 +52,10 @@ struct SkinnedMesh { ozz::animation::SamplingCache m_cache; ozz::vector m_local_matrices; ozz::vector m_model_matrices; + + bool m_sync_track_override; + int m_override_anim; + float m_override_ratio; }; #endif //ANIMTESTBED_SKINNEDMESH_H diff --git a/src/SyncTrack.cc b/src/SyncTrack.cc new file mode 100644 index 0000000..878b5bb --- /dev/null +++ b/src/SyncTrack.cc @@ -0,0 +1,46 @@ +// +// Created by martin on 19.11.21. +// + +#include "SyncTrack.h" + +#include + +#include + +void SyncTrack::DrawDebugUi() { + ImGui::SliderFloat("duration", &m_duration, 0.001f, 10.f); + + ImGui::Text("Marker"); + ImGui::SameLine(); + ImGui::Text("%d", m_num_intervals); + ImGui::SameLine(); + if (ImGui::Button("+")) { + if (m_num_intervals < cSyncTrackMaxIntervals) { + m_num_intervals ++; + } + } + ImGui::SameLine(); + if (ImGui::Button("-")) { + if (m_num_intervals > 0) { + m_num_intervals --; + } + } + + ImGui::Text("Marker:"); + for (int i = 0; i < m_num_intervals; i++) { + ImGui::Text("%2d:", i); + ImGui::SameLine(); + std::ostringstream marker_stream; + marker_stream << i; + ImGui::SliderFloat( + marker_stream.str().c_str(), + &m_sync_markers[i], + 0.f, + 1.f); + } + + if (ImGui::Button ("Update Intervals")) { + CalcIntervals(); + } +} \ No newline at end of file diff --git a/src/SyncTrack.h b/src/SyncTrack.h new file mode 100644 index 0000000..ead2844 --- /dev/null +++ b/src/SyncTrack.h @@ -0,0 +1,148 @@ +// +// Created by martin on 19.11.21. +// + +#ifndef ANIMTESTBED_SYNCTRACK_H +#define ANIMTESTBED_SYNCTRACK_H + +#include +#include + +constexpr int cSyncTrackMaxIntervals = 8; + +struct SyncTrack { + float m_duration; + int m_num_intervals; + float m_sync_markers[cSyncTrackMaxIntervals]; + float m_interval_start[cSyncTrackMaxIntervals]; + float m_interval_ratio[cSyncTrackMaxIntervals]; + + void CalcIntervals() { + if (m_num_intervals == 0) { + m_num_intervals = 1; + m_sync_markers[0] = 0.f; + } + for (int i = 0; i < m_num_intervals; i++) { + assert(m_sync_markers[i] >= 0.f && m_sync_markers[i] <= 1.0f); + + int end_index = i < m_num_intervals - 1 ? i + 1 : 0; + + m_interval_start[i] = m_sync_markers[i]; + float interval_end = m_sync_markers[end_index]; + + if (interval_end < m_interval_start[i]) { + interval_end += 1.0f; + } + + m_interval_ratio[i] = interval_end - m_interval_start[i]; + } + } + + float CalcSyncFromAbsTime(float abs_time) { + float sync_time = fmodf(abs_time, m_duration) / m_duration; + + int interval_index = 0; + while (sync_time >= m_interval_ratio[interval_index]) { + sync_time -= m_interval_ratio[interval_index]; + interval_index++; + } + + return float(interval_index) + sync_time / m_interval_ratio[interval_index]; + } + + float CalcRatioFromSyncTime(float sync_time) { + float interval_ratio = fmodf(sync_time, 1.0f); + int interval = int(sync_time - interval_ratio); + + return fmodf( + m_interval_start[interval] + + m_interval_ratio[interval] * interval_ratio, + 1.0f); + } + + bool operator==(const SyncTrack& other) const { + bool result = m_duration == other.m_duration + && m_num_intervals == other.m_num_intervals; + + if (!result) { + return false; + } + + for (int i = 0; i < m_num_intervals; i++) { + if ((fabsf(m_interval_start[i] - other.m_interval_start[i]) > 1.0e-5) + || (fabsf(m_interval_ratio[i] - other.m_interval_ratio[i]) + > 1.0e-5)) { + return false; + } + } + + return true; + } + + static SyncTrack CreateFromMarkers( + float duration, + int n_markers, + float markers[cSyncTrackMaxIntervals]) { + SyncTrack result; + result.m_duration = duration; + result.m_num_intervals = n_markers; + for (int i = 0; i < n_markers; i++) { + result.m_sync_markers[i] = markers[i]; + } + + result.CalcIntervals(); + + return result; + } + + static SyncTrack + Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) { + assert(track_A.m_num_intervals == track_B.m_num_intervals); + + SyncTrack result; + result.m_num_intervals = track_A.m_num_intervals; + + result.m_duration = + (1.0f - weight) * track_A.m_duration + weight * track_B.m_duration; + + float interval_0_offset = + track_B.m_interval_start[0] - track_A.m_interval_start[0]; + if (interval_0_offset > 0.5f) { + interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f); + } else if (interval_0_offset < -0.5) { + interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f); + } + + result.m_interval_start[0] = fmodf( + 1.0 + (1.0f - weight) * track_A.m_interval_start[0] + + weight * (track_A.m_interval_start[0] + interval_0_offset), + 1.0f); + result.m_sync_markers[0] = result.m_interval_start[0]; + + for (int i = 0; i < result.m_num_intervals; i++) { + float interval_duration_A = track_A.m_interval_ratio[i]; + float interval_duration_B = track_B.m_interval_ratio[i]; + result.m_interval_ratio[i] = + (1.0f - weight) * interval_duration_A + weight * interval_duration_B; + + if (i < cSyncTrackMaxIntervals) { + result.m_interval_start[i + 1] = + result.m_interval_start[i] + result.m_interval_ratio[i]; + if (result.m_interval_start[i + 1] > 1.0f) { + result.m_interval_start[i + 1] = + fmodf(result.m_interval_start[i + 1], 1.0f); + } + + result.m_sync_markers[i + 1] = result.m_interval_start[i + 1]; + } + } + + assert (result.m_num_intervals < cSyncTrackMaxIntervals); + + return result; + } + + void DrawDebugUi(); +}; + +#endif //ANIMTESTBED_SYNCTRACK_H diff --git a/src/main.cc b/src/main.cc index 61ab7fa..db00ece 100644 --- a/src/main.cc +++ b/src/main.cc @@ -199,11 +199,36 @@ int main() { SkinnedMesh skinned_mesh; skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz"); skinned_mesh.LoadAnimation("../media/Idle-loop.ozz"); + + int anim_idx = 0; + float idle_markers[] = {0.f, 0.5}; + skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(skinned_mesh.m_animations[anim_idx]->duration(), 2, idle_markers); + + anim_idx = 1; skinned_mesh.LoadAnimation("../media/Walking-loop.ozz"); + float walking_markers[] = {0.293, 0.762}; + skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(skinned_mesh.m_animations[anim_idx]->duration(), 2, walking_markers); + + anim_idx = 2; skinned_mesh.LoadAnimation("../media/RunningSlow-loop.ozz"); + float running_slow_markers[] = {0.315, 0.834}; + skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(skinned_mesh.m_animations[anim_idx]->duration(), 2, running_slow_markers); + + anim_idx = 3; skinned_mesh.LoadAnimation("../media/RunningFast-loop.ozz"); + float running_fast_markers[] = {0.403, 0.818}; + skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(skinned_mesh.m_animations[anim_idx]->duration(), 2, running_fast_markers); + + anim_idx = 4; skinned_mesh.LoadAnimation("../media/Limping-loop.ozz"); + float limping_markers[] = {0.757, 0.044}; + skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(skinned_mesh.m_animations[anim_idx]->duration(), 2, limping_markers); + + anim_idx = 5; skinned_mesh.LoadAnimation("../media/ZombieWalk-loop.ozz"); + float zombiewalk_markers[] = {0.619, 0.}; + skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(skinned_mesh.m_animations[anim_idx]->duration(), 2, zombiewalk_markers); + skinned_mesh.SetCurrentAnimation(0); AnimationController animation_controller (&skinned_mesh); @@ -408,11 +433,16 @@ int main() { draw_grid(); + ImGui::SetNextWindowPos(ImVec2 (600, 60), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2 (300, 400), ImGuiCond_FirstUseEver); skinned_mesh.DrawDebugUi(); animation_controller.DrawDebugUi(); - animation_controller.UpdateTime(state.time.frame); - animation_controller.Evaluate(); + if (!skinned_mesh.m_sync_track_override) { + animation_controller.UpdateTime(state.time.frame); + animation_controller.Evaluate(); + } + skinned_mesh.CalcModelMatrices(); sgl_defaults(); sgl_matrix_mode_projection(); diff --git a/tests/SyncTrackTests.cc b/tests/SyncTrackTests.cc index ccd3cca..9190434 100644 --- a/tests/SyncTrackTests.cc +++ b/tests/SyncTrackTests.cc @@ -2,7 +2,7 @@ // Created by martin on 16.11.21. // -#include "SkinnedMesh.h" +#include "SyncTrack.h" #include "catch.hpp" TEST_CASE("Basic", "[SyncTrack]") {