Added a simple animation controller.

AnimGraphEditor
Martin Felis 2021-11-12 20:10:56 +01:00
parent 551bcca478
commit 37d2845b09
6 changed files with 229 additions and 27 deletions

View File

@ -46,6 +46,8 @@ target_sources(AnimTestbed PRIVATE
src/Camera.c src/Camera.c
src/SkinnedMesh.cc src/SkinnedMesh.cc
src/SkinnedMesh.h src/SkinnedMesh.h
src/AnimationController.cc
src/BlendNode.cc
3rdparty/glfw/deps/glad_gl.c 3rdparty/glfw/deps/glad_gl.c
3rdparty/imgui/imgui.cpp 3rdparty/imgui/imgui.cpp
3rdparty/imgui/imgui_draw.cpp 3rdparty/imgui/imgui_draw.cpp

138
src/AnimationController.cc Normal file
View File

@ -0,0 +1,138 @@
//
// Created by martin on 12.11.21.
//
#include "AnimationController.h"
#include <imgui.h>
#include <ozz/animation/runtime/blending_job.h>
#include <ozz/animation/runtime/local_to_model_job.h>
#include <ozz/animation/runtime/sampling_job.h>
#include "SkinnedMesh.h"
AnimationController::AnimationController(SkinnedMesh* skinned_mesh)
: m_current_time(0.f),
m_skinned_mesh(skinned_mesh),
m_blend_anim_A(nullptr),
m_blend_anim_B(nullptr),
m_blend_weight(0.f),
m_time_scale_A(1.f),
m_time_scale_B(1.f),
m_override_ratio_A(false),
m_override_ratio_B(false) {
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_sampling_cache_A.Resize(num_joints);
m_sampling_cache_B.Resize(num_joints);
m_local_matrices_A.resize(num_soa_joints);
m_local_matrices_B.resize(num_soa_joints);
m_local_matrices_blended.resize(num_soa_joints);
assert(skinned_mesh->m_animations.size() > 1);
SetBlendAnims(skinned_mesh->m_animations[1], skinned_mesh->m_animations[0]);
ResetAnims();
}
void AnimationController::Update(float dt) {
m_current_time += dt;
if (!m_override_ratio_A) {
m_anim_time_A += dt * m_time_scale_A;
const float duration_A = m_blend_anim_A->duration();
m_anim_ratio_A = fmodf((float)m_anim_time_A / duration_A, 1.0f);
}
if (!m_override_ratio_B) {
m_anim_time_B += dt * m_time_scale_B;
const float duration_B = m_blend_anim_B->duration();
m_anim_ratio_B = fmodf((float)m_anim_time_B / duration_B, 1.0f);
}
}
void AnimationController::Evaluate() {
// sample animation A
ozz::animation::SamplingJob sampling_job;
sampling_job.animation = m_blend_anim_A;
sampling_job.cache = &m_sampling_cache_A;
sampling_job.ratio = m_anim_ratio_A;
sampling_job.output = make_span(m_local_matrices_A);
if (!sampling_job.Run()) {
ozz::log::Err() << "Error sampling animation." << std::endl;
}
// sample animation B
sampling_job.animation = m_blend_anim_B;
sampling_job.cache = &m_sampling_cache_B;
sampling_job.ratio = m_anim_ratio_B;
sampling_job.output = make_span(m_local_matrices_B);
if (!sampling_job.Run()) {
ozz::log::Err() << "Error sampling animation." << std::endl;
}
// perform blend
ozz::animation::BlendingJob::Layer layers[2];
layers[0].transform = make_span(m_local_matrices_A);
layers[0].weight = (1.0f - m_blend_weight);
layers[1].transform = make_span(m_local_matrices_B);
layers[1].weight = (m_blend_weight);
ozz::animation::BlendingJob blend_job;
blend_job.threshold = ozz::animation::BlendingJob().threshold;
blend_job.layers = layers;
blend_job.bind_pose = m_skinned_mesh->m_skeleton.joint_bind_poses();
blend_job.output = make_span(m_local_matrices_blended);
if (!blend_job.Run()) {
ozz::log::Err() << "Error blending animations." << std::endl;
}
// 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_blended);
ltm_job.output = make_span(m_skinned_mesh->m_model_matrices);
ltm_job.Run();
};
void AnimationController::DrawDebugUi() {
ImGui::Begin("AnimationController");
int anim_count = m_skinned_mesh->m_animation_names.size();
const char* items[255] = {0};
int item_current_A = 0;
int item_current_B = 0;
for (int i = 0; i < anim_count; i++) {
items[i] = m_skinned_mesh->m_animation_names[i].c_str();
if (m_skinned_mesh->m_animations[i] == m_blend_anim_A) {
item_current_A = i;
}
if (m_skinned_mesh->m_animations[i] == m_blend_anim_B) {
item_current_B = i;
}
}
bool anim_changed = false;
anim_changed =
ImGui::Combo("Animation A", &item_current_A, items, anim_count);
ImGui::SliderFloat("Time Scale", &m_time_scale_A, 0.01f, 5.f);
ImGui::Checkbox("Override", &m_override_ratio_A);
ImGui::SameLine();
ImGui::SliderFloat("Ratio", &m_anim_ratio_A, 0.f, 1.f);
anim_changed = ImGui::Combo("Animation B", &item_current_B, items, anim_count)
|| anim_changed;
ImGui::SliderFloat("Time Scale##", &m_time_scale_B, 0.01f, 5.f);
ImGui::Checkbox("Override##", &m_override_ratio_B);
ImGui::SameLine();
ImGui::SliderFloat("Ratio##", &m_anim_ratio_B, 0.f, 1.f);
SetBlendAnims(
m_skinned_mesh->m_animations[item_current_A],
m_skinned_mesh->m_animations[item_current_B]);
ImGui::SliderFloat("Weight", &m_blend_weight, 0.f, 1.f);
ImGui::End();
}

60
src/AnimationController.h Normal file
View File

@ -0,0 +1,60 @@
//
// Created by martin on 12.11.21.
//
#ifndef ANIMTESTBED_ANIMATIONCONTROLLER_H
#define ANIMTESTBED_ANIMATIONCONTROLLER_H
#include <ozz/animation/runtime/sampling_job.h>
#include "ozz/animation/runtime/animation.h"
#include "ozz/base/containers/vector.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/maths/vec_float.h"
struct SkinnedMesh;
struct AnimationController {
explicit AnimationController(SkinnedMesh* skinned_mesh);
void SetBlendAnims(
ozz::animation::Animation* blend_anim_A,
ozz::animation::Animation* blend_anim_B) {
m_blend_anim_A = blend_anim_A;
m_blend_anim_B = blend_anim_B;
}
void ResetAnims() {
m_anim_time_A = 0.f;
m_anim_time_B = 0.f;
}
void Update(float dt);
void Evaluate();
void DrawDebugUi();
float m_current_time;
ozz::animation::Animation* m_blend_anim_A;
ozz::animation::Animation* m_blend_anim_B;
float m_time_scale_A;
float m_time_scale_B;
float m_anim_time_A;
float m_anim_time_B;
float m_anim_ratio_A;
float m_anim_ratio_B;
bool m_override_ratio_A;
bool m_override_ratio_B;
float m_blend_weight;
ozz::vector<ozz::math::SoaTransform> m_local_matrices_A;
ozz::vector<ozz::math::SoaTransform> m_local_matrices_B;
ozz::vector<ozz::math::SoaTransform> m_local_matrices_blended;
ozz::animation::SamplingCache m_sampling_cache_A;
ozz::animation::SamplingCache m_sampling_cache_B;
SkinnedMesh* m_skinned_mesh = nullptr;
};
#endif //ANIMTESTBED_ANIMATIONCONTROLLER_H

View File

@ -6,9 +6,9 @@
#include <HandmadeMath.h> #include <HandmadeMath.h>
#include <imgui.h> #include <imgui.h>
#include <util/sokol_gl.h>
#include "sokol_gfx.h" #include "sokol_gfx.h"
#include "util/sokol_gl.h"
static void draw_vec(const ozz::math::SimdFloat4& vec) { static void draw_vec(const ozz::math::SimdFloat4& vec) {
sgl_v3f(ozz::math::GetX(vec), ozz::math::GetY(vec), ozz::math::GetZ(vec)); sgl_v3f(ozz::math::GetX(vec), ozz::math::GetY(vec), ozz::math::GetZ(vec));
@ -60,7 +60,6 @@ void SkinnedMesh::DrawJoint(int joint_index, int parent_joint_index) {
draw_line(p5, p2); draw_line(p5, p2);
} }
SkinnedMesh::~SkinnedMesh() { SkinnedMesh::~SkinnedMesh() {
while (m_animations.size() > 0) { while (m_animations.size() > 0) {
ozz::animation::Animation* animation_ptr = ozz::animation::Animation* animation_ptr =
@ -133,10 +132,8 @@ bool SkinnedMesh::LoadAnimation(const char* filename) {
return true; return true;
} }
void SkinnedMesh::SetCurrentAnimation(int index) {
void SkinnedMesh::SetCurrentAnimation(int index) if (index < 0 || index >= m_animations.size()) {
{
if (index <= 0 || index >= m_animations.size()) {
ozz::log::Err() << "Invalid animation index " << index << " valid range: [" ozz::log::Err() << "Invalid animation index " << index << " valid range: ["
<< 0 << ", " << m_animations.size() << "]'" << std::endl; << 0 << ", " << m_animations.size() << "]'" << std::endl;
} }
@ -182,11 +179,10 @@ void SkinnedMesh::DrawSkeleton() {
sgl_pop_matrix(); sgl_pop_matrix();
} }
void SkinnedMesh::DrawDebugUi() {
void SkinnedMesh::DrawUi() {
ImGui::Begin("SkinnedMesh"); ImGui::Begin("SkinnedMesh");
ImGui::Checkbox("Time override", &state.time.anim_ratio_ui_override); // ImGui::Checkbox("Time override", &state.time.anim_ratio_ui_override);
// const float anim_duration = state.ozz->animation.duration(); // const float anim_duration = state.ozz->animation.duration();
// if (!state.time.anim_ratio_ui_override) { // if (!state.time.anim_ratio_ui_override) {
@ -195,12 +191,11 @@ void SkinnedMesh::DrawUi() {
// } // }
// ImGui::SliderFloat("Time", &state.time.anim_ratio, 0.f, 1.f); // ImGui::SliderFloat("Time", &state.time.anim_ratio, 0.f, 1.f);
ImGui::Text( // ImGui::Text(
"Application average %.3f ms/frame (%.1f FPS)", // "Application average %.3f ms/frame (%.1f FPS)",
1000.0f / ImGui::GetIO().Framerate, // 1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate); // ImGui::GetIO().Framerate);
ImGui::End(); ImGui::End();
}
} }

View File

@ -34,7 +34,7 @@ struct SkinnedMesh {
void DrawSkeleton(); void DrawSkeleton();
void DrawJoint(int joint_index, int parent_joint_index); void DrawJoint(int joint_index, int parent_joint_index);
void DrawUi(); void DrawDebugUi();
// void DrawSkinnedMesh(); // void DrawSkinnedMesh();
ozz::vector<ozz::animation::Animation*> m_animations; ozz::vector<ozz::animation::Animation*> m_animations;

View File

@ -46,6 +46,7 @@ static void draw_imgui(ImDrawData*);
#include <cmath> // fmodf #include <cmath> // fmodf
#include <memory> // std::unique_ptr, std::make_unique #include <memory> // std::unique_ptr, std::make_unique
#include "AnimationController.h"
#include "ozz/animation/runtime/animation.h" #include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/local_to_model_job.h" #include "ozz/animation/runtime/local_to_model_job.h"
#include "ozz/animation/runtime/sampling_job.h" #include "ozz/animation/runtime/sampling_job.h"
@ -194,13 +195,15 @@ int main() {
sgl_setup(&sgldesc); sgl_setup(&sgldesc);
printf ("default allocator: 0x%p\n", (void*)ozz::memory::default_allocator()); printf ("default allocator: 0x%p\n", (void*)ozz::memory::default_allocator());
SkinnedMesh gSkinnedMesh; SkinnedMesh skinned_mesh;
gSkinnedMesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz"); skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
gSkinnedMesh.LoadAnimation("../media/Idle-loop.ozz"); skinned_mesh.LoadAnimation("../media/Idle-loop.ozz");
gSkinnedMesh.LoadAnimation("../media/Walking-loop.ozz"); skinned_mesh.LoadAnimation("../media/Walking-loop.ozz");
gSkinnedMesh.LoadAnimation("../media/RunningSlow-loop.ozz"); skinned_mesh.LoadAnimation("../media/RunningSlow-loop.ozz");
gSkinnedMesh.LoadAnimation("../media/RunningFast-loop.ozz"); skinned_mesh.LoadAnimation("../media/RunningFast-loop.ozz");
gSkinnedMesh.SetCurrentAnimation(0); skinned_mesh.SetCurrentAnimation(0);
AnimationController animation_controller (&skinned_mesh);
// state.ozz = std::make_unique<ozz_t>(); // state.ozz = std::make_unique<ozz_t>();
state.time.factor = 1.0f; state.time.factor = 1.0f;
@ -314,6 +317,8 @@ int main() {
state.time.frame = stm_sec( state.time.frame = stm_sec(
stm_round_to_common_refresh_rate(stm_laptime(&state.time.laptime))); stm_round_to_common_refresh_rate(stm_laptime(&state.time.laptime)));
state.time.absolute += state.time.frame * state.time.factor;
int cur_width, cur_height; int cur_width, cur_height;
glfwGetFramebufferSize(w, &cur_width, &cur_height); glfwGetFramebufferSize(w, &cur_width, &cur_height);
@ -398,18 +403,20 @@ int main() {
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
} }
draw_grid();
gSkinnedMesh.DrawUi();
state.time.absolute += state.time.frame * state.time.factor; draw_grid();
gSkinnedMesh.EvalAnimation(state.time.absolute); skinned_mesh.DrawDebugUi();
animation_controller.DrawDebugUi();
animation_controller.Update(state.time.frame);
animation_controller.Evaluate();
sgl_defaults(); sgl_defaults();
sgl_matrix_mode_projection(); sgl_matrix_mode_projection();
sgl_load_matrix((const float*)&state.camera.mtxProj); sgl_load_matrix((const float*)&state.camera.mtxProj);
sgl_matrix_mode_modelview(); sgl_matrix_mode_modelview();
sgl_load_matrix((const float*)&state.camera.mtxView); sgl_load_matrix((const float*)&state.camera.mtxView);
gSkinnedMesh.DrawSkeleton(); skinned_mesh.DrawSkeleton();
// 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow() // 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow()
if (show_imgui_demo_window) { if (show_imgui_demo_window) {