Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ac22ebac0 | ||
|
|
64cdddea96 | ||
|
|
65c7a9aaaf | ||
|
|
5d01dfcca2 | ||
|
|
89fedce539 | ||
|
|
f6f7e92cea | ||
|
|
d32c247cc0 | ||
|
|
36c7f7a11e | ||
|
|
3b5537fc9d | ||
|
|
c173707a18 | ||
|
|
887131af37 | ||
|
|
86ea476881 | ||
|
|
f52b19a8d2 | ||
|
|
283306f225 | ||
|
|
b4eda31242 |
+8
-3
@@ -45,8 +45,6 @@ set(ThirdPartyIncludeDeps
|
|||||||
|
|
||||||
# Shared code by main executable and tests
|
# Shared code by main executable and tests
|
||||||
add_library(AnimTestbedCode OBJECT
|
add_library(AnimTestbedCode OBJECT
|
||||||
src/SyncTrack.cc
|
|
||||||
src/SyncTrack.h
|
|
||||||
src/ozzutils.cc
|
src/ozzutils.cc
|
||||||
src/AnimGraph/AnimGraphNodes.cc
|
src/AnimGraph/AnimGraphNodes.cc
|
||||||
src/AnimGraph/AnimGraphNodes.h
|
src/AnimGraph/AnimGraphNodes.h
|
||||||
@@ -59,7 +57,11 @@ add_library(AnimTestbedCode OBJECT
|
|||||||
src/AnimGraph/AnimNode.cc
|
src/AnimGraph/AnimNode.cc
|
||||||
src/AnimGraph/AnimNode.h
|
src/AnimGraph/AnimNode.h
|
||||||
src/AnimGraph/AnimGraphResource.cc
|
src/AnimGraph/AnimGraphResource.cc
|
||||||
src/AnimGraph/AnimGraphResource.h)
|
src/AnimGraph/AnimGraphResource.h
|
||||||
|
src/AnimGraph/SyncTrack.cc
|
||||||
|
src/AnimGraph/SyncTrack.h
|
||||||
|
src/AnimGraph/AnimLibrary.h
|
||||||
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
AnimTestbedCode
|
AnimTestbedCode
|
||||||
@@ -150,6 +152,9 @@ target_sources(runtests PRIVATE
|
|||||||
tests/AnimGraphEvalTests.cc
|
tests/AnimGraphEvalTests.cc
|
||||||
tests/NodeDescriptorTests.cc
|
tests/NodeDescriptorTests.cc
|
||||||
tests/SyncTrackTests.cc
|
tests/SyncTrackTests.cc
|
||||||
|
tests/AnimDataTests.cc
|
||||||
|
tests/TestAnimData.cc
|
||||||
|
tests/TestAnimData.h
|
||||||
tests/main.cc
|
tests/main.cc
|
||||||
${ozz_offline_test_objs}
|
${ozz_offline_test_objs}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
## Graph
|
||||||
|
|
||||||
|
### 1. Support of math nodes (or non-AnimNodes in general)
|
||||||
|
|
||||||
|
* Enables animators to add custom math for blend inputs or to adjust other inputs (e.g. LookAt or IK
|
||||||
|
targets).
|
||||||
|
|
||||||
|
**Open Issues**
|
||||||
|
|
||||||
|
1. When to do the evaluation? Two types of subgraphs:
|
||||||
|
a) Instant inputs (needed for blend node inputs) that have to be evaluated before
|
||||||
|
UpdateConnections
|
||||||
|
b) Processing nodes, e.g. for extracted bones.
|
||||||
|
|
||||||
|
### 2. Support of multiple output sockets
|
||||||
|
|
||||||
|
* E.g. extract Bone transform
|
||||||
|
|
||||||
|
* Increases Node complexity:
|
||||||
|
* AnimOutput
|
||||||
|
* AnimOutput + Data
|
||||||
|
* Data
|
||||||
|
|
||||||
|
(Data = bool, float, vec3, quat, ...)
|
||||||
|
|
||||||
|
**Open Issues**
|
||||||
|
|
||||||
|
1. Unclear when this is actually needed. Using more specific nodes that perform the desired logic
|
||||||
|
may be better (
|
||||||
|
c.f. https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-blueprint-bone-driven-controller-in-unreal-engine).
|
||||||
|
Likely this is not crucial so should be avoided for now.
|
||||||
|
|
||||||
|
### 3. Multi-skeleton evaluation
|
||||||
|
|
||||||
|
Use case: riding on a horse, interaction between two characters.
|
||||||
@@ -15,9 +15,9 @@ bool AnimGraphBlendTree::Init(AnimGraphContext& context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
for (size_t i = 0; i < m_pose_blocks.size(); i++) {
|
||||||
int num_soa_joints = context.m_skeleton->num_soa_joints();
|
int num_soa_joints = context.m_skeleton->num_soa_joints();
|
||||||
m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints);
|
m_pose_blocks[i]->m_local_matrices.resize(num_soa_joints);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#define ANIMTESTBED_ANIMGRAPHBLENDTREE_H
|
#define ANIMTESTBED_ANIMGRAPHBLENDTREE_H
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "AnimNode.h"
|
#include "AnimNode.h"
|
||||||
|
|
||||||
@@ -13,8 +14,6 @@
|
|||||||
// AnimGraph (Runtime)
|
// AnimGraph (Runtime)
|
||||||
//
|
//
|
||||||
struct AnimGraphBlendTree : public AnimNode {
|
struct AnimGraphBlendTree : public AnimNode {
|
||||||
AnimData m_local_transforms;
|
|
||||||
|
|
||||||
std::vector<AnimNode*> m_nodes;
|
std::vector<AnimNode*> m_nodes;
|
||||||
std::vector<AnimNode*> m_eval_ordered_nodes;
|
std::vector<AnimNode*> m_eval_ordered_nodes;
|
||||||
std::vector<std::vector<AnimGraphConnection> > m_node_input_connections;
|
std::vector<std::vector<AnimGraphConnection> > m_node_input_connections;
|
||||||
@@ -30,15 +29,17 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
return m_node_input_connections[0];
|
return m_node_input_connections[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<AnimData*> m_animdata_blocks;
|
std::vector<Pose*> m_pose_blocks;
|
||||||
NodeDescriptorBase* m_node_descriptor = nullptr;
|
NodeDescriptorBase* m_node_descriptor = nullptr;
|
||||||
char* m_input_buffer = nullptr;
|
char* m_input_buffer = nullptr;
|
||||||
char* m_output_buffer = nullptr;
|
char* m_output_buffer = nullptr;
|
||||||
char* m_connection_data_storage = nullptr;
|
char* m_connection_data_storage = nullptr;
|
||||||
char* m_const_node_inputs = nullptr;
|
char* m_const_node_inputs = nullptr;
|
||||||
|
|
||||||
std::vector<Socket>& GetGraphOutputs() { return m_node_descriptor->m_inputs; }
|
std::vector<Socket>& GetGraphOutputs() {
|
||||||
std::vector<Socket>& GetGraphInputs() { return m_node_descriptor->m_outputs; }
|
return m_node_descriptor->m_outputs;
|
||||||
|
}
|
||||||
|
std::vector<Socket>& GetGraphInputs() { return m_node_descriptor->m_inputs; }
|
||||||
|
|
||||||
AnimDataAllocator m_anim_data_allocator;
|
AnimDataAllocator m_anim_data_allocator;
|
||||||
|
|
||||||
@@ -48,6 +49,13 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
|
|
||||||
// AnimNode overrides
|
// AnimNode overrides
|
||||||
bool Init(AnimGraphContext& context) override;
|
bool Init(AnimGraphContext& context) override;
|
||||||
|
|
||||||
|
/// Determines which nodes in the BlendTree are active.
|
||||||
|
///
|
||||||
|
/// Note: this does not use the provided input_connections, instead it marks
|
||||||
|
/// all nodes directly connected to the BlendTree outputs as active and then
|
||||||
|
/// propagates the node state throught the tree. For this each active node's
|
||||||
|
/// AnimNode::MarkActiveInputs() gets called.
|
||||||
void MarkActiveInputs(
|
void MarkActiveInputs(
|
||||||
const std::vector<AnimGraphConnection>& input_connections) override;
|
const std::vector<AnimGraphConnection>& input_connections) override;
|
||||||
void CalcSyncTrack(
|
void CalcSyncTrack(
|
||||||
@@ -58,10 +66,10 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
void PropagateTimeToNodeInputs(const AnimNode* node);
|
void PropagateTimeToNodeInputs(const AnimNode* node);
|
||||||
|
|
||||||
void dealloc() {
|
void dealloc() {
|
||||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
for (size_t i = 0; i < m_pose_blocks.size(); i++) {
|
||||||
m_animdata_blocks[i]->m_local_matrices.vector::~vector();
|
m_pose_blocks[i]->m_local_matrices.vector::~vector();
|
||||||
}
|
}
|
||||||
m_animdata_blocks.clear();
|
m_pose_blocks.clear();
|
||||||
|
|
||||||
m_node_input_connections.clear();
|
m_node_input_connections.clear();
|
||||||
m_node_output_connections.clear();
|
m_node_output_connections.clear();
|
||||||
@@ -93,7 +101,7 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the address that is used for the specified AnimGraph input Socket.
|
/** Sets the address that is used for the specified BlendTree input Socket.
|
||||||
*
|
*
|
||||||
* @tparam T Type of the Socket.
|
* @tparam T Type of the Socket.
|
||||||
* @param name Name of the Socket.
|
* @param name Name of the Socket.
|
||||||
@@ -101,7 +109,7 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void SetInput(const char* name, T* value_ptr) {
|
void SetInput(const char* name, T* value_ptr) {
|
||||||
m_node_descriptor->SetOutput(name, value_ptr);
|
m_node_descriptor->SetInput(name, value_ptr);
|
||||||
|
|
||||||
std::vector<size_t> connected_node_indices;
|
std::vector<size_t> connected_node_indices;
|
||||||
|
|
||||||
@@ -115,14 +123,14 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the address that is used for the specified AnimGraph output Socket.
|
/** Sets the address that is used for the specified BlendTree output Socket.
|
||||||
*
|
*
|
||||||
* We update the pointer of the outputting node. We also have to ensure that
|
* We update the pointer of the outputting node. We also have to ensure that
|
||||||
* all usages of that output use the same pointer.
|
* all usages of that output use the same pointer.
|
||||||
*
|
*
|
||||||
* @tparam T Type of the Socket.
|
* @tparam T Type of the Socket.
|
||||||
* @param name Name of the Socket.
|
* @param name Name of the Socket.
|
||||||
* @param value_ptr Pointer where the graph output output is written to at the end of evaluation.
|
* @param value_ptr Pointer where the graph output is written to at the end of evaluation.
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void SetOutput(const char* name, T* value_ptr) {
|
void SetOutput(const char* name, T* value_ptr) {
|
||||||
@@ -148,6 +156,7 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that all connections that consume the output use the updated address.
|
||||||
size_t output_node_index =
|
size_t output_node_index =
|
||||||
GetAnimNodeIndex(graph_output_connection->m_source_node);
|
GetAnimNodeIndex(graph_output_connection->m_source_node);
|
||||||
|
|
||||||
@@ -161,6 +170,12 @@ struct AnimGraphBlendTree : public AnimNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
*graph_output_connection->m_socket.m_reference.ptr_ptr = value_ptr;
|
*graph_output_connection->m_socket.m_reference.ptr_ptr = value_ptr;
|
||||||
|
|
||||||
|
// And additionally update the BlendTree's node descriptor:
|
||||||
|
Socket* blend_tree_output_socket = m_node_descriptor->GetOutputSocket(name);
|
||||||
|
assert(blend_tree_output_socket != nullptr);
|
||||||
|
|
||||||
|
*blend_tree_output_socket->m_reference.ptr_ptr = value_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the address that is used for the specified AnimGraph output Socket.
|
/** Returns the address that is used for the specified AnimGraph output Socket.
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <ozz/base/maths/soa_transform.h>
|
#include <ozz/base/maths/soa_transform.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -25,26 +24,53 @@
|
|||||||
struct AnimGraph;
|
struct AnimGraph;
|
||||||
struct AnimNode;
|
struct AnimNode;
|
||||||
|
|
||||||
struct AnimData {
|
struct Pose {
|
||||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices;
|
ozz::vector<ozz::math::SoaTransform> m_local_matrices;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AnimDataRef {
|
struct AnimDataRef {
|
||||||
AnimData* ptr = nullptr;
|
Pose* ptr = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AnimationResource {
|
||||||
|
std::string m_name;
|
||||||
|
std::string m_filename;
|
||||||
|
|
||||||
|
ozz::animation::Animation* m_animation;
|
||||||
|
SyncTrack m_sync_track;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void to_json(
|
||||||
|
nlohmann::json& j,
|
||||||
|
const AnimationResource& animation_resource) {
|
||||||
|
j["type"] = "AnimationResource";
|
||||||
|
j["name"] = animation_resource.m_name;
|
||||||
|
j["filename"] = animation_resource.m_filename;
|
||||||
|
j["synctrack"] = animation_resource.m_sync_track;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void from_json(
|
||||||
|
const nlohmann::json& j,
|
||||||
|
AnimationResource& animation_resource) {
|
||||||
|
assert(j["type"] == "AnimationResource");
|
||||||
|
|
||||||
|
animation_resource.m_name = j["name"];
|
||||||
|
animation_resource.m_filename = j["filename"];
|
||||||
|
animation_resource.m_sync_track = j["synctrack"];
|
||||||
|
}
|
||||||
|
|
||||||
struct AnimDataAllocator {
|
struct AnimDataAllocator {
|
||||||
struct AnimDataList {
|
struct PoseList {
|
||||||
AnimData* m_anim_data = nullptr;
|
Pose* m_anim_data = nullptr;
|
||||||
AnimDataList* next = nullptr;
|
PoseList* next = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::list<AnimData*> m_anim_data_list;
|
std::list<Pose*> m_anim_data_list;
|
||||||
size_t m_num_allocations = 0;
|
size_t m_num_allocations = 0;
|
||||||
|
|
||||||
~AnimDataAllocator() {
|
~AnimDataAllocator() {
|
||||||
while (!m_anim_data_list.empty()) {
|
while (!m_anim_data_list.empty()) {
|
||||||
AnimData* front = m_anim_data_list.front();
|
Pose* front = m_anim_data_list.front();
|
||||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||||
std::cout << "about to delete with size "
|
std::cout << "about to delete with size "
|
||||||
<< front->m_anim_data->m_local_matrices.size()
|
<< front->m_anim_data->m_local_matrices.size()
|
||||||
@@ -55,9 +81,9 @@ struct AnimDataAllocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimData* allocate(ozz::animation::Skeleton* skeleton) {
|
Pose* allocate(ozz::animation::Skeleton* skeleton) {
|
||||||
if (m_anim_data_list.empty()) {
|
if (m_anim_data_list.empty()) {
|
||||||
AnimData* result = new AnimData();
|
Pose* result = new Pose();
|
||||||
result->m_local_matrices.resize(skeleton->num_soa_joints());
|
result->m_local_matrices.resize(skeleton->num_soa_joints());
|
||||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||||
std::cout << "Allocated with size " << result->m_local_matrices.size()
|
std::cout << "Allocated with size " << result->m_local_matrices.size()
|
||||||
@@ -67,7 +93,7 @@ struct AnimDataAllocator {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimData* result = m_anim_data_list.front();
|
Pose* result = m_anim_data_list.front();
|
||||||
m_anim_data_list.pop_front();
|
m_anim_data_list.pop_front();
|
||||||
|
|
||||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||||
@@ -78,7 +104,7 @@ struct AnimDataAllocator {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void free(AnimData* anim_data) {
|
void free(Pose* anim_data) {
|
||||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||||
std::cout << "Storing buffer with size "
|
std::cout << "Storing buffer with size "
|
||||||
<< anim_data->m_local_matrices.size() << " " << anim_data
|
<< anim_data->m_local_matrices.size() << " " << anim_data
|
||||||
@@ -98,18 +124,20 @@ enum class AnimGraphType {
|
|||||||
GraphTypeLast
|
GraphTypeLast
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*** Defines data on which an animation graph is executed (i.e. skeleton, animations).
|
||||||
|
*/
|
||||||
struct AnimGraphContext {
|
struct AnimGraphContext {
|
||||||
AnimGraph* m_graph = nullptr;
|
AnimGraph* m_graph = nullptr;
|
||||||
ozz::animation::Skeleton* m_skeleton = nullptr;
|
ozz::animation::Skeleton* m_skeleton = nullptr;
|
||||||
|
|
||||||
typedef std::map<std::string, ozz::animation::Animation*> AnimationFileMap;
|
typedef std::map<std::string, AnimationResource> AnimationFileMap;
|
||||||
AnimationFileMap m_animation_map;
|
AnimationFileMap m_animation_map;
|
||||||
|
|
||||||
void freeAnimations() {
|
void freeAnimations() {
|
||||||
AnimationFileMap::iterator animation_map_iter = m_animation_map.begin();
|
AnimationFileMap::iterator animation_map_iter = m_animation_map.begin();
|
||||||
|
|
||||||
while (animation_map_iter != m_animation_map.end()) {
|
while (animation_map_iter != m_animation_map.end()) {
|
||||||
delete animation_map_iter->second;
|
delete animation_map_iter->second.m_animation;
|
||||||
animation_map_iter++;
|
animation_map_iter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,7 +266,7 @@ SocketType GetSocketType() {
|
|||||||
return SocketType::SocketTypeBool;
|
return SocketType::SocketTypeBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (std::is_same<T, AnimData>::value) {
|
if constexpr (std::is_same<T, Pose>::value) {
|
||||||
return SocketType::SocketTypeAnimation;
|
return SocketType::SocketTypeAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,11 +401,6 @@ struct NodeDescriptorBase {
|
|||||||
*socket->m_reference.ptr_ptr = value_ptr;
|
*socket->m_reference.ptr_ptr = value_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetOutputUnchecked(const char* name, void* value_ptr) {
|
|
||||||
Socket* socket = FindSocket(name, m_outputs);
|
|
||||||
*socket->m_reference.ptr_ptr = value_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Socket* GetOutputSocket(const char* name) const {
|
Socket* GetOutputSocket(const char* name) const {
|
||||||
return FindSocket(name, m_outputs);
|
return FindSocket(name, m_outputs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ bool AnimSamplerNode::Init(AnimGraphContext& context) {
|
|||||||
AnimGraphContext::AnimationFileMap::const_iterator animation_map_iter;
|
AnimGraphContext::AnimationFileMap::const_iterator animation_map_iter;
|
||||||
animation_map_iter = context.m_animation_map.find(m_filename);
|
animation_map_iter = context.m_animation_map.find(m_filename);
|
||||||
if (animation_map_iter != context.m_animation_map.end()) {
|
if (animation_map_iter != context.m_animation_map.end()) {
|
||||||
m_animation = animation_map_iter->second;
|
m_animation = animation_map_iter->second.m_animation;
|
||||||
|
m_sync_track = animation_map_iter->second.m_sync_track;
|
||||||
} else {
|
} else {
|
||||||
m_animation = new ozz::animation::Animation();
|
m_animation = new ozz::animation::Animation();
|
||||||
ozz::io::File file(m_filename.c_str(), "rb");
|
ozz::io::File file(m_filename.c_str(), "rb");
|
||||||
@@ -150,7 +151,11 @@ bool AnimSamplerNode::Init(AnimGraphContext& context) {
|
|||||||
|
|
||||||
archive >> *m_animation;
|
archive >> *m_animation;
|
||||||
|
|
||||||
context.m_animation_map[m_filename] = m_animation;
|
context.m_animation_map[m_filename] = {
|
||||||
|
m_filename,
|
||||||
|
m_filename,
|
||||||
|
m_animation,
|
||||||
|
SyncTrack()};
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(context.m_skeleton != nullptr);
|
assert(context.m_skeleton != nullptr);
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ struct AnimNode;
|
|||||||
// Blend2Node
|
// Blend2Node
|
||||||
//
|
//
|
||||||
struct Blend2Node : public AnimNode {
|
struct Blend2Node : public AnimNode {
|
||||||
AnimData* i_input0 = nullptr;
|
Pose* i_input0 = nullptr;
|
||||||
AnimData* i_input1 = nullptr;
|
Pose* i_input1 = nullptr;
|
||||||
AnimData* o_output = nullptr;
|
Pose* o_output = nullptr;
|
||||||
float* i_blend_weight = nullptr;
|
float* i_blend_weight = nullptr;
|
||||||
bool m_sync_blend = false;
|
bool m_sync_blend = false;
|
||||||
|
|
||||||
@@ -33,12 +33,12 @@ struct Blend2Node : public AnimNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (input.m_target_socket_name == "Input0" && *i_blend_weight < 0.999) {
|
if (input.m_target_socket_name == "Input0" && *i_blend_weight < 0.999) {
|
||||||
input_node->m_state = AnimNodeEvalState::Activated;
|
input_node->Activate(m_tick_number);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.m_target_socket_name == "Input1" && *i_blend_weight > 0.001) {
|
if (input.m_target_socket_name == "Input1" && *i_blend_weight > 0.001) {
|
||||||
input_node->m_state = AnimNodeEvalState::Activated;
|
input_node->Activate(m_tick_number);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,8 @@ struct NodeDescriptor<Blend2Node> : public NodeDescriptorBase {
|
|||||||
RegisterProperty("Sync", &node->m_sync_blend);
|
RegisterProperty("Sync", &node->m_sync_blend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~NodeDescriptor() = default;
|
||||||
|
|
||||||
void UpdateFlags() override {
|
void UpdateFlags() override {
|
||||||
Socket* weight_input_socket = FindSocket("Weight", m_inputs);
|
Socket* weight_input_socket = FindSocket("Weight", m_inputs);
|
||||||
assert(weight_input_socket != nullptr);
|
assert(weight_input_socket != nullptr);
|
||||||
@@ -75,8 +77,8 @@ struct NodeDescriptor<Blend2Node> : public NodeDescriptorBase {
|
|||||||
// SpeedScaleNode
|
// SpeedScaleNode
|
||||||
//
|
//
|
||||||
struct SpeedScaleNode : public AnimNode {
|
struct SpeedScaleNode : public AnimNode {
|
||||||
AnimData* i_input = nullptr;
|
Pose* i_input = nullptr;
|
||||||
AnimData* o_output = nullptr;
|
Pose* o_output = nullptr;
|
||||||
float* i_speed_scale = nullptr;
|
float* i_speed_scale = nullptr;
|
||||||
|
|
||||||
void UpdateTime(float time_last, float time_now) override {
|
void UpdateTime(float time_last, float time_now) override {
|
||||||
@@ -104,13 +106,15 @@ struct NodeDescriptor<SpeedScaleNode> : public NodeDescriptorBase {
|
|||||||
|
|
||||||
RegisterOutput("Output", &node->o_output);
|
RegisterOutput("Output", &node->o_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~NodeDescriptor() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// AnimSamplerNode
|
// AnimSamplerNode
|
||||||
//
|
//
|
||||||
struct AnimSamplerNode : public AnimNode {
|
struct AnimSamplerNode : public AnimNode {
|
||||||
AnimData* o_output = nullptr;
|
Pose* o_output = nullptr;
|
||||||
std::string m_filename;
|
std::string m_filename;
|
||||||
ozz::animation::SamplingJob::Context m_sampling_context;
|
ozz::animation::SamplingJob::Context m_sampling_context;
|
||||||
ozz::animation::Animation* m_animation = nullptr;
|
ozz::animation::Animation* m_animation = nullptr;
|
||||||
@@ -132,14 +136,16 @@ struct NodeDescriptor<AnimSamplerNode> : public NodeDescriptorBase {
|
|||||||
|
|
||||||
RegisterProperty("Filename", &node->m_filename);
|
RegisterProperty("Filename", &node->m_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~NodeDescriptor() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// LockTranslationNode
|
// LockTranslationNode
|
||||||
//
|
//
|
||||||
struct LockTranslationNode : public AnimNode {
|
struct LockTranslationNode : public AnimNode {
|
||||||
AnimData* i_input = nullptr;
|
Pose* i_input = nullptr;
|
||||||
AnimData* o_output = nullptr;
|
Pose* o_output = nullptr;
|
||||||
int m_locked_bone_index = 0;
|
int m_locked_bone_index = 0;
|
||||||
bool m_lock_x = false;
|
bool m_lock_x = false;
|
||||||
bool m_lock_y = false;
|
bool m_lock_y = false;
|
||||||
@@ -159,6 +165,8 @@ struct NodeDescriptor<LockTranslationNode> : public NodeDescriptorBase {
|
|||||||
RegisterProperty("LockAxisY", &node->m_lock_y);
|
RegisterProperty("LockAxisY", &node->m_lock_y);
|
||||||
RegisterProperty("LockAxisZ", &node->m_lock_z);
|
RegisterProperty("LockAxisZ", &node->m_lock_z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~NodeDescriptor() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -177,6 +185,8 @@ struct NodeDescriptor<ConstScalarNode> : public NodeDescriptorBase {
|
|||||||
RegisterOutput("ScalarOutput", &node->o_value);
|
RegisterOutput("ScalarOutput", &node->o_value);
|
||||||
RegisterProperty("ScalarValue", &node->value);
|
RegisterProperty("ScalarValue", &node->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~NodeDescriptor() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -202,6 +212,8 @@ struct NodeDescriptor<MathAddNode> : public NodeDescriptorBase {
|
|||||||
RegisterInput("Input1", &node->i_input1);
|
RegisterInput("Input1", &node->i_input1);
|
||||||
RegisterOutput("Output", &node->o_output);
|
RegisterOutput("Output", &node->o_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~NodeDescriptor() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -232,6 +244,8 @@ struct NodeDescriptor<MathFloatToVec3Node> : public NodeDescriptorBase {
|
|||||||
RegisterInput("Input2", &node->i_input2);
|
RegisterInput("Input2", &node->i_input2);
|
||||||
RegisterOutput("Output", &node->o_output);
|
RegisterOutput("Output", &node->o_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~NodeDescriptor() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
AnimNode* AnimNodeFactory(const std::string& name);
|
AnimNode* AnimNodeFactory(const std::string& name);
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ Socket sJsonToSocket(const json& json_data) {
|
|||||||
}
|
}
|
||||||
} else if (type_string == "Animation") {
|
} else if (type_string == "Animation") {
|
||||||
result.m_type = SocketType::SocketTypeAnimation;
|
result.m_type = SocketType::SocketTypeAnimation;
|
||||||
result.m_type_size = sizeof(AnimData);
|
result.m_type_size = sizeof(Pose);
|
||||||
} else if (type_string == "Int") {
|
} else if (type_string == "Int") {
|
||||||
result.m_type = SocketType::SocketTypeInt;
|
result.m_type = SocketType::SocketTypeInt;
|
||||||
result.m_type_size = sizeof(int);
|
result.m_type_size = sizeof(int);
|
||||||
@@ -933,9 +933,9 @@ void BlendTreeResource::CreateBlendTreeRuntimeNodeInstances(
|
|||||||
embedded_blend_tree_resource->CreateBlendTreeInstance(
|
embedded_blend_tree_resource->CreateBlendTreeInstance(
|
||||||
*embedded_blend_tree);
|
*embedded_blend_tree);
|
||||||
embedded_blend_tree_resource->m_virtual_socket_accessor->m_inputs =
|
embedded_blend_tree_resource->m_virtual_socket_accessor->m_inputs =
|
||||||
embedded_blend_tree->m_node_descriptor->m_outputs;
|
|
||||||
embedded_blend_tree_resource->m_virtual_socket_accessor->m_outputs =
|
|
||||||
embedded_blend_tree->m_node_descriptor->m_inputs;
|
embedded_blend_tree->m_node_descriptor->m_inputs;
|
||||||
|
embedded_blend_tree_resource->m_virtual_socket_accessor->m_outputs =
|
||||||
|
embedded_blend_tree->m_node_descriptor->m_outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
node->m_name = node_resource->m_name;
|
node->m_name = node_resource->m_name;
|
||||||
@@ -955,9 +955,9 @@ void BlendTreeResource::PrepareBlendTreeIOData(
|
|||||||
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
|
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
|
||||||
|
|
||||||
instance.m_node_descriptor->m_outputs =
|
instance.m_node_descriptor->m_outputs =
|
||||||
GetGraphInputNode()->m_virtual_socket_accessor->m_outputs;
|
|
||||||
instance.m_node_descriptor->m_inputs =
|
|
||||||
GetGraphOutputNode()->m_virtual_socket_accessor->m_inputs;
|
GetGraphOutputNode()->m_virtual_socket_accessor->m_inputs;
|
||||||
|
instance.m_node_descriptor->m_inputs =
|
||||||
|
GetGraphInputNode()->m_virtual_socket_accessor->m_outputs;
|
||||||
|
|
||||||
//
|
//
|
||||||
// graph inputs
|
// graph inputs
|
||||||
@@ -977,7 +977,7 @@ void BlendTreeResource::PrepareBlendTreeIOData(
|
|||||||
for (int i = 0; i < graph_inputs.size(); i++) {
|
for (int i = 0; i < graph_inputs.size(); i++) {
|
||||||
graph_inputs[i].m_reference.ptr =
|
graph_inputs[i].m_reference.ptr =
|
||||||
(void*)&instance.m_input_buffer[input_block_offset];
|
(void*)&instance.m_input_buffer[input_block_offset];
|
||||||
instance.m_node_descriptor->m_outputs[i].m_reference.ptr =
|
instance.m_node_descriptor->m_inputs[i].m_reference.ptr =
|
||||||
&instance.m_input_buffer[input_block_offset];
|
&instance.m_input_buffer[input_block_offset];
|
||||||
input_block_offset += sizeof(void*);
|
input_block_offset += sizeof(void*);
|
||||||
}
|
}
|
||||||
@@ -998,13 +998,18 @@ void BlendTreeResource::PrepareBlendTreeIOData(
|
|||||||
|
|
||||||
int output_block_offset = 0;
|
int output_block_offset = 0;
|
||||||
for (int i = 0; i < graph_outputs.size(); i++) {
|
for (int i = 0; i < graph_outputs.size(); i++) {
|
||||||
instance.m_node_descriptor->m_inputs[i].m_reference.ptr =
|
instance.m_node_descriptor->m_outputs[i].m_reference.ptr =
|
||||||
&instance.m_output_buffer[output_block_offset];
|
&instance.m_output_buffer[output_block_offset];
|
||||||
output_block_offset += sizeof(void*);
|
output_block_offset += sizeof(void*);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// connecton data storage
|
// connection data storage: we reserve memory for the used outputs of all
|
||||||
|
// nodes:
|
||||||
|
// * If a node output does not get used: we do not reserve a memory block.
|
||||||
|
// * If a node output gets used by multiple nodes: we only reserve a single
|
||||||
|
// memory block.
|
||||||
|
// The resulting memory offsets are stored in the node_offset_map.
|
||||||
//
|
//
|
||||||
size_t connection_data_storage_size = 0;
|
size_t connection_data_storage_size = 0;
|
||||||
for (const BlendTreeConnectionResource& connection : GetConnections()) {
|
for (const BlendTreeConnectionResource& connection : GetConnections()) {
|
||||||
@@ -1028,6 +1033,61 @@ void BlendTreeResource::PrepareBlendTreeIOData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlendTreeResource::SetRuntimeNodeProperties(
|
||||||
|
AnimGraphBlendTree& result) const {
|
||||||
|
for (int i = 2; i < GetNumNodes(); i++) {
|
||||||
|
const AnimNodeResource* node_resource = GetNode(i);
|
||||||
|
|
||||||
|
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
|
||||||
|
node_resource->m_node_type_name,
|
||||||
|
result.m_nodes[i]);
|
||||||
|
|
||||||
|
std::vector<Socket>& resource_properties =
|
||||||
|
node_resource->m_virtual_socket_accessor->m_properties;
|
||||||
|
for (const auto& property : resource_properties) {
|
||||||
|
const std::string& name = property.m_name;
|
||||||
|
|
||||||
|
switch (property.m_type) {
|
||||||
|
case SocketType::SocketTypeBool:
|
||||||
|
node_instance_accessor->SetProperty(
|
||||||
|
name.c_str(),
|
||||||
|
property.m_value.flag);
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeInt:
|
||||||
|
node_instance_accessor->SetProperty(
|
||||||
|
name.c_str(),
|
||||||
|
property.m_value.int_value);
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeFloat:
|
||||||
|
node_instance_accessor->SetProperty(
|
||||||
|
name.c_str(),
|
||||||
|
property.m_value.float_value);
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeVec3:
|
||||||
|
node_instance_accessor->SetProperty<Vec3>(
|
||||||
|
name.c_str(),
|
||||||
|
property.m_value.vec3);
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeQuat:
|
||||||
|
node_instance_accessor->SetProperty(
|
||||||
|
name.c_str(),
|
||||||
|
property.m_value.quat);
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeString:
|
||||||
|
node_instance_accessor->SetProperty(
|
||||||
|
name.c_str(),
|
||||||
|
property.m_value_string);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cerr << "Invalid socket type "
|
||||||
|
<< static_cast<int>(property.m_type) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete node_instance_accessor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BlendTreeResource::CreateBlendTreeConnectionInstances(
|
void BlendTreeResource::CreateBlendTreeConnectionInstances(
|
||||||
AnimGraphBlendTree& instance,
|
AnimGraphBlendTree& instance,
|
||||||
NodeSocketDataOffsetMap& node_offset_map) const {
|
NodeSocketDataOffsetMap& node_offset_map) const {
|
||||||
@@ -1047,9 +1107,10 @@ void BlendTreeResource::CreateBlendTreeConnectionInstances(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instance_node_descriptors[0]->m_inputs = instance.m_node_descriptor->m_inputs;
|
instance_node_descriptors[0]->m_inputs =
|
||||||
instance_node_descriptors[1]->m_outputs =
|
|
||||||
instance.m_node_descriptor->m_outputs;
|
instance.m_node_descriptor->m_outputs;
|
||||||
|
instance_node_descriptors[1]->m_outputs =
|
||||||
|
instance.m_node_descriptor->m_inputs;
|
||||||
|
|
||||||
for (const BlendTreeConnectionResource& connection : GetConnections()) {
|
for (const BlendTreeConnectionResource& connection : GetConnections()) {
|
||||||
NodeDescriptorBase* source_node_descriptor =
|
NodeDescriptorBase* source_node_descriptor =
|
||||||
@@ -1181,9 +1242,8 @@ void BlendTreeResource::CreateBlendTreeConnectionInstances(
|
|||||||
&instance.m_connection_data_storage[socket_data_offset];
|
&instance.m_connection_data_storage[socket_data_offset];
|
||||||
|
|
||||||
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
|
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
|
||||||
instance.m_animdata_blocks.push_back(
|
instance.m_pose_blocks.push_back(
|
||||||
(AnimData*)(&instance
|
(Pose*)(&instance.m_connection_data_storage[socket_data_offset]));
|
||||||
.m_connection_data_storage[socket_data_offset]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1236,59 +1296,4 @@ void BlendTreeResource::CreateBlendTreeConnectionInstances(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlendTreeResource::SetRuntimeNodeProperties(
|
|
||||||
AnimGraphBlendTree& result) const {
|
|
||||||
for (int i = 2; i < GetNumNodes(); i++) {
|
|
||||||
const AnimNodeResource* node_resource = GetNode(i);
|
|
||||||
|
|
||||||
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
|
|
||||||
node_resource->m_node_type_name,
|
|
||||||
result.m_nodes[i]);
|
|
||||||
|
|
||||||
std::vector<Socket>& resource_properties =
|
|
||||||
node_resource->m_virtual_socket_accessor->m_properties;
|
|
||||||
for (const auto& property : resource_properties) {
|
|
||||||
const std::string& name = property.m_name;
|
|
||||||
|
|
||||||
switch (property.m_type) {
|
|
||||||
case SocketType::SocketTypeBool:
|
|
||||||
node_instance_accessor->SetProperty(
|
|
||||||
name.c_str(),
|
|
||||||
property.m_value.flag);
|
|
||||||
break;
|
|
||||||
case SocketType::SocketTypeInt:
|
|
||||||
node_instance_accessor->SetProperty(
|
|
||||||
name.c_str(),
|
|
||||||
property.m_value.int_value);
|
|
||||||
break;
|
|
||||||
case SocketType::SocketTypeFloat:
|
|
||||||
node_instance_accessor->SetProperty(
|
|
||||||
name.c_str(),
|
|
||||||
property.m_value.float_value);
|
|
||||||
break;
|
|
||||||
case SocketType::SocketTypeVec3:
|
|
||||||
node_instance_accessor->SetProperty<Vec3>(
|
|
||||||
name.c_str(),
|
|
||||||
property.m_value.vec3);
|
|
||||||
break;
|
|
||||||
case SocketType::SocketTypeQuat:
|
|
||||||
node_instance_accessor->SetProperty(
|
|
||||||
name.c_str(),
|
|
||||||
property.m_value.quat);
|
|
||||||
break;
|
|
||||||
case SocketType::SocketTypeString:
|
|
||||||
node_instance_accessor->SetProperty(
|
|
||||||
name.c_str(),
|
|
||||||
property.m_value_string);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
std::cerr << "Invalid socket type "
|
|
||||||
<< static_cast<int>(property.m_type) << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete node_instance_accessor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||||
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "3rdparty/json/json.hpp"
|
#include "3rdparty/json/json.hpp"
|
||||||
#include "AnimGraphNodes.h"
|
#include "AnimGraphNodes.h"
|
||||||
|
|
||||||
@@ -75,8 +77,7 @@ struct BlendTreeResource : AnimGraphResource {
|
|||||||
m_virtual_socket_accessor = VirtualAnimNodeDescriptorFactory("BlendTree");
|
m_virtual_socket_accessor = VirtualAnimNodeDescriptorFactory("BlendTree");
|
||||||
|
|
||||||
InitGraphConnectors();
|
InitGraphConnectors();
|
||||||
RegisterBlendTreeOutputSocket<AnimData>(
|
RegisterBlendTreeOutputSocket<Pose>(AnimGraphResource::DefaultAnimOutput);
|
||||||
AnimGraphResource::DefaultAnimOutput);
|
|
||||||
}
|
}
|
||||||
~BlendTreeResource() { ClearAllNodes(); }
|
~BlendTreeResource() { ClearAllNodes(); }
|
||||||
|
|
||||||
@@ -375,12 +376,12 @@ struct BlendTreeResource : AnimGraphResource {
|
|||||||
AnimGraphBlendTree& instance,
|
AnimGraphBlendTree& instance,
|
||||||
NodeSocketDataOffsetMap& node_offset_map) const;
|
NodeSocketDataOffsetMap& node_offset_map) const;
|
||||||
|
|
||||||
|
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
|
||||||
|
|
||||||
void CreateBlendTreeConnectionInstances(
|
void CreateBlendTreeConnectionInstances(
|
||||||
AnimGraphBlendTree& instance,
|
AnimGraphBlendTree& instance,
|
||||||
NodeSocketDataOffsetMap& node_offset_map) const;
|
NodeSocketDataOffsetMap& node_offset_map) const;
|
||||||
|
|
||||||
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
|
|
||||||
|
|
||||||
void InitGraphConnectors() {
|
void InitGraphConnectors() {
|
||||||
AddNode(AnimNodeResourceFactory("BlendTreeSockets"));
|
AddNode(AnimNodeResourceFactory("BlendTreeSockets"));
|
||||||
AnimNodeResource* output_node = GetGraphOutputNode();
|
AnimNodeResource* output_node = GetGraphOutputNode();
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
//
|
||||||
|
// Created by martin on 11.04.25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ANIMLIBRARY_H
|
||||||
|
#define ANIMLIBRARY_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "AnimGraph/AnimGraphData.h"
|
||||||
|
#include "ozz/base/io/archive.h"
|
||||||
|
#include "ozz/base/io/stream.h"
|
||||||
|
#include "ozz/base/log.h"
|
||||||
|
|
||||||
|
/** Manage a set of animations used for an AnimGraph.
|
||||||
|
*
|
||||||
|
* By default, it behaves like a resource that allows to resolve animation names to
|
||||||
|
* AnimationResources. However, it can also trigger loading of the referenced resources from their
|
||||||
|
* filenames. This only happens on-demand.
|
||||||
|
*/
|
||||||
|
struct AnimLibrary {
|
||||||
|
typedef std::map<std::string, AnimationResource> AnimationResourceMap;
|
||||||
|
AnimationResourceMap mAnimations = {};
|
||||||
|
std::vector<ozz::animation::Animation*> mManagedAnimations = {};
|
||||||
|
static constexpr const char* EXTERNAL_ANIMATION = "<external>";
|
||||||
|
|
||||||
|
AnimLibrary() = default;
|
||||||
|
~AnimLibrary() { Reset(); }
|
||||||
|
|
||||||
|
bool AddAnimation(
|
||||||
|
const std::string& name,
|
||||||
|
ozz::animation::Animation* animation) {
|
||||||
|
AnimationResource animation_resource;
|
||||||
|
animation_resource.m_name = name;
|
||||||
|
animation_resource.m_filename = EXTERNAL_ANIMATION;
|
||||||
|
animation_resource.m_animation = animation;
|
||||||
|
mAnimations[name] = animation_resource;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddAnimationFile(const std::string& name, const std::string& filename) {
|
||||||
|
if (mAnimations.find(name) != mAnimations.end()) {
|
||||||
|
std::cerr << "Cannot add animation '" << name
|
||||||
|
<< "' to library. Animation already exists." << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationResource animation_resource;
|
||||||
|
animation_resource.m_name = name;
|
||||||
|
animation_resource.m_animation = nullptr;
|
||||||
|
animation_resource.m_filename = filename;
|
||||||
|
|
||||||
|
mAnimations[name] = animation_resource;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
for (ozz::animation::Animation* animation : mManagedAnimations) {
|
||||||
|
assert(animation->num_tracks() < 5);
|
||||||
|
delete animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
mManagedAnimations.clear();
|
||||||
|
mAnimations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadAnimations() {
|
||||||
|
for (AnimationResourceMap::iterator iter = mAnimations.begin();
|
||||||
|
iter != mAnimations.end();
|
||||||
|
++iter) {
|
||||||
|
if (iter->second.m_filename == EXTERNAL_ANIMATION) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iter->second.m_animation != nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!iter->second.m_filename.empty());
|
||||||
|
ozz::io::File file(iter->second.m_filename.c_str(), "rb");
|
||||||
|
if (!file.opened()) {
|
||||||
|
ozz::log::Err() << "Failed to open animation file "
|
||||||
|
<< iter->second.m_filename << "." << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ozz::io::IArchive archive(&file);
|
||||||
|
if (!archive.TestTag<ozz::animation::Animation>()) {
|
||||||
|
ozz::log::Err() << "Failed to load animation instance from file "
|
||||||
|
<< iter->second.m_filename << "." << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
iter->second.m_animation = new ozz::animation::Animation;
|
||||||
|
|
||||||
|
archive >> *iter->second.m_animation;
|
||||||
|
|
||||||
|
mManagedAnimations.push_back(iter->second.m_animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void to_json(nlohmann::json& j, const AnimLibrary& animation_library) {
|
||||||
|
j["type"] = "AnimationLibrary";
|
||||||
|
|
||||||
|
for (AnimLibrary::AnimationResourceMap::const_iterator iter =
|
||||||
|
animation_library.mAnimations.cbegin();
|
||||||
|
iter != animation_library.mAnimations.cend();
|
||||||
|
++iter) {
|
||||||
|
j["animations"][iter->first] = iter->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void from_json(const nlohmann::json& j, AnimLibrary& animation_library) {
|
||||||
|
animation_library.Reset();
|
||||||
|
|
||||||
|
if (!j.contains("type") || j["type"] != "AnimationLibrary") {
|
||||||
|
std::cerr << "Invalid type. Expected 'AnimationLibrary'." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!j.contains("animations")) {
|
||||||
|
std::cerr << "Invalid AnimationLibrary. Expected 'animations' key."
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (nlohmann::json::const_iterator iter = j["animations"].begin();
|
||||||
|
iter != j["animations"].cend();
|
||||||
|
++iter) {
|
||||||
|
AnimationResource animation_resource = *iter;
|
||||||
|
|
||||||
|
if (!animation_resource.m_filename.empty()) {
|
||||||
|
animation_library.AddAnimationFile(
|
||||||
|
iter.key(),
|
||||||
|
j["animations"][iter.key()]["filename"]);
|
||||||
|
animation_library.mAnimations[iter.key()].m_sync_track =
|
||||||
|
animation_resource.m_sync_track;
|
||||||
|
} else {
|
||||||
|
animation_library.mAnimations[iter.key()] = *iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //ANIMLIBRARY_H
|
||||||
@@ -45,8 +45,7 @@ struct AnimNode {
|
|||||||
for (const auto& input : input_connections) {
|
for (const auto& input : input_connections) {
|
||||||
AnimNode* input_node = input.m_source_node;
|
AnimNode* input_node = input.m_source_node;
|
||||||
if (input_node != nullptr) {
|
if (input_node != nullptr) {
|
||||||
input_node->m_tick_number = m_tick_number;
|
input_node->Activate(m_tick_number);
|
||||||
input_node->m_state = AnimNodeEvalState::Activated;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,13 +63,18 @@ struct AnimNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void UpdateTime(float time_last, float time_now) {
|
virtual void UpdateTime(const float time_last, const float time_now) {
|
||||||
m_time_last = time_last;
|
m_time_last = time_last;
|
||||||
m_time_now = time_now;
|
m_time_now = time_now;
|
||||||
m_state = AnimNodeEvalState::TimeUpdated;
|
m_state = AnimNodeEvalState::TimeUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Evaluate(AnimGraphContext& context){};
|
virtual void Evaluate(AnimGraphContext& context) {};
|
||||||
|
|
||||||
|
void Activate(const int tick_number) {
|
||||||
|
m_tick_number = tick_number;
|
||||||
|
m_state = AnimNodeEvalState::Activated;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //ANIMTESTBED_ANIMNODE_H
|
#endif //ANIMTESTBED_ANIMNODE_H
|
||||||
|
|||||||
@@ -3,7 +3,3 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "SyncTrack.h"
|
#include "SyncTrack.h"
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
//
|
||||||
|
// Created by martin on 19.11.21.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ANIMTESTBED_SYNCTRACK_H
|
||||||
|
#define ANIMTESTBED_SYNCTRACK_H
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "3rdparty/json/json.hpp"
|
||||||
|
|
||||||
|
constexpr int cSyncTrackMaxIntervals = 8;
|
||||||
|
|
||||||
|
/// Metadata used for synced animation blending.
|
||||||
|
//
|
||||||
|
// A SyncTrack consists of multiple SyncInterval that are adjacent to each
|
||||||
|
// other.
|
||||||
|
//
|
||||||
|
// Important definitions:
|
||||||
|
//
|
||||||
|
// - Absolute Time: time within an animation duration in seconds.
|
||||||
|
// - Ratio: time relative to the animations duration, e.g. 0.5 corresponds to
|
||||||
|
// 50% of the duration.
|
||||||
|
// - SyncTime is a floating point value where the integer parts defines the
|
||||||
|
// SyncInterval and the fractional part the fraction within the interval. I.e.
|
||||||
|
// a SyncTime of 5.332 means it is ~33% through interval 5.
|
||||||
|
//
|
||||||
|
// A SyncInterval is defined by a ratio of the starting point and the ratio of
|
||||||
|
// the interval's duration.
|
||||||
|
struct SyncTrack {
|
||||||
|
SyncTrack() : m_duration(0.f), m_num_intervals(1) {
|
||||||
|
for (int i = 0; i < cSyncTrackMaxIntervals; i++) {
|
||||||
|
m_interval_start_ratio[i] = 0.f;
|
||||||
|
m_interval_duration_ratio[i] = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_interval_duration_ratio[0] = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float m_duration;
|
||||||
|
int m_num_intervals;
|
||||||
|
float m_interval_start_ratio
|
||||||
|
[cSyncTrackMaxIntervals]; //< Starting time of interval in absolute time.
|
||||||
|
float m_interval_duration_ratio[cSyncTrackMaxIntervals]; //<
|
||||||
|
|
||||||
|
float CalcSyncFromAbsTime(float abs_time) {
|
||||||
|
for (int i = 0; i < m_num_intervals; i++) {
|
||||||
|
float query_abs_time = abs_time;
|
||||||
|
float interval_start = m_interval_start_ratio[i] * m_duration;
|
||||||
|
float interval_end =
|
||||||
|
interval_start + m_interval_duration_ratio[i] * m_duration;
|
||||||
|
|
||||||
|
if (query_abs_time < interval_start) {
|
||||||
|
query_abs_time += m_duration;
|
||||||
|
}
|
||||||
|
if (query_abs_time >= interval_start && query_abs_time < interval_end) {
|
||||||
|
return float(i)
|
||||||
|
+ (query_abs_time - interval_start)
|
||||||
|
/ (interval_end - interval_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false && "Invalid absolute time");
|
||||||
|
return -1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CalcRatioFromSyncTime(float sync_time) {
|
||||||
|
float interval_ratio = fmodf(sync_time, 1.0f);
|
||||||
|
int interval = int(sync_time - interval_ratio);
|
||||||
|
|
||||||
|
return fmodf(
|
||||||
|
m_interval_start_ratio[interval]
|
||||||
|
+ m_interval_duration_ratio[interval] * interval_ratio,
|
||||||
|
1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const SyncTrack& other) const {
|
||||||
|
bool result = m_duration == other.m_duration
|
||||||
|
&& m_num_intervals == other.m_num_intervals;
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_num_intervals; i++) {
|
||||||
|
if ((fabsf(m_interval_start_ratio[i] - other.m_interval_start_ratio[i])
|
||||||
|
> 1.0e-5)
|
||||||
|
|| (fabsf(
|
||||||
|
m_interval_duration_ratio[i]
|
||||||
|
- other.m_interval_duration_ratio[i])
|
||||||
|
> 1.0e-5)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructs SyncTrack from markers.
|
||||||
|
*
|
||||||
|
* Markers are specified in absolute time and must be >= 0 and <= duration.
|
||||||
|
* They define the start of the interval. The last marker is implicitly the
|
||||||
|
* (possibly looped) first marker.
|
||||||
|
*/
|
||||||
|
static SyncTrack CreateFromMarkers(
|
||||||
|
float duration,
|
||||||
|
const std::vector<float>& markers) {
|
||||||
|
assert(markers.size() > 0);
|
||||||
|
assert(markers.size() < cSyncTrackMaxIntervals);
|
||||||
|
|
||||||
|
SyncTrack result;
|
||||||
|
result.m_duration = duration;
|
||||||
|
result.m_num_intervals = markers.size();
|
||||||
|
|
||||||
|
for (int i = 0; i < markers.size(); i++) {
|
||||||
|
assert(markers[i] >= 0.f && markers[i] <= duration);
|
||||||
|
|
||||||
|
int end_index = i == (markers.size() - 1) ? 0 : i + 1;
|
||||||
|
|
||||||
|
float interval_start = markers[i];
|
||||||
|
float interval_end = markers[end_index];
|
||||||
|
|
||||||
|
if (interval_end == interval_start) {
|
||||||
|
interval_end = interval_start + duration;
|
||||||
|
} else if (interval_end < interval_start) {
|
||||||
|
interval_end += duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.m_interval_start_ratio[i] = interval_start / duration;
|
||||||
|
result.m_interval_duration_ratio[i] =
|
||||||
|
(interval_end - interval_start) / duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SyncTrack
|
||||||
|
Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) {
|
||||||
|
assert(track_A.m_num_intervals == track_B.m_num_intervals);
|
||||||
|
|
||||||
|
SyncTrack result;
|
||||||
|
result.m_num_intervals = track_A.m_num_intervals;
|
||||||
|
|
||||||
|
result.m_duration =
|
||||||
|
(1.0f - weight) * track_A.m_duration + weight * track_B.m_duration;
|
||||||
|
|
||||||
|
float interval_0_offset =
|
||||||
|
track_B.m_interval_start_ratio[0] - track_A.m_interval_start_ratio[0];
|
||||||
|
if (interval_0_offset > 0.5f) {
|
||||||
|
interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f);
|
||||||
|
} else if (interval_0_offset < -0.5) {
|
||||||
|
interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.m_interval_start_ratio[0] = fmodf(
|
||||||
|
1.0 + (1.0f - weight) * track_A.m_interval_start_ratio[0]
|
||||||
|
+ weight * (track_A.m_interval_start_ratio[0] + interval_0_offset),
|
||||||
|
1.0f);
|
||||||
|
|
||||||
|
for (int i = 0; i < result.m_num_intervals; i++) {
|
||||||
|
float interval_duration_A = track_A.m_interval_duration_ratio[i];
|
||||||
|
float interval_duration_B = track_B.m_interval_duration_ratio[i];
|
||||||
|
result.m_interval_duration_ratio[i] =
|
||||||
|
(1.0f - weight) * interval_duration_A + weight * interval_duration_B;
|
||||||
|
|
||||||
|
if (i < cSyncTrackMaxIntervals) {
|
||||||
|
result.m_interval_start_ratio[i + 1] =
|
||||||
|
result.m_interval_start_ratio[i]
|
||||||
|
+ result.m_interval_duration_ratio[i];
|
||||||
|
if (result.m_interval_start_ratio[i + 1] > 1.0f) {
|
||||||
|
result.m_interval_start_ratio[i + 1] =
|
||||||
|
fmodf(result.m_interval_start_ratio[i + 1], 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(result.m_num_intervals < cSyncTrackMaxIntervals);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void to_json(nlohmann::json& j, const SyncTrack& sync_track) {
|
||||||
|
j["type"] = "SyncTrack";
|
||||||
|
j["duration"] = sync_track.m_duration;
|
||||||
|
for (int i = 0; i < sync_track.m_num_intervals; i++) {
|
||||||
|
j["interval"][i]["start"] = sync_track.m_interval_start_ratio[i];
|
||||||
|
j["interval"][i]["ratio"] = sync_track.m_interval_duration_ratio[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void from_json(const nlohmann::json& j, SyncTrack& sync_track) {
|
||||||
|
assert(j["type"] == "SyncTrack");
|
||||||
|
|
||||||
|
sync_track.m_duration = j["duration"];
|
||||||
|
|
||||||
|
sync_track.m_num_intervals = j["interval"].size();
|
||||||
|
assert(sync_track.m_num_intervals < cSyncTrackMaxIntervals);
|
||||||
|
|
||||||
|
for (int i = 0; i < sync_track.m_num_intervals; i++) {
|
||||||
|
sync_track.m_interval_start_ratio[i] = j["interval"][i]["start"];
|
||||||
|
sync_track.m_interval_duration_ratio[i] = j["interval"][i]["ratio"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //ANIMTESTBED_SYNCTRACK_H
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "imnodes.h"
|
#include "imnodes.h"
|
||||||
#include "misc/cpp/imgui_stdlib.h"
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
#include "nfd.h"
|
#include "nfd.h"
|
||||||
|
#include "ozz/base/log.h"
|
||||||
#include "src/AnimGraph/AnimGraphResource.h"
|
#include "src/AnimGraph/AnimGraphResource.h"
|
||||||
|
|
||||||
struct EditorState {
|
struct EditorState {
|
||||||
@@ -154,21 +155,7 @@ void SyncTrackEditor(SyncTrack* sync_track) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Marker:");
|
ImGui::Text("Marker:");
|
||||||
for (int i = 0; i < sync_track->m_num_intervals; i++) {
|
ImGui::Text("TODO");
|
||||||
ImGui::Text("%2d:", i);
|
|
||||||
ImGui::SameLine();
|
|
||||||
std::ostringstream marker_stream;
|
|
||||||
marker_stream << i;
|
|
||||||
ImGui::SliderFloat(
|
|
||||||
marker_stream.str().c_str(),
|
|
||||||
&sync_track->m_sync_markers[i],
|
|
||||||
0.f,
|
|
||||||
1.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::Button("Update Intervals")) {
|
|
||||||
sync_track->CalcIntervals();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
|
void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
|
||||||
@@ -797,9 +784,9 @@ void BlendTreeEditorDebugWidget() {
|
|||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
ImGui::Text("Pin");
|
ImGui::Text("Pin");
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("%x", sNodeConnectionDebugState.sourceSocket.pin.AsPointer());
|
ImGui::Text("%p", sNodeConnectionDebugState.sourceSocket.pin.AsPointer());
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("%x", sNodeConnectionDebugState.targetSocket.pin.AsPointer());
|
ImGui::Text("%p", sNodeConnectionDebugState.targetSocket.pin.AsPointer());
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
|||||||
+21
-20
@@ -42,8 +42,8 @@ inline void Camera_Init(Camera* camera) {
|
|||||||
camera->pitch = 10 * M_PI / 180.0f;
|
camera->pitch = 10 * M_PI / 180.0f;
|
||||||
|
|
||||||
memcpy(&camera->mtxView, &mtx_identity, sizeof(camera->mtxView));
|
memcpy(&camera->mtxView, &mtx_identity, sizeof(camera->mtxView));
|
||||||
Camera_CalcToMatrix(camera, &camera->mtxView);
|
Camera_CalcToMatrix(camera, &camera->mtxView[0]);
|
||||||
Camera_CalcFromMatrix(camera, &camera->mtxView);
|
Camera_CalcFromMatrix(camera, &camera->mtxView[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera_CalcFromMatrix(Camera* camera, float* mat) {
|
void Camera_CalcFromMatrix(Camera* camera, float* mat) {
|
||||||
@@ -72,11 +72,11 @@ void Camera_CalcFromMatrix(Camera* camera, float* mat) {
|
|||||||
camera->pos[1] = -simd4f_get_y(eye);
|
camera->pos[1] = -simd4f_get_y(eye);
|
||||||
camera->pos[2] = -simd4f_get_z(eye);
|
camera->pos[2] = -simd4f_get_z(eye);
|
||||||
|
|
||||||
// gLog ("ViewMat");
|
// gLog ("ViewMat");
|
||||||
// gLog ("%f, %f, %f, %f", mtx->x[0], mtx->x[1], mtx->x[2], mtx->x[3]);
|
// gLog ("%f, %f, %f, %f", mtx->x[0], mtx->x[1], mtx->x[2], mtx->x[3]);
|
||||||
// gLog ("%f, %f, %f, %f", mtx->y[0], mtx->y[1], mtx->y[2], mtx->y[3]);
|
// gLog ("%f, %f, %f, %f", mtx->y[0], mtx->y[1], mtx->y[2], mtx->y[3]);
|
||||||
// gLog ("%f, %f, %f, %f", mtx->z[0], mtx->z[1], mtx->z[2], mtx->z[3]);
|
// gLog ("%f, %f, %f, %f", mtx->z[0], mtx->z[1], mtx->z[2], mtx->z[3]);
|
||||||
// gLog ("%f, %f, %f, %f", mtx->w[0], mtx->w[1], mtx->w[2], mtx->w[3]);
|
// gLog ("%f, %f, %f, %f", mtx->w[0], mtx->w[1], mtx->w[2], mtx->w[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera_CalcToMatrix(Camera* camera, float* mat) {
|
void Camera_CalcToMatrix(Camera* camera, float* mat) {
|
||||||
@@ -87,9 +87,10 @@ void Camera_CalcToMatrix(Camera* camera, float* mat) {
|
|||||||
|
|
||||||
const float d = 10.0f;
|
const float d = 10.0f;
|
||||||
|
|
||||||
simd4f eye = simd4f_create (camera->pos[0], camera->pos[1], camera->pos[2], 1.f);
|
simd4f eye =
|
||||||
simd4f forward = simd4f_create (-cp * ch, -sp, cp * sh, 0.f);
|
simd4f_create(camera->pos[0], camera->pos[1], camera->pos[2], 1.f);
|
||||||
simd4f right = simd4f_cross3 (forward, simd4f_create (0.f, 1.f, 0.f, 1.f));
|
simd4f forward = simd4f_create(-cp * ch, -sp, cp * sh, 0.f);
|
||||||
|
simd4f right = simd4f_cross3(forward, simd4f_create(0.f, 1.f, 0.f, 1.f));
|
||||||
simd4f up = simd4f_cross3(right, forward);
|
simd4f up = simd4f_cross3(right, forward);
|
||||||
simd4f center = simd4f_add(simd4f_mul(forward, simd4f_splat(d)), eye);
|
simd4f center = simd4f_add(simd4f_mul(forward, simd4f_splat(d)), eye);
|
||||||
|
|
||||||
@@ -105,9 +106,9 @@ void Camera_CalcToMatrix(Camera* camera, float* mat) {
|
|||||||
simd4x4f_lookat(&mtx, eye, center, up);
|
simd4x4f_lookat(&mtx, eye, center, up);
|
||||||
|
|
||||||
simd4f_ustore4(mtx.x, mat);
|
simd4f_ustore4(mtx.x, mat);
|
||||||
simd4f_ustore4(mtx.y, mat +4);
|
simd4f_ustore4(mtx.y, mat + 4);
|
||||||
simd4f_ustore4(mtx.z, mat +8);
|
simd4f_ustore4(mtx.z, mat + 8);
|
||||||
simd4f_ustore4(mtx.w, mat +12);
|
simd4f_ustore4(mtx.w, mat + 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Camera_Update(
|
inline void Camera_Update(
|
||||||
@@ -120,14 +121,14 @@ inline void Camera_Update(
|
|||||||
float accel[3]) {
|
float accel[3]) {
|
||||||
assert(camera);
|
assert(camera);
|
||||||
assert((width > 0) && (height > 0));
|
assert((width > 0) && (height > 0));
|
||||||
const float w = (float) width;
|
const float w = (float)width;
|
||||||
const float h = (float) height;
|
const float h = (float)height;
|
||||||
simd4x4f proj;
|
simd4x4f proj;
|
||||||
simd4x4f_perspective(&proj, camera->fov, w/h, camera->near, camera->far);
|
simd4x4f_perspective(&proj, camera->fov, w / h, camera->near, camera->far);
|
||||||
simd4f_ustore4(proj.x, camera->mtxProj);
|
simd4f_ustore4(proj.x, camera->mtxProj);
|
||||||
simd4f_ustore4(proj.y, camera->mtxProj +4);
|
simd4f_ustore4(proj.y, camera->mtxProj + 4);
|
||||||
simd4f_ustore4(proj.z, camera->mtxProj +8);
|
simd4f_ustore4(proj.z, camera->mtxProj + 8);
|
||||||
simd4f_ustore4(proj.w, camera->mtxProj +12);
|
simd4f_ustore4(proj.w, camera->mtxProj + 12);
|
||||||
|
|
||||||
if (mouse_dx != 0.f || mouse_dy != 0.f || accel != NULL) {
|
if (mouse_dx != 0.f || mouse_dy != 0.f || accel != NULL) {
|
||||||
const float mouse_sensitivity = 20.0f;
|
const float mouse_sensitivity = 20.0f;
|
||||||
@@ -153,6 +154,6 @@ inline void Camera_Update(
|
|||||||
camera->vel[i] = camera->vel[i] * 0.1;
|
camera->vel[i] = camera->vel[i] * 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Camera_CalcToMatrix(camera, &camera->mtxView);
|
Camera_CalcToMatrix(camera, &camera->mtxView[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
-2
@@ -4,9 +4,13 @@
|
|||||||
|
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
|
|
||||||
#include <HandmadeMath.h>
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||||
|
#include "ozz/base/io/archive.h"
|
||||||
|
#include "ozz/base/io/stream.h"
|
||||||
|
#include "ozz/base/log.h"
|
||||||
|
|
||||||
SkinnedMesh::~SkinnedMesh() {
|
SkinnedMesh::~SkinnedMesh() {
|
||||||
while (m_animations.size() > 0) {
|
while (m_animations.size() > 0) {
|
||||||
ozz::animation::Animation* animation_ptr =
|
ozz::animation::Animation* animation_ptr =
|
||||||
@@ -98,4 +102,3 @@ void SkinnedMesh::CalcModelMatrices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SkinnedMesh::DrawSkeleton() {}
|
void SkinnedMesh::DrawSkeleton() {}
|
||||||
|
|
||||||
|
|||||||
+1
-9
@@ -5,21 +5,13 @@
|
|||||||
#ifndef ANIMTESTBED_SKINNEDMESH_H
|
#ifndef ANIMTESTBED_SKINNEDMESH_H
|
||||||
#define ANIMTESTBED_SKINNEDMESH_H
|
#define ANIMTESTBED_SKINNEDMESH_H
|
||||||
|
|
||||||
// ozz-animation headers
|
#include "AnimGraph/SyncTrack.h"
|
||||||
#include <cmath> // fmodf
|
|
||||||
#include <memory> // std::unique_ptr, std::make_unique
|
|
||||||
|
|
||||||
#include "SyncTrack.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/sampling_job.h"
|
#include "ozz/animation/runtime/sampling_job.h"
|
||||||
#include "ozz/animation/runtime/skeleton.h"
|
#include "ozz/animation/runtime/skeleton.h"
|
||||||
#include "ozz/base/containers/vector.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/soa_transform.h"
|
||||||
#include "ozz/base/maths/vec_float.h"
|
|
||||||
|
|
||||||
struct SkinnedMesh {
|
struct SkinnedMesh {
|
||||||
SkinnedMesh() : m_sync_track_override(false), m_override_anim(0.f) {}
|
SkinnedMesh() : m_sync_track_override(false), m_override_anim(0.f) {}
|
||||||
|
|||||||
@@ -5,72 +5,15 @@
|
|||||||
#include "SkinnedMeshResource.h"
|
#include "SkinnedMeshResource.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "3rdparty/json/json.hpp"
|
#include "3rdparty/json/json.hpp"
|
||||||
|
|
||||||
inline void to_json(nlohmann::json& j, const SyncTrack& syncTrack) {
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||||
j["type"] = "SyncTrack";
|
SkinnedMeshResource,
|
||||||
j["duration"] = syncTrack.m_duration;
|
m_type,
|
||||||
for (int i = 0; i < syncTrack.m_num_intervals; i++) {
|
m_resource_file,
|
||||||
j["markers"][i] = syncTrack.m_sync_markers[i];
|
m_skeleton_file);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void from_json(const nlohmann::json& j, SyncTrack& syncTrack) {
|
|
||||||
if (!j.contains("type") || j["type"] != "SyncTrack") {
|
|
||||||
std::cerr << "Unable to parse SyncTrack: wrong json type!" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
syncTrack.m_duration = j["duration"];
|
|
||||||
syncTrack.m_num_intervals = j["markers"].size();
|
|
||||||
|
|
||||||
if (syncTrack.m_num_intervals > cSyncTrackMaxIntervals) {
|
|
||||||
std::cerr << "Invalid number of sync intervals: found " << syncTrack.m_num_intervals << " maximum is " << cSyncTrackMaxIntervals << "." << std::endl;
|
|
||||||
syncTrack = SyncTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < syncTrack.m_num_intervals; i++) {
|
|
||||||
syncTrack.m_sync_markers[i] = j["markers"].at(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void to_json(nlohmann::json& j, const SkinnedMeshResource& skinnedMeshResource) {
|
|
||||||
j["type"] = "SkinnedMeshResource";
|
|
||||||
j["skeleton"]["file"] = skinnedMeshResource.m_skeleton_file;
|
|
||||||
for (int i = 0; i < skinnedMeshResource.m_animation_files.size(); i++) {
|
|
||||||
j["animations"][i]["file"] = skinnedMeshResource.m_animation_files[i];
|
|
||||||
j["animations"][i]["sync_track"] = skinnedMeshResource.m_sync_tracks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void from_json(const nlohmann::json& j, SkinnedMeshResource& skinnedMeshResource) {
|
|
||||||
if (!j.contains("type") || j["type"] != "SkinnedMeshResource") {
|
|
||||||
std::cerr << "Unable to parse SkinnedMeshResource: wrong json type!" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!j.contains("skeleton") || !j["skeleton"].contains("file")) {
|
|
||||||
std::cerr << "Unable to parse SkinnedMeshResource: no skeleton file found!" << std::endl;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
skinnedMeshResource.m_skeleton_file = j["skeleton"]["file"];
|
|
||||||
|
|
||||||
if (j.contains("animations")) {
|
|
||||||
int num_animations = j["animations"].size();
|
|
||||||
|
|
||||||
for (int i = 0; i < num_animations; i++) {
|
|
||||||
if (!j["animations"][i].contains("file") || !j["animations"][i].contains("sync_track")) {
|
|
||||||
std::cerr << "Unable to parse SkinnedMeshResource: invalid animation definition" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
skinnedMeshResource.m_animation_files.push_back(j["animations"][i]["file"]);
|
|
||||||
skinnedMeshResource.m_sync_tracks.push_back(j["animations"][i]["sync_track"].get<SyncTrack>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SkinnedMeshResource::saveToFile(const char* filename) const {
|
bool SkinnedMeshResource::saveToFile(const char* filename) const {
|
||||||
nlohmann::json j = *this;
|
nlohmann::json j = *this;
|
||||||
@@ -103,10 +46,4 @@ bool SkinnedMeshResource::loadFromFile(const char* filename) {
|
|||||||
|
|
||||||
void SkinnedMeshResource::createInstance(SkinnedMesh& skinnedMesh) const {
|
void SkinnedMeshResource::createInstance(SkinnedMesh& skinnedMesh) const {
|
||||||
skinnedMesh.LoadSkeleton(m_skeleton_file.c_str());
|
skinnedMesh.LoadSkeleton(m_skeleton_file.c_str());
|
||||||
for (int i = 0; i < m_animation_files.size(); i++) {
|
|
||||||
skinnedMesh.LoadAnimation(m_animation_files[i].c_str());
|
|
||||||
skinnedMesh.m_animation_sync_track.back() = m_sync_tracks[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "SyncTrack.h"
|
#include "AnimGraph/SyncTrack.h"
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
|
|
||||||
struct SkinnedMeshResource {
|
struct SkinnedMeshResource {
|
||||||
|
constexpr static char TypeStr[] = "SkinnedMeshResource";
|
||||||
|
std::string m_type = TypeStr;
|
||||||
std::string m_resource_file;
|
std::string m_resource_file;
|
||||||
std::string m_skeleton_file;
|
std::string m_skeleton_file;
|
||||||
std::vector<std::string> m_animation_files;
|
|
||||||
std::vector<SyncTrack> m_sync_tracks;
|
|
||||||
|
|
||||||
bool saveToFile(const char* filename) const;
|
bool saveToFile(const char* filename) const;
|
||||||
bool loadFromFile(const char* filename);
|
bool loadFromFile(const char* filename);
|
||||||
|
|||||||
-155
@@ -1,155 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by martin on 19.11.21.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef ANIMTESTBED_SYNCTRACK_H
|
|
||||||
#define ANIMTESTBED_SYNCTRACK_H
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cmath>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
constexpr int cSyncTrackMaxIntervals = 8;
|
|
||||||
|
|
||||||
struct SyncTrack {
|
|
||||||
SyncTrack() : m_duration(0.f), m_num_intervals(0) {
|
|
||||||
for (int i = 0; i < cSyncTrackMaxIntervals; i++) {
|
|
||||||
m_sync_markers[i] = 0.f;
|
|
||||||
m_interval_ratio[i] = 0.f;
|
|
||||||
m_interval_ratio[i] = 0.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float m_duration;
|
|
||||||
int m_num_intervals;
|
|
||||||
float m_sync_markers[cSyncTrackMaxIntervals];
|
|
||||||
float m_interval_start[cSyncTrackMaxIntervals];
|
|
||||||
float m_interval_ratio[cSyncTrackMaxIntervals];
|
|
||||||
|
|
||||||
void CalcIntervals() {
|
|
||||||
if (m_num_intervals == 0) {
|
|
||||||
m_num_intervals = 1;
|
|
||||||
m_sync_markers[0] = 0.f;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < m_num_intervals; i++) {
|
|
||||||
assert(m_sync_markers[i] >= 0.f && m_sync_markers[i] <= 1.0f);
|
|
||||||
|
|
||||||
int end_index = i < m_num_intervals - 1 ? i + 1 : 0;
|
|
||||||
|
|
||||||
m_interval_start[i] = m_sync_markers[i];
|
|
||||||
float interval_end = m_sync_markers[end_index];
|
|
||||||
|
|
||||||
if (interval_end < m_interval_start[i]) {
|
|
||||||
interval_end += 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_interval_ratio[i] = interval_end - m_interval_start[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float CalcSyncFromAbsTime(float abs_time) {
|
|
||||||
float sync_time = fmodf(abs_time, m_duration) / m_duration;
|
|
||||||
|
|
||||||
int interval_index = 0;
|
|
||||||
while (sync_time >= m_interval_ratio[interval_index]) {
|
|
||||||
sync_time -= m_interval_ratio[interval_index];
|
|
||||||
interval_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float(interval_index) + sync_time / m_interval_ratio[interval_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
float CalcRatioFromSyncTime(float sync_time) {
|
|
||||||
float interval_ratio = fmodf(sync_time, 1.0f);
|
|
||||||
int interval = int(sync_time - interval_ratio);
|
|
||||||
|
|
||||||
return fmodf(
|
|
||||||
m_interval_start[interval]
|
|
||||||
+ m_interval_ratio[interval] * interval_ratio,
|
|
||||||
1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const SyncTrack& other) const {
|
|
||||||
bool result = m_duration == other.m_duration
|
|
||||||
&& m_num_intervals == other.m_num_intervals;
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < m_num_intervals; i++) {
|
|
||||||
if ((fabsf(m_interval_start[i] - other.m_interval_start[i]) > 1.0e-5)
|
|
||||||
|| (fabsf(m_interval_ratio[i] - other.m_interval_ratio[i])
|
|
||||||
> 1.0e-5)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SyncTrack CreateFromMarkers(
|
|
||||||
float duration,
|
|
||||||
int n_markers,
|
|
||||||
float markers[cSyncTrackMaxIntervals]) {
|
|
||||||
SyncTrack result;
|
|
||||||
result.m_duration = duration;
|
|
||||||
result.m_num_intervals = n_markers;
|
|
||||||
for (int i = 0; i < n_markers; i++) {
|
|
||||||
result.m_sync_markers[i] = markers[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
result.CalcIntervals();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SyncTrack
|
|
||||||
Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) {
|
|
||||||
assert(track_A.m_num_intervals == track_B.m_num_intervals);
|
|
||||||
|
|
||||||
SyncTrack result;
|
|
||||||
result.m_num_intervals = track_A.m_num_intervals;
|
|
||||||
|
|
||||||
result.m_duration =
|
|
||||||
(1.0f - weight) * track_A.m_duration + weight * track_B.m_duration;
|
|
||||||
|
|
||||||
float interval_0_offset =
|
|
||||||
track_B.m_interval_start[0] - track_A.m_interval_start[0];
|
|
||||||
if (interval_0_offset > 0.5f) {
|
|
||||||
interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f);
|
|
||||||
} else if (interval_0_offset < -0.5) {
|
|
||||||
interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.m_interval_start[0] = fmodf(
|
|
||||||
1.0 + (1.0f - weight) * track_A.m_interval_start[0]
|
|
||||||
+ weight * (track_A.m_interval_start[0] + interval_0_offset),
|
|
||||||
1.0f);
|
|
||||||
result.m_sync_markers[0] = result.m_interval_start[0];
|
|
||||||
|
|
||||||
for (int i = 0; i < result.m_num_intervals; i++) {
|
|
||||||
float interval_duration_A = track_A.m_interval_ratio[i];
|
|
||||||
float interval_duration_B = track_B.m_interval_ratio[i];
|
|
||||||
result.m_interval_ratio[i] =
|
|
||||||
(1.0f - weight) * interval_duration_A + weight * interval_duration_B;
|
|
||||||
|
|
||||||
if (i < cSyncTrackMaxIntervals) {
|
|
||||||
result.m_interval_start[i + 1] =
|
|
||||||
result.m_interval_start[i] + result.m_interval_ratio[i];
|
|
||||||
if (result.m_interval_start[i + 1] > 1.0f) {
|
|
||||||
result.m_interval_start[i + 1] =
|
|
||||||
fmodf(result.m_interval_start[i + 1], 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.m_sync_markers[i + 1] = result.m_interval_start[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert (result.m_num_intervals < cSyncTrackMaxIntervals);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //ANIMTESTBED_SYNCTRACK_H
|
|
||||||
+314
-455
@@ -39,14 +39,207 @@ const int MaxIndices = MaxVertices * 3;
|
|||||||
uint64_t last_time = 0;
|
uint64_t last_time = 0;
|
||||||
const int cMSAASampleCount = 8;
|
const int cMSAASampleCount = 8;
|
||||||
|
|
||||||
sg_pass_action pass_action;
|
|
||||||
sg_pipeline pip;
|
|
||||||
sg_bindings bind;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ImVec2 disp_size;
|
ImVec2 disp_size;
|
||||||
} vs_params_t;
|
} vs_params_t;
|
||||||
|
|
||||||
|
struct SokolImGuiState {
|
||||||
|
sg_pass_action pass_action;
|
||||||
|
sg_pipeline pip;
|
||||||
|
sg_bindings bind;
|
||||||
|
|
||||||
|
void Init(GLFWwindow* window);
|
||||||
|
void Render(ImDrawData* draw_data, int width, int height);
|
||||||
|
void Shutdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
void SokolImGuiState::Init(GLFWwindow* window) {
|
||||||
|
// setup Dear Imgui
|
||||||
|
ImGui::CreateContext();
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||||
|
io.IniFilename = "ATPImgui.ini";
|
||||||
|
|
||||||
|
//io.Fonts->AddFontDefault();
|
||||||
|
ImFontConfig font_config;
|
||||||
|
font_config.OversampleH = 4;
|
||||||
|
font_config.OversampleV = 4;
|
||||||
|
font_config.GlyphExtraSpacing.x = 1.0f;
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
|
// roboto_medium_ttf_compressed_data,
|
||||||
|
// roboto_medium_ttf_compressed_size,
|
||||||
|
droid_sans_ttf_compressed_data,
|
||||||
|
droid_sans_ttf_compressed_size,
|
||||||
|
14,
|
||||||
|
&font_config);
|
||||||
|
|
||||||
|
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||||
|
ImGui_ImplOpenGL3_Init("#version 130");
|
||||||
|
|
||||||
|
// ImNodes
|
||||||
|
ImNodes::CreateContext();
|
||||||
|
|
||||||
|
// dynamic vertex- and index-buffers for imgui-generated geometry
|
||||||
|
sg_buffer_desc vbuf_desc = {};
|
||||||
|
vbuf_desc.usage = SG_USAGE_STREAM;
|
||||||
|
vbuf_desc.size = MaxVertices * sizeof(ImDrawVert);
|
||||||
|
bind.vertex_buffers[0] = sg_make_buffer(&vbuf_desc);
|
||||||
|
|
||||||
|
sg_buffer_desc ibuf_desc = {};
|
||||||
|
ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER;
|
||||||
|
ibuf_desc.usage = SG_USAGE_STREAM;
|
||||||
|
ibuf_desc.size = MaxIndices * sizeof(ImDrawIdx);
|
||||||
|
bind.index_buffer = sg_make_buffer(&ibuf_desc);
|
||||||
|
|
||||||
|
// font texture for imgui's default font
|
||||||
|
unsigned char* font_pixels;
|
||||||
|
int font_width, font_height;
|
||||||
|
io.Fonts->GetTexDataAsRGBA32(&font_pixels, &font_width, &font_height);
|
||||||
|
sg_image_desc img_desc = {};
|
||||||
|
img_desc.width = font_width;
|
||||||
|
img_desc.height = font_height;
|
||||||
|
img_desc.pixel_format = SG_PIXELFORMAT_RGBA8;
|
||||||
|
img_desc.wrap_u = SG_WRAP_CLAMP_TO_EDGE;
|
||||||
|
img_desc.wrap_v = SG_WRAP_CLAMP_TO_EDGE;
|
||||||
|
img_desc.data.subimage[0][0] =
|
||||||
|
sg_range{font_pixels, size_t(font_width * font_height * 4)};
|
||||||
|
bind.fs_images[0] = sg_make_image(&img_desc);
|
||||||
|
|
||||||
|
// shader object for imgui rendering
|
||||||
|
sg_shader_desc shd_desc = {};
|
||||||
|
auto& ub = shd_desc.vs.uniform_blocks[0];
|
||||||
|
ub.size = sizeof(vs_params_t);
|
||||||
|
ub.uniforms[0].name = "disp_size";
|
||||||
|
ub.uniforms[0].type = SG_UNIFORMTYPE_FLOAT2;
|
||||||
|
shd_desc.vs.source =
|
||||||
|
"#version 330\n"
|
||||||
|
"uniform vec2 disp_size;\n"
|
||||||
|
"layout(location=0) in vec2 position;\n"
|
||||||
|
"layout(location=1) in vec2 texcoord0;\n"
|
||||||
|
"layout(location=2) in vec4 color0;\n"
|
||||||
|
"out vec2 uv;\n"
|
||||||
|
"out vec4 color;\n"
|
||||||
|
"void main() {\n"
|
||||||
|
" gl_Position = vec4(((position/disp_size)-0.5)*vec2(2.0,-2.0), 0.5, "
|
||||||
|
"1.0);\n"
|
||||||
|
" uv = texcoord0;\n"
|
||||||
|
" color = color0;\n"
|
||||||
|
"}\n";
|
||||||
|
shd_desc.fs.images[0].name = "tex";
|
||||||
|
shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D;
|
||||||
|
shd_desc.fs.source =
|
||||||
|
"#version 330\n"
|
||||||
|
"uniform sampler2D tex;\n"
|
||||||
|
"in vec2 uv;\n"
|
||||||
|
"in vec4 color;\n"
|
||||||
|
"out vec4 frag_color;\n"
|
||||||
|
"void main() {\n"
|
||||||
|
" frag_color = texture(tex, uv) * color;\n"
|
||||||
|
"}\n";
|
||||||
|
sg_shader shd = sg_make_shader(&shd_desc);
|
||||||
|
|
||||||
|
// pipeline object for imgui rendering
|
||||||
|
sg_pipeline_desc pip_desc = {};
|
||||||
|
pip_desc.layout.buffers[0].stride = sizeof(ImDrawVert);
|
||||||
|
auto& attrs = pip_desc.layout.attrs;
|
||||||
|
attrs[0].format = SG_VERTEXFORMAT_FLOAT2;
|
||||||
|
attrs[1].format = SG_VERTEXFORMAT_FLOAT2;
|
||||||
|
attrs[2].format = SG_VERTEXFORMAT_UBYTE4N;
|
||||||
|
pip_desc.shader = shd;
|
||||||
|
pip_desc.index_type = SG_INDEXTYPE_UINT16;
|
||||||
|
pip_desc.colors[0].blend.enabled = true;
|
||||||
|
pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA;
|
||||||
|
pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
pip_desc.colors[0].write_mask = SG_COLORMASK_RGB;
|
||||||
|
pip_desc.sample_count = cMSAASampleCount;
|
||||||
|
pip_desc.label = "imgui-rendering";
|
||||||
|
pip = sg_make_pipeline(&pip_desc);
|
||||||
|
|
||||||
|
// initial clear color
|
||||||
|
pass_action.colors[0].action = SG_ACTION_CLEAR;
|
||||||
|
pass_action.colors[0].value = {0.1f, 0.1f, 0.1f, 1.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SokolImGuiState::Render(ImDrawData* draw_data, int width, int height) {
|
||||||
|
assert(draw_data);
|
||||||
|
|
||||||
|
sg_begin_default_pass(&pass_action, width, height);
|
||||||
|
|
||||||
|
if (draw_data->CmdListsCount == 0) {
|
||||||
|
sg_end_pass();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sg_image default_image = bind.fs_images[0];
|
||||||
|
|
||||||
|
// render the command list
|
||||||
|
sg_apply_pipeline(pip);
|
||||||
|
vs_params_t vs_params;
|
||||||
|
vs_params.disp_size.x = ImGui::GetIO().DisplaySize.x;
|
||||||
|
vs_params.disp_size.y = ImGui::GetIO().DisplaySize.y;
|
||||||
|
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE(vs_params));
|
||||||
|
for (int cl_index = 0; cl_index < draw_data->CmdListsCount; cl_index++) {
|
||||||
|
const ImDrawList* cl = draw_data->CmdLists[cl_index];
|
||||||
|
|
||||||
|
// append vertices and indices to buffers, record start offsets in resource binding struct
|
||||||
|
const uint32_t vtx_size = cl->VtxBuffer.size() * sizeof(ImDrawVert);
|
||||||
|
const uint32_t idx_size = cl->IdxBuffer.size() * sizeof(ImDrawIdx);
|
||||||
|
const uint32_t vb_offset = sg_append_buffer(
|
||||||
|
bind.vertex_buffers[0],
|
||||||
|
{&cl->VtxBuffer.front(), vtx_size});
|
||||||
|
const uint32_t ib_offset =
|
||||||
|
sg_append_buffer(bind.index_buffer, {&cl->IdxBuffer.front(), idx_size});
|
||||||
|
/* don't render anything if the buffer is in overflow state (this is also
|
||||||
|
checked internally in sokol_gfx, draw calls that attempt from
|
||||||
|
overflowed buffers will be silently dropped)
|
||||||
|
*/
|
||||||
|
if (sg_query_buffer_overflow(bind.vertex_buffers[0])
|
||||||
|
|| sg_query_buffer_overflow(bind.index_buffer)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.vertex_buffer_offsets[0] = vb_offset;
|
||||||
|
bind.index_buffer_offset = ib_offset;
|
||||||
|
sg_apply_bindings(&bind);
|
||||||
|
|
||||||
|
int base_element = 0;
|
||||||
|
for (const ImDrawCmd& pcmd : cl->CmdBuffer) {
|
||||||
|
if (pcmd.UserCallback) {
|
||||||
|
pcmd.UserCallback(cl, &pcmd);
|
||||||
|
} else {
|
||||||
|
uint32_t prev_fs_image_id = bind.fs_images[0].id;
|
||||||
|
if (pcmd.TextureId != 0) {
|
||||||
|
bind.fs_images[0].id = (uint32_t)(uintptr_t)pcmd.TextureId;
|
||||||
|
sg_apply_bindings(&bind);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int scissor_x = (int)(pcmd.ClipRect.x);
|
||||||
|
const int scissor_y = (int)(pcmd.ClipRect.y);
|
||||||
|
const int scissor_w = (int)(pcmd.ClipRect.z - pcmd.ClipRect.x);
|
||||||
|
const int scissor_h = (int)(pcmd.ClipRect.w - pcmd.ClipRect.y);
|
||||||
|
sg_apply_scissor_rect(scissor_x, scissor_y, scissor_w, scissor_h, true);
|
||||||
|
sg_draw(base_element, pcmd.ElemCount, 1);
|
||||||
|
|
||||||
|
if (pcmd.TextureId != 0) {
|
||||||
|
bind.fs_images[0].id = prev_fs_image_id;
|
||||||
|
sg_apply_bindings(&bind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
base_element += pcmd.ElemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sg_end_pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SokolImGuiState::Shutdown() {
|
||||||
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
|
ImGui_ImplGlfw_Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
SokolImGuiState gSokolImGuiState = {};
|
||||||
|
|
||||||
static void draw_imgui(ImDrawData*);
|
static void draw_imgui(ImDrawData*);
|
||||||
|
|
||||||
#define HANDMADE_MATH_NO_SSE
|
#define HANDMADE_MATH_NO_SSE
|
||||||
@@ -61,7 +254,6 @@ static void draw_imgui(ImDrawData*);
|
|||||||
#include "ozz/animation/runtime/skeleton.h"
|
#include "ozz/animation/runtime/skeleton.h"
|
||||||
#include "ozz/base/containers/vector.h"
|
#include "ozz/base/containers/vector.h"
|
||||||
#include "ozz/base/io/archive.h"
|
#include "ozz/base/io/archive.h"
|
||||||
#include "ozz/base/io/stream.h"
|
|
||||||
#include "ozz/base/log.h"
|
#include "ozz/base/log.h"
|
||||||
#include "ozz/base/maths/soa_transform.h"
|
#include "ozz/base/maths/soa_transform.h"
|
||||||
|
|
||||||
@@ -72,7 +264,7 @@ static struct {
|
|||||||
ozz::vector<ozz::math::SoaTransform> local_matrices;
|
ozz::vector<ozz::math::SoaTransform> local_matrices;
|
||||||
} ozz;
|
} ozz;
|
||||||
sg_pass_action pass_action = {};
|
sg_pass_action pass_action = {};
|
||||||
Camera camera;
|
Camera camera = {};
|
||||||
struct {
|
struct {
|
||||||
bool skeleton;
|
bool skeleton;
|
||||||
bool animation;
|
bool animation;
|
||||||
@@ -163,201 +355,92 @@ struct Viewport {
|
|||||||
|
|
||||||
this->pass = sg_make_pass(&offscreen_pass_desc);
|
this->pass = sg_make_pass(&offscreen_pass_desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Render() {
|
||||||
|
sg_begin_pass(pass, &pass_action);
|
||||||
|
sgl_load_pipeline(glpip);
|
||||||
|
sgl_draw();
|
||||||
|
sg_end_pass();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ApplicationConfig {
|
struct ApplicationConfig {
|
||||||
int window_position[2] = {100, 30};
|
constexpr static char TypeStr[] = "ApplicationConfig";
|
||||||
int window_size[2] = {1000, 600};
|
std::string type = TypeStr;
|
||||||
|
struct ScreenRect {
|
||||||
|
int position[2];
|
||||||
|
int size[2];
|
||||||
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(ScreenRect, position, size);
|
||||||
|
ScreenRect window = {.position = {100, 30}, .size = {1000, 600}};
|
||||||
|
|
||||||
struct GraphEditor {
|
struct GraphEditor {
|
||||||
|
ScreenRect rect = {.position = {20, 20}, .size = {800, 500}};
|
||||||
bool visible = false;
|
bool visible = false;
|
||||||
int position[2] = {20, 20};
|
|
||||||
int size[2] = {800, 500};
|
|
||||||
ax::NodeEditor::Config config = {};
|
ax::NodeEditor::Config config = {};
|
||||||
ax::NodeEditor::EditorContext* context = nullptr;
|
ax::NodeEditor::EditorContext* context = nullptr;
|
||||||
};
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(GraphEditor, rect, visible);
|
||||||
GraphEditor graph_editor;
|
GraphEditor graph_editor;
|
||||||
|
|
||||||
struct SkinnedMeshWidget {
|
struct WidgetRect {
|
||||||
|
ScreenRect rect;
|
||||||
bool visible = false;
|
bool visible = false;
|
||||||
int position[2] = {20, 20};
|
|
||||||
int size[2] = {800, 500};
|
|
||||||
};
|
};
|
||||||
SkinnedMeshWidget skinned_mesh_widget;
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(WidgetRect, rect, visible);
|
||||||
|
WidgetRect skinned_mesh_widget = {
|
||||||
|
.rect = {.position = {20, 20}, .size = {800, 500}},
|
||||||
|
.visible = false};
|
||||||
|
|
||||||
struct AnimationPlayerWidget {
|
WidgetRect animation_player_widget = {
|
||||||
bool visible = false;
|
.rect = {.position = {20, 20}, .size = {800, 500}},
|
||||||
int position[2] = {20, 20};
|
.visible = false};
|
||||||
int size[2] = {800, 500};
|
|
||||||
};
|
|
||||||
AnimationPlayerWidget animation_player_widget;
|
|
||||||
|
|
||||||
struct ViewportWidget {
|
WidgetRect viewport_widget;
|
||||||
bool visible = true;
|
|
||||||
int position[2] = {20, 20};
|
|
||||||
int size[2] = {800, 500};
|
|
||||||
};
|
|
||||||
ViewportWidget viewport_widget;
|
|
||||||
|
|
||||||
bool show_imgui_demo_window = false;
|
bool show_imgui_demo_window = false;
|
||||||
bool show_another_window = false;
|
bool show_another_window = false;
|
||||||
bool show_camera_widget = false;
|
bool show_camera_widget = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void to_json(nlohmann::json& j, const ApplicationConfig& config) {
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||||
j["type"] = "AnimTestbedConfig";
|
ApplicationConfig,
|
||||||
|
type,
|
||||||
|
window,
|
||||||
|
graph_editor,
|
||||||
|
skinned_mesh_widget,
|
||||||
|
animation_player_widget,
|
||||||
|
viewport_widget,
|
||||||
|
show_imgui_demo_window,
|
||||||
|
show_another_window,
|
||||||
|
show_camera_widget);
|
||||||
|
|
||||||
j["main_window"]["position"][0] = config.window_position[0];
|
struct ProjectConfig {
|
||||||
j["main_window"]["position"][1] = config.window_position[1];
|
static constexpr char TypeStr[] = "ProjectConfig";
|
||||||
|
std::string type = TypeStr;
|
||||||
|
std::string animation_library_file = "";
|
||||||
|
std::string skeleton_file = "";
|
||||||
|
std::string graph_resource_file = "";
|
||||||
|
struct ViewConfig {
|
||||||
|
int pos[2];
|
||||||
|
};
|
||||||
|
ViewConfig view_config = {.pos = {300, 200}};
|
||||||
|
};
|
||||||
|
|
||||||
j["main_window"]["size"][0] = config.window_size[0];
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ProjectConfig::ViewConfig, pos);
|
||||||
j["main_window"]["size"][1] = config.window_size[1];
|
|
||||||
|
|
||||||
j["graph_editor"]["visible"] = config.graph_editor.visible;
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||||
j["graph_editor"]["position"][0] = config.graph_editor.position[0];
|
ProjectConfig,
|
||||||
j["graph_editor"]["position"][1] = config.graph_editor.position[1];
|
type,
|
||||||
j["graph_editor"]["size"][0] = config.graph_editor.size[0];
|
animation_library_file,
|
||||||
j["graph_editor"]["size"][1] = config.graph_editor.size[1];
|
skeleton_file,
|
||||||
|
graph_resource_file,
|
||||||
j["skinned_mesh_widget"]["visible"] = config.skinned_mesh_widget.visible;
|
view_config);
|
||||||
j["skinned_mesh_widget"]["position"][0] =
|
|
||||||
config.skinned_mesh_widget.position[0];
|
|
||||||
j["skinned_mesh_widget"]["position"][1] =
|
|
||||||
config.skinned_mesh_widget.position[1];
|
|
||||||
j["skinned_mesh_widget"]["size"][0] = config.skinned_mesh_widget.size[0];
|
|
||||||
j["skinned_mesh_widget"]["size"][1] = config.skinned_mesh_widget.size[1];
|
|
||||||
|
|
||||||
j["animation_player_widget"]["visible"] =
|
|
||||||
config.animation_player_widget.visible;
|
|
||||||
j["animation_player_widget"]["position"][0] =
|
|
||||||
config.animation_player_widget.position[0];
|
|
||||||
j["animation_player_widget"]["position"][1] =
|
|
||||||
config.animation_player_widget.position[1];
|
|
||||||
j["animation_player_widget"]["size"][0] =
|
|
||||||
config.animation_player_widget.size[0];
|
|
||||||
j["animation_player_widget"]["size"][1] =
|
|
||||||
config.animation_player_widget.size[1];
|
|
||||||
|
|
||||||
j["viewport_widget"]["visible"] = config.viewport_widget.visible;
|
|
||||||
j["viewport_widget"]["position"][0] = config.viewport_widget.position[0];
|
|
||||||
j["viewport_widget"]["position"][1] = config.viewport_widget.position[1];
|
|
||||||
j["viewport_widget"]["size"][0] = config.viewport_widget.size[0];
|
|
||||||
j["viewport_widget"]["size"][1] = config.viewport_widget.size[1];
|
|
||||||
|
|
||||||
j["camera_widget"]["visible"] = config.show_camera_widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
void from_json(const nlohmann::json& j, ApplicationConfig& config) {
|
|
||||||
if (j.contains("main_window")) {
|
|
||||||
if (j["main_window"].contains("position")
|
|
||||||
and j["main_window"]["position"].size() == 2) {
|
|
||||||
config.window_position[0] = j["main_window"]["position"].at(0);
|
|
||||||
config.window_position[1] = j["main_window"]["position"].at(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["main_window"].contains("size")
|
|
||||||
and j["main_window"]["size"].size() == 2) {
|
|
||||||
config.window_size[0] = j["main_window"]["size"].at(0);
|
|
||||||
config.window_size[1] = j["main_window"]["size"].at(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.contains("graph_editor")) {
|
|
||||||
if (j["graph_editor"].contains("visible")) {
|
|
||||||
config.graph_editor.visible = j["graph_editor"]["visible"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["graph_editor"].contains("position")
|
|
||||||
and j["graph_editor"]["position"].size() == 2) {
|
|
||||||
config.graph_editor.position[0] = j["graph_editor"]["position"].at(0);
|
|
||||||
config.graph_editor.position[1] = j["graph_editor"]["position"].at(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["graph_editor"].contains("size")
|
|
||||||
and j["graph_editor"]["size"].size() == 2) {
|
|
||||||
config.graph_editor.size[0] = j["graph_editor"]["size"].at(0);
|
|
||||||
config.graph_editor.size[1] = j["graph_editor"]["size"].at(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.contains("skinned_mesh_widget")) {
|
|
||||||
if (j["skinned_mesh_widget"].contains("visible")) {
|
|
||||||
config.skinned_mesh_widget.visible = j["skinned_mesh_widget"]["visible"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["skinned_mesh_widget"].contains("position")
|
|
||||||
and j["skinned_mesh_widget"]["position"].size() == 2) {
|
|
||||||
config.skinned_mesh_widget.position[0] =
|
|
||||||
j["skinned_mesh_widget"]["position"].at(0);
|
|
||||||
config.skinned_mesh_widget.position[1] =
|
|
||||||
j["skinned_mesh_widget"]["position"].at(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["skinned_mesh_widget"].contains("size")
|
|
||||||
and j["skinned_mesh_widget"]["size"].size() == 2) {
|
|
||||||
config.skinned_mesh_widget.size[0] =
|
|
||||||
j["skinned_mesh_widget"]["size"].at(0);
|
|
||||||
config.skinned_mesh_widget.size[1] =
|
|
||||||
j["skinned_mesh_widget"]["size"].at(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.contains("animation_player_widget")) {
|
|
||||||
if (j["animation_player_widget"].contains("visible")) {
|
|
||||||
config.animation_player_widget.visible =
|
|
||||||
j["animation_player_widget"]["visible"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["animation_player_widget"].contains("position")
|
|
||||||
and j["animation_player_widget"]["position"].size() == 2) {
|
|
||||||
config.animation_player_widget.position[0] =
|
|
||||||
j["animation_player_widget"]["position"].at(0);
|
|
||||||
config.animation_player_widget.position[1] =
|
|
||||||
j["animation_player_widget"]["position"].at(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["animation_player_widget"].contains("size")
|
|
||||||
and j["animation_player_widget"]["size"].size() == 2) {
|
|
||||||
config.animation_player_widget.size[0] =
|
|
||||||
j["animation_player_widget"]["size"].at(0);
|
|
||||||
config.animation_player_widget.size[1] =
|
|
||||||
j["animation_player_widget"]["size"].at(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.contains("viewport_widget")) {
|
|
||||||
if (j["viewport_widget"].contains("visible")) {
|
|
||||||
config.viewport_widget.visible = j["viewport_widget"]["visible"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["viewport_widget"].contains("position")
|
|
||||||
and j["viewport_widget"]["position"].size() == 2) {
|
|
||||||
config.viewport_widget.position[0] =
|
|
||||||
j["viewport_widget"]["position"].at(0);
|
|
||||||
config.viewport_widget.position[1] =
|
|
||||||
j["viewport_widget"]["position"].at(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j["viewport_widget"].contains("size")
|
|
||||||
and j["viewport_widget"]["size"].size() == 2) {
|
|
||||||
config.viewport_widget.size[0] = j["viewport_widget"]["size"].at(0);
|
|
||||||
config.viewport_widget.size[1] = j["viewport_widget"]["size"].at(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.contains("camera_widget") and j["camera_widget"].contains("visible")) {
|
|
||||||
config.show_camera_widget = j["camera_widget"]["visible"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationConfig gApplicationConfig;
|
ApplicationConfig gApplicationConfig;
|
||||||
|
ProjectConfig gProjectConfig;
|
||||||
// io buffers for skeleton and animation data files, we know the max file size upfront
|
|
||||||
static uint8_t skel_data_buffer[4 * 1024];
|
|
||||||
static uint8_t anim_data_buffer[32 * 1024];
|
|
||||||
|
|
||||||
static void draw_grid();
|
static void draw_grid();
|
||||||
static void frame();
|
|
||||||
|
|
||||||
void handle_mouse(GLFWwindow* w, GuiInputState* io_input_state) {
|
void handle_mouse(GLFWwindow* w, GuiInputState* io_input_state) {
|
||||||
if (!glfwGetWindowAttrib(w, GLFW_FOCUSED)) {
|
if (!glfwGetWindowAttrib(w, GLFW_FOCUSED)) {
|
||||||
@@ -374,7 +457,7 @@ void handle_mouse(GLFWwindow* w, GuiInputState* io_input_state) {
|
|||||||
io_input_state->mousedX = 0;
|
io_input_state->mousedX = 0;
|
||||||
io_input_state->mousedY = 0;
|
io_input_state->mousedY = 0;
|
||||||
}
|
}
|
||||||
io_input_state->mouseX = int32_t(mouse_x);
|
io_input_state->mouseX = static_cast<int32_t>(mouse_x);
|
||||||
io_input_state->mouseY = int32_t(mouse_y);
|
io_input_state->mouseY = int32_t(mouse_y);
|
||||||
|
|
||||||
io_input_state->mouseButton = glfwGetMouseButton(w, 0)
|
io_input_state->mouseButton = glfwGetMouseButton(w, 0)
|
||||||
@@ -397,9 +480,9 @@ void load_application_config(const char* filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (application_config.value("type", "undefined") != "AnimTestbedConfig") {
|
if (application_config.value("type", "undefined") != "AnimTestbedConfig") {
|
||||||
std::cerr
|
std::cerr << "Invalid json object. Expected type '"
|
||||||
<< "Invalid json object. Expected type 'AnimTestbedConfig' but got '"
|
<< ApplicationConfig::TypeStr << "' but got '"
|
||||||
<< application_config["type"] << "'." << std::endl;
|
<< application_config["type"] << "'." << std::endl;
|
||||||
application_config["type"] = "AnimTestbedConfig";
|
application_config["type"] = "AnimTestbedConfig";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +516,6 @@ void sokol_logger(
|
|||||||
int main() {
|
int main() {
|
||||||
// window and GL context via GLFW and flextGL
|
// window and GL context via GLFW and flextGL
|
||||||
glfwInit();
|
glfwInit();
|
||||||
const char* glsl_version = "#version 130";
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
||||||
@@ -446,20 +528,20 @@ int main() {
|
|||||||
glfwSwapInterval(1);
|
glfwSwapInterval(1);
|
||||||
|
|
||||||
load_application_config("animtestbed_config.json");
|
load_application_config("animtestbed_config.json");
|
||||||
if (gApplicationConfig.window_position[0] != 0
|
if (gApplicationConfig.window.position[0] != 0
|
||||||
|| gApplicationConfig.window_position[1] != 0) {
|
|| gApplicationConfig.window.position[1] != 0) {
|
||||||
glfwSetWindowPos(
|
glfwSetWindowPos(
|
||||||
window,
|
window,
|
||||||
gApplicationConfig.window_position[0],
|
gApplicationConfig.window.position[0],
|
||||||
gApplicationConfig.window_position[1]);
|
gApplicationConfig.window.position[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gApplicationConfig.window_size[0] != 0
|
if (gApplicationConfig.window.size[0] != 0
|
||||||
|| gApplicationConfig.window_size[1] != 0) {
|
|| gApplicationConfig.window.size[1] != 0) {
|
||||||
glfwSetWindowSize(
|
glfwSetWindowSize(
|
||||||
window,
|
window,
|
||||||
gApplicationConfig.window_size[0],
|
gApplicationConfig.window.size[0],
|
||||||
gApplicationConfig.window_size[1]);
|
gApplicationConfig.window.size[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
NFD_Init();
|
NFD_Init();
|
||||||
@@ -479,19 +561,15 @@ int main() {
|
|||||||
sgl_setup(&sgldesc);
|
sgl_setup(&sgldesc);
|
||||||
sgl_defaults();
|
sgl_defaults();
|
||||||
|
|
||||||
// sgl_context_desc_t sgl_context_desc = {};
|
|
||||||
// sgl_context ctx = sgl_make_context(&sgl_context_desc);
|
|
||||||
|
|
||||||
SkinnedMeshResource skinned_mesh_resource;
|
SkinnedMeshResource skinned_mesh_resource;
|
||||||
skinned_mesh_resource.loadFromFile("../media/SampleSkinnedMesh.json");
|
skinned_mesh_resource.loadFromFile("../media/SampleSkinnedMesh.json");
|
||||||
|
|
||||||
SkinnedMesh skinned_mesh;
|
SkinnedMesh skinned_mesh;
|
||||||
skinned_mesh_resource.createInstance(skinned_mesh);
|
skinned_mesh_resource.createInstance(skinned_mesh);
|
||||||
skinned_mesh.SetCurrentAnimation(0);
|
|
||||||
|
|
||||||
AnimGraphBlendTree anim_graph;
|
AnimGraphBlendTree anim_graph;
|
||||||
AnimGraphContext anim_graph_context;
|
AnimGraphContext anim_graph_context;
|
||||||
AnimData anim_graph_output;
|
Pose anim_graph_output;
|
||||||
anim_graph_output.m_local_matrices.resize(
|
anim_graph_output.m_local_matrices.resize(
|
||||||
skinned_mesh.m_skeleton.num_soa_joints());
|
skinned_mesh.m_skeleton.num_soa_joints());
|
||||||
AnimGraphEditorClear();
|
AnimGraphEditorClear();
|
||||||
@@ -500,111 +578,8 @@ int main() {
|
|||||||
|
|
||||||
Camera_Init(&state.camera);
|
Camera_Init(&state.camera);
|
||||||
|
|
||||||
// setup Dear Imgui
|
gSokolImGuiState.Init(window);
|
||||||
ImGui::CreateContext();
|
|
||||||
ImGui::StyleColorsDark();
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
|
||||||
io.IniFilename = "ATPImgui.ini";
|
|
||||||
|
|
||||||
//io.Fonts->AddFontDefault();
|
|
||||||
ImFontConfig font_config;
|
|
||||||
font_config.OversampleH = 4;
|
|
||||||
font_config.OversampleV = 4;
|
|
||||||
font_config.GlyphExtraSpacing.x = 1.0f;
|
|
||||||
io.Fonts->AddFontFromMemoryCompressedTTF(
|
|
||||||
// roboto_medium_ttf_compressed_data,
|
|
||||||
// roboto_medium_ttf_compressed_size,
|
|
||||||
droid_sans_ttf_compressed_data,
|
|
||||||
droid_sans_ttf_compressed_size,
|
|
||||||
14,
|
|
||||||
&font_config);
|
|
||||||
|
|
||||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
|
||||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
|
||||||
|
|
||||||
// ImNodes
|
|
||||||
ImNodes::CreateContext();
|
|
||||||
|
|
||||||
// dynamic vertex- and index-buffers for imgui-generated geometry
|
|
||||||
sg_buffer_desc vbuf_desc = {};
|
|
||||||
vbuf_desc.usage = SG_USAGE_STREAM;
|
|
||||||
vbuf_desc.size = MaxVertices * sizeof(ImDrawVert);
|
|
||||||
bind.vertex_buffers[0] = sg_make_buffer(&vbuf_desc);
|
|
||||||
|
|
||||||
sg_buffer_desc ibuf_desc = {};
|
|
||||||
ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER;
|
|
||||||
ibuf_desc.usage = SG_USAGE_STREAM;
|
|
||||||
ibuf_desc.size = MaxIndices * sizeof(ImDrawIdx);
|
|
||||||
bind.index_buffer = sg_make_buffer(&ibuf_desc);
|
|
||||||
|
|
||||||
// font texture for imgui's default font
|
|
||||||
unsigned char* font_pixels;
|
|
||||||
int font_width, font_height;
|
|
||||||
io.Fonts->GetTexDataAsRGBA32(&font_pixels, &font_width, &font_height);
|
|
||||||
sg_image_desc img_desc = {};
|
|
||||||
img_desc.width = font_width;
|
|
||||||
img_desc.height = font_height;
|
|
||||||
img_desc.pixel_format = SG_PIXELFORMAT_RGBA8;
|
|
||||||
img_desc.wrap_u = SG_WRAP_CLAMP_TO_EDGE;
|
|
||||||
img_desc.wrap_v = SG_WRAP_CLAMP_TO_EDGE;
|
|
||||||
img_desc.data.subimage[0][0] =
|
|
||||||
sg_range{font_pixels, size_t(font_width * font_height * 4)};
|
|
||||||
bind.fs_images[0] = sg_make_image(&img_desc);
|
|
||||||
|
|
||||||
// shader object for imgui rendering
|
|
||||||
sg_shader_desc shd_desc = {};
|
|
||||||
auto& ub = shd_desc.vs.uniform_blocks[0];
|
|
||||||
ub.size = sizeof(vs_params_t);
|
|
||||||
ub.uniforms[0].name = "disp_size";
|
|
||||||
ub.uniforms[0].type = SG_UNIFORMTYPE_FLOAT2;
|
|
||||||
shd_desc.vs.source =
|
|
||||||
"#version 330\n"
|
|
||||||
"uniform vec2 disp_size;\n"
|
|
||||||
"layout(location=0) in vec2 position;\n"
|
|
||||||
"layout(location=1) in vec2 texcoord0;\n"
|
|
||||||
"layout(location=2) in vec4 color0;\n"
|
|
||||||
"out vec2 uv;\n"
|
|
||||||
"out vec4 color;\n"
|
|
||||||
"void main() {\n"
|
|
||||||
" gl_Position = vec4(((position/disp_size)-0.5)*vec2(2.0,-2.0), 0.5, "
|
|
||||||
"1.0);\n"
|
|
||||||
" uv = texcoord0;\n"
|
|
||||||
" color = color0;\n"
|
|
||||||
"}\n";
|
|
||||||
shd_desc.fs.images[0].name = "tex";
|
|
||||||
shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D;
|
|
||||||
shd_desc.fs.source =
|
|
||||||
"#version 330\n"
|
|
||||||
"uniform sampler2D tex;\n"
|
|
||||||
"in vec2 uv;\n"
|
|
||||||
"in vec4 color;\n"
|
|
||||||
"out vec4 frag_color;\n"
|
|
||||||
"void main() {\n"
|
|
||||||
" frag_color = texture(tex, uv) * color;\n"
|
|
||||||
"}\n";
|
|
||||||
sg_shader shd = sg_make_shader(&shd_desc);
|
|
||||||
|
|
||||||
// pipeline object for imgui rendering
|
|
||||||
sg_pipeline_desc pip_desc = {};
|
|
||||||
pip_desc.layout.buffers[0].stride = sizeof(ImDrawVert);
|
|
||||||
auto& attrs = pip_desc.layout.attrs;
|
|
||||||
attrs[0].format = SG_VERTEXFORMAT_FLOAT2;
|
|
||||||
attrs[1].format = SG_VERTEXFORMAT_FLOAT2;
|
|
||||||
attrs[2].format = SG_VERTEXFORMAT_UBYTE4N;
|
|
||||||
pip_desc.shader = shd;
|
|
||||||
pip_desc.index_type = SG_INDEXTYPE_UINT16;
|
|
||||||
pip_desc.colors[0].blend.enabled = true;
|
|
||||||
pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA;
|
|
||||||
pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
||||||
pip_desc.colors[0].write_mask = SG_COLORMASK_RGB;
|
|
||||||
pip_desc.sample_count = cMSAASampleCount;
|
|
||||||
pip_desc.label = "imgui-rendering";
|
|
||||||
pip = sg_make_pipeline(&pip_desc);
|
|
||||||
|
|
||||||
// initial clear color
|
|
||||||
pass_action.colors[0].action = SG_ACTION_CLEAR;
|
|
||||||
pass_action.colors[0].value = {0.1f, 0.1f, 0.1f, 1.0f};
|
|
||||||
|
|
||||||
Viewport offscreen_viewport;
|
Viewport offscreen_viewport;
|
||||||
|
|
||||||
@@ -635,13 +610,13 @@ int main() {
|
|||||||
// Update window state
|
// Update window state
|
||||||
glfwGetWindowPos(
|
glfwGetWindowPos(
|
||||||
window,
|
window,
|
||||||
&gApplicationConfig.window_position[0],
|
&gApplicationConfig.window.position[0],
|
||||||
&gApplicationConfig.window_position[1]);
|
&gApplicationConfig.window.position[1]);
|
||||||
|
|
||||||
glfwGetWindowSize(
|
glfwGetWindowSize(
|
||||||
window,
|
window,
|
||||||
&gApplicationConfig.window_size[0],
|
&gApplicationConfig.window.size[0],
|
||||||
&gApplicationConfig.window_size[1]);
|
&gApplicationConfig.window.size[1]);
|
||||||
|
|
||||||
int cur_width, cur_height;
|
int cur_width, cur_height;
|
||||||
glfwGetFramebufferSize(window, &cur_width, &cur_height);
|
glfwGetFramebufferSize(window, &cur_width, &cur_height);
|
||||||
@@ -769,14 +744,16 @@ int main() {
|
|||||||
ImGui::SetNextWindowPos(
|
ImGui::SetNextWindowPos(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.viewport_widget.position[0]),
|
gApplicationConfig.viewport_widget.rect.position[0]),
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.viewport_widget.position[1])),
|
gApplicationConfig.viewport_widget.rect.position[1])),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
ImGui::SetNextWindowSize(
|
ImGui::SetNextWindowSize(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
static_cast<float>(gApplicationConfig.viewport_widget.size[0]),
|
static_cast<float>(
|
||||||
static_cast<float>(gApplicationConfig.viewport_widget.size[1])),
|
gApplicationConfig.viewport_widget.rect.size[0]),
|
||||||
|
static_cast<float>(
|
||||||
|
gApplicationConfig.viewport_widget.rect.size[1])),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
ImGui::Begin("Viewport", &gApplicationConfig.viewport_widget.visible);
|
ImGui::Begin("Viewport", &gApplicationConfig.viewport_widget.visible);
|
||||||
@@ -791,9 +768,9 @@ int main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImVec2 viewport_widget_size = ImGui::GetWindowSize();
|
ImVec2 viewport_widget_size = ImGui::GetWindowSize();
|
||||||
gApplicationConfig.viewport_widget.size[0] =
|
gApplicationConfig.viewport_widget.rect.size[0] =
|
||||||
static_cast<int>(viewport_widget_size.x);
|
static_cast<int>(viewport_widget_size.x);
|
||||||
gApplicationConfig.viewport_widget.size[1] =
|
gApplicationConfig.viewport_widget.rect.size[1] =
|
||||||
static_cast<int>(viewport_widget_size.y);
|
static_cast<int>(viewport_widget_size.y);
|
||||||
|
|
||||||
ImGui::Text(
|
ImGui::Text(
|
||||||
@@ -841,16 +818,16 @@ int main() {
|
|||||||
ImGui::SetNextWindowPos(
|
ImGui::SetNextWindowPos(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.skinned_mesh_widget.position[0]),
|
gApplicationConfig.skinned_mesh_widget.rect.position[0]),
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.skinned_mesh_widget.position[1])),
|
gApplicationConfig.skinned_mesh_widget.rect.position[1])),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
ImGui::SetNextWindowSize(
|
ImGui::SetNextWindowSize(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.skinned_mesh_widget.size[0]),
|
gApplicationConfig.skinned_mesh_widget.rect.size[0]),
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.skinned_mesh_widget.size[1])),
|
gApplicationConfig.skinned_mesh_widget.rect.size[1])),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
ImGui::Begin(
|
ImGui::Begin(
|
||||||
@@ -858,15 +835,15 @@ int main() {
|
|||||||
&gApplicationConfig.skinned_mesh_widget.visible);
|
&gApplicationConfig.skinned_mesh_widget.visible);
|
||||||
|
|
||||||
ImVec2 skinned_mesh_widget_position = ImGui::GetWindowPos();
|
ImVec2 skinned_mesh_widget_position = ImGui::GetWindowPos();
|
||||||
gApplicationConfig.skinned_mesh_widget.position[0] =
|
gApplicationConfig.skinned_mesh_widget.rect.position[0] =
|
||||||
static_cast<int>(skinned_mesh_widget_position.x);
|
static_cast<int>(skinned_mesh_widget_position.x);
|
||||||
gApplicationConfig.skinned_mesh_widget.position[1] =
|
gApplicationConfig.skinned_mesh_widget.rect.position[1] =
|
||||||
static_cast<int>(skinned_mesh_widget_position.y);
|
static_cast<int>(skinned_mesh_widget_position.y);
|
||||||
|
|
||||||
ImVec2 skinned_mesh_widget_size = ImGui::GetWindowSize();
|
ImVec2 skinned_mesh_widget_size = ImGui::GetWindowSize();
|
||||||
gApplicationConfig.skinned_mesh_widget.size[0] =
|
gApplicationConfig.skinned_mesh_widget.rect.size[0] =
|
||||||
static_cast<int>(skinned_mesh_widget_size.x);
|
static_cast<int>(skinned_mesh_widget_size.x);
|
||||||
gApplicationConfig.skinned_mesh_widget.size[1] =
|
gApplicationConfig.skinned_mesh_widget.rect.size[1] =
|
||||||
static_cast<int>(skinned_mesh_widget_size.y);
|
static_cast<int>(skinned_mesh_widget_size.y);
|
||||||
|
|
||||||
SkinnedMeshWidget(&skinned_mesh);
|
SkinnedMeshWidget(&skinned_mesh);
|
||||||
@@ -877,17 +854,17 @@ int main() {
|
|||||||
if (gApplicationConfig.animation_player_widget.visible) {
|
if (gApplicationConfig.animation_player_widget.visible) {
|
||||||
ImGui::SetNextWindowPos(
|
ImGui::SetNextWindowPos(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
static_cast<float>(
|
static_cast<float>(gApplicationConfig.animation_player_widget
|
||||||
gApplicationConfig.animation_player_widget.position[0]),
|
.rect.position[0]),
|
||||||
static_cast<float>(
|
static_cast<float>(gApplicationConfig.animation_player_widget
|
||||||
gApplicationConfig.animation_player_widget.position[1])),
|
.rect.position[1])),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
ImGui::SetNextWindowSize(
|
ImGui::SetNextWindowSize(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.animation_player_widget.size[0]),
|
gApplicationConfig.animation_player_widget.rect.size[0]),
|
||||||
static_cast<float>(
|
static_cast<float>(
|
||||||
gApplicationConfig.animation_player_widget.size[1])),
|
gApplicationConfig.animation_player_widget.rect.size[1])),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
ImGui::Begin(
|
ImGui::Begin(
|
||||||
@@ -895,15 +872,15 @@ int main() {
|
|||||||
&gApplicationConfig.animation_player_widget.visible);
|
&gApplicationConfig.animation_player_widget.visible);
|
||||||
|
|
||||||
ImVec2 animation_player_widget_position = ImGui::GetWindowPos();
|
ImVec2 animation_player_widget_position = ImGui::GetWindowPos();
|
||||||
gApplicationConfig.animation_player_widget.position[0] =
|
gApplicationConfig.animation_player_widget.rect.position[0] =
|
||||||
static_cast<int>(animation_player_widget_position.x);
|
static_cast<int>(animation_player_widget_position.x);
|
||||||
gApplicationConfig.animation_player_widget.position[1] =
|
gApplicationConfig.animation_player_widget.rect.position[1] =
|
||||||
static_cast<int>(animation_player_widget_position.y);
|
static_cast<int>(animation_player_widget_position.y);
|
||||||
|
|
||||||
ImVec2 animation_player_widget_size = ImGui::GetWindowSize();
|
ImVec2 animation_player_widget_size = ImGui::GetWindowSize();
|
||||||
gApplicationConfig.animation_player_widget.size[0] =
|
gApplicationConfig.animation_player_widget.rect.size[0] =
|
||||||
static_cast<int>(animation_player_widget_size.x);
|
static_cast<int>(animation_player_widget_size.x);
|
||||||
gApplicationConfig.animation_player_widget.size[1] =
|
gApplicationConfig.animation_player_widget.rect.size[1] =
|
||||||
static_cast<int>(animation_player_widget_size.y);
|
static_cast<int>(animation_player_widget_size.y);
|
||||||
|
|
||||||
if (anim_graph.m_nodes.size() > 0) {
|
if (anim_graph.m_nodes.size() > 0) {
|
||||||
@@ -1005,13 +982,13 @@ int main() {
|
|||||||
if (gApplicationConfig.graph_editor.visible) {
|
if (gApplicationConfig.graph_editor.visible) {
|
||||||
ImGui::SetNextWindowPos(
|
ImGui::SetNextWindowPos(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
gApplicationConfig.graph_editor.position[0],
|
gApplicationConfig.graph_editor.rect.position[0],
|
||||||
gApplicationConfig.graph_editor.position[1]),
|
gApplicationConfig.graph_editor.rect.position[1]),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
ImGui::SetNextWindowSize(
|
ImGui::SetNextWindowSize(
|
||||||
ImVec2(
|
ImVec2(
|
||||||
gApplicationConfig.graph_editor.size[0],
|
gApplicationConfig.graph_editor.rect.size[0],
|
||||||
gApplicationConfig.graph_editor.size[1]),
|
gApplicationConfig.graph_editor.rect.size[1]),
|
||||||
ImGuiCond_FirstUseEver);
|
ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
ImGui::Begin(
|
ImGui::Begin(
|
||||||
@@ -1020,12 +997,14 @@ int main() {
|
|||||||
ImGuiWindowFlags_MenuBar);
|
ImGuiWindowFlags_MenuBar);
|
||||||
|
|
||||||
ImVec2 graph_editor_position = ImGui::GetWindowPos();
|
ImVec2 graph_editor_position = ImGui::GetWindowPos();
|
||||||
gApplicationConfig.graph_editor.position[0] = graph_editor_position.x;
|
gApplicationConfig.graph_editor.rect.position[0] =
|
||||||
gApplicationConfig.graph_editor.position[1] = graph_editor_position.y;
|
graph_editor_position.x;
|
||||||
|
gApplicationConfig.graph_editor.rect.position[1] =
|
||||||
|
graph_editor_position.y;
|
||||||
|
|
||||||
ImVec2 graph_editor_size = ImGui::GetWindowSize();
|
ImVec2 graph_editor_size = ImGui::GetWindowSize();
|
||||||
gApplicationConfig.graph_editor.size[0] = graph_editor_size.x;
|
gApplicationConfig.graph_editor.rect.size[0] = graph_editor_size.x;
|
||||||
gApplicationConfig.graph_editor.size[1] = graph_editor_size.y;
|
gApplicationConfig.graph_editor.rect.size[1] = graph_editor_size.y;
|
||||||
|
|
||||||
AnimGraphEditorUpdate(gApplicationConfig.graph_editor.context);
|
AnimGraphEditorUpdate(gApplicationConfig.graph_editor.context);
|
||||||
|
|
||||||
@@ -1038,16 +1017,12 @@ int main() {
|
|||||||
ImGui::ShowDemoWindow();
|
ImGui::ShowDemoWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
sg_begin_pass(offscreen_viewport.pass, &offscreen_viewport.pass_action);
|
offscreen_viewport.Render();
|
||||||
sgl_load_pipeline(offscreen_viewport.glpip);
|
|
||||||
sgl_draw();
|
|
||||||
sg_end_pass();
|
|
||||||
|
|
||||||
// Rendering of the main gui
|
// Rendering of the main gui
|
||||||
sg_begin_default_pass(&pass_action, cur_width, cur_height);
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
draw_imgui(ImGui::GetDrawData());
|
|
||||||
sg_end_pass();
|
gSokolImGuiState.Render(ImGui::GetDrawData(), cur_width, cur_height);
|
||||||
|
|
||||||
sg_commit();
|
sg_commit();
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
@@ -1060,8 +1035,7 @@ int main() {
|
|||||||
ax::NodeEditor::DestroyEditor(gApplicationConfig.graph_editor.context);
|
ax::NodeEditor::DestroyEditor(gApplicationConfig.graph_editor.context);
|
||||||
ImNodes::DestroyContext();
|
ImNodes::DestroyContext();
|
||||||
|
|
||||||
ImGui_ImplOpenGL3_Shutdown();
|
gSokolImGuiState.Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
|
||||||
|
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
@@ -1074,54 +1048,6 @@ int main() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoadSkeleton(const char* _filename, ozz::animation::Skeleton* _skeleton) {
|
|
||||||
assert(_filename && _skeleton);
|
|
||||||
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::animation::Skeleton>()) {
|
|
||||||
ozz::log::Err() << "Failed to load skeleton instance from file "
|
|
||||||
<< _filename << "." << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once the tag is validated, reading cannot fail.
|
|
||||||
archive >> *_skeleton;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LoadAnimation(
|
|
||||||
const char* _filename,
|
|
||||||
ozz::animation::Animation* _animation) {
|
|
||||||
assert(_filename && _animation);
|
|
||||||
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::animation::Animation>()) {
|
|
||||||
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;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
@@ -1172,70 +1098,3 @@ static void draw_grid(void) {
|
|||||||
|
|
||||||
sgl_end();
|
sgl_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw ImGui draw lists via sokol-gfx
|
|
||||||
void draw_imgui(ImDrawData* draw_data) {
|
|
||||||
assert(draw_data);
|
|
||||||
if (draw_data->CmdListsCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sg_image default_image = bind.fs_images[0];
|
|
||||||
|
|
||||||
// render the command list
|
|
||||||
sg_apply_pipeline(pip);
|
|
||||||
vs_params_t vs_params;
|
|
||||||
vs_params.disp_size.x = ImGui::GetIO().DisplaySize.x;
|
|
||||||
vs_params.disp_size.y = ImGui::GetIO().DisplaySize.y;
|
|
||||||
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE(vs_params));
|
|
||||||
for (int cl_index = 0; cl_index < draw_data->CmdListsCount; cl_index++) {
|
|
||||||
const ImDrawList* cl = draw_data->CmdLists[cl_index];
|
|
||||||
|
|
||||||
// append vertices and indices to buffers, record start offsets in resource binding struct
|
|
||||||
const uint32_t vtx_size = cl->VtxBuffer.size() * sizeof(ImDrawVert);
|
|
||||||
const uint32_t idx_size = cl->IdxBuffer.size() * sizeof(ImDrawIdx);
|
|
||||||
const uint32_t vb_offset = sg_append_buffer(
|
|
||||||
bind.vertex_buffers[0],
|
|
||||||
{&cl->VtxBuffer.front(), vtx_size});
|
|
||||||
const uint32_t ib_offset =
|
|
||||||
sg_append_buffer(bind.index_buffer, {&cl->IdxBuffer.front(), idx_size});
|
|
||||||
/* don't render anything if the buffer is in overflow state (this is also
|
|
||||||
checked internally in sokol_gfx, draw calls that attempt from
|
|
||||||
overflowed buffers will be silently dropped)
|
|
||||||
*/
|
|
||||||
if (sg_query_buffer_overflow(bind.vertex_buffers[0])
|
|
||||||
|| sg_query_buffer_overflow(bind.index_buffer)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind.vertex_buffer_offsets[0] = vb_offset;
|
|
||||||
bind.index_buffer_offset = ib_offset;
|
|
||||||
sg_apply_bindings(&bind);
|
|
||||||
|
|
||||||
int base_element = 0;
|
|
||||||
for (const ImDrawCmd& pcmd : cl->CmdBuffer) {
|
|
||||||
if (pcmd.UserCallback) {
|
|
||||||
pcmd.UserCallback(cl, &pcmd);
|
|
||||||
} else {
|
|
||||||
uint32_t prev_fs_image_id = bind.fs_images[0].id;
|
|
||||||
if (pcmd.TextureId != 0) {
|
|
||||||
bind.fs_images[0].id = (uint32_t)(uintptr_t)pcmd.TextureId;
|
|
||||||
sg_apply_bindings(&bind);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int scissor_x = (int)(pcmd.ClipRect.x);
|
|
||||||
const int scissor_y = (int)(pcmd.ClipRect.y);
|
|
||||||
const int scissor_w = (int)(pcmd.ClipRect.z - pcmd.ClipRect.x);
|
|
||||||
const int scissor_h = (int)(pcmd.ClipRect.w - pcmd.ClipRect.y);
|
|
||||||
sg_apply_scissor_rect(scissor_x, scissor_y, scissor_w, scissor_h, true);
|
|
||||||
sg_draw(base_element, pcmd.ElemCount, 1);
|
|
||||||
|
|
||||||
if (pcmd.TextureId != 0) {
|
|
||||||
bind.fs_images[0].id = prev_fs_image_id;
|
|
||||||
sg_apply_bindings(&bind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
base_element += pcmd.ElemCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Created by martin on 11.04.25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "3rdparty/json/json.hpp"
|
||||||
|
#include "AnimGraph/AnimLibrary.h"
|
||||||
|
#include "TestAnimData.h"
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
using namespace nlohmann;
|
||||||
|
|
||||||
|
TEST_CASE("Serialize AnimLibrary", "[AnimLibrary]") {
|
||||||
|
AnimLibrary library;
|
||||||
|
|
||||||
|
TestAnimData::SingleBoneSkeleton single_bone_testdata;
|
||||||
|
|
||||||
|
REQUIRE(library.AddAnimationFile(
|
||||||
|
"translation_x",
|
||||||
|
single_bone_testdata.animation_translate_x_resource.m_filename));
|
||||||
|
REQUIRE(library.AddAnimationFile(
|
||||||
|
"translation_y",
|
||||||
|
single_bone_testdata.animation_translate_y_resource.m_filename));
|
||||||
|
|
||||||
|
// We're not actually doing anything with the animations, however loading them here ensures that
|
||||||
|
// when running valgrind a memory leak is detected.
|
||||||
|
library.LoadAnimations();
|
||||||
|
|
||||||
|
// serialize
|
||||||
|
json library_data;
|
||||||
|
library_data = library;
|
||||||
|
|
||||||
|
// deserialize
|
||||||
|
AnimLibrary library_deserialized;
|
||||||
|
library_deserialized = library_data;
|
||||||
|
|
||||||
|
CHECK(library_deserialized.mAnimations.size() == library.mAnimations.size());
|
||||||
|
for (AnimLibrary::AnimationResourceMap::const_iterator iter =
|
||||||
|
library.mAnimations.cbegin();
|
||||||
|
iter != library.mAnimations.cend();
|
||||||
|
++iter) {
|
||||||
|
CHECK(
|
||||||
|
library_deserialized.mAnimations.find(iter->first)
|
||||||
|
!= library_deserialized.mAnimations.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
+124
-42
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "AnimGraph/AnimGraphBlendTree.h"
|
#include "AnimGraph/AnimGraphBlendTree.h"
|
||||||
#include "AnimGraph/AnimGraphResource.h"
|
#include "AnimGraph/AnimGraphResource.h"
|
||||||
#include "AnimGraphEditor/AnimGraphEditor.h"
|
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
#include "ozz/animation/offline/animation_builder.h"
|
#include "ozz/animation/offline/animation_builder.h"
|
||||||
#include "ozz/animation/offline/raw_animation.h"
|
#include "ozz/animation/offline/raw_animation.h"
|
||||||
@@ -14,16 +13,20 @@
|
|||||||
#include "ozz/animation/runtime/sampling_job.h"
|
#include "ozz/animation/runtime/sampling_job.h"
|
||||||
#include "ozz/animation/runtime/skeleton.h"
|
#include "ozz/animation/runtime/skeleton.h"
|
||||||
#include "ozz/base/io/archive.h"
|
#include "ozz/base/io/archive.h"
|
||||||
#include "ozz/base/io/stream.h"
|
|
||||||
#include "ozz/base/log.h"
|
#include "ozz/base/log.h"
|
||||||
#include "ozz/base/maths/soa_transform.h"
|
#include "ozz/base/maths/soa_transform.h"
|
||||||
|
|
||||||
struct SimpleAnimFixture {
|
struct SimpleAnimFixture {
|
||||||
ozz::unique_ptr<ozz::animation::Skeleton> skeleton = nullptr;
|
ozz::unique_ptr<ozz::animation::Skeleton> skeleton = nullptr;
|
||||||
|
|
||||||
ozz::animation::offline::RawAnimation raw_animation_translation_x;
|
ozz::animation::offline::RawAnimation raw_animation_translation_x;
|
||||||
ozz::unique_ptr<ozz::animation::Animation> animation_translate_x = nullptr;
|
ozz::unique_ptr<ozz::animation::Animation> animation_translate_x = nullptr;
|
||||||
|
SyncTrack animation_translate_x_sync_track = {};
|
||||||
|
|
||||||
ozz::animation::offline::RawAnimation raw_animation_translation_y;
|
ozz::animation::offline::RawAnimation raw_animation_translation_y;
|
||||||
ozz::unique_ptr<ozz::animation::Animation> animation_translate_y = nullptr;
|
ozz::unique_ptr<ozz::animation::Animation> animation_translate_y = nullptr;
|
||||||
|
SyncTrack animation_translate_y_sync_track = {};
|
||||||
|
|
||||||
ozz::vector<ozz::math::SoaTransform> animation_output;
|
ozz::vector<ozz::math::SoaTransform> animation_output;
|
||||||
ozz::animation::SamplingJob::Context sampling_context;
|
ozz::animation::SamplingJob::Context sampling_context;
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ struct SimpleAnimFixture {
|
|||||||
bone0_translations.push_back(translation_key);
|
bone0_translations.push_back(translation_key);
|
||||||
|
|
||||||
translation_key.time = 1.f;
|
translation_key.time = 1.f;
|
||||||
translation_key.value = ozz::math::Float3(1.f, 0.f, 9.f);
|
translation_key.value = ozz::math::Float3(1.f, 0.f, 0.f);
|
||||||
bone0_translations.push_back(translation_key);
|
bone0_translations.push_back(translation_key);
|
||||||
|
|
||||||
bone0_track.translations = bone0_translations;
|
bone0_track.translations = bone0_translations;
|
||||||
@@ -123,18 +126,18 @@ TEST_CASE_METHOD(
|
|||||||
sampled_translation.z[0] == Approx(translation_key.value.z).margin(0.01));
|
sampled_translation.z[0] == Approx(translation_key.value.z).margin(0.01));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("AnimDataPlacementNew", "[AnimGraphEval]") {
|
TEST_CASE("PosePlacementNew", "[AnimGraphEval]") {
|
||||||
int anim_data_size = sizeof(AnimData);
|
int pose_size = sizeof(Pose);
|
||||||
char* buf = new char[anim_data_size];
|
char* buf = new char[pose_size];
|
||||||
|
|
||||||
AnimData* anim_data_newed = new AnimData;
|
Pose* pose_newed = new Pose;
|
||||||
anim_data_newed->m_local_matrices.resize(2);
|
pose_newed->m_local_matrices.resize(2);
|
||||||
delete anim_data_newed;
|
delete pose_newed;
|
||||||
|
|
||||||
AnimData* anim_data_ptr = new (buf) AnimData;
|
Pose* pose_ptr = new (buf) Pose;
|
||||||
anim_data_ptr->m_local_matrices.resize(4);
|
pose_ptr->m_local_matrices.resize(4);
|
||||||
anim_data_ptr->m_local_matrices.resize(0);
|
pose_ptr->m_local_matrices.resize(0);
|
||||||
anim_data_ptr->m_local_matrices.vector::~vector();
|
pose_ptr->m_local_matrices.vector::~vector();
|
||||||
|
|
||||||
delete[] buf;
|
delete[] buf;
|
||||||
}
|
}
|
||||||
@@ -172,10 +175,6 @@ TEST_CASE_METHOD(
|
|||||||
AnimNodeResource* blend_node = blend_tree_resource->GetNode(blend_node_index);
|
AnimNodeResource* blend_node = blend_tree_resource->GetNode(blend_node_index);
|
||||||
blend_node->m_name = "BlendWalkRun";
|
blend_node->m_name = "BlendWalkRun";
|
||||||
|
|
||||||
// Setup graph outputs and inputs
|
|
||||||
AnimNodeResource* graph_output_node =
|
|
||||||
blend_tree_resource->GetGraphOutputNode();
|
|
||||||
|
|
||||||
blend_tree_resource->RegisterBlendTreeInputSocket<float>("GraphFloatInput");
|
blend_tree_resource->RegisterBlendTreeInputSocket<float>("GraphFloatInput");
|
||||||
|
|
||||||
// Wire up nodes
|
// Wire up nodes
|
||||||
@@ -199,8 +198,16 @@ TEST_CASE_METHOD(
|
|||||||
// Prepare animation maps
|
// Prepare animation maps
|
||||||
AnimGraphContext graph_context;
|
AnimGraphContext graph_context;
|
||||||
graph_context.m_skeleton = skeleton.get();
|
graph_context.m_skeleton = skeleton.get();
|
||||||
graph_context.m_animation_map["trans_x"] = animation_translate_x.get();
|
graph_context.m_animation_map["trans_x"] = {
|
||||||
graph_context.m_animation_map["trans_y"] = animation_translate_y.get();
|
"trans_x",
|
||||||
|
"",
|
||||||
|
animation_translate_x.get(),
|
||||||
|
animation_translate_x_sync_track};
|
||||||
|
graph_context.m_animation_map["trans_y"] = {
|
||||||
|
"trans_y",
|
||||||
|
"",
|
||||||
|
animation_translate_y.get(),
|
||||||
|
animation_translate_y_sync_track};
|
||||||
|
|
||||||
// Instantiate graph
|
// Instantiate graph
|
||||||
AnimGraphBlendTree blend_tree;
|
AnimGraphBlendTree blend_tree;
|
||||||
@@ -211,34 +218,109 @@ TEST_CASE_METHOD(
|
|||||||
// Get runtime graph inputs and outputs
|
// Get runtime graph inputs and outputs
|
||||||
float graph_float_input = 0.f;
|
float graph_float_input = 0.f;
|
||||||
blend_tree.SetInput("GraphFloatInput", &graph_float_input);
|
blend_tree.SetInput("GraphFloatInput", &graph_float_input);
|
||||||
|
CHECK(blend_tree.GetGraphInputs().size() == 1);
|
||||||
|
CHECK(
|
||||||
|
*blend_tree.GetGraphInputs()[0].m_reference.ptr_ptr
|
||||||
|
== &graph_float_input);
|
||||||
|
|
||||||
AnimData graph_anim_output;
|
Pose graph_anim_output;
|
||||||
graph_anim_output.m_local_matrices.resize(skeleton->num_joints());
|
graph_anim_output.m_local_matrices.resize(skeleton->num_joints());
|
||||||
blend_tree.SetOutput("GraphOutput", &graph_anim_output);
|
blend_tree.SetOutput("Output", &graph_anim_output);
|
||||||
|
|
||||||
// Evaluate graph
|
CHECK(blend_tree.GetGraphOutputs().size() == 1);
|
||||||
graph_float_input = 0.1f;
|
CHECK(
|
||||||
|
*blend_tree.GetGraphOutputs()[0].m_reference.ptr_ptr
|
||||||
|
== &graph_anim_output);
|
||||||
|
|
||||||
blend_tree.StartUpdateTick();
|
WHEN("Blend Weight == 0.") {
|
||||||
blend_tree.MarkActiveInputs(blend_tree.GetGraphOutputConnections());
|
// Evaluate graph
|
||||||
|
graph_float_input = 0.f;
|
||||||
|
|
||||||
CHECK(
|
blend_tree.StartUpdateTick();
|
||||||
blend_tree.m_nodes[trans_x_node_index]->m_state
|
blend_tree.MarkActiveInputs({});
|
||||||
== AnimNodeEvalState::Activated);
|
|
||||||
CHECK(
|
|
||||||
blend_tree.m_nodes[trans_y_node_index]->m_state
|
|
||||||
== AnimNodeEvalState::Activated);
|
|
||||||
CHECK(
|
|
||||||
blend_tree.m_nodes[blend_node_index]->m_state
|
|
||||||
== AnimNodeEvalState::Activated);
|
|
||||||
|
|
||||||
blend_tree.UpdateTime(0.0, 0.5f);
|
THEN("Only Blend2 and first input of Blend2 node is active.") {
|
||||||
blend_tree.Evaluate(graph_context);
|
CHECK(
|
||||||
|
blend_tree.m_nodes[trans_x_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Activated);
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[trans_y_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Deactivated);
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[blend_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Activated);
|
||||||
|
}
|
||||||
|
|
||||||
CHECK(
|
blend_tree.UpdateTime(0.0, 0.5f);
|
||||||
graph_anim_output.m_local_matrices[0].translation.x[0]
|
blend_tree.Evaluate(graph_context);
|
||||||
== Approx(0.5).margin(0.1));
|
|
||||||
CHECK(
|
CHECK(
|
||||||
graph_anim_output.m_local_matrices[0].translation.y[0]
|
graph_anim_output.m_local_matrices[0].translation.x[0]
|
||||||
== Approx(0.05).margin(0.01));
|
== Approx(0.5).margin(0.01));
|
||||||
|
CHECK(
|
||||||
|
graph_anim_output.m_local_matrices[0].translation.y[0]
|
||||||
|
== Approx(0.0).margin(0.01));
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Blend Weight 0.1") {
|
||||||
|
// Evaluate graph
|
||||||
|
graph_float_input = 0.1f;
|
||||||
|
|
||||||
|
blend_tree.StartUpdateTick();
|
||||||
|
blend_tree.MarkActiveInputs({});
|
||||||
|
|
||||||
|
THEN("All nodes are active.") {
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[trans_x_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Activated);
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[trans_y_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Activated);
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[blend_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Activated);
|
||||||
|
}
|
||||||
|
|
||||||
|
blend_tree.UpdateTime(0.0, 0.5f);
|
||||||
|
blend_tree.Evaluate(graph_context);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
graph_anim_output.m_local_matrices[0].translation.x[0]
|
||||||
|
== Approx(0.45).margin(0.01));
|
||||||
|
CHECK(
|
||||||
|
graph_anim_output.m_local_matrices[0].translation.y[0]
|
||||||
|
== Approx(0.05).margin(0.01));
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Blend Weight 1.") {
|
||||||
|
// Evaluate graph
|
||||||
|
graph_float_input = 1.f;
|
||||||
|
|
||||||
|
blend_tree.StartUpdateTick();
|
||||||
|
blend_tree.MarkActiveInputs({});
|
||||||
|
|
||||||
|
THEN("Only Blend2 and second input of Blend2 are active.") {
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[trans_x_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Deactivated);
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[trans_y_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Activated);
|
||||||
|
CHECK(
|
||||||
|
blend_tree.m_nodes[blend_node_index]->m_state
|
||||||
|
== AnimNodeEvalState::Activated);
|
||||||
|
}
|
||||||
|
|
||||||
|
blend_tree.UpdateTime(0.0, 0.5f);
|
||||||
|
blend_tree.Evaluate(graph_context);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
graph_anim_output.m_local_matrices[0].translation.x[0]
|
||||||
|
== Approx(0.).margin(0.01));
|
||||||
|
CHECK(
|
||||||
|
graph_anim_output.m_local_matrices[0].translation.y[0]
|
||||||
|
== Approx(0.5).margin(0.01));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete blend_tree_resource;
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ class SimpleAnimSamplerBlendTreeResourceFixture
|
|||||||
std::string("media/Walking-loop.ozz"));
|
std::string("media/Walking-loop.ozz"));
|
||||||
|
|
||||||
AnimNodeResource* graph_node = blend_tree_resource->GetGraphOutputNode();
|
AnimNodeResource* graph_node = blend_tree_resource->GetGraphOutputNode();
|
||||||
graph_node->m_virtual_socket_accessor->RegisterInput<AnimData>(
|
graph_node->m_virtual_socket_accessor->RegisterInput<Pose>(
|
||||||
"GraphOutput",
|
"GraphOutput",
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
@@ -161,14 +161,14 @@ class EmbeddedBlendTreeGraphResource : public BlendTreeResourceFixture {
|
|||||||
// Embedded: outputs
|
// Embedded: outputs
|
||||||
AnimNodeResource* embedded_outputs =
|
AnimNodeResource* embedded_outputs =
|
||||||
embedded_blend_tree_resource->GetGraphOutputNode();
|
embedded_blend_tree_resource->GetGraphOutputNode();
|
||||||
embedded_outputs->m_virtual_socket_accessor->RegisterInput<AnimData>(
|
embedded_outputs->m_virtual_socket_accessor->RegisterInput<Pose>(
|
||||||
"AnimOutput",
|
"AnimOutput",
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
// Embedded: inputs
|
// Embedded: inputs
|
||||||
AnimNodeResource* embedded_inputs =
|
AnimNodeResource* embedded_inputs =
|
||||||
embedded_blend_tree_resource->GetGraphInputNode();
|
embedded_blend_tree_resource->GetGraphInputNode();
|
||||||
embedded_inputs->m_virtual_socket_accessor->RegisterOutput<AnimData>(
|
embedded_inputs->m_virtual_socket_accessor->RegisterOutput<Pose>(
|
||||||
"AnimInput",
|
"AnimInput",
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ class EmbeddedTreeBlend2GraphResource : public BlendTreeResourceFixture {
|
|||||||
embedded_blend_tree_resource->m_name = "EmbeddedTreeBlend2GraphResource";
|
embedded_blend_tree_resource->m_name = "EmbeddedTreeBlend2GraphResource";
|
||||||
|
|
||||||
// Embedded: inputs
|
// Embedded: inputs
|
||||||
embedded_blend_tree_resource->RegisterBlendTreeInputSocket<AnimData>(
|
embedded_blend_tree_resource->RegisterBlendTreeInputSocket<Pose>(
|
||||||
"AnimInput");
|
"AnimInput");
|
||||||
embedded_blend_tree_resource->RegisterBlendTreeInputSocket<float>(
|
embedded_blend_tree_resource->RegisterBlendTreeInputSocket<float>(
|
||||||
"BlendWeight");
|
"BlendWeight");
|
||||||
@@ -482,7 +482,7 @@ TEST_CASE_METHOD(
|
|||||||
REQUIRE(anim_sampler_walk->m_animation != nullptr);
|
REQUIRE(anim_sampler_walk->m_animation != nullptr);
|
||||||
|
|
||||||
// Ensure that outputs are properly propagated.
|
// Ensure that outputs are properly propagated.
|
||||||
AnimData output;
|
Pose output;
|
||||||
output.m_local_matrices.resize(skeleton.num_soa_joints());
|
output.m_local_matrices.resize(skeleton.num_soa_joints());
|
||||||
anim_graph_blend_tree.SetOutput(
|
anim_graph_blend_tree.SetOutput(
|
||||||
AnimGraphResource::DefaultAnimOutput,
|
AnimGraphResource::DefaultAnimOutput,
|
||||||
@@ -764,9 +764,8 @@ TEST_CASE_METHOD(
|
|||||||
CHECK(blend2_instance->i_input0 == anim_sampler_walk->o_output);
|
CHECK(blend2_instance->i_input0 == anim_sampler_walk->o_output);
|
||||||
CHECK(blend2_instance->i_input1 == anim_sampler_run->o_output);
|
CHECK(blend2_instance->i_input1 == anim_sampler_run->o_output);
|
||||||
|
|
||||||
AnimData* graph_output =
|
Pose* graph_output = blend_tree_graph.GetOutputPtr<Pose>(
|
||||||
static_cast<AnimData*>(blend_tree_graph.GetOutputPtr<AnimData>(
|
AnimGraphResource::DefaultAnimOutput);
|
||||||
AnimGraphResource::DefaultAnimOutput));
|
|
||||||
|
|
||||||
CHECK(
|
CHECK(
|
||||||
graph_output->m_local_matrices.size()
|
graph_output->m_local_matrices.size()
|
||||||
|
|||||||
@@ -8,26 +8,34 @@
|
|||||||
|
|
||||||
TEST_CASE("Descriptor Access", "[NodeDescriptorTests]") {
|
TEST_CASE("Descriptor Access", "[NodeDescriptorTests]") {
|
||||||
Blend2Node blend2Node;
|
Blend2Node blend2Node;
|
||||||
NodeDescriptor<Blend2Node> blend2Descriptor (&blend2Node);
|
NodeDescriptor<Blend2Node> blend2Descriptor(&blend2Node);
|
||||||
CHECK(blend2Descriptor.m_inputs.size() == 3);
|
CHECK(blend2Descriptor.m_inputs.size() == 3);
|
||||||
CHECK(*blend2Descriptor.m_inputs[0].m_reference.ptr_ptr == blend2Node.i_input0);
|
CHECK(
|
||||||
CHECK(*blend2Descriptor.m_inputs[1].m_reference.ptr_ptr == blend2Node.i_input1);
|
*blend2Descriptor.m_inputs[0].m_reference.ptr_ptr == blend2Node.i_input0);
|
||||||
CHECK(*blend2Descriptor.m_inputs[2].m_reference.ptr_ptr == blend2Node.i_blend_weight);
|
CHECK(
|
||||||
|
*blend2Descriptor.m_inputs[1].m_reference.ptr_ptr == blend2Node.i_input1);
|
||||||
|
CHECK(
|
||||||
|
*blend2Descriptor.m_inputs[2].m_reference.ptr_ptr
|
||||||
|
== blend2Node.i_blend_weight);
|
||||||
|
|
||||||
CHECK(blend2Descriptor.m_inputs[0].m_type_size == sizeof(AnimData));
|
CHECK(blend2Descriptor.m_inputs[0].m_type_size == sizeof(Pose));
|
||||||
CHECK(blend2Descriptor.m_inputs[2].m_type_size == 4);
|
CHECK(blend2Descriptor.m_inputs[2].m_type_size == 4);
|
||||||
|
|
||||||
CHECK(blend2Descriptor.m_outputs.size() == 1);
|
CHECK(blend2Descriptor.m_outputs.size() == 1);
|
||||||
CHECK(*blend2Descriptor.m_outputs[0].m_reference.ptr_ptr == blend2Node.o_output);
|
CHECK(
|
||||||
|
*blend2Descriptor.m_outputs[0].m_reference.ptr_ptr
|
||||||
|
== blend2Node.o_output);
|
||||||
|
|
||||||
CHECK(blend2Descriptor.m_properties.size() == 1);
|
CHECK(blend2Descriptor.m_properties.size() == 1);
|
||||||
CHECK(blend2Descriptor.m_properties[0].m_reference.ptr == &blend2Node.m_sync_blend);
|
CHECK(
|
||||||
|
blend2Descriptor.m_properties[0].m_reference.ptr
|
||||||
|
== &blend2Node.m_sync_blend);
|
||||||
|
|
||||||
// Check we can properly update inputs
|
// Check we can properly update inputs
|
||||||
CHECK(blend2Node.i_input0 == nullptr);
|
CHECK(blend2Node.i_input0 == nullptr);
|
||||||
AnimData some_anim_data;
|
Pose pose;
|
||||||
blend2Descriptor.SetInput("Input0", &some_anim_data);
|
blend2Descriptor.SetInput("Input0", &pose);
|
||||||
CHECK(blend2Node.i_input0 == &some_anim_data);
|
CHECK(blend2Node.i_input0 == &pose);
|
||||||
|
|
||||||
// Check we properly can set properties
|
// Check we properly can set properties
|
||||||
CHECK(blend2Node.m_sync_blend == false);
|
CHECK(blend2Node.m_sync_blend == false);
|
||||||
@@ -41,6 +49,7 @@ TEST_CASE("Descriptor Access", "[NodeDescriptorTests]") {
|
|||||||
blend2Descriptor.UpdateFlags();
|
blend2Descriptor.UpdateFlags();
|
||||||
Socket* weight_input_socket = blend2Descriptor.GetInputSocket("Weight");
|
Socket* weight_input_socket = blend2Descriptor.GetInputSocket("Weight");
|
||||||
CHECK(weight_input_socket != nullptr);
|
CHECK(weight_input_socket != nullptr);
|
||||||
CHECK(weight_input_socket->m_flags & SocketFlagAffectsTime == SocketFlagAffectsTime);
|
CHECK(
|
||||||
|
weight_input_socket->m_flags
|
||||||
|
& SocketFlagAffectsTime == SocketFlagAffectsTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+156
-125
@@ -2,201 +2,232 @@
|
|||||||
// Created by martin on 16.11.21.
|
// Created by martin on 16.11.21.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "SyncTrack.h"
|
#include "AnimGraph/SyncTrack.h"
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
|
|
||||||
TEST_CASE("Basic", "[SyncTrack]") {
|
TEST_CASE("Basic", "[SyncTrack]") {
|
||||||
SyncTrack track_A;
|
SyncTrack track_a;
|
||||||
track_A.m_num_intervals = 2;
|
track_a.m_num_intervals = 2;
|
||||||
track_A.m_duration = 2.0;
|
track_a.m_duration = 2.0;
|
||||||
track_A.m_interval_start[0] = 0.f;
|
track_a.m_interval_start_ratio[0] = 0.f;
|
||||||
track_A.m_interval_ratio[0] = 0.7;
|
track_a.m_interval_duration_ratio[0] = 0.7;
|
||||||
track_A.m_interval_start[1] = 0.7f;
|
track_a.m_interval_start_ratio[1] = 0.7f;
|
||||||
track_A.m_interval_ratio[1] = 0.3;
|
track_a.m_interval_duration_ratio[1] = 0.3;
|
||||||
|
|
||||||
SyncTrack track_B;
|
SyncTrack track_b;
|
||||||
track_B.m_num_intervals = 2;
|
track_b.m_num_intervals = 2;
|
||||||
track_B.m_duration = 1.5;
|
track_b.m_duration = 1.5;
|
||||||
track_B.m_interval_start[0] = 0.0f;
|
track_b.m_interval_start_ratio[0] = 0.0f;
|
||||||
track_B.m_interval_ratio[0] = 0.6;
|
track_b.m_interval_duration_ratio[0] = 0.6;
|
||||||
track_B.m_interval_start[1] = 0.6f;
|
track_b.m_interval_start_ratio[1] = 0.6f;
|
||||||
track_B.m_interval_ratio[1] = 0.4;
|
track_b.m_interval_duration_ratio[1] = 0.4;
|
||||||
|
|
||||||
WHEN("Calculating sync time of track_B at 0.5 duration") {
|
WHEN("Calculating sync time of track_B at 0.5 duration") {
|
||||||
float sync_time_at_0_75 =
|
float sync_time_at_0_75 =
|
||||||
track_B.CalcSyncFromAbsTime(0.5 * track_B.m_duration);
|
track_b.CalcSyncFromAbsTime(0.5 * track_b.m_duration);
|
||||||
REQUIRE(sync_time_at_0_75 == Catch::Detail::Approx(0.83333));
|
REQUIRE(sync_time_at_0_75 == Catch::Detail::Approx(0.83333));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Calculating sync time of track_B at 0.6 duration") {
|
WHEN("Calculating sync time of track_B at 0.6 duration") {
|
||||||
float sync_time_at_0_6 =
|
float sync_time_at_0_6 =
|
||||||
track_B.CalcSyncFromAbsTime(0.6 * track_B.m_duration);
|
track_b.CalcSyncFromAbsTime(0.6 * track_b.m_duration);
|
||||||
REQUIRE(sync_time_at_0_6 == Catch::Detail::Approx(1.0));
|
REQUIRE(sync_time_at_0_6 == Catch::Detail::Approx(1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Calculating sync time of track_B at 0.7 duration") {
|
WHEN("Calculating sync time of track_B at 0.7 duration") {
|
||||||
float sync_time_at_0_7 =
|
float sync_time_at_0_7 =
|
||||||
track_B.CalcSyncFromAbsTime(0.7 * track_B.m_duration);
|
track_b.CalcSyncFromAbsTime(0.7 * track_b.m_duration);
|
||||||
REQUIRE(sync_time_at_0_7 == Catch::Detail::Approx(1.25));
|
REQUIRE(sync_time_at_0_7 == Catch::Detail::Approx(1.25));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Calculating sync time of track_B at 0.0 duration") {
|
WHEN("Calculating sync time of track_B at 0.0 duration") {
|
||||||
float sync_time_at_1_0 =
|
float sync_time_at_1_0 =
|
||||||
track_B.CalcSyncFromAbsTime(0.0 * track_B.m_duration);
|
track_b.CalcSyncFromAbsTime(0.0 * track_b.m_duration);
|
||||||
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(0.0));
|
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(0.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Calculating sync time of track_B at 1.0 duration") {
|
WHEN("Calculating sync time of track_B at 1.0 duration") {
|
||||||
float sync_time_at_1_0 =
|
float sync_time_at_1_0 =
|
||||||
track_B.CalcSyncFromAbsTime(0.9999 * track_B.m_duration);
|
track_b.CalcSyncFromAbsTime(0.9999 * track_b.m_duration);
|
||||||
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(2.0).epsilon(0.001f));
|
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(2.0).epsilon(0.001f));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Calculating ratio from sync time on track_A at 0.83333") {
|
WHEN("Calculating ratio from sync time on track_A at 0.83333") {
|
||||||
float ratio = track_A.CalcRatioFromSyncTime(0.83333333);
|
float ratio = track_a.CalcRatioFromSyncTime(0.83333333);
|
||||||
REQUIRE(ratio == Catch::Detail::Approx(0.5833333));
|
REQUIRE(ratio == Catch::Detail::Approx(0.5833333));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Calculating ratio from sync time on track_A at 0.83333") {
|
WHEN("Calculating ratio from sync time on track_A at 0.83333") {
|
||||||
float ratio = track_A.CalcRatioFromSyncTime(1.25);
|
float ratio = track_a.CalcRatioFromSyncTime(1.25);
|
||||||
REQUIRE(ratio == Catch::Detail::Approx(0.775));
|
REQUIRE(ratio == Catch::Detail::Approx(0.775));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Blending two synctracks with weight 0.") {
|
WHEN("Blending two synctracks with weight 0.") {
|
||||||
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);
|
SyncTrack blended = SyncTrack::Blend(0.f, track_a, track_b);
|
||||||
|
|
||||||
THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
|
THEN("Result must equal track_A") { REQUIRE(track_a == blended); }
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Blending two synctracks with weight 1.") {
|
WHEN("Blending two synctracks with weight 1.") {
|
||||||
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);
|
SyncTrack blended = SyncTrack::Blend(1.f, track_a, track_b);
|
||||||
|
|
||||||
THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
|
THEN("Result must equal track_B") { REQUIRE(track_b == blended); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") {
|
TEST_CASE("Sync Track From Marker", "[SyncTrack]") {
|
||||||
SyncTrack track_A;
|
SyncTrack track = SyncTrack::CreateFromMarkers(2.0f, {0.9f, 0.2f});
|
||||||
track_A.m_num_intervals = 2;
|
|
||||||
track_A.m_duration = 2.0;
|
|
||||||
track_A.m_sync_markers[0] = 0.9;
|
|
||||||
track_A.m_sync_markers[1] = 0.2;
|
|
||||||
|
|
||||||
WHEN("Calculating intervals") {
|
WHEN("Querying Ratios") {
|
||||||
track_A.CalcIntervals();
|
CHECK(track.m_interval_start_ratio[0] == Catch::Detail::Approx(0.45f));
|
||||||
|
CHECK(track.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.65f));
|
||||||
|
|
||||||
CHECK(track_A.m_interval_start[0] == Catch::Detail::Approx(0.9f));
|
CHECK(track.m_interval_start_ratio[1] == Catch::Detail::Approx(0.1f));
|
||||||
CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3f));
|
CHECK(track.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.35f));
|
||||||
|
|
||||||
CHECK(track_A.m_interval_start[1] == Catch::Detail::Approx(0.2f));
|
WHEN("Querying ratio at sync time at 0.001") {
|
||||||
CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.7f));
|
float ratio = track.CalcRatioFromSyncTime(0.0001f);
|
||||||
|
CHECK(ratio == Catch::Detail::Approx(0.45).epsilon(0.001));
|
||||||
|
}
|
||||||
|
|
||||||
WHEN("Querying ratio at sync time at 1.001") {
|
WHEN("Querying ratio at sync time at 0.9999") {
|
||||||
float ratio = track_A.CalcRatioFromSyncTime(1.0001f);
|
float ratio = track.CalcRatioFromSyncTime(0.9999f);
|
||||||
CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001));
|
CHECK(ratio == Catch::Detail::Approx(0.1).epsilon(0.001));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Querying ratio at sync time at 1.001") {
|
WHEN("Querying ratio at sync time at 1.001") {
|
||||||
float ratio = track_A.CalcRatioFromSyncTime(0.0001f);
|
float ratio = track.CalcRatioFromSyncTime(1.0001f);
|
||||||
CHECK(ratio == Catch::Detail::Approx(0.9).epsilon(0.001));
|
CHECK(ratio == Catch::Detail::Approx(0.1).epsilon(0.001));
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Querying ratio at sync time at 1.9999") {
|
WHEN("Querying ratio at sync time at 1.9999") {
|
||||||
float ratio = track_A.CalcRatioFromSyncTime(0.9999f);
|
float ratio = track.CalcRatioFromSyncTime(1.9999f);
|
||||||
CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001));
|
CHECK(ratio == Catch::Detail::Approx(0.45).epsilon(0.001));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Blending sync track with 3 events") {
|
WHEN("Querying SyncTime from Absolute Time") {
|
||||||
track_A.m_num_intervals = 3;
|
WHEN("Querying absolute time at 0.9001s") {
|
||||||
track_A.m_duration = 2.0;
|
float sync_time = track.CalcSyncFromAbsTime(0.9001f);
|
||||||
track_A.m_sync_markers[0] = 0.;
|
CHECK_THAT(sync_time, Catch::WithinAbs(0.0, 0.001));
|
||||||
track_A.m_sync_markers[1] = 0.3;
|
|
||||||
track_A.m_sync_markers[2] = 0.9;
|
|
||||||
track_A.CalcIntervals();
|
|
||||||
|
|
||||||
SyncTrack track_B;
|
|
||||||
track_B.m_num_intervals = 3;
|
|
||||||
track_B.m_duration = 1.5;
|
|
||||||
track_B.m_sync_markers[0] = 0.7;
|
|
||||||
track_B.m_sync_markers[1] = 0.9;
|
|
||||||
track_B.m_sync_markers[2] = 0.2;
|
|
||||||
track_B.CalcIntervals();
|
|
||||||
|
|
||||||
WHEN("Calculating A's durations") {
|
|
||||||
CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3));
|
|
||||||
CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.6));
|
|
||||||
CHECK(track_A.m_interval_ratio[2] == Catch::Detail::Approx(0.1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Calculating B's durations") {
|
WHEN("Querying absolute time at 0.2001s") {
|
||||||
CHECK(track_B.m_interval_ratio[0] == Catch::Detail::Approx(0.2));
|
float sync_time = track.CalcSyncFromAbsTime(0.2001f);
|
||||||
CHECK(track_B.m_interval_ratio[1] == Catch::Detail::Approx(0.3));
|
CHECK_THAT(sync_time, Catch::WithinAbs(1.0, 0.001));
|
||||||
CHECK(track_B.m_interval_ratio[2] == Catch::Detail::Approx(0.5));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Blending two synctracks with weight 0.") {
|
WHEN("Querying absolute time at 0.8999s") {
|
||||||
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);
|
float sync_time = track.CalcSyncFromAbsTime(0.8999f);
|
||||||
|
CHECK_THAT(sync_time, Catch::WithinAbs(1.999, 0.001));
|
||||||
THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Blending two synctracks with weight 1.") {
|
WHEN("Querying absolute time at 1.9999s") {
|
||||||
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);
|
float sync_time = track.CalcSyncFromAbsTime(1.9999f);
|
||||||
|
CHECK_THAT(sync_time, Catch::WithinAbs(0.84615384, 0.001));
|
||||||
THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WHEN("Blending with weight 0.2") {
|
TEST_CASE("Sync Track Blending", "[SyncTrack]") {
|
||||||
float weight = 0.2f;
|
SyncTrack track_a = SyncTrack::CreateFromMarkers(2.0, {0., 0.6, 1.8});
|
||||||
SyncTrack blended = SyncTrack::Blend(weight, track_A, track_B);
|
SyncTrack track_b = SyncTrack::CreateFromMarkers(1.5f, {1.05, 1.35, 0.3});
|
||||||
|
|
||||||
REQUIRE(
|
WHEN("Calculating A's durations") {
|
||||||
blended.m_duration
|
CHECK(track_a.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.3));
|
||||||
== (1.0f - weight) * track_A.m_duration
|
CHECK(track_a.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.6));
|
||||||
+ weight * track_B.m_duration);
|
CHECK(track_a.m_interval_duration_ratio[2] == Catch::Detail::Approx(0.1));
|
||||||
REQUIRE(
|
}
|
||||||
blended.m_interval_start[0]
|
|
||||||
== fmodf(
|
|
||||||
(1.0f - weight) * (track_A.m_interval_start[0] + 1.0f)
|
|
||||||
+ weight * (track_B.m_interval_start[0]),
|
|
||||||
1.0f));
|
|
||||||
REQUIRE(
|
|
||||||
blended.m_interval_ratio[1]
|
|
||||||
== (1.0f - weight) * (track_A.m_interval_ratio[1])
|
|
||||||
+ weight * (track_B.m_interval_ratio[1])
|
|
||||||
);
|
|
||||||
REQUIRE(
|
|
||||||
blended.m_interval_ratio[2]
|
|
||||||
== (1.0f - weight) * (track_A.m_interval_ratio[2])
|
|
||||||
+ weight * (track_B.m_interval_ratio[2])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
WHEN("Inverted blending with weight 0.2") {
|
WHEN("Calculating B's durations") {
|
||||||
float weight = 0.2f;
|
CHECK(track_b.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.2));
|
||||||
SyncTrack blended = SyncTrack::Blend(weight, track_B, track_A);
|
CHECK(track_b.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.3));
|
||||||
|
CHECK(track_b.m_interval_duration_ratio[2] == Catch::Detail::Approx(0.5));
|
||||||
|
}
|
||||||
|
|
||||||
REQUIRE(
|
WHEN("Blending two synctracks with weight 0.") {
|
||||||
blended.m_duration
|
SyncTrack blended = SyncTrack::Blend(0.f, track_a, track_b);
|
||||||
== (1.0f - weight) * track_B.m_duration
|
|
||||||
+ weight * track_A.m_duration);
|
THEN("Result must equal track_A") { REQUIRE(track_a == blended); }
|
||||||
REQUIRE(
|
}
|
||||||
blended.m_interval_start[0]
|
|
||||||
== fmodf(
|
WHEN("Blending two synctracks with weight 1.") {
|
||||||
(1.0f - weight) * (track_B.m_interval_start[0])
|
SyncTrack blended = SyncTrack::Blend(1.f, track_a, track_b);
|
||||||
+ weight * (track_A.m_interval_start[0] + 1.0f),
|
|
||||||
1.0f));
|
THEN("Result must equal track_B") { REQUIRE(track_b == blended); }
|
||||||
REQUIRE(
|
}
|
||||||
blended.m_interval_ratio[1]
|
|
||||||
== (1.0f - weight) * (track_B.m_interval_ratio[1])
|
WHEN("Blending with weight 0.2") {
|
||||||
+ weight * (track_A.m_interval_ratio[1])
|
float weight = 0.2f;
|
||||||
);
|
SyncTrack blended = SyncTrack::Blend(weight, track_a, track_b);
|
||||||
REQUIRE(
|
|
||||||
blended.m_interval_ratio[2]
|
REQUIRE(
|
||||||
== (1.0f - weight) * (track_B.m_interval_ratio[2])
|
blended.m_duration
|
||||||
+ weight * (track_A.m_interval_ratio[2])
|
== (1.0f - weight) * track_a.m_duration + weight * track_b.m_duration);
|
||||||
);
|
REQUIRE(
|
||||||
}
|
blended.m_interval_start_ratio[0]
|
||||||
|
== fmodf(
|
||||||
|
(1.0f - weight) * (track_a.m_interval_start_ratio[0] + 1.0f)
|
||||||
|
+ weight * (track_b.m_interval_start_ratio[0]),
|
||||||
|
1.0f));
|
||||||
|
REQUIRE(
|
||||||
|
blended.m_interval_duration_ratio[1]
|
||||||
|
== (1.0f - weight) * (track_a.m_interval_duration_ratio[1])
|
||||||
|
+ weight * (track_b.m_interval_duration_ratio[1]));
|
||||||
|
REQUIRE(
|
||||||
|
blended.m_interval_duration_ratio[2]
|
||||||
|
== (1.0f - weight) * (track_a.m_interval_duration_ratio[2])
|
||||||
|
+ weight * (track_b.m_interval_duration_ratio[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Inverted blending with weight 0.2") {
|
||||||
|
float weight = 0.2f;
|
||||||
|
SyncTrack blended = SyncTrack::Blend(weight, track_b, track_a);
|
||||||
|
|
||||||
|
REQUIRE(
|
||||||
|
blended.m_duration
|
||||||
|
== (1.0f - weight) * track_b.m_duration + weight * track_a.m_duration);
|
||||||
|
REQUIRE(
|
||||||
|
blended.m_interval_start_ratio[0]
|
||||||
|
== fmodf(
|
||||||
|
(1.0f - weight) * (track_b.m_interval_start_ratio[0])
|
||||||
|
+ weight * (track_a.m_interval_start_ratio[0] + 1.0f),
|
||||||
|
1.0f));
|
||||||
|
REQUIRE(
|
||||||
|
blended.m_interval_duration_ratio[1]
|
||||||
|
== (1.0f - weight) * (track_b.m_interval_duration_ratio[1])
|
||||||
|
+ weight * (track_a.m_interval_duration_ratio[1]));
|
||||||
|
REQUIRE(
|
||||||
|
blended.m_interval_duration_ratio[2]
|
||||||
|
== (1.0f - weight) * (track_b.m_interval_duration_ratio[2])
|
||||||
|
+ weight * (track_a.m_interval_duration_ratio[2]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Serialization", "[SyncTrack]") {
|
||||||
|
SyncTrack track;
|
||||||
|
track.m_num_intervals = 3;
|
||||||
|
track.m_duration = 2.0;
|
||||||
|
track.m_interval_start_ratio[0] = 0.f;
|
||||||
|
track.m_interval_duration_ratio[0] = 0.7;
|
||||||
|
track.m_interval_start_ratio[1] = 0.7f;
|
||||||
|
track.m_interval_duration_ratio[1] = 0.3;
|
||||||
|
track.m_interval_start_ratio[2] = 0.7f;
|
||||||
|
track.m_interval_duration_ratio[2] = 0.3;
|
||||||
|
|
||||||
|
nlohmann::json synctrack_json = track;
|
||||||
|
|
||||||
|
const SyncTrack synctrack_deserialized = synctrack_json;
|
||||||
|
|
||||||
|
CHECK(synctrack_deserialized.m_duration == track.m_duration);
|
||||||
|
CHECK(synctrack_deserialized.m_num_intervals == track.m_num_intervals);
|
||||||
|
|
||||||
|
for (int i = 0; i < track.m_num_intervals; i++) {
|
||||||
|
CHECK(
|
||||||
|
synctrack_deserialized.m_interval_start_ratio[i]
|
||||||
|
== track.m_interval_start_ratio[i]);
|
||||||
|
CHECK(
|
||||||
|
synctrack_deserialized.m_interval_duration_ratio[i]
|
||||||
|
== track.m_interval_duration_ratio[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
//
|
||||||
|
// Created by martin on 11.04.25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "TestAnimData.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "ozz/animation/offline/animation_builder.h"
|
||||||
|
#include "ozz/animation/offline/raw_animation.h"
|
||||||
|
#include "ozz/animation/offline/raw_skeleton.h"
|
||||||
|
#include "ozz/base/io/archive.h"
|
||||||
|
#include "ozz/base/log.h"
|
||||||
|
|
||||||
|
namespace TestAnimData {
|
||||||
|
|
||||||
|
SingleBoneSkeleton::SingleBoneSkeleton() {
|
||||||
|
using namespace ozz::animation::offline;
|
||||||
|
|
||||||
|
RawSkeleton raw_skeleton;
|
||||||
|
RawSkeleton::Joint raw_joint;
|
||||||
|
|
||||||
|
raw_joint.name = "Bone0";
|
||||||
|
raw_joint.transform.translation.x = 1.f;
|
||||||
|
raw_joint.transform.translation.y = 2.f;
|
||||||
|
raw_joint.transform.translation.z = 3.f;
|
||||||
|
|
||||||
|
raw_skeleton.roots.push_back(raw_joint);
|
||||||
|
|
||||||
|
SkeletonBuilder skeleton_builder;
|
||||||
|
skeleton = skeleton_builder(raw_skeleton);
|
||||||
|
|
||||||
|
// SingleBoneSkeleton Animations
|
||||||
|
ozz::animation::offline::RawAnimation raw_animation_translation_x;
|
||||||
|
raw_animation_translation_x.name = "TranslationX";
|
||||||
|
RawAnimation::JointTrack bone0_track;
|
||||||
|
RawAnimation::JointTrack::Translations bone0_translations;
|
||||||
|
|
||||||
|
// animation_translate_x
|
||||||
|
RawAnimation::TranslationKey translation_key;
|
||||||
|
translation_key.time = 0.f;
|
||||||
|
translation_key.value = ozz::math::Float3(0.f, 0.f, 0.f);
|
||||||
|
bone0_translations.push_back(translation_key);
|
||||||
|
|
||||||
|
translation_key.time = 1.f;
|
||||||
|
translation_key.value = ozz::math::Float3(1.f, 0.f, 0.f);
|
||||||
|
bone0_translations.push_back(translation_key);
|
||||||
|
|
||||||
|
bone0_track.translations = bone0_translations;
|
||||||
|
raw_animation_translation_x.tracks.push_back(bone0_track);
|
||||||
|
raw_animation_translation_x.duration = 1.f;
|
||||||
|
if (!raw_animation_translation_x.Validate()) {
|
||||||
|
std::cerr << "Error: could animation raw data invalid!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationBuilder animation_builder;
|
||||||
|
animation_translate_x = animation_builder(raw_animation_translation_x);
|
||||||
|
|
||||||
|
animation_translate_x_resource.m_animation = animation_translate_x.get();
|
||||||
|
SaveAnimation("single_bone_translation_z.ozz", animation_translate_x.get());
|
||||||
|
animation_translate_x_resource.m_name = "single_bone_translation_z";
|
||||||
|
animation_translate_x_resource.m_filename = "single_bone_translation_z.ozz";
|
||||||
|
|
||||||
|
// animation_translate_y
|
||||||
|
ozz::animation::offline::RawAnimation raw_animation_translation_y;
|
||||||
|
raw_animation_translation_y.name = "TranslationY";
|
||||||
|
bone0_translations.clear();
|
||||||
|
|
||||||
|
translation_key.time = 0.f;
|
||||||
|
translation_key.value = ozz::math::Float3(0.f, 0.f, 0.f);
|
||||||
|
bone0_translations.push_back(translation_key);
|
||||||
|
|
||||||
|
translation_key.time = 1.f;
|
||||||
|
translation_key.value = ozz::math::Float3(0.f, 1.f, 0.f);
|
||||||
|
bone0_translations.push_back(translation_key);
|
||||||
|
|
||||||
|
bone0_track.translations = bone0_translations;
|
||||||
|
raw_animation_translation_y.tracks.push_back(bone0_track);
|
||||||
|
raw_animation_translation_y.duration = 1.f;
|
||||||
|
if (!raw_animation_translation_y.Validate()) {
|
||||||
|
std::cerr << "Error: could animation raw data invalid!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_translate_y = animation_builder(raw_animation_translation_y);
|
||||||
|
|
||||||
|
animation_translate_y_resource.m_animation = animation_translate_y.get();
|
||||||
|
SaveAnimation("single_bone_translation_y.ozz", animation_translate_y.get());
|
||||||
|
animation_translate_y_resource.m_name = "single_bone_translation_y";
|
||||||
|
animation_translate_y_resource.m_filename = "single_bone_translation_y.ozz";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleBoneSkeleton::SaveSkeleton(
|
||||||
|
const char* filename,
|
||||||
|
ozz::animation::Skeleton* skeleton) {
|
||||||
|
assert(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleBoneSkeleton::SaveAnimation(
|
||||||
|
const char* filename,
|
||||||
|
ozz::animation::Animation* animation) {
|
||||||
|
ozz::io::File file(filename, "wb");
|
||||||
|
if (!file.opened()) {
|
||||||
|
ozz::log::Err() << "Failed to create animation file " << filename << "."
|
||||||
|
<< std::endl;
|
||||||
|
delete animation;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ozz::io::OArchive archive(&file);
|
||||||
|
|
||||||
|
archive << *animation;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace TestAnimData
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// Created by martin on 11.04.25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TESTANIMDATA_H
|
||||||
|
#define TESTANIMDATA_H
|
||||||
|
|
||||||
|
#include "AnimGraph/AnimGraphData.h"
|
||||||
|
#include "AnimGraph/SyncTrack.h"
|
||||||
|
#include "ozz/animation/offline/skeleton_builder.h"
|
||||||
|
#include "ozz/animation/runtime/animation.h"
|
||||||
|
#include "ozz/animation/runtime/skeleton.h"
|
||||||
|
|
||||||
|
namespace TestAnimData {
|
||||||
|
|
||||||
|
struct SingleBoneSkeleton {
|
||||||
|
SingleBoneSkeleton();
|
||||||
|
|
||||||
|
ozz::unique_ptr<ozz::animation::Skeleton> skeleton = nullptr;
|
||||||
|
|
||||||
|
ozz::unique_ptr<ozz::animation::Animation> animation_translate_x = nullptr;
|
||||||
|
AnimationResource animation_translate_x_resource;
|
||||||
|
SyncTrack animation_translate_x_sync_track = {};
|
||||||
|
|
||||||
|
ozz::unique_ptr<ozz::animation::Animation> animation_translate_y = nullptr;
|
||||||
|
AnimationResource animation_translate_y_resource;
|
||||||
|
SyncTrack animation_translate_y_sync_track = {};
|
||||||
|
|
||||||
|
bool SaveSkeleton(const char* filename, ozz::animation::Skeleton* skeleton);
|
||||||
|
bool SaveAnimation(
|
||||||
|
const char* filename,
|
||||||
|
ozz::animation::Animation* animation);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace TestAnimData
|
||||||
|
|
||||||
|
#endif //TESTANIMDATA_H
|
||||||
Reference in New Issue
Block a user