diff --git a/synced_animation_node.h b/synced_animation_node.h index 9d597ac..5e172ed 100644 --- a/synced_animation_node.h +++ b/synced_animation_node.h @@ -76,10 +76,9 @@ protected: }; struct SyncTrack { - }; -class SyncedAnimationNode: public Resource { +class SyncedAnimationNode : public Resource { GDCLASS(SyncedAnimationNode, Resource); friend class SyncedAnimationGraph; @@ -133,11 +132,22 @@ public: } } } - virtual void evaluate(GraphEvaluationContext &context, const Vector& inputs, AnimationData &output) {} + virtual void evaluate(GraphEvaluationContext &context, const Vector &inputs, AnimationData &output) {} bool is_active() const { return active; } bool set_input_node(const StringName &socket_name, SyncedAnimationNode *node); - virtual void get_input_names(Vector &inputs) {}; + virtual void get_input_names(Vector &inputs) const {} + + int get_node_input_index(const StringName &port_name) const { + Vector inputs; + get_input_names(inputs); + return inputs.find(port_name); + } + int get_node_input_count() const { + Vector inputs; + get_input_names(inputs); + return inputs.size(); + } private: bool active = false; @@ -153,19 +163,19 @@ private: Ref animation; void initialize(GraphEvaluationContext &context) override; - void evaluate(GraphEvaluationContext &context, const Vector& inputs, AnimationData &output) override; + void evaluate(GraphEvaluationContext &context, const Vector &inputs, AnimationData &output) override; }; class OutputNode : public SyncedAnimationNode { public: - void get_input_names(Vector &inputs) override { + void get_input_names(Vector &inputs) const override { inputs.push_back("Input"); } }; class AnimationBlend2Node : public SyncedAnimationNode { public: - void get_input_names(Vector &inputs) override { + void get_input_names(Vector &inputs) const override { inputs.push_back("Input0"); inputs.push_back("Input1"); } @@ -178,8 +188,37 @@ struct BlendTreeConnection { }; struct SortedTreeConstructor { - Vector> node_subgraph; - Vector> nodes; + struct NodeConnectionInfo { + int parent_node_index = -1; + HashSet input_subtree_node_indices; + LocalVector connected_child_node_index_at_port; + + NodeConnectionInfo() = default; + + explicit NodeConnectionInfo(const SyncedAnimationNode *node) { + parent_node_index = -1; + for (int i = 0; i < node->get_node_input_count(); i++) { + connected_child_node_index_at_port.push_back(-1); + } + } + + 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 + LocalVector node_connection_info; Vector connections; SortedTreeConstructor() { @@ -189,11 +228,11 @@ struct SortedTreeConstructor { add_node(output_node); } - Ref get_output_node() { + Ref get_output_node() const { return nodes[0]; } - int get_node_index(const Ref node) { + int get_node_index(const Ref &node) const { for (int i = 0; i < nodes.size(); i++) { if (nodes[i] == node) { return i; @@ -203,35 +242,56 @@ struct SortedTreeConstructor { return -1; } - void add_node(const Ref& node) { + void add_node(const Ref &node) { nodes.push_back(node); - node_subgraph.push_back(HashSet()); + node_connection_info.push_back(NodeConnectionInfo(node.ptr())); } - bool add_connection(const Ref& source_node, const Ref& target_node, const StringName& target_port_name) { + void add_index_and_update_subtrees_recursive(int node, int node_parent) { + if (node_parent == -1) { + return; + } + + node_connection_info[node_parent].input_subtree_node_indices.insert(node); + + for (int index : node_connection_info[node].input_subtree_node_indices) { + node_connection_info[node_parent].input_subtree_node_indices.insert(index); + } + + add_index_and_update_subtrees_recursive(node_parent, node_connection_info[node_parent].parent_node_index); + } + + bool add_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { if (!is_connection_valid(source_node, target_node, target_port_name)) { return false; } - // check for loops int source_node_index = get_node_index(source_node); - if (node_subgraph.get(source_node_index).has(target_node.ptr())) { - return false; - } - int target_node_index = get_node_index(target_node); - node_subgraph.get(target_node_index).insert(source_node.ptr()); + int target_input_port_index = target_node->get_node_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; + + add_index_and_update_subtrees_recursive(source_node_index, target_node_index); return true; } - bool is_connection_valid(const Ref& source_node, const Ref& target_node, StringName target_port_name) { - if (get_node_index(source_node) == -1) { + bool is_connection_valid(const Ref &source_node, const Ref &target_node, StringName target_port_name) { + int source_node_index = get_node_index(source_node); + if (source_node_index == -1) { print_error("Cannot connect nodes: source node not found."); return false; } - if (get_node_index(target_node) == -1) { + if (node_connection_info[source_node_index].parent_node_index != -1) { + print_error("Cannot connect node: source node already has a parent."); + return false; + } + + int target_node_index = get_node_index(target_node); + if (target_node_index == -1) { print_error("Cannot connect nodes: target node not found."); return false; } @@ -249,12 +309,22 @@ struct SortedTreeConstructor { return false; } + int target_input_port_index = target_node->get_node_input_index(target_port_name); + if (node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] != -1) { + print_error("Cannot connect node: target port already connected"); + return false; + } + + if (node_connection_info[source_node_index].input_subtree_node_indices.has(target_node_index)) { + print_error("Cannot connect node: connection would create loop."); + return false; + } + return true; } }; class SyncedBlendTree : public SyncedAnimationNode { - Vector> tree_nodes; Vector> tree_node_subgraph; @@ -268,7 +338,6 @@ class SyncedBlendTree : public SyncedAnimationNode { Vector> node_output_data; void _setup_graph_evaluation() { - // After this functions we must have: // * nodes sorted by evaluation order // * node_parent filled @@ -297,47 +366,12 @@ public: return -1; } - int add_node(const Ref& node) { + int add_node(const Ref &node) { nodes.push_back(node); int node_index = nodes.size() - 1; return node_index; } - bool connect_nodes(const Ref& source_node, const Ref& target_node, StringName target_socket_name) { - if (!is_connection_valid(source_node, target_node, target_socket_name)) { - return false; - } - - return false; - } - - bool is_connection_valid(const Ref& source_node, const Ref& target_node, StringName target_socket_name) { - if (get_node_index(source_node) == -1) { - print_error("Cannot connect nodes: source node not found."); - return false; - } - - if (get_node_index(target_node) == -1) { - print_error("Cannot connect nodes: target node not found."); - return false; - } - - if (target_node == get_output_node() && tree_connections.size() > 0) { - print_error("Cannot add connection to output node: output node is already connected"); - return false; - } - - Vector target_inputs; - target_node->get_input_names(target_inputs); - - if (!target_inputs.has(target_socket_name)) { - print_error("Cannot connect nodes: target socket not found."); - return false; - } - - return true; - } - // overrides from SyncedAnimationNode void initialize(GraphEvaluationContext &context) override { for (Ref node : nodes) { @@ -346,18 +380,14 @@ public: } void activate_inputs() override { - } void calculate_sync_track() override { - } void update_time(double p_delta) override { - } - void evaluate(GraphEvaluationContext &context, const Vector& inputs, AnimationData &output) override { - + void evaluate(GraphEvaluationContext &context, const Vector &inputs, AnimationData &output) override { } }; diff --git a/tests/test_synced_animation_graph.h b/tests/test_synced_animation_graph.h index 25d791d..7f3feb2 100644 --- a/tests/test_synced_animation_graph.h +++ b/tests/test_synced_animation_graph.h @@ -7,16 +7,16 @@ #include "tests/test_macros.h" struct SyncedAnimationGraphFixture { - Node* character_node; - Skeleton3D* skeleton_node; - AnimationPlayer* player_node; + Node *character_node; + Skeleton3D *skeleton_node; + AnimationPlayer *player_node; int hip_bone_index = -1; Ref test_animation; Ref animation_library; - SyncedAnimationGraph* synced_animation_graph; + SyncedAnimationGraph *synced_animation_graph; SyncedAnimationGraphFixture() { character_node = memnew(Node); character_node->set_name("CharacterNode"); @@ -37,7 +37,7 @@ struct SyncedAnimationGraphFixture { CHECK(track_index == 0); test_animation->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.)); test_animation->track_insert_key(track_index, 1.0, Vector3(1., 2., 3.)); - test_animation->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(),"Hips"))); + test_animation->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips"))); animation_library.instantiate(); animation_library->add_animation("TestAnimation", test_animation); @@ -68,19 +68,61 @@ TEST_CASE("[SyncedAnimationGraph] TestBlendTreeConstruction") { animation_sampler_node1->name = "Sampler1"; tree_constructor.add_node(animation_sampler_node1); + Ref animation_sampler_node2; + animation_sampler_node2.instantiate(); + animation_sampler_node2->name = "Sampler2"; + tree_constructor.add_node(animation_sampler_node2); + Ref node_blend0; node_blend0.instantiate(); - node_blend0->name = "Blend2"; + node_blend0->name = "Blend0"; tree_constructor.add_node(node_blend0); Ref node_blend1; node_blend1.instantiate(); - node_blend1->name = "Blend2"; + node_blend1->name = "Blend1"; tree_constructor.add_node(node_blend1); + // Tree + // Sampler0 -\ + // Sampler1 -+- Blend0 -\ + // Sampler2 ------------+ Blend1 - Output + CHECK(tree_constructor.add_connection(animation_sampler_node0, node_blend0, "Input0")); - CHECK(tree_constructor.add_connection(node_blend1, node_blend0, "Input1")); + + // Ensure that subtree is properly updated + int sampler0_index = tree_constructor.get_node_index(animation_sampler_node0); + int blend0_index = tree_constructor.get_node_index(node_blend0); + CHECK(tree_constructor.node_connection_info[blend0_index].input_subtree_node_indices.has(sampler0_index)); + + // Connect blend0 to blend1 + CHECK(tree_constructor.add_connection(node_blend0, node_blend1, "Input0")); + + // Connecting to an already connected port must fail + CHECK(!tree_constructor.add_connection(animation_sampler_node1, node_blend0, "Input0")); + // Correct connection of Sampler1 to Blend0 + CHECK(tree_constructor.add_connection(animation_sampler_node1, node_blend0, "Input1")); + + // Ensure that subtree is properly updated + int sampler1_index = tree_constructor.get_node_index(animation_sampler_node0); + int blend1_index = tree_constructor.get_node_index(node_blend1); + CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(sampler1_index)); + CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(sampler0_index)); + CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(blend0_index)); + + // Creating a loop must fail CHECK(!tree_constructor.add_connection(node_blend1, node_blend0, "Input1")); + + // Perform remaining connections + CHECK(tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Input")); + CHECK(tree_constructor.add_connection(animation_sampler_node2, node_blend1, "Input1")); + + // Output node must have all nodes in its subtree: + CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(1)); + CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(2)); + CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(3)); + CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(4)); + CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(5)); } TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SimpleAnimationSamplerTest" * doctest::skip(true)) { @@ -105,6 +147,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph CHECK(hip_bone_position.z == doctest::Approx(0.03)); } +// Currently disabled! TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SimpleBlendTreeTest" * doctest::skip(true)) { Ref synced_blend_tree_node; synced_blend_tree_node.instantiate(); @@ -114,9 +157,6 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph animation_sampler_node->animation_name = "animation_library/TestAnimation"; synced_blend_tree_node->add_node(animation_sampler_node); - - synced_blend_tree_node->connect_nodes(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Input"); - synced_animation_graph->set_graph_root_node(synced_blend_tree_node); Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin; @@ -134,5 +174,4 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph CHECK(hip_bone_position.z == doctest::Approx(0.03)); } - -} \ No newline at end of file +} //namespace TestSyncedAnimationGraph \ No newline at end of file