WIP: making BlendTree Editor usable.

This commit is contained in:
Martin Felis 2026-01-30 15:33:27 +01:00
parent 6330e34ea5
commit 0554691e46
4 changed files with 110 additions and 41 deletions

View File

@ -132,12 +132,16 @@ void BLTAnimationGraph::_get_property_list(List<PropertyInfo> *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<AnimationPlayer>(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<BLTAnimationNode> &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<AnimationPlayer>(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);
}

View File

@ -23,7 +23,7 @@ private:
void _update_properties() const;
void _update_properties_for_node(const String &p_base_path, Ref<BLTAnimationNode> p_node) const;
void _tree_changed();
void _graph_changed(const StringName &node_name);
protected:
void _notification(int p_what);

View File

@ -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<StringName> BLTAnimationNodeSampler::get_animations_as_typed_array() const {
TypedArray<StringName> typed_arr;
if (animation_player == nullptr) {
print_error(vformat("BLTAnimationNodeSampler '%s' not yet initialized", get_name()));
return typed_arr;
}
Vector<StringName> vec;
List<StringName> animation_libraries;
animation_player->get_animation_library_list(&animation_libraries);
for (const StringName &library_name : animation_libraries) {
Ref<AnimationLibrary> library = animation_player->get_animation_library(library_name);
List<StringName> 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<AnimationData *> &inputs, AnimationData &output) {
@ -476,10 +517,10 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref<BLTAnimati
node_connection_info.push_back(connection_info);
}
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref<BLTAnimationNode> &node) {
bool BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref<BLTAnimationNode> &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<BLTAnim
}
}
}
return true;
}
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::sort_nodes_and_references() {
@ -665,6 +708,8 @@ BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTr
uint32_t connection_index = find_connection_index(source_node, target_node, target_port_name);
assert(connection_index >= 0);
connections.remove_at(connection_index);
} else {
return CONNECTION_ERROR_CONNECTION_NOT_FOUND;
}
return CONNECTION_OK;

View File

@ -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 <cassert>
@ -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<Ref<BLTAnimationNode>> &input_nodes) {
// By default, all inputs nodes are activated.
for (const Ref<BLTAnimationNode> &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<StringName> get_animations_as_typed_array() const;
private:
Ref<Animation> 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<int> &removed_subtree_indices);
void add_node(const Ref<BLTAnimationNode> &node);
void remove_node(const Ref<BLTAnimationNode> &node);
bool remove_node(const Ref<BLTAnimationNode> &node);
ConnectionError is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, StringName target_port_name) const;
ConnectionError add_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &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<BLTAnimationNode> &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<BLTAnimationNode> &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<StringName> get_node_names_as_typed_array() const {
@ -646,30 +658,25 @@ public:
}
ConnectionError is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &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<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &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<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &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<Ref<BLTAnimationNode>> &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<BLTAnimationNode> &node = tree_graph.nodes[i];
@ -759,7 +773,7 @@ public:
}
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &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<BLTAnimationNode> &node = tree_graph.nodes[i];