Author SHA1 Message Date
Martin Felis 5ac22ebac0 Added graph design ramblings. 2025-11-21 13:17:47 +01:00
Martin Felis 64cdddea96 Minor serialization refactoring. 2025-04-24 19:01:24 +02:00
Martin Felis 65c7a9aaaf AnimLibraries do not load animations by default. 2025-04-13 22:35:06 +02:00
Martin Felis 5d01dfcca2 Started working on AnimationLibrary. 2025-04-11 19:03:13 +02:00
Martin Felis 89fedce539 Cleanup. 2025-04-11 13:08:19 +02:00
Martin Felis f6f7e92cea Added name to AnimationResource. 2025-04-11 13:05:50 +02:00
Martin Felis d32c247cc0 Cleaned up includes. 2025-04-11 13:03:38 +02:00
Martin Felis 36c7f7a11e Refactor/cleanup of SyncTrack and added serialization to/from json. 2025-04-11 12:22:03 +02:00
Martin Felis 3b5537fc9d Minor cleanup. 2025-04-11 12:22:03 +02:00
Martin Felis c173707a18 Minor cleanup and refactoring. 2025-03-30 22:35:49 +02:00
Martin Felis 887131af37 Added AnimationFileResource that combine animations with sync tracks. 2025-03-20 16:16:16 +01:00
Martin Felis 86ea476881 Renamed AnimData to Pose. 2025-03-19 21:43:01 +01:00
Martin Felis f52b19a8d2 Further cleanup and added comments. 2025-03-19 21:12:27 +01:00
Martin Felis 283306f225 Minor cleanup and added comments. 2025-03-18 22:20:50 +01:00
Martin Felis b4eda31242 AnimGraphEvalTests now properly evaluates. 2025-03-17 22:23:12 +01:00
Martin Felis 1870a9d214 Added custom imgui-node-editor changes. 2025-03-16 22:59:05 +01:00
Martin Felis 07d02a2e42 AnimGraphEvalTests now compiles again. 2025-03-16 22:58:14 +01:00
Martin Felis 2ea2c56bbb Refactored BlendTreeResources to be a subclass of AnimGraphResource.
The latter is used to abstract saving/loading for both BlendTrees and StateMachines.
2025-03-16 18:15:31 +01:00
35 changed files with 2180 additions and 1801 deletions
@@ -929,7 +929,8 @@ struct Example:
{ {
auto cursorTopLeft = ImGui::GetCursorScreenPos(); auto cursorTopLeft = ImGui::GetCursorScreenPos();
util::BlueprintNodeBuilder builder(m_HeaderBackground, GetTextureWidth(m_HeaderBackground), GetTextureHeight(m_HeaderBackground)); // util::BlueprintNodeBuilder builder(m_HeaderBackground, GetTextureWidth(m_HeaderBackground), GetTextureHeight(m_HeaderBackground));
util::BlueprintNodeBuilder builder;
for (auto& node : m_Nodes) for (auto& node : m_Nodes)
{ {
@@ -11,7 +11,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
# include <imgui_node_editor.h> # include "3rdparty/imgui-node-editor/imgui_node_editor.h"
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
+8 -5
View File
@@ -1256,6 +1256,9 @@ void ed::EditorContext::End()
auto control = BuildControl(m_CurrentAction && m_CurrentAction->IsDragging()); // NavigateAction.IsMovingOverEdge() auto control = BuildControl(m_CurrentAction && m_CurrentAction->IsDragging()); // NavigateAction.IsMovingOverEdge()
//auto& editorStyle = GetStyle(); //auto& editorStyle = GetStyle();
// martin.felis, 2024-05-01, Start: expose Hot Node
m_HotNode = control.HotNode ? control.HotNode->m_ID : 0;
// martin.felis, 2024-05-01, End: expose Hot Node
m_HoveredNode = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : 0; m_HoveredNode = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : 0;
m_HoveredPin = control.HotPin && m_CurrentAction == nullptr ? control.HotPin->m_ID : 0; m_HoveredPin = control.HotPin && m_CurrentAction == nullptr ? control.HotPin->m_ID : 0;
m_HoveredLink = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : 0; m_HoveredLink = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : 0;
@@ -4391,15 +4394,15 @@ ed::EditorAction::AcceptResult ed::ShortcutAction::Accept(const Control& control
Action candidateAction = None; Action candidateAction = None;
auto& io = ImGui::GetIO(); auto& io = ImGui::GetIO();
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_X))
candidateAction = Cut; candidateAction = Cut;
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_C))
candidateAction = Copy; candidateAction = Copy;
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_V))
candidateAction = Paste; candidateAction = Paste;
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(GetKeyIndexForD())) if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(GetKeyIndexForD()))
candidateAction = Duplicate; candidateAction = Duplicate;
if (!io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space))) if (!io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_Space))
candidateAction = CreateNode; candidateAction = CreateNode;
if (candidateAction != None) if (candidateAction != None)
@@ -4953,7 +4956,7 @@ ed::EditorAction::AcceptResult ed::DeleteItemsAction::Accept(const Control& cont
return False; return False;
auto& io = ImGui::GetIO(); auto& io = ImGui::GetIO();
if (Editor->CanAcceptUserInput() && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)) && Editor->AreShortcutsEnabled()) if (Editor->CanAcceptUserInput() && ImGui::IsKeyPressed(ImGuiKey_Delete) && Editor->AreShortcutsEnabled())
{ {
auto& selection = Editor->GetSelectedObjects(); auto& selection = Editor->GetSelectedObjects();
if (!selection.empty()) if (!selection.empty())
+3
View File
@@ -409,6 +409,9 @@ IMGUI_NODE_EDITOR_API void EndShortcut();
IMGUI_NODE_EDITOR_API float GetCurrentZoom(); IMGUI_NODE_EDITOR_API float GetCurrentZoom();
// martin.felis, 2024-05-01, Start: expose Hot Node
IMGUI_NODE_EDITOR_API NodeId GetHotNode();
// martin.felis, 2024-05-01, Stop: expose Hot Node
IMGUI_NODE_EDITOR_API NodeId GetHoveredNode(); IMGUI_NODE_EDITOR_API NodeId GetHoveredNode();
IMGUI_NODE_EDITOR_API PinId GetHoveredPin(); IMGUI_NODE_EDITOR_API PinId GetHoveredPin();
IMGUI_NODE_EDITOR_API LinkId GetHoveredLink(); IMGUI_NODE_EDITOR_API LinkId GetHoveredLink();
+7
View File
@@ -667,6 +667,13 @@ float ax::NodeEditor::GetCurrentZoom()
return s_Editor->GetView().InvScale; return s_Editor->GetView().InvScale;
} }
// martin.felis, 2024-05-01, Start: expose Hot Node
ax::NodeEditor::NodeId ax::NodeEditor::GetHotNode()
{
return s_Editor->GetHotNode();
}
// martin.felis, 2024-05-01, End: expose Hot Node
ax::NodeEditor::NodeId ax::NodeEditor::GetHoveredNode() ax::NodeEditor::NodeId ax::NodeEditor::GetHoveredNode()
{ {
return s_Editor->GetHoveredNode(); return s_Editor->GetHoveredNode();
@@ -1447,6 +1447,9 @@ struct EditorContext
void EnableShortcuts(bool enable); void EnableShortcuts(bool enable);
bool AreShortcutsEnabled(); bool AreShortcutsEnabled();
// martin.felis, 2024-05-01, Start: expose Hot Node
NodeId GetHotNode() const { return m_HotNode; }
// martin.felis, 2024-05-01, End: expose Hot Node
NodeId GetHoveredNode() const { return m_HoveredNode; } NodeId GetHoveredNode() const { return m_HoveredNode; }
PinId GetHoveredPin() const { return m_HoveredPin; } PinId GetHoveredPin() const { return m_HoveredPin; }
LinkId GetHoveredLink() const { return m_HoveredLink; } LinkId GetHoveredLink() const { return m_HoveredLink; }
@@ -1528,6 +1531,9 @@ private:
vector<AnimationController*> m_AnimationControllers; vector<AnimationController*> m_AnimationControllers;
FlowAnimationController m_FlowAnimationController; FlowAnimationController m_FlowAnimationController;
// martin.felis, 2024-05-01, Start: expose Hot Node
NodeId m_HotNode;
// martin.felis, 2024-05-01, End: expose Hot Node
NodeId m_HoveredNode; NodeId m_HoveredNode;
PinId m_HoveredPin; PinId m_HoveredPin;
LinkId m_HoveredLink; LinkId m_HoveredLink;
+9 -4
View File
@@ -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
@@ -147,9 +149,12 @@ set(ozz_offline_test_objs
target_sources(runtests PRIVATE target_sources(runtests PRIVATE
tests/AnimGraphResourceTests.cc tests/AnimGraphResourceTests.cc
tests/AnimGraphEditorTests.cc tests/AnimGraphEditorTests.cc
# 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}
) )
+35
View File
@@ -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.
+2 -2
View File
@@ -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;
+27 -12
View File
@@ -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.
+43 -20
View File
@@ -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);
} }
+7 -2
View File
@@ -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);
+24 -10
View File
@@ -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);
+368 -393
View File
@@ -16,11 +16,11 @@
using json = nlohmann::json; using json = nlohmann::json;
// forward declarations // forward declarations
static json sAnimGraphResourceBlendTreeToJson( static json sBlendTreeToJson(const BlendTreeResource& anim_graph_resource);
const AnimGraphResource& anim_graph_resource);
static bool sAnimGraphResourceBlendTreeFromJson( static bool sBlendTreeFromJson(
const json& json_data, const json& json_data,
AnimGraphResource* result_graph_resource); BlendTreeResource* result_graph_resource);
// //
// Socket <-> json // Socket <-> json
@@ -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);
@@ -134,7 +134,7 @@ Socket sJsonToSocket(const json& json_data) {
// //
json sAnimGraphNodeToJson( json sAnimGraphNodeToJson(
const AnimNodeResource* node, const AnimNodeResource* node,
size_t node_index, const size_t node_index,
const std::vector<BlendTreeConnectionResource>& connections) { const std::vector<BlendTreeConnectionResource>& connections) {
json result; json result;
@@ -181,9 +181,9 @@ AnimNodeResource* sAnimGraphNodeFromJson(
std::string node_type = json_node["node_type"]; std::string node_type = json_node["node_type"];
if (node_type == "BlendTree") { if (node_type == "BlendTree") {
AnimGraphResource* result = BlendTreeResource* result =
dynamic_cast<AnimGraphResource*>(AnimNodeResourceFactory("BlendTree")); dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
sAnimGraphResourceBlendTreeFromJson(json_node, result); sBlendTreeFromJson(json_node, result);
return result; return result;
} }
@@ -244,26 +244,25 @@ BlendTreeConnectionResource sAnimGraphConnectionFromJson(
return connection; return connection;
} }
static json sAnimGraphResourceBlendTreeToJson( //
const AnimGraphResource& anim_graph_resource) { // BlendTreeResource <-> Json
//
static json sBlendTreeToJson(const BlendTreeResource& blend_tree_resource) {
json result; json result;
result["name"] = anim_graph_resource.m_name; result["name"] = blend_tree_resource.m_name;
result["type"] = "AnimNodeResource"; result["type"] = "AnimNodeResource";
result["node_type"] = "BlendTree"; result["node_type"] = "BlendTree";
result["position"][0] = anim_graph_resource.m_position[0]; result["position"][0] = blend_tree_resource.m_position[0];
result["position"][1] = anim_graph_resource.m_position[1]; result["position"][1] = blend_tree_resource.m_position[1];
const BlendTreeResource& blend_tree_resource =
anim_graph_resource.m_blend_tree_resource;
for (size_t i = 0; i < blend_tree_resource.GetNumNodes(); i++) { for (size_t i = 0; i < blend_tree_resource.GetNumNodes(); i++) {
const AnimNodeResource* node = blend_tree_resource.GetNode(i); const AnimNodeResource* node = blend_tree_resource.GetNode(i);
if (node->m_node_type_name == "BlendTree") { if (node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = const BlendTreeResource* blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node); dynamic_cast<const BlendTreeResource*>(node);
result["nodes"][i] = sAnimGraphResourceBlendTreeToJson(*graph_resource); result["nodes"][i] = sBlendTreeToJson(*blend_tree_resource);
} else { } else {
result["nodes"][i] = result["nodes"][i] =
sAnimGraphNodeToJson(node, i, blend_tree_resource.GetConnections()); sAnimGraphNodeToJson(node, i, blend_tree_resource.GetConnections());
@@ -295,20 +294,16 @@ static json sAnimGraphResourceBlendTreeToJson(
return result; return result;
} }
static bool sAnimGraphResourceBlendTreeFromJson( static bool sBlendTreeFromJson(
const json& json_data, const json& json_data,
AnimGraphResource* result_graph_resource) { BlendTreeResource* result_blend_tree_resource) {
BlendTreeResource& blend_tree_resource = result_blend_tree_resource->m_node_type_name = "BlendTree";
result_graph_resource->m_blend_tree_resource; result_blend_tree_resource->m_name = json_data["name"];
result_blend_tree_resource->m_position[0] = json_data["position"][0];
result_graph_resource->m_graph_type_name = "BlendTree"; result_blend_tree_resource->m_position[1] = json_data["position"][1];
result_graph_resource->m_node_type_name = "BlendTree";
result_graph_resource->m_name = json_data["name"];
result_graph_resource->m_position[0] = json_data["position"][0];
result_graph_resource->m_position[1] = json_data["position"][1];
// Clear all nodes as we overwrite them here anyway. // Clear all nodes as we overwrite them here anyway.
blend_tree_resource.ClearAllNodes(); result_blend_tree_resource->ClearAllNodes();
// Load nodes // Load nodes
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) { for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
@@ -321,14 +316,14 @@ static bool sAnimGraphResourceBlendTreeFromJson(
} }
AnimNodeResource* node = sAnimGraphNodeFromJson(json_node, i); AnimNodeResource* node = sAnimGraphNodeFromJson(json_node, i);
blend_tree_resource.AddNode(node); result_blend_tree_resource->AddNode(node);
} }
// Graph outputs // Graph outputs
if (json_data["nodes"][0].contains("inputs")) { if (json_data["nodes"][0].contains("inputs")) {
const json& graph_outputs = json_data["nodes"][0]["inputs"]; const json& graph_outputs = json_data["nodes"][0]["inputs"];
for (const auto& graph_output : graph_outputs) { for (const auto& graph_output : graph_outputs) {
result_graph_resource->RegisterBlendTreeOutputSocket( result_blend_tree_resource->RegisterBlendTreeOutputSocket(
sJsonToSocket(graph_output)); sJsonToSocket(graph_output));
} }
} }
@@ -337,7 +332,7 @@ static bool sAnimGraphResourceBlendTreeFromJson(
if (json_data["nodes"][1].contains("outputs")) { if (json_data["nodes"][1].contains("outputs")) {
const json& graph_inputs = json_data["nodes"][1]["outputs"]; const json& graph_inputs = json_data["nodes"][1]["outputs"];
for (const auto& graph_input : graph_inputs) { for (const auto& graph_input : graph_inputs) {
result_graph_resource->RegisterBlendTreeInputSocket( result_blend_tree_resource->RegisterBlendTreeInputSocket(
sJsonToSocket(graph_input)); sJsonToSocket(graph_input));
} }
} }
@@ -356,10 +351,10 @@ static bool sAnimGraphResourceBlendTreeFromJson(
BlendTreeConnectionResource connection = BlendTreeConnectionResource connection =
sAnimGraphConnectionFromJson(json_connection); sAnimGraphConnectionFromJson(json_connection);
blend_tree_resource.ConnectSockets( result_blend_tree_resource->ConnectSockets(
blend_tree_resource.GetNode(connection.source_node_index), result_blend_tree_resource->GetNode(connection.source_node_index),
connection.source_socket_name, connection.source_socket_name,
blend_tree_resource.GetNode(connection.target_node_index), result_blend_tree_resource->GetNode(connection.target_node_index),
connection.target_socket_name); connection.target_socket_name);
} }
} }
@@ -375,6 +370,92 @@ static bool sAnimGraphResourceStateMachineFromJson(
return false; return false;
} }
AnimGraphResource* AnimGraphResource::CreateFromFile(const char* filename) {
std::ifstream input_file;
input_file.open(filename);
std::stringstream buffer;
buffer << input_file.rdbuf();
json json_data = json::parse(buffer.str(), nullptr, false);
if (json_data.is_discarded()) {
std::cerr << "Error parsing json of file '" << filename << "'."
<< std::endl;
return nullptr;
}
if (json_data["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_data["type"] << "'." << std::endl;
return nullptr;
}
AnimGraphResource* result = nullptr;
if (json_data["node_type"] == "BlendTree") {
result = BlendTreeResource::CreateFromFile(filename);
} else if (json_data["node_type"] == "StateMachine") {
sAnimGraphResourceStateMachineFromJson(json_data, result);
} else {
std::cerr << "Invalid node_type. Expected type 'BlendTree' or "
"'StateMachine' but got '"
<< json_data["node_type"] << "'." << std::endl;
}
return result;
}
bool BlendTreeResource::SaveToFile(const char* filename) const {
json result;
result = sBlendTreeToJson(*this);
std::ofstream output_file;
output_file.open(filename);
output_file << result.dump(4, ' ') << std::endl;
output_file.close();
return true;
}
BlendTreeResource* BlendTreeResource::CreateFromFile(const char* filename) {
std::ifstream input_file;
input_file.open(filename);
std::stringstream buffer;
buffer << input_file.rdbuf();
json json_data = json::parse(buffer.str(), nullptr, false);
if (json_data.is_discarded()) {
std::cerr << "Error parsing json of file '" << filename << "'."
<< std::endl;
return nullptr;
}
if (json_data["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_data["type"] << "'." << std::endl;
return nullptr;
}
if (json_data["node_type"] != "BlendTree") {
std::cerr << "Invalid node type. Expected type 'BlendTree' but got '"
<< json_data["node_type"] << "'." << std::endl;
return nullptr;
}
BlendTreeResource* result = nullptr;
result =
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
sBlendTreeFromJson(json_data, result);
return result;
}
void BlendTreeResource::RemoveConnectionsForSocket( void BlendTreeResource::RemoveConnectionsForSocket(
const AnimNodeResource* node_resource, const AnimNodeResource* node_resource,
const Socket& socket) { const Socket& socket) {
@@ -563,12 +644,10 @@ Socket* BlendTreeResource::GetNodeOutputSocket(
} }
if (output_socket == nullptr && node->m_node_type_name == "BlendTree") { if (output_socket == nullptr && node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = const BlendTreeResource* blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node); dynamic_cast<const BlendTreeResource*>(node);
const BlendTreeResource& blend_tree_resource =
graph_resource->m_blend_tree_resource;
output_socket = output_socket =
blend_tree_resource.GetGraphOutputSocket(output_socket_name.c_str()); blend_tree_resource->GetGraphOutputSocket(output_socket_name.c_str());
} }
return output_socket; return output_socket;
@@ -582,12 +661,10 @@ const Socket* BlendTreeResource::GetNodeOutputSocketByIndex(
if (node->m_virtual_socket_accessor) { if (node->m_virtual_socket_accessor) {
output_sockets = &node->m_virtual_socket_accessor->m_outputs; output_sockets = &node->m_virtual_socket_accessor->m_outputs;
} else if (node->m_node_type_name == "BlendTree") { } else if (node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = const BlendTreeResource* blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node); dynamic_cast<const BlendTreeResource*>(node);
const BlendTreeResource& blend_tree_resource =
graph_resource->m_blend_tree_resource;
output_sockets = &blend_tree_resource.GetGraphOutputNode() output_sockets = &blend_tree_resource->GetGraphOutputNode()
->m_virtual_socket_accessor->m_outputs; ->m_virtual_socket_accessor->m_outputs;
} }
@@ -610,12 +687,10 @@ Socket* BlendTreeResource::GetNodeInputSocket(
} }
if (input_socket == nullptr && node->m_node_type_name == "BlendTree") { if (input_socket == nullptr && node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = const BlendTreeResource* blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node); dynamic_cast<const BlendTreeResource*>(node);
const BlendTreeResource& blend_tree_resource =
graph_resource->m_blend_tree_resource;
input_socket = input_socket =
blend_tree_resource.GetGraphInputSocket(input_socket_name.c_str()); blend_tree_resource->GetGraphInputSocket(input_socket_name.c_str());
} }
return input_socket; return input_socket;
@@ -629,12 +704,10 @@ const Socket* BlendTreeResource::GetNodeInputSocketByIndex(
if (node->m_virtual_socket_accessor) { if (node->m_virtual_socket_accessor) {
output_sockets = &node->m_virtual_socket_accessor->m_inputs; output_sockets = &node->m_virtual_socket_accessor->m_inputs;
} else if (node->m_node_type_name == "BlendTree") { } else if (node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = const BlendTreeResource* blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node); dynamic_cast<const BlendTreeResource*>(node);
const BlendTreeResource& blend_tree_resource =
graph_resource->m_blend_tree_resource;
output_sockets = &blend_tree_resource.GetGraphOutputNode() output_sockets = &blend_tree_resource->GetGraphOutputNode()
->m_virtual_socket_accessor->m_outputs; ->m_virtual_socket_accessor->m_outputs;
} }
@@ -653,11 +726,10 @@ std::vector<Socket> BlendTreeResource::GetNodeOutputSockets(
} }
if (node->m_node_type_name == "BlendTree") { if (node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = const BlendTreeResource* blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node); dynamic_cast<const BlendTreeResource*>(node);
const BlendTreeResource& blend_tree_resource =
graph_resource->m_blend_tree_resource; return blend_tree_resource->GetGraphOutputNode()
return blend_tree_resource.GetGraphOutputNode()
->m_virtual_socket_accessor->m_inputs; ->m_virtual_socket_accessor->m_inputs;
} }
@@ -671,11 +743,10 @@ std::vector<Socket> BlendTreeResource::GetNodeInputSockets(
} }
if (node->m_node_type_name == "BlendTree") { if (node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = const BlendTreeResource* blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node); dynamic_cast<const BlendTreeResource*>(node);
const BlendTreeResource& blend_tree_resource =
graph_resource->m_blend_tree_resource; return blend_tree_resource->GetGraphInputNode()
return blend_tree_resource.GetGraphInputNode()
->m_virtual_socket_accessor->m_outputs; ->m_virtual_socket_accessor->m_outputs;
} }
@@ -824,92 +895,12 @@ void BlendTreeResource::UpdateNodeSubtrees() {
} }
} }
AnimGraphResource::AnimGraphResource(AnimGraphType graph_type) { void BlendTreeResource::CreateBlendTreeInstance(
if (graph_type == AnimGraphType::GraphTypeBlendTree) {
m_graph_type_name = "BlendTree";
m_virtual_socket_accessor = VirtualAnimNodeDescriptorFactory("BlendTree");
m_blend_tree_resource.InitGraphConnectors();
RegisterBlendTreeOutputSocket<AnimData>(
AnimGraphResource::DefaultAnimOutput);
} else {
std::cerr
<< "Warning: construction of state machine graphs not yet implemented!"
<< std::endl;
}
}
AnimGraphResource* AnimGraphResource::CreateFromFile(const char* filename) {
std::ifstream input_file;
input_file.open(filename);
std::stringstream buffer;
buffer << input_file.rdbuf();
json json_data = json::parse(buffer.str(), nullptr, false);
if (json_data.is_discarded()) {
std::cerr << "Error parsing json of file '" << filename << "'."
<< std::endl;
return nullptr;
}
if (json_data["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_data["type"] << "'." << std::endl;
return nullptr;
}
AnimGraphResource* result = nullptr;
if (json_data["node_type"] == "BlendTree") {
result =
dynamic_cast<AnimGraphResource*>(AnimNodeResourceFactory("BlendTree"));
sAnimGraphResourceBlendTreeFromJson(json_data, result);
} else if (json_data["node_type"] == "StateMachine") {
sAnimGraphResourceStateMachineFromJson(json_data, result);
} else {
std::cerr << "Invalid node_type. Expected type 'BlendTree' or "
"'StateMachine' but got '"
<< json_data["node_type"] << "'." << std::endl;
}
return result;
}
bool AnimGraphResource::SaveToFile(const char* filename) const {
if (m_graph_type_name == "BlendTree") {
return SaveBlendTreeResourceToFile(filename);
} else if (m_graph_type_name == "StateMachine") {
return SaveStateMachineResourceToFile(filename);
}
std::cerr << "Invalid AnimGraphResource type: " << m_graph_type_name << "."
<< std::endl;
return false;
}
bool AnimGraphResource::SaveBlendTreeResourceToFile(
const char* filename) const {
json result;
result = sAnimGraphResourceBlendTreeToJson(*this);
std::ofstream output_file;
output_file.open(filename);
output_file << result.dump(4, ' ') << std::endl;
output_file.close();
return true;
}
void AnimGraphResource::CreateBlendTreeInstance(
AnimGraphBlendTree& result) const { AnimGraphBlendTree& result) const {
if (m_node_type_name != "BlendTree") { if (m_node_type_name != "BlendTree") {
std::cerr std::cerr
<< "Invalid AnimGraphResource. Expected type 'BlendTree' but got '" << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '"
<< m_graph_type_name << "'." << std::endl; << m_node_type_name << "'." << std::endl;
return; return;
} }
@@ -926,15 +917,14 @@ void AnimGraphResource::CreateBlendTreeInstance(
result.ResetNodeStates(); result.ResetNodeStates();
} }
void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances( void BlendTreeResource::CreateBlendTreeRuntimeNodeInstances(
AnimGraphBlendTree& result) const { AnimGraphBlendTree& result) const {
for (const AnimNodeResource* node_resource : for (const AnimNodeResource* node_resource : GetNodes()) {
m_blend_tree_resource.GetNodes()) {
AnimNode* node = AnimNodeFactory(node_resource->m_node_type_name); AnimNode* node = AnimNodeFactory(node_resource->m_node_type_name);
if (node_resource->m_node_type_name == "BlendTree") { if (node_resource->m_node_type_name == "BlendTree") {
const AnimGraphResource* embedded_blend_tree_resource = const BlendTreeResource* embedded_blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(node_resource); dynamic_cast<const BlendTreeResource*>(node_resource);
assert(embedded_blend_tree_resource != nullptr); assert(embedded_blend_tree_resource != nullptr);
AnimGraphBlendTree* embedded_blend_tree = AnimGraphBlendTree* embedded_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(node); dynamic_cast<AnimGraphBlendTree*>(node);
@@ -943,9 +933,9 @@ void AnimGraphResource::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;
@@ -958,18 +948,16 @@ void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
} }
} }
void AnimGraphResource::PrepareBlendTreeIOData( void BlendTreeResource::PrepareBlendTreeIOData(
AnimGraphBlendTree& instance, AnimGraphBlendTree& instance,
NodeSocketDataOffsetMap& node_offset_map) const { NodeSocketDataOffsetMap& node_offset_map) const {
instance.m_node_descriptor = instance.m_node_descriptor =
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]); AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
instance.m_node_descriptor->m_outputs = instance.m_node_descriptor->m_outputs =
m_blend_tree_resource.GetGraphInputNode() GetGraphOutputNode()->m_virtual_socket_accessor->m_inputs;
->m_virtual_socket_accessor->m_outputs;
instance.m_node_descriptor->m_inputs = instance.m_node_descriptor->m_inputs =
m_blend_tree_resource.GetGraphOutputNode() GetGraphInputNode()->m_virtual_socket_accessor->m_outputs;
->m_virtual_socket_accessor->m_inputs;
// //
// graph inputs // graph inputs
@@ -989,7 +977,7 @@ void AnimGraphResource::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*);
} }
@@ -1010,24 +998,27 @@ void AnimGraphResource::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 : for (const BlendTreeConnectionResource& connection : GetConnections()) {
m_blend_tree_resource.GetConnections()) { const AnimNodeResource* source_node = GetNode(connection.source_node_index);
const AnimNodeResource* source_node =
m_blend_tree_resource.GetNode(connection.source_node_index);
Socket* source_socket = Socket* source_socket =
source_node->m_virtual_socket_accessor->GetOutputSocket( source_node->m_virtual_socket_accessor->GetOutputSocket(
connection.source_socket_name.c_str()); connection.source_socket_name.c_str());
NodeSocketPair source_socket_pair{source_node, source_socket->m_name}; NodeSocketNamePair source_socket_pair{source_node, source_socket->m_name};
if (node_offset_map.find(source_socket_pair) == node_offset_map.end()) { if (node_offset_map.find(source_socket_pair) == node_offset_map.end()) {
node_offset_map.insert( node_offset_map.insert(
{source_socket_pair, connection_data_storage_size}); {source_socket_pair, connection_data_storage_size});
@@ -1042,221 +1033,10 @@ void AnimGraphResource::PrepareBlendTreeIOData(
} }
} }
void AnimGraphResource::CreateBlendTreeConnectionInstances( void BlendTreeResource::SetRuntimeNodeProperties(
AnimGraphBlendTree& instance,
NodeSocketDataOffsetMap& node_offset_map) const {
std::vector<NodeDescriptorBase*> instance_node_descriptors(
m_blend_tree_resource.GetNumNodes(),
nullptr);
for (int i = 0; i < m_blend_tree_resource.GetNumNodes(); i++) {
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
m_blend_tree_resource.GetNode(i)->m_node_type_name,
instance.m_nodes[i]);
if (i > 1
&& m_blend_tree_resource.GetNode(i)->m_node_type_name == "BlendTree") {
instance_node_descriptors[i]->m_inputs =
m_blend_tree_resource.GetNode(i)->m_virtual_socket_accessor->m_inputs;
instance_node_descriptors[i]->m_outputs =
m_blend_tree_resource.GetNode(i)
->m_virtual_socket_accessor->m_outputs;
}
}
instance_node_descriptors[0]->m_inputs = instance.m_node_descriptor->m_inputs;
instance_node_descriptors[1]->m_outputs =
instance.m_node_descriptor->m_outputs;
for (const BlendTreeConnectionResource& connection :
m_blend_tree_resource.GetConnections()) {
NodeDescriptorBase* source_node_descriptor =
instance_node_descriptors[connection.source_node_index];
NodeDescriptorBase* target_node_descriptor =
instance_node_descriptors[connection.target_node_index];
AnimNode* source_node = instance.m_nodes[connection.source_node_index];
AnimNode* target_node = instance.m_nodes[connection.target_node_index];
Socket* source_socket = source_node_descriptor->GetOutputSocket(
connection.source_socket_name.c_str());
Socket* target_socket = target_node_descriptor->GetInputSocket(
connection.target_socket_name.c_str());
AnimGraphConnection instance_connection;
if (source_node->m_node_type_name == "BlendTree") {
// For embedded subgraphs we have to ensure two things:
// 1. The parent graph target node must activate the source node within
// the embedded subgraph.
// 2. The parent graph target node must activate the AnimNode of the
// parent graph which contains the embedded subgraph. Otherwise, the
// embedded graph does not get evaluated.
// For each case we have to add a connection here. First we insert the
// default connection within the graph (i.e. for case 2). Then we alter
// the source node such that it points to the node within the embedded
// subgraph.
AnimGraphConnection embedded_graph_activation_connection;
embedded_graph_activation_connection.m_source_node = source_node;
embedded_graph_activation_connection.m_source_socket_name =
source_socket->m_name;
embedded_graph_activation_connection.m_target_node = target_node;
embedded_graph_activation_connection.m_target_socket_name =
target_socket->m_name;
embedded_graph_activation_connection.m_socket = *target_socket;
instance.m_node_input_connections[connection.target_node_index].push_back(
embedded_graph_activation_connection);
instance.m_node_output_connections[connection.source_node_index]
.push_back(embedded_graph_activation_connection);
const AnimGraphResource* source_blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(
m_blend_tree_resource.GetNode(connection.source_node_index));
AnimGraphBlendTree* source_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(source_node);
size_t source_blend_tree_output_node_index =
source_blend_tree_resource->m_blend_tree_resource
.GetNodeIndexForOutputSocket(connection.source_socket_name);
source_node =
source_blend_tree->m_nodes[source_blend_tree_output_node_index];
instance_connection.m_crosses_hierarchy = true;
} else if (target_node->m_node_type_name == "BlendTree") {
// When a connection points to an embedded blend tree we have to ensure
// that the embedded node knows about its connection partner in the parent
// tree. This allows the embedded node to properly activate the node in
// the parent graph.
const AnimGraphResource* target_blend_tree_resource =
dynamic_cast<const AnimGraphResource*>(
m_blend_tree_resource.GetNode(connection.target_node_index));
AnimGraphBlendTree* target_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(target_node);
size_t target_blend_tree_output_node_index =
target_blend_tree_resource->m_blend_tree_resource
.GetNodeIndexForInputSocket(connection.target_socket_name);
target_node =
target_blend_tree->m_nodes[target_blend_tree_output_node_index];
// Make sure that the embedded node input activates the parent graph node.
std::vector<AnimGraphConnection>& embeded_target_node_inputs =
target_blend_tree
->m_node_input_connections[target_blend_tree_output_node_index];
for (size_t j = 0; j < embeded_target_node_inputs.size(); j++) {
AnimGraphConnection& embedded_target_connection =
embeded_target_node_inputs[j];
if (embedded_target_connection.m_source_socket_name
== target_socket->m_name) {
embedded_target_connection.m_source_node = source_node;
embedded_target_connection.m_crosses_hierarchy = true;
// In addition: make sure we expose the embedded socket to the
// connection in the parent blend tree. That way
// parent_tree.SetValue<>() correctly propagates the value to the
// embedded node socket.
target_socket = &embedded_target_connection.m_socket;
}
}
}
instance_connection.m_source_node = source_node;
instance_connection.m_source_socket_name = source_socket->m_name;
instance_connection.m_target_node = target_node;
instance_connection.m_target_socket_name = target_socket->m_name;
instance_connection.m_socket = *target_socket;
size_t socket_data_offset = 0;
if (connection.source_node_index == 1) {
instance_connection.m_socket = *target_socket;
} else {
if (connection.target_node_index == 0) {
instance_connection.m_socket = *source_socket;
}
NodeSocketPair node_socket_pair{
m_blend_tree_resource.GetNode(connection.source_node_index),
source_socket->m_name};
NodeSocketDataOffsetMap::const_iterator socket_data_offset_iter =
node_offset_map.find(node_socket_pair);
if (socket_data_offset_iter != node_offset_map.end()) {
socket_data_offset = socket_data_offset_iter->second;
} else {
std::cerr << "Error: could not find data offset for node '"
<< source_node->m_name << "' and socket '"
<< source_socket->m_name << "'." << std::endl;
assert(false);
abort();
}
*target_socket->m_reference.ptr_ptr =
&instance.m_connection_data_storage[socket_data_offset];
*source_socket->m_reference.ptr_ptr =
&instance.m_connection_data_storage[socket_data_offset];
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
instance.m_animdata_blocks.push_back(
(AnimData*)(&instance
.m_connection_data_storage[socket_data_offset]));
}
}
instance.m_node_input_connections[connection.target_node_index].push_back(
instance_connection);
instance.m_node_output_connections[connection.source_node_index].push_back(
instance_connection);
}
//
// const node inputs
//
std::vector<Socket*> const_inputs =
m_blend_tree_resource.GetConstantNodeInputs(instance_node_descriptors);
size_t const_node_inputs_buffer_size = 0;
for (auto& const_input : const_inputs) {
if (const_input->m_type == SocketType::SocketTypeString) {
// TODO: implement string const node input support
std::cerr << "Error: const inputs for strings not yet implemented!"
<< std::endl;
abort();
}
const_node_inputs_buffer_size += const_input->m_type_size;
}
if (const_node_inputs_buffer_size > 0) {
instance.m_const_node_inputs = new char[const_node_inputs_buffer_size];
memset(instance.m_const_node_inputs, '\0', const_node_inputs_buffer_size);
}
size_t const_input_buffer_offset = 0;
for (auto& i : const_inputs) {
Socket* const_input = i;
// TODO: implement string const node input support
assert(const_input->m_type != SocketType::SocketTypeString);
*const_input->m_reference.ptr_ptr =
&instance.m_const_node_inputs[const_input_buffer_offset];
memcpy(
*const_input->m_reference.ptr_ptr,
&const_input->m_value,
i->m_type_size);
const_input_buffer_offset += i->m_type_size;
}
for (int i = 0; i < m_blend_tree_resource.GetNumNodes(); i++) {
delete instance_node_descriptors[i];
}
}
void AnimGraphResource::SetRuntimeNodeProperties(
AnimGraphBlendTree& result) const { AnimGraphBlendTree& result) const {
for (int i = 2; i < m_blend_tree_resource.GetNumNodes(); i++) { for (int i = 2; i < GetNumNodes(); i++) {
const AnimNodeResource* node_resource = m_blend_tree_resource.GetNode(i); const AnimNodeResource* node_resource = GetNode(i);
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory( NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
node_resource->m_node_type_name, node_resource->m_node_type_name,
@@ -1308,17 +1088,212 @@ void AnimGraphResource::SetRuntimeNodeProperties(
} }
} }
bool AnimGraphResource::SaveStateMachineResourceToFile( void BlendTreeResource::CreateBlendTreeConnectionInstances(
const char* filename) const { AnimGraphBlendTree& instance,
assert(false && "Not yet implemented"); NodeSocketDataOffsetMap& node_offset_map) const {
std::vector<NodeDescriptorBase*> instance_node_descriptors(
GetNumNodes(),
nullptr);
for (int i = 0; i < GetNumNodes(); i++) {
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
GetNode(i)->m_node_type_name,
instance.m_nodes[i]);
return false; if (i > 1 && GetNode(i)->m_node_type_name == "BlendTree") {
instance_node_descriptors[i]->m_inputs =
GetNode(i)->m_virtual_socket_accessor->m_inputs;
instance_node_descriptors[i]->m_outputs =
GetNode(i)->m_virtual_socket_accessor->m_outputs;
}
}
instance_node_descriptors[0]->m_inputs =
instance.m_node_descriptor->m_outputs;
instance_node_descriptors[1]->m_outputs =
instance.m_node_descriptor->m_inputs;
for (const BlendTreeConnectionResource& connection : GetConnections()) {
NodeDescriptorBase* source_node_descriptor =
instance_node_descriptors[connection.source_node_index];
NodeDescriptorBase* target_node_descriptor =
instance_node_descriptors[connection.target_node_index];
AnimNode* source_node = instance.m_nodes[connection.source_node_index];
AnimNode* target_node = instance.m_nodes[connection.target_node_index];
Socket* source_socket = source_node_descriptor->GetOutputSocket(
connection.source_socket_name.c_str());
Socket* target_socket = target_node_descriptor->GetInputSocket(
connection.target_socket_name.c_str());
AnimGraphConnection instance_connection;
if (source_node->m_node_type_name == "BlendTree") {
// For embedded subgraphs we have to ensure two things:
// 1. The parent graph target node must activate the source node within
// the embedded subgraph.
// 2. The parent graph target node must activate the AnimNode of the
// parent graph which contains the embedded subgraph. Otherwise, the
// embedded graph does not get evaluated.
// For each case we have to add a connection here. First we insert the
// default connection within the graph (i.e. for case 2). Then we alter
// the source node such that it points to the node within the embedded
// subgraph.
AnimGraphConnection embedded_graph_activation_connection;
embedded_graph_activation_connection.m_source_node = source_node;
embedded_graph_activation_connection.m_source_socket_name =
source_socket->m_name;
embedded_graph_activation_connection.m_target_node = target_node;
embedded_graph_activation_connection.m_target_socket_name =
target_socket->m_name;
embedded_graph_activation_connection.m_socket = *target_socket;
instance.m_node_input_connections[connection.target_node_index].push_back(
embedded_graph_activation_connection);
instance.m_node_output_connections[connection.source_node_index]
.push_back(embedded_graph_activation_connection);
const BlendTreeResource* source_blend_tree_resource =
dynamic_cast<const BlendTreeResource*>(
GetNode(connection.source_node_index));
AnimGraphBlendTree* source_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(source_node);
size_t source_blend_tree_output_node_index =
source_blend_tree_resource->GetNodeIndexForOutputSocket(
connection.source_socket_name);
source_node =
source_blend_tree->m_nodes[source_blend_tree_output_node_index];
instance_connection.m_crosses_hierarchy = true;
} else if (target_node->m_node_type_name == "BlendTree") {
// When a connection points to an embedded blend tree we have to ensure
// that the embedded node knows about its connection partner in the parent
// tree. This allows the embedded node to properly activate the node in
// the parent graph.
const BlendTreeResource* target_blend_tree_resource =
dynamic_cast<const BlendTreeResource*>(
GetNode(connection.target_node_index));
AnimGraphBlendTree* target_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(target_node);
size_t target_blend_tree_output_node_index =
target_blend_tree_resource->GetNodeIndexForInputSocket(
connection.target_socket_name);
target_node =
target_blend_tree->m_nodes[target_blend_tree_output_node_index];
// Make sure that the embedded node input activates the parent graph node.
std::vector<AnimGraphConnection>& embeded_target_node_inputs =
target_blend_tree
->m_node_input_connections[target_blend_tree_output_node_index];
for (size_t j = 0; j < embeded_target_node_inputs.size(); j++) {
AnimGraphConnection& embedded_target_connection =
embeded_target_node_inputs[j];
if (embedded_target_connection.m_source_socket_name
== target_socket->m_name) {
embedded_target_connection.m_source_node = source_node;
embedded_target_connection.m_crosses_hierarchy = true;
// In addition: make sure we expose the embedded socket to the
// connection in the parent blend tree. That way
// parent_tree.SetValue<>() correctly propagates the value to the
// embedded node socket.
target_socket = &embedded_target_connection.m_socket;
}
}
}
instance_connection.m_source_node = source_node;
instance_connection.m_source_socket_name = source_socket->m_name;
instance_connection.m_target_node = target_node;
instance_connection.m_target_socket_name = target_socket->m_name;
instance_connection.m_socket = *target_socket;
size_t socket_data_offset = 0;
if (connection.source_node_index == 1) {
instance_connection.m_socket = *target_socket;
} else {
if (connection.target_node_index == 0) {
instance_connection.m_socket = *source_socket;
}
NodeSocketNamePair node_socket_pair{
GetNode(connection.source_node_index),
source_socket->m_name};
NodeSocketDataOffsetMap::const_iterator socket_data_offset_iter =
node_offset_map.find(node_socket_pair);
if (socket_data_offset_iter != node_offset_map.end()) {
socket_data_offset = socket_data_offset_iter->second;
} else {
std::cerr << "Error: could not find data offset for node '"
<< source_node->m_name << "' and socket '"
<< source_socket->m_name << "'." << std::endl;
assert(false);
abort();
}
*target_socket->m_reference.ptr_ptr =
&instance.m_connection_data_storage[socket_data_offset];
*source_socket->m_reference.ptr_ptr =
&instance.m_connection_data_storage[socket_data_offset];
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
instance.m_pose_blocks.push_back(
(Pose*)(&instance.m_connection_data_storage[socket_data_offset]));
}
}
instance.m_node_input_connections[connection.target_node_index].push_back(
instance_connection);
instance.m_node_output_connections[connection.source_node_index].push_back(
instance_connection);
}
//
// const node inputs
//
std::vector<Socket*> const_inputs =
GetConstantNodeInputs(instance_node_descriptors);
size_t const_node_inputs_buffer_size = 0;
for (auto& const_input : const_inputs) {
if (const_input->m_type == SocketType::SocketTypeString) {
// TODO: implement string const node input support
std::cerr << "Error: const inputs for strings not yet implemented!"
<< std::endl;
abort();
}
const_node_inputs_buffer_size += const_input->m_type_size;
}
if (const_node_inputs_buffer_size > 0) {
instance.m_const_node_inputs = new char[const_node_inputs_buffer_size];
memset(instance.m_const_node_inputs, '\0', const_node_inputs_buffer_size);
}
size_t const_input_buffer_offset = 0;
for (auto& i : const_inputs) {
Socket* const_input = i;
// TODO: implement string const node input support
assert(const_input->m_type != SocketType::SocketTypeString);
*const_input->m_reference.ptr_ptr =
&instance.m_const_node_inputs[const_input_buffer_offset];
memcpy(
*const_input->m_reference.ptr_ptr,
&const_input->m_value,
i->m_type_size);
const_input_buffer_offset += i->m_type_size;
}
for (int i = 0; i < GetNumNodes(); i++) {
delete instance_node_descriptors[i];
}
} }
bool AnimGraphResource::LoadStateMachineResourceFromJson(
nlohmann::json const& json_data) {
assert(false && "Not yet implemented");
return false;
}
#pragma clang diagnostic pop #pragma clang diagnostic pop
+122 -114
View File
@@ -5,14 +5,17 @@
#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"
struct AnimGraphBlendTree; struct AnimGraphBlendTree;
struct AnimGraphStateMachine; struct AnimGraphStateMachine;
struct BlendTreeResource;
struct AnimNodeResource { struct AnimNodeResource {
virtual ~AnimNodeResource() { delete m_virtual_socket_accessor; }; virtual ~AnimNodeResource() { delete m_virtual_socket_accessor; }
std::string m_name; std::string m_name;
std::string m_node_type_name; std::string m_node_type_name;
@@ -23,6 +26,30 @@ struct AnimNodeResource {
static inline AnimNodeResource* AnimNodeResourceFactory( static inline AnimNodeResource* AnimNodeResourceFactory(
const std::string& node_type_name); const std::string& node_type_name);
struct StateMachineTransitionResources {
size_t source_state_index = -1;
size_t target_state_index = -1;
float blend_time = 0.f;
bool sync_blend = false;
};
struct StateMachineResource {
std::vector<AnimNodeResource> m_states;
std::vector<StateMachineTransitionResources> m_transitions;
};
struct AnimGraphResource : AnimNodeResource {
~AnimGraphResource() override = default;
static constexpr char DefaultAnimOutput[] = "Output";
[[maybe_unused]] virtual bool SaveToFile(const char* filename) const = 0;
static AnimGraphResource* CreateFromFile(const char* filename);
};
typedef std::unique_ptr<AnimGraphResource> AnimGraphResourcePtr;
struct BlendTreeConnectionResource { struct BlendTreeConnectionResource {
int source_node_index = -1; int source_node_index = -1;
std::string source_socket_name; std::string source_socket_name;
@@ -39,12 +66,26 @@ struct BlendTreeConnectionResource {
} }
}; };
struct BlendTreeResource { struct BlendTreeResource : AnimGraphResource {
typedef std::pair<const AnimNodeResource*, std::string> NodeSocketNamePair;
typedef std::map<NodeSocketNamePair, int> NodeSocketDataOffsetMap;
std::vector<std::vector<size_t> > m_node_input_connection_indices; std::vector<std::vector<size_t> > m_node_input_connection_indices;
std::vector<std::vector<size_t> > m_node_inputs_subtree; std::vector<std::vector<size_t> > m_node_inputs_subtree;
BlendTreeResource() {
m_virtual_socket_accessor = VirtualAnimNodeDescriptorFactory("BlendTree");
InitGraphConnectors();
RegisterBlendTreeOutputSocket<Pose>(AnimGraphResource::DefaultAnimOutput);
}
~BlendTreeResource() { ClearAllNodes(); } ~BlendTreeResource() { ClearAllNodes(); }
[[maybe_unused]] bool SaveToFile(const char* filename) const override;
static BlendTreeResource* CreateFromFile(const char* filename);
void CreateBlendTreeInstance(AnimGraphBlendTree& result) const;
void Reset() { void Reset() {
ClearAllNodes(); ClearAllNodes();
@@ -65,18 +106,77 @@ struct BlendTreeResource {
[[nodiscard]] AnimNodeResource* GetGraphOutputNode() const { [[nodiscard]] AnimNodeResource* GetGraphOutputNode() const {
return m_nodes[0]; return m_nodes[0];
} }
[[nodiscard]] AnimNodeResource* GetGraphInputNode() const { [[nodiscard]] AnimNodeResource* GetGraphInputNode() const {
return m_nodes[1]; return m_nodes[1];
} }
Socket* GetGraphOutputSocket(const char* socket_name) const { Socket* GetGraphOutputSocket(const char* socket_name) const {
return GetGraphOutputNode()->m_virtual_socket_accessor->GetInputSocket( return GetGraphOutputNode()->m_virtual_socket_accessor->GetInputSocket(
socket_name); socket_name);
} }
Socket* GetGraphInputSocket(const char* socket_name) const { Socket* GetGraphInputSocket(const char* socket_name) const {
return GetGraphInputNode()->m_virtual_socket_accessor->GetOutputSocket( return GetGraphInputNode()->m_virtual_socket_accessor->GetOutputSocket(
socket_name); socket_name);
} }
template <typename T>
bool RegisterBlendTreeInputSocket(const std::string& socket_name) {
Socket socket;
socket.m_name = socket_name;
socket.m_type = GetSocketType<T>();
socket.m_type_size = sizeof(T);
return RegisterBlendTreeInputSocket(socket);
}
bool RegisterBlendTreeInputSocket(const Socket& socket) {
AnimNodeResource* input_node = GetGraphInputNode();
Socket* input_socket = GetGraphInputSocket(socket.m_name.c_str());
if (input_socket != nullptr) {
std::cerr << "Error: cannot register output socket as socket with name '"
<< socket.m_name << "' already exists!" << std::endl;
return false;
}
input_node->m_virtual_socket_accessor->m_outputs.push_back(socket);
m_virtual_socket_accessor->m_inputs =
input_node->m_virtual_socket_accessor->m_outputs;
return true;
}
template <typename T>
bool RegisterBlendTreeOutputSocket(const std::string& socket_name) {
Socket socket;
socket.m_name = socket_name;
socket.m_type = GetSocketType<T>();
socket.m_type_size = sizeof(T);
return RegisterBlendTreeOutputSocket(socket);
}
bool RegisterBlendTreeOutputSocket(const Socket& socket) {
AnimNodeResource* output_node = GetGraphOutputNode();
Socket* output_socket = GetGraphOutputSocket(socket.m_name.c_str());
if (output_socket != nullptr) {
std::cerr << "Error: cannot register output socket as socket with name '"
<< socket.m_name << "' already exists!" << std::endl;
return false;
}
output_node->m_virtual_socket_accessor->m_inputs.push_back(socket);
m_virtual_socket_accessor->m_outputs =
output_node->m_virtual_socket_accessor->m_inputs;
return true;
}
int GetNodeIndex(const AnimNodeResource* node_resource) const { int GetNodeIndex(const AnimNodeResource* node_resource) const {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) { for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
if (m_nodes[i] == node_resource) { if (m_nodes[i] == node_resource) {
@@ -125,14 +225,18 @@ struct BlendTreeResource {
void RemoveConnectionsForSocket( void RemoveConnectionsForSocket(
const AnimNodeResource* node_resource, const AnimNodeResource* node_resource,
const Socket& socket); const Socket& socket);
void RemoveNodeConnections(AnimNodeResource* node_resource); void RemoveNodeConnections(AnimNodeResource* node_resource);
[[maybe_unused]] bool RemoveNode(AnimNodeResource* node_resource); [[maybe_unused]] bool RemoveNode(AnimNodeResource* node_resource);
[[nodiscard]] size_t GetNumNodes() const { return m_nodes.size(); } [[nodiscard]] size_t GetNumNodes() const { return m_nodes.size(); }
[[nodiscard]] AnimNodeResource* GetNode(size_t i) { return m_nodes[i]; } [[nodiscard]] AnimNodeResource* GetNode(size_t i) { return m_nodes[i]; }
[[nodiscard]] const AnimNodeResource* GetNode(size_t i) const { [[nodiscard]] const AnimNodeResource* GetNode(size_t i) const {
return m_nodes[i]; return m_nodes[i];
} }
[[nodiscard]] const std::vector<AnimNodeResource*>& GetNodes() const { [[nodiscard]] const std::vector<AnimNodeResource*>& GetNodes() const {
return m_nodes; return m_nodes;
} }
@@ -172,6 +276,7 @@ struct BlendTreeResource {
const size_t socket_input_index) const; const size_t socket_input_index) const;
std::vector<Socket> GetNodeOutputSockets(const AnimNodeResource* node) const; std::vector<Socket> GetNodeOutputSockets(const AnimNodeResource* node) const;
std::vector<Socket> GetNodeInputSockets(const AnimNodeResource* node) const; std::vector<Socket> GetNodeInputSockets(const AnimNodeResource* node) const;
bool ConnectSockets( bool ConnectSockets(
@@ -265,6 +370,18 @@ struct BlendTreeResource {
} }
private: private:
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
void PrepareBlendTreeIOData(
AnimGraphBlendTree& instance,
NodeSocketDataOffsetMap& node_offset_map) const;
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
void CreateBlendTreeConnectionInstances(
AnimGraphBlendTree& instance,
NodeSocketDataOffsetMap& node_offset_map) const;
void InitGraphConnectors() { void InitGraphConnectors() {
AddNode(AnimNodeResourceFactory("BlendTreeSockets")); AddNode(AnimNodeResourceFactory("BlendTreeSockets"));
AnimNodeResource* output_node = GetGraphOutputNode(); AnimNodeResource* output_node = GetGraphOutputNode();
@@ -281,7 +398,9 @@ struct BlendTreeResource {
m_node_eval_order.clear(); m_node_eval_order.clear();
UpdateNodeEvalOrderRecursive(0); UpdateNodeEvalOrderRecursive(0);
} }
void UpdateNodeEvalOrderRecursive(size_t node_index); void UpdateNodeEvalOrderRecursive(size_t node_index);
void UpdateNodeSubtrees(); void UpdateNodeSubtrees();
std::vector<AnimNodeResource*> m_nodes; std::vector<AnimNodeResource*> m_nodes;
@@ -291,123 +410,12 @@ struct BlendTreeResource {
friend class AnimGraphResource; friend class AnimGraphResource;
}; };
struct StateMachineTransitionResources {
size_t source_state_index = -1;
size_t target_state_index = -1;
float blend_time = 0.f;
bool sync_blend = false;
};
struct StateMachineResource {
std::vector<AnimNodeResource> m_states;
std::vector<StateMachineTransitionResources> m_transitions;
};
struct AnimGraphResource : AnimNodeResource {
explicit AnimGraphResource(AnimGraphType graph_type);
virtual ~AnimGraphResource() { Clear(); };
static constexpr char DefaultAnimOutput[] = "Output";
std::string m_graph_type_name;
BlendTreeResource m_blend_tree_resource;
typedef std::pair<const AnimNodeResource*, std::string> NodeSocketPair;
typedef std::map<NodeSocketPair, int> NodeSocketDataOffsetMap;
StateMachineResource m_state_machine_resource;
void Clear() { m_blend_tree_resource.Reset(); }
[[maybe_unused]] bool SaveToFile(const char* filename) const;
static AnimGraphResource* CreateFromFile(const char* filename);
void CreateBlendTreeInstance(AnimGraphBlendTree& result) const;
template <typename T>
bool RegisterBlendTreeInputSocket(const std::string& socket_name) {
Socket socket;
socket.m_name = socket_name;
socket.m_type = GetSocketType<T>();
socket.m_type_size = sizeof(T);
return RegisterBlendTreeInputSocket(socket);
}
bool RegisterBlendTreeInputSocket(const Socket& socket) {
AnimNodeResource* input_node = m_blend_tree_resource.GetGraphInputNode();
Socket* input_socket =
m_blend_tree_resource.GetGraphInputSocket(socket.m_name.c_str());
if (input_socket != nullptr) {
std::cerr << "Error: cannot register output socket as socket with name '"
<< socket.m_name << "' already exists!" << std::endl;
return false;
}
input_node->m_virtual_socket_accessor->m_outputs.push_back(socket);
m_virtual_socket_accessor->m_inputs =
input_node->m_virtual_socket_accessor->m_outputs;
return true;
}
template <typename T>
bool RegisterBlendTreeOutputSocket(const std::string& socket_name) {
Socket socket;
socket.m_name = socket_name;
socket.m_type = GetSocketType<T>();
socket.m_type_size = sizeof(T);
return RegisterBlendTreeOutputSocket(socket);
}
bool RegisterBlendTreeOutputSocket(const Socket& socket) {
AnimNodeResource* output_node = m_blend_tree_resource.GetGraphOutputNode();
Socket* output_socket =
m_blend_tree_resource.GetGraphOutputSocket(socket.m_name.c_str());
if (output_socket != nullptr) {
std::cerr << "Error: cannot register output socket as socket with name '"
<< socket.m_name << "' already exists!" << std::endl;
return false;
}
output_node->m_virtual_socket_accessor->m_inputs.push_back(socket);
m_virtual_socket_accessor->m_outputs =
output_node->m_virtual_socket_accessor->m_inputs;
return true;
}
void CreateStateMachineInstance(AnimGraphStateMachine& result) const;
private:
// BlendTree
bool SaveBlendTreeResourceToFile(const char* filename) const;
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
void PrepareBlendTreeIOData(
AnimGraphBlendTree& instance,
NodeSocketDataOffsetMap& node_offset_map) const;
void CreateBlendTreeConnectionInstances(
AnimGraphBlendTree& instance,
NodeSocketDataOffsetMap& node_offset_map) const;
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
bool SaveStateMachineResourceToFile(const char* filename) const;
bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data);
};
typedef std::unique_ptr<AnimGraphResource> AnimGraphResourcePtr;
inline AnimNodeResource* AnimNodeResourceFactory( inline AnimNodeResource* AnimNodeResourceFactory(
const std::string& node_type_name) { const std::string& node_type_name) {
AnimNodeResource* result; AnimNodeResource* result;
if (node_type_name == "BlendTree") { if (node_type_name == "BlendTree") {
AnimGraphResource* blend_tree_resource = AnimGraphResource* blend_tree_resource = new BlendTreeResource();
new AnimGraphResource(AnimGraphType::GraphTypeBlendTree);
result = blend_tree_resource; result = blend_tree_resource;
} else { } else {
result = new AnimNodeResource(); result = new AnimNodeResource();
+144
View File
@@ -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
+8 -4
View File
@@ -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>
+206
View File
@@ -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
+78 -85
View File
@@ -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) {
@@ -214,9 +201,15 @@ void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
} }
} }
void AnimGraphEditorRenderSidebar( void BlendTreeEditorRenderSidebar(
BlendTreeResource& blend_tree_resource, BlendTreeResource* blend_tree_resource,
AnimNodeResource* node_resource) { AnimNodeResource* node_resource) {
BlendTreeResource* current_blend_tree_resource =
dynamic_cast<BlendTreeResource*>(
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]);
assert(current_blend_tree_resource != nullptr);
ImGui::Text( ImGui::Text(
"[%s (%2.2f, %2.2f)]", "[%s (%2.2f, %2.2f)]",
node_resource->m_node_type_name.c_str(), node_resource->m_node_type_name.c_str(),
@@ -276,7 +269,7 @@ void AnimGraphEditorRenderSidebar(
} }
} }
if (node_resource == blend_tree_resource.GetGraphOutputNode()) { if (node_resource == blend_tree_resource->GetGraphOutputNode()) {
ImGui::Text("Outputs"); ImGui::Text("Outputs");
// Graph outputs are the inputs of the output node! // Graph outputs are the inputs of the output node!
@@ -319,17 +312,14 @@ void AnimGraphEditorRenderSidebar(
ImGui::PopStyleVar(); ImGui::PopStyleVar();
if (ImGui::Button("+")) { if (ImGui::Button("+")) {
AnimGraphResource* current_graph_resource = current_blend_tree_resource->RegisterBlendTreeOutputSocket<float>(
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
current_graph_resource->RegisterBlendTreeOutputSocket<float>(
"GraphFloatOutput" "GraphFloatOutput"
+ std::to_string(current_graph_resource->m_blend_tree_resource + std::to_string(current_blend_tree_resource->GetGraphOutputNode()
.GetGraphOutputNode()
->m_virtual_socket_accessor->m_inputs.size())); ->m_virtual_socket_accessor->m_inputs.size()));
} }
} }
if (node_resource == blend_tree_resource.GetGraphInputNode()) { if (node_resource == blend_tree_resource->GetGraphInputNode()) {
ImGui::Text("Inputs"); ImGui::Text("Inputs");
// Graph inputs are the outputs of the input node! // Graph inputs are the outputs of the input node!
@@ -346,7 +336,7 @@ void AnimGraphEditorRenderSidebar(
current_graph_resource->m_virtual_socket_accessor->m_inputs = inputs; current_graph_resource->m_virtual_socket_accessor->m_inputs = inputs;
} }
if (ImGui::Button("X")) { if (ImGui::Button("X")) {
blend_tree_resource.RemoveConnectionsForSocket(node_resource, input); blend_tree_resource->RemoveConnectionsForSocket(node_resource, input);
iter = inputs.erase(iter); iter = inputs.erase(iter);
} else { } else {
iter++; iter++;
@@ -355,13 +345,10 @@ void AnimGraphEditorRenderSidebar(
} }
if (ImGui::Button("+")) { if (ImGui::Button("+")) {
AnimGraphResource* current_graph_resource = current_blend_tree_resource->RegisterBlendTreeInputSocket<float>(
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
current_graph_resource->RegisterBlendTreeInputSocket<float>(
"GraphFloatInput" "GraphFloatInput"
+ std::to_string( + std::to_string(current_blend_tree_resource->GetGraphInputNode()
current_graph_resource->m_blend_tree_resource.GetGraphInputNode() ->m_virtual_socket_accessor->m_outputs.size()));
->m_virtual_socket_accessor->m_outputs.size()));
} }
} }
} }
@@ -439,12 +426,15 @@ void BlendTreeEditorNodePopup() {
} }
if (!node_type_name.empty()) { if (!node_type_name.empty()) {
BlendTreeResource* current_blend_tree_resource =
dynamic_cast<BlendTreeResource*>(
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]);
AnimNodeResource* node_resource = AnimNodeResourceFactory(node_type_name); AnimNodeResource* node_resource = AnimNodeResourceFactory(node_type_name);
ax::NodeEditor::SetNodePosition( ax::NodeEditor::SetNodePosition(
ax::NodeEditor::NodeId(node_resource), ax::NodeEditor::NodeId(node_resource),
sEditorState.mousePopupStart); sEditorState.mousePopupStart);
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex] current_blend_tree_resource->AddNode(node_resource);
->m_blend_tree_resource.AddNode(node_resource);
} }
ImGui::EndPopup(); ImGui::EndPopup();
@@ -559,7 +549,7 @@ void AnimGraphEditorBreadcrumbNavigation() {
} }
} }
void HandleConnectionCreation(BlendTreeResource& current_blend_tree) { void BlendTreeHandleConnectionCreation(BlendTreeResource* current_blend_tree) {
if (ax::NodeEditor::BeginCreate()) { if (ax::NodeEditor::BeginCreate()) {
ax::NodeEditor::PinId input_pin_id, output_pin_id; ax::NodeEditor::PinId input_pin_id, output_pin_id;
if (ax::NodeEditor::QueryNewLink(&input_pin_id, &output_pin_id)) { if (ax::NodeEditor::QueryNewLink(&input_pin_id, &output_pin_id)) {
@@ -603,14 +593,14 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
&source_node_index, &source_node_index,
&source_node_socket_index); &source_node_socket_index);
source_node = current_blend_tree.GetNode(source_node_index); source_node = current_blend_tree->GetNode(source_node_index);
if (source_node != nullptr) { if (source_node != nullptr) {
if (source_node->m_virtual_socket_accessor->m_outputs.size() if (source_node->m_virtual_socket_accessor->m_outputs.size()
< source_node_socket_index) { < source_node_socket_index) {
source_node_socket_index = -1; source_node_socket_index = -1;
} else { } else {
source_socket = current_blend_tree.GetNodeOutputSocketByIndex( source_socket = current_blend_tree->GetNodeOutputSocketByIndex(
source_node, source_node,
source_node_socket_index); source_node_socket_index);
} }
@@ -628,14 +618,14 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
&target_node_index, &target_node_index,
&target_node_socket_index); &target_node_socket_index);
target_node = current_blend_tree.GetNode(target_node_index); target_node = current_blend_tree->GetNode(target_node_index);
if (target_node != nullptr) { if (target_node != nullptr) {
if (target_node->m_virtual_socket_accessor->m_inputs.size() if (target_node->m_virtual_socket_accessor->m_inputs.size()
< target_node_socket_index) { < target_node_socket_index) {
target_node_socket_index = -1; target_node_socket_index = -1;
} else { } else {
target_socket = current_blend_tree.GetNodeInputSocketByIndex( target_socket = current_blend_tree->GetNodeInputSocketByIndex(
target_node, target_node,
target_node_socket_index); target_node_socket_index);
} }
@@ -658,14 +648,14 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
if (!source_pin.Invalid && !target_pin.Invalid) { if (!source_pin.Invalid && !target_pin.Invalid) {
if (source_socket == nullptr || target_socket == nullptr if (source_socket == nullptr || target_socket == nullptr
|| !current_blend_tree.IsConnectionValid( || !current_blend_tree->IsConnectionValid(
source_node, source_node,
source_socket->m_name, source_socket->m_name,
target_node, target_node,
target_socket->m_name)) { target_socket->m_name)) {
ax::NodeEditor::RejectNewItem(); ax::NodeEditor::RejectNewItem();
} else if (ax::NodeEditor::AcceptNewItem()) { } else if (ax::NodeEditor::AcceptNewItem()) {
current_blend_tree.ConnectSockets( current_blend_tree->ConnectSockets(
source_node, source_node,
source_socket->m_name, source_socket->m_name,
target_node, target_node,
@@ -678,12 +668,12 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
} }
void BlendTreeRenderNodes( void BlendTreeRenderNodes(
BlendTreeResource& current_blend_tree, BlendTreeResource* current_blend_tree,
ax::NodeEditor::Utilities::BlueprintNodeBuilder& builder) { ax::NodeEditor::Utilities::BlueprintNodeBuilder& builder) {
for (size_t node_index = 0, n = current_blend_tree.GetNumNodes(); for (size_t node_index = 0, n = current_blend_tree->GetNumNodes();
node_index < n; node_index < n;
node_index++) { node_index++) {
AnimNodeResource* node_resource = current_blend_tree.GetNode(node_index); AnimNodeResource* node_resource = current_blend_tree->GetNode(node_index);
ax::NodeEditor::NodeId node_id(node_resource); ax::NodeEditor::NodeId node_id(node_resource);
@@ -706,7 +696,7 @@ void BlendTreeRenderNodes(
// Inputs // Inputs
std::vector<Socket> node_inputs = std::vector<Socket> node_inputs =
current_blend_tree.GetNodeInputSockets(node_resource); current_blend_tree->GetNodeInputSockets(node_resource);
for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) { for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) {
Socket& socket = node_inputs[j]; Socket& socket = node_inputs[j];
ax::NodeEditor::PinId input_pin = NodeIndexAndSocketIndexToInputPinId( ax::NodeEditor::PinId input_pin = NodeIndexAndSocketIndexToInputPinId(
@@ -718,7 +708,7 @@ void BlendTreeRenderNodes(
DrawSocketIcon( DrawSocketIcon(
socket.m_type, socket.m_type,
current_blend_tree.IsSocketConnected(node_resource, socket.m_name)); current_blend_tree->IsSocketConnected(node_resource, socket.m_name));
ImGui::Spring(0); ImGui::Spring(0);
//ImGui::PushItemWidth(100.0f); //ImGui::PushItemWidth(100.0f);
@@ -729,7 +719,7 @@ void BlendTreeRenderNodes(
// Outputs // Outputs
std::vector<Socket> node_outputs = std::vector<Socket> node_outputs =
current_blend_tree.GetNodeOutputSockets(node_resource); current_blend_tree->GetNodeOutputSockets(node_resource);
for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) { for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) {
Socket& socket = node_outputs[j]; Socket& socket = node_outputs[j];
builder.Output(NodeIndexAndSocketIndexToOutputPinId( builder.Output(NodeIndexAndSocketIndexToOutputPinId(
@@ -741,7 +731,7 @@ void BlendTreeRenderNodes(
ImGui::Spring(0); ImGui::Spring(0);
DrawSocketIcon( DrawSocketIcon(
socket.m_type, socket.m_type,
current_blend_tree.IsSocketConnected(node_resource, socket.m_name)); current_blend_tree->IsSocketConnected(node_resource, socket.m_name));
builder.EndOutput(); builder.EndOutput();
} }
@@ -754,21 +744,21 @@ void BlendTreeRenderNodes(
} }
} }
void BlendTreeRenderConnections(BlendTreeResource& current_blend_tree) { void BlendTreeRenderConnections(BlendTreeResource* current_blend_tree) {
for (size_t connection_id = 0, n = current_blend_tree.GetNumConnections(); for (size_t connection_id = 0, n = current_blend_tree->GetNumConnections();
connection_id < n; connection_id < n;
connection_id++) { connection_id++) {
const BlendTreeConnectionResource* connection_resource = const BlendTreeConnectionResource* connection_resource =
current_blend_tree.GetConnection(connection_id); current_blend_tree->GetConnection(connection_id);
const AnimNodeResource* source_node_resource = const AnimNodeResource* source_node_resource =
current_blend_tree.GetNode(connection_resource->source_node_index); current_blend_tree->GetNode(connection_resource->source_node_index);
int source_socket_index = int source_socket_index =
source_node_resource->m_virtual_socket_accessor->GetOutputIndex( source_node_resource->m_virtual_socket_accessor->GetOutputIndex(
connection_resource->source_socket_name.c_str()); connection_resource->source_socket_name.c_str());
const AnimNodeResource* target_node_resource = const AnimNodeResource* target_node_resource =
current_blend_tree.GetNode(connection_resource->target_node_index); current_blend_tree->GetNode(connection_resource->target_node_index);
int target_socket_index = int target_socket_index =
target_node_resource->m_virtual_socket_accessor->GetInputIndex( target_node_resource->m_virtual_socket_accessor->GetInputIndex(
connection_resource->target_socket_name.c_str()); connection_resource->target_socket_name.c_str());
@@ -786,16 +776,17 @@ void BlendTreeRenderConnections(BlendTreeResource& current_blend_tree) {
target_socket_pin_id); target_socket_pin_id);
} }
} }
void AnimGraphEditorDebugWidget() {
void BlendTreeEditorDebugWidget() {
ImGui::Begin("Connection Debug Panel"); ImGui::Begin("Connection Debug Panel");
ImGui::BeginTable("Connection", 3); ImGui::BeginTable("Connection", 3);
ImGui::TableNextRow(); ImGui::TableNextRow();
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);
@@ -882,17 +873,19 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
graph_size.y -= 20; graph_size.y -= 20;
ax::NodeEditor::Begin("Graph Editor", graph_size); ax::NodeEditor::Begin("Graph Editor", graph_size);
AnimGraphResource* current_graph = BlendTreeResource* current_blend_tree_resource =
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]; dynamic_cast<BlendTreeResource*>(
BlendTreeResource& current_blend_tree = current_graph->m_blend_tree_resource; sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]);
ax::NodeEditor::Utilities::BlueprintNodeBuilder builder; if (current_blend_tree_resource) {
ax::NodeEditor::Utilities::BlueprintNodeBuilder builder;
BlendTreeRenderNodes(current_blend_tree, builder); BlendTreeRenderNodes(current_blend_tree_resource, builder);
BlendTreeRenderConnections(current_blend_tree); BlendTreeRenderConnections(current_blend_tree_resource);
BlendTreeHandleConnectionCreation(current_blend_tree_resource);
HandleConnectionCreation(current_blend_tree); BlendTreeEditorNodePopup();
BlendTreeEditorNodePopup(); }
ax::NodeEditor::End(); ax::NodeEditor::End();
@@ -903,22 +896,22 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
// //
ImGui::TableSetColumnIndex(1); ImGui::TableSetColumnIndex(1);
if (ax::NodeEditor::GetSelectedObjectCount() > 0) { if (current_blend_tree_resource
&& ax::NodeEditor::GetSelectedObjectCount() > 0) {
ax::NodeEditor::NodeId selected_node_id = 0; ax::NodeEditor::NodeId selected_node_id = 0;
ax::NodeEditor::GetSelectedNodes(&selected_node_id, 1); ax::NodeEditor::GetSelectedNodes(&selected_node_id, 1);
if (selected_node_id.Get() != 0) { if (selected_node_id.Get() != 0) {
AnimGraphEditorRenderSidebar( BlendTreeEditorRenderSidebar(
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex] current_blend_tree_resource,
->m_blend_tree_resource,
selected_node_id.AsPointer<AnimNodeResource>()); selected_node_id.AsPointer<AnimNodeResource>());
} }
} }
ImGui::EndTable(); ImGui::EndTable();
AnimGraphEditorDebugWidget(); BlendTreeEditorDebugWidget();
// Clear flag, however it may be re-set further down when handling double // Clear flag, however it may be re-set further down when handling double
// clicking into subgraphs. // clicking into subgraphs.
@@ -962,15 +955,14 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
BlendTreeConnectionResource* connection_resource = BlendTreeConnectionResource* connection_resource =
hovered_link.AsPointer<BlendTreeConnectionResource>(); hovered_link.AsPointer<BlendTreeConnectionResource>();
if (connection_resource && ImGui::IsKeyPressed(ImGuiKey_Delete)) { if (connection_resource && current_blend_tree_resource
BlendTreeResource* blend_tree_resource = && ImGui::IsKeyPressed(ImGuiKey_Delete)) {
&sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex] current_blend_tree_resource->DisconnectSockets(
->m_blend_tree_resource; current_blend_tree_resource->GetNode(
connection_resource->source_node_index),
blend_tree_resource->DisconnectSockets(
blend_tree_resource->GetNode(connection_resource->source_node_index),
connection_resource->source_socket_name, connection_resource->source_socket_name,
blend_tree_resource->GetNode(connection_resource->target_node_index), current_blend_tree_resource->GetNode(
connection_resource->target_node_index),
connection_resource->target_socket_name); connection_resource->target_socket_name);
ax::NodeEditor::DeleteLink(hovered_link); ax::NodeEditor::DeleteLink(hovered_link);
@@ -982,19 +974,20 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
AnimNodeResource* node_resource = AnimNodeResource* node_resource =
hovered_node.AsPointer<AnimNodeResource>(); hovered_node.AsPointer<AnimNodeResource>();
if (node_resource && ImGui::IsKeyPressed(ImGuiKey_Delete)) { if (node_resource && current_blend_tree_resource
AnimGraphResource* current_graph_resource = && ImGui::IsKeyPressed(ImGuiKey_Delete)) {
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]; current_blend_tree_resource->RemoveNodeConnections(node_resource);
current_blend_tree_resource->RemoveNode(node_resource);
current_graph_resource->m_blend_tree_resource.RemoveNodeConnections(
node_resource);
current_graph_resource->m_blend_tree_resource.RemoveNode(node_resource);
} }
} }
ax::NodeEditor::SetCurrentEditor(nullptr); ax::NodeEditor::SetCurrentEditor(nullptr);
} }
void AnimGraphEditorGetRuntimeGraph(AnimGraphBlendTree& blend_tree) { void AnimGraphEditorGetRuntimeBlendTree(AnimGraphBlendTree& blend_tree) {
sEditorState.rootGraphResource->CreateBlendTreeInstance(blend_tree); BlendTreeResource* root_blend_tree_resource =
dynamic_cast<BlendTreeResource*>(sEditorState.rootGraphResource);
assert(root_blend_tree_resource);
root_blend_tree_resource->CreateBlendTreeInstance(blend_tree);
} }
+1 -1
View File
@@ -83,6 +83,6 @@ void AnimGraphEditorClear();
void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context); void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context);
void AnimGraphEditorGetRuntimeGraph(AnimGraphBlendTree& anim_graph); void AnimGraphEditorGetRuntimeBlendTree(AnimGraphBlendTree& anim_graph);
#endif //ANIMTESTBED_ANIMGRAPHEDITOR_H #endif //ANIMTESTBED_ANIMGRAPHEDITOR_H
+21 -20
View File
@@ -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
View File
@@ -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
View File
@@ -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) {}
+6 -69
View File
@@ -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];
}
} }
+3 -3
View File
@@ -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
View File
@@ -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
+315 -456
View File
@@ -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);
@@ -745,7 +720,7 @@ int main() {
if (ImGui::Button("Update Runtime Graph")) { if (ImGui::Button("Update Runtime Graph")) {
anim_graph.dealloc(); anim_graph.dealloc();
AnimGraphEditorGetRuntimeGraph(anim_graph); AnimGraphEditorGetRuntimeBlendTree(anim_graph);
anim_graph_context.m_skeleton = &skinned_mesh.m_skeleton; anim_graph_context.m_skeleton = &skinned_mesh.m_skeleton;
anim_graph.Init(anim_graph_context); anim_graph.Init(anim_graph_context);
@@ -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;
}
}
}
+45
View File
@@ -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());
}
}
+169 -61
View File
@@ -2,25 +2,31 @@
// Created by martin on 04.02.22. // Created by martin on 04.02.22.
// //
#include "AnimGraph/AnimGraph.h" #include "AnimGraph/AnimGraphBlendTree.h"
#include "AnimGraph/AnimGraphBlendTreeResource.h" #include "AnimGraph/AnimGraphResource.h"
#include "AnimGraph/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"
#include "ozz/animation/offline/raw_skeleton.h" #include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/animation/offline/skeleton_builder.h" #include "ozz/animation/offline/skeleton_builder.h"
#include "ozz/animation/runtime/animation.h" #include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/sampling_job.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"
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;
@@ -63,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;
@@ -120,99 +126,201 @@ 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;
} }
TEST_CASE_METHOD( TEST_CASE_METHOD(
SimpleAnimFixture, SimpleAnimFixture,
"AnimGraphSimpleEval", "AnimGraphSimpleEval",
"[AnimGraphEvalTests]") { "[AnimGraphEvalTests]") {
AnimGraphBlendTreeResource graph_resource; BlendTreeResource* blend_tree_resource =
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
// Add nodes // Add nodes
size_t trans_x_node_index = size_t trans_x_node_index =
graph_resource.addNode(AnimNodeResourceFactory("AnimSampler")); blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
size_t trans_y_node_index = size_t trans_y_node_index =
graph_resource.addNode(AnimNodeResourceFactory("AnimSampler")); blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
size_t blend_node_index = size_t blend_node_index =
graph_resource.addNode(AnimNodeResourceFactory("Blend2")); blend_tree_resource->AddNode(AnimNodeResourceFactory("Blend2"));
// Setup nodes // Setup nodes
AnimNodeResource& trans_x_node = graph_resource.m_nodes[trans_x_node_index]; AnimNodeResource* trans_x_node =
trans_x_node.m_socket_accessor->SetPropertyValue("Filename", std::string("trans_x")); blend_tree_resource->GetNode(trans_x_node_index);
trans_x_node.m_name = "trans_x"; trans_x_node->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("trans_x"));
trans_x_node->m_name = "trans_x";
AnimNodeResource& trans_y_node = graph_resource.m_nodes[trans_y_node_index]; AnimNodeResource* trans_y_node =
trans_y_node.m_socket_accessor->SetPropertyValue("Filename", std::string("trans_y")); blend_tree_resource->GetNode(trans_y_node_index);
trans_y_node.m_name = "trans_y"; trans_y_node->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("trans_y"));
trans_y_node->m_name = "trans_y";
AnimNodeResource& blend_node = graph_resource.m_nodes[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 blend_tree_resource->RegisterBlendTreeInputSocket<float>("GraphFloatInput");
AnimNodeResource& graph_output_node = graph_resource.getGraphOutputNode();
graph_output_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
AnimNodeResource& graph_input_node =
graph_resource.getGraphInputNode();
graph_input_node.m_socket_accessor->RegisterOutput<float>(
"GraphFloatInput",
nullptr);
// Wire up nodes // Wire up nodes
graph_resource.connectSockets(trans_x_node, "Output", blend_node, "Input0"); CHECK(blend_tree_resource
graph_resource.connectSockets(trans_y_node, "Output", blend_node, "Input1"); ->ConnectSockets(trans_x_node, "Output", blend_node, "Input0"));
graph_resource.connectSockets( CHECK(blend_tree_resource
->ConnectSockets(trans_y_node, "Output", blend_node, "Input1"));
CHECK(blend_tree_resource->ConnectSockets(
blend_node, blend_node,
"Output", "Output",
graph_resource.getGraphOutputNode(), blend_tree_resource->GetGraphOutputNode(),
"GraphOutput"); "Output"));
REQUIRE(graph_resource.connectSockets(graph_input_node, "GraphFloatInput", blend_node, "Weight"));
CHECK(blend_tree_resource->ConnectSockets(
blend_tree_resource->GetGraphInputNode(),
"GraphFloatInput",
blend_node,
"Weight"));
// 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
AnimGraph graph; AnimGraphBlendTree blend_tree;
graph_resource.createInstance(graph); blend_tree_resource->CreateBlendTreeInstance(blend_tree);
graph.init(graph_context);
blend_tree.Init(graph_context);
// Get runtime graph inputs and outputs // Get runtime graph inputs and outputs
float graph_float_input = 0.f; float graph_float_input = 0.f;
graph.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());
graph.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);
graph.markActiveNodes(); WHEN("Blend Weight == 0.") {
CHECK(graph.m_nodes[trans_x_node_index]->m_state == AnimNodeEvalState::Activated); // Evaluate graph
CHECK(graph.m_nodes[trans_y_node_index]->m_state == AnimNodeEvalState::Activated); graph_float_input = 0.f;
CHECK(graph.m_nodes[blend_node_index]->m_state == AnimNodeEvalState::Activated);
graph.updateTime(0.5f); blend_tree.StartUpdateTick();
graph.evaluate(graph_context); blend_tree.MarkActiveInputs({});
CHECK(graph_anim_output.m_local_matrices[0].translation.x[0] == Approx(0.5).margin(0.1)); THEN("Only Blend2 and first input of Blend2 node is active.") {
CHECK(graph_anim_output.m_local_matrices[0].translation.y[0] == Approx(0.05).margin(0.01)); 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);
}
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.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;
} }
+181 -229
View File
@@ -11,22 +11,26 @@
#include "ozz/base/io/stream.h" #include "ozz/base/io/stream.h"
#include "ozz/base/log.h" #include "ozz/base/log.h"
class SimpleAnimSamplerGraphResource { class BlendTreeResourceFixture {
protected: public:
AnimGraphResource graph_resource; BlendTreeResourceFixture() {
blend_tree_resource =
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
}
virtual ~BlendTreeResourceFixture() { delete blend_tree_resource; }
BlendTreeResource* blend_tree_resource = nullptr; BlendTreeResource* blend_tree_resource = nullptr;
};
class SimpleAnimSamplerBlendTreeResourceFixture
: public BlendTreeResourceFixture {
protected:
size_t walk_node_index = -1; size_t walk_node_index = -1;
AnimNodeResource* walk_node = nullptr; AnimNodeResource* walk_node = nullptr;
public: public:
SimpleAnimSamplerGraphResource() SimpleAnimSamplerBlendTreeResourceFixture() {
: graph_resource(AnimGraphType::GraphTypeBlendTree) {
graph_resource.m_name = "AnimSamplerBlendTree";
graph_resource.m_node_type_name = "BlendTree";
graph_resource.m_graph_type_name = "BlendTree";
blend_tree_resource = &graph_resource.m_blend_tree_resource;
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
walk_node_index = walk_node_index =
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler")); blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
@@ -37,7 +41,7 @@ class SimpleAnimSamplerGraphResource {
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);
@@ -49,10 +53,8 @@ class SimpleAnimSamplerGraphResource {
} }
}; };
class Blend2GraphResource { class Blend2BlendTreeResource : public BlendTreeResourceFixture {
protected: protected:
AnimGraphResourcePtr graph_resource;
BlendTreeResource* blend_tree_resource = nullptr;
size_t walk_node_index = -1; size_t walk_node_index = -1;
size_t run_node_index = -1; size_t run_node_index = -1;
size_t blend_node_index = -1; size_t blend_node_index = -1;
@@ -61,13 +63,8 @@ class Blend2GraphResource {
AnimNodeResource* blend_node = nullptr; AnimNodeResource* blend_node = nullptr;
public: public:
Blend2GraphResource() Blend2BlendTreeResource() {
: graph_resource( blend_tree_resource->m_name = "WalkRunBlendGraph";
dynamic_cast<AnimGraphResource*>(
AnimNodeResourceFactory("BlendTree"))) {
graph_resource->m_name = "WalkRunBlendGraph";
blend_tree_resource = &graph_resource->m_blend_tree_resource;
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
walk_node_index = walk_node_index =
@@ -129,12 +126,8 @@ class Blend2GraphResource {
// | | // | |
// +----------------------------------------+ // +----------------------------------------+
// //
class EmbeddedBlendTreeGraphResource { class EmbeddedBlendTreeGraphResource : public BlendTreeResourceFixture {
protected: protected:
AnimGraphResource parent_graph_resource;
BlendTreeResource* parent_blend_tree_resource = nullptr;
AnimGraphResource* embedded_graph = nullptr;
BlendTreeResource* embedded_blend_tree_resource = nullptr; BlendTreeResource* embedded_blend_tree_resource = nullptr;
size_t walk_node_index = -1; size_t walk_node_index = -1;
@@ -143,19 +136,14 @@ class EmbeddedBlendTreeGraphResource {
size_t embedded_speed_scale_index = -1; size_t embedded_speed_scale_index = -1;
public: public:
EmbeddedBlendTreeGraphResource() EmbeddedBlendTreeGraphResource() {
: parent_graph_resource(AnimGraphType::GraphTypeBlendTree) { blend_tree_resource->m_name = "ParentBlendTree";
parent_graph_resource.m_name = "ParentBlendTree";
parent_graph_resource.m_graph_type_name = "BlendTree";
parent_graph_resource.m_node_type_name = "BlendTree";
parent_blend_tree_resource = &parent_graph_resource.m_blend_tree_resource;
// Parent AnimSampler // Parent AnimSampler
walk_node_index = parent_blend_tree_resource->AddNode( walk_node_index =
AnimNodeResourceFactory("AnimSampler")); blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
walk_node_resource = parent_blend_tree_resource->GetNode(walk_node_index); walk_node_resource = blend_tree_resource->GetNode(walk_node_index);
walk_node_resource->m_name = "WalkAnim"; walk_node_resource->m_name = "WalkAnim";
walk_node_resource->m_virtual_socket_accessor->SetPropertyValue( walk_node_resource->m_virtual_socket_accessor->SetPropertyValue(
"Filename", "Filename",
@@ -164,26 +152,23 @@ class EmbeddedBlendTreeGraphResource {
// //
// Embedded Tree // Embedded Tree
// //
embedded_blend_tree_node_index = parent_blend_tree_resource->AddNode( embedded_blend_tree_node_index =
AnimNodeResourceFactory("BlendTree")); blend_tree_resource->AddNode(AnimNodeResourceFactory("BlendTree"));
embedded_graph = dynamic_cast<AnimGraphResource*>( embedded_blend_tree_resource = dynamic_cast<BlendTreeResource*>(
parent_blend_tree_resource->GetNode(embedded_blend_tree_node_index)); blend_tree_resource->GetNode(embedded_blend_tree_node_index));
embedded_graph->m_name = "EmbeddedBlendTree"; embedded_blend_tree_resource->m_name = "EmbeddedBlendTree";
embedded_graph->m_node_type_name = "BlendTree";
embedded_graph->m_graph_type_name = "BlendTree";
embedded_blend_tree_resource = &embedded_graph->m_blend_tree_resource;
// 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);
@@ -210,15 +195,15 @@ class EmbeddedBlendTreeGraphResource {
// Parent: setup connections // Parent: setup connections
const AnimNodeResource* parent_blend_tree_outputs = const AnimNodeResource* parent_blend_tree_outputs =
parent_blend_tree_resource->GetGraphOutputNode(); blend_tree_resource->GetGraphOutputNode();
REQUIRE(parent_blend_tree_resource->ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
walk_node_resource, walk_node_resource,
"Output", "Output",
embedded_graph, embedded_blend_tree_resource,
"AnimInput")); "AnimInput"));
REQUIRE(parent_blend_tree_resource->ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
embedded_graph, embedded_blend_tree_resource,
"AnimOutput", "AnimOutput",
parent_blend_tree_outputs, parent_blend_tree_outputs,
"Output")); "Output"));
@@ -238,12 +223,8 @@ class EmbeddedBlendTreeGraphResource {
// | | // | |
// +----------------------------------------+ // +----------------------------------------+
// //
class EmbeddedTreeBlend2GraphResource { class EmbeddedTreeBlend2GraphResource : public BlendTreeResourceFixture {
protected: protected:
AnimGraphResource parent_graph_resource;
BlendTreeResource* parent_blend_tree_resource = nullptr;
AnimGraphResource* embedded_graph = nullptr;
BlendTreeResource* embedded_blend_tree_resource = nullptr; BlendTreeResource* embedded_blend_tree_resource = nullptr;
size_t walk_node_index = -1; size_t walk_node_index = -1;
@@ -256,26 +237,21 @@ class EmbeddedTreeBlend2GraphResource {
AnimNodeResource* embedded_run_node_resource = nullptr; AnimNodeResource* embedded_run_node_resource = nullptr;
public: public:
EmbeddedTreeBlend2GraphResource() EmbeddedTreeBlend2GraphResource() {
: parent_graph_resource(AnimGraphType::GraphTypeBlendTree) { blend_tree_resource->m_name = "ParentBlendTree";
parent_graph_resource.m_name = "ParentBlendTree";
parent_graph_resource.m_graph_type_name = "BlendTree";
parent_graph_resource.m_node_type_name = "BlendTree";
parent_blend_tree_resource = &parent_graph_resource.m_blend_tree_resource;
// Setup parent inputs // Setup parent inputs
AnimNodeResource* parent_blend_tree_inputs = AnimNodeResource* parent_blend_tree_inputs =
parent_blend_tree_resource->GetGraphInputNode(); blend_tree_resource->GetGraphInputNode();
parent_blend_tree_inputs->m_virtual_socket_accessor->RegisterOutput<float>( parent_blend_tree_inputs->m_virtual_socket_accessor->RegisterOutput<float>(
"EmbeddedBlend2Weight", "EmbeddedBlend2Weight",
nullptr); nullptr);
// Parent AnimSampler // Parent AnimSampler
walk_node_index = parent_blend_tree_resource->AddNode( walk_node_index =
AnimNodeResourceFactory("AnimSampler")); blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
walk_node_resource = parent_blend_tree_resource->GetNode(walk_node_index); walk_node_resource = blend_tree_resource->GetNode(walk_node_index);
walk_node_resource->m_name = "WalkAnim"; walk_node_resource->m_name = "WalkAnim";
walk_node_resource->m_virtual_socket_accessor->SetPropertyValue( walk_node_resource->m_virtual_socket_accessor->SetPropertyValue(
"Filename", "Filename",
@@ -284,18 +260,17 @@ class EmbeddedTreeBlend2GraphResource {
// //
// Embedded Tree // Embedded Tree
// //
embedded_blend_tree_node_index = parent_blend_tree_resource->AddNode( embedded_blend_tree_node_index =
AnimNodeResourceFactory("BlendTree")); blend_tree_resource->AddNode(AnimNodeResourceFactory("BlendTree"));
embedded_graph = dynamic_cast<AnimGraphResource*>( embedded_blend_tree_resource = dynamic_cast<BlendTreeResource*>(
parent_blend_tree_resource->GetNode(embedded_blend_tree_node_index)); blend_tree_resource->GetNode(embedded_blend_tree_node_index));
embedded_graph->m_name = "EmbeddedTreeBlend2GraphResource"; embedded_blend_tree_resource->m_name = "EmbeddedTreeBlend2GraphResource";
embedded_graph->m_node_type_name = "BlendTree";
embedded_graph->m_graph_type_name = "BlendTree";
embedded_blend_tree_resource = &embedded_graph->m_blend_tree_resource;
// Embedded: inputs // Embedded: inputs
embedded_graph->RegisterBlendTreeInputSocket<AnimData>("AnimInput"); embedded_blend_tree_resource->RegisterBlendTreeInputSocket<Pose>(
embedded_graph->RegisterBlendTreeInputSocket<float>("BlendWeight"); "AnimInput");
embedded_blend_tree_resource->RegisterBlendTreeInputSocket<float>(
"BlendWeight");
// Embedded nodes // Embedded nodes
embedded_blend2_node_index = embedded_blend_tree_resource->AddNode( embedded_blend2_node_index = embedded_blend_tree_resource->AddNode(
@@ -342,22 +317,22 @@ class EmbeddedTreeBlend2GraphResource {
// Parent: setup connections // Parent: setup connections
AnimNodeResource* parent_blend_tree_outputs = AnimNodeResource* parent_blend_tree_outputs =
parent_blend_tree_resource->GetGraphOutputNode(); blend_tree_resource->GetGraphOutputNode();
REQUIRE(parent_blend_tree_resource->ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
walk_node_resource, walk_node_resource,
"Output", "Output",
embedded_graph, embedded_blend_tree_resource,
"AnimInput")); "AnimInput"));
REQUIRE(parent_blend_tree_resource->ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
embedded_graph, embedded_blend_tree_resource,
AnimGraphResource::DefaultAnimOutput, AnimGraphResource::DefaultAnimOutput,
parent_blend_tree_outputs, parent_blend_tree_outputs,
AnimGraphResource::DefaultAnimOutput)); AnimGraphResource::DefaultAnimOutput));
REQUIRE(parent_blend_tree_resource->ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
parent_blend_tree_inputs, parent_blend_tree_inputs,
"EmbeddedBlend2Weight", "EmbeddedBlend2Weight",
embedded_graph, embedded_blend_tree_resource,
"BlendWeight")); "BlendWeight"));
} }
}; };
@@ -386,6 +361,14 @@ bool load_skeleton(ozz::animation::Skeleton& skeleton, const char* filename) {
void CheckBlendTreeResourcesEqual( void CheckBlendTreeResourcesEqual(
const BlendTreeResource* blend_tree_resource_reference, const BlendTreeResource* blend_tree_resource_reference,
const BlendTreeResource* blend_tree_resource_rhs) { const BlendTreeResource* blend_tree_resource_rhs) {
REQUIRE(
blend_tree_resource_reference->m_node_type_name
== blend_tree_resource_rhs->m_node_type_name);
REQUIRE(blend_tree_resource_reference->m_node_type_name == "BlendTree");
REQUIRE(
blend_tree_resource_reference->m_name == blend_tree_resource_rhs->m_name);
REQUIRE( REQUIRE(
blend_tree_resource_reference->GetNumNodes() blend_tree_resource_reference->GetNumNodes()
== blend_tree_resource_rhs->GetNumNodes()); == blend_tree_resource_rhs->GetNumNodes());
@@ -420,27 +403,6 @@ void CheckBlendTreeResourcesEqual(
} }
} }
void CheckAnimGraphResourceEqual(
const AnimGraphResource& graph_resource_reference,
const AnimGraphResource& graph_resource_rhs) {
REQUIRE(
graph_resource_reference.m_graph_type_name
== graph_resource_rhs.m_graph_type_name);
REQUIRE(graph_resource_reference.m_name == graph_resource_rhs.m_name);
REQUIRE(graph_resource_reference.m_graph_type_name == "BlendTree");
const BlendTreeResource* blend_tree_resource_reference =
&graph_resource_reference.m_blend_tree_resource;
const BlendTreeResource* blend_tree_resource_rhs =
&graph_resource_rhs.m_blend_tree_resource;
CheckBlendTreeResourcesEqual(
blend_tree_resource_reference,
blend_tree_resource_rhs);
}
TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") { TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
int node_id = 3321; int node_id = 3321;
int input_index = 221; int input_index = 221;
@@ -461,24 +423,26 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
} }
TEST_CASE_METHOD( TEST_CASE_METHOD(
SimpleAnimSamplerGraphResource, SimpleAnimSamplerBlendTreeResourceFixture,
"SimpleAnimSamplerGraphResource saving and loading results in same " "SimpleAnimSamplerGraphResource saving and loading results in same "
"resource", "resource",
"[SimpleAnimSamplerGraphResource]") { "[SimpleAnimSamplerGraphResource]") {
graph_resource.SaveToFile("TestGraphAnimSamplerBlendTree.json"); blend_tree_resource->SaveToFile("TestGraphAnimSamplerBlendTree.json");
std::unique_ptr<AnimGraphResource> graph_resource_loaded( std::unique_ptr<BlendTreeResource> blend_tree_resource_loaded(
AnimGraphResource::CreateFromFile("TestGraphAnimSamplerBlendTree.json")); BlendTreeResource::CreateFromFile("TestGraphAnimSamplerBlendTree.json"));
CheckAnimGraphResourceEqual(graph_resource, *graph_resource_loaded); CheckBlendTreeResourcesEqual(
blend_tree_resource,
blend_tree_resource_loaded.get());
} }
TEST_CASE_METHOD( TEST_CASE_METHOD(
SimpleAnimSamplerGraphResource, SimpleAnimSamplerBlendTreeResourceFixture,
"SimpleAnimSamplerGraphResource emulated evaluation", "SimpleAnimSamplerGraphResource emulated evaluation",
"[SimpleAnimSamplerGraphResource]") { "[SimpleAnimSamplerGraphResource]") {
AnimGraphBlendTree anim_graph_blend_tree; AnimGraphBlendTree anim_graph_blend_tree;
graph_resource.CreateBlendTreeInstance(anim_graph_blend_tree); blend_tree_resource->CreateBlendTreeInstance(anim_graph_blend_tree);
AnimGraphContext graph_context; AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton; ozz::animation::Skeleton skeleton;
@@ -518,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,
@@ -537,29 +501,25 @@ TEST_CASE_METHOD(
// Checks that node const inputs are properly set. // Checks that node const inputs are properly set.
// //
TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
AnimGraphResourcePtr graph_resource( BlendTreeResource* blend_tree_resource =
dynamic_cast<AnimGraphResource*>(AnimNodeResourceFactory("BlendTree"))); dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
graph_resource->m_name = "AnimSamplerSpeedScaleGraph"; blend_tree_resource->m_name = "AnimSamplerSpeedScaleBlendTree";
graph_resource->m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource =
graph_resource->m_blend_tree_resource;
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
size_t walk_node_index = size_t walk_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("AnimSampler")); blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
size_t speed_scale_node_index = size_t speed_scale_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("SpeedScale")); blend_tree_resource->AddNode(AnimNodeResourceFactory("SpeedScale"));
AnimNodeResource* walk_node = blend_tree_resource.GetNode(walk_node_index); AnimNodeResource* walk_node = blend_tree_resource->GetNode(walk_node_index);
walk_node->m_name = "WalkAnim"; walk_node->m_name = "WalkAnim";
walk_node->m_virtual_socket_accessor->SetPropertyValue( walk_node->m_virtual_socket_accessor->SetPropertyValue(
"Filename", "Filename",
std::string("media/Walking-loop.ozz")); std::string("media/Walking-loop.ozz"));
AnimNodeResource* speed_scale_node = AnimNodeResource* speed_scale_node =
blend_tree_resource.GetNode(speed_scale_node_index); blend_tree_resource->GetNode(speed_scale_node_index);
speed_scale_node->m_name = "SpeedScale"; speed_scale_node->m_name = "SpeedScale";
float speed_scale_value = 1.35f; float speed_scale_value = 1.35f;
speed_scale_node->m_virtual_socket_accessor->SetInputValue( speed_scale_node->m_virtual_socket_accessor->SetInputValue(
@@ -567,25 +527,23 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
speed_scale_value); speed_scale_value);
blend_tree_resource blend_tree_resource
.ConnectSockets(walk_node, "Output", speed_scale_node, "Input"); ->ConnectSockets(walk_node, "Output", speed_scale_node, "Input");
blend_tree_resource.ConnectSockets( blend_tree_resource->ConnectSockets(
speed_scale_node, speed_scale_node,
"Output", "Output",
blend_tree_resource.GetGraphOutputNode(), blend_tree_resource->GetGraphOutputNode(),
AnimGraphResource::DefaultAnimOutput); AnimGraphResource::DefaultAnimOutput);
graph_resource->SaveToFile( constexpr char filename[] =
"TestGraphAnimSamplerSpeedScaleGraph.animgraph.json"); "TestGraphAnimSamplerSpeedScaleGraph.animgraph.json";
AnimGraphResourcePtr graph_resource_loaded(
AnimGraphResource::CreateFromFile(
"TestGraphAnimSamplerSpeedScaleGraph.animgraph.json"));
BlendTreeResource& blend_tree_resource_loaded = REQUIRE(blend_tree_resource->SaveToFile(filename));
graph_resource_loaded->m_blend_tree_resource; BlendTreeResource* blend_tree_resource_loaded =
BlendTreeResource::CreateFromFile(filename);
Socket* speed_scale_resource_loaded_input = Socket* speed_scale_resource_loaded_input =
blend_tree_resource_loaded.GetNode(speed_scale_node_index) blend_tree_resource_loaded->GetNode(speed_scale_node_index)
->m_virtual_socket_accessor->GetInputSocket("SpeedScale"); ->m_virtual_socket_accessor->GetInputSocket("SpeedScale");
REQUIRE(speed_scale_resource_loaded_input != nullptr); REQUIRE(speed_scale_resource_loaded_input != nullptr);
@@ -594,7 +552,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
Catch::Matchers::WithinAbs(speed_scale_value, 0.1)); Catch::Matchers::WithinAbs(speed_scale_value, 0.1));
AnimGraphBlendTree blend_tree; AnimGraphBlendTree blend_tree;
graph_resource_loaded->CreateBlendTreeInstance(blend_tree); blend_tree_resource_loaded->CreateBlendTreeInstance(blend_tree);
REQUIRE_THAT( REQUIRE_THAT(
*dynamic_cast<SpeedScaleNode*>(blend_tree.m_nodes[speed_scale_node_index]) *dynamic_cast<SpeedScaleNode*>(blend_tree.m_nodes[speed_scale_node_index])
@@ -603,7 +561,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
WHEN("Checking node eval order and node subtrees") { WHEN("Checking node eval order and node subtrees") {
const std::vector<size_t>& eval_order = const std::vector<size_t>& eval_order =
graph_resource_loaded->m_blend_tree_resource.GetNodeEvalOrder(); blend_tree_resource_loaded->GetNodeEvalOrder();
THEN("Walk node gets evaluated before speed scale node") { THEN("Walk node gets evaluated before speed scale node") {
CHECK(eval_order.size() == 2); CHECK(eval_order.size() == 2);
@@ -613,23 +571,26 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
THEN("Subtree of the speed scale node contains only the walk node") { THEN("Subtree of the speed scale node contains only the walk node") {
CHECK( CHECK(
graph_resource_loaded->m_blend_tree_resource blend_tree_resource_loaded
.m_node_inputs_subtree[speed_scale_node_index] ->m_node_inputs_subtree[speed_scale_node_index]
.size() .size()
== 1); == 1);
CHECK( CHECK(
graph_resource_loaded->m_blend_tree_resource blend_tree_resource_loaded
.m_node_inputs_subtree[speed_scale_node_index][0] ->m_node_inputs_subtree[speed_scale_node_index][0]
== walk_node_index); == walk_node_index);
} }
} }
delete blend_tree_resource_loaded;
delete blend_tree_resource;
} }
// //
// Checks that connections additions and removals are properly validated. // Checks that connections additions and removals are properly validated.
// //
TEST_CASE_METHOD( TEST_CASE_METHOD(
Blend2GraphResource, Blend2BlendTreeResource,
"Connectivity Tests", "Connectivity Tests",
"[AnimGraphResource][Blend2GraphResource]") { "[AnimGraphResource][Blend2GraphResource]") {
INFO("Removing Blend2 -> Output Connection") INFO("Removing Blend2 -> Output Connection")
@@ -704,18 +665,16 @@ TEST_CASE("FreeAnimGraphResource", "[Test]") {
} }
TEST_CASE_METHOD( TEST_CASE_METHOD(
Blend2GraphResource, Blend2BlendTreeResource,
"Blend2GraphResource saving and loading results in same resource", "Blend2GraphResource saving and loading results in same resource",
"[Blend2GraphResource]") { "[Blend2GraphResource]") {
graph_resource->SaveToFile("TestGraphBlend2Graph.animgraph.json"); constexpr char filename[] = "TestGraphBlend2Graph.animgraph.json";
AnimGraphResourcePtr graph_resource_loaded(
AnimGraphResource::CreateFromFile("TestGraphBlend2Graph.animgraph.json"));
CheckAnimGraphResourceEqual(*graph_resource, *graph_resource_loaded);
REQUIRE(blend_tree_resource->SaveToFile(filename));
BlendTreeResource* blend_tree_resource_loaded = BlendTreeResource* blend_tree_resource_loaded =
&graph_resource_loaded->m_blend_tree_resource; BlendTreeResource::CreateFromFile(filename);
CheckBlendTreeResourcesEqual(blend_tree_resource, blend_tree_resource_loaded);
// Check that the constant weight of the Blend2 node was properly applied when // Check that the constant weight of the Blend2 node was properly applied when
// loading the resource. // loading the resource.
@@ -729,14 +688,16 @@ TEST_CASE_METHOD(
Catch::Matchers::WithinAbs( Catch::Matchers::WithinAbs(
blend2_node_descriptor_loaded->GetInputValue<float>("Weight"), blend2_node_descriptor_loaded->GetInputValue<float>("Weight"),
0.01)); 0.01));
delete blend_tree_resource_loaded;
} }
TEST_CASE_METHOD( TEST_CASE_METHOD(
Blend2GraphResource, Blend2BlendTreeResource,
"Blend2GraphResource graph unsynced evaluation", "Blend2GraphResource graph unsynced evaluation",
"[Blend2GraphResource]") { "[Blend2GraphResource]") {
AnimGraphBlendTree blend_tree_graph; AnimGraphBlendTree blend_tree_graph;
graph_resource->CreateBlendTreeInstance(blend_tree_graph); blend_tree_resource->CreateBlendTreeInstance(blend_tree_graph);
AnimGraphContext graph_context; AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton; ozz::animation::Skeleton skeleton;
@@ -803,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()
@@ -835,19 +795,16 @@ TEST_CASE_METHOD(
// //
// //
TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
AnimGraphResource graph_resource_origin(AnimGraphType::GraphTypeBlendTree); BlendTreeResource* blend_tree_resource =
graph_resource_origin.m_name = "TestInputOutputGraph"; dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
graph_resource_origin.m_graph_type_name = "BlendTree"; blend_tree_resource->m_name = "TestInputOutputGraph";
BlendTreeResource& blend_tree_resource =
graph_resource_origin.m_blend_tree_resource;
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
size_t float_to_vec3_node_index = blend_tree_resource.AddNode( size_t float_to_vec3_node_index = blend_tree_resource->AddNode(
AnimNodeResourceFactory("MathFloatToVec3Node")); AnimNodeResourceFactory("MathFloatToVec3Node"));
AnimNodeResource* graph_output_node = AnimNodeResource* graph_output_node =
blend_tree_resource.GetGraphOutputNode(); blend_tree_resource->GetGraphOutputNode();
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>( graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
"GraphFloatOutput", "GraphFloatOutput",
nullptr); nullptr);
@@ -856,38 +813,38 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
nullptr); nullptr);
AnimNodeResource* graph_input_node_resource = AnimNodeResource* graph_input_node_resource =
blend_tree_resource.GetGraphInputNode(); blend_tree_resource->GetGraphInputNode();
graph_input_node_resource->m_virtual_socket_accessor->RegisterOutput<float>( graph_input_node_resource->m_virtual_socket_accessor->RegisterOutput<float>(
"GraphFloatInput", "GraphFloatInput",
nullptr); nullptr);
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
AnimNodeResource* float_to_vec3_node_resource = AnimNodeResource* float_to_vec3_node_resource =
blend_tree_resource.GetNode(float_to_vec3_node_index); blend_tree_resource->GetNode(float_to_vec3_node_index);
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node_resource, graph_input_node_resource,
"GraphFloatInput", "GraphFloatInput",
graph_output_node, graph_output_node,
"GraphFloatOutput")); "GraphFloatOutput"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node_resource, graph_input_node_resource,
"GraphFloatInput", "GraphFloatInput",
float_to_vec3_node_resource, float_to_vec3_node_resource,
"Input0")); "Input0"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node_resource, graph_input_node_resource,
"GraphFloatInput", "GraphFloatInput",
float_to_vec3_node_resource, float_to_vec3_node_resource,
"Input1")); "Input1"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node_resource, graph_input_node_resource,
"GraphFloatInput", "GraphFloatInput",
float_to_vec3_node_resource, float_to_vec3_node_resource,
"Input2")); "Input2"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
float_to_vec3_node_resource, float_to_vec3_node_resource,
"Output", "Output",
graph_output_node, graph_output_node,
@@ -895,18 +852,15 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
WHEN("Saving and loading graph resource") { WHEN("Saving and loading graph resource") {
const char* filename = "TestGraphResourceSaveLoadGraphInputs.json"; const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
graph_resource_origin.SaveToFile(filename); REQUIRE(blend_tree_resource->SaveToFile(filename));
AnimGraphResourcePtr graph_resource_loaded( BlendTreeResource* blend_tree_resource_loaded =
AnimGraphResource::CreateFromFile(filename)); BlendTreeResource::CreateFromFile(filename);
BlendTreeResource& graph_blend_tree_loaded =
graph_resource_loaded->m_blend_tree_resource;
const AnimNodeResource* graph_loaded_output_node = const AnimNodeResource* graph_loaded_output_node =
graph_blend_tree_loaded.GetGraphOutputNode(); blend_tree_resource_loaded->GetGraphOutputNode();
const AnimNodeResource* graph_loaded_input_node = const AnimNodeResource* graph_loaded_input_node =
graph_blend_tree_loaded.GetGraphInputNode(); blend_tree_resource_loaded->GetGraphInputNode();
THEN("Graph inputs and outputs must be in loaded resource as well.") { THEN("Graph inputs and outputs must be in loaded resource as well.") {
REQUIRE( REQUIRE(
@@ -935,7 +889,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
WHEN("Instantiating an AnimGraph") { WHEN("Instantiating an AnimGraph") {
AnimGraphBlendTree blend_tree_node; AnimGraphBlendTree blend_tree_node;
graph_resource_loaded->CreateBlendTreeInstance(blend_tree_node); blend_tree_resource_loaded->CreateBlendTreeInstance(blend_tree_node);
float graph_float_input = 123.456f; float graph_float_input = 123.456f;
blend_tree_node.SetInput("GraphFloatInput", &graph_float_input); blend_tree_node.SetInput("GraphFloatInput", &graph_float_input);
@@ -975,7 +929,11 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
} }
} }
} }
delete blend_tree_resource_loaded;
} }
delete blend_tree_resource;
} }
// //
@@ -992,21 +950,18 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
// GraphFloat1Output -> GraphFLoatInputSingle * 3 // GraphFloat1Output -> GraphFLoatInputSingle * 3
// //
TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
AnimGraphResource graph_resource_origin(AnimGraphType::GraphTypeBlendTree); BlendTreeResource* blend_tree_resource =
graph_resource_origin.m_name = "TestSimpleMathGraph"; dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
graph_resource_origin.m_graph_type_name = "BlendTree"; blend_tree_resource->m_name = "TestSimpleMathGraph";
BlendTreeResource& blend_tree_resource =
graph_resource_origin.m_blend_tree_resource;
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
size_t math_add0_node_index = size_t math_add0_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("MathAddNode")); blend_tree_resource->AddNode(AnimNodeResourceFactory("MathAddNode"));
size_t math_add1_node_index = size_t math_add1_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("MathAddNode")); blend_tree_resource->AddNode(AnimNodeResourceFactory("MathAddNode"));
AnimNodeResource* graph_output_node = AnimNodeResource* graph_output_node =
blend_tree_resource.GetGraphOutputNode(); blend_tree_resource->GetGraphOutputNode();
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>( graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
"GraphFloat0Output", "GraphFloat0Output",
@@ -1018,57 +973,57 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
"GraphFloat2Output", "GraphFloat2Output",
nullptr); nullptr);
AnimNodeResource* graph_input_node = blend_tree_resource.GetGraphInputNode(); AnimNodeResource* graph_input_node = blend_tree_resource->GetGraphInputNode();
graph_input_node->m_virtual_socket_accessor->RegisterOutput<float>( graph_input_node->m_virtual_socket_accessor->RegisterOutput<float>(
"GraphFloatInput", "GraphFloatInput",
nullptr); nullptr);
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
AnimNodeResource* math_add0_node = AnimNodeResource* math_add0_node =
blend_tree_resource.GetNode(math_add0_node_index); blend_tree_resource->GetNode(math_add0_node_index);
AnimNodeResource* math_add1_node = AnimNodeResource* math_add1_node =
blend_tree_resource.GetNode(math_add1_node_index); blend_tree_resource->GetNode(math_add1_node_index);
// direct output // direct output
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node, graph_input_node,
"GraphFloatInput", "GraphFloatInput",
graph_output_node, graph_output_node,
"GraphFloat0Output")); "GraphFloat0Output"));
// add0 node // add0 node
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node, graph_input_node,
"GraphFloatInput", "GraphFloatInput",
math_add0_node, math_add0_node,
"Input0")); "Input0"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node, graph_input_node,
"GraphFloatInput", "GraphFloatInput",
math_add0_node, math_add0_node,
"Input1")); "Input1"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
math_add0_node, math_add0_node,
"Output", "Output",
graph_output_node, graph_output_node,
"GraphFloat1Output")); "GraphFloat1Output"));
// add1 node // add1 node
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
math_add0_node, math_add0_node,
"Output", "Output",
math_add1_node, math_add1_node,
"Input0")); "Input0"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
graph_input_node, graph_input_node,
"GraphFloatInput", "GraphFloatInput",
math_add1_node, math_add1_node,
"Input1")); "Input1"));
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource->ConnectSockets(
math_add1_node, math_add1_node,
"Output", "Output",
graph_output_node, graph_output_node,
@@ -1076,14 +1031,14 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
WHEN("Saving and loading graph resource") { WHEN("Saving and loading graph resource") {
const char* filename = "TestGraphResourceSaveLoadGraphInputs.json"; const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
graph_resource_origin.SaveToFile(filename); REQUIRE(blend_tree_resource->SaveToFile(filename));
AnimGraphResourcePtr graph_resource_loaded( BlendTreeResource* blend_tree_resource_loaded =
AnimGraphResource::CreateFromFile(filename)); BlendTreeResource::CreateFromFile(filename);
WHEN("Instantiating an AnimGraph") { WHEN("Instantiating an AnimGraph") {
AnimGraphBlendTree blend_tree; AnimGraphBlendTree blend_tree;
graph_resource_loaded->CreateBlendTreeInstance(blend_tree); blend_tree_resource_loaded->CreateBlendTreeInstance(blend_tree);
float graph_float_input = 123.456f; float graph_float_input = 123.456f;
blend_tree.SetInput("GraphFloatInput", &graph_float_input); blend_tree.SetInput("GraphFloatInput", &graph_float_input);
@@ -1117,7 +1072,11 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
context.freeAnimations(); context.freeAnimations();
} }
} }
delete blend_tree_resource_loaded;
} }
delete blend_tree_resource;
} }
// //
@@ -1137,33 +1096,28 @@ TEST_CASE_METHOD(
"EmbeddedBlendTreeGraphResource saving and loading results in same " "EmbeddedBlendTreeGraphResource saving and loading results in same "
"resource", "resource",
"[EmbeddedBlendTreeGraphResource]") { "[EmbeddedBlendTreeGraphResource]") {
parent_graph_resource.SaveToFile("TestGraphEmbeddedBlendTree.json"); constexpr char filename[] = "TestGraphEmbeddedBlendTree.json";
REQUIRE(blend_tree_resource->SaveToFile(filename));
AnimGraphResourcePtr parent_graph_resource_loaded( BlendTreeResource* blend_tree_resource_loaded =
AnimGraphResource::CreateFromFile("TestGraphEmbeddedBlendTree.json")); BlendTreeResource::CreateFromFile(filename);
// Check the loaded parent graph // Check the loaded parent graph
CheckAnimGraphResourceEqual( CheckBlendTreeResourcesEqual(blend_tree_resource, blend_tree_resource_loaded);
parent_graph_resource,
*parent_graph_resource_loaded);
const BlendTreeResource& parent_blend_tree_resource_loaded =
parent_graph_resource_loaded->m_blend_tree_resource;
// Check the loaded embedded graph // Check the loaded embedded graph
REQUIRE( REQUIRE(
parent_blend_tree_resource_loaded.GetNode(3)->m_node_type_name blend_tree_resource_loaded->GetNode(3)->m_node_type_name == "BlendTree");
== "BlendTree");
const AnimGraphResource* embedded_graph_loaded =
dynamic_cast<const AnimGraphResource*>(
parent_blend_tree_resource_loaded.GetNode(3));
const BlendTreeResource* embedded_blend_tree_resource_loaded = const BlendTreeResource* embedded_blend_tree_resource_loaded =
&embedded_graph_loaded->m_blend_tree_resource; dynamic_cast<const BlendTreeResource*>(
blend_tree_resource_loaded->GetNode(3));
CheckBlendTreeResourcesEqual( CheckBlendTreeResourcesEqual(
embedded_blend_tree_resource, embedded_blend_tree_resource,
embedded_blend_tree_resource_loaded); embedded_blend_tree_resource_loaded);
delete blend_tree_resource_loaded;
} }
TEST_CASE_METHOD( TEST_CASE_METHOD(
@@ -1172,7 +1126,7 @@ TEST_CASE_METHOD(
"[EmbeddedBlendTreeGraphResource]") { "[EmbeddedBlendTreeGraphResource]") {
AnimGraphBlendTree blend_tree; AnimGraphBlendTree blend_tree;
parent_graph_resource.CreateBlendTreeInstance(blend_tree); blend_tree_resource->CreateBlendTreeInstance(blend_tree);
AnimGraphContext graph_context; AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton; ozz::animation::Skeleton skeleton;
@@ -1249,7 +1203,7 @@ TEST_CASE_METHOD(
"[EmbeddedTreeBlend2GraphResource]") { "[EmbeddedTreeBlend2GraphResource]") {
AnimGraphBlendTree blend_tree; AnimGraphBlendTree blend_tree;
parent_graph_resource.CreateBlendTreeInstance(blend_tree); blend_tree_resource->CreateBlendTreeInstance(blend_tree);
AnimGraphContext graph_context; AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton; ozz::animation::Skeleton skeleton;
@@ -1332,21 +1286,19 @@ TEST_CASE_METHOD(
TEST_CASE( TEST_CASE(
"Register AnimGraphResource Blendtree Sockets", "Register AnimGraphResource Blendtree Sockets",
"[AnimGraphResource]") { "[AnimGraphResource]") {
AnimNodeResource* blend_tree_anim_node_resource = BlendTreeResource* blend_tree_resource =
AnimNodeResourceFactory("BlendTree"); dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
AnimGraphResource* blend_tree_graph_resource =
dynamic_cast<AnimGraphResource*>(blend_tree_anim_node_resource);
Socket socket; Socket socket;
socket.m_name = "FloatSocket"; socket.m_name = "FloatSocket";
socket.m_type = SocketType::SocketTypeFloat; socket.m_type = SocketType::SocketTypeFloat;
socket.m_reference.ptr = nullptr; socket.m_reference.ptr = nullptr;
CHECK(blend_tree_graph_resource->RegisterBlendTreeInputSocket(socket)); CHECK(blend_tree_resource->RegisterBlendTreeInputSocket(socket));
CHECK(!blend_tree_graph_resource->RegisterBlendTreeInputSocket(socket)); CHECK(!blend_tree_resource->RegisterBlendTreeInputSocket(socket));
CHECK(blend_tree_graph_resource->RegisterBlendTreeOutputSocket(socket)); CHECK(blend_tree_resource->RegisterBlendTreeOutputSocket(socket));
CHECK(!blend_tree_graph_resource->RegisterBlendTreeOutputSocket(socket)); CHECK(!blend_tree_resource->RegisterBlendTreeOutputSocket(socket));
delete blend_tree_anim_node_resource; delete blend_tree_resource;
} }
+21 -12
View File
@@ -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);
} }
+160 -129
View File
@@ -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") {
float weight = 0.2f;
SyncTrack blended = SyncTrack::Blend(weight, track_A, track_B);
REQUIRE(
blended.m_duration
== (1.0f - weight) * track_A.m_duration
+ weight * track_B.m_duration);
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") {
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[0]
== fmodf(
(1.0f - weight) * (track_B.m_interval_start[0])
+ weight * (track_A.m_interval_start[0] + 1.0f),
1.0f));
REQUIRE(
blended.m_interval_ratio[1]
== (1.0f - weight) * (track_B.m_interval_ratio[1])
+ weight * (track_A.m_interval_ratio[1])
);
REQUIRE(
blended.m_interval_ratio[2]
== (1.0f - weight) * (track_B.m_interval_ratio[2])
+ weight * (track_A.m_interval_ratio[2])
);
} }
} }
} }
TEST_CASE("Sync Track Blending", "[SyncTrack]") {
SyncTrack track_a = SyncTrack::CreateFromMarkers(2.0, {0., 0.6, 1.8});
SyncTrack track_b = SyncTrack::CreateFromMarkers(1.5f, {1.05, 1.35, 0.3});
WHEN("Calculating A's durations") {
CHECK(track_a.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.3));
CHECK(track_a.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.6));
CHECK(track_a.m_interval_duration_ratio[2] == Catch::Detail::Approx(0.1));
}
WHEN("Calculating B's durations") {
CHECK(track_b.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.2));
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));
}
WHEN("Blending two synctracks with weight 0.") {
SyncTrack blended = SyncTrack::Blend(0.f, track_a, track_b);
THEN("Result must equal track_A") { REQUIRE(track_a == blended); }
}
WHEN("Blending two synctracks with weight 1.") {
SyncTrack blended = SyncTrack::Blend(1.f, track_a, track_b);
THEN("Result must equal track_B") { REQUIRE(track_b == blended); }
}
WHEN("Blending with weight 0.2") {
float weight = 0.2f;
SyncTrack blended = SyncTrack::Blend(weight, track_a, track_b);
REQUIRE(
blended.m_duration
== (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]);
}
}
+117
View File
@@ -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
+37
View File
@@ -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