diff --git a/core/object/object.h b/core/object/object.h
index c6446a2a0209..d8909ec6e320 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -585,6 +585,7 @@ class Object {
// Store on each object a bitfield to quickly test whether it is derived from some "key" classes
// that are commonly tested in performance sensitive code.
// Ensure unsigned to bitpack.
+ // If you want to add more classes, please make sure to edit the Object::_ancestry field.
enum class AncestralClass : unsigned int {
REF_COUNTED = 1 << 0,
NODE = 1 << 1,
@@ -603,6 +604,8 @@ class Object {
COLLISION_OBJECT_3D = 1 << 12,
PHYSICS_BODY_3D = 1 << 13,
MESH_INSTANCE_3D = 1 << 14,
+ // Used a lot in AnimationMixer, SkeletonModifier3D and BoneAttachment3D.
+ SKELETON_3D = 1 << 15,
};
static constexpr AncestralClass static_ancestral_class = (AncestralClass)0;
@@ -653,7 +656,7 @@ class Object {
void _initialize();
void _postinitialize();
- uint32_t _ancestry : 15;
+ uint32_t _ancestry : 16;
bool _block_signals : 1;
bool _can_translate : 1;
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index 44ba38236a01..0bd062874e98 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -117,6 +117,12 @@ bool NodePath::operator==(const NodePath &p_path) const {
return false;
}
+ if (data->hash_cache_valid && p_path.data->hash_cache_valid) {
+ if (data->hash_cache != p_path.data->hash_cache) {
+ return false;
+ }
+ }
+
if (data->absolute != p_path.data->absolute) {
return false;
}
diff --git a/core/templates/a_hash_map.h b/core/templates/a_hash_map.h
index 079cf1b13020..1eb217f81bec 100644
--- a/core/templates/a_hash_map.h
+++ b/core/templates/a_hash_map.h
@@ -339,6 +339,22 @@ class AHashMap {
return nullptr;
}
+ TValue &get_value_ref_or_add_default(const TKey &p_key, bool &r_was_added) {
+ uint32_t element_idx = 0;
+ uint32_t meta_idx = 0;
+ uint32_t hash = _hash(p_key);
+ bool exists = _lookup_idx_with_hash(p_key, element_idx, meta_idx, hash);
+
+ if (exists) {
+ r_was_added = false;
+ return _elements[element_idx].value;
+ } else {
+ r_was_added = true;
+ element_idx = _insert_element(p_key, TValue(), hash);
+ return _elements[element_idx].value;
+ }
+ }
+
bool has(const TKey &p_key) const {
uint32_t _idx = 0;
uint32_t meta_idx = 0;
@@ -592,17 +608,8 @@ class AHashMap {
}
TValue &operator[](const TKey &p_key) {
- uint32_t element_idx = 0;
- uint32_t meta_idx = 0;
- uint32_t hash = _hash(p_key);
- bool exists = _lookup_idx_with_hash(p_key, element_idx, meta_idx, hash);
-
- if (exists) {
- return _elements[element_idx].value;
- } else {
- element_idx = _insert_element(p_key, TValue(), hash);
- return _elements[element_idx].value;
- }
+ bool dummy;
+ return get_value_ref_or_add_default(p_key, dummy);
}
/* Insert */
diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml
index 5a2642423296..a7e7457bd261 100644
--- a/doc/classes/AnimationNode.xml
+++ b/doc/classes/AnimationNode.xml
@@ -225,6 +225,12 @@
Emitted by nodes that inherit from this class and that have an internal tree when one of their animation node names changes. The animation nodes that emit this signal are [AnimationNodeBlendSpace1D], [AnimationNodeBlendSpace2D], [AnimationNodeStateMachine], and [AnimationNodeBlendTree].
+
+
+
+ Emitted by [AnimationNodeAnimation] when its [member AnimationNodeAnimation.animation] resource is changed, or by [AnimationNodeBlendTree] when its connections change.
+
+
Emitted by nodes that inherit from this class and that have an internal tree when one of their animation nodes changes. The animation nodes that emit this signal are [AnimationNodeBlendSpace1D], [AnimationNodeBlendSpace2D], [AnimationNodeStateMachine], [AnimationNodeBlendTree] and [AnimationNodeTransition].
diff --git a/editor/animation/animation_blend_space_1d_editor.cpp b/editor/animation/animation_blend_space_1d_editor.cpp
index 382d00b39347..96a7a6832f77 100644
--- a/editor/animation/animation_blend_space_1d_editor.cpp
+++ b/editor/animation/animation_blend_space_1d_editor.cpp
@@ -43,6 +43,7 @@
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/separator.h"
#include "scene/gui/spin_box.h"
@@ -77,15 +78,12 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref classes;
- ClassDB::get_inheriters_from_class("AnimationRootNode", classes);
+ ClassDB::get_inheriters_from_class(SNAME("AnimationRootNode"), classes);
classes.sort_custom();
menu->add_submenu_node_item(TTR("Add Animation"), animations_menu);
- List names;
- tree->get_animation_list(&names);
-
- for (const StringName &E : names) {
+ for (const StringName &E : tree->get_sorted_animation_list()) {
animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E);
animations_to_add.push_back(E);
}
@@ -588,7 +586,7 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
error_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
- error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+ error_label->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
tool_blend->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));
tool_select->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
@@ -608,18 +606,7 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
return;
}
- String error;
-
- error = tree->get_editor_error_message();
-
- if (error != error_label->get_text()) {
- error_label->set_text(error);
- if (!error.is_empty()) {
- error_panel->show();
- } else {
- error_panel->hide();
- }
- }
+ update_error_message(tree, error_panel, error_label);
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -809,8 +796,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
error_panel = memnew(PanelContainer);
add_child(error_panel);
- error_label = memnew(Label);
- error_label->set_focus_mode(FOCUS_ACCESSIBILITY);
+ error_label = create_error_label_node();
error_panel->add_child(error_label);
error_panel->hide();
diff --git a/editor/animation/animation_blend_space_1d_editor.h b/editor/animation/animation_blend_space_1d_editor.h
index 44e357195cd9..b2ec2f6517dd 100644
--- a/editor/animation/animation_blend_space_1d_editor.h
+++ b/editor/animation/animation_blend_space_1d_editor.h
@@ -77,7 +77,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
Control *blend_space_draw = nullptr;
PanelContainer *error_panel = nullptr;
- Label *error_label = nullptr;
+ RichTextLabel *error_label = nullptr;
bool updating = false;
@@ -94,7 +94,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
PopupMenu *menu = nullptr;
PopupMenu *animations_menu = nullptr;
- Vector animations_to_add;
+ Vector animations_to_add;
float add_point_pos = 0.0f;
Vector points;
diff --git a/editor/animation/animation_blend_space_2d_editor.cpp b/editor/animation/animation_blend_space_2d_editor.cpp
index 807188c1dc09..a89eec45c0fb 100644
--- a/editor/animation/animation_blend_space_2d_editor.cpp
+++ b/editor/animation/animation_blend_space_2d_editor.cpp
@@ -49,6 +49,7 @@
#include "scene/gui/option_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/panel_container.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/separator.h"
#include "scene/gui/spin_box.h"
#include "scene/main/window.h"
@@ -126,9 +127,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refadd_submenu_node_item(TTR("Add Animation"), animations_menu);
- List names;
- tree->get_animation_list(&names);
- for (const StringName &E : names) {
+ for (const StringName &E : tree->get_sorted_animation_list()) {
animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E);
animations_to_add.push_back(E);
}
@@ -813,7 +812,7 @@ void AnimationNodeBlendSpace2DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
error_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
- error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+ error_label->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
tool_blend->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));
tool_select->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
@@ -835,22 +834,7 @@ void AnimationNodeBlendSpace2DEditor::_notification(int p_what) {
return;
}
- String error;
-
- error = tree->get_editor_error_message();
-
- if (error.is_empty() && blend_space->get_triangle_count() == 0) {
- error = TTR("No triangles exist, so no blending can take place.");
- }
-
- if (error != error_label->get_text()) {
- error_label->set_text(error);
- if (!error.is_empty()) {
- error_panel->show();
- } else {
- error_panel->hide();
- }
- }
+ update_error_message(tree, error_panel, error_label);
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -1096,8 +1080,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
error_panel = memnew(PanelContainer);
add_child(error_panel);
- error_label = memnew(Label);
- error_label->set_focus_mode(FOCUS_ACCESSIBILITY);
+ error_label = create_error_label_node();
error_panel->add_child(error_label);
error_panel->hide();
diff --git a/editor/animation/animation_blend_space_2d_editor.h b/editor/animation/animation_blend_space_2d_editor.h
index 684e7c4524bf..f5328a406083 100644
--- a/editor/animation/animation_blend_space_2d_editor.h
+++ b/editor/animation/animation_blend_space_2d_editor.h
@@ -82,7 +82,7 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin {
Control *blend_space_draw = nullptr;
PanelContainer *error_panel = nullptr;
- Label *error_label = nullptr;
+ RichTextLabel *error_label = nullptr;
bool updating;
@@ -99,7 +99,7 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin {
PopupMenu *menu = nullptr;
PopupMenu *animations_menu = nullptr;
- Vector animations_to_add;
+ Vector animations_to_add;
Vector2 add_point_pos;
Vector points;
diff --git a/editor/animation/animation_blend_tree_editor_plugin.cpp b/editor/animation/animation_blend_tree_editor_plugin.cpp
index c2940b3f2484..2aa8214e9ed0 100644
--- a/editor/animation/animation_blend_tree_editor_plugin.cpp
+++ b/editor/animation/animation_blend_tree_editor_plugin.cpp
@@ -47,6 +47,7 @@
#include "scene/gui/menu_button.h"
#include "scene/gui/option_button.h"
#include "scene/gui/progress_bar.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/separator.h"
#include "scene/gui/view_panner.h"
#include "scene/main/window.h"
@@ -119,6 +120,18 @@ void AnimationNodeBlendTreeEditor::update_graph() {
if (updating || blend_tree.is_null()) {
return;
}
+ if (graph_update_queued) {
+ return;
+ }
+ graph_update_queued = true;
+ // Defer to idle time, so multiple requests can be merged.
+ callable_mp(this, &AnimationNodeBlendTreeEditor::update_graph_immediately).call_deferred();
+}
+
+void AnimationNodeBlendTreeEditor::update_graph_immediately() {
+ if (updating || blend_tree.is_null()) {
+ return;
+ }
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
if (!tree) {
@@ -128,7 +141,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
visible_properties.clear();
// Store selected nodes before clearing the graph.
- List selected_nodes;
+ LocalVector selected_nodes;
for (int i = 0; i < graph->get_child_count(); i++) {
GraphNode *gn = Object::cast_to(graph->get_child(i));
if (gn && gn->is_selected()) {
@@ -164,6 +177,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
node->set_title(agnode->get_caption());
node->set_name(E);
+ node->set_meta(animation_node_name_meta, E);
int base = 0;
if (E != SceneStringName(output)) {
@@ -174,8 +188,8 @@ void AnimationNodeBlendTreeEditor::update_graph() {
name->set_custom_minimum_size(Vector2(100, 0) * EDSCALE);
node->add_child(name);
node->set_slot(0, false, 0, Color(), true, read_only ? -1 : 0, get_theme_color(SceneStringName(font_color), SNAME("Label")));
- name->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode), CONNECT_DEFERRED);
- name->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out).bind(agnode), CONNECT_DEFERRED);
+ name->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode, E), CONNECT_DEFERRED);
+ name->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out).bind(agnode, E), CONNECT_DEFERRED);
name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed), CONNECT_DEFERRED);
base = 1;
agnode->set_deletable(true);
@@ -198,7 +212,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
node->set_slot(base + i, true, read_only ? -1 : 0, get_theme_color(SceneStringName(font_color), SNAME("Label")), false, 0, Color());
}
- List pinfo;
+ LocalVector pinfo;
agnode->get_parameter_list(&pinfo);
for (const PropertyInfo &F : pinfo) {
if (!(F.usage & PROPERTY_USAGE_EDITOR)) {
@@ -218,7 +232,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
}
prop->set_name_split_ratio(ratio);
prop->update_property();
- prop->connect("property_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_property_changed));
+ prop->connect(SNAME("property_changed"), callable_mp(this, &AnimationNodeBlendTreeEditor::_property_changed));
if (F.hint == PROPERTY_HINT_RESOURCE_TYPE) {
// Give the resource editor some more space to make the inside readable.
@@ -232,7 +246,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
}
}
- node->connect("dragged", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_dragged).bind(E));
+ node->connect(SNAME("dragged"), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_dragged).bind(E));
if (AnimationTreeEditor::get_singleton()->can_edit(agnode)) {
node->add_child(memnew(HSeparator));
@@ -272,10 +286,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
ProgressBar *pb = memnew(ProgressBar);
- List anims;
- tree->get_animation_list(&anims);
-
- for (const StringName &F : anims) {
+ for (const StringName &F : tree->get_sorted_animation_list()) {
mb->get_popup()->add_item(F);
options.push_back(F);
}
@@ -285,25 +296,25 @@ void AnimationNodeBlendTreeEditor::update_graph() {
animations[E] = pb;
node->add_child(pb);
- mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected).bind(options, E), CONNECT_DEFERRED);
+ mb->get_popup()->connect(SNAME("index_pressed"), callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected).bind(options, E), CONNECT_DEFERRED);
}
- Ref sb_panel = node->get_theme_stylebox(SceneStringName(panel), "GraphNode")->duplicate();
+ Ref sb_panel = node->get_theme_stylebox(SceneStringName(panel), SNAME("GraphNode"))->duplicate();
if (sb_panel.is_valid()) {
sb_panel->set_content_margin(SIDE_TOP, 12 * EDSCALE);
sb_panel->set_content_margin(SIDE_BOTTOM, 12 * EDSCALE);
node->add_theme_style_override(SceneStringName(panel), sb_panel);
}
- node->add_theme_constant_override("separation", 4 * EDSCALE);
+ node->add_theme_constant_override(SNAME("separation"), 4 * EDSCALE);
}
- List node_connections;
+ LocalVector node_connections;
blend_tree->get_node_connections(&node_connections);
for (const AnimationNodeBlendTree::NodeConnection &E : node_connections) {
- StringName from = E.output_node;
- StringName to = E.input_node;
+ const StringName &from = E.output_node;
+ const StringName &to = E.input_node;
int to_idx = E.input_index;
graph->connect_node(from, 0, to, to_idx);
@@ -324,6 +335,48 @@ void AnimationNodeBlendTreeEditor::update_graph() {
}
}
}
+
+ graph_update_queued = false;
+}
+
+void AnimationNodeBlendTreeEditor::pan_to_node(const StringName &p_node_name, int p_input_index) {
+ GraphNode *target_node = nullptr;
+ for (int i = 0; i < graph->get_child_count(); i++) {
+ if (GraphNode *gn = Object::cast_to(graph->get_child(i)); gn && gn->has_meta(animation_node_name_meta)) {
+ if (gn->get_meta(animation_node_name_meta).operator StringName() == p_node_name) {
+ target_node = gn;
+ break;
+ }
+ }
+ }
+ ERR_FAIL_NULL(target_node);
+
+ Vector2 position = target_node->get_position_offset();
+ if (p_input_index != -1) {
+ const Vector2 slot_pos = target_node->get_input_port_position(p_input_index);
+ position += slot_pos;
+ } else {
+ position += target_node->get_size() * 0.5f; // Center of the node.
+ }
+ const Vector2 target = position * graph->get_zoom() - graph->get_size() * 0.5f;
+
+ if (pan_to_tween.is_valid()) {
+ pan_to_tween->kill();
+ }
+ pan_to_tween = Ref(graph->create_tween());
+
+ bool is_close_enough = graph->get_scroll_offset().distance_to(target) < 10.0f;
+ if (!is_close_enough) {
+ pan_to_tween
+ ->set_trans(Tween::TRANS_CUBIC)
+ ->set_ease(Tween::EASE_OUT)
+ ->tween_method(callable_mp(graph, &GraphEdit::set_scroll_offset), graph->get_scroll_offset(), target, 0.25);
+ }
+
+ pan_to_tween->tween_property(target_node, NodePath("modulate"), Color(1, 1, 1, 1) * 10, 0.05);
+ pan_to_tween->set_trans(Tween::TRANS_LINEAR)
+ ->set_ease(Tween::EASE_OUT)
+ ->tween_property(target_node, NodePath("modulate"), Color(1, 1, 1, 1), 0.3);
}
void AnimationNodeBlendTreeEditor::_file_opened(const String &p_file) {
@@ -547,7 +600,7 @@ void AnimationNodeBlendTreeEditor::_delete_node_request(const String &p_which) {
undo_redo->add_do_method(blend_tree.ptr(), "remove_node", p_which);
undo_redo->add_undo_method(blend_tree.ptr(), "add_node", p_which, blend_tree->get_node(p_which), blend_tree.ptr()->get_node_position(p_which));
- List conns;
+ LocalVector conns;
blend_tree->get_node_connections(&conns);
for (const AnimationNodeBlendTree::NodeConnection &E : conns) {
@@ -795,16 +848,13 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano
updating = true;
- HashSet paths;
- HashMap> types;
+ HashSet paths;
+ HashMap> types;
{
- List animation_list;
- tree->get_animation_list(&animation_list);
-
- for (const StringName &E : animation_list) {
+ for (const StringName &E : tree->get_sorted_animation_list()) {
Ref anim = tree->get_animation(E);
for (int i = 0; i < anim->get_track_count(); i++) {
- String track_path = String(anim->track_get_path(i));
+ NodePath track_path = anim->track_get_path(i);
paths.insert(track_path);
String track_type_name;
@@ -835,8 +885,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano
HashMap parenthood;
- for (const String &E : paths) {
- NodePath path = E;
+ for (const NodePath &path : paths) {
TreeItem *ti = nullptr;
String accum;
for (int i = 0; i < path.get_name_count(); i++) {
@@ -856,8 +905,8 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano
ti->set_selectable(0, false);
ti->set_editable(0, false);
- if (base->has_node(accum)) {
- Node *node = base->get_node(accum);
+ Node *node = base->get_node_or_null(accum);
+ if (node) {
ti->set_icon(0, EditorNode::get_singleton()->get_object_icon(node));
}
@@ -866,10 +915,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano
}
}
- Node *node = nullptr;
- if (base->has_node(accum)) {
- node = base->get_node(accum);
- }
+ Node *node = base->get_node_or_null(accum);
if (!node) {
continue; //no node, can't edit
}
@@ -994,7 +1040,7 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
error_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
- error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+ error_label->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
if (is_visible_in_tree()) {
update_graph();
@@ -1007,20 +1053,13 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
return; // Node has been changed.
}
- String error;
-
- error = tree->get_editor_error_message();
-
- if (error != error_label->get_text()) {
- error_label->set_text(error);
- if (!error.is_empty()) {
- error_panel->show();
- } else {
- error_panel->hide();
- }
+ if (graph_update_queued) {
+ return;
}
- List conns;
+ update_error_message(tree, error_panel, error_label);
+
+ LocalVector conns;
blend_tree->get_node_connections(&conns);
for (const AnimationNodeBlendTree::NodeConnection &E : conns) {
float activity = 0;
@@ -1094,7 +1133,7 @@ void AnimationNodeBlendTreeEditor::_node_changed(const StringName &p_node_name)
update_graph();
}
-void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref p_node) {
+void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref p_node, const StringName &p_name) {
if (blend_tree.is_null()) {
return;
}
@@ -1104,13 +1143,14 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Refget_node_name(p_node);
+ String prev_name = p_name;
ERR_FAIL_COND(prev_name.is_empty());
GraphNode *gn = Object::cast_to(graph->get_node(prev_name));
ERR_FAIL_NULL(gn);
const String &new_name = p_text;
+ ERR_FAIL_COND_MSG(String(p_name).validate_node_name() != p_name, "Invalid AnimationNode name, ignoring rename.");
ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/'));
if (new_name == prev_name) {
@@ -1137,6 +1177,7 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Refcommit_action();
updating = false;
gn->set_name(new_name);
+ gn->set_meta(animation_node_name_meta, new_name);
gn->set_size(gn->get_minimum_size());
//change editors accordingly
@@ -1151,7 +1192,7 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Refclear_connections();
- List node_connections;
+ LocalVector node_connections;
blend_tree->get_node_connections(&node_connections);
for (const AnimationNodeBlendTree::NodeConnection &E : node_connections) {
@@ -1175,11 +1216,11 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref p_node) {
+void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Ref p_node, const StringName &p_name) {
if (current_node_rename_text.is_empty()) {
return; // The text_submitted signal triggered the graph update and freed the LineEdit.
}
- _node_renamed(current_node_rename_text, p_node);
+ _node_renamed(current_node_rename_text, p_node, p_name);
}
void AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed(const String &p_text) {
@@ -1269,10 +1310,8 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
error_panel = memnew(PanelContainer);
add_child(error_panel);
- error_label = memnew(Label);
- error_label->set_focus_mode(FOCUS_ACCESSIBILITY);
+ error_label = create_error_label_node();
error_panel->add_child(error_label);
- error_label->set_text("eh");
filter_dialog = memnew(AcceptDialog);
add_child(filter_dialog);
diff --git a/editor/animation/animation_blend_tree_editor_plugin.h b/editor/animation/animation_blend_tree_editor_plugin.h
index a74e0eb50cd2..8876c581e899 100644
--- a/editor/animation/animation_blend_tree_editor_plugin.h
+++ b/editor/animation/animation_blend_tree_editor_plugin.h
@@ -47,6 +47,7 @@ class EditorFileDialog;
class EditorProperty;
class MenuButton;
class PanelContainer;
+class RichTextLabel;
class EditorInspectorPluginAnimationNodeAnimation;
class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
@@ -62,7 +63,7 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
bool use_position_from_popup_menu;
PanelContainer *error_panel = nullptr;
- Label *error_label = nullptr;
+ RichTextLabel *error_label = nullptr;
AcceptDialog *filter_dialog = nullptr;
Tree *filters = nullptr;
@@ -95,15 +96,17 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
void _add_node(int p_idx);
void _update_options_menu(bool p_has_input_ports = false);
+ StringName animation_node_name_meta = StringName("_animation_node_name");
static AnimationNodeBlendTreeEditor *singleton;
void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, const StringName &p_which);
- void _node_renamed(const String &p_text, Ref p_node);
- void _node_renamed_focus_out(Ref p_node);
+ void _node_renamed(const String &p_text, Ref p_node, const StringName &p_name);
+ void _node_renamed_focus_out(Ref p_node, const StringName &p_name);
void _node_rename_lineedit_changed(const String &p_text);
void _node_changed(const StringName &p_node_name);
String current_node_rename_text;
+ bool graph_update_queued;
bool updating;
void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index);
@@ -150,6 +153,7 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
};
Ref animation_node_inspector_plugin;
+ Ref pan_to_tween;
protected:
void _notification(int p_what);
@@ -167,7 +171,9 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
virtual void edit(const Ref &p_node) override;
void update_graph();
+ void update_graph_immediately();
+ void pan_to_node(const StringName &p_node_name, int p_input_index = -1);
AnimationNodeBlendTreeEditor();
};
diff --git a/editor/animation/animation_library_editor.cpp b/editor/animation/animation_library_editor.cpp
index b36723b77da0..f96cdf84a0c5 100644
--- a/editor/animation/animation_library_editor.cpp
+++ b/editor/animation/animation_library_editor.cpp
@@ -374,7 +374,7 @@ void AnimationLibraryEditor::_load_files(const PackedStringArray &p_paths) {
continue;
}
- List libs;
+ LocalVector libs;
mixer->get_animation_library_list(&libs);
bool is_already_added = false;
for (const StringName &K : libs) {
@@ -684,7 +684,7 @@ void AnimationLibraryEditor::update_tree() {
Color ss_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
TreeItem *root = tree->create_item();
- List libs;
+ LocalVector libs;
Vector collapsed_lib_ids = _load_mixer_libs_folding();
mixer->get_animation_library_list(&libs);
@@ -951,7 +951,7 @@ String AnimationLibraryEditor::_get_mixer_signature() const {
String signature = String();
// Get all libraries sorted for consistency
- List libs;
+ LocalVector libs;
mixer->get_animation_library_list(&libs);
libs.sort_custom();
diff --git a/editor/animation/animation_player_editor_plugin.cpp b/editor/animation/animation_player_editor_plugin.cpp
index ff9dd6738a9b..32fabe9ca193 100644
--- a/editor/animation/animation_player_editor_plugin.cpp
+++ b/editor/animation/animation_player_editor_plugin.cpp
@@ -811,10 +811,8 @@ void AnimationPlayerEditor::_update_animation_blend() {
blend_editor.tree->clear();
- String current = animation->get_item_text(animation->get_selected());
+ StringName current = animation->get_item_text(animation->get_selected());
- List anims;
- player->get_animation_list(&anims);
TreeItem *root = blend_editor.tree->create_item();
updating_blends = true;
@@ -823,7 +821,7 @@ void AnimationPlayerEditor::_update_animation_blend() {
blend_editor.next->clear();
blend_editor.next->add_item("", i);
- for (const StringName &to : anims) {
+ for (const StringName &to : player->get_sorted_animation_list()) {
TreeItem *blend = blend_editor.tree->create_item(root);
blend->set_editable(0, false);
blend->set_editable(1, true);
@@ -1020,7 +1018,7 @@ void AnimationPlayerEditor::_update_player() {
return;
}
- List libraries;
+ LocalVector libraries;
player->get_animation_library_list(&libraries);
int active_idx = -1;
@@ -1144,7 +1142,7 @@ void AnimationPlayerEditor::_update_name_dialog_library_dropdown() {
}
}
- List libraries;
+ LocalVector libraries;
player->get_animation_library_list(&libraries);
library->clear();
@@ -1478,7 +1476,7 @@ void AnimationPlayerEditor::_current_animation_changed(const StringName &p_name)
}
// Determine the read-only status of the animation's library and the libraries as a whole.
- List libraries;
+ LocalVector libraries;
player->get_animation_library_list(&libraries);
bool current_animation_library_is_readonly = false;
diff --git a/editor/animation/animation_state_machine_editor.cpp b/editor/animation/animation_state_machine_editor.cpp
index 3aa55f02ba8a..0f7e2e497b42 100644
--- a/editor/animation/animation_state_machine_editor.cpp
+++ b/editor/animation/animation_state_machine_editor.cpp
@@ -42,6 +42,7 @@
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/separator.h"
#include "scene/main/viewport.h"
#include "scene/main/window.h"
@@ -821,8 +822,7 @@ void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) {
animations_menu->clear();
animations_to_add.clear();
- List animation_names;
- tree->get_animation_list(&animation_names);
+ LocalVector animation_names = tree->get_sorted_animation_list();
menu->add_submenu_node_item(TTR("Add Animation"), animations_menu);
if (animation_names.is_empty()) {
menu->set_item_disabled(menu->get_item_idx_from_text(TTR("Add Animation")), true);
@@ -982,7 +982,7 @@ void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) {
anim->set_animation(animations_to_add[p_index]);
- String base_name = animations_to_add[p_index].validate_node_name();
+ String base_name = String(animations_to_add[p_index]).validate_node_name();
int base = 1;
String name = base_name;
while (state_machine->has_node(name)) {
@@ -1646,7 +1646,7 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
error_panel->add_theme_style_override(SceneStringName(panel), theme_cache.error_panel_style);
- error_label->add_theme_color_override(SceneStringName(font_color), theme_cache.error_color);
+ error_label->add_theme_color_override(SNAME("default_color"), theme_cache.error_color);
tool_select->set_button_icon(theme_cache.tool_icon_select);
tool_create->set_button_icon(theme_cache.tool_icon_create);
@@ -1677,24 +1677,14 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
Ref playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
if (error_time > 0) {
- error = error_text;
error_time -= get_process_delta_time();
- } else {
- error = tree->get_editor_error_message();
}
- if (error.is_empty() && playback.is_null()) {
+ if (playback.is_null()) {
error = vformat(TTR("No playback resource set at path: %s."), AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
}
- if (error != error_label->get_text()) {
- error_label->set_text(error);
- if (!error.is_empty()) {
- error_panel->show();
- } else {
- error_panel->hide();
- }
- }
+ update_error_message(tree, error_panel, error_label, &error);
for (int i = 0; i < transition_lines.size(); i++) {
int tidx = -1;
@@ -2149,8 +2139,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
error_panel = memnew(PanelContainer);
add_child(error_panel);
- error_label = memnew(Label);
- error_label->set_focus_mode(FOCUS_ACCESSIBILITY);
+ error_label = create_error_label_node();
error_panel->add_child(error_label);
error_panel->hide();
diff --git a/editor/animation/animation_state_machine_editor.h b/editor/animation/animation_state_machine_editor.h
index d2008b40d826..48e62de8afeb 100644
--- a/editor/animation/animation_state_machine_editor.h
+++ b/editor/animation/animation_state_machine_editor.h
@@ -75,7 +75,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
Control *state_machine_play_pos = nullptr;
PanelContainer *error_panel = nullptr;
- Label *error_label = nullptr;
+ RichTextLabel *error_label = nullptr;
struct ThemeCache {
Ref panel_style;
@@ -144,7 +144,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
PopupMenu *state_machine_menu = nullptr;
PopupMenu *end_menu = nullptr;
PopupMenu *animations_menu = nullptr;
- Vector animations_to_add;
+ Vector animations_to_add;
Vector nodes_to_connect;
Vector2 add_node_pos;
@@ -280,7 +280,6 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
float fading_pos = 0.0f;
float error_time = 0.0f;
- String error_text;
EditorFileDialog *open_file = nullptr;
Ref file_loaded;
diff --git a/editor/animation/animation_track_editor.cpp b/editor/animation/animation_track_editor.cpp
index 142d5cf233a9..3229f4d4ecc5 100644
--- a/editor/animation/animation_track_editor.cpp
+++ b/editor/animation/animation_track_editor.cpp
@@ -646,9 +646,7 @@ void AnimationTrackKeyEdit::_get_property_list(List *p_list) const
if (root_path) {
AnimationPlayer *ap = Object::cast_to(root_path->get_node_or_null(animation->track_get_path(track)));
if (ap) {
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &E : anims) {
+ for (const StringName &E : ap->get_sorted_animation_list()) {
if (!animations.is_empty()) {
animations += ",";
}
@@ -1252,9 +1250,7 @@ void AnimationMultiTrackKeyEdit::_get_property_list(List *p_list)
if (root_path) {
AnimationPlayer *ap = Object::cast_to(root_path->get_node_or_null(animation->track_get_path(first_track)));
if (ap) {
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &anim : anims) {
+ for (const StringName &anim : ap->get_sorted_animation_list()) {
if (!animations.is_empty()) {
animations += ",";
}
@@ -4245,10 +4241,8 @@ void AnimationTrackEditor::_animation_track_remove_request(int p_track, Reftrack_get_path(i) == p_from_animation->track_get_path(p_track)) {
// Check if the reset track isn't used by other animations.
bool used = false;
- List animation_list;
- player->get_animation_list(&animation_list);
- for (const StringName &anim_name : animation_list) {
+ for (const StringName &anim_name : player->get_sorted_animation_list()) {
Ref anim = player->get_animation(anim_name);
if (anim == p_from_animation || anim == reset) {
continue;
@@ -7626,9 +7620,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
} break;
case EDIT_CLEAN_UP_ANIMATION_CONFIRM: {
if (cleanup_all->is_pressed()) {
- List names;
- AnimationPlayerEditor::get_singleton()->get_player()->get_animation_list(&names);
- for (const StringName &E : names) {
+ for (const StringName &E : AnimationPlayerEditor::get_singleton()->get_player()->get_sorted_animation_list()) {
_cleanup_animation(AnimationPlayerEditor::get_singleton()->get_player()->get_animation(E));
}
} else {
diff --git a/editor/animation/animation_tree_editor_plugin.cpp b/editor/animation/animation_tree_editor_plugin.cpp
index f4e9d7d46ada..93cce78680b9 100644
--- a/editor/animation/animation_tree_editor_plugin.cpp
+++ b/editor/animation/animation_tree_editor_plugin.cpp
@@ -34,15 +34,163 @@
#include "animation_blend_space_2d_editor.h"
#include "animation_blend_tree_editor_plugin.h"
#include "animation_state_machine_editor.h"
+#include "core/string/string_buffer.h"
#include "editor/editor_node.h"
+#include "editor/editor_string_names.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/settings/editor_command_palette.h"
#include "editor/themes/editor_scale.h"
#include "scene/animation/animation_blend_tree.h"
#include "scene/gui/button.h"
#include "scene/gui/margin_container.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/separator.h"
+#include "scene/gui/texture_rect.h"
+
+RichTextLabel *AnimationTreeNodeEditorPlugin::create_error_label_node() {
+ RichTextLabel *error_label = memnew(RichTextLabel);
+ error_label->set_focus_mode(FOCUS_ACCESSIBILITY);
+ error_label->set_fit_content(true);
+ error_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ error_label->set_meta_underline(true);
+ error_label->connect("meta_clicked", callable_mp(this, &AnimationTreeNodeEditorPlugin::_meta_clicked));
+ return error_label;
+}
+
+void AnimationTreeNodeEditorPlugin::_meta_clicked(Variant p_meta) {
+ if (p_meta.get_type() != Variant::STRING) {
+ return;
+ }
+ const String raw_info_str = p_meta;
+ const String full_path_str = raw_info_str.get_slice("::", 0);
+ ERR_FAIL_COND(!full_path_str.ends_with("/"));
+
+ int input_index = -1;
+ if (raw_info_str.contains("::")) {
+ const String input_index_str = raw_info_str.get_slice("::", 1);
+ ERR_FAIL_COND(!input_index_str.is_valid_int());
+ input_index = input_index_str.to_int();
+ }
+
+ AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
+ ERR_FAIL_NULL(tree);
+
+ // e.g. "parameters/blend_tree/node_name/" -> "parameters/blend_tree/"
+ String parent_path = full_path_str.rstrip("/").substr(0, full_path_str.rstrip("/").rfind_char('/') + 1);
+
+ Ref root_node = tree->get_animation_node_by_path(parent_path);
+ ERR_FAIL_COND(root_node.is_null());
+
+ String to_edit_root_path = String(parent_path);
+ to_edit_root_path = to_edit_root_path.replace_first(Animation::PARAMETERS_BASE_PATH, "");
+ to_edit_root_path = to_edit_root_path.trim_suffix("/");
+
+ Vector navigate_to;
+ if (!to_edit_root_path.is_empty()) {
+ // empty string still has 1 element when split.
+ navigate_to = to_edit_root_path.split("/");
+ }
+ if (AnimationTreeEditor::get_singleton()->get_edited_path() != navigate_to) {
+ AnimationTreeEditor::get_singleton()->edit_path(navigate_to);
+ }
+
+ // Special case for AnimationNodeBlendTree.
+ if (Ref blend_tree = root_node; blend_tree.is_valid()) {
+ String child_name = full_path_str;
+ child_name = child_name.replace_first(parent_path, "");
+ child_name = child_name.trim_suffix("/");
+ callable_mp(AnimationNodeBlendTreeEditor::get_singleton(), &AnimationNodeBlendTreeEditor::pan_to_node).call_deferred(child_name, input_index);
+ }
+}
+
+void AnimationTreeNodeEditorPlugin::update_error_message(const AnimationTree *p_tree, PanelContainer *p_error_panel, RichTextLabel *p_error_label, const String *p_other_errors) {
+ const String editor_error_message = p_tree->get_editor_error_message();
+ const AHashMap &invalid_instances = p_tree->get_invalid_instances();
+
+ if (editor_error_message.is_empty() && invalid_instances.is_empty() && (!p_other_errors || p_other_errors->is_empty())) {
+ last_error_key = String();
+ p_error_panel->hide();
+ return;
+ }
+
+ // Cheaper to do this, than rebuild rich text label every frame.
+ // Though it would be better, to only call this when the tree changes.
+ {
+ StringBuffer k;
+ k += editor_error_message;
+ if (p_other_errors) {
+ k += *p_other_errors;
+ }
+ for (const KeyValue &kv : invalid_instances) {
+ k += kv.key;
+ for (const String &reason : kv.value.errors) {
+ k += reason;
+ }
+ for (const AnimationNode::InvalidInstance::InputError &E : kv.value.input_errors) {
+ k += itos(E.index);
+ k += E.error;
+ }
+ }
+
+ String error_key = k.as_string();
+ if (error_key == last_error_key) {
+ return;
+ }
+ last_error_key = error_key;
+ }
+
+ const String point = String::utf8("• ");
+
+ p_error_label->clear();
+ p_error_label->append_text(editor_error_message);
+ if (p_other_errors) {
+ p_error_label->append_text(*p_other_errors);
+ }
+
+ bool first = true;
+ for (const KeyValue &kv : invalid_instances) {
+ if (!first) {
+ p_error_label->add_newline();
+ }
+ first = false;
+
+ Ref node = p_tree->get_animation_node_by_path(kv.key);
+ ERR_CONTINUE(node.is_null());
+
+ p_error_label->append_text(vformat(RTR("%s at "), node->get_class()));
+ p_error_label->push_meta(String(kv.key));
+ {
+ p_error_label->append_text(vformat(RTR("'%s'"), kv.key));
+ }
+ p_error_label->pop();
+ p_error_label->append_text(RTR(" has errors.\n"));
+
+ StringBuffer instance_error_builder;
+ for (const String &reason : kv.value.errors) {
+ instance_error_builder += point;
+ instance_error_builder += reason;
+ instance_error_builder += "\n";
+ }
+ p_error_label->append_text(instance_error_builder.as_string());
+
+ // Input errors.
+ String input_error_base = String(kv.key) + "::";
+ for (const AnimationNode::InvalidInstance::InputError &input_error : kv.value.input_errors) {
+ const String input_name = node->get_input_name(input_error.index);
+
+ p_error_label->append_text(point + input_error.error + " ");
+ p_error_label->push_meta(input_error_base + itos(input_error.index));
+ {
+ p_error_label->append_text(vformat(RTR("input %d '%s'."), input_error.index, input_name));
+ }
+ p_error_label->pop();
+ p_error_label->add_newline();
+ }
+ }
+
+ p_error_panel->show();
+}
void AnimationTreeEditor::edit(AnimationTree *p_tree) {
if (p_tree && !p_tree->is_connected("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) {
@@ -103,6 +251,14 @@ void AnimationTreeEditor::_update_path() {
b->connect(SceneStringName(pressed), callable_mp(this, &AnimationTreeEditor::_path_button_pressed).bind(-1));
path_hb->add_child(b);
for (int i = 0; i < button_path.size(); i++) {
+ // bread crumbs.
+ TextureRect *texture_rect = memnew(TextureRect);
+ texture_rect->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
+ texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
+ texture_rect->set_custom_minimum_size(Size2(16, 16) * EDSCALE);
+ texture_rect->set_texture(get_editor_theme_icon(SNAME("GuiTreeArrowRight")));
+ path_hb->add_child(texture_rect);
+
b = memnew(Button);
b->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
b->set_text(button_path[i]);
@@ -125,7 +281,7 @@ void AnimationTreeEditor::edit_path(const Vector &p_path) {
for (int i = 0; i < p_path.size(); i++) {
Ref child = node->get_child_by_name(p_path[i]);
- ERR_BREAK(child.is_null());
+ ERR_BREAK_MSG(child.is_null(), vformat("Cannot edit path '%s': node '%s' not found as child of '%s'.", String("/").join(p_path), p_path[i], node->get_class()));
node = child;
button_path.push_back(p_path[i]);
}
@@ -233,26 +389,19 @@ bool AnimationTreeEditor::can_edit(const Ref &p_node) const {
return false;
}
-Vector AnimationTreeEditor::get_animation_list() {
+LocalVector AnimationTreeEditor::get_animation_list() {
// This can be called off the main thread due to resource preview generation. Quit early in that case.
if (!singleton->tree || !Thread::is_main_thread() || !singleton->is_visible()) {
// When tree is empty, singleton not in the main thread.
- return Vector();
+ return LocalVector();
}
AnimationTree *tree = singleton->tree;
if (!tree) {
- return Vector();
- }
-
- List anims;
- tree->get_animation_list(&anims);
- Vector ret;
- for (const StringName &E : anims) {
- ret.push_back(E);
+ return LocalVector();
}
- return ret;
+ return tree->get_sorted_animation_list();
}
AnimationTreeEditor::AnimationTreeEditor() {
diff --git a/editor/animation/animation_tree_editor_plugin.h b/editor/animation/animation_tree_editor_plugin.h
index aa081846b7fb..6957ade5a5c9 100644
--- a/editor/animation/animation_tree_editor_plugin.h
+++ b/editor/animation/animation_tree_editor_plugin.h
@@ -37,6 +37,7 @@
class Button;
class EditorFileDialog;
class ScrollContainer;
+class RichTextLabel;
class AnimationTreeNodeEditorPlugin : public VBoxContainer {
GDCLASS(AnimationTreeNodeEditorPlugin, VBoxContainer);
@@ -44,6 +45,16 @@ class AnimationTreeNodeEditorPlugin : public VBoxContainer {
public:
virtual bool can_edit(const Ref &p_node) = 0;
virtual void edit(const Ref &p_node) = 0;
+
+protected:
+ RichTextLabel *create_error_label_node();
+
+ void _meta_clicked(Variant p_meta);
+
+ void update_error_message(const AnimationTree *p_tree, PanelContainer *p_error_panel, RichTextLabel *p_error_label, const String *p_other_errors = nullptr);
+
+private:
+ String last_error_key;
};
class AnimationTreeEditor : public VBoxContainer {
@@ -66,7 +77,7 @@ class AnimationTreeEditor : public VBoxContainer {
void _path_button_pressed(int p_path);
void _animation_list_changed();
- static Vector get_animation_list();
+ static LocalVector get_animation_list();
protected:
void _notification(int p_what);
diff --git a/editor/docks/scene_tree_dock.cpp b/editor/docks/scene_tree_dock.cpp
index 364de90438d7..b3c0e5302ad7 100644
--- a/editor/docks/scene_tree_dock.cpp
+++ b/editor/docks/scene_tree_dock.cpp
@@ -1918,10 +1918,7 @@ bool SceneTreeDock::_has_tracks_to_delete(Node *p_node, List &p_to_delet
if (ap) {
Node *root = ap->get_node(ap->get_root_node());
if (root && !p_to_delete.find(root)) {
- List anims;
- ap->get_animation_list(&anims);
-
- for (const StringName &E : anims) {
+ for (const StringName &E : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(E);
if (anim.is_null()) {
continue;
@@ -2173,15 +2170,13 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap
}
if (!points_to_other_animation_player) {
- List anims;
- mixer->get_animation_list(&anims);
Node *root = mixer->get_node(mixer->get_root_node());
if (root) {
HashMap::Iterator found_root_path = p_renames->find(root);
NodePath new_root_path = found_root_path ? found_root_path->value : root->get_path();
if (!new_root_path.is_empty()) { // No renaming if root node is deleted.
- for (const StringName &E : anims) {
+ for (const StringName &E : mixer->get_sorted_animation_list()) {
Ref anim = mixer->get_animation(E);
if (!r_rem_anims->has(anim)) {
r_rem_anims->insert(anim, HashSet());
diff --git a/editor/import/3d/post_import_plugin_skeleton_renamer.cpp b/editor/import/3d/post_import_plugin_skeleton_renamer.cpp
index 938c924f2922..b4fcc9ecaf01 100644
--- a/editor/import/3d/post_import_plugin_skeleton_renamer.cpp
+++ b/editor/import/3d/post_import_plugin_skeleton_renamer.cpp
@@ -95,9 +95,7 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p
TypedArray nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int len = anim->get_track_count();
for (int i = 0; i < len; i++) {
@@ -208,9 +206,7 @@ void PostImportPluginSkeletonRenamer::internal_process(InternalImportCategory p_
TypedArray nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
index 47deb0c1c2c3..c26c0cfca7e3 100644
--- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
@@ -167,9 +167,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
TypedArray nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
@@ -235,9 +233,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
TypedArray nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
if (String(name).contains_char('/')) {
continue; // Avoid animation library which may be created by importer dynamically.
}
@@ -398,9 +394,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
TypedArray nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
@@ -569,9 +563,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
String general_skeleton_pathname = UNIQUE_NODE_PREFIX + profile_skeleton->get_name();
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
@@ -705,9 +697,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
ERR_CONTINUE(!ap);
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
@@ -837,9 +827,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
TypedArray nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
diff --git a/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp b/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp
index c6befa13f1a9..6cc93e7ed73e 100644
--- a/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp
+++ b/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp
@@ -69,13 +69,11 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate
TypedArray nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to(nodes.pop_back());
- List anims;
- ap->get_animation_list(&anims);
Ref unmapped_al;
unmapped_al.instantiate();
- for (const StringName &name : anims) {
+ for (const StringName &name : ap->get_sorted_animation_list()) {
Ref anim = ap->get_animation(name);
int track_len = anim->get_track_count();
Vector remove_indices;
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index fc686d01e91c..297867d6f159 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -615,7 +615,7 @@ void _populate_scalable_nodes_collection(Node *p_node, ScalableNodeCollection &p
}
AnimationPlayer *animation_player = Object::cast_to(p_node);
if (animation_player) {
- List animation_list;
+ LocalVector animation_list;
animation_player->get_animation_list(&animation_list);
for (const StringName &E : animation_list) {
@@ -701,7 +701,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap anims;
+ LocalVector anims;
ap->get_animation_list(&anims);
for (const StringName &E : anims) {
Ref anim = ap->get_animation(E);
@@ -1078,7 +1078,7 @@ Node *ResourceImporterScene::_pre_fix_animations(Node *p_node, Node *p_root, con
if (Object::cast_to(p_node)) {
AnimationPlayer *ap = Object::cast_to(p_node);
- List anims;
+ LocalVector anims;
ap->get_animation_list(&anims);
AnimationImportTracks import_tracks_mode[TRACK_CHANNEL_MAX] = {
@@ -1126,7 +1126,7 @@ Node *ResourceImporterScene::_post_fix_animations(Node *p_node, Node *p_root, co
if (Object::cast_to(p_node)) {
AnimationPlayer *ap = Object::cast_to(p_node);
- List anims;
+ LocalVector anims;
ap->get_animation_list(&anims);
if (p_remove_immutable_tracks) {
@@ -1503,10 +1503,10 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
for (int node_i = 0; node_i < children.size(); node_i++) {
AnimationPlayer *anim_player = cast_to(children[node_i]);
ERR_CONTINUE(anim_player == nullptr);
- List anim_list;
+ LocalVector anim_list;
anim_player->get_animation_list(&anim_list);
if (anim_list.size() == 1) {
- selected_animation_name = anim_list.front()->get();
+ selected_animation_name = anim_list[0];
}
rest_animation = anim_player->get_animation(selected_animation_name);
if (rest_animation.is_valid()) {
@@ -1872,7 +1872,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
}
if (post_importer_plugins.size()) {
- List anims;
+ LocalVector anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
if (p_animation_data.has(name)) {
@@ -2097,7 +2097,7 @@ void ResourceImporterScene::_create_slices(AnimationPlayer *ap, Ref a
}
void ResourceImporterScene::_optimize_animations(AnimationPlayer *anim, float p_max_vel_error, float p_max_ang_error, int p_prc_error) {
- List anim_names;
+ LocalVector anim_names;
anim->get_animation_list(&anim_names);
for (const StringName &E : anim_names) {
Ref a = anim->get_animation(E);
@@ -2106,7 +2106,7 @@ void ResourceImporterScene::_optimize_animations(AnimationPlayer *anim, float p_
}
void ResourceImporterScene::_compress_animations(AnimationPlayer *anim, int p_page_size_kb) {
- List anim_names;
+ LocalVector anim_names;
anim->get_animation_list(&anim_names);
for (const StringName &E : anim_names) {
Ref a = anim->get_animation(E);
@@ -2817,7 +2817,7 @@ void ResourceImporterScene::_copy_meta(Object *p_src_object, Object *p_dst_objec
}
void ResourceImporterScene::_optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions) {
- List anims;
+ LocalVector anims;
p_player->get_animation_list(&anims);
Node *parent = p_player->get_parent();
ERR_FAIL_NULL(parent);
@@ -3332,10 +3332,10 @@ Error ResourceImporterScene::import(ResourceUID::ID p_source_id, const String &p
for (int i = 0; i < scene->get_child_count(); i++) {
AnimationPlayer *ap = Object::cast_to(scene->get_child(i));
if (ap) {
- List libs;
+ LocalVector libs;
ap->get_animation_library_list(&libs);
if (libs.size()) {
- library = ap->get_animation_library(libs.front()->get());
+ library = ap->get_animation_library(libs[0]);
break;
}
}
diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 250fceda777a..1b82e8438dfe 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -460,9 +460,7 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
AnimationPlayer *anim_node = Object::cast_to(p_node);
if (anim_node) {
Vector animation_list;
- List animations;
- anim_node->get_animation_list(&animations);
- for (const StringName &E : animations) {
+ for (const StringName &E : anim_node->get_sorted_animation_list()) {
_fill_animation(scene_tree, anim_node->get_animation(E), E, item);
animation_list.append(E);
}
diff --git a/editor/scene/3d/root_motion_editor_plugin.cpp b/editor/scene/3d/root_motion_editor_plugin.cpp
index c6f1f45ac3c5..61eaaa010ae7 100644
--- a/editor/scene/3d/root_motion_editor_plugin.cpp
+++ b/editor/scene/3d/root_motion_editor_plugin.cpp
@@ -66,10 +66,7 @@ void EditorPropertyRootMotion::_node_assign() {
HashSet paths;
{
- List animations;
- mixer->get_animation_list(&animations);
-
- for (const StringName &E : animations) {
+ for (const StringName &E : mixer->get_sorted_animation_list()) {
Ref anim = mixer->get_animation(E);
for (int i = 0; i < anim->get_track_count(); i++) {
String pathname = anim->track_get_path(i).get_concatenated_names();
diff --git a/misc/extension_api_validation/4.5-stable.expected b/misc/extension_api_validation/4.5-stable.expected
index fa1cda7f5b03..796719743c18 100644
--- a/misc/extension_api_validation/4.5-stable.expected
+++ b/misc/extension_api_validation/4.5-stable.expected
@@ -174,3 +174,11 @@ Validate extension JSON: Error: Field 'classes/GLTFState/methods/get_unique_name
Validate extension JSON: Error: Field 'classes/GLTFState/methods/get_use_named_skin_binds': is_const changed value in new API, from false to true.
GLTFState getters made const. Compatibility methods registered.
+
+
+GH-112308
+---------
+Validate extension JSON: Error: Field 'classes/Animation/methods/get_length/return_value': meta changed value in new API, from "float" to "double".
+Validate extension JSON: Error: Field 'classes/Animation/methods/set_length/arguments/0': meta changed value in new API, from "float" to "double".
+
+Return type and parameter changed from real_t to double to match internals. No compatibility methods registered because GDExtension FFI already uses double.
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index b2a568e585c7..28e4101c15cc 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -3563,9 +3563,7 @@ Error GLTFDocument::_serialize_animations(Ref p_state) {
}
for (int32_t player_i = 0; player_i < p_state->animation_players.size(); player_i++) {
AnimationPlayer *animation_player = p_state->animation_players[player_i];
- List animations;
- animation_player->get_animation_list(&animations);
- for (const StringName &animation_name : animations) {
+ for (const StringName &animation_name : animation_player->get_sorted_animation_list()) {
_convert_animation(p_state, animation_player, animation_name);
}
}
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index a66655ebd3a5..ac186ca57483 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -321,7 +321,6 @@ void Skeleton3D::_notification(int p_what) {
// Process modifiers.
- LocalVector bones_backup;
_find_modifiers();
if (!modifiers.is_empty()) {
bones_backup.resize(bones.size());
@@ -840,12 +839,16 @@ void Skeleton3D::clear_bones() {
// Posing api
void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) {
+ set_bone_pose_components(p_bone, p_pose.origin, p_pose.basis.get_rotation_quaternion(), p_pose.basis.get_scale());
+}
+
+void Skeleton3D::set_bone_pose_components(int p_bone, const Vector3 &p_position, const Quaternion &p_rotation, const Vector3 &p_scale) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones[p_bone].pose_position = p_pose.origin;
- bones[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion();
- bones[p_bone].pose_scale = p_pose.basis.get_scale();
+ bones[p_bone].pose_position = p_position;
+ bones[p_bone].pose_rotation = p_rotation;
+ bones[p_bone].pose_scale = p_scale;
bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
@@ -1416,6 +1419,7 @@ void Skeleton3D::physical_bones_remove_collision_exception(RID p_exception) {
#endif // _DISABLE_DEPRECATED
Skeleton3D::Skeleton3D() {
+ _define_ancestry(AncestralClass::SKELETON_3D);
}
Skeleton3D::~Skeleton3D() {
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 66ae8bd7c6fb..1ae318bc1e8e 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -76,6 +76,8 @@ class Skeleton3D : public Node3D {
#endif // _DISABLE_DEPRECATED && PHYSICS_3D_DISABLED
public:
+ static constexpr AncestralClass static_ancestral_class = AncestralClass::SKELETON_3D;
+
enum ModifierCallbackModeProcess {
MODIFIER_CALLBACK_MODE_PROCESS_PHYSICS,
MODIFIER_CALLBACK_MODE_PROCESS_IDLE,
@@ -160,6 +162,7 @@ class Skeleton3D : public Node3D {
HashSet skin_bindings;
void _skin_changed();
+ LocalVector bones_backup;
mutable LocalVector bones;
mutable bool process_order_dirty = false;
@@ -267,6 +270,7 @@ class Skeleton3D : public Node3D {
Quaternion get_bone_pose_rotation(int p_bone) const;
Vector3 get_bone_pose_scale(int p_bone) const;
void set_bone_pose(int p_bone, const Transform3D &p_pose);
+ void set_bone_pose_components(int p_bone, const Vector3 &p_position, const Quaternion &p_rotation, const Vector3 &p_scale);
void set_bone_pose_position(int p_bone, const Vector3 &p_position);
void set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation);
void set_bone_pose_scale(int p_bone, const Vector3 &p_scale);
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp
index a57ea2e3fde4..b48547cc2fe8 100644
--- a/scene/animation/animation_blend_space_1d.cpp
+++ b/scene/animation/animation_blend_space_1d.cpp
@@ -32,7 +32,7 @@
#include "animation_blend_tree.h"
-void AnimationNodeBlendSpace1D::get_parameter_list(List *r_list) const {
+void AnimationNodeBlendSpace1D::get_parameter_list(LocalVector *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position));
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
@@ -77,6 +77,14 @@ void AnimationNodeBlendSpace1D::_animation_node_removed(const ObjectID &p_oid, c
AnimationRootNode::_animation_node_removed(p_oid, p_node);
}
+void AnimationNodeBlendSpace1D::validate_node(const AnimationTree *p_tree, const StringName &p_path) const {
+ AnimationRootNode::validate_node(p_tree, p_path);
+
+ if (get_blend_point_count() == 0) {
+ add_validation_error(p_tree, p_path, RTR("No blend points exist, so blending cannot take place."));
+ }
+}
+
void AnimationNodeBlendSpace1D::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace1D::set_blend_point_position);
@@ -123,7 +131,7 @@ void AnimationNodeBlendSpace1D::_bind_methods() {
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY);
}
-void AnimationNodeBlendSpace1D::get_child_nodes(List *r_child_nodes) {
+void AnimationNodeBlendSpace1D::get_child_nodes(LocalVector *r_child_nodes) {
for (int i = 0; i < blend_points_used; i++) {
ChildNode cn;
cn.name = itos(i);
@@ -149,12 +157,10 @@ void AnimationNodeBlendSpace1D::add_blend_point(const Ref &p_
blend_points[p_at_index].node = p_node;
blend_points[p_at_index].position = p_position;
- blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_at_index].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
+ _add_node(blend_points[p_at_index].node);
blend_points_used++;
- emit_signal(SNAME("tree_changed"));
+ _tree_changed();
}
void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) {
@@ -168,17 +174,13 @@ void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Refdisconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed));
- blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed));
- blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed));
+ _remove_node(blend_points[p_point].node);
}
blend_points[p_point].node = p_node;
- blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_point].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_point].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
+ _add_node(blend_points[p_point].node);
- emit_signal(SNAME("tree_changed"));
+ _tree_changed();
}
float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const {
@@ -195,9 +197,7 @@ void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) {
ERR_FAIL_INDEX(p_point, blend_points_used);
ERR_FAIL_COND(blend_points[p_point].node.is_null());
- blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed));
- blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed));
- blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed));
+ _remove_node(blend_points[p_point].node);
for (int i = p_point; i < blend_points_used - 1; i++) {
blend_points[i] = blend_points[i + 1];
@@ -206,7 +206,7 @@ void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) {
blend_points_used--;
emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point));
- emit_signal(SNAME("tree_changed"));
+ _tree_changed();
}
int AnimationNodeBlendSpace1D::get_blend_point_count() const {
@@ -277,7 +277,7 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref max_weight) {
max_weight = pi.weight;
mind = t;
@@ -358,7 +360,8 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM
}
} else if (sync) {
pi.weight = 0;
- blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
+ AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(blend_points[i].name);
+ blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
}
}
} else {
@@ -373,34 +376,33 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM
}
}
+ AnimationNodeInstance *instance_current_closest = p_instance.get_child_instance_by_path_or_null(blend_points[cur_closest].name);
if (new_closest != cur_closest && new_closest != -1) {
+ AnimationNodeInstance *instance_new_closest = p_instance.get_child_instance_by_path_or_null(blend_points[new_closest].name);
+
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
NodeTimeInfo from;
// For ping-pong loop.
Ref na_c = static_cast[>(blend_points[cur_closest].node);
Ref na_n = static_cast][>(blend_points[new_closest].node);
- if (na_c.is_valid() && na_n.is_valid()) {
- na_n->process_state = process_state;
- na_c->process_state = process_state;
-
- na_n->set_backward(na_c->is_backward());
-
+ if (na_c.is_valid() && na_n.is_valid() && instance_current_closest && instance_new_closest) {
+ na_n->set_backward(*instance_new_closest, p_process_state, na_c->is_backward(*instance_current_closest, p_process_state));
na_n = nullptr;
na_c = nullptr;
}
// See how much animation remains.
pi.seeked = false;
pi.weight = 0;
- from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, true);
+ from = blend_node(p_process_state, p_instance, instance_current_closest, pi, FILTER_IGNORE, true, true);
pi.time = from.position;
}
pi.seeked = true;
pi.weight = 1.0;
- mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
+ mind = blend_node(p_process_state, p_instance, instance_new_closest, pi, FILTER_IGNORE, true, p_test_only);
cur_closest = new_closest;
} else {
pi.weight = 1.0;
- mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
+ mind = blend_node(p_process_state, p_instance, instance_current_closest, pi, FILTER_IGNORE, true, p_test_only);
}
if (sync) {
@@ -408,13 +410,14 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM
pi.weight = 0;
for (int i = 0; i < blend_points_used; i++) {
if (i != cur_closest) {
- blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
+ AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(blend_points[i].name);
+ blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
}
}
}
}
- set_parameter(closest, cur_closest);
+ p_instance.set_parameter_closest(cur_closest, p_process_state.is_testing);
return mind;
}
diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h
index 0bd29cd97e9f..816be8f85c6c 100644
--- a/scene/animation/animation_blend_space_1d.h
+++ b/scene/animation/animation_blend_space_1d.h
@@ -80,10 +80,12 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode {
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
public:
- virtual void get_parameter_list(List *r_list) const override;
+ void validate_node(const AnimationTree *p_tree, const StringName &p_path) const override;
+
+ virtual void get_parameter_list(LocalVector *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
- virtual void get_child_nodes(List *r_child_nodes) override;
+ virtual void get_child_nodes(LocalVector *r_child_nodes) override;
void add_blend_point(const Ref &p_node, float p_position, int p_at_index = -1);
void set_blend_point_position(int p_point, float p_position);
@@ -112,7 +114,7 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode {
void set_use_sync(bool p_sync);
bool is_using_sync() const;
- virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only = false) override;
String get_caption() const override;
Ref get_child_by_name(const StringName &p_name) const override;
diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp
index 62ba7edd4e0c..684513e7f7b9 100644
--- a/scene/animation/animation_blend_space_2d.cpp
+++ b/scene/animation/animation_blend_space_2d.cpp
@@ -34,7 +34,7 @@
#include "core/math/geometry_2d.h"
#include "scene/resources/material.h"
-void AnimationNodeBlendSpace2D::get_parameter_list(List *r_list) const {
+void AnimationNodeBlendSpace2D::get_parameter_list(LocalVector *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::VECTOR2, blend_position));
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
@@ -53,7 +53,7 @@ Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName
}
}
-void AnimationNodeBlendSpace2D::get_child_nodes(List *r_child_nodes) {
+void AnimationNodeBlendSpace2D::get_child_nodes(LocalVector *r_child_nodes) {
for (int i = 0; i < blend_points_used; i++) {
ChildNode cn;
cn.name = itos(i);
@@ -84,14 +84,12 @@ void AnimationNodeBlendSpace2D::add_blend_point(const Ref &p_
blend_points[p_at_index].node = p_node;
blend_points[p_at_index].position = p_position;
- blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_at_index].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
+ _add_node(blend_points[p_at_index].node);
blend_points_used++;
_queue_auto_triangles();
- emit_signal(SNAME("tree_changed"));
+ _tree_changed();
}
void AnimationNodeBlendSpace2D::set_blend_point_position(int p_point, const Vector2 &p_position) {
@@ -105,16 +103,12 @@ void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Refdisconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed));
- blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed));
- blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed));
+ _remove_node(blend_points[p_point].node);
}
blend_points[p_point].node = p_node;
- blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_point].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
- blend_points[p_point].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
+ _add_node(blend_points[p_point].node);
- emit_signal(SNAME("tree_changed"));
+ _tree_changed();
}
Vector2 AnimationNodeBlendSpace2D::get_blend_point_position(int p_point) const {
@@ -131,9 +125,7 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) {
ERR_FAIL_INDEX(p_point, blend_points_used);
ERR_FAIL_COND(blend_points[p_point].node.is_null());
- blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed));
- blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed));
- blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed));
+ _remove_node(blend_points[p_point].node);
for (int i = 0; i < triangles.size(); i++) {
bool erase = false;
@@ -158,7 +150,7 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) {
blend_points_used--;
emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point));
- emit_signal(SNAME("tree_changed"));
+ _tree_changed();
}
int AnimationNodeBlendSpace2D::get_blend_point_count() const {
@@ -444,15 +436,15 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect
r_weights[2] = w;
}
-AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) {
_update_triangles();
if (!blend_points_used) {
return NodeTimeInfo();
}
- Vector2 blend_pos = get_parameter(blend_position);
- int cur_closest = get_parameter(closest);
+ Vector2 blend_pos = p_instance.get_parameter(blend_position);
+ int cur_closest = p_instance.get_parameter_closest();
NodeTimeInfo mind; //time of min distance point
AnimationMixer::PlaybackInfo pi = p_playback_info;
@@ -519,7 +511,8 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM
if (i == triangle_points[j]) {
//blend with the given weight
pi.weight = blend_weights[j];
- NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
+ AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(blend_points[i].name);
+ NodeTimeInfo t = blend_node(p_process_state, p_instance, other_instance, pi, FILTER_IGNORE, true, p_test_only);
if (first || pi.weight > max_weight) {
mind = t;
max_weight = pi.weight;
@@ -532,7 +525,8 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM
if (sync && !found) {
pi.weight = 0;
- blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
+ AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(blend_points[i].name);
+ blend_node(p_process_state, p_instance, other_instance, pi, FILTER_IGNORE, true, p_test_only);
}
}
} else {
@@ -547,34 +541,33 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM
}
}
+ AnimationNodeInstance *instance_current_closest = p_instance.get_child_instance_by_path_or_null(blend_points[cur_closest].name);
if (new_closest != cur_closest && new_closest != -1) {
+ AnimationNodeInstance *instance_new_closest = p_instance.get_child_instance_by_path_or_null(blend_points[new_closest].name);
+
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
NodeTimeInfo from;
// For ping-pong loop.
Ref na_c = static_cast][>(blend_points[cur_closest].node);
Ref na_n = static_cast][>(blend_points[new_closest].node);
- if (na_c.is_valid() && na_n.is_valid()) {
- na_n->process_state = process_state;
- na_c->process_state = process_state;
-
- na_n->set_backward(na_c->is_backward());
-
+ if (na_c.is_valid() && na_n.is_valid() && instance_current_closest && instance_new_closest) {
+ na_n->set_backward(*instance_new_closest, p_process_state, na_c->is_backward(*instance_current_closest, p_process_state));
na_n = nullptr;
na_c = nullptr;
}
// See how much animation remains.
pi.seeked = false;
pi.weight = 0;
- from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, true);
+ from = blend_node(p_process_state, p_instance, instance_current_closest, pi, FILTER_IGNORE, true, true);
pi.time = from.position;
}
pi.seeked = true;
pi.weight = 1.0;
- mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
+ mind = blend_node(p_process_state, p_instance, instance_new_closest, pi, FILTER_IGNORE, true, p_test_only);
cur_closest = new_closest;
} else {
pi.weight = 1.0;
- mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
+ mind = blend_node(p_process_state, p_instance, instance_current_closest, pi, FILTER_IGNORE, true, p_test_only);
}
if (sync) {
@@ -582,13 +575,14 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM
pi.weight = 0;
for (int i = 0; i < blend_points_used; i++) {
if (i != cur_closest) {
- blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
+ AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(blend_points[i].name);
+ blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
}
}
}
}
- set_parameter(closest, cur_closest);
+ p_instance.set_parameter_closest(cur_closest, p_process_state.is_testing);
return mind;
}
@@ -654,6 +648,15 @@ void AnimationNodeBlendSpace2D::_animation_node_removed(const ObjectID &p_oid, c
AnimationRootNode::_animation_node_removed(p_oid, p_node);
}
+void AnimationNodeBlendSpace2D::validate_node(const AnimationTree *p_tree, const StringName &p_path) const {
+ AnimationRootNode::validate_node(p_tree, p_path);
+
+ const_cast(this)->_update_triangles();
+ if (get_triangle_count() == 0) {
+ add_validation_error(p_tree, p_path, RTR("No triangles exist, so blending cannot take place."));
+ }
+}
+
void AnimationNodeBlendSpace2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position);
diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h
index 4c4791c26015..5968fdc57a0d 100644
--- a/scene/animation/animation_blend_space_2d.h
+++ b/scene/animation/animation_blend_space_2d.h
@@ -93,10 +93,12 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode {
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
public:
- virtual void get_parameter_list(List *r_list) const override;
+ void validate_node(const AnimationTree *p_tree, const StringName &p_path) const override;
+
+ virtual void get_parameter_list(LocalVector *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
- virtual void get_child_nodes(List *r_child_nodes) override;
+ virtual void get_child_nodes(LocalVector *r_child_nodes) override;
void add_blend_point(const Ref &p_node, const Vector2 &p_position, int p_at_index = -1);
void set_blend_point_position(int p_point, const Vector2 &p_position);
@@ -127,7 +129,7 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode {
void set_y_label(const String &p_label);
String get_y_label() const;
- virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only = false) override;
virtual String get_caption() const override;
Vector2 get_closest_point(const Vector2 &p_point);
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index 83024eed7a2d..4951e63c3a2f 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -33,16 +33,33 @@
#include "scene/resources/animation.h"
void AnimationNodeAnimation::set_animation(const StringName &p_name) {
+ if (animation == p_name) {
+ return;
+ }
animation = p_name;
+ animation_version++;
+ if (unlikely(animation_version == 0)) {
+ animation_version = 1;
+ }
+ _node_updated(get_instance_id());
}
StringName AnimationNodeAnimation::get_animation() const {
return animation;
}
-Vector (*AnimationNodeAnimation::get_editable_animation_list)() = nullptr;
+LocalVector (*AnimationNodeAnimation::get_editable_animation_list)() = nullptr;
+
+void AnimationNodeAnimation::validate_node(const AnimationTree *p_tree, const StringName &p_path) const {
+ AnimationRootNode::validate_node(p_tree, p_path);
-void AnimationNodeAnimation::get_parameter_list(List *r_list) const {
+ const Ref &animation_resource = p_tree->get_animation_or_null(animation);
+ if (animation_resource.is_null()) {
+ add_validation_error(p_tree, p_path, vformat(RTR("Animation '%s' not found."), animation));
+ }
+}
+
+void AnimationNodeAnimation::get_parameter_list(LocalVector *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::BOOL, backward, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
@@ -58,30 +75,11 @@ Variant AnimationNodeAnimation::get_parameter_default_value(const StringName &p_
return 0.0;
}
-AnimationNode::NodeTimeInfo AnimationNodeAnimation::get_node_time_info() const {
- NodeTimeInfo nti;
- if (!process_state->tree->has_animation(animation)) {
- return nti;
- }
-
- if (use_custom_timeline) {
- nti.length = timeline_length;
- nti.loop_mode = loop_mode;
- } else {
- Ref anim = process_state->tree->get_animation(animation);
- nti.length = (double)anim->get_length();
- nti.loop_mode = anim->get_loop_mode();
- }
- nti.position = get_parameter(current_position);
-
- return nti;
-}
-
void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const {
if (Engine::get_singleton()->is_editor_hint() && p_property.name == "animation" && get_editable_animation_list) {
- Vector names = get_editable_animation_list();
+ LocalVector names = get_editable_animation_list();
String anims;
- for (int i = 0; i < names.size(); i++) {
+ for (uint32_t i = 0; i < names.size(); i++) {
if (i > 0) {
anims += ",";
}
@@ -100,52 +98,27 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const
}
}
-AnimationNode::NodeTimeInfo AnimationNodeAnimation::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- process_state->is_testing = p_test_only;
+AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) {
+ _update_animation_cache(p_process_state.tree, p_instance);
+ const Ref &anim = p_instance.cached_animation;
+ // Should not ever happen due to validation earlier.
+ ERR_FAIL_COND_V(anim.is_null(), NodeTimeInfo());
+ double anim_size = anim->get_length();
- AnimationMixer::PlaybackInfo pi = p_playback_info;
- if (p_playback_info.seeked) {
- if (p_playback_info.is_external_seeking) {
- pi.delta = get_node_time_info().position - p_playback_info.time;
- }
+ double cur_len;
+ Animation::LoopMode cur_loop_mode;
+ if (use_custom_timeline) {
+ cur_len = timeline_length;
+ cur_loop_mode = loop_mode;
} else {
- pi.time = get_node_time_info().position + (get_parameter(backward) ? -p_playback_info.delta : p_playback_info.delta);
- }
-
- NodeTimeInfo nti = _process(pi, p_test_only);
-
- if (!p_test_only) {
- set_node_time_info(nti);
- }
-
- return nti;
-}
-
-AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- if (!process_state->tree->has_animation(animation)) {
- AnimationNodeBlendTree *tree = Object::cast_to(node_state.parent);
- if (tree) {
- String node_name = tree->get_node_name(Ref(this));
- make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), node_name, animation));
-
- } else {
- make_invalid(vformat(RTR("Animation not found: '%s'"), animation));
- }
-
- return NodeTimeInfo();
+ cur_len = anim_size;
+ cur_loop_mode = anim->get_loop_mode();
}
- Ref anim = process_state->tree->get_animation(animation);
- double anim_size = (double)anim->get_length();
-
- NodeTimeInfo cur_nti = get_node_time_info();
- double cur_len = cur_nti.length;
double cur_time = p_playback_info.time;
double cur_delta = p_playback_info.delta;
- bool cur_backward = get_parameter(backward);
-
- Animation::LoopMode cur_loop_mode = cur_nti.loop_mode;
- double prev_time = cur_nti.position;
+ bool cur_backward = p_instance.get_parameter_backward();
+ double prev_time = p_instance.get_parameter_current_position();
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
bool node_backward = play_mode == PLAY_MODE_BACKWARD;
@@ -251,15 +224,15 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
// Emit start & finish signal. Internally, the detections are the same for backward.
// We should use call_deferred since the track keys are still being processed.
- if (process_state->tree && !p_test_only) {
+ if (p_process_state.tree && !p_test_only) {
// AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection.
if (is_started) {
- process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_started), animation);
+ p_process_state.tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_started), animation);
}
// Finished.
if (Animation::is_less_approx(prev_playback_time, anim_size) && Animation::is_greater_or_equal_approx(cur_playback_time, anim_size)) {
cur_playback_time = anim_size;
- process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_finished), animation);
+ p_process_state.tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_finished), animation);
}
}
}
@@ -280,10 +253,10 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
}
pi.weight = 1.0;
pi.looped_flag = looped_flag;
- blend_animation(animation, pi);
+ blend_animation(p_process_state, p_instance, animation, pi);
}
- set_parameter(backward, cur_backward);
+ p_instance.set_parameter_backward(cur_backward, p_process_state.is_testing);
return nti;
}
@@ -300,12 +273,12 @@ AnimationNodeAnimation::PlayMode AnimationNodeAnimation::get_play_mode() const {
return play_mode;
}
-void AnimationNodeAnimation::set_backward(bool p_backward) {
- set_parameter(backward, p_backward);
+void AnimationNodeAnimation::set_backward(AnimationNodeInstance &p_instance, ProcessState &p_process_state, bool p_backward) {
+ p_instance.set_parameter_backward(p_backward, p_process_state.is_testing);
}
-bool AnimationNodeAnimation::is_backward() const {
- return get_parameter(backward);
+bool AnimationNodeAnimation::is_backward(AnimationNodeInstance &p_instance, ProcessState &p_process_state) const {
+ return p_instance.get_parameter_backward();
}
void AnimationNodeAnimation::set_advance_on_start(bool p_advance_on_start) {
@@ -396,6 +369,21 @@ void AnimationNodeAnimation::_bind_methods() {
BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD);
}
+void AnimationNodeAnimation::_update_animation_cache(AnimationTree *p_tree, AnimationNodeInstance &p_instance) const {
+ if (p_instance.cached_animation_version == animation_version) {
+ return;
+ }
+
+ const Ref &anim = p_tree->get_animation_or_null(animation);
+ if (anim.is_null()) {
+ // I don't think this can even occur with validation now.
+ return;
+ }
+
+ p_instance.cached_animation = anim;
+ p_instance.cached_animation_version = animation_version;
+}
+
AnimationNodeAnimation::AnimationNodeAnimation() {
}
@@ -420,7 +408,7 @@ AnimationNodeSync::AnimationNodeSync() {
}
////////////////////////////////////////////////////////
-void AnimationNodeOneShot::get_parameter_list(List *r_list) const {
+void AnimationNodeOneShot::get_parameter_list(LocalVector *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::BOOL, active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY));
r_list->push_back(PropertyInfo(Variant::BOOL, internal_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY));
@@ -546,16 +534,16 @@ bool AnimationNodeOneShot::has_filter() const {
return true;
}
-AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- OneShotRequest cur_request = static_cast((int)get_parameter(request));
- bool cur_active = get_parameter(active);
- bool cur_internal_active = get_parameter(internal_active);
- NodeTimeInfo cur_nti = get_node_time_info();
- double cur_time_to_restart = get_parameter(time_to_restart);
- double cur_fade_in_remaining = get_parameter(fade_in_remaining);
- double cur_fade_out_remaining = get_parameter(fade_out_remaining);
+AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) {
+ OneShotRequest cur_request = static_cast(p_instance.get_parameter_request());
+ bool cur_active = p_instance.get_parameter_active();
+ bool cur_internal_active = p_instance.get_parameter_internal_active();
+ double prev_position = p_instance.get_parameter_current_position();
+ double cur_time_to_restart = p_instance.get_parameter_time_to_restart();
+ double cur_fade_in_remaining = p_instance.get_parameter_fade_in_remaining();
+ double cur_fade_out_remaining = p_instance.get_parameter_fade_out_remaining();
- set_parameter(request, ONE_SHOT_REQUEST_NONE);
+ p_instance.set_parameter_request(ONE_SHOT_REQUEST_NONE, p_process_state.is_testing);
bool is_shooting = true;
bool is_fading_out = cur_active == true && cur_internal_active == false;
@@ -579,10 +567,10 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
}
if (is_abort) {
- set_parameter(internal_active, false);
- set_parameter(active, false);
- set_parameter(time_to_restart, -1);
- set_parameter(fade_out_remaining, 0);
+ p_instance.set_parameter_internal_active(false, p_process_state.is_testing);
+ p_instance.set_parameter_active(false, p_process_state.is_testing);
+ p_instance.set_parameter_time_to_restart(-1, p_process_state.is_testing);
+ p_instance.set_parameter_fade_out_remaining(0, p_process_state.is_testing);
cur_fade_out_remaining = 0;
is_fading_out = false;
is_shooting = false;
@@ -596,15 +584,15 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
// Shot is ended, do nothing.
is_shooting = false;
}
- set_parameter(internal_active, false);
- set_parameter(time_to_restart, -1);
+ p_instance.set_parameter_internal_active(false, p_process_state.is_testing);
+ p_instance.set_parameter_time_to_restart(-1, p_process_state.is_testing);
} else if (!do_start && !cur_active) {
if (Animation::is_greater_or_equal_approx(cur_time_to_restart, 0) && !p_seek) {
cur_time_to_restart -= abs_delta;
if (Animation::is_less_approx(cur_time_to_restart, 0)) {
do_start = true; // Restart.
}
- set_parameter(time_to_restart, cur_time_to_restart);
+ p_instance.set_parameter_time_to_restart(cur_time_to_restart, p_process_state.is_testing);
}
if (!do_start) {
is_shooting = false;
@@ -616,7 +604,7 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
if (!is_shooting) {
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
- return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ return blend_input(p_process_state, p_instance, 0, pi, FILTER_IGNORE, sync, p_test_only);
}
if (do_start) {
@@ -625,9 +613,9 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
cur_fade_in_remaining = fade_in; // If already active, don't fade-in again.
}
cur_internal_active = true;
- set_parameter(request, ONE_SHOT_REQUEST_NONE);
- set_parameter(internal_active, true);
- set_parameter(active, true);
+ p_instance.set_parameter_request(ONE_SHOT_REQUEST_NONE, p_process_state.is_testing);
+ p_instance.set_parameter_internal_active(true, p_process_state.is_testing);
+ p_instance.set_parameter_active(true, p_process_state.is_testing);
}
real_t blend = 1.0;
@@ -661,23 +649,23 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
NodeTimeInfo main_nti;
if (mix == MIX_MODE_ADD) {
pi.weight = 1.0;
- main_nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ main_nti = blend_input(p_process_state, p_instance, 0, pi, FILTER_IGNORE, sync, p_test_only);
} else {
pi.seeked &= use_blend;
pi.weight = 1.0 - blend;
- main_nti = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
+ main_nti = blend_input(p_process_state, p_instance, 0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
}
pi = p_playback_info;
if (do_start) {
pi.time = 0;
} else if (os_seek) {
- pi.time = cur_nti.position;
+ pi.time = prev_position;
}
pi.seeked = os_seek;
pi.weight = Math::is_zero_approx(blend) ? CMP_EPSILON : blend;
- NodeTimeInfo os_nti = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
+ NodeTimeInfo os_nti = blend_input(p_process_state, p_instance, 1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (Animation::is_less_or_equal_approx(cur_fade_in_remaining, 0) && !do_start && !is_fading_out) {
// Predict time scale by difference of delta times to estimate input animation's remain time in self time scale.
@@ -689,17 +677,17 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
is_fading_out = true;
cur_fade_out_remaining = os_rem + abs_delta;
cur_fade_in_remaining = 0;
- set_parameter(internal_active, false);
+ p_instance.set_parameter_internal_active(false, p_process_state.is_testing);
}
}
if (!p_seek) {
if (Animation::is_less_or_equal_approx(os_nti.get_remain(break_loop_at_end), 0) || (is_fading_out && Animation::is_less_or_equal_approx(cur_fade_out_remaining, 0))) {
- set_parameter(internal_active, false);
- set_parameter(active, false);
+ p_instance.set_parameter_internal_active(false, p_process_state.is_testing);
+ p_instance.set_parameter_active(false, p_process_state.is_testing);
if (auto_restart) {
double restart_sec = auto_restart_delay + Math::randd() * auto_restart_random_delay;
- set_parameter(time_to_restart, restart_sec);
+ p_instance.set_parameter_time_to_restart(restart_sec, p_process_state.is_testing);
}
}
if (!do_start) {
@@ -708,8 +696,8 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
cur_fade_out_remaining = MAX(0, cur_fade_out_remaining - abs_delta);
}
- set_parameter(fade_in_remaining, cur_fade_in_remaining);
- set_parameter(fade_out_remaining, cur_fade_out_remaining);
+ p_instance.set_parameter_fade_in_remaining(cur_fade_in_remaining, p_process_state.is_testing);
+ p_instance.set_parameter_fade_in_remaining(cur_fade_out_remaining, p_process_state.is_testing);
return cur_internal_active ? os_nti : main_nti;
}
@@ -776,7 +764,7 @@ AnimationNodeOneShot::AnimationNodeOneShot() {
////////////////////////////////////////////////
-void AnimationNodeAdd2::get_parameter_list(List *r_list) const {
+void AnimationNodeAdd2::get_parameter_list(LocalVector *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
@@ -798,14 +786,14 @@ bool AnimationNodeAdd2::has_filter() const {
return true;
}
-AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- double amount = get_parameter(add_amount);
+AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) {
+ double amount = p_instance.get_parameter_add_amount();
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
- NodeTimeInfo nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ NodeTimeInfo nti = blend_input(p_process_state, p_instance, 0, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = amount;
- blend_input(1, pi, FILTER_PASS, sync, p_test_only);
+ blend_input(p_process_state, p_instance, 1, pi, FILTER_PASS, sync, p_test_only);
return nti;
}
@@ -817,7 +805,7 @@ AnimationNodeAdd2::AnimationNodeAdd2() {
////////////////////////////////////////////////
-void AnimationNodeAdd3::get_parameter_list(List *r_list) const {
+void AnimationNodeAdd3::get_parameter_list(LocalVector *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater"));
}
@@ -839,16 +827,16 @@ bool AnimationNodeAdd3::has_filter() const {
return true;
}
-AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- double amount = get_parameter(add_amount);
+AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) {
+ double amount = p_instance.get_parameter_add_amount();
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = MAX(0, -amount);
- blend_input(0, pi, FILTER_PASS, sync, p_test_only);
+ blend_input(p_process_state, p_instance, 0, pi, FILTER_PASS, sync, p_test_only);
pi.weight = 1.0;
- NodeTimeInfo nti = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
+ NodeTimeInfo nti = blend_input(p_process_state, p_instance, 1, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = MAX(0, amount);
- blend_input(2, pi, FILTER_PASS, sync, p_test_only);
+ blend_input(p_process_state, p_instance, 2, pi, FILTER_PASS, sync, p_test_only);
return nti;
}
@@ -861,7 +849,7 @@ AnimationNodeAdd3::AnimationNodeAdd3() {
/////////////////////////////////////////////
-void AnimationNodeBlend2::get_parameter_list(List *r_list) const {
+void AnimationNodeBlend2::get_parameter_list(LocalVector *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
@@ -879,14 +867,14 @@ String AnimationNodeBlend2::get_caption() const {
return "Blend2";
}
-AnimationNode::NodeTimeInfo AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- double amount = get_parameter(blend_amount);
+AnimationNode::NodeTimeInfo AnimationNodeBlend2::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) {
+ double amount = p_instance.get_parameter_blend_amount();
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0 - amount;
- NodeTimeInfo nti0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only);
+ NodeTimeInfo nti0 = blend_input(p_process_state, p_instance, 0, pi, FILTER_BLEND, sync, p_test_only);
pi.weight = amount;
- NodeTimeInfo nti1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only);
+ NodeTimeInfo nti1 = blend_input(p_process_state, p_instance, 1, pi, FILTER_PASS, sync, p_test_only);
return amount > 0.5 ? nti1 : nti0; // Hacky but good enough.
}
@@ -902,7 +890,7 @@ AnimationNodeBlend2::AnimationNodeBlend2() {
//////////////////////////////////////
-void AnimationNodeBlend3::get_parameter_list(List]