From d57fe50d5fc00e2a96ead41feb14c0e566633bce Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Sun, 22 Feb 2026 00:56:53 +0100 Subject: [PATCH] Added BLTAnimationNodeTimeScale. --- blendalot_animation_node.cpp | 272 ++++++++++++++++++++--------------- blendalot_animation_node.h | 50 ++++++- register_types.cpp | 1 + 3 files changed, 201 insertions(+), 122 deletions(-) diff --git a/blendalot_animation_node.cpp b/blendalot_animation_node.cpp index 2a3c60b..3cd933c 100644 --- a/blendalot_animation_node.cpp +++ b/blendalot_animation_node.cpp @@ -49,125 +49,6 @@ void BLTAnimationNode::_animation_node_removed(const ObjectID &p_oid, const Stri emit_signal(SNAME("animation_node_removed"), p_oid, p_node); } -void BLTAnimationNodeBlendTree::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_node", "animation_node"), &BLTAnimationNodeBlendTree::add_node); - ClassDB::bind_method(D_METHOD("remove_node", "animation_node"), &BLTAnimationNodeBlendTree::remove_node); - ClassDB::bind_method(D_METHOD("get_node", "node_name"), &BLTAnimationNodeBlendTree::get_node); - ClassDB::bind_method(D_METHOD("get_output_node"), &BLTAnimationNodeBlendTree::get_output_node); - ClassDB::bind_method(D_METHOD("get_node_names"), &BLTAnimationNodeBlendTree::get_node_names_as_typed_array); - - ClassDB::bind_method(D_METHOD("is_connection_valid", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::is_connection_valid); - 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); - - ClassDB::bind_method(D_METHOD("set_graph_offset", "graph_offset"), &BLTAnimationNodeBlendTree::set_graph_offset); - ClassDB::bind_method(D_METHOD("get_graph_offset"), &BLTAnimationNodeBlendTree::get_graph_offset); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_graph_offset", "get_graph_offset"); - - BIND_CONSTANT(CONNECTION_OK); - BIND_CONSTANT(CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED); - BIND_CONSTANT(CONNECTION_ERROR_NO_SOURCE_NODE); - BIND_CONSTANT(CONNECTION_ERROR_NO_TARGET_NODE); - BIND_CONSTANT(CONNECTION_ERROR_PARENT_EXISTS); - BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_NOT_FOUND); - BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED); - BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_CREATES_LOOP); -} - -void BLTAnimationNodeBlendTree::_get_property_list(List *p_list) const { - for (const Ref &node : tree_graph.nodes) { - String prop_name = node->get_name(); - if (prop_name != "Output") { - p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); - } - p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + prop_name + "/graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } - - p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); -} - -bool BLTAnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_value) const { - String prop_name = p_name; - if (prop_name.begins_with("nodes/")) { - String node_name = prop_name.get_slicec('/', 1); - String what = prop_name.get_slicec('/', 2); - int node_index = find_node_index_by_name(node_name); - - if (what == "node") { - if (node_index != -1) { - r_value = tree_graph.nodes[node_index]; - return true; - } - } - - if (what == "graph_offset") { - if (node_index != -1) { - r_value = tree_graph.nodes[node_index]->position; - return true; - } - } - } else if (prop_name == "node_connections") { - Array conns; - conns.resize(tree_graph.connections.size() * 3); - - int idx = 0; - for (const BLTBlendTreeConnection &connection : tree_graph.connections) { - conns[idx * 3 + 0] = connection.target_node->get_name(); - conns[idx * 3 + 1] = connection.target_node->get_input_index(connection.target_port_name); - conns[idx * 3 + 2] = connection.source_node->get_name(); - idx++; - } - - r_value = conns; - return true; - } - - return false; -} - -bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) { - String prop_name = p_name; - if (prop_name.begins_with("nodes/")) { - String node_name = prop_name.get_slicec('/', 1); - String what = prop_name.get_slicec('/', 2); - - if (what == "node") { - Ref anode = p_value; - if (anode.is_valid()) { - anode->set_name(node_name); - add_node(anode); - } - return true; - } - - if (what == "graph_offset") { - int node_index = find_node_index_by_name(node_name); - if (node_index > -1) { - tree_graph.nodes[node_index]->position = p_value; - } - return true; - } - } else if (prop_name == "node_connections") { - Array conns = p_value; - ERR_FAIL_COND_V(conns.size() % 3 != 0, false); - - for (int i = 0; i < conns.size(); i += 3) { - int target_node_index = find_node_index_by_name(conns[i]); - int target_node_port_index = conns[i + 1]; - int source_node_index = find_node_index_by_name(conns[i + 2]); - - Ref target_node = tree_graph.nodes[target_node_index]; - Vector target_input_names = target_node->get_input_names(); - - add_connection(tree_graph.nodes[source_node_index], target_node, target_input_names[target_node_port_index]); - } - return true; - } - - return false; -} - void AnimationData::sample_from_animation(const Ref &animation, const Skeleton3D *skeleton_3d, double p_time) { GodotProfileZone("AnimationData::sample_from_animation"); @@ -273,6 +154,9 @@ void AnimationDataAllocator::register_track_values(const Ref &animati default_data.allocate_track_values(animation, skeleton_3d); } +// +// BLTAnimationNodeSampler +// bool BLTAnimationNodeSampler::initialize(GraphEvaluationContext &context) { if (!BLTAnimationNode::initialize(context)) { return false; @@ -428,6 +312,34 @@ void BLTAnimationNodeSampler::_bind_methods() { ClassDB::bind_method(D_METHOD("get_animations"), &BLTAnimationNodeSampler::get_animations_as_typed_array); } +// +// BLTAnimationNodeTimeScale +// +void BLTAnimationNodeTimeScale::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::FLOAT, scale_name, PROPERTY_HINT_RANGE, "-10,10,0.01,or_less,or_greater")); +} + +bool BLTAnimationNodeTimeScale::_get(const StringName &p_name, Variant &r_value) const { + if (p_name == scale_name) { + r_value = scale; + return true; + } + + return false; +} + +bool BLTAnimationNodeTimeScale::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == scale_name) { + scale = p_value; + return true; + } + + return false; +} + +// +// BLTAnimationNodeBlend2 +// void BLTAnimationNodeBlend2::evaluate(GraphEvaluationContext &context, const LocalVector &inputs, AnimationData &output) { GodotProfileZone("AnimationBlend2Node::evaluate"); @@ -494,6 +406,9 @@ bool BLTAnimationNodeBlend2::_set(const StringName &p_name, const Variant &p_val return false; } +// +// BLTAnimationNodeBlendTree +// BLTAnimationNodeBlendTree::BLTBlendTreeGraph::BLTBlendTreeGraph() { Ref output_node; output_node.instantiate(); @@ -761,3 +676,122 @@ BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTr return CONNECTION_OK; } + +void BLTAnimationNodeBlendTree::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_node", "animation_node"), &BLTAnimationNodeBlendTree::add_node); + ClassDB::bind_method(D_METHOD("remove_node", "animation_node"), &BLTAnimationNodeBlendTree::remove_node); + ClassDB::bind_method(D_METHOD("get_node", "node_name"), &BLTAnimationNodeBlendTree::get_node); + ClassDB::bind_method(D_METHOD("get_output_node"), &BLTAnimationNodeBlendTree::get_output_node); + ClassDB::bind_method(D_METHOD("get_node_names"), &BLTAnimationNodeBlendTree::get_node_names_as_typed_array); + + ClassDB::bind_method(D_METHOD("is_connection_valid", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::is_connection_valid); + 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); + + ClassDB::bind_method(D_METHOD("set_graph_offset", "graph_offset"), &BLTAnimationNodeBlendTree::set_graph_offset); + ClassDB::bind_method(D_METHOD("get_graph_offset"), &BLTAnimationNodeBlendTree::get_graph_offset); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_graph_offset", "get_graph_offset"); + + BIND_CONSTANT(CONNECTION_OK); + BIND_CONSTANT(CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED); + BIND_CONSTANT(CONNECTION_ERROR_NO_SOURCE_NODE); + BIND_CONSTANT(CONNECTION_ERROR_NO_TARGET_NODE); + BIND_CONSTANT(CONNECTION_ERROR_PARENT_EXISTS); + BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_NOT_FOUND); + BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED); + BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_CREATES_LOOP); +} + +void BLTAnimationNodeBlendTree::_get_property_list(List *p_list) const { + for (const Ref &node : tree_graph.nodes) { + String prop_name = node->get_name(); + if (prop_name != "Output") { + p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + prop_name + "/graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } + + p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); +} + +bool BLTAnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_value) const { + String prop_name = p_name; + if (prop_name.begins_with("nodes/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); + int node_index = find_node_index_by_name(node_name); + + if (what == "node") { + if (node_index != -1) { + r_value = tree_graph.nodes[node_index]; + return true; + } + } + + if (what == "graph_offset") { + if (node_index != -1) { + r_value = tree_graph.nodes[node_index]->position; + return true; + } + } + } else if (prop_name == "node_connections") { + Array conns; + conns.resize(tree_graph.connections.size() * 3); + + int idx = 0; + for (const BLTBlendTreeConnection &connection : tree_graph.connections) { + conns[idx * 3 + 0] = connection.target_node->get_name(); + conns[idx * 3 + 1] = connection.target_node->get_input_index(connection.target_port_name); + conns[idx * 3 + 2] = connection.source_node->get_name(); + idx++; + } + + r_value = conns; + return true; + } + + return false; +} + +bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) { + String prop_name = p_name; + if (prop_name.begins_with("nodes/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); + + if (what == "node") { + Ref anode = p_value; + if (anode.is_valid()) { + anode->set_name(node_name); + add_node(anode); + } + return true; + } + + if (what == "graph_offset") { + int node_index = find_node_index_by_name(node_name); + if (node_index > -1) { + tree_graph.nodes[node_index]->position = p_value; + } + return true; + } + } else if (prop_name == "node_connections") { + Array conns = p_value; + ERR_FAIL_COND_V(conns.size() % 3 != 0, false); + + for (int i = 0; i < conns.size(); i += 3) { + int target_node_index = find_node_index_by_name(conns[i]); + int target_node_port_index = conns[i + 1]; + int source_node_index = find_node_index_by_name(conns[i + 2]); + + Ref target_node = tree_graph.nodes[target_node_index]; + Vector target_input_names = target_node->get_input_names(); + + add_connection(tree_graph.nodes[source_node_index], target_node, target_input_names[target_node_port_index]); + } + return true; + } + + return false; +} \ No newline at end of file diff --git a/blendalot_animation_node.h b/blendalot_animation_node.h index 9c97a82..04d5310 100644 --- a/blendalot_animation_node.h +++ b/blendalot_animation_node.h @@ -105,7 +105,8 @@ struct AnimationData { buffer = other.buffer; } AnimationData(AnimationData &&other) noexcept : - value_buffer_offset(std::exchange(other.value_buffer_offset, AHashMap())), + // We skip copying the offset as that should be identical for all nodes within a BLTAnimationGraph. + // value_buffer_offset(std::exchange(other.value_buffer_offset, AHashMap())), buffer(std::exchange(other.buffer, LocalVector())) { } AnimationData &operator=(const AnimationData &other) { @@ -301,9 +302,10 @@ public: } virtual void evaluate(GraphEvaluationContext &context, const LocalVector &input_datas, AnimationData &output_data) { + GodotProfileZone("AnimationNode::evaluate"); // By default, use the AnimationData of the first input. if (input_datas.size() > 0) { - output_data = *input_datas[0]; + output_data = std::move(*input_datas[0]); } } @@ -365,6 +367,47 @@ protected: static void _bind_methods(); }; +class BLTAnimationNodeTimeScale : public BLTAnimationNode { + GDCLASS(BLTAnimationNodeTimeScale, BLTAnimationNode); + +public: + float scale = 1.0f; + +private: + Ref animation; + + Vector get_input_names() const override { + return { "Input" }; + } + + bool initialize(GraphEvaluationContext &context) override { + node_time_info = {}; + // TODO: it should not be necessary to force looping here. node_time_info.loop_mode = Animation::LOOP_LINEAR; + return true; + } + void calculate_sync_track(const Vector> &input_nodes) override { + if (node_time_info.is_synced) { + node_time_info.sync_track = input_nodes[0]->node_time_info.sync_track; + node_time_info.sync_track.duration *= scale; + } + } + void update_time(double p_time) override { + if (node_time_info.is_synced) { + return; + } + + BLTAnimationNode::update_time(p_time * scale); + } + +protected: + void _get_property_list(List *p_list) const; + bool _get(const StringName &p_name, Variant &r_value) const; + bool _set(const StringName &p_name, const Variant &p_value); + +private: + StringName scale_name = PNAME("scale"); +}; + class BLTAnimationNodeOutput : public BLTAnimationNode { GDCLASS(BLTAnimationNodeOutput, BLTAnimationNode); @@ -413,7 +456,8 @@ public: void calculate_sync_track(const Vector> &input_nodes) override { if (node_time_info.is_synced || sync) { - assert(input_nodes[0]->node_time_info.loop_mode == input_nodes[1]->node_time_info.loop_mode); + // TODO: figure out whether we need to enforce looping mode when syncing is enabled. + // assert(input_nodes[0]->node_time_info.loop_mode == input_nodes[1]->node_time_info.loop_mode); node_time_info.sync_track = SyncTrack::blend(blend_weight, input_nodes[0]->node_time_info.sync_track, input_nodes[1]->node_time_info.sync_track); } } diff --git a/register_types.cpp b/register_types.cpp index e33bf83..730d3c2 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -12,6 +12,7 @@ void initialize_blendalot_animgraph_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); }