diff --git a/CMakeLists.txt b/CMakeLists.txt index 392922a..a3c1fbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,8 @@ target_link_libraries(AnimTestbed glfw ozz_base ozz_geometry ozz_animation ${OPE target_sources(AnimTestbed PRIVATE src/main.cc src/Camera.c + src/SkinnedMesh.cc + src/SkinnedMesh.h 3rdparty/glfw/deps/glad_gl.c 3rdparty/imgui/imgui.cpp 3rdparty/imgui/imgui_draw.cpp diff --git a/src/SkinnedMesh.cc b/src/SkinnedMesh.cc new file mode 100644 index 0000000..71ae8d5 --- /dev/null +++ b/src/SkinnedMesh.cc @@ -0,0 +1,206 @@ +// +// Created by martin on 12.11.21. +// + +#include "SkinnedMesh.h" + +#include +#include +#include + +#include "sokol_gfx.h" + +static void draw_vec(const ozz::math::SimdFloat4& vec) { + sgl_v3f(ozz::math::GetX(vec), ozz::math::GetY(vec), ozz::math::GetZ(vec)); +} + +static void draw_line( + const ozz::math::SimdFloat4& v0, + const ozz::math::SimdFloat4& v1) { + draw_vec(v0); + draw_vec(v1); +} + +// this draws a wireframe 3d rhombus between the current and parent joints +void SkinnedMesh::DrawJoint(int joint_index, int parent_joint_index) { + if (parent_joint_index < 0) { + return; + } + + using namespace ozz::math; + + const Float4x4& m0 = m_model_matrices[joint_index]; + const Float4x4& m1 = m_model_matrices[parent_joint_index]; + + const SimdFloat4 p0 = m0.cols[3]; + const SimdFloat4 p1 = m1.cols[3]; + const SimdFloat4 ny = m1.cols[1]; + const SimdFloat4 nz = m1.cols[2]; + + const SimdFloat4 len = SplatX(Length3(p1 - p0)) * simd_float4::Load1(0.1f); + + const SimdFloat4 pmid = p0 + (p1 - p0) * simd_float4::Load1(0.66f); + const SimdFloat4 p2 = pmid + ny * len; + const SimdFloat4 p3 = pmid + nz * len; + const SimdFloat4 p4 = pmid - ny * len; + const SimdFloat4 p5 = pmid - nz * len; + + sgl_c3f(1.0f, 1.0f, 0.0f); + draw_line(p0, p2); + draw_line(p0, p3); + draw_line(p0, p4); + draw_line(p0, p5); + draw_line(p1, p2); + draw_line(p1, p3); + draw_line(p1, p4); + draw_line(p1, p5); + draw_line(p2, p3); + draw_line(p3, p4); + draw_line(p4, p5); + draw_line(p5, p2); +} + + +SkinnedMesh::~SkinnedMesh() { + while (m_animations.size() > 0) { + ozz::animation::Animation* animation_ptr = + m_animations[m_animations.size() - 1]; + delete animation_ptr; + m_animations.pop_back(); + } +} + +bool SkinnedMesh::LoadSkeleton(const char* filename) { + // const char* skeleton_file = "../media/skeleton.ozz"; + const char* skeleton_file = "../media/MixamoYBot-skeleton.ozz"; + + assert(filename); + ozz::log::Out() << "Loading skeleton archive " << filename << "." + << std::endl; + ozz::io::File file(filename, "rb"); + if (!file.opened()) { + ozz::log::Err() << "Failed to open skeleton file " << filename << "." + << std::endl; + return false; + } + ozz::io::IArchive archive(&file); + if (!archive.TestTag()) { + ozz::log::Err() << "Failed to load skeleton instance from file " << filename + << "." << std::endl; + return false; + } + + // Once the tag is validated, reading cannot fail. + archive >> m_skeleton; + + const int num_soa_joints = m_skeleton.num_soa_joints(); + const int num_joints = m_skeleton.num_joints(); + m_local_matrices.resize(num_soa_joints); + m_model_matrices.resize(num_joints); + m_cache.Resize(num_joints); + std::cout << "Successfully loaded " << skeleton_file + << " (soa: " << num_soa_joints << ", joints: " << num_joints << ")" + << std::endl; + + return true; +} + +bool SkinnedMesh::LoadAnimation(const char* filename) { + ozz::animation::Animation* animation_ptr = new ozz::animation::Animation(); + + assert(filename); + ozz::log::Out() << "Loading animation archive: " << filename << "." + << std::endl; + ozz::io::File file(filename, "rb"); + if (!file.opened()) { + ozz::log::Err() << "Failed to open animation file " << filename << "." + << std::endl; + return false; + } + ozz::io::IArchive archive(&file); + if (!archive.TestTag()) { + ozz::log::Err() << "Failed to load animation instance from file " + << filename << "." << std::endl; + return false; + } + + // Once the tag is validated, reading cannot fail. + archive >> *animation_ptr; + + m_animations.push_back(animation_ptr); + m_animation_names.push_back(filename); + + return true; +} + + +void SkinnedMesh::SetCurrentAnimation(int index) +{ + if (index <= 0 || index >= m_animations.size()) { + ozz::log::Err() << "Invalid animation index " << index << " valid range: [" + << 0 << ", " << m_animations.size() << "]'" << std::endl; + } + + 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(); + + // convert joint matrices from local to model space + ozz::animation::LocalToModelJob ltm_job; + ltm_job.skeleton = &m_skeleton; + ltm_job.input = make_span(m_local_matrices); + ltm_job.output = make_span(m_model_matrices); + ltm_job.Run(); +} + +void SkinnedMesh::DrawSkeleton() { + sgl_matrix_mode_modelview(); + sgl_push_matrix(); + hmm_mat4 scale_mat = HMM_Scale(HMM_Vec3(0.01f, 0.01f, 0.01f)); + sgl_mult_matrix((const float*)&scale_mat); + + const int num_joints = m_skeleton.num_joints(); + ozz::span joint_parents = m_skeleton.joint_parents(); + sgl_begin_lines(); + for (int joint_index = 0; joint_index < num_joints; joint_index++) { + DrawJoint(joint_index, joint_parents[joint_index]); + } + sgl_end(); + sgl_pop_matrix(); +} + + +void SkinnedMesh::DrawUi() { + ImGui::Begin("SkinnedMesh"); + + ImGui::Checkbox("Time override", &state.time.anim_ratio_ui_override); + + // 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); + + ImGui::Text( + "Application average %.3f ms/frame (%.1f FPS)", + 1000.0f / ImGui::GetIO().Framerate, + ImGui::GetIO().Framerate); + + ImGui::End(); +} + +} \ No newline at end of file diff --git a/src/SkinnedMesh.h b/src/SkinnedMesh.h new file mode 100644 index 0000000..552309e --- /dev/null +++ b/src/SkinnedMesh.h @@ -0,0 +1,49 @@ +// +// Created by martin on 12.11.21. +// + +#ifndef ANIMTESTBED_SKINNEDMESH_H +#define ANIMTESTBED_SKINNEDMESH_H + +// ozz-animation headers +#include // fmodf +#include // std::unique_ptr, std::make_unique + +#include "ozz/animation/runtime/animation.h" +#include "ozz/animation/runtime/local_to_model_job.h" +#include "ozz/animation/runtime/sampling_job.h" +#include "ozz/animation/runtime/skeleton.h" +#include "ozz/base/containers/vector.h" +#include "ozz/base/io/archive.h" +#include "ozz/base/io/stream.h" +#include "ozz/base/log.h" +#include "ozz/base/maths/soa_transform.h" +#include "ozz/base/maths/vec_float.h" + +struct SkinnedMesh { + virtual ~SkinnedMesh(); + bool LoadSkeleton(const char* filename); + bool LoadAnimation(const char* filename); + //bool LoadMesh (const char* filename); + + void SetCurrentAnimation(int index); + const ozz::animation::Animation* GetCurrentAnimation() {return m_current_animation; }; + float GetCurrentAnimationDuration() {return m_current_animation->duration(); }; + + void EvalAnimation(float in_time); + void DrawSkeleton(); + void DrawJoint(int joint_index, int parent_joint_index); + + void DrawUi(); + // void DrawSkinnedMesh(); + + ozz::vector m_animations; + std::vector m_animation_names; + ozz::animation::Skeleton m_skeleton; + ozz::animation::Animation* m_current_animation; + ozz::animation::SamplingCache m_cache; + ozz::vector m_local_matrices; + ozz::vector m_model_matrices; +}; + +#endif //ANIMTESTBED_SKINNEDMESH_H diff --git a/src/main.cc b/src/main.cc index 91c4b6e..47da501 100644 --- a/src/main.cc +++ b/src/main.cc @@ -15,6 +15,7 @@ #include #include "Camera.h" +#include "SkinnedMesh.h" #include "GLFW/glfw3.h" const int Width = 1024; @@ -109,10 +110,6 @@ ControlMode gControlMode = ControlMode::ControlModeNone; static uint8_t skel_data_buffer[4 * 1024]; static uint8_t anim_data_buffer[32 * 1024]; -static void load_skeleton(void); -static void load_animation(void); -static void eval_animation(void); -static void draw_skeleton(void); static void draw_grid(void); static void draw_ui(void); // static void skeleton_data_loaded(const sfetch_response_t* response); @@ -196,7 +193,16 @@ int main() { sgldesc.sample_count = 0; sgl_setup(&sgldesc); - state.ozz = std::make_unique(); + printf ("default allocator: 0x%p\n", (void*)ozz::memory::default_allocator()); + SkinnedMesh gSkinnedMesh; + gSkinnedMesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz"); + gSkinnedMesh.LoadAnimation("../media/Idle-loop.ozz"); + gSkinnedMesh.LoadAnimation("../media/Walking-loop.ozz"); + gSkinnedMesh.LoadAnimation("../media/RunningSlow-loop.ozz"); + gSkinnedMesh.LoadAnimation("../media/RunningFast-loop.ozz"); + gSkinnedMesh.SetCurrentAnimation(0); + +// state.ozz = std::make_unique(); state.time.factor = 1.0f; Camera_Init(&state.camera); @@ -303,9 +309,6 @@ int main() { pass_action.colors[0].action = SG_ACTION_CLEAR; pass_action.colors[0].value = {0.1f, 0.1f, 0.1f, 1.0f}; - load_skeleton(); - load_animation(); - // draw loop while (!glfwWindowShouldClose(w)) { state.time.frame = stm_sec( @@ -395,8 +398,18 @@ int main() { ImGui::EndMainMenuBar(); } - frame(); - draw_ui(); + draw_grid(); + gSkinnedMesh.DrawUi(); + + state.time.absolute += state.time.frame * state.time.factor; + gSkinnedMesh.EvalAnimation(state.time.absolute); + + sgl_defaults(); + sgl_matrix_mode_projection(); + sgl_load_matrix((const float*)&state.camera.mtxProj); + sgl_matrix_mode_modelview(); + sgl_load_matrix((const float*)&state.camera.mtxView); + gSkinnedMesh.DrawSkeleton(); // 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow() if (show_imgui_demo_window) { @@ -416,7 +429,7 @@ int main() { glfwPollEvents(); } - state.ozz = nullptr; +// state.ozz = nullptr; /* cleanup */ ImGui::DestroyContext(); @@ -505,64 +518,6 @@ static void load_animation(void) { std::cout << "Successfully loade " << anim_file << std::endl; } -static void draw_ui() { - // 1. Show a simple window - // Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets appears in a window automatically called "Debug" - ImGui::Begin("Animation"); - - ImGui::Checkbox("Time override", &state.time.anim_ratio_ui_override); - - 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); - - ImGui::Text( - "Application average %.3f ms/frame (%.1f FPS)", - 1000.0f / ImGui::GetIO().Framerate, - ImGui::GetIO().Framerate); - - ImGui::End(); -} - -static void frame() { - draw_grid(); - - if (state.loaded.animation && state.loaded.skeleton) { - if (!state.time.paused) { - state.time.absolute += state.time.frame * state.time.factor; - } - eval_animation(); - draw_skeleton(); - } -} - -static void eval_animation() { - // convert current time to animation ration (0.0 .. 1.0) - 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); - } - - // sample animation - ozz::animation::SamplingJob sampling_job; - sampling_job.animation = &state.ozz->animation; - sampling_job.cache = &state.ozz->cache; - sampling_job.ratio = state.time.anim_ratio; - sampling_job.output = make_span(state.ozz->local_matrices); - sampling_job.Run(); - - // convert joint matrices from local to model space - ozz::animation::LocalToModelJob ltm_job; - ltm_job.skeleton = &state.ozz->skeleton; - ltm_job.input = make_span(state.ozz->local_matrices); - ltm_job.output = make_span(state.ozz->model_matrices); - ltm_job.Run(); -} - static void draw_vec(const ozz::math::SimdFloat4& vec) { sgl_v3f(ozz::math::GetX(vec), ozz::math::GetY(vec), ozz::math::GetZ(vec)); } @@ -574,44 +529,6 @@ static void draw_line( draw_vec(v1); } -// this draws a wireframe 3d rhombus between the current and parent joints -static void draw_joint(int joint_index, int parent_joint_index) { - if (parent_joint_index < 0) { - return; - } - - using namespace ozz::math; - - const Float4x4& m0 = state.ozz->model_matrices[joint_index]; - const Float4x4& m1 = state.ozz->model_matrices[parent_joint_index]; - - const SimdFloat4 p0 = m0.cols[3]; - const SimdFloat4 p1 = m1.cols[3]; - const SimdFloat4 ny = m1.cols[1]; - const SimdFloat4 nz = m1.cols[2]; - - const SimdFloat4 len = SplatX(Length3(p1 - p0)) * simd_float4::Load1(0.1f); - - const SimdFloat4 pmid = p0 + (p1 - p0) * simd_float4::Load1(0.66f); - const SimdFloat4 p2 = pmid + ny * len; - const SimdFloat4 p3 = pmid + nz * len; - const SimdFloat4 p4 = pmid - ny * len; - const SimdFloat4 p5 = pmid - nz * len; - - sgl_c3f(1.0f, 1.0f, 0.0f); - draw_line(p0, p2); - draw_line(p0, p3); - draw_line(p0, p4); - draw_line(p0, p5); - draw_line(p1, p2); - draw_line(p1, p3); - draw_line(p1, p4); - draw_line(p1, p5); - draw_line(p2, p3); - draw_line(p3, p4); - draw_line(p4, p5); - draw_line(p5, p2); -} static void draw_grid(void) { sgl_defaults(); @@ -649,23 +566,6 @@ static void draw_grid(void) { sgl_end(); } -static void draw_skeleton(void) { - sgl_defaults(); - sgl_matrix_mode_projection(); - sgl_load_matrix((const float*)&state.camera.mtxProj); - sgl_matrix_mode_modelview(); - hmm_mat4 scale_mat = HMM_Scale(HMM_Vec3(0.01f, 0.01f, 0.01f)); - sgl_load_matrix((const float*)&state.camera.mtxView); - sgl_mult_matrix((const float*)&scale_mat); - - const int num_joints = state.ozz->skeleton.num_joints(); - ozz::span joint_parents = state.ozz->skeleton.joint_parents(); - sgl_begin_lines(); - for (int joint_index = 0; joint_index < num_joints; joint_index++) { - draw_joint(joint_index, joint_parents[joint_index]); - } - sgl_end(); -} // draw ImGui draw lists via sokol-gfx void draw_imgui(ImDrawData* draw_data) {