diff --git a/CMakeLists.txt b/CMakeLists.txt index 08c3110..8cacc5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,14 +46,22 @@ add_library(AnimTestbedCode OBJECT src/SyncTrack.cc src/SyncTrack.h src/ozzutils.cc - src/AnimGraph/AnimGraphResource.cc - src/AnimGraph/AnimGraphResource.h + # src/AnimGraph/AnimGraphBlendTreeResource.cc + # src/AnimGraph/AnimGraphBlendTreeResource.h src/AnimGraph/AnimGraph.cc src/AnimGraph/AnimGraph.h src/AnimGraph/AnimGraphNodes.cc src/AnimGraph/AnimGraphNodes.h src/AnimGraph/AnimGraphData.cc - src/AnimGraph/AnimGraphData.h) + src/AnimGraph/AnimGraphData.h + src/AnimGraph/AnimGraphBlendTree.cc + src/AnimGraph/AnimGraphBlendTree.h + src/AnimGraph/AnimGraphStateMachine.cc + src/AnimGraph/AnimGraphStateMachine.h + src/AnimGraph/AnimNode.cc + src/AnimGraph/AnimNode.h + src/AnimGraph/AnimGraphResource.cc + src/AnimGraph/AnimGraphResource.h) target_include_directories( AnimTestbedCode @@ -120,7 +128,7 @@ set(ozz_offline_test_objs target_sources(runtests PRIVATE tests/AnimGraphResourceTests.cc - tests/AnimGraphEvalTests.cc + # tests/AnimGraphEvalTests.cc tests/NodeDescriptorTests.cc tests/SyncTrackTests.cc tests/main.cc diff --git a/src/AnimGraph/AnimGraph.cc b/src/AnimGraph/AnimGraph.cc index 2cc1dc4..0a3edb8 100644 --- a/src/AnimGraph/AnimGraph.cc +++ b/src/AnimGraph/AnimGraph.cc @@ -1,202 +1,3 @@ // // Created by martin on 25.03.22. -// - -#include "AnimGraph.h" - -#include -#include - -bool AnimGraph::init(AnimGraphContext& context) { - context.m_graph = this; - - for (size_t i = 2; i < m_nodes.size(); i++) { - if (!m_nodes[i]->Init(context)) { - return false; - } - } - - for (size_t i = 0; i < m_animdata_blocks.size(); i++) { - int num_soa_joints = context.m_skeleton->num_soa_joints(); - m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints); - } - - return true; -} - -void AnimGraph::updateOrderedNodes() { - m_eval_ordered_nodes.clear(); - updateOrderedNodesRecursive(0); -} - -void AnimGraph::updateOrderedNodesRecursive(int node_index) { - AnimNode* node = m_nodes[node_index]; - const std::vector& node_input_connections = - m_node_input_connections[node_index]; - for (size_t i = 0, n = node_input_connections.size(); i < n; i++) { - int input_node_index = - getAnimNodeIndex(node_input_connections.at(i).m_source_node); - - if (input_node_index == 1) { - continue; - } - - updateOrderedNodesRecursive(input_node_index); - } - - if (node_index != 0) { - // In case we have multiple output connections from the node we here - // ensure that use the node evaluation that is the furthest away from - // the output. - std::vector::iterator find_iter = std::find( - m_eval_ordered_nodes.begin(), - m_eval_ordered_nodes.end(), - node); - if (find_iter != m_eval_ordered_nodes.end()) { - m_eval_ordered_nodes.erase(find_iter); - } - - m_eval_ordered_nodes.push_back(node); - } -} - -void AnimGraph::markActiveNodes() { - for (size_t i = 0, n = m_nodes.size(); i < n; i++) { - m_nodes[i]->m_state = AnimNodeEvalState::Deactivated; - } - - const std::vector& graph_output_inputs = - m_node_input_connections[0]; - for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) { - const AnimGraphConnection& graph_input = graph_output_inputs[i]; - AnimNode* node = graph_input.m_source_node; - if (node != nullptr) { - node->m_state = AnimNodeEvalState::Activated; - } - } - - for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; i--) { - AnimNode* node = m_eval_ordered_nodes[i]; - if (checkIsNodeActive(node)) { - int node_index = node->m_index; - node->MarkActiveInputs(m_node_input_connections[node_index]); - - // Non-animation data inputs are always active. - for (size_t j = 0, nj = m_node_input_connections[node_index].size(); - j < nj; - j++) { - const AnimGraphConnection& input = - m_node_input_connections[node_index][j]; - if (input.m_source_node != nullptr - && input.m_target_socket.m_type - != SocketType::SocketTypeAnimation) { - input.m_source_node->m_state = AnimNodeEvalState::Activated; - } - } - } - } -} - -void AnimGraph::evalSyncTracks() { - for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) { - AnimNode* node = m_eval_ordered_nodes[i]; - int node_index = node->m_index; - if (node->m_state == AnimNodeEvalState::Deactivated) { - continue; - } - - node->CalcSyncTrack(m_node_input_connections[node_index]); - } -} - -void AnimGraph::updateTime(float dt) { - const std::vector& graph_output_inputs = - m_node_input_connections[0]; - for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) { - AnimNode* node = graph_output_inputs[i].m_source_node; - if (node != nullptr) { - node->UpdateTime(node->m_time_now, node->m_time_now + dt); - } - } - - for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; --i) { - AnimNode* node = m_eval_ordered_nodes[i]; - if (node->m_state != AnimNodeEvalState::TimeUpdated) { - continue; - } - - int node_index = node->m_index; - float node_time_now = node->m_time_now; - float node_time_last = node->m_time_last; - - const std::vector& node_input_connections = - m_node_input_connections[node_index]; - for (size_t i = 0, n = node_input_connections.size(); i < n; i++) { - AnimNode* input_node = node_input_connections[i].m_source_node; - - // Only propagate time updates via animation sockets. - if (input_node != nullptr - && node_input_connections[i].m_target_socket.m_type - == SocketType::SocketTypeAnimation - && input_node->m_state == AnimNodeEvalState::Activated) { - input_node->UpdateTime(node_time_last, node_time_now); - } - } - } -} - -void AnimGraph::evaluate(AnimGraphContext& context) { - for (int i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { - AnimNode* node = m_eval_ordered_nodes[i]; - - if (node->m_state == AnimNodeEvalState::Deactivated) { - continue; - } - - node->Evaluate(context); - } -} - -Socket* AnimGraph::getInputSocket(const std::string& name) { - for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { - AnimGraphConnection& connection = m_node_output_connections[1][i]; - if (connection.m_source_socket.m_name == name) { - return &connection.m_source_socket; - } - } - - return nullptr; -} - -Socket* AnimGraph::getOutputSocket(const std::string& name) { - for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) { - AnimGraphConnection& connection = m_node_input_connections[0][i]; - if (connection.m_target_socket.m_name == name) { - return &connection.m_target_socket; - } - } - - return nullptr; -} - -const Socket* AnimGraph::getInputSocket(const std::string& name) const { - for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { - const AnimGraphConnection& connection = m_node_output_connections[1][i]; - if (connection.m_source_socket.m_name == name) { - return &connection.m_source_socket; - } - } - - return nullptr; -} - -const Socket* AnimGraph::getOutputSocket(const std::string& name) const { - for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) { - const AnimGraphConnection& connection = m_node_input_connections[0][i]; - if (connection.m_target_socket.m_name == name) { - return &connection.m_target_socket; - } - } - - return nullptr; -} \ No newline at end of file +// \ No newline at end of file diff --git a/src/AnimGraph/AnimGraph.h b/src/AnimGraph/AnimGraph.h index 2b5ab7a..2474147 100644 --- a/src/AnimGraph/AnimGraph.h +++ b/src/AnimGraph/AnimGraph.h @@ -5,227 +5,34 @@ #ifndef ANIMTESTBED_ANIMGRAPH_H #define ANIMTESTBED_ANIMGRAPH_H +#include "AnimNode.h" #include "AnimGraphData.h" -#include "AnimGraphNodes.h" // // AnimGraph (Runtime) // struct AnimGraph { - AnimData m_local_transforms; + ~AnimGraph() {} - std::vector m_nodes; - std::vector m_eval_ordered_nodes; - std::vector > m_node_input_connections; - std::vector > m_node_output_connections; - std::vector m_animdata_blocks; - NodeDescriptorBase* m_node_descriptor = nullptr; - char* m_input_buffer = nullptr; - char* m_output_buffer = nullptr; - char* m_connection_data_storage = nullptr; - char* m_const_node_inputs = nullptr; + bool Init(AnimGraphContext& context) { + m_root_node->Init(context); + } + void UpdateTime(float dt) { + m_time_last = m_time_now; + m_time_now = m_time_now + dt; + m_root_node->UpdateTime(m_time_last, m_time_now); + } + void Evaluate(AnimGraphContext& context) { - std::vector& getGraphOutputs() { return m_node_descriptor->m_inputs; } - std::vector& getGraphInputs() { return m_node_descriptor->m_outputs; } - - AnimDataAllocator m_anim_data_allocator; - - ~AnimGraph() { dealloc(); } - - bool init(AnimGraphContext& context); - void dealloc() { - for (size_t i = 0; i < m_animdata_blocks.size(); i++) { - m_animdata_blocks[i]->m_local_matrices.vector::~vector(); - } - m_animdata_blocks.clear(); - - m_node_input_connections.clear(); - m_node_output_connections.clear(); - - delete[] m_input_buffer; - delete[] m_output_buffer; - delete[] m_connection_data_storage; - delete[] m_const_node_inputs; - - for (int i = 0; i < m_nodes.size(); i++) { - delete m_nodes[i]; - } - m_nodes.clear(); - - delete m_node_descriptor; } - void updateOrderedNodes(); - void updateOrderedNodesRecursive(int node_index); - void markActiveNodes(); - bool checkIsNodeActive(AnimNode* node) { - return node->m_state != AnimNodeEvalState::Deactivated; - } + AnimNode* m_root_node = nullptr; - void evalSyncTracks(); - void updateTime(float dt); - void evaluate(AnimGraphContext& context); - void resetNodeStates() { - for (size_t i = 0, n = m_nodes.size(); i < n; i++) { - m_nodes[i]->m_time_now = 0.f; - m_nodes[i]->m_time_last = 0.f; - m_nodes[i]->m_state = AnimNodeEvalState::Undefined; - } - } + float m_time_now = 0.f; + float m_time_last = 0.f; - Socket* getInputSocket(const std::string& name); - Socket* getOutputSocket(const std::string& name); - - const Socket* getInputSocket(const std::string& name) const; - const Socket* getOutputSocket(const std::string& name) const; - - /** Sets the address that is used for the specified AnimGraph input Socket. - * - * @tparam T Type of the Socket. - * @param name Name of the Socket. - * @param value_ptr Pointer where the input is fetched during evaluation. - */ - template - void SetInput(const char* name, T* value_ptr) { - m_node_descriptor->SetOutput(name, value_ptr); - - for (int i = 0; i < m_node_output_connections[1].size(); i++) { - const AnimGraphConnection& graph_input_connection = - m_node_output_connections[1][i]; - - if (graph_input_connection.m_source_socket.m_name == name) { - *graph_input_connection.m_target_socket.m_reference.ptr_ptr = value_ptr; - } - } - } - - /** Sets the address that is used for the specified AnimGraph output Socket. - * - * @tparam T Type 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. - */ - template - void SetOutput(const char* name, T* value_ptr) { - m_node_descriptor->SetInput(name, value_ptr); - - for (int i = 0; i < m_node_input_connections[0].size(); i++) { - const AnimGraphConnection& graph_output_connection = - m_node_input_connections[0][i]; - - if (graph_output_connection.m_target_socket.m_name == name) { - if (graph_output_connection.m_source_node == m_nodes[1] - && graph_output_connection.m_target_node == m_nodes[0]) { - std::cerr << "Error: cannot set output for direct graph input to graph " - "output connections. Use GetOutptPtr for output instead!" - << std::endl; - - return; - } - - *graph_output_connection.m_source_socket.m_reference.ptr_ptr = - value_ptr; - - // Make sure all other output connections of this pin use the same output pointer - int source_node_index = getAnimNodeIndex(graph_output_connection.m_source_node); - for (int j = 0; j < m_node_output_connections[source_node_index].size(); j++) { - const AnimGraphConnection& source_output_connection = m_node_output_connections[source_node_index][j]; - if (source_output_connection.m_target_node == m_nodes[0]) { - continue; - } - - if (source_output_connection.m_source_socket.m_name == graph_output_connection.m_source_socket.m_name) { - *source_output_connection.m_target_socket.m_reference.ptr_ptr = value_ptr; - } - } - } - } - } - - /** Returns the address that is used for the specified AnimGraph output Socket. - * - * This function is needed for connections that directly connect an AnimGraph - * input Socket to an output Socket of the same AnimGraph. - * - * @tparam T Type of the Socket. - * @param name Name of the Socket. - * @return Address that is used for the specified AnimGraph output Socket. - */ - template - T* GetOutputPtr(const char* name) { - for (int i = 0; i < m_node_input_connections[0].size(); i++) { - const AnimGraphConnection& graph_output_connection = - m_node_input_connections[0][i]; - if (graph_output_connection.m_target_socket.m_name == name) { - return static_cast(*graph_output_connection.m_source_socket.m_reference.ptr_ptr); - } - } - - return nullptr; - } - - void* getInputPtr(const std::string& name) const { - const Socket* input_socket = getInputSocket(name); - if (input_socket != nullptr) { - return input_socket->m_reference.ptr; - } - - return nullptr; - } - - void* getOutputPtr(const std::string& name) const { - const Socket* input_socket = getOutputSocket(name); - if (input_socket != nullptr) { - return input_socket->m_reference.ptr; - } - - return nullptr; - } - - int getNodeEvalOrderIndex(const AnimNode* node) { - for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { - if (m_eval_ordered_nodes[i] == node) { - return i; - } - } - - return -1; - } - const AnimNode* getAnimNodeForInput( - size_t node_index, - const std::string& input_name) const { - assert(node_index < m_nodes.size()); - - const std::vector& input_connection = - m_node_input_connections[node_index]; - for (size_t i = 0, n = input_connection.size(); i < n; i++) { - if (input_connection[i].m_target_socket.m_name == input_name) { - return input_connection[i].m_source_node; - } - } - - return nullptr; - } - - AnimNode* getAnimNode(const char* name) { - for (size_t i = 0; i < m_nodes.size(); i++) { - if (m_nodes[i]->m_name == name) { - return m_nodes[i]; - } - } - - return nullptr; - } - - size_t getAnimNodeIndex(AnimNode* node) { - for (size_t i = 0; i < m_nodes.size(); i++) { - if (m_nodes[i] == node) { - return i; - } - } - - return -1; - } + Vec3 m_root_bone_translation = {}; + Quat m_root_bone_rotation = {}; }; -#endif //ANIMTESTBED_ANIMGRAPH_H +#endif // ANIMTESTBED_ANIMGRAPH_H diff --git a/src/AnimGraph/AnimGraphBlendTree.cc b/src/AnimGraph/AnimGraphBlendTree.cc new file mode 100644 index 0000000..8e94d5f --- /dev/null +++ b/src/AnimGraph/AnimGraphBlendTree.cc @@ -0,0 +1,201 @@ +// +// Created by martin on 17.03.24. +// + +#include "AnimGraphBlendTree.h" + +#include +#include + +bool AnimGraphBlendTree::Init(AnimGraphContext& context) { + for (size_t i = 2; i < m_nodes.size(); i++) { + if (!m_nodes[i]->Init(context)) { + return false; + } + } + + for (size_t i = 0; i < m_animdata_blocks.size(); i++) { + int num_soa_joints = context.m_skeleton->num_soa_joints(); + m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints); + } + + return true; +} + +void AnimGraphBlendTree::UpdateOrderedNodes() { + m_eval_ordered_nodes.clear(); + UpdateOrderedNodesRecursive(0); +} + +void AnimGraphBlendTree::UpdateOrderedNodesRecursive(int node_index) { + AnimNode* node = m_nodes[node_index]; + const std::vector& node_input_connections = + m_node_input_connections[node_index]; + for (size_t i = 0, n = node_input_connections.size(); i < n; i++) { + int input_node_index = + GetAnimNodeIndex(node_input_connections.at(i).m_source_node); + + if (input_node_index == 1) { + continue; + } + + UpdateOrderedNodesRecursive(input_node_index); + } + + if (node_index != 0) { + // In case we have multiple output connections from the node we here + // ensure that use the node evaluation that is the furthest away from + // the output. + std::vector::iterator find_iter = std::find( + m_eval_ordered_nodes.begin(), + m_eval_ordered_nodes.end(), + node); + if (find_iter != m_eval_ordered_nodes.end()) { + m_eval_ordered_nodes.erase(find_iter); + } + + m_eval_ordered_nodes.push_back(node); + } +} + +void AnimGraphBlendTree::MarkActiveInputs() { + for (size_t i = 0, n = m_nodes.size(); i < n; i++) { + m_nodes[i]->m_state = AnimNodeEvalState::Deactivated; + } + + const std::vector& graph_output_inputs = + m_node_input_connections[0]; + for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) { + const AnimGraphConnection& graph_input = graph_output_inputs[i]; + AnimNode* node = graph_input.m_source_node; + if (node != nullptr) { + node->m_state = AnimNodeEvalState::Activated; + } + } + + for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; i--) { + AnimNode* node = m_eval_ordered_nodes[i]; + if (checkIsNodeActive(node)) { + node->MarkActiveInputs(); + size_t node_index = GetAnimNodeIndex(node); + + // Non-animation data inputs are always active. + for (size_t j = 0, nj = m_node_input_connections[node_index].size(); + j < nj; + j++) { + const AnimGraphConnection& input = + m_node_input_connections[node_index][j]; + if (input.m_source_node != nullptr + && input.m_target_socket.m_type + != SocketType::SocketTypeAnimation) { + input.m_source_node->m_state = AnimNodeEvalState::Activated; + } + } + } + } +} + +void AnimGraphBlendTree::CalcSyncTrack() { + for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) { + AnimNode* node = m_eval_ordered_nodes[i]; + if (node->m_state == AnimNodeEvalState::Deactivated) { + continue; + } + + node->CalcSyncTrack(); + } +} + +void AnimGraphBlendTree::UpdateTime(float time_last, float time_now) { + float dt = time_now - time_last; + + const std::vector& graph_output_inputs = + m_node_input_connections[0]; + for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) { + AnimNode* node = graph_output_inputs[i].m_source_node; + if (node != nullptr) { + node->UpdateTime(node->m_time_now, node->m_time_now + dt); + } + } + + for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; --i) { + AnimNode* node = m_eval_ordered_nodes[i]; + if (node->m_state != AnimNodeEvalState::TimeUpdated) { + continue; + } + + size_t node_index = GetAnimNodeIndex(node); + float node_time_now = node->m_time_now; + float node_time_last = node->m_time_last; + + const std::vector& node_input_connections = + m_node_input_connections[node_index]; + for (size_t i = 0, n = node_input_connections.size(); i < n; i++) { + AnimNode* input_node = node_input_connections[i].m_source_node; + + // Only propagate time updates via animation sockets. + if (input_node != nullptr + && node_input_connections[i].m_target_socket.m_type + == SocketType::SocketTypeAnimation + && input_node->m_state == AnimNodeEvalState::Activated) { + input_node->UpdateTime(node_time_last, node_time_now); + } + } + } +} + +void AnimGraphBlendTree::Evaluate(AnimGraphContext& context) { + for (int i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { + AnimNode* node = m_eval_ordered_nodes[i]; + + if (node->m_state == AnimNodeEvalState::Deactivated) { + continue; + } + + node->Evaluate(context); + } +} + +Socket* AnimGraphBlendTree::getInputSocket(const std::string& name) { + for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { + AnimGraphConnection& connection = m_node_output_connections[1][i]; + if (connection.m_source_socket.m_name == name) { + return &connection.m_source_socket; + } + } + + return nullptr; +} + +Socket* AnimGraphBlendTree::getOutputSocket(const std::string& name) { + for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) { + AnimGraphConnection& connection = m_node_input_connections[0][i]; + if (connection.m_target_socket.m_name == name) { + return &connection.m_target_socket; + } + } + + return nullptr; +} + +const Socket* AnimGraphBlendTree::getInputSocket(const std::string& name) const { + for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { + const AnimGraphConnection& connection = m_node_output_connections[1][i]; + if (connection.m_source_socket.m_name == name) { + return &connection.m_source_socket; + } + } + + return nullptr; +} + +const Socket* AnimGraphBlendTree::getOutputSocket(const std::string& name) const { + for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) { + const AnimGraphConnection& connection = m_node_input_connections[0][i]; + if (connection.m_target_socket.m_name == name) { + return &connection.m_target_socket; + } + } + + return nullptr; +} \ No newline at end of file diff --git a/src/AnimGraph/AnimGraphBlendTree.h b/src/AnimGraph/AnimGraphBlendTree.h new file mode 100644 index 0000000..b6341e5 --- /dev/null +++ b/src/AnimGraph/AnimGraphBlendTree.h @@ -0,0 +1,234 @@ +// +// Created by martin on 17.03.24. +// + +#ifndef ANIMTESTBED_ANIMGRAPHBLENDTREE_H +#define ANIMTESTBED_ANIMGRAPHBLENDTREE_H + +#include "AnimNode.h" + +// +// AnimGraph (Runtime) +// +struct AnimGraphBlendTree : public AnimNode { + AnimData m_local_transforms; + + std::vector m_nodes; + std::vector m_eval_ordered_nodes; + std::vector > m_node_input_connections; + std::vector > m_node_output_connections; + std::vector m_animdata_blocks; + NodeDescriptorBase* m_node_descriptor = nullptr; + char* m_input_buffer = nullptr; + char* m_output_buffer = nullptr; + char* m_connection_data_storage = nullptr; + char* m_const_node_inputs = nullptr; + + std::vector& getGraphOutputs() { return m_node_descriptor->m_inputs; } + std::vector& getGraphInputs() { return m_node_descriptor->m_outputs; } + + AnimDataAllocator m_anim_data_allocator; + + ~AnimGraphBlendTree() { dealloc(); } + + // AnimNode overrides + bool Init(AnimGraphContext& context); + void MarkActiveInputs() override; + void CalcSyncTrack() override; + void UpdateTime(float time_last, float time_now) override; + void Evaluate(AnimGraphContext& context) override; + + void dealloc() { + for (size_t i = 0; i < m_animdata_blocks.size(); i++) { + m_animdata_blocks[i]->m_local_matrices.vector::~vector(); + } + m_animdata_blocks.clear(); + + m_node_input_connections.clear(); + m_node_output_connections.clear(); + + delete[] m_input_buffer; + delete[] m_output_buffer; + delete[] m_connection_data_storage; + delete[] m_const_node_inputs; + + for (int i = 0; i < m_nodes.size(); i++) { + delete m_nodes[i]; + } + m_nodes.clear(); + + delete m_node_descriptor; + } + + void UpdateOrderedNodes(); + void UpdateOrderedNodesRecursive(int node_index); + bool checkIsNodeActive(AnimNode* node) { + return node->m_state != AnimNodeEvalState::Deactivated; + } + + void ResetNodeStates() { + for (size_t i = 0, n = m_nodes.size(); i < n; i++) { + m_nodes[i]->m_time_now = 0.f; + m_nodes[i]->m_time_last = 0.f; + m_nodes[i]->m_state = AnimNodeEvalState::Undefined; + } + } + + Socket* getInputSocket(const std::string& name); + Socket* getOutputSocket(const std::string& name); + + const Socket* getInputSocket(const std::string& name) const; + const Socket* getOutputSocket(const std::string& name) const; + + /** Sets the address that is used for the specified AnimGraph input Socket. + * + * @tparam T Type of the Socket. + * @param name Name of the Socket. + * @param value_ptr Pointer where the input is fetched during evaluation. + */ + template + void SetInput(const char* name, T* value_ptr) { + m_node_descriptor->SetOutput(name, value_ptr); + + for (int i = 0; i < m_node_output_connections[1].size(); i++) { + const AnimGraphConnection& graph_input_connection = + m_node_output_connections[1][i]; + + if (graph_input_connection.m_source_socket.m_name == name) { + *graph_input_connection.m_target_socket.m_reference.ptr_ptr = value_ptr; + } + } + } + + /** Sets the address that is used for the specified AnimGraph output Socket. + * + * @tparam T Type 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. + */ + template + void SetOutput(const char* name, T* value_ptr) { + m_node_descriptor->SetInput(name, value_ptr); + + for (int i = 0; i < m_node_input_connections[0].size(); i++) { + const AnimGraphConnection& graph_output_connection = + m_node_input_connections[0][i]; + + if (graph_output_connection.m_target_socket.m_name == name) { + if (graph_output_connection.m_source_node == m_nodes[1] + && graph_output_connection.m_target_node == m_nodes[0]) { + std::cerr << "Error: cannot set output for direct graph input to graph " + "output connections. Use GetOutptPtr for output instead!" + << std::endl; + + return; + } + + *graph_output_connection.m_source_socket.m_reference.ptr_ptr = + value_ptr; + + // Make sure all other output connections of this pin use the same output pointer + int source_node_index = + GetAnimNodeIndex(graph_output_connection.m_source_node); + for (int j = 0; j < m_node_output_connections[source_node_index].size(); j++) { + const AnimGraphConnection& source_output_connection = m_node_output_connections[source_node_index][j]; + if (source_output_connection.m_target_node == m_nodes[0]) { + continue; + } + + if (source_output_connection.m_source_socket.m_name == graph_output_connection.m_source_socket.m_name) { + *source_output_connection.m_target_socket.m_reference.ptr_ptr = value_ptr; + } + } + } + } + } + + /** Returns the address that is used for the specified AnimGraph output Socket. + * + * This function is needed for connections that directly connect an AnimGraph + * input Socket to an output Socket of the same AnimGraph. + * + * @tparam T Type of the Socket. + * @param name Name of the Socket. + * @return Address that is used for the specified AnimGraph output Socket. + */ + template + T* GetOutputPtr(const char* name) { + for (int i = 0; i < m_node_input_connections[0].size(); i++) { + const AnimGraphConnection& graph_output_connection = + m_node_input_connections[0][i]; + if (graph_output_connection.m_target_socket.m_name == name) { + return static_cast(*graph_output_connection.m_source_socket.m_reference.ptr_ptr); + } + } + + return nullptr; + } + + void* getInputPtr(const std::string& name) const { + const Socket* input_socket = getInputSocket(name); + if (input_socket != nullptr) { + return input_socket->m_reference.ptr; + } + + return nullptr; + } + + void* getOutputPtr(const std::string& name) const { + const Socket* input_socket = getOutputSocket(name); + if (input_socket != nullptr) { + return input_socket->m_reference.ptr; + } + + return nullptr; + } + + int getNodeEvalOrderIndex(const AnimNode* node) { + for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { + if (m_eval_ordered_nodes[i] == node) { + return i; + } + } + + return -1; + } + const AnimNode* getAnimNodeForInput( + size_t node_index, + const std::string& input_name) const { + assert(node_index < m_nodes.size()); + + const std::vector& input_connection = + m_node_input_connections[node_index]; + for (size_t i = 0, n = input_connection.size(); i < n; i++) { + if (input_connection[i].m_target_socket.m_name == input_name) { + return input_connection[i].m_source_node; + } + } + + return nullptr; + } + + AnimNode* getAnimNode(const char* name) { + for (size_t i = 0; i < m_nodes.size(); i++) { + if (m_nodes[i]->m_name == name) { + return m_nodes[i]; + } + } + + return nullptr; + } + + size_t GetAnimNodeIndex(AnimNode* node) { + for (size_t i = 0; i < m_nodes.size(); i++) { + if (m_nodes[i] == node) { + return i; + } + } + + return -1; + } +}; + + +#endif //ANIMTESTBED_ANIMGRAPHBLENDTREE_H diff --git a/src/AnimGraph/AnimGraphBlendTreeResource.cc b/src/AnimGraph/AnimGraphBlendTreeResource.cc new file mode 100644 index 0000000..cf7e4f7 --- /dev/null +++ b/src/AnimGraph/AnimGraphBlendTreeResource.cc @@ -0,0 +1,616 @@ +// +// Created by martin on 04.02.22. +// + +#include +#include + +#include "3rdparty/json/json.hpp" + +#include "AnimGraphBlendTreeResource.h" +#include "AnimGraphNodes.h" + +using json = nlohmann::json; + +// +// Socket <-> json +// +std::string sSocketTypeToStr(SocketType pin_type) { + if (pin_type < SocketType::SocketTypeUndefined + || pin_type >= SocketType::SocketTypeLast) { + return "Unknown"; + } + + return SocketTypeNames[static_cast(pin_type)]; +} + +json sSocketToJson(const Socket& socket) { + json result; + result["name"] = socket.m_name; + result["type"] = sSocketTypeToStr(socket.m_type); + + if (socket.m_type == SocketType::SocketTypeString + && !socket.m_value_string.empty()) { + result["value"] = socket.m_value_string; + } else if (socket.m_value.flag) { + if (socket.m_type == SocketType::SocketTypeBool) { + result["value"] = socket.m_value.flag; + } else if (socket.m_type == SocketType::SocketTypeAnimation) { + } else if (socket.m_type == SocketType::SocketTypeInt) { + result["value"] = socket.m_value.int_value; + } else if (socket.m_type == SocketType::SocketTypeFloat) { + result["value"] = socket.m_value.float_value; + } else if (socket.m_type == SocketType::SocketTypeVec3) { + result["value"][0] = socket.m_value.vec3.v[0]; + result["value"][1] = socket.m_value.vec3.v[1]; + result["value"][2] = socket.m_value.vec3.v[2]; + } else if (socket.m_type == SocketType::SocketTypeQuat) { + result["value"][0] = socket.m_value.quat.v[0]; + result["value"][1] = socket.m_value.quat.v[1]; + result["value"][2] = socket.m_value.quat.v[2]; + result["value"][3] = socket.m_value.quat.v[3]; + } else { + std::cerr << "Invalid socket type '" << static_cast(socket.m_type) + << "'." << std::endl; + } + } + return result; +} + +Socket sJsonToSocket(const json& json_data) { + Socket result; + result.m_type = SocketType::SocketTypeUndefined; + result.m_reference.ptr = &result.m_value.int_value; + result.m_name = json_data["name"]; + + std::string type_string = json_data["type"]; + bool have_value = json_data.contains("value"); + + if (type_string == "Bool") { + result.m_type = SocketType::SocketTypeBool; + result.m_type_size = sizeof(bool); + result.m_reference.ptr = &result.m_value.int_value; + + if (have_value) { + result.m_value.flag = json_data["value"]; + } + } else if (type_string == "Animation") { + result.m_type = SocketType::SocketTypeAnimation; + result.m_type_size = sizeof(AnimData); + } else if (type_string == "Int") { + result.m_type = SocketType::SocketTypeInt; + result.m_type_size = sizeof(int); + if (have_value) { + result.m_value.int_value = json_data["value"]; + } + } else if (type_string == "Float") { + result.m_type = SocketType::SocketTypeFloat; + result.m_type_size = sizeof(float); + if (have_value) { + result.m_value.float_value = json_data["value"]; + } + } else if (type_string == "Vec3") { + result.m_type = SocketType::SocketTypeVec3; + result.m_type_size = sizeof(Vec3); + if (have_value) { + result.m_value.vec3.x = json_data["value"][0]; + result.m_value.vec3.y = json_data["value"][1]; + result.m_value.vec3.z = json_data["value"][2]; + } + } else if (type_string == "Quat") { + result.m_type = SocketType::SocketTypeQuat; + result.m_type_size = sizeof(Quat); + if (have_value) { + result.m_value.quat.x = json_data["value"][0]; + result.m_value.quat.y = json_data["value"][1]; + result.m_value.quat.z = json_data["value"][2]; + result.m_value.quat.w = json_data["value"][3]; + } + } else if (type_string == "String") { + result.m_type = SocketType::SocketTypeString; + result.m_type_size = sizeof(std::string); + if (have_value) { + result.m_value_string = json_data["value"]; + } + } else { + std::cerr << "Invalid socket type '" << type_string << "'." << std::endl; + } + + return result; +} + +// +// AnimGraphNode <-> json +// +json sAnimGraphNodeToJson( + const AnimNodeResource& node, + size_t node_index, + const std::vector& connections) { + json result; + + result["name"] = node.m_name; + result["type"] = "AnimNodeResource"; + result["node_type"] = node.m_type_name; + + for (size_t j = 0; j < 2; j++) { + result["position"][j] = node.m_position[j]; + } + + for (const auto & socket : node.m_socket_accessor->m_inputs) { + if (socket.m_type == SocketType::SocketTypeAnimation) { + continue; + } + + bool socket_connected = false; + for (const auto & connection : connections) { + if (connection.source_node_index == node_index + && connection.source_socket_name == socket.m_name) { + socket_connected = true; + break; + } + } + + if (!socket_connected) { + result["inputs"].push_back(sSocketToJson(socket)); + } + } + + for (auto & property : node.m_socket_accessor->m_properties) { + result["properties"][property.m_name] = sSocketToJson(property); + } + + return result; +} + +AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index) { + AnimNodeResource result; + + result.m_name = json_node["name"]; + result.m_type_name = json_node["node_type"]; + result.m_position[0] = json_node["position"][0]; + result.m_position[1] = json_node["position"][1]; + + result.m_anim_node = AnimNodeFactory(result.m_type_name); + result.m_socket_accessor = + AnimNodeDescriptorFactory(result.m_type_name, result.m_anim_node); + + for (auto & property : result.m_socket_accessor->m_properties) { + property = sJsonToSocket(json_node["properties"][property.m_name]); + } + + if (node_index != 0 && node_index != 1 && json_node.contains("inputs")) { + for (size_t j = 0, n = json_node["inputs"].size(); j < n; j++) { + assert(json_node["inputs"][j].contains("name")); + std::string input_name = json_node["inputs"][j]["name"]; + Socket* input_socket = + result.m_socket_accessor->GetInputSocket(input_name.c_str()); + if (input_socket == nullptr) { + std::cerr << "Could not find input socket with name " << input_name + << " for node type " << result.m_type_name << std::endl; + abort(); + } + *input_socket = sJsonToSocket(json_node["inputs"][j]); + } + } + + return result; +} + +// +// AnimGraphConnectionResource <-> Json +// +json sAnimGraphConnectionToJson( + const AnimGraphConnectionResource& connection) { + json result; + + result["type"] = "AnimGraphConnectionResource"; + + result["source_node_index"] = connection.source_node_index; + result["source_socket_name"] = connection.source_socket_name; + + result["target_node_index"] = connection.target_node_index; + result["target_socket_name"] = connection.target_socket_name; + + return result; +} + +AnimGraphConnectionResource sAnimGraphConnectionFromJson( + const json& json_node) { + AnimGraphConnectionResource connection; + + connection.source_node_index = json_node["source_node_index"]; + connection.source_socket_name = json_node["source_socket_name"]; + + connection.target_node_index = json_node["target_node_index"]; + connection.target_socket_name = json_node["target_socket_name"]; + + return connection; +} + +void AnimGraphBlendTreeResource::clear() { + m_name = ""; + + clearNodes(); + m_connections.clear(); + + initGraphConnectors(); +} + +void AnimGraphBlendTreeResource::clearNodes() { + for (auto & m_node : m_nodes) { + delete m_node.m_socket_accessor; + m_node.m_socket_accessor = nullptr; + delete m_node.m_anim_node; + m_node.m_anim_node = nullptr; + } + m_nodes.clear(); +} + +void AnimGraphBlendTreeResource::initGraphConnectors() { + m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); + m_nodes[0].m_name = "Outputs"; + m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); + m_nodes[1].m_name = "Inputs"; +} + +bool AnimGraphBlendTreeResource::saveToFile(const char* filename) const { + json result; + + result["name"] = m_name; + result["type"] = "AnimGraphResource"; + + for (size_t i = 0; i < m_nodes.size(); i++) { + const AnimNodeResource& node = m_nodes[i]; + result["nodes"][i] = sAnimGraphNodeToJson(node, i, m_connections); + } + + for (size_t i = 0; i < m_connections.size(); i++) { + const AnimGraphConnectionResource& connection = m_connections[i]; + result["connections"][i] = sAnimGraphConnectionToJson(connection); + } + + // Graph inputs and outputs + { + const AnimNodeResource& graph_output_node = m_nodes[0]; + const std::vector graph_inputs = + graph_output_node.m_socket_accessor->m_inputs; + for (size_t i = 0; i < graph_inputs.size(); i++) { + result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]); + } + + const AnimNodeResource& graph_input_node = m_nodes[1]; + const std::vector graph_outputs = + graph_input_node.m_socket_accessor->m_outputs; + for (size_t i = 0; i < graph_outputs.size(); i++) { + result["nodes"][1]["outputs"][i] = sSocketToJson(graph_outputs[i]); + } + } + + std::ofstream output_file; + output_file.open(filename); + output_file << result.dump(4, ' ') << std::endl; + output_file.close(); + + return true; +} + +bool AnimGraphBlendTreeResource::loadFromFile(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; + } + + if (json_data["type"] != "AnimGraphResource") { + std::cerr + << "Invalid json object. Expected type 'AnimGraphResource' but got '" + << json_data["type"] << "'." << std::endl; + } + + clear(); + clearNodes(); + + m_name = json_data["name"]; + + // Load nodes + for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) { + const json& json_node = json_data["nodes"][i]; + if (json_node["type"] != "AnimNodeResource") { + std::cerr + << "Invalid json object. Expected type 'AnimNodeResource' but got '" + << json_node["type"] << "'." << std::endl; + return false; + } + + AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i); + m_nodes.push_back(node); + } + + // Setup graph inputs and outputs + const json& graph_outputs = json_data["nodes"][0]["inputs"]; + for (const auto & graph_output : graph_outputs) { + AnimNodeResource& graph_node = m_nodes[0]; + graph_node.m_socket_accessor->m_inputs.push_back( + sJsonToSocket(graph_output)); + } + + const json& graph_inputs = json_data["nodes"][1]["outputs"]; + for (const auto & graph_input : graph_inputs) { + AnimNodeResource& graph_node = m_nodes[1]; + graph_node.m_socket_accessor->m_outputs.push_back( + sJsonToSocket(graph_input)); + } + + // Load connections + for (const auto & json_connection : json_data["connections"]) { + if (json_connection["type"] != "AnimGraphConnectionResource") { + std::cerr + << "Invalid json object. Expected type 'AnimGraphConnectionResource' " + "but got '" + << json_connection["type"] << "'." << std::endl; + return false; + } + + AnimGraphConnectionResource connection = + sAnimGraphConnectionFromJson(json_connection); + m_connections.push_back(connection); + } + + return true; +} + +void AnimGraphBlendTreeResource::createRuntimeNodeInstances(AnimGraph& instance) const { + for (int i = 0; i < m_nodes.size(); i++) { + const AnimNodeResource& node_resource = m_nodes[i]; + AnimNode* node = AnimNodeFactory(node_resource.m_type_name); + node->m_name = node_resource.m_name; + node->m_node_type_name = node_resource.m_type_name; + node->m_index = i; + instance.m_nodes.push_back(node); + + // runtime node connections + instance.m_node_input_connections.emplace_back(); + instance.m_node_output_connections.emplace_back(); + + } +} + +void AnimGraphBlendTreeResource::prepareGraphIOData(AnimGraph& instance) const { + instance.m_node_descriptor = + AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]); + instance.m_node_descriptor->m_outputs = + m_nodes[1].m_socket_accessor->m_outputs; + instance.m_node_descriptor->m_inputs = m_nodes[0].m_socket_accessor->m_inputs; + + // + // graph inputs + // + int input_block_size = 0; + std::vector& graph_inputs = instance.getGraphInputs(); + for (int i = 0; i < graph_inputs.size(); i++) { + input_block_size += sizeof(void*); + } + + if (input_block_size > 0) { + instance.m_input_buffer = new char[input_block_size]; + memset(instance.m_input_buffer, 0, input_block_size); + } + + int input_block_offset = 0; + for (int i = 0; i < graph_inputs.size(); i++) { + graph_inputs[i].m_reference.ptr = + (void*)&instance.m_input_buffer[input_block_offset]; + instance.m_node_descriptor->m_outputs[i].m_reference.ptr = + &instance.m_input_buffer[input_block_offset]; + input_block_offset += sizeof(void*); + } + + // + // graph outputs + // + int output_block_size = 0; + std::vector& graph_outputs = instance.getGraphOutputs(); + for (int i = 0; i < graph_outputs.size(); i++) { + output_block_size += sizeof(void*); + } + + if (output_block_size > 0) { + instance.m_output_buffer = new char[output_block_size]; + memset(instance.m_output_buffer, 0, output_block_size); + } + + int output_block_offset = 0; + for (int i = 0; i < graph_outputs.size(); i++) { + instance.m_node_descriptor->m_inputs[i].m_reference.ptr = + &instance.m_output_buffer[output_block_offset]; + output_block_offset += sizeof(void*); + } + + // connections: make source and target sockets point to the same address in the connection data storage. + // TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once. + size_t connection_data_storage_size = 0; + for (const auto & connection : m_connections) { + const AnimNodeResource& source_node = m_nodes[connection.source_node_index]; + Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket( + connection.source_socket_name.c_str()); + connection_data_storage_size += source_socket->m_type_size; + } + + if (connection_data_storage_size > 0) { + instance.m_connection_data_storage = new char[connection_data_storage_size]; + memset(instance.m_connection_data_storage, 0, connection_data_storage_size); + } + + std::vector instance_node_descriptors( + m_nodes.size(), + nullptr); + for (int i = 0; i < m_nodes.size(); i++) { + instance_node_descriptors[i] = AnimNodeDescriptorFactory( + m_nodes[i].m_type_name, + instance.m_nodes[i]); + } + + instance_node_descriptors[0]->m_inputs = instance.m_node_descriptor->m_inputs; + instance_node_descriptors[1]->m_outputs = + instance.m_node_descriptor->m_outputs; + + size_t connection_data_offset = 0; + for (const auto & connection : m_connections) { + 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; + instance_connection.m_source_node = source_node; + instance_connection.m_source_socket = *source_socket; + instance_connection.m_target_node = target_node; + instance_connection.m_target_socket = *target_socket; + 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); + + source_node_descriptor->SetOutputUnchecked( + connection.source_socket_name.c_str(), + &instance.m_connection_data_storage[connection_data_offset]); + + target_node_descriptor->SetInputUnchecked( + connection.target_socket_name.c_str(), + &instance.m_connection_data_storage[connection_data_offset]); + + if (source_socket->m_type == SocketType::SocketTypeAnimation) { + instance.m_animdata_blocks.push_back( + (AnimData*)(&instance + .m_connection_data_storage[connection_data_offset])); + } + + connection_data_offset += source_socket->m_type_size; + } + + // + // const node inputs + // + std::vector const_inputs = + getConstNodeInputs( 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_nodes.size(); i++) { + delete instance_node_descriptors[i]; + } +} + +void AnimGraphBlendTreeResource::setRuntimeNodeProperties(AnimGraph& instance) const { + for (int i = 2; i < m_nodes.size(); i++) { + const AnimNodeResource& node_resource = m_nodes[i]; + + NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory( + node_resource.m_type_name, + instance.m_nodes[i]); + + std::vector& resource_properties = + node_resource.m_socket_accessor->m_properties; + for (const auto & property : resource_properties) { + const std::string& name = property.m_name; + + switch (property.m_type) { + case SocketType::SocketTypeBool: + node_instance_accessor->SetProperty( + name.c_str(), + property.m_value.flag); + break; + case SocketType::SocketTypeInt: + node_instance_accessor->SetProperty( + name.c_str(), + property.m_value.int_value); + break; + case SocketType::SocketTypeFloat: + node_instance_accessor->SetProperty( + name.c_str(), + property.m_value.float_value); + break; + case SocketType::SocketTypeVec3: + node_instance_accessor->SetProperty( + name.c_str(), + property.m_value.vec3); + break; + case SocketType::SocketTypeQuat: + node_instance_accessor->SetProperty( + name.c_str(), + property.m_value.quat); + break; + case SocketType::SocketTypeString: + node_instance_accessor->SetProperty( + name.c_str(), + property.m_value_string); + break; + default: + std::cerr << "Invalid socket type " + << static_cast(property.m_type) << std::endl; + } + } + + delete node_instance_accessor; + } +} + +std::vector AnimGraphBlendTreeResource::getConstNodeInputs( + std::vector& instance_node_descriptors) const { + std::vector result; + + for (size_t i = 0; i < m_nodes.size(); i++) { + for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size(); + j < num_inputs; + j++) { + Socket& input = instance_node_descriptors[i]->m_inputs[j]; + + if (*input.m_reference.ptr_ptr == nullptr) { + memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue)); + result.push_back(&input); + } + } + } + + return result; +} diff --git a/src/AnimGraph/AnimGraphBlendTreeResource.h b/src/AnimGraph/AnimGraphBlendTreeResource.h new file mode 100644 index 0000000..4385032 --- /dev/null +++ b/src/AnimGraph/AnimGraphBlendTreeResource.h @@ -0,0 +1,138 @@ +// +// Created by martin on 04.02.22. +// + +#ifndef ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H +#define ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H + +#include +#include +#include +#include +#include +#include + +#include "AnimGraph.h" +#include "AnimGraphData.h" +#include "AnimGraphNodes.h" +#include "SyncTrack.h" + +struct AnimNode; + +struct AnimNodeResource { + std::string m_name; + std::string m_type_name; + AnimNode* m_anim_node = nullptr; + NodeDescriptorBase* m_socket_accessor = nullptr; + float m_position[2] = {0.f, 0.f}; +}; + + + +// +// AnimGraphResource +// +struct AnimGraphConnectionResource { + size_t source_node_index = -1; + std::string source_socket_name; + size_t target_node_index = -1; + std::string target_socket_name; +}; + +struct AnimGraphBlendTreeResource { + std::string m_name; + std::vector m_nodes; + std::vector m_connections; + + ~AnimGraphBlendTreeResource() { + for (auto & m_node : m_nodes) { + delete m_node.m_anim_node; + delete m_node.m_socket_accessor; + } + } + + AnimGraphBlendTreeResource() { clear(); } + + void clear(); + void clearNodes(); + void initGraphConnectors(); + bool saveToFile(const char* filename) const; + bool loadFromFile(const char* filename); + + AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; } + AnimNodeResource& getGraphInputNode() { return m_nodes[1]; } + + size_t getNodeIndex(const AnimNodeResource& node_resource) const { + for (size_t i = 0, n = m_nodes.size(); i < n; i++) { + if (&m_nodes[i] == &node_resource) { + return i; + } + } + + return -1; + } + + size_t addNode(const AnimNodeResource &node_resource) { + m_nodes.push_back(node_resource); + return m_nodes.size() - 1; + } + + bool connectSockets( + const AnimNodeResource& source_node, + const std::string& source_socket_name, + const AnimNodeResource& target_node, + const std::string& target_socket_name) { + size_t source_node_index = getNodeIndex(source_node); + size_t target_node_index = getNodeIndex(target_node); + + if (source_node_index >= m_nodes.size() + || target_node_index >= m_nodes.size()) { + std::cerr << "Cannot connect nodes: could not find nodes." << std::endl; + return false; + } + + Socket* source_socket = + source_node.m_socket_accessor->GetOutputSocket(source_socket_name.c_str()); + Socket* target_socket = + target_node.m_socket_accessor->GetInputSocket(target_socket_name.c_str()); + + if (source_socket == nullptr || target_socket == nullptr) { + std::cerr << "Cannot connect nodes: could not find sockets." << std::endl; + return false; + } + + AnimGraphConnectionResource connection; + connection.source_node_index = source_node_index; + connection.source_socket_name = source_socket_name; + connection.target_node_index = target_node_index; + connection.target_socket_name = target_socket_name; + m_connections.push_back(connection); + + return true; + } + + bool isSocketConnected( + const AnimNodeResource& node, + const std::string& socket_name) { + size_t node_index = getNodeIndex(node); + for (const auto & connection : m_connections) { + if ((connection.source_node_index == node_index + && connection.source_socket_name == socket_name) + || ((connection.target_node_index == node_index) + && connection.target_socket_name == socket_name)) { + return true; + } + } + + return false; + } + + void createInstance(AnimGraph& result) const; + + void createRuntimeNodeInstances(AnimGraph& instance) const; + void prepareGraphIOData(AnimGraph& instance) const; + void setRuntimeNodeProperties(AnimGraph& instance) const; + std::vector getConstNodeInputs(std::vector& instance_node_descriptors) const; +}; + +#endif //ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H diff --git a/src/AnimGraph/AnimGraphData.h b/src/AnimGraph/AnimGraphData.h index b9252ea..cd3423f 100644 --- a/src/AnimGraph/AnimGraphData.h +++ b/src/AnimGraph/AnimGraphData.h @@ -22,8 +22,8 @@ // // Data types // - struct AnimGraph; +struct AnimNode; struct AnimData { ozz::vector m_local_matrices; @@ -260,6 +260,14 @@ SocketType GetSocketType() { return SocketType::SocketTypeUndefined; } +struct AnimGraphConnection { + AnimNode* m_source_node = nullptr; + Socket m_source_socket; + AnimNode* m_target_node = nullptr; + Socket m_target_socket; +}; + + struct NodeDescriptorBase { std::vector m_inputs; std::vector m_outputs; diff --git a/src/AnimGraph/AnimGraphEditor.cc b/src/AnimGraph/AnimGraphEditor.cc index f94cb86..4a4df15 100644 --- a/src/AnimGraph/AnimGraphEditor.cc +++ b/src/AnimGraph/AnimGraphEditor.cc @@ -7,13 +7,14 @@ #include #include "3rdparty/imgui-node-editor/imgui_node_editor.h" -#include "AnimGraphResource.h" +#include "AnimGraphBlendTreeResource.h" #include "SkinnedMesh.h" #include "imgui.h" #include "imnodes.h" #include "misc/cpp/imgui_stdlib.h" -static AnimGraphResource sGraphGresource = AnimGraphResource(); +static AnimGraphBlendTreeResource sGraphGresource = + AnimGraphBlendTreeResource(); static bool sGraphLoadedThisFrame = false; ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) { @@ -58,7 +59,7 @@ void NodeSocketEditor(Socket& socket) { } void RemoveConnectionsForSocket( - AnimGraphResource& graph_resource, + AnimGraphBlendTreeResource& graph_resource, AnimNodeResource& node_resource, Socket& socket) { std::vector::iterator iter = @@ -159,7 +160,7 @@ void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) { } void AnimGraphEditorRenderSidebar( - AnimGraphResource& graph_resource, + AnimGraphBlendTreeResource& graph_resource, AnimNodeResource& node_resource) { ImGui::Text("[%s]", node_resource.m_type_name.c_str()); diff --git a/src/AnimGraph/AnimGraphNodes.h b/src/AnimGraph/AnimGraphNodes.h index 9c9d7f5..0c199b7 100644 --- a/src/AnimGraph/AnimGraphNodes.h +++ b/src/AnimGraph/AnimGraphNodes.h @@ -7,72 +7,13 @@ #include +#include "AnimNode.h" #include "AnimGraphData.h" #include "SyncTrack.h" #include "ozz/animation/runtime/sampling_job.h" struct AnimNode; -enum class AnimNodeEvalState { - Undefined, - Deactivated, - Activated, - SyncTrackUpdated, - TimeUpdated, - Evaluated -}; - -struct AnimGraphConnection { - AnimNode* m_source_node = nullptr; - Socket m_source_socket; - AnimNode* m_target_node = nullptr; - Socket m_target_socket; -}; - -struct AnimNode { - std::string m_name; - std::string m_node_type_name; - float m_time_now = 0.f; - float m_time_last = 0.f; - size_t m_index = -1; - AnimNodeEvalState m_state = AnimNodeEvalState::Undefined; - SyncTrack m_sync_track; - - virtual ~AnimNode() = default; - - virtual bool Init(AnimGraphContext& context) { return true; }; - - virtual void MarkActiveInputs(const std::vector& inputs) { - for (const auto & input : inputs) { - AnimNode* input_node = input.m_source_node; - if (input_node != nullptr) { - input_node->m_state = AnimNodeEvalState::Activated; - } - } - } - - virtual void CalcSyncTrack(const std::vector& inputs) { - for (const auto & input : inputs) { - AnimNode* input_node = input.m_source_node; - if (input_node != nullptr - && input.m_source_socket.m_type == SocketType::SocketTypeAnimation - && input_node->m_state != AnimNodeEvalState::Deactivated) { - m_sync_track = input_node->m_sync_track; - return; - } - } - } - - virtual void UpdateTime(float time_last, float time_now) { - m_time_last = time_last; - m_time_now = time_now; - m_state = AnimNodeEvalState::TimeUpdated; - } - - virtual void Evaluate(AnimGraphContext& context){}; -}; - - // // BlendTreeNode // @@ -93,8 +34,8 @@ struct Blend2Node : public AnimNode { float* i_blend_weight = nullptr; bool m_sync_blend = false; - virtual void MarkActiveInputs(const std::vector& inputs) override { - for (const auto & input : inputs) { + virtual void MarkActiveInputs() override { + for (const auto & input : m_inputs) { AnimNode* input_node = input.m_source_node; if (input_node == nullptr) { continue; diff --git a/src/AnimGraph/AnimGraphResource.cc b/src/AnimGraph/AnimGraphResource.cc index f7844ca..a194d35 100644 --- a/src/AnimGraph/AnimGraphResource.cc +++ b/src/AnimGraph/AnimGraphResource.cc @@ -1,5 +1,5 @@ // -// Created by martin on 04.02.22. +// Created by martin on 17.03.24. // #include "AnimGraphResource.h" @@ -9,6 +9,9 @@ #include "3rdparty/json/json.hpp" +#include "AnimGraphBlendTree.h" +#include "AnimGraphNodes.h" + using json = nlohmann::json; // @@ -124,7 +127,7 @@ Socket sJsonToSocket(const json& json_data) { json sAnimGraphNodeToJson( const AnimNodeResource& node, size_t node_index, - const std::vector& connections) { + const std::vector& connections) { json result; result["name"] = node.m_name; @@ -135,13 +138,13 @@ json sAnimGraphNodeToJson( result["position"][j] = node.m_position[j]; } - for (const auto & socket : node.m_socket_accessor->m_inputs) { + for (const auto& socket : node.m_socket_accessor->m_inputs) { if (socket.m_type == SocketType::SocketTypeAnimation) { continue; } bool socket_connected = false; - for (const auto & connection : connections) { + for (const auto& connection : connections) { if (connection.source_node_index == node_index && connection.source_socket_name == socket.m_name) { socket_connected = true; @@ -154,14 +157,16 @@ json sAnimGraphNodeToJson( } } - for (auto & property : node.m_socket_accessor->m_properties) { + for (auto& property : node.m_socket_accessor->m_properties) { result["properties"][property.m_name] = sSocketToJson(property); } return result; } -AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index) { +AnimNodeResource sAnimGraphNodeFromJson( + const json& json_node, + size_t node_index) { AnimNodeResource result; result.m_name = json_node["name"]; @@ -173,7 +178,7 @@ AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index result.m_socket_accessor = AnimNodeDescriptorFactory(result.m_type_name, result.m_anim_node); - for (auto & property : result.m_socket_accessor->m_properties) { + for (auto& property : result.m_socket_accessor->m_properties) { property = sJsonToSocket(json_node["properties"][property.m_name]); } @@ -198,8 +203,7 @@ AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index // // AnimGraphConnectionResource <-> Json // -json sAnimGraphConnectionToJson( - const AnimGraphConnectionResource& connection) { +json sAnimGraphConnectionToJson(const BlendTreeConnectionResource& connection) { json result; result["type"] = "AnimGraphConnectionResource"; @@ -213,9 +217,9 @@ json sAnimGraphConnectionToJson( return result; } -AnimGraphConnectionResource sAnimGraphConnectionFromJson( +BlendTreeConnectionResource sAnimGraphConnectionFromJson( const json& json_node) { - AnimGraphConnectionResource connection; + BlendTreeConnectionResource connection; connection.source_node_index = json_node["source_node_index"]; connection.source_socket_name = json_node["source_socket_name"]; @@ -226,58 +230,136 @@ AnimGraphConnectionResource sAnimGraphConnectionFromJson( return connection; } -void AnimGraphResource::clear() { - m_name = ""; +bool AnimGraphResource::LoadFromFile(const char* filename) { + std::ifstream input_file; + input_file.open(filename); + std::stringstream buffer; + buffer << input_file.rdbuf(); - clearNodes(); - m_connections.clear(); + json json_data = json::parse(buffer.str(), nullptr, false); + if (json_data.is_discarded()) { + std::cerr << "Error parsing json of file '" << filename << "'." + << std::endl; - initGraphConnectors(); -} - -void AnimGraphResource::clearNodes() { - for (auto & m_node : m_nodes) { - delete m_node.m_socket_accessor; - m_node.m_socket_accessor = nullptr; - delete m_node.m_anim_node; - m_node.m_anim_node = nullptr; + return false; } - m_nodes.clear(); + + if (json_data["type"] != "AnimNodeResource") { + std::cerr + << "Invalid json object. Expected type 'AnimNodeResource' but got '" + << json_data["type"] << "'." << std::endl; + + return false; + } + + if (json_data["node_type"] == "BlendTree") { + return LoadBlendTreeResourceFromJson(json_data); + } else if (json_data["node_type"] == "StateMachine") { + return LoadStateMachineResourceFromJson(json_data); + } + + std::cerr << "Invalid node_type. Expected type 'BlendTree' or " + "'StateMachine' but got '" + << json_data["node_type"] << "'." << std::endl; + + return false; } -void AnimGraphResource::initGraphConnectors() { - m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); - m_nodes[0].m_name = "Outputs"; - m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); - m_nodes[1].m_name = "Inputs"; +bool AnimGraphResource::LoadBlendTreeResourceFromJson(nlohmann::json const& json_data) { + m_blend_tree_resource.Reset(); + m_type = "BlendTree"; + m_name = json_data["name"]; + + // Load nodes + for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) { + const json& json_node = json_data["nodes"][i]; + if (json_node["type"] != "AnimNodeResource") { + std::cerr + << "Invalid json object. Expected type 'AnimNodeResource' but got '" + << json_node["type"] << "'." << std::endl; + return false; + } + + AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i); + m_blend_tree_resource.m_nodes.push_back(node); + } + + // Setup graph inputs and outputs + const json& graph_outputs = json_data["nodes"][0]["inputs"]; + for (const auto& graph_output : graph_outputs) { + AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[0]; + graph_node.m_socket_accessor->m_inputs.push_back( + sJsonToSocket(graph_output)); + } + + const json& graph_inputs = json_data["nodes"][1]["outputs"]; + for (const auto& graph_input : graph_inputs) { + AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[1]; + graph_node.m_socket_accessor->m_outputs.push_back( + sJsonToSocket(graph_input)); + } + + // Load connections + for (const auto& json_connection : json_data["connections"]) { + if (json_connection["type"] != "AnimGraphConnectionResource") { + std::cerr << "Invalid json object. Expected type " + "'AnimGraphConnectionResource' " + "but got '" + << json_connection["type"] << "'." << std::endl; + return false; + } + + BlendTreeConnectionResource connection = + sAnimGraphConnectionFromJson(json_connection); + m_blend_tree_resource.m_connections.push_back(connection); + } + + return true; } -bool AnimGraphResource::saveToFile(const char* filename) const { +bool AnimGraphResource::SaveToFile(const char* filename) const { + if (m_type == "BlendTree") { + return SaveBlendTreeResourceToFile(filename); + } else if (m_type == "StateMachine") { + return SaveStateMachineResourceToFile(filename); + } + + std::cerr << "Invalid AnimGraphResource type: " << m_type << "." << std::endl; + + return false; +} + +bool AnimGraphResource::SaveBlendTreeResourceToFile( + const char* filename) const { json result; result["name"] = m_name; - result["type"] = "AnimGraphResource"; + result["type"] = "AnimNodeResource"; + result["node_type"] = "BlendTree"; - for (size_t i = 0; i < m_nodes.size(); i++) { - const AnimNodeResource& node = m_nodes[i]; - result["nodes"][i] = sAnimGraphNodeToJson(node, i, m_connections); + for (size_t i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { + const AnimNodeResource& node = m_blend_tree_resource.m_nodes[i]; + result["nodes"][i] = + sAnimGraphNodeToJson(node, i, m_blend_tree_resource.m_connections); } - for (size_t i = 0; i < m_connections.size(); i++) { - const AnimGraphConnectionResource& connection = m_connections[i]; + for (size_t i = 0; i < m_blend_tree_resource.m_connections.size(); i++) { + const BlendTreeConnectionResource& connection = + m_blend_tree_resource.m_connections[i]; result["connections"][i] = sAnimGraphConnectionToJson(connection); } // Graph inputs and outputs { - const AnimNodeResource& graph_output_node = m_nodes[0]; + const AnimNodeResource& graph_output_node = + m_blend_tree_resource.m_nodes[0]; const std::vector graph_inputs = graph_output_node.m_socket_accessor->m_inputs; for (size_t i = 0; i < graph_inputs.size(); i++) { result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]); } - const AnimNodeResource& graph_input_node = m_nodes[1]; + const AnimNodeResource& graph_input_node = m_blend_tree_resource.m_nodes[1]; const std::vector graph_outputs = graph_input_node.m_socket_accessor->m_outputs; for (size_t i = 0; i < graph_outputs.size(); i++) { @@ -293,107 +375,45 @@ bool AnimGraphResource::saveToFile(const char* filename) const { return true; } -bool AnimGraphResource::loadFromFile(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; +void AnimGraphResource::CreateBlendTreeInstance( + AnimGraphBlendTree& result) const { + if (m_type != "BlendTree") { + std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '" + << m_type << "'." << std::endl; + return; } - if (json_data["type"] != "AnimGraphResource") { - std::cerr - << "Invalid json object. Expected type 'AnimGraphResource' but got '" - << json_data["type"] << "'." << std::endl; - } + CreateBlendTreeRuntimeNodeInstances(result); + PrepareBlendTreeIOData(result); + SetRuntimeNodeProperties(result); - clear(); - clearNodes(); - - m_name = json_data["name"]; - - // Load nodes - for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) { - const json& json_node = json_data["nodes"][i]; - if (json_node["type"] != "AnimNodeResource") { - std::cerr - << "Invalid json object. Expected type 'AnimNodeResource' but got '" - << json_node["type"] << "'." << std::endl; - return false; - } - - AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i); - m_nodes.push_back(node); - } - - // Setup graph inputs and outputs - const json& graph_outputs = json_data["nodes"][0]["inputs"]; - for (const auto & graph_output : graph_outputs) { - AnimNodeResource& graph_node = m_nodes[0]; - graph_node.m_socket_accessor->m_inputs.push_back( - sJsonToSocket(graph_output)); - } - - const json& graph_inputs = json_data["nodes"][1]["outputs"]; - for (const auto & graph_input : graph_inputs) { - AnimNodeResource& graph_node = m_nodes[1]; - graph_node.m_socket_accessor->m_outputs.push_back( - sJsonToSocket(graph_input)); - } - - // Load connections - for (const auto & json_connection : json_data["connections"]) { - if (json_connection["type"] != "AnimGraphConnectionResource") { - std::cerr - << "Invalid json object. Expected type 'AnimGraphConnectionResource' " - "but got '" - << json_connection["type"] << "'." << std::endl; - return false; - } - - AnimGraphConnectionResource connection = - sAnimGraphConnectionFromJson(json_connection); - m_connections.push_back(connection); - } - - return true; + result.UpdateOrderedNodes(); + result.ResetNodeStates(); } -void AnimGraphResource::createInstance(AnimGraph& result) const { - createRuntimeNodeInstances(result); - prepareGraphIOData(result); - setRuntimeNodeProperties(result); - - result.updateOrderedNodes(); - result.resetNodeStates(); -} - -void AnimGraphResource::createRuntimeNodeInstances(AnimGraph& instance) const { - for (int i = 0; i < m_nodes.size(); i++) { - const AnimNodeResource& node_resource = m_nodes[i]; +void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances( + AnimGraphBlendTree& result) const { + for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { + const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i]; AnimNode* node = AnimNodeFactory(node_resource.m_type_name); node->m_name = node_resource.m_name; node->m_node_type_name = node_resource.m_type_name; - node->m_index = i; - instance.m_nodes.push_back(node); + result.m_nodes.push_back(node); // runtime node connections - instance.m_node_input_connections.emplace_back(); - instance.m_node_output_connections.emplace_back(); - + result.m_node_input_connections.emplace_back(); + result.m_node_output_connections.emplace_back(); } } -void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const { +void AnimGraphResource::PrepareBlendTreeIOData( + AnimGraphBlendTree& instance) const { instance.m_node_descriptor = AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]); instance.m_node_descriptor->m_outputs = - m_nodes[1].m_socket_accessor->m_outputs; - instance.m_node_descriptor->m_inputs = m_nodes[0].m_socket_accessor->m_inputs; + m_blend_tree_resource.m_nodes[1].m_socket_accessor->m_outputs; + instance.m_node_descriptor->m_inputs = + m_blend_tree_resource.m_nodes[0].m_socket_accessor->m_inputs; // // graph inputs @@ -442,8 +462,9 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const { // connections: make source and target sockets point to the same address in the connection data storage. // TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once. size_t connection_data_storage_size = 0; - for (const auto & connection : m_connections) { - const AnimNodeResource& source_node = m_nodes[connection.source_node_index]; + for (const auto& connection : m_blend_tree_resource.m_connections) { + const AnimNodeResource& source_node = + m_blend_tree_resource.m_nodes[connection.source_node_index]; Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket( connection.source_socket_name.c_str()); connection_data_storage_size += source_socket->m_type_size; @@ -455,11 +476,11 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const { } std::vector instance_node_descriptors( - m_nodes.size(), + m_blend_tree_resource.m_nodes.size(), nullptr); - for (int i = 0; i < m_nodes.size(); i++) { + for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { instance_node_descriptors[i] = AnimNodeDescriptorFactory( - m_nodes[i].m_type_name, + m_blend_tree_resource.m_nodes[i].m_type_name, instance.m_nodes[i]); } @@ -468,7 +489,7 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const { instance.m_node_descriptor->m_outputs; size_t connection_data_offset = 0; - for (const auto & connection : m_connections) { + for (const auto& connection : m_blend_tree_resource.m_connections) { NodeDescriptorBase* source_node_descriptor = instance_node_descriptors[connection.source_node_index]; NodeDescriptorBase* target_node_descriptor = @@ -513,9 +534,9 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const { // const node inputs // std::vector const_inputs = - getConstNodeInputs( instance_node_descriptors); + m_blend_tree_resource.GetConstantNodeInputs(instance_node_descriptors); size_t const_node_inputs_buffer_size = 0; - for (auto & const_input : const_inputs) { + 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!" @@ -531,7 +552,7 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const { } size_t const_input_buffer_offset = 0; - for (auto & i : const_inputs) { + for (auto& i : const_inputs) { Socket* const_input = i; // TODO: implement string const node input support @@ -539,27 +560,30 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const { *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); + 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_nodes.size(); i++) { + for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { delete instance_node_descriptors[i]; } } -void AnimGraphResource::setRuntimeNodeProperties(AnimGraph& instance) const { - for (int i = 2; i < m_nodes.size(); i++) { - const AnimNodeResource& node_resource = m_nodes[i]; +void AnimGraphResource::SetRuntimeNodeProperties( + AnimGraphBlendTree& result) const { + for (int i = 2; i < m_blend_tree_resource.m_nodes.size(); i++) { + const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i]; - NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory( - node_resource.m_type_name, - instance.m_nodes[i]); + NodeDescriptorBase* node_instance_accessor = + AnimNodeDescriptorFactory(node_resource.m_type_name, result.m_nodes[i]); std::vector& resource_properties = node_resource.m_socket_accessor->m_properties; - for (const auto & property : resource_properties) { + for (const auto& property : resource_properties) { const std::string& name = property.m_name; switch (property.m_type) { @@ -603,22 +627,15 @@ void AnimGraphResource::setRuntimeNodeProperties(AnimGraph& instance) const { } } -std::vector AnimGraphResource::getConstNodeInputs( - std::vector& instance_node_descriptors) const { - std::vector result; +bool AnimGraphResource::SaveStateMachineResourceToFile( + const char* filename) const { + assert(false && "Not yet implemented"); - for (size_t i = 0; i < m_nodes.size(); i++) { - for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size(); - j < num_inputs; - j++) { - Socket& input = instance_node_descriptors[i]->m_inputs[j]; - - if (*input.m_reference.ptr_ptr == nullptr) { - memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue)); - result.push_back(&input); - } - } - } - - return result; + return false; } + +bool AnimGraphResource::LoadStateMachineResourceFromJson(nlohmann::json const& json_data) { + assert(false && "Not yet implemented"); + + return false; +} \ No newline at end of file diff --git a/src/AnimGraph/AnimGraphResource.h b/src/AnimGraph/AnimGraphResource.h index 96648b0..6a0d1fd 100644 --- a/src/AnimGraph/AnimGraphResource.h +++ b/src/AnimGraph/AnimGraphResource.h @@ -1,23 +1,17 @@ // -// Created by martin on 04.02.22. +// Created by martin on 17.03.24. // #ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H #define ANIMTESTBED_ANIMGRAPHRESOURCE_H -#include -#include -#include -#include -#include -#include - #include "AnimGraph.h" -#include "AnimGraphData.h" #include "AnimGraphNodes.h" -#include "SyncTrack.h" -struct AnimNode; +#include "3rdparty/json/json.hpp" + +struct AnimGraphBlendTree; +struct AnimGraphStateMachine; struct AnimNodeResource { std::string m_name; @@ -37,40 +31,33 @@ static inline AnimNodeResource AnimNodeResourceFactory( return result; } -// -// AnimGraphResource -// -struct AnimGraphConnectionResource { +struct BlendTreeConnectionResource { size_t source_node_index = -1; std::string source_socket_name; size_t target_node_index = -1; std::string target_socket_name; }; -struct AnimGraphResource { - std::string m_name; +struct BlendTreeResource { std::vector m_nodes; - std::vector m_connections; + std::vector m_connections; - ~AnimGraphResource() { - for (auto & m_node : m_nodes) { - delete m_node.m_anim_node; - delete m_node.m_socket_accessor; - } + void Reset() { + m_nodes.clear(); + m_connections.clear(); } - AnimGraphResource() { clear(); } + void InitGraphConnectors() { + m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); + m_nodes[0].m_name = "Outputs"; + m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); + m_nodes[1].m_name = "Inputs"; + } - void clear(); - void clearNodes(); - void initGraphConnectors(); - bool saveToFile(const char* filename) const; - bool loadFromFile(const char* filename); + AnimNodeResource& GetGraphOutputNode() { return m_nodes[0]; } + AnimNodeResource& GetGraphInputNode() { return m_nodes[1]; } - AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; } - AnimNodeResource& getGraphInputNode() { return m_nodes[1]; } - - size_t getNodeIndex(const AnimNodeResource& node_resource) const { + size_t GetNodeIndex(const AnimNodeResource& node_resource) const { for (size_t i = 0, n = m_nodes.size(); i < n; i++) { if (&m_nodes[i] == &node_resource) { return i; @@ -80,18 +67,13 @@ struct AnimGraphResource { return -1; } - size_t addNode(const AnimNodeResource &node_resource) { - m_nodes.push_back(node_resource); - return m_nodes.size() - 1; - } - - bool connectSockets( + bool ConnectSockets ( const AnimNodeResource& source_node, const std::string& source_socket_name, const AnimNodeResource& target_node, const std::string& target_socket_name) { - size_t source_node_index = getNodeIndex(source_node); - size_t target_node_index = getNodeIndex(target_node); + size_t source_node_index = GetNodeIndex(source_node); + size_t target_node_index = GetNodeIndex(target_node); if (source_node_index >= m_nodes.size() || target_node_index >= m_nodes.size()) { @@ -109,7 +91,7 @@ struct AnimGraphResource { return false; } - AnimGraphConnectionResource connection; + BlendTreeConnectionResource connection; connection.source_node_index = source_node_index; connection.source_socket_name = source_socket_name; connection.target_node_index = target_node_index; @@ -119,28 +101,62 @@ struct AnimGraphResource { return true; } - bool isSocketConnected( - const AnimNodeResource& node, - const std::string& socket_name) { - size_t node_index = getNodeIndex(node); - for (const auto & connection : m_connections) { - if ((connection.source_node_index == node_index - && connection.source_socket_name == socket_name) - || ((connection.target_node_index == node_index) - && connection.target_socket_name == socket_name)) { - return true; + std::vector GetConstantNodeInputs( + std::vector& instance_node_descriptors) const { + std::vector result; + + for (size_t i = 0; i < m_nodes.size(); i++) { + for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size(); + j < num_inputs; + j++) { + Socket& input = instance_node_descriptors[i]->m_inputs[j]; + + if (*input.m_reference.ptr_ptr == nullptr) { + memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue)); + result.push_back(&input); + } } } - return false; + return result; } +}; - void createInstance(AnimGraph& result) const; +struct StateMachineTransitionResources { + size_t source_state_index = -1; + size_t target_state_index = -1; + float blend_time = 0.f; + bool sync_blend = false; +}; - void createRuntimeNodeInstances(AnimGraph& instance) const; - void prepareGraphIOData(AnimGraph& instance) const; - void setRuntimeNodeProperties(AnimGraph& instance) const; - std::vector getConstNodeInputs(std::vector& instance_node_descriptors) const; +struct StateMachineResource { + std::vector m_states; + std::vector m_transitions; +}; + +struct AnimGraphResource { + std::string m_type; + std::string m_name; + + BlendTreeResource m_blend_tree_resource; + StateMachineResource m_state_machine_resource; + + bool SaveToFile(const char* filename) const; + bool LoadFromFile(const char* filename); + + void CreateBlendTreeInstance(AnimGraphBlendTree& result) const; + void CreateStateMachineInstance(AnimGraphStateMachine& result) const; + + private: + // BlendTree + bool SaveBlendTreeResourceToFile(const char* filename) const; + bool LoadBlendTreeResourceFromJson(nlohmann::json const& json_data); + void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const; + void PrepareBlendTreeIOData(AnimGraphBlendTree& instance) const; + void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const; + + bool SaveStateMachineResourceToFile(const char* filename) const; + bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data); }; #endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H diff --git a/src/AnimGraph/AnimGraphStateMachine.cc b/src/AnimGraph/AnimGraphStateMachine.cc new file mode 100644 index 0000000..eefb271 --- /dev/null +++ b/src/AnimGraph/AnimGraphStateMachine.cc @@ -0,0 +1,25 @@ +// +// Created by martin on 17.03.24. +// + +#include "AnimGraphStateMachine.h" + +bool AnimGraphStateMachine::Init(AnimGraphContext& context) { + +} + +void AnimGraphStateMachine::MarkActiveInputs() { + +} + +void AnimGraphStateMachine::CalcSyncTrack() { + +} + +void AnimGraphStateMachine::UpdateTime(float time_last, float time_now) { + +} + +void AnimGraphStateMachine::Evaluate(AnimGraphContext& context) { + +} \ No newline at end of file diff --git a/src/AnimGraph/AnimGraphStateMachine.h b/src/AnimGraph/AnimGraphStateMachine.h new file mode 100644 index 0000000..dbea8b1 --- /dev/null +++ b/src/AnimGraph/AnimGraphStateMachine.h @@ -0,0 +1,35 @@ +// +// Created by martin on 17.03.24. +// + +#ifndef ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H +#define ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H + +#include "AnimGraphNodes.h" + +struct Transition { + AnimNode* m_source_state = nullptr; + AnimNode* m_target_state = nullptr; + + float m_blend_time = 0.f; + bool m_sync_blend = false; +}; + +struct AnimGraphStateMachine : public AnimNode { + std::vector m_states; + std::vector m_transitions; + std::vector > m_state_out_transitions; + + AnimNode* m_next_state = nullptr; + AnimNode* m_current_state = nullptr; + Transition* m_active_transition = nullptr; + + bool Init(AnimGraphContext& context); + void MarkActiveInputs() override; + void CalcSyncTrack() override; + void UpdateTime(float time_last, float time_now) override; + void Evaluate(AnimGraphContext& context) override; +}; + + +#endif //ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H diff --git a/src/AnimGraph/AnimNode.cc b/src/AnimGraph/AnimNode.cc new file mode 100644 index 0000000..e581006 --- /dev/null +++ b/src/AnimGraph/AnimNode.cc @@ -0,0 +1,5 @@ +// +// Created by martin on 17.03.24. +// + +#include "AnimNode.h" diff --git a/src/AnimGraph/AnimNode.h b/src/AnimGraph/AnimNode.h new file mode 100644 index 0000000..91c3e3e --- /dev/null +++ b/src/AnimGraph/AnimNode.h @@ -0,0 +1,70 @@ +// +// Created by martin on 17.03.24. +// + +#ifndef ANIMTESTBED_ANIMNODE_H +#define ANIMTESTBED_ANIMNODE_H + +#include +#include + +#include "SyncTrack.h" +#include "AnimGraphData.h" + +struct AnimNode; + +enum class AnimNodeEvalState { + Undefined, + Deactivated, + Activated, + SyncTrackUpdated, + TimeUpdated, + Evaluated +}; + +struct AnimNode { + std::string m_name; + std::string m_node_type_name; + AnimNodeEvalState m_state = AnimNodeEvalState::Undefined; + + float m_time_now = 0.f; + float m_time_last = 0.f; + SyncTrack m_sync_track; + + std::vector m_inputs; + + virtual ~AnimNode() = default; + + virtual bool Init(AnimGraphContext& context) { return true; }; + + virtual void MarkActiveInputs() { + for (const auto & input : m_inputs) { + AnimNode* input_node = input.m_source_node; + if (input_node != nullptr) { + input_node->m_state = AnimNodeEvalState::Activated; + } + } + } + + virtual void CalcSyncTrack() { + for (const auto & input : m_inputs) { + AnimNode* input_node = input.m_source_node; + if (input_node != nullptr + && input.m_source_socket.m_type == SocketType::SocketTypeAnimation + && input_node->m_state != AnimNodeEvalState::Deactivated) { + m_sync_track = input_node->m_sync_track; + return; + } + } + } + + virtual void UpdateTime(float time_last, float time_now) { + m_time_last = time_last; + m_time_now = time_now; + m_state = AnimNodeEvalState::TimeUpdated; + } + + virtual void Evaluate(AnimGraphContext& context){}; +}; + +#endif //ANIMTESTBED_ANIMNODE_H diff --git a/tests/AnimGraphEvalTests.cc b/tests/AnimGraphEvalTests.cc index ec1e748..2dc60d3 100644 --- a/tests/AnimGraphEvalTests.cc +++ b/tests/AnimGraphEvalTests.cc @@ -3,8 +3,8 @@ // #include "AnimGraph/AnimGraph.h" +#include "AnimGraph/AnimGraphBlendTreeResource.h" #include "AnimGraph/AnimGraphEditor.h" -#include "AnimGraph/AnimGraphResource.h" #include "catch.hpp" #include "ozz/animation/offline/animation_builder.h" #include "ozz/animation/offline/raw_animation.h" @@ -141,7 +141,7 @@ TEST_CASE_METHOD( SimpleAnimFixture, "AnimGraphSimpleEval", "[AnimGraphEvalTests]") { - AnimGraphResource graph_resource; + AnimGraphBlendTreeResource graph_resource; // Add nodes size_t trans_x_node_index = diff --git a/tests/AnimGraphResourceTests.cc b/tests/AnimGraphResourceTests.cc index 69014a8..8e91b2c 100644 --- a/tests/AnimGraphResourceTests.cc +++ b/tests/AnimGraphResourceTests.cc @@ -3,7 +3,9 @@ // #include "AnimGraph/AnimGraph.h" +#include "AnimGraph/AnimGraphBlendTree.h" #include "AnimGraph/AnimGraphEditor.h" +#include "AnimGraph/AnimGraphNodes.h" #include "AnimGraph/AnimGraphResource.h" #include "catch.hpp" #include "ozz/base/io/archive.h" @@ -33,61 +35,65 @@ bool load_skeleton(ozz::animation::Skeleton& skeleton, const char* filename) { TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { AnimGraphResource graph_resource; + graph_resource.m_name = "AnimSamplerBlendTree"; + graph_resource.m_type = "BlendTree"; - graph_resource.clear(); - graph_resource.m_name = "AnimSamplerGraph"; + BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; + blend_tree_resource.Reset(); + blend_tree_resource.InitGraphConnectors(); // Prepare graph inputs and outputs - size_t walk_node_index = - graph_resource.addNode(AnimNodeResourceFactory("AnimSampler")); + blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("AnimSampler")); + size_t walk_node_index = blend_tree_resource.m_nodes.size() - 1; - AnimNodeResource& walk_node = graph_resource.m_nodes[walk_node_index]; + AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; walk_node.m_name = "WalkAnim"; walk_node.m_socket_accessor->SetPropertyValue( "Filename", std::string("media/Walking-loop.ozz")); - AnimNodeResource& graph_node = graph_resource.m_nodes[0]; + AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; graph_node.m_socket_accessor->RegisterInput("GraphOutput", nullptr); - graph_resource.connectSockets( + blend_tree_resource.ConnectSockets( walk_node, "Output", - graph_resource.getGraphOutputNode(), + blend_tree_resource.GetGraphOutputNode(), "GraphOutput"); - graph_resource.saveToFile("AnimSamplerGraph.animgraph.json"); - AnimGraphResource graph_resource_loaded; - graph_resource_loaded.loadFromFile("AnimSamplerGraph.animgraph.json"); + graph_resource.SaveToFile("AnimSamplerBlendTree.json"); - AnimGraph graph; - graph_resource_loaded.createInstance(graph); + AnimGraphResource graph_resource_loaded; + graph_resource_loaded.LoadFromFile("AnimSamplerBlendTree.json"); + + AnimGraphBlendTree anim_graph_blend_tree; + graph_resource_loaded.CreateBlendTreeInstance(anim_graph_blend_tree); AnimGraphContext graph_context; ozz::animation::Skeleton skeleton; REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz")); graph_context.m_skeleton = &skeleton; - REQUIRE(graph.init(graph_context)); + REQUIRE(anim_graph_blend_tree.Init(graph_context)); - REQUIRE(graph.m_nodes.size() == 3); - REQUIRE(graph.m_nodes[0]->m_node_type_name == "BlendTree"); - REQUIRE(graph.m_nodes[1]->m_node_type_name == "BlendTree"); - REQUIRE(graph.m_nodes[2]->m_node_type_name == "AnimSampler"); + REQUIRE(anim_graph_blend_tree.m_nodes.size() == 3); + REQUIRE(anim_graph_blend_tree.m_nodes[0]->m_node_type_name == "BlendTree"); + REQUIRE(anim_graph_blend_tree.m_nodes[1]->m_node_type_name == "BlendTree"); + REQUIRE(anim_graph_blend_tree.m_nodes[2]->m_node_type_name == "AnimSampler"); // connections within the graph AnimSamplerNode* anim_sampler_walk = - dynamic_cast(graph.m_nodes[2]); + dynamic_cast(anim_graph_blend_tree.m_nodes[2]); BlendTreeNode* graph_output_node = - dynamic_cast(graph.m_nodes[0]); + dynamic_cast(anim_graph_blend_tree.m_nodes[0]); // check node input dependencies - size_t anim_sampler_index = anim_sampler_walk->m_index; + size_t anim_sampler_index = anim_graph_blend_tree.GetAnimNodeIndex(anim_sampler_walk); - REQUIRE(graph.m_node_output_connections[anim_sampler_index].size() == 1); + REQUIRE(anim_graph_blend_tree.m_node_output_connections[anim_sampler_index].size() == 1); CHECK( - graph.m_node_output_connections[anim_sampler_index][0].m_target_node + anim_graph_blend_tree.m_node_output_connections[anim_sampler_index][0].m_target_node == graph_output_node); // Ensure animation sampler nodes use the correct files @@ -97,11 +103,11 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { // Ensure that outputs are properly propagated. AnimData output; output.m_local_matrices.resize(skeleton.num_soa_joints()); - graph.SetOutput("GraphOutput", &output); + anim_graph_blend_tree.SetOutput("GraphOutput", &output); REQUIRE(anim_sampler_walk->o_output == &output); WHEN("Emulating Graph Evaluation") { - CHECK(graph.m_anim_data_allocator.size() == 0); + CHECK(anim_graph_blend_tree.m_anim_data_allocator.size() == 0); anim_sampler_walk->Evaluate(graph_context); } @@ -109,10 +115,12 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { } /* - * Checks that node const inputs are properly set. - */ + +// +// Checks that node const inputs are properly set. +// TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { - AnimGraphResource graph_resource; + AnimGraphBlendTreeResource graph_resource; graph_resource.clear(); graph_resource.m_name = "AnimSamplerSpeedScaleGraph"; @@ -150,7 +158,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { "GraphOutput"); graph_resource.saveToFile("AnimSamplerSpeedScaleGraph.animgraph.json"); - AnimGraphResource graph_resource_loaded; + AnimGraphBlendTreeResource graph_resource_loaded; graph_resource_loaded.loadFromFile( "AnimSamplerSpeedScaleGraph.animgraph.json"); @@ -172,7 +180,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { TEST_CASE("Blend2Graph", "[AnimGraphResource]") { - AnimGraphResource graph_resource; + AnimGraphBlendTreeResource graph_resource; graph_resource.clear(); graph_resource.m_name = "WalkRunBlendGraph"; @@ -214,7 +222,7 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") { "GraphOutput"); graph_resource.saveToFile("Blend2Graph.animgraph.json"); - AnimGraphResource graph_resource_loaded; + AnimGraphBlendTreeResource graph_resource_loaded; graph_resource_loaded.loadFromFile("Blend2Graph.animgraph.json"); AnimGraph graph; @@ -311,7 +319,7 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") { } TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { - AnimGraphResource graph_resource_origin; + AnimGraphBlendTreeResource graph_resource_origin; graph_resource_origin.clear(); graph_resource_origin.m_name = "TestInputOutputGraph"; @@ -370,7 +378,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { const char* filename = "ResourceSaveLoadGraphInputs.json"; graph_resource_origin.saveToFile(filename); - AnimGraphResource graph_resource_loaded; + AnimGraphBlendTreeResource graph_resource_loaded; graph_resource_loaded.loadFromFile(filename); const AnimNodeResource& graph_loaded_output_node = @@ -444,7 +452,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { } TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { - AnimGraphResource graph_resource_origin; + AnimGraphBlendTreeResource graph_resource_origin; graph_resource_origin.clear(); graph_resource_origin.m_name = "TestInputOutputGraph"; @@ -529,7 +537,7 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { const char* filename = "ResourceSaveLoadGraphInputs.json"; graph_resource_origin.saveToFile(filename); - AnimGraphResource graph_resource_loaded; + AnimGraphBlendTreeResource graph_resource_loaded; graph_resource_loaded.loadFromFile(filename); const AnimNodeResource& graph_loaded_output_node = @@ -580,3 +588,5 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { } } } + +*/ \ No newline at end of file