Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd4785afcd | ||
|
|
b8d863c8e1 | ||
|
|
1e1d36c5b1 | ||
|
|
6aabe98931 | ||
|
|
d4f9459060 | ||
|
|
89108f4e1f |
+6
-1
@@ -49,9 +49,11 @@ add_library(AnimTestbedCode OBJECT
|
||||
src/AnimNode.cc
|
||||
src/AnimNodes/AnimSamplerNode.cc
|
||||
src/AnimNodes/SpeedScaleNode.cc
|
||||
src/AnimNodes/BlendSpace1D.cc
|
||||
src/AnimNodes/BlendNode.cc
|
||||
src/AnimNodes/LockTranslationNode.cc
|
||||
src/AnimationController.cc
|
||||
src/ozzutils.cc
|
||||
3rdparty/imgui/imgui.cpp
|
||||
3rdparty/imgui/imgui_draw.cpp
|
||||
3rdparty/imgui/imgui_widgets.cpp
|
||||
@@ -59,6 +61,8 @@ add_library(AnimTestbedCode OBJECT
|
||||
|
||||
target_include_directories(
|
||||
AnimTestbedCode
|
||||
PUBLIC
|
||||
src
|
||||
${ThirdPartyIncludeDeps}
|
||||
)
|
||||
|
||||
@@ -66,6 +70,7 @@ target_include_directories(
|
||||
add_executable(AnimTestbed)
|
||||
target_include_directories(
|
||||
AnimTestbed
|
||||
PUBLIC
|
||||
${ThirdPartyIncludeDeps}
|
||||
)
|
||||
|
||||
@@ -83,7 +88,7 @@ target_link_libraries(AnimTestbed AnimTestbedCode glfw ozz_base ozz_geometry ozz
|
||||
# Tests
|
||||
add_executable(runtests)
|
||||
|
||||
target_sources(runtests PRIVATE tests/main.cc tests/SyncTrackTests.cc)
|
||||
target_sources(runtests PRIVATE tests/main.cc tests/AnimSampleNodeTests.cc tests/SyncTrackTests.cc)
|
||||
target_include_directories(
|
||||
runtests
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
|
||||
|
||||
+7
-4
@@ -5,6 +5,8 @@
|
||||
#ifndef ANIMTESTBED_ANIMNODE_H
|
||||
#define ANIMTESTBED_ANIMNODE_H
|
||||
|
||||
#include <ozz/base/maths/transform.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "AnimationController.h"
|
||||
@@ -15,7 +17,7 @@ enum class AnimNodeType { Blend, SpeedScale, AnimSampler };
|
||||
struct AnimNode {
|
||||
AnimNode(AnimationController* animation_controller)
|
||||
: m_animation_controller(animation_controller),
|
||||
m_current_time(0.f),
|
||||
m_time_current(0.f),
|
||||
m_is_time_synced(false) {}
|
||||
virtual ~AnimNode(){};
|
||||
|
||||
@@ -25,10 +27,10 @@ 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_time_current;
|
||||
SyncTrack m_sync_track;
|
||||
|
||||
virtual void Reset() { m_current_time = 0.f; }
|
||||
virtual void Reset() { m_time_current = 0.f; }
|
||||
|
||||
// Mark current node according to is_synced and propagate flag to animation inputs.
|
||||
virtual void UpdateIsSynced(bool is_synced) = 0;
|
||||
@@ -42,7 +44,8 @@ struct AnimNode {
|
||||
|
||||
// Evaluate the current node and write output to local_matrices.
|
||||
virtual void Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices) = 0;
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) = 0;
|
||||
|
||||
// Returns a list of animation nodes that provide input for this node.
|
||||
virtual void GetInputNodes(std::vector<AnimNode*>& input_nodes) const = 0 ;
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
|
||||
#include "AnimSamplerNode.h"
|
||||
|
||||
#include "ozzutils.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../SkinnedMesh.h"
|
||||
#include "../ozzutils.h"
|
||||
|
||||
void AnimSamplerNode::SetAnimation(ozz::animation::Animation* animation, const SyncTrack& sync_track) {
|
||||
m_animation = animation;
|
||||
@@ -21,7 +24,8 @@ void AnimSamplerNode::SetAnimation(ozz::animation::Animation* animation, const S
|
||||
}
|
||||
|
||||
void AnimSamplerNode::Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices) {
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) {
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = m_animation;
|
||||
sampling_job.cache = &m_sampling_cache;
|
||||
@@ -30,6 +34,18 @@ void AnimSamplerNode::Evaluate(
|
||||
if (!sampling_job.Run()) {
|
||||
ozz::log::Err() << "Error sampling animation." << std::endl;
|
||||
}
|
||||
|
||||
if (root_transform == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ozz::math::Transform root_trans_prev;
|
||||
calc_bone_translation (m_anim_ratio_prev, 0, m_animation, root_trans_prev);
|
||||
|
||||
ozz::math::Transform root_trans_cur;
|
||||
calc_bone_translation (m_anim_ratio, 0, m_animation, root_trans_cur);
|
||||
|
||||
root_transform->translation = root_trans_cur.translation - root_trans_prev.translation;
|
||||
}
|
||||
|
||||
void AnimSamplerNode::DrawDebugUi() {
|
||||
|
||||
@@ -10,37 +10,60 @@
|
||||
struct AnimSamplerNode : public AnimNode {
|
||||
AnimSamplerNode(AnimationController* animation_controller)
|
||||
: AnimNode(animation_controller),
|
||||
m_time_prev(0.f),
|
||||
m_override_ratio(false),
|
||||
m_anim_ratio(0.f) {
|
||||
assert(m_current_time < 100.0f);
|
||||
m_anim_ratio(0.f),
|
||||
m_root_bone_index(0) {
|
||||
assert(m_time_current < 100.0f);
|
||||
m_anim_node_type = AnimNodeType::AnimSampler;
|
||||
};
|
||||
virtual ~AnimSamplerNode() {}
|
||||
|
||||
ozz::animation::Animation* m_animation;
|
||||
|
||||
float m_time_prev;
|
||||
bool m_override_ratio;
|
||||
float m_anim_ratio;
|
||||
float m_anim_ratio_prev;
|
||||
bool m_root_bone_index;
|
||||
|
||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices;
|
||||
ozz::vector<ozz::math::SoaTransform> m_root_output;
|
||||
ozz::animation::SamplingCache m_sampling_cache;
|
||||
|
||||
void SetAnimation(ozz::animation::Animation* animation, const SyncTrack& sync_track);
|
||||
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 UpdateSyncTrack() override {
|
||||
}
|
||||
virtual void UpdateSyncTrack() override {}
|
||||
|
||||
virtual void UpdateTime(float dt) override {
|
||||
m_current_time += dt;
|
||||
if (!m_override_ratio) {
|
||||
if (m_override_ratio) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_time_prev = m_time_current;
|
||||
m_time_current += dt;
|
||||
|
||||
if (m_is_time_synced) {
|
||||
m_anim_ratio = m_sync_track.CalcRatioFromSyncTime(m_current_time);
|
||||
m_anim_ratio = m_sync_track.CalcRatioFromSyncTime(m_time_current);
|
||||
float prev_sync_time = m_time_current - dt;
|
||||
if (m_time_current < 0) {
|
||||
prev_sync_time += m_sync_track.m_num_intervals;
|
||||
}
|
||||
m_anim_ratio_prev = m_sync_track.CalcRatioFromSyncTime(prev_sync_time);
|
||||
} else {
|
||||
m_anim_ratio = fmodf((float)m_current_time / m_animation->duration(), 1.0f);
|
||||
m_anim_ratio =
|
||||
fmodf((float)m_time_current / m_animation->duration(), 1.0f);
|
||||
m_anim_ratio_prev =
|
||||
fmodf((float)m_time_prev / m_animation->duration(), 1.0f);
|
||||
|
||||
if (m_anim_ratio_prev < 0) {
|
||||
m_anim_ratio_prev += 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +73,11 @@ struct AnimSamplerNode : public AnimNode {
|
||||
}
|
||||
|
||||
virtual void Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices) override;
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) override;
|
||||
|
||||
virtual void GetInputNodes(std::vector<AnimNode*>& input_nodes) const override{};
|
||||
virtual void GetInputNodes(
|
||||
std::vector<AnimNode*>& input_nodes) const override{};
|
||||
|
||||
virtual void DrawDebugUi();
|
||||
};
|
||||
|
||||
@@ -23,11 +23,17 @@ BlendNode::BlendNode(AnimationController* animation_controller)
|
||||
m_local_matrices_B.resize(num_soa_joints);
|
||||
}
|
||||
|
||||
void BlendNode::Evaluate(ozz::vector<ozz::math::SoaTransform>* local_matrices) {
|
||||
void BlendNode::Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) {
|
||||
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
|
||||
|
||||
m_input_A->Evaluate(&m_local_matrices_A);
|
||||
m_input_B->Evaluate(&m_local_matrices_B);
|
||||
m_input_A->Evaluate(
|
||||
&m_local_matrices_A,
|
||||
root_transform != nullptr ? &m_root_transform_A : nullptr);
|
||||
m_input_B->Evaluate(
|
||||
&m_local_matrices_B,
|
||||
root_transform != nullptr ? &m_root_transform_B : nullptr);
|
||||
|
||||
// perform blend
|
||||
ozz::animation::BlendingJob::Layer layers[2];
|
||||
@@ -49,9 +55,7 @@ void BlendNode::Evaluate(ozz::vector<ozz::math::SoaTransform>* local_matrices) {
|
||||
}
|
||||
|
||||
void BlendNode::DrawDebugUi() {
|
||||
if (ImGui::SliderFloat("Weight", &m_weight, 0.f, 1.f)) {
|
||||
m_sync_track = SyncTrack::Blend(m_weight, m_input_A->m_sync_track, m_input_B->m_sync_track);
|
||||
}
|
||||
ImGui::SliderFloat("Weight", &m_weight, 0.f, 1.f);
|
||||
ImGui::Checkbox("Sync Inputs", &m_sync_inputs);
|
||||
|
||||
ImGui::Text("SyncTrack");
|
||||
|
||||
+20
-11
@@ -19,10 +19,10 @@ struct BlendNode : public AnimNode {
|
||||
|
||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices_A;
|
||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices_B;
|
||||
ozz::math::Transform m_root_transform_A;
|
||||
ozz::math::Transform m_root_transform_B;
|
||||
|
||||
virtual void Reset() {
|
||||
m_current_time = 0.f;
|
||||
}
|
||||
virtual void Reset() { m_time_current = 0.f; }
|
||||
|
||||
virtual void UpdateIsSynced(bool is_synced) override {
|
||||
m_is_time_synced = is_synced;
|
||||
@@ -37,7 +37,10 @@ struct BlendNode : public AnimNode {
|
||||
|
||||
virtual void UpdateSyncTrack() override {
|
||||
if (m_is_time_synced) {
|
||||
m_sync_track = SyncTrack::Blend(m_weight, m_input_A->m_sync_track, m_input_B->m_sync_track);
|
||||
m_sync_track = SyncTrack::Blend(
|
||||
m_weight,
|
||||
m_input_A->m_sync_track,
|
||||
m_input_B->m_sync_track);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@@ -45,22 +48,28 @@ struct BlendNode : public AnimNode {
|
||||
|
||||
virtual void UpdateTime(float dt) {
|
||||
if (m_is_time_synced) {
|
||||
m_current_time = fmodf(m_current_time + dt, m_sync_track.m_duration);
|
||||
float current_sync_time = m_sync_track.CalcSyncFromAbsTime(m_current_time);
|
||||
float sync_time_old = m_sync_track.CalcSyncFromAbsTime(m_time_current);
|
||||
m_time_current = fmodf(m_time_current + dt, m_sync_track.m_duration);
|
||||
float sync_time_dt =
|
||||
m_sync_track.CalcSyncFromAbsTime(m_time_current) - sync_time_old;
|
||||
|
||||
m_input_A->UpdateTime(current_sync_time - m_input_A->m_current_time);
|
||||
m_input_B->UpdateTime(current_sync_time - m_input_B->m_current_time);
|
||||
m_input_A->m_time_current = sync_time_old;
|
||||
m_input_B->m_time_current = sync_time_old;
|
||||
m_input_A->UpdateTime(sync_time_dt);
|
||||
m_input_B->UpdateTime(sync_time_dt);
|
||||
} else {
|
||||
m_current_time += dt;
|
||||
m_time_current += dt;
|
||||
m_input_A->UpdateTime(dt);
|
||||
m_input_B->UpdateTime(dt);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices) override;
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) override;
|
||||
|
||||
virtual void GetInputNodes(std::vector<AnimNode*>& input_nodes) const override {
|
||||
virtual void GetInputNodes(
|
||||
std::vector<AnimNode*>& input_nodes) const override {
|
||||
input_nodes.push_back(m_input_A);
|
||||
input_nodes.push_back(m_input_B);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Created by martin on 19.11.21.
|
||||
//
|
||||
|
||||
#include "BlendSpace1D.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <ozz/animation/runtime/blending_job.h>
|
||||
|
||||
#include "../SkinnedMesh.h"
|
||||
|
||||
BlendSpace1D::BlendSpace1D(AnimationController* animation_controller)
|
||||
: AnimNode(animation_controller),
|
||||
m_input_0(nullptr),
|
||||
m_weight_0(0.f),
|
||||
m_input_1(nullptr),
|
||||
m_weight_1(0.f),
|
||||
m_weight(0.f),
|
||||
m_sync_inputs(false) {
|
||||
m_anim_node_type = AnimNodeType::Blend;
|
||||
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
|
||||
const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
|
||||
const int num_joints = skinned_mesh->m_skeleton.num_joints();
|
||||
m_local_matrices_0.resize(num_soa_joints);
|
||||
m_local_matrices_1.resize(num_soa_joints);
|
||||
}
|
||||
|
||||
void BlendSpace1D::Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) {
|
||||
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
|
||||
|
||||
m_input_0->Evaluate(
|
||||
&m_local_matrices_0,
|
||||
root_transform != nullptr ? &m_root_transform_0 : nullptr);
|
||||
m_input_1->Evaluate(
|
||||
&m_local_matrices_1,
|
||||
root_transform != nullptr ? &m_root_transform_1 : nullptr);
|
||||
|
||||
// perform blend
|
||||
ozz::animation::BlendingJob::Layer layers[2];
|
||||
layers[0].transform = make_span(m_local_matrices_0);
|
||||
layers[0].weight = (1.0f - m_normalized_weight);
|
||||
|
||||
layers[1].transform = make_span(m_local_matrices_1);
|
||||
layers[1].weight = (m_normalized_weight);
|
||||
|
||||
ozz::animation::BlendingJob blend_job;
|
||||
blend_job.threshold = ozz::animation::BlendingJob().threshold;
|
||||
blend_job.layers = layers;
|
||||
blend_job.bind_pose = skinned_mesh->m_skeleton.joint_bind_poses();
|
||||
blend_job.output = make_span(*local_matrices);
|
||||
|
||||
if (!blend_job.Run()) {
|
||||
ozz::log::Err() << "Error blending animations." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void BlendSpace1D::DrawDebugUi() {
|
||||
float min_weight = m_input_weights[0];
|
||||
float max_weight = m_input_weights.back();
|
||||
ImGui::SliderFloat("Weight", &m_weight, min_weight, max_weight);
|
||||
ImGui::Checkbox("Sync Inputs", &m_sync_inputs);
|
||||
|
||||
ImGui::Text("SyncTrack");
|
||||
m_sync_track.DrawDebugUi();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// Created by martin on 19.11.21.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_BLENDSPACE1D_H
|
||||
#define ANIMTESTBED_BLENDSPACE1D_H
|
||||
|
||||
#include "../AnimNode.h"
|
||||
|
||||
struct BlendSpace1D : public AnimNode {
|
||||
BlendSpace1D(AnimationController* animation_controller);
|
||||
|
||||
virtual ~BlendSpace1D() {}
|
||||
|
||||
int m_num_inputs;
|
||||
std::vector<float> m_input_weights;
|
||||
std::vector<AnimNode*> m_inputs;
|
||||
float m_weight;
|
||||
bool m_sync_inputs;
|
||||
|
||||
AnimNode* m_input_0;
|
||||
AnimNode* m_input_1;
|
||||
|
||||
float m_normalized_weight;
|
||||
float m_weight_0;
|
||||
float m_weight_1;
|
||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices_0;
|
||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices_1;
|
||||
ozz::math::Transform m_root_transform_0;
|
||||
ozz::math::Transform m_root_transform_1;
|
||||
|
||||
virtual void Reset() { m_time_current = 0.f; }
|
||||
|
||||
virtual void UpdateIsSynced(bool is_synced) override {
|
||||
m_is_time_synced = is_synced;
|
||||
|
||||
if (m_sync_inputs) {
|
||||
m_is_time_synced = true;
|
||||
}
|
||||
|
||||
assert(m_input_weights.size() > 0);
|
||||
assert(
|
||||
m_weight >= m_input_weights[0]
|
||||
&& m_weight <= m_input_weights[m_input_weights.size() - 1]);
|
||||
|
||||
int prev_idx = 0;
|
||||
for (int next_idx = 1; next_idx < m_input_weights.size(); next_idx++) {
|
||||
if (m_input_weights[prev_idx] <= m_weight
|
||||
&& m_input_weights[next_idx] >= m_weight) {
|
||||
m_input_0 = m_inputs[prev_idx];
|
||||
m_weight_0 = m_input_weights[prev_idx];
|
||||
|
||||
m_input_1 = m_inputs[next_idx];
|
||||
m_weight_1 = m_input_weights[next_idx];
|
||||
|
||||
break;
|
||||
}
|
||||
prev_idx = next_idx;
|
||||
}
|
||||
|
||||
m_input_0->UpdateIsSynced(m_is_time_synced);
|
||||
m_input_1->UpdateIsSynced(m_is_time_synced);
|
||||
|
||||
m_normalized_weight = (m_weight - m_weight_0) / (m_weight_1 - m_weight_0);
|
||||
}
|
||||
|
||||
virtual void UpdateSyncTrack() override {
|
||||
if (m_is_time_synced) {
|
||||
m_sync_track = SyncTrack::Blend(
|
||||
m_normalized_weight,
|
||||
m_input_0->m_sync_track,
|
||||
m_input_1->m_sync_track);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void UpdateTime(float dt) {
|
||||
if (m_is_time_synced) {
|
||||
float sync_time_old = m_sync_track.CalcSyncFromAbsTime(m_time_current);
|
||||
m_time_current = fmodf(m_time_current + dt, m_sync_track.m_duration);
|
||||
float sync_time_dt =
|
||||
m_sync_track.CalcSyncFromAbsTime(m_time_current) - sync_time_old;
|
||||
|
||||
m_input_0->m_time_current = sync_time_old;
|
||||
m_input_1->m_time_current = sync_time_old;
|
||||
m_input_0->UpdateTime(sync_time_dt);
|
||||
m_input_1->UpdateTime(sync_time_dt);
|
||||
} else {
|
||||
m_time_current += dt;
|
||||
m_input_0->UpdateTime(dt);
|
||||
m_input_1->UpdateTime(dt);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) override;
|
||||
|
||||
virtual void GetInputNodes(
|
||||
std::vector<AnimNode*>& input_nodes) const override {
|
||||
for (int i = 0; i < m_inputs.size(); i++) {
|
||||
input_nodes.push_back(m_inputs[i]);
|
||||
}
|
||||
};
|
||||
|
||||
virtual void DrawDebugUi();
|
||||
};
|
||||
|
||||
#endif //ANIMTESTBED_BLENDSPACE1D_H
|
||||
@@ -5,12 +5,15 @@
|
||||
#include "LockTranslationNode.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
|
||||
void LockTranslationNode::Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices) {
|
||||
m_input->Evaluate(local_matrices);
|
||||
ozz::math::SoaFloat3 translation = (*local_matrices)[m_locked_bone_index].translation;
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) {
|
||||
m_input->Evaluate(local_matrices, root_transform);
|
||||
ozz::math::SoaFloat3 translation =
|
||||
(*local_matrices)[m_locked_bone_index].translation;
|
||||
float x[4];
|
||||
float y[4];
|
||||
float z[4];
|
||||
@@ -41,7 +44,8 @@ void LockTranslationNode::Evaluate(
|
||||
}
|
||||
|
||||
void LockTranslationNode::DrawDebugUi() {
|
||||
const ozz::animation::Skeleton& skeleton = m_animation_controller->m_skinned_mesh->m_skeleton;
|
||||
const ozz::animation::Skeleton& skeleton =
|
||||
m_animation_controller->m_skinned_mesh->m_skeleton;
|
||||
ozz::span<const char* const> joint_names = skeleton.joint_names();
|
||||
|
||||
const char* items[255] = {0};
|
||||
@@ -54,6 +58,4 @@ void LockTranslationNode::DrawDebugUi() {
|
||||
ImGui::Checkbox("Lock X", &m_lock_x);
|
||||
ImGui::Checkbox("Lock Y", &m_lock_y);
|
||||
ImGui::Checkbox("Lock Z", &m_lock_z);
|
||||
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@ struct LockTranslationNode : public AnimNode {
|
||||
bool m_lock_y;
|
||||
bool m_lock_z;
|
||||
|
||||
virtual void Reset() { m_current_time = 0.f; }
|
||||
virtual void Reset() { m_time_current = 0.f; }
|
||||
|
||||
virtual void UpdateIsSynced(bool is_synced) override {
|
||||
m_is_time_synced = is_synced;
|
||||
@@ -39,7 +39,8 @@ struct LockTranslationNode : public AnimNode {
|
||||
virtual void UpdateTime(float dt) { m_input->UpdateTime(dt); }
|
||||
|
||||
virtual void Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices) override;
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform = nullptr) override;
|
||||
|
||||
virtual void GetInputNodes(
|
||||
std::vector<AnimNode*>& input_nodes) const override {
|
||||
|
||||
@@ -16,7 +16,7 @@ struct SpeedScaleNode : public AnimNode {
|
||||
float m_time_scale;
|
||||
AnimNode* m_input_node;
|
||||
|
||||
virtual void Reset() { m_current_time = 0.f; }
|
||||
virtual void Reset() { m_time_current = 0.f; }
|
||||
|
||||
virtual void UpdateIsSynced(bool is_synced) override {
|
||||
m_is_time_synced = is_synced;
|
||||
@@ -26,25 +26,28 @@ struct SpeedScaleNode : public AnimNode {
|
||||
virtual void UpdateSyncTrack() override {
|
||||
assert(fabs(m_time_scale) >= 0.01f);
|
||||
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);
|
||||
m_sync_track.m_duration =
|
||||
fabsf(m_input_node->m_sync_track.m_duration / m_time_scale);
|
||||
}
|
||||
|
||||
virtual void UpdateTime(float dt) {
|
||||
if (!m_is_time_synced) {
|
||||
m_current_time += dt * m_time_scale;
|
||||
m_time_current += dt * m_time_scale;
|
||||
m_input_node->UpdateTime(dt * m_time_scale);
|
||||
} else {
|
||||
m_current_time += dt;
|
||||
m_time_current += dt;
|
||||
m_input_node->UpdateTime(dt);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Evaluate(
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices) override {
|
||||
m_input_node->Evaluate(local_matrices);
|
||||
ozz::vector<ozz::math::SoaTransform>* local_matrices,
|
||||
ozz::math::Transform* root_transform) override {
|
||||
m_input_node->Evaluate(local_matrices, root_transform);
|
||||
};
|
||||
|
||||
virtual void GetInputNodes(std::vector<AnimNode*>& input_nodes) const override {
|
||||
virtual void GetInputNodes(
|
||||
std::vector<AnimNode*>& input_nodes) const override {
|
||||
input_nodes.push_back(m_input_node);
|
||||
};
|
||||
|
||||
|
||||
+41
-12
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "AnimNodes/AnimSamplerNode.h"
|
||||
#include "AnimNodes/BlendNode.h"
|
||||
#include "AnimNodes/BlendSpace1D.h"
|
||||
#include "AnimNodes/LockTranslationNode.h"
|
||||
#include "AnimNodes/SpeedScaleNode.h"
|
||||
#include "SkinnedMesh.h"
|
||||
@@ -27,29 +28,51 @@ AnimationController::AnimationController(SkinnedMesh* skinned_mesh)
|
||||
|
||||
AnimSamplerNode* sampler_node0 = new AnimSamplerNode(this);
|
||||
sampler_node0->m_name = "AnimSampler0";
|
||||
sampler_node0->SetAnimation(skinned_mesh->m_animations[1], skinned_mesh->m_animation_sync_track[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], skinned_mesh->m_animation_sync_track[2]);
|
||||
sampler_node1->SetAnimation(
|
||||
skinned_mesh->m_animations[2],
|
||||
skinned_mesh->m_animation_sync_track[2]);
|
||||
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);
|
||||
AnimSamplerNode* sampler_node2 = new AnimSamplerNode(this);
|
||||
sampler_node2->m_name = "AnimSampler2";
|
||||
sampler_node2->SetAnimation(
|
||||
skinned_mesh->m_animations[3],
|
||||
skinned_mesh->m_animation_sync_track[3]);
|
||||
m_anim_nodes.push_back(sampler_node2);
|
||||
|
||||
BlendSpace1D* blend_space = new BlendSpace1D(this);
|
||||
blend_space->m_name = "BlendSpace0";
|
||||
blend_space->m_weight = 0.f;
|
||||
blend_space->m_inputs.push_back(sampler_node0);
|
||||
blend_space->m_input_weights.push_back(-1.f);
|
||||
blend_space->m_inputs.push_back(sampler_node1);
|
||||
blend_space->m_input_weights.push_back(0.f);
|
||||
blend_space->m_inputs.push_back(sampler_node2);
|
||||
blend_space->m_input_weights.push_back(1.f);
|
||||
m_anim_nodes.push_back(blend_space);
|
||||
|
||||
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 = sampler_node0;
|
||||
blend_node->m_input_A = speed_node;
|
||||
blend_node->m_input_B = sampler_node1;
|
||||
blend_node->m_sync_inputs = true;
|
||||
m_anim_nodes.push_back(blend_node);
|
||||
|
||||
SpeedScaleNode* speed_node1 = new SpeedScaleNode(this);
|
||||
speed_node1->m_name = "SpeedNode1";
|
||||
speed_node1->m_input_node = blend_node;
|
||||
speed_node1->m_input_node = blend_space;
|
||||
m_anim_nodes.push_back(speed_node1);
|
||||
|
||||
LockTranslationNode* lock_node = new LockTranslationNode(this);
|
||||
@@ -100,13 +123,17 @@ void AnimationController::UpdateOrderedNodes() {
|
||||
}
|
||||
|
||||
void AnimationController::UpdateTime(float dt) {
|
||||
if (m_paused || m_output_node == nullptr) {
|
||||
if (m_output_node == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark all nodes that evaluate time using sync tracks.
|
||||
m_output_node->UpdateIsSynced(false);
|
||||
|
||||
if (m_paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For all synced nodes calculate their current sync track durations
|
||||
for (int i = m_ordered_nodes.size() - 1; i >= 0; i--) {
|
||||
AnimNode* node = m_ordered_nodes[i];
|
||||
@@ -124,7 +151,9 @@ void AnimationController::Evaluate() {
|
||||
return;
|
||||
}
|
||||
|
||||
m_output_node->Evaluate(&m_skinned_mesh->m_local_matrices);
|
||||
m_output_node->Evaluate(
|
||||
&m_skinned_mesh->m_local_matrices,
|
||||
m_calc_root_transform ? &m_root_transform : nullptr);
|
||||
};
|
||||
|
||||
void AnimationController::DrawDebugUi() {
|
||||
@@ -200,8 +229,8 @@ void AnimationController::DrawDebugUi() {
|
||||
ImGui::NextColumn();
|
||||
ImGui::PopID();
|
||||
|
||||
ImGui::PushID((void*)&node->m_current_time);
|
||||
ImGui::Text("%2.3f", node->m_current_time);
|
||||
ImGui::PushID((void*)&node->m_time_current);
|
||||
ImGui::Text("%2.3f", node->m_time_current);
|
||||
ImGui::NextColumn();
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <ozz/animation/runtime/sampling_job.h>
|
||||
#include <ozz/base/containers/vector.h>
|
||||
#include <ozz/base/maths/soa_transform.h>
|
||||
#include <ozz/base/maths/transform.h>
|
||||
#include <ozz/base/maths/vec_float.h>
|
||||
|
||||
struct SkinnedMesh;
|
||||
@@ -33,6 +34,8 @@ struct AnimationController {
|
||||
|
||||
float m_current_time;
|
||||
bool m_paused;
|
||||
bool m_calc_root_transform;
|
||||
ozz::math::Transform m_root_transform;
|
||||
|
||||
SkinnedMesh* m_skinned_mesh = nullptr;
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Created by martin on 10.12.21.
|
||||
//
|
||||
|
||||
#include "ozzutils.h"
|
||||
|
||||
#include <ozz/base/maths/simd_math.h>
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER
|
||||
#include "../src/animation/runtime/animation_keyframe.h"
|
||||
#undef OZZ_INCLUDE_PRIVATE_HEADER
|
||||
|
||||
void calc_bone_translation(
|
||||
float ratio,
|
||||
int i_bone_idx,
|
||||
const ozz::animation::Animation* i_animation,
|
||||
ozz::math::Transform& o_transform) {
|
||||
int key_idx = 1;
|
||||
const ozz::span<const ozz::animation::Float3Key>& translations =
|
||||
i_animation->translations();
|
||||
|
||||
int key_idx_prev = 0;
|
||||
int key_idx_next = 0;
|
||||
while (key_idx < translations.size()) {
|
||||
key_idx_next = key_idx;
|
||||
if (translations[key_idx].track == i_bone_idx) {
|
||||
if (translations[key_idx].ratio < ratio) {
|
||||
key_idx_prev = key_idx;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
key_idx++;
|
||||
}
|
||||
|
||||
const ozz::animation::Float3Key& key_prev = translations[key_idx_prev];
|
||||
const ozz::animation::Float3Key& key_next = translations[key_idx_next];
|
||||
|
||||
float r = 0.f;
|
||||
if (key_prev.ratio != key_next.ratio) {
|
||||
r = (ratio - key_prev.ratio) / (key_next.ratio - key_prev.ratio);
|
||||
}
|
||||
o_transform.translation.x =
|
||||
ozz::math::HalfToFloat(key_prev.value[0])
|
||||
+ r
|
||||
* (ozz::math::HalfToFloat(key_next.value[0])
|
||||
- ozz::math::HalfToFloat(key_prev.value[0]));
|
||||
o_transform.translation.y =
|
||||
ozz::math::HalfToFloat(key_prev.value[1])
|
||||
+ r
|
||||
* (ozz::math::HalfToFloat(key_next.value[1])
|
||||
- ozz::math::HalfToFloat(key_prev.value[1]));
|
||||
o_transform.translation.z =
|
||||
ozz::math::HalfToFloat(key_prev.value[2])
|
||||
+ r
|
||||
* (ozz::math::HalfToFloat(key_next.value[2])
|
||||
- ozz::math::HalfToFloat(key_prev.value[2]));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by martin on 10.12.21.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_OZZUTILS_H
|
||||
#define ANIMTESTBED_OZZUTILS_H
|
||||
|
||||
#include <ozz/base/maths/transform.h>
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
|
||||
void calc_bone_translation(
|
||||
float ratio,
|
||||
int i_bone_idx,
|
||||
const ozz::animation::Animation* i_animation,
|
||||
ozz::math::Transform& o_transform);
|
||||
|
||||
#endif //ANIMTESTBED_OZZUTILS_H
|
||||
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// Created by martin on 21.11.21.
|
||||
//
|
||||
|
||||
#include "AnimNodes/AnimSamplerNode.h"
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER
|
||||
#include "../src/animation/runtime/animation_keyframe.h"
|
||||
#include "catch.hpp"
|
||||
#include "ozzutils.h"
|
||||
|
||||
void get_bone_transform(
|
||||
int i_bone_idx,
|
||||
const ozz::vector<ozz::math::SoaTransform>& i_local_matrices,
|
||||
ozz::math::Transform& o_transform) {
|
||||
int matrix_index = i_bone_idx / 4;
|
||||
short simd_component = i_bone_idx % 4;
|
||||
o_transform.translation.x =
|
||||
i_local_matrices[matrix_index].translation.x[simd_component];
|
||||
o_transform.translation.y =
|
||||
i_local_matrices[matrix_index].translation.y[simd_component];
|
||||
o_transform.translation.z =
|
||||
i_local_matrices[matrix_index].translation.z[simd_component];
|
||||
}
|
||||
|
||||
void sample_bone_transform(
|
||||
float ratio,
|
||||
int i_bone_idx,
|
||||
const ozz::animation::Animation* i_animation,
|
||||
ozz::animation::SamplingCache& io_cache,
|
||||
ozz::vector<ozz::math::SoaTransform>& io_local_matrices,
|
||||
ozz::math::Transform& o_transform) {
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = i_animation;
|
||||
sampling_job.cache = &io_cache;
|
||||
sampling_job.ratio = ratio;
|
||||
sampling_job.output = make_span(io_local_matrices);
|
||||
|
||||
if (!sampling_job.Run()) {
|
||||
ozz::log::Err() << "Error sampling animation." << std::endl;
|
||||
}
|
||||
|
||||
get_bone_transform(i_bone_idx, io_local_matrices, o_transform);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Sample single bone channel", "[AnimSamplerNode]") {
|
||||
SkinnedMesh skinned_mesh;
|
||||
skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
|
||||
skinned_mesh.LoadAnimation("../media/Walking-loop.ozz");
|
||||
|
||||
ozz::animation::Animation* animation = skinned_mesh.m_animations[0];
|
||||
const int num_soa_joints = skinned_mesh.m_skeleton.num_soa_joints();
|
||||
const int num_joints = skinned_mesh.m_skeleton.num_joints();
|
||||
|
||||
ozz::vector<ozz::math::SoaTransform> local_matrices;
|
||||
ozz::animation::SamplingCache sampling_cache;
|
||||
|
||||
local_matrices.resize(num_soa_joints);
|
||||
sampling_cache.Resize(num_joints);
|
||||
|
||||
// sample at ratio 0.
|
||||
float ratio = 0.f;
|
||||
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = animation;
|
||||
sampling_job.cache = &sampling_cache;
|
||||
sampling_job.ratio = ratio;
|
||||
sampling_job.output = make_span(local_matrices);
|
||||
|
||||
if (!sampling_job.Run()) {
|
||||
ozz::log::Err() << "Error sampling animation." << std::endl;
|
||||
}
|
||||
|
||||
ozz::math::Transform root_transform_0;
|
||||
sample_bone_transform(
|
||||
0.f,
|
||||
0,
|
||||
animation,
|
||||
sampling_cache,
|
||||
local_matrices,
|
||||
root_transform_0);
|
||||
|
||||
int n_samples = 53;
|
||||
for (int i = 0; i <= n_samples; i++) {
|
||||
float ratio = i * 1.f / n_samples;
|
||||
ozz::math::Transform sampled_root_transform;
|
||||
sample_bone_transform(
|
||||
i * 1.f / n_samples,
|
||||
0,
|
||||
animation,
|
||||
sampling_cache,
|
||||
local_matrices,
|
||||
sampled_root_transform);
|
||||
|
||||
ozz::math::Transform calculated_root_transform;
|
||||
calc_bone_translation(ratio, 0, animation, calculated_root_transform);
|
||||
|
||||
std::cout << "ratio: " << ratio << "\t" << "err: " << ozz::math::Length(sampled_root_transform.translation - calculated_root_transform.translation) << std::endl;
|
||||
}
|
||||
|
||||
ozz::math::Transform root_transform_1;
|
||||
calc_bone_translation(0.f, 0, animation, root_transform_0);
|
||||
calc_bone_translation(1.f, 0, animation, root_transform_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Root Bone Sampling", "[AnimSamplerNode]") {
|
||||
SkinnedMesh skinned_mesh;
|
||||
skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
|
||||
int anim_idx = 0;
|
||||
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);
|
||||
|
||||
AnimationController anim_controller(&skinned_mesh);
|
||||
AnimSamplerNode test_node(&anim_controller);
|
||||
test_node.SetAnimation(skinned_mesh.m_animations[0], SyncTrack());
|
||||
|
||||
test_node.Reset();
|
||||
test_node.UpdateTime(0.2f);
|
||||
|
||||
ozz::math::Transform root_transform;
|
||||
test_node.Evaluate(&skinned_mesh.m_local_matrices, &root_transform);
|
||||
}
|
||||
Reference in New Issue
Block a user