Extremely basic blend tree editing.
This commit is contained in:
		
							parent
							
								
									3fb2995b02
								
							
						
					
					
						commit
						5e34aaf3db
					
				| @ -124,6 +124,7 @@ set(ozz_offline_test_objs | ||||
| 
 | ||||
| target_sources(runtests PRIVATE | ||||
|         tests/AnimGraphResourceTests.cc | ||||
|         tests/AnimGraphEditorTests.cc | ||||
|         # tests/AnimGraphEvalTests.cc | ||||
|         tests/NodeDescriptorTests.cc | ||||
|         tests/SyncTrackTests.cc | ||||
|  | ||||
| @ -37,14 +37,6 @@ ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) { | ||||
|   return ImNodesPinShape_Quad; | ||||
| } | ||||
| 
 | ||||
| int GetNodeInputSocketId(int node_index, int input_socket_index) { | ||||
|   return node_index * 1000 + input_socket_index; | ||||
| } | ||||
| 
 | ||||
| int GetNodeOutputSocketId(int node_index, int output_socket_index) { | ||||
|   return node_index * 1000 + 100 + output_socket_index; | ||||
| } | ||||
| 
 | ||||
| void NodeSocketEditor(Socket& socket) { | ||||
|   int mode_current = static_cast<int>(socket.m_type); | ||||
|   ImGui::InputText("Name", &socket.m_name); | ||||
| @ -61,20 +53,18 @@ void RemoveBlendTreeConnectionsForSocket( | ||||
|     BlendTreeResource& blend_tree_resource, | ||||
|     AnimNodeResource* node_resource, | ||||
|     Socket& socket) { | ||||
|   std::vector<BlendTreeConnectionResource>::const_iterator iter = | ||||
|       blend_tree_resource.GetConnections().begin(); | ||||
|   const BlendTreeConnectionResource* connection = | ||||
|       blend_tree_resource.FindConnectionForSocket(node_resource, socket.m_name); | ||||
|   while (connection != nullptr) { | ||||
|     blend_tree_resource.DisconnectSockets( | ||||
|         blend_tree_resource.GetNode(connection->source_node_index), | ||||
|         connection->source_socket_name, | ||||
|         blend_tree_resource.GetNode(connection->target_node_index), | ||||
|         connection->target_socket_name); | ||||
| 
 | ||||
|   while (iter != blend_tree_resource.GetConnections().end()) { | ||||
|     // TODO adjust for refactor
 | ||||
|     assert(false); | ||||
| 
 | ||||
|     //    AnimGraphConnectionResource& connection = *iter;
 | ||||
|     //    if (connection.m_source_node == &node_resource
 | ||||
|     //        && connection.m_source_socket == &socket) {
 | ||||
|     //      iter = sGraphGresource.m_connections.erase(iter);
 | ||||
|     //    } else {
 | ||||
|     //      iter++;
 | ||||
|     //    }
 | ||||
|     connection = blend_tree_resource.FindConnectionForSocket( | ||||
|         node_resource, | ||||
|         socket.m_name); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @ -260,6 +250,12 @@ void AnimGraphEditorRenderSidebar( | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void AnimGraphEditorClear() { | ||||
|   sGraphGresource.Clear(); | ||||
|   sGraphGresource.m_blend_tree_resource.InitGraphConnectors(); | ||||
|   sGraphGresource.m_graph_type_name = "BlendTree"; | ||||
| } | ||||
| 
 | ||||
| void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { | ||||
|   ImGui::BeginMenuBar(); | ||||
|   if (ImGui::Button("Save")) { | ||||
| @ -268,16 +264,9 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { | ||||
|   if (ImGui::Button("Load")) { | ||||
|     sGraphGresource.LoadFromFile("editor_graph.json"); | ||||
|     sGraphLoadedThisFrame = true; | ||||
| 
 | ||||
|     //    for (size_t i = 0, n = sGraphGresource.m_nodes.size(); i < n; i++) {
 | ||||
|     //      const AnimNodeResource& node_resource = sGraphGresource.m_nodes[i];
 | ||||
|     //      ImNodes::SetNodeGridSpacePos(
 | ||||
|     //          i,
 | ||||
|     //          ImVec2(node_resource.m_position[0], node_resource.m_position[1]));
 | ||||
|     //    }
 | ||||
|   } | ||||
|   if (ImGui::Button("Clear")) { | ||||
|     sGraphGresource.Clear(); | ||||
|     AnimGraphEditorClear(); | ||||
|   } | ||||
|   char graph_name_buffer[256]; | ||||
|   memset(graph_name_buffer, 0, sizeof(graph_name_buffer)); | ||||
| @ -323,7 +312,9 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { | ||||
|     for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) { | ||||
|       Socket& socket = node_inputs[j]; | ||||
|       ax::NodeEditor::BeginPin( | ||||
|           GetNodeInputSocketId(static_cast<int>(node_id), static_cast<int>(j)), | ||||
|           NodeIndexAndSocketIndexToInputPinId( | ||||
|               static_cast<int>(node_id), | ||||
|               static_cast<int>(j)), | ||||
|           ax::NodeEditor::PinKind::Input); | ||||
|       ImGui::Text("%s", socket.m_name.c_str()); | ||||
|       ax::NodeEditor::EndPin(); | ||||
| @ -335,13 +326,19 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { | ||||
|     for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) { | ||||
|       Socket& socket = node_outputs[j]; | ||||
|       ax::NodeEditor::BeginPin( | ||||
|           GetNodeOutputSocketId(static_cast<int>(node_id), static_cast<int>(j)), | ||||
|           NodeIndexAndSocketIndexToOutputPinId( | ||||
|               static_cast<int>(node_id), | ||||
|               static_cast<int>(j)), | ||||
|           ax::NodeEditor::PinKind::Output); | ||||
|       ImGui::Text("%s", socket.m_name.c_str()); | ||||
|       ax::NodeEditor::EndPin(); | ||||
|     } | ||||
| 
 | ||||
|     ax::NodeEditor::EndNode(); | ||||
| 
 | ||||
|     ImVec2 node_position = ax::NodeEditor::GetNodePosition(node_id); | ||||
|     node_resource->m_position[0] = node_position.x; | ||||
|     node_resource->m_position[1] = node_position.y; | ||||
|   } | ||||
| 
 | ||||
|   int link_id = 0; | ||||
| @ -366,14 +363,14 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { | ||||
|         target_node_resource->m_socket_accessor->GetInputIndex( | ||||
|             connection_resource->target_socket_name.c_str()); | ||||
| 
 | ||||
|     int source_socket_id = GetNodeOutputSocketId( | ||||
|     int source_socket_pin_id = NodeIndexAndSocketIndexToOutputPinId( | ||||
|         static_cast<int>(connection_resource->source_node_index), | ||||
|         source_socket_index); | ||||
|     int target_socket_id = GetNodeInputSocketId( | ||||
|     int target_socket_pin_id = NodeIndexAndSocketIndexToInputPinId( | ||||
|         static_cast<int>(connection_resource->target_node_index), | ||||
|         target_socket_index); | ||||
| 
 | ||||
|     ax::NodeEditor::Link(link_id++, source_socket_id, target_socket_id); | ||||
|     ax::NodeEditor::Link(link_id++, source_socket_pin_id, target_socket_pin_id); | ||||
|   } | ||||
| 
 | ||||
| #endif | ||||
| @ -385,6 +382,56 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { | ||||
|     if (ax::NodeEditor::QueryNewLink(&input_pin_id, &output_pin_id)) { | ||||
|       if (input_pin_id && output_pin_id) { | ||||
|         if (ax::NodeEditor::AcceptNewItem()) { | ||||
|           int source_node_index; | ||||
|           int source_node_socket_index; | ||||
| 
 | ||||
|           OutputPinIdToNodeIndexAndSocketIndex( | ||||
|               input_pin_id.Get(), | ||||
|               &source_node_index, | ||||
|               &source_node_socket_index); | ||||
| 
 | ||||
|           const AnimNodeResource* source_node = | ||||
|               sGraphGresource.m_blend_tree_resource.GetNode(source_node_index); | ||||
|           if (source_node->m_socket_accessor->m_outputs.size() | ||||
|               < source_node_socket_index) { | ||||
|             source_node_socket_index = -1; | ||||
|           } | ||||
| 
 | ||||
|           int target_node_index; | ||||
|           int target_node_socket_index; | ||||
| 
 | ||||
|           InputPinIdToNodeIndexAndSocketIndex( | ||||
|               output_pin_id.Get(), | ||||
|               &target_node_index, | ||||
|               &target_node_socket_index); | ||||
| 
 | ||||
|           const AnimNodeResource* target_node = | ||||
|               sGraphGresource.m_blend_tree_resource.GetNode(target_node_index); | ||||
|           if (target_node->m_socket_accessor->m_inputs.size() | ||||
|               < target_node_socket_index) { | ||||
|             target_node_socket_index = -1; | ||||
|           } | ||||
| 
 | ||||
|           if (source_node_socket_index == -1 | ||||
|               || target_node_socket_index == -1) { | ||||
|             ax::NodeEditor::RejectNewItem(); | ||||
|           } else { | ||||
|             const std::string& source_socket_name = | ||||
|                 source_node->m_socket_accessor | ||||
|                     ->m_outputs[source_node_socket_index] | ||||
|                     .m_name; | ||||
| 
 | ||||
|             const std::string& target_socket_name = | ||||
|                 target_node->m_socket_accessor | ||||
|                     ->m_inputs[target_node_socket_index] | ||||
|                     .m_name; | ||||
| 
 | ||||
|             sGraphGresource.m_blend_tree_resource.ConnectSockets( | ||||
|                 source_node, | ||||
|                 source_socket_name, | ||||
|                 target_node, | ||||
|                 target_socket_name); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @ -392,6 +439,64 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { | ||||
|   ax::NodeEditor::EndCreate(); | ||||
| #endif | ||||
| 
 | ||||
|   // Popup menu
 | ||||
|   { | ||||
|     const bool open_popup = ImGui::IsMouseReleased(ImGuiMouseButton_Right); | ||||
| 
 | ||||
|     ImVec2 popup_mouse_position = ImGui::GetMousePos(); | ||||
| 
 | ||||
|     ax::NodeEditor::Suspend(); | ||||
|     if (open_popup) { | ||||
|       ImGui::OpenPopup("add node"); | ||||
|     } | ||||
|     ax::NodeEditor::Resume(); | ||||
| 
 | ||||
|     ax::NodeEditor::Suspend(); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f)); | ||||
|     if (ImGui::BeginPopup("add node")) { | ||||
|       std::string node_type_name = ""; | ||||
|       if (ImGui::MenuItem("AnimSampler")) { | ||||
|         node_type_name = "AnimSampler"; | ||||
|       } | ||||
| 
 | ||||
|       if (ImGui::MenuItem("Blend2")) { | ||||
|         node_type_name = "Blend2"; | ||||
|       } | ||||
| 
 | ||||
|       if (ImGui::MenuItem("SpeedScale")) { | ||||
|         node_type_name = "SpeedScale"; | ||||
|       } | ||||
| 
 | ||||
|       if (ImGui::MenuItem("LockTranslationNode")) { | ||||
|         node_type_name = "LockTranslationNode"; | ||||
|       } | ||||
| 
 | ||||
|       if (ImGui::MenuItem("MathAddNode")) { | ||||
|         node_type_name = "MathAddNode"; | ||||
|       } | ||||
| 
 | ||||
|       if (ImGui::MenuItem("MathFloatToVec3Node")) { | ||||
|         node_type_name = "MathFloatToVec3Node"; | ||||
|       } | ||||
| 
 | ||||
|       if (ImGui::MenuItem("ConstScalarNode")) { | ||||
|         node_type_name = "ConstScalarNode"; | ||||
|       } | ||||
| 
 | ||||
|       if (node_type_name.empty()) { | ||||
|         AnimNodeResource* node_resource = | ||||
|             AnimNodeResourceFactory(node_type_name); | ||||
|         size_t node_id = sGraphGresource.m_blend_tree_resource.GetNumNodes(); | ||||
|         ax::NodeEditor::SetNodePosition(node_id, popup_mouse_position); | ||||
|         sGraphGresource.m_blend_tree_resource.AddNode(node_resource); | ||||
|       } | ||||
| 
 | ||||
|       ImGui::EndPopup(); | ||||
|     } | ||||
|     ImGui::PopStyleVar(); | ||||
|     ax::NodeEditor::Resume(); | ||||
|   } | ||||
| 
 | ||||
|   ax::NodeEditor::End(); | ||||
| 
 | ||||
|   sGraphLoadedThisFrame = false; | ||||
|  | ||||
| @ -33,10 +33,40 @@ SplitOutputAttributeId(int attribute_id, int* node_id, int* output_index) { | ||||
|   *output_index = (attribute_id >> 23) - 1; | ||||
| } | ||||
| 
 | ||||
| inline int NodeIndexAndSocketIndexToInputPinId( | ||||
|     int node_index, | ||||
|     int input_socket_index) { | ||||
|   return node_index * 1000 + input_socket_index; | ||||
| } | ||||
| 
 | ||||
| inline int NodeIndexAndSocketIndexToOutputPinId( | ||||
|     int node_index, | ||||
|     int output_socket_index) { | ||||
|   return node_index * 1000 + 500 + output_socket_index; | ||||
| } | ||||
| 
 | ||||
| inline void InputPinIdToNodeIndexAndSocketIndex( | ||||
|     unsigned long input_pin_id, | ||||
|     int* node_index, | ||||
|     int* socket_index) { | ||||
|   *socket_index = input_pin_id % 1000; | ||||
|   *node_index = (input_pin_id - *socket_index) / 1000; | ||||
| } | ||||
| 
 | ||||
| inline void OutputPinIdToNodeIndexAndSocketIndex( | ||||
|     unsigned long output_pin_id, | ||||
|     int* node_index, | ||||
|     int* socket_index) { | ||||
|   *socket_index = ((output_pin_id - 500) % 1000); | ||||
|   *node_index = (output_pin_id - *socket_index) / 1000; | ||||
| } | ||||
| 
 | ||||
| void SyncTrackEditor(SyncTrack* sync_track); | ||||
| 
 | ||||
| void SkinnedMeshWidget(SkinnedMesh* skinned_mesh); | ||||
| 
 | ||||
| void AnimGraphEditorClear(); | ||||
| 
 | ||||
| void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context); | ||||
| 
 | ||||
| void LegacyAnimGraphEditorUpdate(); | ||||
|  | ||||
| @ -323,11 +323,13 @@ static bool sAnimGraphResourceBlendTreeFromJson( | ||||
|   } | ||||
| 
 | ||||
|   // Graph outputs
 | ||||
|   const json& graph_outputs = json_data["nodes"][0]["inputs"]; | ||||
|   for (const auto& graph_output : graph_outputs) { | ||||
|     AnimNodeResource* graph_node = blend_tree_resource.GetNode(0); | ||||
|     graph_node->m_socket_accessor->m_inputs.push_back( | ||||
|         sJsonToSocket(graph_output)); | ||||
|   if (json_data["nodes"][0].contains("inputs")) { | ||||
|     const json& graph_outputs = json_data["nodes"][0]["inputs"]; | ||||
|     for (const auto& graph_output : graph_outputs) { | ||||
|       AnimNodeResource* graph_node = blend_tree_resource.GetNode(0); | ||||
|       graph_node->m_socket_accessor->m_inputs.push_back( | ||||
|           sJsonToSocket(graph_output)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Graph inputs (optional)
 | ||||
| @ -573,6 +575,33 @@ bool BlendTreeResource::IsConnectionValid( | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| const BlendTreeConnectionResource* BlendTreeResource::FindConnectionForSocket( | ||||
|     const AnimNodeResource* node, | ||||
|     const std::string& socket_name) const { | ||||
|   int node_index = GetNodeIndex(node); | ||||
| 
 | ||||
|   std::vector<BlendTreeConnectionResource>::const_iterator connection_iter = | ||||
|       std::find_if( | ||||
|           m_connections.begin(), | ||||
|           m_connections.end(), | ||||
|           [node_index, | ||||
|            socket_name](const BlendTreeConnectionResource& connection) { | ||||
|             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; | ||||
|           }); | ||||
| 
 | ||||
|   if (connection_iter != m_connections.end()) { | ||||
|     return &*connection_iter; | ||||
|   } | ||||
| 
 | ||||
|   return nullptr; | ||||
| } | ||||
| 
 | ||||
| void BlendTreeResource::UpdateTreeTopologyInfo() { | ||||
|   // TODO: Updating eval order and subtrees may get slow with many nodes. An
 | ||||
|   // iterative approach would scale better. But let's leave that optimization
 | ||||
| @ -640,6 +669,8 @@ void BlendTreeResource::UpdateNodeSubtrees() { | ||||
| } | ||||
| 
 | ||||
| bool AnimGraphResource::LoadFromFile(const char* filename) { | ||||
|   Clear(); | ||||
| 
 | ||||
|   std::ifstream input_file; | ||||
|   input_file.open(filename); | ||||
|   std::stringstream buffer; | ||||
|  | ||||
| @ -154,11 +154,16 @@ struct BlendTreeResource { | ||||
|       const AnimNodeResource* target_node, | ||||
|       const std::string& target_socket_name) const; | ||||
| 
 | ||||
|   const BlendTreeConnectionResource* FindConnectionForSocket( | ||||
|       const AnimNodeResource* node, | ||||
|       const std::string& socket_name) const; | ||||
| 
 | ||||
|   bool IsSocketConnected( | ||||
|       const AnimNodeResource* source_node, | ||||
|       const std::string& socket_name) { | ||||
|     assert(false && "Not yet implemented"); | ||||
|     return false; | ||||
|       const AnimNodeResource* node, | ||||
|       const std::string& socket_name) const { | ||||
|     const BlendTreeConnectionResource* connection = | ||||
|         FindConnectionForSocket(node, socket_name); | ||||
|     return connection != nullptr; | ||||
|   } | ||||
| 
 | ||||
|   std::vector<Socket*> GetConstantNodeInputs( | ||||
|  | ||||
| @ -561,6 +561,7 @@ int main() { | ||||
|   AnimData anim_graph_output; | ||||
|   anim_graph_output.m_local_matrices.resize( | ||||
|       skinned_mesh.m_skeleton.num_soa_joints()); | ||||
|   AnimGraphEditorClear(); | ||||
| 
 | ||||
|   state.time.factor = 1.0f; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										40
									
								
								tests/AnimGraphEditorTests.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								tests/AnimGraphEditorTests.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| #include "AnimGraph/AnimGraphEditor.h" | ||||
| #include "catch.hpp" | ||||
| 
 | ||||
| TEST_CASE("Node Socket To InputPin Conversion", "[animGraphEditor]") { | ||||
|   int node_index = 321; | ||||
|   int socket_index = 221; | ||||
|   long socket_id; | ||||
| 
 | ||||
|   socket_id = NodeIndexAndSocketIndexToInputPinId(node_index, socket_index); | ||||
| 
 | ||||
|   int node_index_resolved; | ||||
|   int socket_index_resolved; | ||||
| 
 | ||||
|   InputPinIdToNodeIndexAndSocketIndex( | ||||
|       socket_id, | ||||
|       &node_index_resolved, | ||||
|       &socket_index_resolved); | ||||
| 
 | ||||
|   CHECK(node_index == node_index_resolved); | ||||
|   CHECK(socket_index == socket_index_resolved); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("Node Socket To OutputPin Conversion", "[animGraphEditor]") { | ||||
|   int node_index = 321; | ||||
|   int socket_index = 221; | ||||
|   long socket_id; | ||||
| 
 | ||||
|   socket_id = NodeIndexAndSocketIndexToOutputPinId(node_index, socket_index); | ||||
| 
 | ||||
|   int node_index_resolved; | ||||
|   int socket_index_resolved; | ||||
| 
 | ||||
|   OutputPinIdToNodeIndexAndSocketIndex( | ||||
|       socket_id, | ||||
|       &node_index_resolved, | ||||
|       &socket_index_resolved); | ||||
| 
 | ||||
|   CHECK(node_index == node_index_resolved); | ||||
|   CHECK(socket_index == socket_index_resolved); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Martin Felis
						Martin Felis