From 5e34aaf3db79351a1ca088ef852830c93e232f70 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Thu, 25 Apr 2024 21:12:08 +0200 Subject: [PATCH] Extremely basic blend tree editing. --- CMakeLists.txt | 1 + src/AnimGraph/AnimGraphEditor.cc | 173 +++++++++++++++++++++++------ src/AnimGraph/AnimGraphEditor.h | 30 +++++ src/AnimGraph/AnimGraphResource.cc | 41 ++++++- src/AnimGraph/AnimGraphResource.h | 13 ++- src/main.cc | 1 + tests/AnimGraphEditorTests.cc | 40 +++++++ 7 files changed, 256 insertions(+), 43 deletions(-) create mode 100644 tests/AnimGraphEditorTests.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 1387d6a..0cfdc42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/AnimGraph/AnimGraphEditor.cc b/src/AnimGraph/AnimGraphEditor.cc index d82e8f2..3371bf8 100644 --- a/src/AnimGraph/AnimGraphEditor.cc +++ b/src/AnimGraph/AnimGraphEditor.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(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::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(node_id), static_cast(j)), + NodeIndexAndSocketIndexToInputPinId( + static_cast(node_id), + static_cast(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(node_id), static_cast(j)), + NodeIndexAndSocketIndexToOutputPinId( + static_cast(node_id), + static_cast(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(connection_resource->source_node_index), source_socket_index); - int target_socket_id = GetNodeInputSocketId( + int target_socket_pin_id = NodeIndexAndSocketIndexToInputPinId( static_cast(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; diff --git a/src/AnimGraph/AnimGraphEditor.h b/src/AnimGraph/AnimGraphEditor.h index 31814b6..1d4e1df 100644 --- a/src/AnimGraph/AnimGraphEditor.h +++ b/src/AnimGraph/AnimGraphEditor.h @@ -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(); diff --git a/src/AnimGraph/AnimGraphResource.cc b/src/AnimGraph/AnimGraphResource.cc index ba346ca..b6794f6 100644 --- a/src/AnimGraph/AnimGraphResource.cc +++ b/src/AnimGraph/AnimGraphResource.cc @@ -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::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; diff --git a/src/AnimGraph/AnimGraphResource.h b/src/AnimGraph/AnimGraphResource.h index 51fcab7..2b93b99 100644 --- a/src/AnimGraph/AnimGraphResource.h +++ b/src/AnimGraph/AnimGraphResource.h @@ -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 GetConstantNodeInputs( diff --git a/src/main.cc b/src/main.cc index 846946d..2c3f7a9 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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; diff --git a/tests/AnimGraphEditorTests.cc b/tests/AnimGraphEditorTests.cc new file mode 100644 index 0000000..04244ec --- /dev/null +++ b/tests/AnimGraphEditorTests.cc @@ -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); +} \ No newline at end of file