From 0554691e46bdd10962ef7d6f7098004697f20a15 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Fri, 30 Jan 2026 15:33:27 +0100 Subject: [PATCH] WIP: making BlendTree Editor usable. --- blendalot_animation_graph.cpp | 18 +++++++-- blendalot_animation_graph.h | 2 +- blendalot_animation_node.cpp | 59 ++++++++++++++++++++++++---- blendalot_animation_node.h | 72 +++++++++++++++++++++-------------- 4 files changed, 110 insertions(+), 41 deletions(-) diff --git a/blendalot_animation_graph.cpp b/blendalot_animation_graph.cpp index ee3921b..079dc04 100644 --- a/blendalot_animation_graph.cpp +++ b/blendalot_animation_graph.cpp @@ -132,12 +132,16 @@ void BLTAnimationGraph::_get_property_list(List *p_list) const { } } -void BLTAnimationGraph::_tree_changed() { +void BLTAnimationGraph::_graph_changed(const StringName &node_name) { + print_line(vformat("Graph changed %x", (uintptr_t)this)); + if (properties_dirty) { return; } callable_mp(this, &BLTAnimationGraph::_update_properties).call_deferred(); + callable_mp(this, &BLTAnimationGraph::_setup_graph).call_deferred(); + properties_dirty = true; } @@ -239,6 +243,8 @@ void BLTAnimationGraph::set_animation_player(const NodePath &p_path) { } graph_context.animation_player = Object::cast_to(get_node_or_null(animation_player_path)); + print_line(vformat("Setting animation player of graph %x to %x", (uintptr_t)(this), (uintptr_t)graph_context.animation_player)); + _setup_evaluation_context(); _setup_graph(); @@ -251,14 +257,15 @@ NodePath BLTAnimationGraph::get_animation_player() const { void BLTAnimationGraph::set_root_animation_node(const Ref &p_animation_node) { if (root_animation_node.is_valid()) { - root_animation_node->disconnect(SNAME("tree_changed"), callable_mp(this, &BLTAnimationGraph::_tree_changed)); + root_animation_node->disconnect(SNAME("node_changed"), callable_mp(this, &BLTAnimationGraph::_graph_changed)); } root_animation_node = p_animation_node; if (root_animation_node.is_valid()) { _setup_graph(); - root_animation_node->connect(SNAME("tree_changed"), callable_mp(this, &BLTAnimationGraph::_tree_changed)); + root_animation_node->connect(SNAME("node_changed"), callable_mp(this, &BLTAnimationGraph::_graph_changed)); + print_line(vformat("connected node_changed event to graph %x", (uintptr_t)this)); } properties_dirty = true; @@ -295,7 +302,7 @@ void BLTAnimationGraph::_process_graph(double p_delta, bool p_update_only) { return; } - if (graph_context.skeleton_3d == nullptr) { + if (graph_context.skeleton_3d == nullptr || graph_context.animation_player == nullptr) { return; } @@ -358,6 +365,7 @@ void BLTAnimationGraph::_set_process(bool p_process, bool p_force) { } void BLTAnimationGraph::_setup_evaluation_context() { + print_line("_setup_evaluation_context()"); _cleanup_evaluation_context(); graph_context.animation_player = Object::cast_to(get_node_or_null(animation_player_path)); @@ -374,6 +382,8 @@ void BLTAnimationGraph::_setup_graph() { return; } + print_line(vformat("_setup_graph() on graph %x and root node %x", (uintptr_t)(void *)(this), (uintptr_t)(root_animation_node.ptr()))); + root_animation_node->initialize(graph_context); } diff --git a/blendalot_animation_graph.h b/blendalot_animation_graph.h index a532997..560ba2c 100644 --- a/blendalot_animation_graph.h +++ b/blendalot_animation_graph.h @@ -23,7 +23,7 @@ private: void _update_properties() const; void _update_properties_for_node(const String &p_base_path, Ref p_node) const; - void _tree_changed(); + void _graph_changed(const StringName &node_name); protected: void _notification(int p_what); diff --git a/blendalot_animation_node.cpp b/blendalot_animation_node.cpp index 5afb8a3..da5c6f7 100644 --- a/blendalot_animation_node.cpp +++ b/blendalot_animation_node.cpp @@ -9,10 +9,11 @@ void BLTAnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_position"), &BLTAnimationNode::get_position); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position"), "set_position", "get_position"); - ADD_SIGNAL(MethodInfo("tree_changed")); ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name"))); ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name"))); + ADD_SIGNAL(MethodInfo(SNAME("node_changed"), PropertyInfo(Variant::STRING_NAME, "node_name"))); + ClassDB::bind_method(D_METHOD("get_input_names"), &BLTAnimationNode::get_input_names_as_typed_array); ClassDB::bind_method(D_METHOD("get_input_count"), &BLTAnimationNode::get_input_count); ClassDB::bind_method(D_METHOD("get_input_index"), &BLTAnimationNode::get_input_index); @@ -36,8 +37,8 @@ Variant BLTAnimationNode::get_parameter(const StringName &p_name) const { return Variant(); } -void BLTAnimationNode::_tree_changed() { - emit_signal(SNAME("tree_changed")); +void BLTAnimationNode::_node_changed() { + emit_signal(SNAME("node_changed"), get_name()); } void BLTAnimationNode::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { @@ -59,7 +60,6 @@ void BLTAnimationNodeBlendTree::_bind_methods() { ClassDB::bind_method(D_METHOD("add_connection", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::add_connection); ClassDB::bind_method(D_METHOD("remove_connection", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::remove_connection); ClassDB::bind_method(D_METHOD("get_connections"), &BLTAnimationNodeBlendTree::get_connections_as_array); - BIND_CONSTANT(CONNECTION_OK); BIND_CONSTANT(CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED); BIND_CONSTANT(CONNECTION_ERROR_NO_SOURCE_NODE); @@ -273,7 +273,12 @@ bool BLTAnimationNodeSampler::initialize(GraphEvaluationContext &context) { return false; } - animation = context.animation_player->get_animation(animation_name); + animation_player = context.animation_player; + if (animation_name.is_empty()) { + return true; + } + + animation = animation_player->get_animation(animation_name); if (!animation.is_valid()) { print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", get_name(), animation_name)); return false; @@ -333,6 +338,11 @@ void BLTAnimationNodeSampler::evaluate(GraphEvaluationContext &context, const Lo output.sample_from_animation(animation, context.skeleton_3d, node_time_info.position); } +void BLTAnimationNodeSampler::set_animation_player(AnimationPlayer *p_player) { + animation_player = p_player; + _node_changed(); +} + void BLTAnimationNodeSampler::set_animation(const StringName &p_name) { animation_name = p_name; } @@ -341,11 +351,42 @@ StringName BLTAnimationNodeSampler::get_animation() const { return animation_name; } +TypedArray BLTAnimationNodeSampler::get_animations_as_typed_array() const { + TypedArray typed_arr; + + if (animation_player == nullptr) { + print_error(vformat("BLTAnimationNodeSampler '%s' not yet initialized", get_name())); + return typed_arr; + } + + Vector vec; + + List animation_libraries; + animation_player->get_animation_library_list(&animation_libraries); + + for (const StringName &library_name : animation_libraries) { + Ref library = animation_player->get_animation_library(library_name); + List animation_list; + library->get_animation_list(&animation_list); + for (const StringName &library_animation : animation_list) { + vec.push_back(library_animation); + } + } + + typed_arr.resize(vec.size()); + for (uint32_t i = 0; i < vec.size(); i++) { + typed_arr[i] = vec[i]; + } + return typed_arr; +} + void BLTAnimationNodeSampler::_bind_methods() { ClassDB::bind_method(D_METHOD("set_animation", "name"), &BLTAnimationNodeSampler::set_animation); ClassDB::bind_method(D_METHOD("get_animation"), &BLTAnimationNodeSampler::get_animation); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); + + ClassDB::bind_method(D_METHOD("get_animations"), &BLTAnimationNodeSampler::get_animations_as_typed_array); } void BLTAnimationNodeBlend2::evaluate(GraphEvaluationContext &context, const LocalVector &inputs, AnimationData &output) { @@ -476,10 +517,10 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref &node) { +bool BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref &node) { if (node == get_output_node()) { // Output node not allowed to be removed - return; + return false; } int removed_node_index = find_node_index(node); @@ -515,6 +556,8 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref= 0); connections.remove_at(connection_index); + } else { + return CONNECTION_ERROR_CONNECTION_NOT_FOUND; } return CONNECTION_OK; diff --git a/blendalot_animation_node.h b/blendalot_animation_node.h index 5c6cb2a..b187890 100644 --- a/blendalot_animation_node.h +++ b/blendalot_animation_node.h @@ -5,6 +5,7 @@ #include "scene/3d/skeleton_3d.h" #include "scene/animation/animation_player.h" +#include "scene/resources/animation_library.h" #include "sync_track.h" #include @@ -245,7 +246,7 @@ protected: virtual void set_parameter(const StringName &p_name, const Variant &p_value); virtual Variant get_parameter(const StringName &p_name) const; - virtual void _tree_changed(); + virtual void _node_changed(); virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name); virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node); @@ -273,6 +274,11 @@ public: virtual void activate_inputs(const Vector> &input_nodes) { // By default, all inputs nodes are activated. for (const Ref &node : input_nodes) { + if (node.ptr() == nullptr) { + // TODO: add checking whether tree can be evaluated, i.e. whether all inputs are properly connected. + continue; + } + node->active = true; node->node_time_info.is_synced = node_time_info.is_synced; } @@ -339,10 +345,14 @@ class BLTAnimationNodeSampler : public BLTAnimationNode { public: StringName animation_name; + AnimationPlayer *animation_player = nullptr; + void set_animation_player(AnimationPlayer *p_player); void set_animation(const StringName &p_name); StringName get_animation() const; + TypedArray get_animations_as_typed_array() const; + private: Ref animation; @@ -459,6 +469,7 @@ public: CONNECTION_ERROR_TARGET_PORT_NOT_FOUND, CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED, CONNECTION_ERROR_CONNECTION_CREATES_LOOP, + CONNECTION_ERROR_CONNECTION_NOT_FOUND }; /** @@ -526,7 +537,7 @@ public: void remove_subtree_and_update_subtrees_recursive(int node, const HashSet &removed_subtree_indices); void add_node(const Ref &node); - void remove_node(const Ref &node); + bool remove_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); @@ -537,6 +548,7 @@ public: private: BLTBlendTreeGraph tree_graph; bool tree_initialized = false; + GraphEvaluationContext *_graph_evaluation_context = nullptr; void sort_nodes() { _node_runtime_data.clear(); @@ -564,7 +576,11 @@ private: for (int port_index = 0; port_index < node->get_input_count(); port_index++) { const int connected_node_index = tree_graph.node_connection_info[i].connected_child_node_index_at_port[port_index]; - node_runtime_data.input_nodes.push_back(tree_graph.nodes[connected_node_index]); + if (connected_node_index == -1) { + node_runtime_data.input_nodes.push_back(nullptr); + } else { + node_runtime_data.input_nodes.push_back(tree_graph.nodes[connected_node_index]); + } } } } @@ -587,26 +603,22 @@ public: return tree_graph.find_node_index(node); } - int find_node_index_by_name(const StringName &name) const { - return tree_graph.find_node_index_by_name(name); + int find_node_index_by_name(const StringName &p_name) const { + return tree_graph.find_node_index_by_name(p_name); } void add_node(const Ref &node) { - if (tree_initialized) { - print_error("Cannot add node to BlendTree: BlendTree already initialized."); - return; - } - tree_graph.add_node(node); + + if (_graph_evaluation_context != nullptr) { + node->initialize(*_graph_evaluation_context); + } } void remove_node(const Ref &node) { - if (tree_initialized) { - print_error("Cannot remove node from BlendTree: BlendTree already initialized."); - return; + if (tree_graph.remove_node(node)) { + _node_changed(); } - - tree_graph.remove_node(node); } TypedArray get_node_names_as_typed_array() const { @@ -646,30 +658,25 @@ public: } ConnectionError is_connection_valid(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { - if (tree_initialized) { - print_error("Cannot add connection to BlendTree: BlendTree already initialized."); - return CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED; - } - return tree_graph.is_connection_valid(source_node, target_node, target_port_name); } ConnectionError add_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { - if (tree_initialized) { - print_error("Cannot add connection to BlendTree: BlendTree already initialized."); - return CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED; + ConnectionError result = tree_graph.add_connection(source_node, target_node, target_port_name); + if (result == CONNECTION_OK) { + _node_changed(); } - return tree_graph.add_connection(source_node, target_node, target_port_name); + return result; } 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; + ConnectionError result = tree_graph.remove_connection(source_node, target_node, target_port_name); + if (result == CONNECTION_OK) { + _node_changed(); } - return tree_graph.remove_connection(source_node, target_node, target_port_name); + return result; } Array get_connections_as_array() const { @@ -689,6 +696,8 @@ public: return false; } + _graph_evaluation_context = &context; + sort_nodes(); setup_runtime_data(); @@ -707,6 +716,11 @@ public: activate_inputs(const Vector> &input_nodes) override { GodotProfileZone("SyncedBlendTree::activate_inputs"); + // TODO: add checking whether tree can be evaluated, i.e. whether all inputs are properly connected. + if (tree_graph.nodes.size() == 1) { + return; + } + tree_graph.nodes[0]->active = true; for (uint32_t i = 0; i < tree_graph.nodes.size(); i++) { const Ref &node = tree_graph.nodes[i]; @@ -759,7 +773,7 @@ public: } void evaluate(GraphEvaluationContext &context, const LocalVector &input_datas, AnimationData &output_data) override { - ZoneScopedN("SyncedBlendTree::evaluate"); + GodotProfileZone("SyncedBlendTree::evaluate"); for (uint32_t i = tree_graph.nodes.size() - 1; i > 0; i--) { const Ref &node = tree_graph.nodes[i];