Compare commits

...

3 Commits

Author SHA1 Message Date
Martin Felis
fbac21cf14 Simple sync track blending tests. 2021-11-16 18:23:04 +01:00
Martin Felis
3e1c150345 Minor restructuring for tests, initial work for sync tracks. 2021-11-16 18:15:56 +01:00
Martin Felis
038f07de0a Added limping and zombie walk animations. 2021-11-16 17:31:18 +01:00
19 changed files with 18191 additions and 79 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
.*.swp
*.fbx
*blend1
*CMakeFiles/**
/.idea
/scratch*

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.13)
cmake_policy(SET CMP0077 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
project(AnimTestbed
VERSION 0.0.1
@ -28,21 +28,19 @@ set(ozz_build_simd_ref OFF CACHE BOOL "")
set(ozz_build_msvc_rt_dll OFF CACHE BOOL "")
add_subdirectory(3rdparty/ozz-animation)
# Simulator Executable
add_executable(AnimTestbed)
target_include_directories(
AnimTestbed
set (ThirdPartyIncludeDeps
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/glfw/deps>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/glfw/deps>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/Handmade-Math>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/imgui>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/ozz-animation/include>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/sokol>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/vectorial/include>
)
target_link_libraries(AnimTestbed glfw ozz_base ozz_geometry ozz_animation ${OPENGL_LIBRARIES})
)
target_sources(AnimTestbed PRIVATE
src/main.cc
# Shared code by main executable and tests
add_library(AnimTestbedCode OBJECT
src/Camera.c
src/SkinnedMesh.cc
src/SkinnedMesh.h
@ -51,11 +49,42 @@ target_sources(AnimTestbed PRIVATE
src/AnimNodes/SpeedScaleNode.cc
src/AnimNodes/BlendNode.cc
src/AnimationController.cc
3rdparty/glfw/deps/glad_gl.c
3rdparty/imgui/imgui.cpp
3rdparty/imgui/imgui_draw.cpp
3rdparty/imgui/imgui_widgets.cpp
)
target_include_directories(
AnimTestbedCode
${ThirdPartyIncludeDeps}
)
# AnimTestbed Executable
add_executable(AnimTestbed)
target_include_directories(
AnimTestbed
${ThirdPartyIncludeDeps}
)
target_sources(AnimTestbed PRIVATE
src/main.cc
src/SkinnedMeshRenderer.cc
3rdparty/glfw/deps/glad_gl.c
3rdparty/imgui/imgui_demo.cpp
3rdparty/imgui/backends/imgui_impl_glfw.cpp
3rdparty/imgui/backends/imgui_impl_opengl3.cpp
)
target_link_libraries(AnimTestbed AnimTestbedCode glfw ozz_base ozz_geometry ozz_animation ${OPENGL_LIBRARIES})
# Tests
add_executable(runtests)
target_sources(runtests PRIVATE tests/main.cc tests/SyncTrackTests.cc)
target_include_directories(
runtests
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
${ThirdPartyIncludeDeps}
)
target_link_libraries(runtests AnimTestbedCode glfw ozz_base ozz_geometry ozz_animation)

BIN
media/Limping-loop.ozz Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
media/ZombieWalk-loop.ozz Normal file

Binary file not shown.

Binary file not shown.

View File

@ -8,6 +8,7 @@
#include <string>
#include "AnimationController.h"
#include "SkinnedMesh.h"
enum class AnimNodeType { Blend, SpeedScale, AnimSampler };
@ -27,6 +28,7 @@ struct AnimNode {
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; }

View File

@ -46,7 +46,7 @@ AnimationController::AnimationController(SkinnedMesh* skinned_mesh)
blend_node->m_name = "Blend0";
blend_node->m_input_A = speed_node;
blend_node->m_input_B = sampler_node1;
blend_node->m_sync_inputs = true;
blend_node->m_sync_inputs = false;
m_anim_nodes.push_back(blend_node);
SpeedScaleNode* speed_node1 = new SpeedScaleNode(this);
@ -97,7 +97,11 @@ void AnimationController::UpdateOrderedNodes() {
}
}
void AnimationController::Update(float dt) {
void AnimationController::UpdateBlendLogic() {
}
void AnimationController::UpdateTime(float dt) {
if (m_output_node == nullptr) {
return;
}

View File

@ -23,8 +23,10 @@ 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 Update(float dt);
void UpdateTime(float dt);
// Recursively evaluates all nodes.
void Evaluate();

View File

@ -7,58 +7,6 @@
#include <HandmadeMath.h>
#include <imgui.h>
#include "sokol_gfx.h"
#include "util/sokol_gl.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) {
@ -164,19 +112,7 @@ void SkinnedMesh::EvalAnimation(float in_time) {
}
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<const int16_t> 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::DrawDebugUi() {

View File

@ -20,6 +20,40 @@
#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_interval_durations[cSyncTrackMaxIntervals];
float ConvertAbsTimeToSyncTime (float abs_time) {
float sync_time = fmodf (abs_time, m_duration) / m_duration;
int interval_index = 0;
while (sync_time < m_interval_durations[interval_index]) {
sync_time -= m_interval_durations[interval_index];
interval_index ++;
}
}
static SyncTrack Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) {
SyncTrack result;
assert (track_A.m_num_intervals == track_B.m_num_intervals);
result.m_num_intervals = track_A.m_num_intervals;
result.m_duration = (1.0f - weight) * track_A.m_duration + weight * track_B.m_duration;
for (int i = 0; i < result.m_num_intervals; i++) {
result.m_interval_durations[i] = (1.0f - weight) * track_A.m_interval_durations[i] + weight * track_B.m_interval_durations[i];
}
return result;
}
};
struct SkinnedMesh {
virtual ~SkinnedMesh();
bool LoadSkeleton(const char* filename);
@ -39,6 +73,7 @@ struct SkinnedMesh {
ozz::vector<ozz::animation::Animation*> m_animations;
std::vector<std::string> m_animation_names;
std::vector<SyncTrack> m_animation_sync_track;
ozz::animation::Skeleton m_skeleton;
ozz::animation::Animation* m_current_animation;
ozz::animation::SamplingCache m_cache;

View File

@ -0,0 +1,75 @@
//
// Created by martin on 16.11.21.
//
#include "SkinnedMeshRenderer.h"
#include "sokol_gfx.h"
#include "util/sokol_gl.h"
#include "HandmadeMath.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 draw_joint (const SkinnedMesh& skinned_mesh, int joint_index, int parent_joint_index) {
if (parent_joint_index < 0) {
return;
}
using namespace ozz::math;
const Float4x4& m0 = skinned_mesh.m_model_matrices[joint_index];
const Float4x4& m1 = skinned_mesh.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);
}
void RenderSkinnedMesh (const SkinnedMesh& skinned_mesh) {
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 = skinned_mesh.m_skeleton.num_joints();
ozz::span<const int16_t> joint_parents = skinned_mesh.m_skeleton.joint_parents();
sgl_begin_lines();
for (int joint_index = 0; joint_index < num_joints; joint_index++) {
draw_joint(skinned_mesh, joint_index, joint_parents[joint_index]);
}
sgl_end();
sgl_pop_matrix();
}

12
src/SkinnedMeshRenderer.h Normal file
View File

@ -0,0 +1,12 @@
//
// Created by martin on 16.11.21.
//
#ifndef ANIMTESTBED_SKINNEDMESHRENDERER_H
#define ANIMTESTBED_SKINNEDMESHRENDERER_H
#include "SkinnedMesh.h"
void RenderSkinnedMesh (const SkinnedMesh& skinned_mesh);
#endif //ANIMTESTBED_SKINNEDMESHRENDERER_H

View File

@ -47,6 +47,7 @@ static void draw_imgui(ImDrawData*);
#include <memory> // std::unique_ptr, std::make_unique
#include "AnimationController.h"
#include "SkinnedMeshRenderer.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/local_to_model_job.h"
#include "ozz/animation/runtime/sampling_job.h"
@ -201,6 +202,8 @@ int main() {
skinned_mesh.LoadAnimation("../media/Walking-loop.ozz");
skinned_mesh.LoadAnimation("../media/RunningSlow-loop.ozz");
skinned_mesh.LoadAnimation("../media/RunningFast-loop.ozz");
skinned_mesh.LoadAnimation("../media/Limping-loop.ozz");
skinned_mesh.LoadAnimation("../media/ZombieWalk-loop.ozz");
skinned_mesh.SetCurrentAnimation(0);
AnimationController animation_controller (&skinned_mesh);
@ -408,7 +411,7 @@ int main() {
skinned_mesh.DrawDebugUi();
animation_controller.DrawDebugUi();
animation_controller.Update(state.time.frame);
animation_controller.UpdateTime(state.time.frame);
animation_controller.Evaluate();
sgl_defaults();
@ -416,7 +419,7 @@ int main() {
sgl_load_matrix((const float*)&state.camera.mtxProj);
sgl_matrix_mode_modelview();
sgl_load_matrix((const float*)&state.camera.mtxView);
skinned_mesh.DrawSkeleton();
RenderSkinnedMesh(skinned_mesh);
// 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow()
if (show_imgui_demo_window) {

45
tests/SyncTrackTests.cc Normal file
View File

@ -0,0 +1,45 @@
//
// Created by martin on 16.11.21.
//
#include "catch.hpp"
#include "SkinnedMesh.h"
TEST_CASE("SyncTrackBlendSimple", "[SyncTrackBlend]") {
SyncTrack track_A;
track_A.m_num_intervals = 2;
track_A.m_duration = 1.0;
track_A.m_interval_durations[0] = 0.8;
track_A.m_interval_durations[1] = 0.2;
SyncTrack track_B;
track_B.m_num_intervals = 2;
track_B.m_duration = 2.0;
track_B.m_interval_durations[0] = 0.1;
track_B.m_interval_durations[1] = 0.9;
WHEN("Blending two synctracks with weight 0.") {
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);
THEN ("Result must equal track_A") {
REQUIRE(blended.m_duration == track_A.m_duration);
REQUIRE(
blended.m_interval_durations[0] == track_A.m_interval_durations[0]);
REQUIRE(
blended.m_interval_durations[1] == track_A.m_interval_durations[1]);
}
}
WHEN("Blending two synctracks with weight 1.") {
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);
THEN ("Result must equal track_B") {
REQUIRE(blended.m_duration == track_B.m_duration);
REQUIRE(
blended.m_interval_durations[0] == track_B.m_interval_durations[0]);
REQUIRE(
blended.m_interval_durations[1] == track_B.m_interval_durations[1]);
}
}
}

17959
tests/catch.hpp Normal file

File diff suppressed because it is too large Load Diff

8
tests/main.cc Normal file
View File

@ -0,0 +1,8 @@
#define CATCH_CONFIG_RUNNER // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"
int main (int argc, char* argv[]) {
int result = Catch::Session().run(argc, argv);
return result;
}