Working on unified BlendTree and StateMachine handling.
This commit is contained in:
		
							parent
							
								
									c7d2d195a3
								
							
						
					
					
						commit
						ccb9bc4e9b
					
				| @ -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 | ||||
|  | ||||
| @ -1,202 +1,3 @@ | ||||
| //
 | ||||
| // Created by martin on 25.03.22.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "AnimGraph.h" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
| 
 | ||||
| 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<AnimGraphConnection>& 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<AnimNode*>::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<AnimGraphConnection>& 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<AnimGraphConnection>& 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<AnimGraphConnection>& 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; | ||||
| } | ||||
| //
 | ||||
| @ -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<AnimNode*> m_nodes; | ||||
|   std::vector<AnimNode*> m_eval_ordered_nodes; | ||||
|   std::vector<std::vector<AnimGraphConnection> > m_node_input_connections; | ||||
|   std::vector<std::vector<AnimGraphConnection> > m_node_output_connections; | ||||
|   std::vector<AnimData*> 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<Socket>& getGraphOutputs() { return m_node_descriptor->m_inputs; } | ||||
|   std::vector<Socket>& 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 <typename T> | ||||
|   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 <typename T> | ||||
|   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 <typename T> | ||||
|   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<float*>(*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<AnimGraphConnection>& 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
 | ||||
|  | ||||
							
								
								
									
										201
									
								
								src/AnimGraph/AnimGraphBlendTree.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								src/AnimGraph/AnimGraphBlendTree.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| //
 | ||||
| // Created by martin on 17.03.24.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "AnimGraphBlendTree.h" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
| 
 | ||||
| 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<AnimGraphConnection>& 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<AnimNode*>::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<AnimGraphConnection>& 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<AnimGraphConnection>& 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<AnimGraphConnection>& 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; | ||||
| } | ||||
							
								
								
									
										234
									
								
								src/AnimGraph/AnimGraphBlendTree.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/AnimGraph/AnimGraphBlendTree.h
									
									
									
									
									
										Normal file
									
								
							| @ -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<AnimNode*> m_nodes; | ||||
|   std::vector<AnimNode*> m_eval_ordered_nodes; | ||||
|   std::vector<std::vector<AnimGraphConnection> > m_node_input_connections; | ||||
|   std::vector<std::vector<AnimGraphConnection> > m_node_output_connections; | ||||
|   std::vector<AnimData*> 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<Socket>& getGraphOutputs() { return m_node_descriptor->m_inputs; } | ||||
|   std::vector<Socket>& 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 <typename T> | ||||
|   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 <typename T> | ||||
|   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 <typename T> | ||||
|   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<float*>(*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<AnimGraphConnection>& 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
 | ||||
							
								
								
									
										616
									
								
								src/AnimGraph/AnimGraphBlendTreeResource.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										616
									
								
								src/AnimGraph/AnimGraphBlendTreeResource.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,616 @@ | ||||
| //
 | ||||
| // Created by martin on 04.02.22.
 | ||||
| //
 | ||||
| 
 | ||||
| #include <cstring> | ||||
| #include <fstream> | ||||
| 
 | ||||
| #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<int>(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<int>(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<AnimGraphConnectionResource>& 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<Socket> 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<Socket> 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<Socket>& 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<Socket>& 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<NodeDescriptorBase*> 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<Socket*> 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<Socket>& 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<Vec3>( | ||||
|               name.c_str(), | ||||
|               property.m_value.vec3); | ||||
|           break; | ||||
|         case SocketType::SocketTypeQuat: | ||||
|           node_instance_accessor->SetProperty( | ||||
|               name.c_str(), | ||||
|               property.m_value.quat); | ||||
|           break; | ||||
|         case SocketType::SocketTypeString: | ||||
|           node_instance_accessor->SetProperty( | ||||
|               name.c_str(), | ||||
|               property.m_value_string); | ||||
|           break; | ||||
|         default: | ||||
|           std::cerr << "Invalid socket type " | ||||
|                     << static_cast<int>(property.m_type) << std::endl; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     delete node_instance_accessor; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| std::vector<Socket*> AnimGraphBlendTreeResource::getConstNodeInputs( | ||||
|     std::vector<NodeDescriptorBase*>& instance_node_descriptors) const { | ||||
|   std::vector<Socket*> 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; | ||||
| } | ||||
							
								
								
									
										138
									
								
								src/AnimGraph/AnimGraphBlendTreeResource.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/AnimGraph/AnimGraphBlendTreeResource.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,138 @@ | ||||
| //
 | ||||
| // Created by martin on 04.02.22.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H | ||||
| #define ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H | ||||
| 
 | ||||
| #include <cstring> | ||||
| #include <iostream> | ||||
| #include <map> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| #include <vector> | ||||
| 
 | ||||
| #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<AnimNodeResource> m_nodes; | ||||
|   std::vector<AnimGraphConnectionResource> 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<Socket*> getConstNodeInputs(std::vector<NodeDescriptorBase*>& instance_node_descriptors) const; | ||||
| }; | ||||
| 
 | ||||
| #endif  //ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H
 | ||||
| @ -22,8 +22,8 @@ | ||||
| //
 | ||||
| // Data types
 | ||||
| //
 | ||||
| 
 | ||||
| struct AnimGraph; | ||||
| struct AnimNode; | ||||
| 
 | ||||
| struct AnimData { | ||||
|   ozz::vector<ozz::math::SoaTransform> 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<Socket> m_inputs; | ||||
|   std::vector<Socket> m_outputs; | ||||
|  | ||||
| @ -7,13 +7,14 @@ | ||||
| #include <sstream> | ||||
| 
 | ||||
| #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<AnimGraphConnectionResource>::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()); | ||||
| 
 | ||||
|  | ||||
| @ -7,72 +7,13 @@ | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #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<AnimGraphConnection>& 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<AnimGraphConnection>& 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<AnimGraphConnection>& 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; | ||||
|  | ||||
| @ -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<AnimGraphConnectionResource>& connections) { | ||||
|     const std::vector<BlendTreeConnectionResource>& 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<Socket> 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<Socket> 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<NodeDescriptorBase*> 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<Socket*> 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<Socket>& 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<Socket*> AnimGraphResource::getConstNodeInputs( | ||||
|     std::vector<NodeDescriptorBase*>& instance_node_descriptors) const { | ||||
|   std::vector<Socket*> 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; | ||||
| } | ||||
| @ -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 <cstring> | ||||
| #include <iostream> | ||||
| #include <map> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| #include <vector> | ||||
| 
 | ||||
| #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<AnimNodeResource> m_nodes; | ||||
|   std::vector<AnimGraphConnectionResource> m_connections; | ||||
|   std::vector<BlendTreeConnectionResource> 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<Socket*> GetConstantNodeInputs( | ||||
|       std::vector<NodeDescriptorBase*>& instance_node_descriptors) const { | ||||
|     std::vector<Socket*> 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<Socket*> getConstNodeInputs(std::vector<NodeDescriptorBase*>& instance_node_descriptors) const; | ||||
| struct StateMachineResource { | ||||
|   std::vector<AnimNodeResource> m_states; | ||||
|   std::vector<StateMachineTransitionResources> 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
 | ||||
|  | ||||
							
								
								
									
										25
									
								
								src/AnimGraph/AnimGraphStateMachine.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/AnimGraph/AnimGraphStateMachine.cc
									
									
									
									
									
										Normal file
									
								
							| @ -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) { | ||||
|    | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/AnimGraph/AnimGraphStateMachine.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/AnimGraph/AnimGraphStateMachine.h
									
									
									
									
									
										Normal file
									
								
							| @ -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<AnimNode> m_states; | ||||
|   std::vector<Transition> m_transitions; | ||||
|   std::vector<std::vector<Transition*> > 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
 | ||||
							
								
								
									
										5
									
								
								src/AnimGraph/AnimNode.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/AnimGraph/AnimNode.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| //
 | ||||
| // Created by martin on 17.03.24.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "AnimNode.h" | ||||
							
								
								
									
										70
									
								
								src/AnimGraph/AnimNode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/AnimGraph/AnimNode.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| //
 | ||||
| // Created by martin on 17.03.24.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef ANIMTESTBED_ANIMNODE_H | ||||
| #define ANIMTESTBED_ANIMNODE_H | ||||
| 
 | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| #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<AnimGraphConnection> 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
 | ||||
| @ -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 = | ||||
|  | ||||
| @ -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<AnimData>("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<AnimSamplerNode*>(graph.m_nodes[2]); | ||||
|       dynamic_cast<AnimSamplerNode*>(anim_graph_blend_tree.m_nodes[2]); | ||||
| 
 | ||||
|   BlendTreeNode* graph_output_node = | ||||
|       dynamic_cast<BlendTreeNode*>(graph.m_nodes[0]); | ||||
|       dynamic_cast<BlendTreeNode*>(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]") { | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| */ | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Martin Felis
						Martin Felis