From 1e7dd4ba45acfccecfdd711650b8a85141979fc7 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Sat, 24 Jan 2026 23:10:40 +0100 Subject: [PATCH] BlendTree connection can now be removed. --- blendalot_animation_node.cpp | 90 ++++++++++++++++++++++------- blendalot_animation_node.h | 38 +++++++++--- tests/test_synced_animation_graph.h | 75 +++++++++++++++++++++++- 3 files changed, 172 insertions(+), 31 deletions(-) diff --git a/blendalot_animation_node.cpp b/blendalot_animation_node.cpp index 936eb83..fc3d97b 100644 --- a/blendalot_animation_node.cpp +++ b/blendalot_animation_node.cpp @@ -468,7 +468,10 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { - ConnectionError result = is_connection_valid(source_node, target_node, target_port_name); - if (result != CONNECTION_OK) { - return result; +void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_subtree_and_update_subtrees_recursive(int node_index, const HashSet &removed_subtree_indices) { + NodeConnectionInfo &connection_info = node_connection_info[node_index]; + + for (int subtree_node_index : removed_subtree_indices) { + connection_info.input_subtree_node_indices.erase(subtree_node_index); } - int source_node_index = find_node_index(source_node); - int target_node_index = find_node_index(target_node); - int target_input_port_index = target_node->get_input_index(target_port_name); - - node_connection_info[source_node_index].parent_node_index = target_node_index; - node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] = source_node_index; - connections.push_back(BLTBlendTreeConnection{ source_node, target_node, target_port_name }); - - add_index_and_update_subtrees_recursive(source_node_index, target_node_index); - - return CONNECTION_OK; + if (connection_info.parent_node_index != -1) { + remove_subtree_and_update_subtrees_recursive(connection_info.parent_node_index, removed_subtree_indices); + } } BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTreeGraph::is_connection_valid(const Ref &source_node, const Ref &target_node, StringName target_port_name) const { @@ -580,3 +576,53 @@ BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTr return CONNECTION_OK; } + +BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { + ConnectionError result = is_connection_valid(source_node, target_node, target_port_name); + if (result != CONNECTION_OK) { + return result; + } + + int source_node_index = find_node_index(source_node); + int target_node_index = find_node_index(target_node); + int target_input_port_index = target_node->get_input_index(target_port_name); + + node_connection_info[source_node_index].parent_node_index = target_node_index; + node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] = source_node_index; + connections.push_back(BLTBlendTreeConnection{ source_node, target_node, target_port_name }); + + add_index_and_update_subtrees_recursive(source_node_index, target_node_index); + + return CONNECTION_OK; +} + +int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_connection_index(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) const { + for (uint32_t i = 0; i < connections.size(); i++) { + if (connections[i].source_node == source_node && connections[i].target_node == target_node && connections[i].target_port_name == target_port_name) { + return i; + } + } + + return -1; +} + +BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { + int source_node_index = find_node_index(source_node); + NodeConnectionInfo &connection_info = node_connection_info[source_node_index]; + + if (connection_info.parent_node_index != -1) { + NodeConnectionInfo &parent_connection_info = node_connection_info[connection_info.parent_node_index]; + parent_connection_info.input_subtree_node_indices.erase(source_node_index); + parent_connection_info.connected_child_node_index_at_port[target_node->get_input_index(target_port_name)] = -1; + + remove_subtree_and_update_subtrees_recursive(connection_info.parent_node_index, connection_info.input_subtree_node_indices); + + connection_info.parent_node_index = -1; + + uint32_t connection_index = find_connection_index(source_node, target_node, target_port_name); + assert(connection_index >= 0); + connections.remove_at(connection_index); + } + + return CONNECTION_OK; +} diff --git a/blendalot_animation_node.h b/blendalot_animation_node.h index 3bea3a7..d9f24c5 100644 --- a/blendalot_animation_node.h +++ b/blendalot_animation_node.h @@ -441,9 +441,9 @@ private: }; struct BLTBlendTreeConnection { - const Ref source_node = nullptr; - const Ref target_node = nullptr; - const StringName target_port_name = ""; + Ref source_node = nullptr; + Ref target_node = nullptr; + StringName target_port_name = ""; }; class BLTAnimationNodeBlendTree : public BLTAnimationNode { @@ -495,7 +495,19 @@ public: } } - void _print_subtree() const; + void _print_subtree() const { + String result = vformat("subtree node indices (%d): ", input_subtree_node_indices.size()); + bool is_first = true; + for (int index : input_subtree_node_indices) { + if (is_first) { + result += vformat("%d", index); + is_first = false; + } else { + result += vformat(", %d", index); + } + } + print_line(result); + } }; Vector> nodes; // All added nodes @@ -505,17 +517,20 @@ public: BLTBlendTreeGraph(); Ref get_output_node(); - int find_node_index(const Ref &node) const; int find_node_index_by_name(const StringName &name) const; void sort_nodes_and_references(); LocalVector get_sorted_node_indices(); void sort_nodes_recursive(int node_index, LocalVector &result); - void add_index_and_update_subtrees_recursive(int node, int node_parent); - ConnectionError is_connection_valid(const Ref &source_node, const Ref &target_node, StringName target_port_name) const; + void add_index_and_update_subtrees_recursive(int node_index, int node_parent_index); + void remove_subtree_and_update_subtrees_recursive(int node, const HashSet &removed_subtree_indices); void add_node(const Ref &node); + + ConnectionError is_connection_valid(const Ref &source_node, const Ref &target_node, StringName target_port_name) const; ConnectionError add_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name); + int find_connection_index(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) const; + ConnectionError remove_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name); }; private: @@ -638,6 +653,15 @@ public: return tree_graph.add_connection(source_node, target_node, target_port_name); } + ConnectionError remove_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { + if (tree_initialized) { + print_error("Cannot remove connection to BlendTree: BlendTree already initialized."); + return CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED; + } + + return tree_graph.remove_connection(source_node, target_node, target_port_name); + } + Array get_connections_as_array() const { Array result; for (const BLTBlendTreeConnection &connection : tree_graph.connections) { diff --git a/tests/test_synced_animation_graph.h b/tests/test_synced_animation_graph.h index 94ef779..1b272ff 100644 --- a/tests/test_synced_animation_graph.h +++ b/tests/test_synced_animation_graph.h @@ -213,10 +213,10 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") { tree_constructor.sort_nodes_and_references(); - // Check that for node i all input nodes have a node index j > i. + // Check that for node i all input nodes have a node index j >= i (i is part of the subtree) for (unsigned int i = 0; i < tree_constructor.nodes.size(); i++) { for (int input_index : tree_constructor.node_connection_info[i].input_subtree_node_indices) { - CHECK(input_index > i); + CHECK(input_index >= i); } } } @@ -455,4 +455,75 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph } } +TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTreeGraph][ChangeConnectivity] BlendTreeGraph with various nodes and connections that are removed") { + BLTAnimationNodeBlendTree::BLTBlendTreeGraph blend_tree_graph; + + // TestAnimationA + Ref animation_sampler_node_a; + animation_sampler_node_a.instantiate(); + animation_sampler_node_a->animation_name = "animation_library/TestAnimationA"; + + blend_tree_graph.add_node(animation_sampler_node_a); + + // TestAnimationB + Ref animation_sampler_node_b; + animation_sampler_node_b.instantiate(); + animation_sampler_node_b->animation_name = "animation_library/TestAnimationB"; + + blend_tree_graph.add_node(animation_sampler_node_b); + + // TestAnimationB + Ref animation_sampler_node_c; + animation_sampler_node_c.instantiate(); + animation_sampler_node_c->animation_name = "animation_library/TestAnimationC"; + + blend_tree_graph.add_node(animation_sampler_node_c); + + // Blend2A + Ref blend2_node_a; + blend2_node_a.instantiate(); + blend2_node_a->set_name("Blend2A"); + blend2_node_a->blend_weight = 0.5; + blend2_node_a->sync = false; + + blend_tree_graph.add_node(blend2_node_a); + + // Blend2B + Ref blend2_node_b; + blend2_node_b.instantiate(); + blend2_node_b->set_name("Blend2A"); + blend2_node_b->blend_weight = 0.5; + blend2_node_b->sync = false; + + blend_tree_graph.add_node(blend2_node_b); + + // Connect nodes: Subgraph Output, Blend2A, SamplerA + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(blend2_node_a, blend_tree_graph.get_output_node(), "Output")); + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_a, blend2_node_a, "Input0")); + + // Connect nodes: Subgraph Blend2A, SamplerB, SamplerB + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_b, blend2_node_b, "Input0")); + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_c, blend2_node_b, "Input1")); + + HashSet subgraph_output_initial = blend_tree_graph.node_connection_info[0].input_subtree_node_indices; + HashSet subgraph_blend2a_initial = blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_a)].input_subtree_node_indices; + HashSet subgraph_blend2b_initial = blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_b)].input_subtree_node_indices; + + // Add and remove connection + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(blend2_node_b, blend2_node_a, "Input1")); + blend_tree_graph.remove_connection(blend2_node_b, blend2_node_a, "Input1"); + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.is_connection_valid(blend2_node_b, blend2_node_a, "Input1")); + + // Check that we have the same subgraphs as before the connection + CHECK(subgraph_output_initial == blend_tree_graph.node_connection_info[0].input_subtree_node_indices); + CHECK(subgraph_blend2a_initial == blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_a)].input_subtree_node_indices); + CHECK(subgraph_blend2b_initial == blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_b)].input_subtree_node_indices); + + // Check that we also do not + for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) { + bool connection_equals_removed_connection = connection.source_node == blend2_node_b && connection.target_node == blend2_node_a && connection.target_port_name == "Input1"; + CHECK(connection_equals_removed_connection == false); + } +} + } //namespace TestSyncedAnimationGraph \ No newline at end of file