Added a simple animation controller.
parent
551bcca478
commit
37d2845b09
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -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();
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
31
src/main.cc
31
src/main.cc
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue