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 *r_list) const { +void AnimationNodeBlend3::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, "-1,1,0.01,or_less,or_greater")); } @@ -920,16 +908,16 @@ String AnimationNodeBlend3::get_caption() const { return "Blend3"; } -AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double amount = get_parameter(blend_amount); +AnimationNode::NodeTimeInfo AnimationNodeBlend3::_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 = MAX(0, -amount); - NodeTimeInfo nti0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti0 = blend_input(p_process_state, p_instance, 0, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = 1.0 - Math::abs(amount); - NodeTimeInfo nti1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti1 = blend_input(p_process_state, p_instance, 1, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = MAX(0, amount); - NodeTimeInfo nti2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti2 = blend_input(p_process_state, p_instance, 2, pi, FILTER_IGNORE, sync, p_test_only); return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough. } @@ -942,7 +930,7 @@ AnimationNodeBlend3::AnimationNodeBlend3() { //////////////////////////////////////////////// -void AnimationNodeSub2::get_parameter_list(List *r_list) const { +void AnimationNodeSub2::get_parameter_list(LocalVector *r_list) const { AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, sub_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater")); } @@ -964,16 +952,16 @@ bool AnimationNodeSub2::has_filter() const { return true; } -AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double amount = get_parameter(sub_amount); +AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + double amount = p_instance.get_parameter_sub_amount(); AnimationMixer::PlaybackInfo pi = p_playback_info; // Out = Sub.Transform3D^(-1) * In.Transform3D 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); 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); } AnimationNodeSub2::AnimationNodeSub2() { @@ -983,7 +971,7 @@ AnimationNodeSub2::AnimationNodeSub2() { ///////////////////////////////// -void AnimationNodeTimeScale::get_parameter_list(List *r_list) const { +void AnimationNodeTimeScale::get_parameter_list(LocalVector *r_list) const { AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_less,or_greater")); } @@ -1001,8 +989,8 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double cur_scale = get_parameter(scale); +AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + double cur_scale = p_instance.get_parameter_scale(); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; @@ -1010,7 +998,7 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixe pi.delta *= cur_scale; } - return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); + return blend_input(p_process_state, p_instance, 0, pi, FILTER_IGNORE, true, p_test_only); } AnimationNodeTimeScale::AnimationNodeTimeScale() { @@ -1019,7 +1007,7 @@ AnimationNodeTimeScale::AnimationNodeTimeScale() { //////////////////////////////////// -void AnimationNodeTimeSeek::get_parameter_list(List *r_list) const { +void AnimationNodeTimeSeek::get_parameter_list(LocalVector *r_list) const { AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, seek_pos_request, PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater")); // It will be reset to -1 after seeking the position immediately. } @@ -1045,8 +1033,8 @@ bool AnimationNodeTimeSeek::is_explicit_elapse() const { return explicit_elapse; } -AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double cur_seek_pos = get_parameter(seek_pos_request); +AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + double cur_seek_pos = p_instance.get_parameter_seek_pos_request(); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; @@ -1054,10 +1042,10 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer pi.time = cur_seek_pos; pi.seeked = true; pi.is_external_seeking = explicit_elapse; - set_parameter(seek_pos_request, -1.0); // Reset. + p_instance.set_parameter_seek_pos_request(-1.0, p_process_state.is_testing); // Reset. } - return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); + return blend_input(p_process_state, p_instance, 0, pi, FILTER_IGNORE, true, p_test_only); } AnimationNodeTimeSeek::AnimationNodeTimeSeek() { @@ -1133,7 +1121,7 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con return true; } -void AnimationNodeTransition::get_parameter_list(List *r_list) const { +void AnimationNodeTransition::get_parameter_list(LocalVector *r_list) const { AnimationNode::get_parameter_list(r_list); String anims; for (int i = 0; i < get_input_count(); i++) { @@ -1270,13 +1258,12 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const { return allow_transition_to_self; } -AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - String cur_transition_request = get_parameter(transition_request); - int cur_current_index = get_parameter(current_index); - int cur_prev_index = get_parameter(prev_index); +AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + const String &cur_transition_request = p_instance.get_parameter_transition_request(); + int cur_current_index = p_instance.get_parameter_current_index(); + int cur_prev_index = p_instance.get_parameter_prev_index(); - NodeTimeInfo cur_nti = get_node_time_info(); - double cur_prev_xfading = get_parameter(prev_xfading); + double cur_prev_xfading = p_instance.get_parameter_prev_xfading(); bool switched = false; bool restart = false; @@ -1284,16 +1271,16 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix if (pending_update) { if (cur_current_index < 0 || cur_current_index >= get_input_count()) { - set_parameter(prev_index, -1); + p_instance.set_parameter_prev_index(-1, p_process_state.is_testing); if (get_input_count() > 0) { - set_parameter(current_index, 0); - set_parameter(current_state, get_input_name(0)); + p_instance.set_parameter_current_index(0, p_process_state.is_testing); + p_instance.set_parameter_current_state(get_input_name(0), p_process_state.is_testing); } else { - set_parameter(current_index, -1); - set_parameter(current_state, StringName()); + p_instance.set_parameter_current_index(-1, p_process_state.is_testing); + p_instance.set_parameter_current_state(StringName(), p_process_state.is_testing); } } else { - set_parameter(current_state, get_input_name(cur_current_index)); + p_instance.set_parameter_current_state(get_input_name(cur_current_index), p_process_state.is_testing); } pending_update = false; } @@ -1318,23 +1305,22 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix } else { switched = true; cur_prev_index = cur_current_index; - set_parameter(prev_index, cur_current_index); + p_instance.set_parameter_prev_index(cur_current_index, p_process_state.is_testing); cur_current_index = new_idx; - set_parameter(current_index, cur_current_index); - set_parameter(current_state, cur_transition_request); + p_instance.set_parameter_current_index(cur_current_index, p_process_state.is_testing); + p_instance.set_parameter_current_state(cur_transition_request, p_process_state.is_testing); } } else { ERR_PRINT("No such input: '" + cur_transition_request + "'"); } - cur_transition_request = String(); - set_parameter(transition_request, cur_transition_request); + p_instance.set_parameter_transition_request(String(), p_process_state.is_testing); } if (clear_remaining_fade) { cur_prev_xfading = 0; - set_parameter(prev_xfading, 0); + p_instance.set_parameter_prev_xfading(0, p_process_state.is_testing); cur_prev_index = -1; - set_parameter(prev_index, -1); + p_instance.set_parameter_prev_index(-1, p_process_state.is_testing); } AnimationMixer::PlaybackInfo pi = p_playback_info; @@ -1344,7 +1330,7 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix pi.time = 0; pi.seeked = true; pi.weight = 1.0; - return blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); + return blend_input(p_process_state, p_instance, cur_current_index, pi, FILTER_IGNORE, true, p_test_only); } if (switched) { @@ -1359,16 +1345,17 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix pi.weight = 0; for (int i = 0; i < get_input_count(); i++) { if (i != cur_current_index && i != cur_prev_index) { - blend_input(i, pi, FILTER_IGNORE, true, p_test_only); + blend_input(p_process_state, p_instance, i, pi, FILTER_IGNORE, true, p_test_only); } } } + NodeTimeInfo cur_nti; if (cur_prev_index < 0) { // Process current animation, check for transition. pi.weight = 1.0; - cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); + cur_nti = blend_input(p_process_state, p_instance, cur_current_index, pi, FILTER_IGNORE, true, p_test_only); if (input_data[cur_current_index].auto_advance && Animation::is_less_or_equal_approx(cur_nti.get_remain(input_data[cur_current_index].break_loop_at_end), xfade_time)) { - set_parameter(transition_request, get_input_name((cur_current_index + 1) % get_input_count())); + p_instance.set_parameter_transition_request(get_input_name((cur_current_index + 1) % get_input_count()), p_process_state.is_testing); } } else { // Cross-fading from prev to current. @@ -1392,21 +1379,21 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix pi.time = 0; pi.seeked = true; } - cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); + cur_nti = blend_input(p_process_state, p_instance, cur_current_index, pi, FILTER_IGNORE, true, p_test_only); pi = p_playback_info; pi.seeked &= use_blend; pi.weight = blend; - blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only); + blend_input(p_process_state, p_instance, cur_prev_index, pi, FILTER_IGNORE, true, p_test_only); if (!p_seek) { if (Animation::is_less_or_equal_approx(cur_prev_xfading, 0)) { - set_parameter(prev_index, -1); + p_instance.set_parameter_prev_index(-1, p_process_state.is_testing); } cur_prev_xfading -= Math::abs(p_playback_info.delta); } } - set_parameter(prev_xfading, cur_prev_xfading); + p_instance.set_parameter_prev_xfading(cur_prev_xfading, p_process_state.is_testing); return cur_nti; } @@ -1456,10 +1443,10 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -AnimationNode::NodeTimeInfo AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeOutput::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; - return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); + return blend_input(p_process_state, p_instance, 0, pi, FILTER_IGNORE, true, p_test_only); } AnimationNodeOutput::AnimationNodeOutput() { @@ -1467,7 +1454,7 @@ AnimationNodeOutput::AnimationNodeOutput() { } /////////////////////////////////////////////////////// -void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref p_node, const Vector2 &p_position) { +void AnimationNodeBlendTree::add_node(const StringName &p_name, const Ref &p_node, const Vector2 &p_position) { ERR_FAIL_COND(nodes.has(p_name)); ERR_FAIL_COND(p_node.is_null()); ERR_FAIL_COND(p_name == SceneStringName(output)); @@ -1482,26 +1469,14 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Refconnect(SNAME("tree_changed"), callable_mp(this, &AnimationNodeBlendTree::_tree_changed), CONNECT_REFERENCE_COUNTED); - p_node->connect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); - p_node->connect(SNAME("animation_node_removed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed), CONNECT_REFERENCE_COUNTED); - p_node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_name), CONNECT_REFERENCE_COUNTED); + _add_node(p_node); + p_node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_child_node_changed).bind(p_name), CONNECT_REFERENCE_COUNTED); } Ref AnimationNodeBlendTree::get_node(const StringName &p_name) const { - ERR_FAIL_COND_V(!nodes.has(p_name), Ref()); - - return nodes[p_name].node; -} - -StringName AnimationNodeBlendTree::get_node_name(const Ref &p_node) const { - for (const KeyValue &E : nodes) { - if (E.value.node == p_node) { - return E.key; - } - } - - ERR_FAIL_V(StringName()); + const Node *node = nodes.getptr(p_name); + ERR_FAIL_NULL_V(node, Ref()); + return node->node; } void AnimationNodeBlendTree::set_node_position(const StringName &p_node, const Vector2 &p_position) { @@ -1514,17 +1489,11 @@ Vector2 AnimationNodeBlendTree::get_node_position(const StringName &p_node) cons return nodes[p_node].position; } -void AnimationNodeBlendTree::get_child_nodes(List *r_child_nodes) { - Vector ns; - +void AnimationNodeBlendTree::get_child_nodes(LocalVector *r_child_nodes) { for (const KeyValue &E : nodes) { - ns.push_back(E.key); - } - - for (int i = 0; i < ns.size(); i++) { ChildNode cn; - cn.name = ns[i]; - cn.node = nodes[cn.name].node; + cn.name = E.key; + cn.node = E.value.node; r_child_nodes->push_back(cn); } } @@ -1533,9 +1502,10 @@ bool AnimationNodeBlendTree::has_node(const StringName &p_name) const { return nodes.has(p_name); } -Vector AnimationNodeBlendTree::get_node_connection_array(const StringName &p_name) const { - ERR_FAIL_COND_V(!nodes.has(p_name), Vector()); - return nodes[p_name].connections; +const LocalVector *AnimationNodeBlendTree::get_node_connection_array(const StringName &p_name) const { + const Node *node = nodes.getptr(p_name); + ERR_FAIL_NULL_V(node, nullptr); + return &node->connections; } void AnimationNodeBlendTree::remove_node(const StringName &p_name) { @@ -1544,19 +1514,17 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) { { Ref node = nodes[p_name].node; - node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationNodeBlendTree::_tree_changed)); - node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed)); - node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed)); - node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed)); + _remove_node(node); + node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_child_node_changed)); } nodes.erase(p_name); // Erase connections to name. for (KeyValue &E : nodes) { - for (int i = 0; i < E.value.connections.size(); i++) { + for (uint32_t i = 0; i < E.value.connections.size(); i++) { if (E.value.connections[i] == p_name) { - E.value.connections.write[i] = StringName(); + E.value.connections[i] = StringName(); } } } @@ -1572,21 +1540,21 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN ERR_FAIL_COND(p_name == SceneStringName(output)); ERR_FAIL_COND(p_new_name == SceneStringName(output)); - nodes[p_name].node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed)); + nodes[p_name].node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_child_node_changed)); nodes[p_new_name] = nodes[p_name]; nodes.erase(p_name); // Rename connections. for (KeyValue &E : nodes) { - for (int i = 0; i < E.value.connections.size(); i++) { + for (uint32_t i = 0; i < E.value.connections.size(); i++) { if (E.value.connections[i] == p_name) { - E.value.connections.write[i] = p_new_name; + E.value.connections[i] = p_new_name; } } } // Connection must be done with new name. - nodes[p_new_name].node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_new_name), CONNECT_REFERENCE_COUNTED); + nodes[p_new_name].node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_child_node_changed).bind(p_new_name), CONNECT_REFERENCE_COUNTED); emit_signal(SNAME("animation_node_renamed"), get_instance_id(), p_name, p_new_name); emit_signal(SNAME("tree_changed")); @@ -1599,27 +1567,29 @@ void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_ ERR_FAIL_COND(p_input_node == p_output_node); Ref input = nodes[p_input_node].node; - ERR_FAIL_INDEX(p_input_index, nodes[p_input_node].connections.size()); + ERR_FAIL_INDEX(p_input_index, (int)nodes[p_input_node].connections.size()); for (KeyValue &E : nodes) { - for (int i = 0; i < E.value.connections.size(); i++) { + for (uint32_t i = 0; i < E.value.connections.size(); i++) { StringName output = E.value.connections[i]; ERR_FAIL_COND(output == p_output_node); } } - nodes[p_input_node].connections.write[p_input_index] = p_output_node; + nodes[p_input_node].connections[p_input_index] = p_output_node; emit_changed(); + _node_updated(input->get_instance_id()); } void AnimationNodeBlendTree::disconnect_node(const StringName &p_node, int p_input_index) { ERR_FAIL_COND(!nodes.has(p_node)); Ref input = nodes[p_node].node; - ERR_FAIL_INDEX(p_input_index, nodes[p_node].connections.size()); + ERR_FAIL_INDEX(p_input_index, (int)nodes[p_node].connections.size()); - nodes[p_node].connections.write[p_input_index] = StringName(); + nodes[p_node].connections[p_input_index] = StringName(); + _node_updated(input->get_instance_id()); } AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const { @@ -1637,7 +1607,7 @@ AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node Ref input = nodes[p_input_node].node; - if (p_input_index < 0 || p_input_index >= nodes[p_input_node].connections.size()) { + if (p_input_index < 0 || p_input_index >= (int)nodes[p_input_node].connections.size()) { return CONNECTION_ERROR_NO_INPUT_INDEX; } @@ -1646,7 +1616,7 @@ AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node } for (const KeyValue &E : nodes) { - for (int i = 0; i < E.value.connections.size(); i++) { + for (uint32_t i = 0; i < E.value.connections.size(); i++) { const StringName output = E.value.connections[i]; if (output == p_output_node) { return CONNECTION_ERROR_CONNECTION_EXISTS; @@ -1656,9 +1626,9 @@ AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node return CONNECTION_OK; } -void AnimationNodeBlendTree::get_node_connections(List *r_connections) const { +void AnimationNodeBlendTree::get_node_connections(LocalVector *r_connections) const { for (const KeyValue &E : nodes) { - for (int i = 0; i < E.value.connections.size(); i++) { + for (uint32_t i = 0; i < E.value.connections.size(); i++) { const StringName output = E.value.connections[i]; if (output != StringName()) { NodeConnection nc; @@ -1675,15 +1645,15 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -AnimationNode::NodeTimeInfo AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - Ref output = nodes[SceneStringName(output)].node; - node_state.connections = nodes[SceneStringName(output)].connections; +AnimationNode::NodeTimeInfo AnimationNodeBlendTree::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + const Ref output = nodes[SceneStringName(output)].node; ERR_FAIL_COND_V(output.is_null(), NodeTimeInfo()); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; - return _blend_node(output, "output", this, pi, FILTER_IGNORE, true, p_test_only, nullptr); + AnimationNodeInstance &output_instance = p_instance.get_child_instance_by_path(SceneStringName(output)); + return _blend_node(p_process_state, p_instance, output_instance, pi, FILTER_IGNORE, true, p_test_only, nullptr); } LocalVector AnimationNodeBlendTree::get_node_list() const { @@ -1771,7 +1741,7 @@ bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) cons } } } else if (prop_name == "node_connections") { - List nc; + LocalVector nc; get_node_connections(&nc); Array conns; conns.resize(nc.size() * 3); @@ -1828,7 +1798,39 @@ void AnimationNodeBlendTree::reset_state() { emit_signal(SNAME("tree_changed")); } -void AnimationNodeBlendTree::_node_changed(const StringName &p_node) { +void AnimationNodeBlendTree::validate_node(const AnimationTree *p_tree, const StringName &p_path) const { + AnimationRootNode::validate_node(p_tree, p_path); + + // Validate output connection. + { + const LocalVector *output_connections = get_node_connection_array(SceneStringName(output)); + const StringName &node_name = output_connections->operator[](0); + + if (const Node *child_node = nodes.getptr(node_name); !child_node) { + add_validation_error(p_tree, String(p_path) + SceneStringName(output) + "/", RTR("Nothing connected to output.")); + } + } + + // Rest of children. + for (const KeyValue &E : nodes) { + const Node &child = E.value; + + // Skip output node, already validated. + if (E.key == SceneStringName(output)) { + continue; + } + + for (uint32_t input = 0; input < child.connections.size(); input++) { + const StringName &connected_node_name = child.connections[input]; + if (const Node *connected_to = nodes.getptr(connected_node_name); !connected_to) { + StringName path = String(p_path) + String(E.key) + "/"; + add_validation_error(p_tree, path, "Nothing connected to", input); + } + } + } +} + +void AnimationNodeBlendTree::_child_node_changed(const StringName &p_node) { ERR_FAIL_COND(!nodes.has(p_node)); nodes[p_node].connections.resize(nodes[p_node].node->get_input_count()); emit_signal(SNAME("node_changed"), p_node); @@ -1887,7 +1889,7 @@ void AnimationNodeBlendTree::_initialize_node_tree() { n.node = output; n.position = Vector2(300, 150); n.connections.resize(1); - nodes["output"] = n; + nodes[SceneStringName(output)] = n; } AnimationNodeBlendTree::AnimationNodeBlendTree() { diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index b5f7ec5d1e6d..6c4fdbc32f6e 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -39,6 +39,7 @@ class AnimationNodeAnimation : public AnimationRootNode { StringName backward = "backward"; // Only used by pingpong animation. StringName animation; + mutable uint32_t animation_version = 1; bool advance_on_start = false; @@ -57,16 +58,18 @@ class AnimationNodeAnimation : public AnimationRootNode { PLAY_MODE_BACKWARD }; - void get_parameter_list(List *r_list) const override; - virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; + void validate_node(const AnimationTree *p_tree, const StringName &p_path) const override; - virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter(). + void get_parameter_list(LocalVector *r_list) const override; + virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; - static Vector (*get_editable_animation_list)(); + static LocalVector (*get_editable_animation_list)(); virtual String get_caption() const override; - virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; - virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + _FORCE_INLINE_ virtual double get_process_delta(AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info) const override final { + return (p_instance.get_parameter_backward() ? -p_playback_info.delta : p_playback_info.delta); + } + virtual NodeTimeInfo _process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only = false) override; void set_animation(const StringName &p_name); StringName get_animation() const; @@ -74,8 +77,8 @@ class AnimationNodeAnimation : public AnimationRootNode { void set_play_mode(PlayMode p_play_mode); PlayMode get_play_mode() const; - void set_backward(bool p_backward); - bool is_backward() const; + void set_backward(AnimationNodeInstance &p_instance, ProcessState &p_process_state, bool p_backward); + bool is_backward(AnimationNodeInstance &p_instance, ProcessState &p_process_state) const; void set_advance_on_start(bool p_advance_on_start); bool is_advance_on_start() const; @@ -103,6 +106,8 @@ class AnimationNodeAnimation : public AnimationRootNode { private: PlayMode play_mode = PLAY_MODE_FORWARD; + + void _update_animation_cache(AnimationTree *p_tree, AnimationNodeInstance &p_instance) const; }; VARIANT_ENUM_CAST(AnimationNodeAnimation::PlayMode) @@ -162,7 +167,7 @@ class AnimationNodeOneShot : public AnimationNodeSync { static void _bind_methods(); public: - virtual void get_parameter_list(List *r_list) 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 bool is_parameter_read_only(const StringName &p_parameter) const override; @@ -198,7 +203,7 @@ class AnimationNodeOneShot : public AnimationNodeSync { bool is_aborted_on_reset() const; virtual bool has_filter() const override; - 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; AnimationNodeOneShot(); }; @@ -212,13 +217,13 @@ class AnimationNodeAdd2 : public AnimationNodeSync { StringName add_amount = PNAME("add_amount"); public: - void get_parameter_list(List *r_list) const override; + void get_parameter_list(LocalVector *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; virtual bool has_filter() const override; - 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; AnimationNodeAdd2(); }; @@ -229,13 +234,13 @@ class AnimationNodeAdd3 : public AnimationNodeSync { StringName add_amount = PNAME("add_amount"); public: - void get_parameter_list(List *r_list) const override; + void get_parameter_list(LocalVector *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; virtual bool has_filter() const override; - 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; AnimationNodeAdd3(); }; @@ -246,11 +251,11 @@ class AnimationNodeBlend2 : public AnimationNodeSync { StringName blend_amount = PNAME("blend_amount"); public: - virtual void get_parameter_list(List *r_list) 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 String get_caption() const override; - 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 bool has_filter() const override; AnimationNodeBlend2(); @@ -262,12 +267,12 @@ class AnimationNodeBlend3 : public AnimationNodeSync { StringName blend_amount = PNAME("blend_amount"); public: - virtual void get_parameter_list(List *r_list) 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 String get_caption() const override; - 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; AnimationNodeBlend3(); }; @@ -277,13 +282,13 @@ class AnimationNodeSub2 : public AnimationNodeSync { StringName sub_amount = PNAME("sub_amount"); public: - void get_parameter_list(List *r_list) const override; + void get_parameter_list(LocalVector *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; virtual bool has_filter() const override; - 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; AnimationNodeSub2(); }; @@ -294,12 +299,12 @@ class AnimationNodeTimeScale : public AnimationNode { StringName scale = PNAME("scale"); public: - virtual void get_parameter_list(List *r_list) 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 String get_caption() const override; - 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; AnimationNodeTimeScale(); }; @@ -314,12 +319,12 @@ class AnimationNodeTimeSeek : public AnimationNode { static void _bind_methods(); public: - virtual void get_parameter_list(List *r_list) 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 String get_caption() const override; - 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; void set_explicit_elapse(bool p_enable); bool is_explicit_elapse() const; @@ -359,7 +364,7 @@ class AnimationNodeTransition : public AnimationNodeSync { void _get_property_list(List *p_list) const; public: - virtual void get_parameter_list(List *r_list) 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 bool is_parameter_read_only(const StringName &p_parameter) const override; @@ -389,7 +394,7 @@ class AnimationNodeTransition : public AnimationNodeSync { void set_allow_transition_to_self(bool p_enable); bool is_allow_transition_to_self() 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; AnimationNodeTransition(); }; @@ -399,7 +404,7 @@ class AnimationNodeOutput : public AnimationNode { public: virtual String get_caption() const override; - 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; AnimationNodeOutput(); }; @@ -411,14 +416,14 @@ class AnimationNodeBlendTree : public AnimationRootNode { struct Node { Ref node; Vector2 position; - Vector connections; + LocalVector connections; }; AHashMap nodes; Vector2 graph_offset; - void _node_changed(const StringName &p_node); + void _child_node_changed(const StringName &p_node); void _initialize_node_tree(); @@ -445,18 +450,19 @@ class AnimationNodeBlendTree : public AnimationRootNode { //no need to check for cycles due to tree topology }; - void add_node(const StringName &p_name, Ref p_node, const Vector2 &p_position = Vector2()); + void validate_node(const AnimationTree *p_tree, const StringName &p_path) const override; + + void add_node(const StringName &p_name, const Ref &p_node, const Vector2 &p_position = Vector2()); Ref get_node(const StringName &p_name) const; void remove_node(const StringName &p_name); void rename_node(const StringName &p_name, const StringName &p_new_name); bool has_node(const StringName &p_name) const; - StringName get_node_name(const Ref &p_node) const; - Vector get_node_connection_array(const StringName &p_name) const; + const LocalVector *get_node_connection_array(const StringName &p_name) const; void set_node_position(const StringName &p_node, const Vector2 &p_position); Vector2 get_node_position(const StringName &p_node) const; - virtual void get_child_nodes(List *r_child_nodes) override; + virtual void get_child_nodes(LocalVector *r_child_nodes) override; void connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node); void disconnect_node(const StringName &p_node, int p_input_index); @@ -468,10 +474,10 @@ class AnimationNodeBlendTree : public AnimationRootNode { }; ConnectionError can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const; - void get_node_connections(List *r_connections) const; + void get_node_connections(LocalVector *r_connections) const; virtual String get_caption() const override; - 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; LocalVector get_node_list() const; TypedArray get_node_list_as_typed_array() const; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 5890baa2ef07..30f259f0d316 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -157,16 +157,17 @@ void AnimationMixer::_animation_set_cache_update() { for (const AnimationLibraryData &lib : animation_libraries) { for (const KeyValue> &K : lib.library->animations) { StringName key = lib.name == StringName() ? K.key : StringName(String(lib.name) + "/" + String(K.key)); - if (!animation_set.has(key)) { - AnimationData ad; + + bool was_added = false; + AnimationData &ad = animation_set.get_value_ref_or_add_default(key, was_added); + + if (was_added) { ad.animation = K.value; ad.animation_library = lib.name; ad.name = key; ad.last_update = animation_set_update_pass; - animation_set.insert(ad.name, ad); cache_valid = false; // No need to delete the cache, but it must be updated to add track caches. } else { - AnimationData &ad = animation_set[key]; if (ad.last_update != animation_set_update_pass) { // Was not updated, update. If the animation is duplicated, the second one will be ignored. if (ad.animation != K.value || ad.animation_library != lib.name) { @@ -183,7 +184,7 @@ void AnimationMixer::_animation_set_cache_update() { } // Check removed. - List to_erase; + LocalVector to_erase; for (const KeyValue &E : animation_set) { if (E.value.last_update != animation_set_update_pass) { // Was not updated, must be erased. @@ -192,9 +193,8 @@ void AnimationMixer::_animation_set_cache_update() { } } - while (to_erase.size()) { - animation_set.erase(to_erase.front()->get()); - to_erase.pop_front(); + for (const StringName &E : to_erase) { + animation_set.erase(E); } if (clear_cache_needed) { @@ -210,7 +210,7 @@ void AnimationMixer::_animation_added(const StringName &p_name, const StringName } void AnimationMixer::_animation_removed(const StringName &p_name, const StringName &p_library) { - StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); + const StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); if (!animation_set.has(name)) { return; // No need to update because not the one from the library being used. @@ -222,8 +222,8 @@ void AnimationMixer::_animation_removed(const StringName &p_name, const StringNa } void AnimationMixer::_animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library) { - StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); - StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name)); + const StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); + const StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name)); if (!animation_set.has(from_name)) { return; // No need to update because not the one from the library being used. @@ -257,7 +257,7 @@ TypedArray AnimationMixer::_get_animation_library_list() const { return ret; } -void AnimationMixer::get_animation_library_list(List *p_libraries) const { +void AnimationMixer::get_animation_library_list(LocalVector *p_libraries) const { for (const AnimationLibraryData &lib : animation_libraries) { p_libraries->push_back(lib.name); } @@ -292,13 +292,14 @@ StringName AnimationMixer::get_animation_library_name(const Ref &p_animation) const { +const StringName &AnimationMixer::find_animation_library(const Ref &p_animation) const { for (const KeyValue &E : animation_set) { if (E.value.animation == p_animation) { return E.value.animation_library; } } - return StringName(); + const static StringName empty = StringName(); + return empty; } Error AnimationMixer::add_animation_library(const StringName &p_name, const Ref &p_animation_library) { @@ -401,21 +402,33 @@ void AnimationMixer::rename_animation_library(const StringName &p_name, const St notify_property_list_changed(); } -void AnimationMixer::get_animation_list(List *p_animations) const { - List anims; +LocalVector AnimationMixer::get_sorted_animation_list() const { + LocalVector animations; + get_animation_list(&animations); + animations.sort_custom(); + return animations; +} + +void AnimationMixer::get_animation_list(LocalVector *p_animations) const { + p_animations->reserve(p_animations->size() + animation_set.size()); for (const KeyValue &E : animation_set) { - anims.push_back(E.key); - } - anims.sort(); - for (const String &E : anims) { - p_animations->push_back(E); + p_animations->push_back(E.key); } } -Ref AnimationMixer::get_animation(const StringName &p_name) const { - ERR_FAIL_COND_V_MSG(!animation_set.has(p_name), Ref(), vformat("Animation not found: \"%s\".", p_name)); - const AnimationData &anim_data = animation_set[p_name]; - return anim_data.animation; +const Ref &AnimationMixer::get_animation(const StringName &p_name) const { + const Ref &animation = get_animation_or_null(p_name); + ERR_FAIL_COND_V_MSG(animation.is_null(), animation, vformat("Animation not found: \"%s\".", p_name)); + return animation; +} + +const Ref &AnimationMixer::get_animation_or_null(const StringName &p_name) const { + const AnimationData *ad = animation_set.getptr(p_name); + if (!ad) { + const static Ref empty = Ref(); + return empty; + } + return ad->animation; } bool AnimationMixer::has_animation(const StringName &p_name) const { @@ -625,7 +638,7 @@ void AnimationMixer::_init_root_motion_cache() { root_motion_scale_accumulator = Vector3(1, 1, 1); } -void AnimationMixer::_create_track_num_to_track_cache_for_animation(Ref &p_animation) { +void AnimationMixer::_create_track_num_to_track_cache_for_animation(const Ref &p_animation) { if (animation_track_num_to_track_cache.has(p_animation)) { // In AnimationMixer::_update_caches, it retrieves all animations via AnimationMixer::get_animation_list // Since multiple AnimationLibraries can share the same Animation, it is possible that the cache is already created. @@ -647,12 +660,15 @@ void AnimationMixer::_create_track_num_to_track_cache_for_animation(Ref sname_list; + LocalVector sname_list; get_animation_list(&sname_list); bool check_path = GLOBAL_GET_CACHED(bool, "animation/warnings/check_invalid_track_paths"); @@ -684,16 +700,18 @@ bool AnimationMixer::_update_caches() { reset_anim = get_animation(SceneStringName(RESET)); } for (const StringName &E : sname_list) { - Ref anim = get_animation(E); + const Ref &anim = get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { NodePath path = anim->track_get_path(i); + (void)path.hash(); // Make sure the cache is valid for faster comparison. + Animation::TypeHash thash = anim->track_get_type_hash(i); Animation::TrackType track_src_type = anim->track_get_type(i); Animation::TrackType track_cache_type = Animation::get_cache_type(track_src_type); TrackCache *track = nullptr; - if (track_cache.has(thash)) { - track = track_cache.get(thash); + if (TrackCache **p = track_cache.getptr(thash)) { + track = *p; } // If not valid, delete track. @@ -955,7 +973,7 @@ bool AnimationMixer::_update_caches() { } } - List to_delete; + LocalVector to_delete; for (const KeyValue &K : track_cache) { if (K.value->setup_pass != setup_pass) { @@ -963,11 +981,9 @@ bool AnimationMixer::_update_caches() { } } - while (to_delete.front()) { - Animation::TypeHash thash = to_delete.front()->get(); + for (const Animation::TypeHash &thash : to_delete) { memdelete(track_cache[thash]); track_cache.erase(thash); - to_delete.pop_front(); } track_map.clear(); @@ -982,9 +998,14 @@ bool AnimationMixer::_update_caches() { K.value->blend_idx = track_map[K.value->path]; } + track_map_version++; + if (track_map_version == 0) { + track_map_version = 1; + } + animation_track_num_to_track_cache.clear(); for (const StringName &E : sname_list) { - Ref anim = get_animation(E); + const Ref &anim = get_animation(E); _create_track_num_to_track_cache_for_animation(anim); } @@ -1149,14 +1170,30 @@ void AnimationMixer::blend_capture(double p_delta) { void AnimationMixer::_blend_calc_total_weight() { for (const AnimationInstance &ai : animation_instances) { - Ref a = ai.animation_data.animation; + const Ref &a = ai.animation_data.animation; real_t weight = ai.playback_info.weight; - const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr(); - int track_weights_count = ai.playback_info.track_weights.size(); - ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cache.has(a), "No animation in cache."); - LocalVector &track_num_to_track_cache = animation_track_num_to_track_cache[a]; - thread_local HashSet processed_hashes; - processed_hashes.clear(); + if (Math::is_zero_approx(weight)) { + continue; + } + + LocalVector *track_weights = ai.playback_info.track_weights; + + LocalVector *t_cache = animation_track_num_to_track_cache.getptr(a); + ERR_CONTINUE_EDMSG(!t_cache, "No animation in cache."); + LocalVector &track_num_to_track_cache = *t_cache; + + uint64_t pass_id = ++animation_instance_weight_pass_counter; + // Handle wrap (slower but rare). + if (unlikely(pass_id == 0)) { + for (KeyValue &kv : track_cache) { + if (kv.value) { + kv.value->animation_instance_weight_applied_at = 0; + } + } + animation_instance_weight_pass_counter = 1; + pass_id = 1; + } + const LocalVector &tracks = a->get_tracks(); Animation::Track *const *tracks_ptr = tracks.ptr(); int count = tracks.size(); @@ -1165,18 +1202,30 @@ void AnimationMixer::_blend_calc_total_weight() { if (!animation_track->enabled) { continue; } - Animation::TypeHash thash = animation_track->thash; TrackCache *track = track_num_to_track_cache[i]; - if (track == nullptr || processed_hashes.has(thash)) { + if (track == nullptr) { // No path, but avoid error spamming. - // Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly. continue; } + + // In some cases (e.g. TrackCacheTransform), + // multiple Animation::Tracks (e.g. TYPE_POSITION_3D, TYPE_ROTATION_3D and TYPE_SCALE_3D) + // can point to the same TrackCache instance. + // So we need to make sure that the weight is added only once per AnimationInstance. + if (track->animation_instance_weight_applied_at == pass_id) { + continue; + } + int blend_idx = track->blend_idx; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); - real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; + real_t blend; + if (track_weights && blend_idx < static_cast(track_weights->size())) { + blend = track_weights->operator[](blend_idx) * weight; + } else { + blend = weight; + } track->total_weight += blend; - processed_hashes.insert(thash); + track->animation_instance_weight_applied_at = pass_id; } } } @@ -1187,7 +1236,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); #endif // TOOLS_ENABLED for (const AnimationInstance &ai : animation_instances) { - Ref a = ai.animation_data.animation; + const Ref &a = ai.animation_data.animation; double time = ai.playback_info.time; double delta = ai.playback_info.delta; double start = ai.playback_info.start; @@ -1196,18 +1245,18 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag; bool is_external_seeking = ai.playback_info.is_external_seeking; real_t weight = ai.playback_info.weight; - const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr(); - int track_weights_count = ai.playback_info.track_weights.size(); bool backward = std::signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. bool seeked_backward = std::signbit(p_delta); #ifndef _3D_DISABLED bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED - ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cache.has(a), "No animation in cache."); - LocalVector &track_num_to_track_cache = animation_track_num_to_track_cache[a]; + LocalVector *t_cache = animation_track_num_to_track_cache.getptr(a); + ERR_CONTINUE_EDMSG(!t_cache, "No animation in cache."); + LocalVector &track_num_to_track_cache = *t_cache; + const LocalVector &tracks = a->get_tracks(); Animation::Track *const *tracks_ptr = tracks.ptr(); - real_t a_length = a->get_length(); + double a_length = a->get_length(); int count = tracks.size(); for (int i = 0; i < count; i++) { const Animation::Track *animation_track = tracks_ptr[i]; @@ -1220,7 +1269,14 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } int blend_idx = track->blend_idx; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); - real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; + real_t blend; + LocalVector *track_weights = ai.playback_info.track_weights; + if (track_weights && blend_idx < static_cast(track_weights->size())) { + blend = track_weights->operator[](blend_idx) * weight; + } else { + blend = weight; + } + if (!deterministic) { // If non-deterministic, do normalization. // It would be better to make this if statement outside the for loop, but come here since too much code... @@ -1231,13 +1287,16 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } Animation::TrackType ttype = animation_track->type; track->root_motion = root_motion_track == animation_track->path; + if (Math::is_zero_approx(blend) && ttype != Animation::TYPE_AUDIO) { + continue; // Nothing to blend. + } switch (ttype) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } TrackCacheTransform *t = static_cast(track); + if (seeked) { + t->reset_cursors(); + } if (track->root_motion && calc_root) { int rot_track = -1; if (root_motion_local) { @@ -1282,46 +1341,50 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Quaternion rot; if (!backward) { if (Animation::is_greater_approx(prev_time, time)) { - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0], backward, &t->position_cursor); if (err != OK) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, end, &loc[1]); + a->try_position_track_interpolate(i, end, &loc[1], backward, &t->position_cursor); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(rot_track, end, &rot); + a->try_rotation_track_interpolate(rot_track, end, &rot, backward, &t->rotation_cursor); rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx); root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend; prev_time = start; + t->position_cursor.reset(); + t->rotation_cursor.reset(); } } else { if (Animation::is_less_approx(prev_time, time)) { - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0], backward, &t->position_cursor); if (err != OK) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, start, &loc[1]); + a->try_position_track_interpolate(i, start, &loc[1], backward, &t->position_cursor); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(rot_track, start, &rot); + a->try_rotation_track_interpolate(rot_track, start, &rot, backward, &t->rotation_cursor); rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx); root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend; prev_time = end; + t->position_cursor.reset(); + t->rotation_cursor.reset(); } } - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0], backward, &t->position_cursor); if (err != OK) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, time, &loc[1]); + a->try_position_track_interpolate(i, time, &loc[1], backward, &t->position_cursor); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(rot_track, time, &rot); + a->try_rotation_track_interpolate(rot_track, time, &rot, backward, &t->rotation_cursor); rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx); root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend; @@ -1330,35 +1393,39 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Vector3 loc[2]; if (!backward) { if (Animation::is_greater_approx(prev_time, time)) { - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0], backward, &t->position_cursor); if (err != OK) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, end, &loc[1]); + a->try_position_track_interpolate(i, end, &loc[1], backward, &t->position_cursor); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = start; + t->position_cursor.reset(); + t->rotation_cursor.reset(); } } else { if (Animation::is_less_approx(prev_time, time)) { - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0], backward, &t->position_cursor); if (err != OK) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, start, &loc[1]); + a->try_position_track_interpolate(i, start, &loc[1], backward, &t->position_cursor); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = end; + t->position_cursor.reset(); + t->rotation_cursor.reset(); } } - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0], backward, &t->position_cursor); if (err != OK) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, time, &loc[1]); + a->try_position_track_interpolate(i, time, &loc[1], backward, &t->position_cursor); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = !backward ? start : end; @@ -1366,7 +1433,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } { Vector3 loc; - Error err = a->try_position_track_interpolate(i, time, &loc); + Error err = a->try_position_track_interpolate(i, time, &loc, backward, &t->position_cursor); if (err != OK) { continue; } @@ -1377,9 +1444,6 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } break; case Animation::TYPE_ROTATION_3D: { #ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } TrackCacheTransform *t = static_cast(track); if (track->root_motion && calc_root) { double prev_time = time - delta; @@ -1419,42 +1483,44 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Quaternion rot[2]; if (!backward) { if (Animation::is_greater_approx(prev_time, time)) { - Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); + Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0], backward, &t->rotation_cursor); if (err != OK) { continue; } rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(i, end, &rot[1]); + a->try_rotation_track_interpolate(i, end, &rot[1], backward, &t->rotation_cursor); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = Animation::interpolate_via_rest(root_motion_cache.rot, rot[1], blend, rot[0]); prev_time = start; + t->rotation_cursor.reset(); } } else { if (Animation::is_less_approx(prev_time, time)) { - Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); + Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0], backward, &t->rotation_cursor); if (err != OK) { continue; } rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(i, start, &rot[1]); + a->try_rotation_track_interpolate(i, start, &rot[1], backward, &t->rotation_cursor); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = Animation::interpolate_via_rest(root_motion_cache.rot, rot[1], blend, rot[0]); prev_time = end; + t->rotation_cursor.reset(); } } - Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); + Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0], backward, &t->rotation_cursor); if (err != OK) { continue; } rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(i, time, &rot[1]); + a->try_rotation_track_interpolate(i, time, &rot[1], backward, &t->rotation_cursor); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = Animation::interpolate_via_rest(root_motion_cache.rot, rot[1], blend, rot[0]); prev_time = !backward ? start : end; } { Quaternion rot; - Error err = a->try_rotation_track_interpolate(i, time, &rot); + Error err = a->try_rotation_track_interpolate(i, time, &rot, backward, &t->rotation_cursor); if (err != OK) { continue; } @@ -1465,9 +1531,6 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } break; case Animation::TYPE_SCALE_3D: { #ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } TrackCacheTransform *t = static_cast(track); if (track->root_motion && calc_root) { double prev_time = time - delta; @@ -1507,42 +1570,44 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Vector3 scale[2]; if (!backward) { if (Animation::is_greater_approx(prev_time, time)) { - Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); + Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0], backward, &t->scale_cursor); if (err != OK) { continue; } scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx); - a->try_scale_track_interpolate(i, end, &scale[1]); + a->try_scale_track_interpolate(i, end, &scale[1], backward, &t->scale_cursor); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; prev_time = start; + t->scale_cursor.reset(); } } else { if (Animation::is_less_approx(prev_time, time)) { - Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); + Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0], backward, &t->scale_cursor); if (err != OK) { continue; } scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx); - a->try_scale_track_interpolate(i, start, &scale[1]); + a->try_scale_track_interpolate(i, start, &scale[1], backward, &t->scale_cursor); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; prev_time = end; + t->scale_cursor.reset(); } } - Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); + Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0], backward, &t->scale_cursor); if (err != OK) { continue; } scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx); - a->try_scale_track_interpolate(i, time, &scale[1]); + a->try_scale_track_interpolate(i, time, &scale[1], backward, &t->scale_cursor); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; prev_time = !backward ? start : end; } { Vector3 scale; - Error err = a->try_scale_track_interpolate(i, time, &scale); + Error err = a->try_scale_track_interpolate(i, time, &scale, backward, &t->scale_cursor); if (err != OK) { continue; } @@ -1553,9 +1618,6 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } break; case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } TrackCacheBlendShape *t = static_cast(track); float value; Error err = a->try_blend_shape_track_interpolate(i, time, &value); @@ -1569,9 +1631,6 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } break; case Animation::TYPE_BEZIER: case Animation::TYPE_VALUE: { - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } TrackCacheValue *t = static_cast(track); bool is_value = ttype == Animation::TYPE_VALUE; bool is_discrete = is_value && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; @@ -1632,7 +1691,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { t_obj->set_indexed(t->subpath, value); } } else { - List indices; + LocalVector indices; a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { t->use_discrete = true; @@ -1652,7 +1711,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } #endif // TOOLS_ENABLED - if (p_update_only || Math::is_zero_approx(blend)) { + if (p_update_only) { continue; } TrackCacheMethod *t = static_cast(track); @@ -1665,7 +1724,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Vector params = a->method_track_get_params(i, idx); _call_object(t->object_id, method, params, callback_mode_method == ANIMATION_CALLBACK_MODE_METHOD_DEFERRED); } else { - List indices; + LocalVector indices; a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { StringName method = a->method_track_get_name(i, F); @@ -1713,10 +1772,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { map.erase(idx); } } else { - List to_play; + LocalVector to_play; a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); if (to_play.size()) { - idx = to_play.back()->get(); + idx = to_play[to_play.size() - 1]; } } if (idx < 0) { @@ -1775,9 +1834,6 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } break; case Animation::TYPE_ANIMATION: { - if (Math::is_zero_approx(blend)) { - continue; - } TrackCacheAnimation *t = static_cast(track); Object *t_obj = ObjectDB::get_instance(t->object_id); if (!t_obj) { @@ -1796,10 +1852,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } double pos = a->track_get_key_time(i, idx); StringName anim_name = a->animation_track_get_key_animation(i, idx); - if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { + const Ref &anim = player2->get_animation_or_null(anim_name); + if (anim_name == SNAME("[stop]") || anim.is_null()) { continue; } - Ref anim = player2->get_animation(anim_name); double at_anim_pos = start; switch (anim->get_loop_mode()) { case Animation::LOOP_NONE: { @@ -1828,12 +1884,12 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } else { // Find stuff to play. - List to_play; + LocalVector to_play; a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); if (to_play.size()) { - int idx = to_play.back()->get(); + int idx = to_play[to_play.size() - 1]; StringName anim_name = a->animation_track_get_key_animation(i, idx); - if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { + if (anim_name == SNAME("[stop]") || !player2->has_animation(anim_name)) { if (playing_caches.has(t)) { playing_caches.erase(t); player2->stop(); @@ -1878,29 +1934,39 @@ void AnimationMixer::_blend_apply() { if (!t_skeleton) { return; } - if (t->loc_used) { - t_skeleton->set_bone_pose_position(t->bone_idx, t->loc); - } - if (t->rot_used) { - t_skeleton->set_bone_pose_rotation(t->bone_idx, t->rot); - } - if (t->scale_used) { - t_skeleton->set_bone_pose_scale(t->bone_idx, t->scale); - } + if (t->loc_used && t->rot_used && t->scale_used) { + t_skeleton->set_bone_pose_components(t->bone_idx, t->loc, t->rot, t->scale); + } else { + if (t->loc_used) { + t_skeleton->set_bone_pose_position(t->bone_idx, t->loc); + } + if (t->rot_used) { + t_skeleton->set_bone_pose_rotation(t->bone_idx, t->rot); + } + if (t->scale_used) { + t_skeleton->set_bone_pose_scale(t->bone_idx, t->scale); + } + } } else if (!t->skeleton_id.is_valid()) { Node3D *t_node_3d = ObjectDB::get_instance(t->object_id); if (!t_node_3d) { return; } - if (t->loc_used) { - t_node_3d->set_position(t->loc); - } - if (t->rot_used) { - t_node_3d->set_rotation(t->rot.get_euler()); - } - if (t->scale_used) { - t_node_3d->set_scale(t->scale); + + if (t->loc_used && t->rot_used && t->scale_used) { + Transform3D transform = Transform3D(Basis(t->rot).scaled(t->scale), t->loc); + t_node_3d->set_transform(transform); + } else { + if (t->loc_used) { + t_node_3d->set_position(t->loc); + } + if (t->rot_used) { + t_node_3d->set_rotation(t->rot.get_euler()); + } + if (t->scale_used) { + t_node_3d->set_scale(t->scale); + } } } #endif // _3D_DISABLED @@ -2039,19 +2105,19 @@ void AnimationMixer::_call_object(ObjectID p_object_id, const StringName &p_meth } } -void AnimationMixer::make_animation_instance(const StringName &p_name, const PlaybackInfo p_playback_info) { - ERR_FAIL_COND(!has_animation(p_name)); - +void AnimationMixer::make_animation_instance(const StringName &p_name, const PlaybackInfo &p_playback_info) { + const Ref &animation = get_animation_or_null(p_name); + ERR_FAIL_COND(animation.is_null()); AnimationData ad; ad.name = p_name; ad.animation = get_animation(p_name); ad.animation_library = find_animation_library(ad.animation); AnimationInstance ai; - ai.animation_data = ad; + ai.animation_data = std::move(ad); ai.playback_info = p_playback_info; - animation_instances.push_back(ai); + animation_instances.push_back(std::move(ai)); } void AnimationMixer::clear_animation_instances() { @@ -2292,9 +2358,9 @@ Ref AnimationMixer::apply_reset(bool p_user_initiated) { void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { ERR_FAIL_COND(!active); - ERR_FAIL_COND(!has_animation(p_name)); + const Ref &reference_animation = get_animation_or_null(p_name); + ERR_FAIL_COND(reference_animation.is_null()); ERR_FAIL_COND(p_duration <= 0); - Ref reference_animation = get_animation(p_name); if (!cache_valid) { _update_caches(); // Need to retrieve object id. @@ -2376,13 +2442,11 @@ void AnimationMixer::get_argument_options(const StringName &p_function, int p_id const String pf = p_function; if (p_idx == 0) { if (pf == "get_animation" || pf == "has_animation") { - List al; - get_animation_list(&al); - for (const StringName &name : al) { + for (const StringName &name : get_sorted_animation_list()) { r_options->push_back(String(name).quote()); } } else if (pf == "get_animation_library" || pf == "has_animation_library" || pf == "remove_animation_library" || pf == "rename_animation_library") { - List al; + LocalVector al; get_animation_library_list(&al); for (const StringName &name : al) { r_options->push_back(String(name).quote()); @@ -2501,7 +2565,7 @@ AnimationMixer::AnimationMixer() { AnimationMixer::~AnimationMixer() { } -void AnimatedValuesBackup::set_data(const AHashMap p_data) { +void AnimatedValuesBackup::set_data(const AHashMap &p_data) { clear_data(); for (const KeyValue &E : p_data) { diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 7b918995f1ef..3d742fbef098 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -76,7 +76,7 @@ class AnimationMixer : public Node { }; struct AnimationData { - String name; + StringName name; Ref animation; StringName animation_library; uint64_t last_update = 0; @@ -91,7 +91,7 @@ class AnimationMixer : public Node { bool is_external_seeking = false; Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; real_t weight = 0.0; - Vector track_weights; + LocalVector *track_weights = nullptr; }; struct AnimationInstance { @@ -105,13 +105,13 @@ class AnimationMixer : public Node { AHashMap animation_set; // HashMap TypedArray _get_animation_library_list() const; + // TODO: This needs to be a TypedArray see this PR for rationale https://github.com/godotengine/godot/pull/110767/ Vector _get_animation_list() const { - List animations; - get_animation_list(&animations); Vector ret; - while (animations.size()) { - ret.push_back(animations.front()->get()); - animations.pop_front(); + LocalVector animations = get_sorted_animation_list(); + ret.resize(animations.size()); + for (uint32_t i = 0; i < animations.size(); ++i) { + ret.write[i] = animations[i]; } return ret; } @@ -141,7 +141,6 @@ class AnimationMixer : public Node { /* ---- Caches for blending ---- */ bool cache_valid = false; uint64_t setup_pass = 1; - uint64_t process_pass = 1; struct TrackCache { bool root_motion = false; @@ -151,6 +150,7 @@ class AnimationMixer : public Node { int blend_idx = -1; ObjectID object_id; real_t total_weight = 0.0; + uint64_t animation_instance_weight_applied_at = 0; TrackCache() = default; TrackCache(const TrackCache &p_other) : @@ -158,12 +158,16 @@ class AnimationMixer : public Node { setup_pass(p_other.setup_pass), type(p_other.type), object_id(p_other.object_id), - total_weight(p_other.total_weight) {} + total_weight(p_other.total_weight), + animation_instance_weight_applied_at(p_other.animation_instance_weight_applied_at) {} virtual ~TrackCache() {} }; struct TrackCacheTransform : public TrackCache { + Animation::KeyCursor position_cursor = Animation::KeyCursor(); + Animation::KeyCursor rotation_cursor = Animation::KeyCursor(); + Animation::KeyCursor scale_cursor = Animation::KeyCursor(); #ifndef _3D_DISABLED ObjectID skeleton_id; #endif // _3D_DISABLED @@ -178,8 +182,17 @@ class AnimationMixer : public Node { Quaternion rot; Vector3 scale; + _ALWAYS_INLINE_ void reset_cursors() { + position_cursor.reset(); + rotation_cursor.reset(); + scale_cursor.reset(); + } + TrackCacheTransform(const TrackCacheTransform &p_other) : TrackCache(p_other), + position_cursor(p_other.position_cursor), + rotation_cursor(p_other.rotation_cursor), + scale_cursor(p_other.scale_cursor), #ifndef _3D_DISABLED skeleton_id(p_other.skeleton_id), #endif @@ -315,14 +328,16 @@ class AnimationMixer : public Node { void _clear_playing_caches(); void _init_root_motion_cache(); bool _update_caches(); - void _create_track_num_to_track_cache_for_animation(Ref &p_animation); + void _create_track_num_to_track_cache_for_animation(const Ref &p_animation); /* ---- Audio ---- */ AudioServer::PlaybackType playback_type; /* ---- Blending processor ---- */ LocalVector animation_instances; + uint64_t animation_instance_weight_pass_counter = 0; AHashMap track_map; + uint64_t track_map_version = 1; int track_count = 0; bool deterministic = false; @@ -401,17 +416,19 @@ class AnimationMixer : public Node { /* ---- Data lists ---- */ Dictionary *get_animation_libraries(); - void get_animation_library_list(List *p_animations) const; + void get_animation_library_list(LocalVector *p_animations) const; Ref get_animation_library(const StringName &p_name) const; bool has_animation_library(const StringName &p_name) const; StringName get_animation_library_name(const Ref &p_animation_library) const; - StringName find_animation_library(const Ref &p_animation) const; + const StringName &find_animation_library(const Ref &p_animation) const; Error add_animation_library(const StringName &p_name, const Ref &p_animation_library); void remove_animation_library(const StringName &p_name); void rename_animation_library(const StringName &p_name, const StringName &p_new_name); - void get_animation_list(List *p_animations) const; - Ref get_animation(const StringName &p_name) const; + LocalVector get_sorted_animation_list() const; + void get_animation_list(LocalVector *p_animations) const; + const Ref &get_animation(const StringName &p_name) const; + const Ref &get_animation_or_null(const StringName &p_name) const; bool has_animation(const StringName &p_name) const; StringName find_animation(const Ref &p_animation) const; @@ -454,7 +471,7 @@ class AnimationMixer : public Node { Vector3 get_root_motion_scale_accumulator() const; /* ---- Blending processor ---- */ - void make_animation_instance(const StringName &p_name, const PlaybackInfo p_playback_info); + void make_animation_instance(const StringName &p_name, const PlaybackInfo &p_playback_info); void clear_animation_instances(); virtual void advance(double p_time); virtual void clear_caches(); // Must be called by hand if an animation was modified after added. @@ -491,7 +508,7 @@ class AnimatedValuesBackup : public RefCounted { AHashMap data; public: - void set_data(const AHashMap p_data); + void set_data(const AHashMap &p_data); AHashMap get_data() const; void clear_data(); diff --git a/scene/animation/animation_node_extension.cpp b/scene/animation/animation_node_extension.cpp index 3d5a63678722..7460ff0994ab 100644 --- a/scene/animation/animation_node_extension.cpp +++ b/scene/animation/animation_node_extension.cpp @@ -30,7 +30,7 @@ #include "animation_node_extension.h" -AnimationNode::NodeTimeInfo AnimationNodeExtension::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeExtension::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { PackedFloat32Array r_ret; GDVIRTUAL_CALL( diff --git a/scene/animation/animation_node_extension.h b/scene/animation/animation_node_extension.h index 71adcc5ba425..82690d7b9c35 100644 --- a/scene/animation/animation_node_extension.h +++ b/scene/animation/animation_node_extension.h @@ -36,7 +36,7 @@ class AnimationNodeExtension : public AnimationNode { GDCLASS(AnimationNodeExtension, AnimationNode); public: - 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; static bool is_looping(const PackedFloat32Array &p_node_info); static double get_remaining_time(const PackedFloat32Array &p_node_info, bool p_break_loop = false); diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index e5df4b0b3d10..82a6b218fa8a 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -192,7 +192,7 @@ AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { //////////////////////////////////////////////////////// -void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state) { +void AnimationNodeStateMachinePlayback::_set_current(AnimationNode::ProcessState &p_process_state, AnimationNodeStateMachine *p_state_machine, const StringName &p_state) { current = p_state; if (current == StringName()) { group_start_transition = Ref(); @@ -200,7 +200,7 @@ void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine * return; } - AnimationTree *tree = p_state_machine->process_state ? p_state_machine->process_state->tree : nullptr; + AnimationTree *tree = p_process_state.tree; Ref anodesm = p_state_machine->find_node_by_path(current); if (anodesm.is_null()) { group_start_transition = Ref(); @@ -354,9 +354,9 @@ bool _is_grouped_state_machine(const Ref p_node) { return p_node.is_valid() && p_node->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED; } -void AnimationNodeStateMachinePlayback::_clear_fading(AnimationNodeStateMachine *p_state_machine, const StringName &p_state) { +void AnimationNodeStateMachinePlayback::_clear_fading(AnimationNode::ProcessState &p_process_state, AnimationNodeStateMachine *p_state_machine, const StringName &p_state) { if (!p_state.is_empty() && !_is_grouped_state_machine(p_state_machine->get_node(p_state))) { - _signal_state_change(p_state_machine->get_animation_tree(), p_state, false); + _signal_state_change(p_process_state.tree, p_state, false); } fading_from = StringName(); fadeing_from_nti = AnimationNode::NodeTimeInfo(); @@ -373,8 +373,8 @@ void AnimationNodeStateMachinePlayback::_signal_state_change(AnimationTree *p_an emit_signal(p_started ? SceneStringName(state_started) : SceneStringName(state_finished), p_state); } -void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { - List child_nodes; +void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { + LocalVector child_nodes; p_state_machine->get_child_nodes(&child_nodes); for (const AnimationNode::ChildNode &child_node : child_nodes) { Ref anodesm = child_node.node; @@ -386,9 +386,9 @@ void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tr playback = playback->duplicate(); } playback->path.clear(); - playback->_clear_path_children(p_tree, anodesm.ptr(), p_test_only); + playback->_clear_path_children(p_process_state, p_tree, anodesm.ptr(), p_test_only); if (current != child_node.name) { - playback->_start(anodesm.ptr()); // Can restart. + playback->_start(p_process_state, anodesm.ptr()); // Can restart. } } } @@ -421,7 +421,7 @@ void AnimationNodeStateMachinePlayback::_start_children(AnimationTree *p_tree, A } } -bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only) { +bool AnimationNodeStateMachinePlayback::_travel_children(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only) { if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { return false; // This function must be fired only by the top state machine, do nothing in child state machine. } @@ -448,7 +448,7 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, playback = playback->duplicate(); } if (!playback->is_playing()) { - playback->_start(anodesm.ptr()); + playback->_start(p_process_state, anodesm.ptr()); } ChildStateMachineInfo child_info; child_info.playback = playback; @@ -471,18 +471,18 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, child_playback = child_playback->duplicate(); } child_playback->_travel_main(SceneStringName(End)); - child_found_route &= child_playback->_travel(p_tree, child_anodesm.ptr(), false, p_test_only); + child_found_route &= child_playback->_travel(p_process_state, p_tree, child_anodesm.ptr(), false, p_test_only); child_path += "/" + child_playback->get_current_node(); } // Force restart target state machine. - playback->_start(anodesm.ptr()); + playback->_start(p_process_state, anodesm.ptr()); } is_parent_same_state = is_current_same_state; bool is_deepest_state = i == temp_path.size() - 1; child_info.is_reset = is_deepest_state ? reset_request_on_teleport : false; playback->_travel_main(temp_path[i], child_info.is_reset); - if (playback->_make_travel_path(p_tree, anodesm.ptr(), is_deepest_state ? p_is_allow_transition_to_self : false, child_info.path, p_test_only)) { + if (playback->_make_travel_path(p_process_state, p_tree, anodesm.ptr(), is_deepest_state ? p_is_allow_transition_to_self : false, child_info.path, p_test_only)) { found_route &= child_found_route; } else { child_info.path.push_back(temp_path[i]); @@ -511,16 +511,16 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, return found_route; } -void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_state_machine) { +void AnimationNodeStateMachinePlayback::_start(AnimationNode::ProcessState &p_process_state, AnimationNodeStateMachine *p_state_machine) { playing = true; - _set_current(p_state_machine, start_request != StringName() ? start_request : SceneStringName(Start)); + _set_current(p_process_state, p_state_machine, start_request != StringName() ? start_request : SceneStringName(Start)); teleport_request = true; stop_request = false; start_request = StringName(); } -bool AnimationNodeStateMachinePlayback::_travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only) { - return _make_travel_path(p_tree, p_state_machine, p_is_allow_transition_to_self, path, p_test_only); +bool AnimationNodeStateMachinePlayback::_travel(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only) { + return _make_travel_path(p_process_state, p_tree, p_state_machine, p_is_allow_transition_to_self, path, p_test_only); } String AnimationNodeStateMachinePlayback::_validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path) { @@ -541,12 +541,12 @@ String AnimationNodeStateMachinePlayback::_validate_path(AnimationNodeStateMachi return target; } -bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only) { +bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only) { StringName travel = travel_request; travel_request = StringName(); if (!playing) { - _start(p_state_machine); + _start(p_process_state, p_state_machine); } ERR_FAIL_COND_V(!p_state_machine->states.has(travel), false); @@ -678,13 +678,13 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, playback = playback->duplicate(); } if (i > 0) { - playback->_start(anodesm.ptr()); + playback->_start(p_process_state, anodesm.ptr()); } if (i >= new_path.size()) { break; // Tracing has been finished, needs to break. } playback->_travel_main(SceneStringName(End)); - if (!playback->_travel(p_tree, anodesm.ptr(), false, p_test_only)) { + if (!playback->_travel(p_process_state, p_tree, anodesm.ptr(), false, p_test_only)) { found_route = false; break; } @@ -705,8 +705,8 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, } } -AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - AnimationNode::NodeTimeInfo nti = _process(p_state_machine, p_playback_info, p_test_only); +AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + AnimationNode::NodeTimeInfo nti = _process(p_process_state, p_instance, p_state_machine, p_playback_info, p_test_only); start_request = StringName(); next_request = false; stop_request = false; @@ -714,8 +714,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(Animation return nti; } -AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - AnimationTree *tree = p_state_machine->process_state->tree; +AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + AnimationTree *tree = p_process_state.tree; double p_time = p_playback_info.time; double p_delta = p_playback_info.delta; @@ -728,9 +728,9 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio // Restart state machine. if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { path.clear(); - _clear_path_children(tree, p_state_machine, p_test_only); + _clear_path_children(p_process_state, tree, p_state_machine, p_test_only); } - _start(p_state_machine); + _start(p_process_state, p_state_machine); reset_request = true; } else { // Reset current state. @@ -754,7 +754,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio // Process start/travel request. if (start_request != StringName() || travel_request != StringName()) { if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { - _clear_path_children(tree, p_state_machine, p_test_only); + _clear_path_children(p_process_state, tree, p_state_machine, p_test_only); } } @@ -768,7 +768,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio } // Teleport to start. if (p_state_machine->states.has(start_request)) { - _start(p_state_machine); + _start(p_process_state, p_state_machine); } else { StringName node = start_request; ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'"); @@ -783,10 +783,10 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio StringName temp_travel_request = travel_request; // For the case that can't travel. // Process children. Vector new_path; - bool can_travel = _make_travel_path(tree, p_state_machine, travel_path.size() <= 1 ? p_state_machine->is_allow_transition_to_self() : false, new_path, p_test_only); + bool can_travel = _make_travel_path(p_process_state, tree, p_state_machine, travel_path.size() <= 1 ? p_state_machine->is_allow_transition_to_self() : false, new_path, p_test_only); if (travel_path.size()) { if (can_travel) { - can_travel = _travel_children(tree, p_state_machine, travel_target, p_state_machine->is_allow_transition_to_self(), travel_path[0] == current, p_test_only); + can_travel = _travel_children(p_process_state, tree, p_state_machine, travel_target, p_state_machine->is_allow_transition_to_self(), travel_path[0] == current, p_test_only); } else { _start_children(tree, p_state_machine, travel_target, p_test_only); } @@ -800,7 +800,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio if (p_state_machine->states.has(temp_travel_request)) { path.clear(); if (current != temp_travel_request || reset_request_on_teleport) { - _set_current(p_state_machine, temp_travel_request); + _set_current(p_process_state, p_state_machine, temp_travel_request); reset_request = reset_request_on_teleport; teleport_request = true; } @@ -823,15 +823,16 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio pi.seeked = true; pi.is_external_seeking = false; pi.weight = 0; - current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); + AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(current); + current_nti = p_state_machine->blend_node(p_process_state, p_instance, other_instance, pi, AnimationNode::FILTER_IGNORE, true, true); // Don't process first node if not necessary, instead process next node. - _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); + _transition_to_next_recursive(p_process_state, p_instance, tree, p_state_machine, p_delta, p_test_only); } // Check current node existence. if (!p_state_machine->states.has(current)) { playing = false; // Current does not exist. - _set_current(p_state_machine, StringName()); + _set_current(p_process_state, p_state_machine, StringName()); return AnimationNode::NodeTimeInfo(); } @@ -873,7 +874,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio pi.time = 0; pi.seeked = true; } - current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(current); + current_nti = p_state_machine->blend_node(p_process_state, p_instance, other_instance, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. // Cross-fade process. if (fading_from != StringName()) { @@ -892,16 +894,17 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio pi.time = 0; pi.seeked = true; } - fadeing_from_nti = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + AnimationNodeInstance *fading_from_instance = p_instance.get_child_instance_by_path_or_null(fading_from); + fadeing_from_nti = p_state_machine->blend_node(p_process_state, p_instance, fading_from_instance, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (Animation::is_greater_or_equal_approx(fading_pos, fading_time)) { // Finish fading. - _clear_fading(p_state_machine, fading_from); + _clear_fading(p_process_state, p_state_machine, fading_from); } } // Find next and see when to transition. - bool will_end = _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only) || current == SceneStringName(End); + bool will_end = _transition_to_next_recursive(p_process_state, p_instance, tree, p_state_machine, p_delta, p_test_only) || current == SceneStringName(End); // Predict remaining time. if (will_end || ((p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) && !p_state_machine->has_transition_from(current))) { @@ -919,7 +922,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(Animatio return current_nti; } -bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, double p_delta, bool p_test_only) { +bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, double p_delta, bool p_test_only) { _reset_request_for_fading_from = false; AnimationMixer::PlaybackInfo pi; @@ -928,9 +931,9 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT Vector transition_path; transition_path.push_back(current); while (true) { - next = _find_next(p_tree, p_state_machine); + next = _find_next(p_process_state, p_instance, p_tree, p_state_machine); - if (!_can_transition_to_next(p_tree, p_state_machine, next, p_test_only)) { + if (!_can_transition_to_next(p_process_state, p_tree, p_state_machine, next, p_test_only)) { break; // Finish transition. } @@ -954,9 +957,10 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT pi.seeked = true; pi.is_external_seeking = false; pi.weight = 0; - p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); + AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(current); + p_state_machine->blend_node(p_process_state, p_instance, other_instance, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); } - _clear_fading(p_state_machine, current); + _clear_fading(p_process_state, p_state_machine, current); fading_time = 0; fading_pos = 0; } @@ -967,7 +971,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT } // Update current status. - _set_current(p_state_machine, next.node); + _set_current(p_process_state, p_state_machine, next.node); current_curve = next.curve; if (current == SceneStringName(End)) { @@ -979,12 +983,13 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT fadeing_from_nti = current_nti; + AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(current); if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { pi.time = current_nti.position; pi.seeked = true; pi.is_external_seeking = false; pi.weight = 0; - p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); + p_state_machine->blend_node(p_process_state, p_instance, other_instance, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); } // Just get length to find next recursive. @@ -992,7 +997,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT pi.is_external_seeking = false; pi.weight = 0; pi.seeked = next.is_reset; - current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. + current_nti = p_state_machine->blend_node(p_process_state, p_instance, other_instance, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. // Fading must be processed. if (fading_time) { @@ -1003,7 +1008,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT return next.node == SceneStringName(End); } -bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only) { +bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only) { if (p_next.node == StringName()) { return false; } @@ -1022,7 +1027,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p } playback->_next_main(); // Then, fading should end. - _clear_fading(p_state_machine, fading_from); + _clear_fading(p_process_state, p_state_machine, fading_from); fading_pos = 0; } else { return true; @@ -1063,7 +1068,7 @@ Ref AnimationNodeStateMachinePlayback::_che return p_transition.transition; } -AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_find_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const { +AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_find_next(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const { NextInfo next; if (path.size()) { for (int i = 0; i < p_state_machine->transitions.size(); i++) { @@ -1092,7 +1097,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ if (ref_transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { continue; } - if (p_state_machine->transitions[i].from == current && (_check_advance_condition(anodesm, ref_transition) || bypass)) { + if (p_state_machine->transitions[i].from == current && (_check_advance_condition(p_process_state, p_instance, anodesm, ref_transition) || bypass)) { if (ref_transition->get_priority() <= priority_best) { priority_best = ref_transition->get_priority(); auto_advance_to = i; @@ -1116,19 +1121,21 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ return next; } -bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref state_machine, const Ref transition) const { +bool AnimationNodeStateMachinePlayback::_check_advance_condition(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, const Ref state_machine, const Ref transition) const { if (transition->get_advance_mode() != AnimationNodeStateMachineTransition::ADVANCE_MODE_AUTO) { return false; } StringName advance_condition_name = transition->get_advance_condition_name(); - if (advance_condition_name != StringName() && !bool(state_machine->get_parameter(advance_condition_name))) { + ERR_FAIL_COND_V(p_instance.resource != state_machine, false); + + if (advance_condition_name != StringName() && !bool(p_instance.get_parameter(advance_condition_name))) { return false; } if (transition->expression.is_valid()) { - AnimationTree *tree_base = state_machine->get_animation_tree(); + AnimationTree *tree_base = p_process_state.tree; ERR_FAIL_NULL_V(tree_base, false); NodePath advance_expression_base_node_path = tree_base->get_advance_expression_base_node(); @@ -1236,7 +1243,7 @@ AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() { /////////////////////////////////////////////////////// -void AnimationNodeStateMachine::get_parameter_list(List *r_list) const { +void AnimationNodeStateMachine::get_parameter_list(LocalVector *r_list) const { AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); // Don't store this object in .tres, it always needs to be made as unique object. List advance_conditions; @@ -1290,12 +1297,9 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Refconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), CONNECT_REFERENCE_COUNTED); - p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); - p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed), CONNECT_REFERENCE_COUNTED); + _tree_changed(); } void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref p_node) { @@ -1306,26 +1310,21 @@ void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref node = states[p_name].node; if (node.is_valid()) { - node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); - node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed)); - node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed)); + _remove_node(node); } } states[p_name].node = p_node; + _add_node(p_node); emit_changed(); - emit_signal(SNAME("tree_changed")); - - p_node->connect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), CONNECT_REFERENCE_COUNTED); - p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); - p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed), CONNECT_REFERENCE_COUNTED); + _tree_changed(); } void AnimationNodeStateMachine::set_state_machine_type(StateMachineType p_state_machine_type) { state_machine_type = p_state_machine_type; emit_changed(); - emit_signal(SNAME("tree_changed")); + _tree_changed(); notify_property_list_changed(); } @@ -1374,7 +1373,7 @@ StringName AnimationNodeStateMachine::get_node_name(const Ref &p_ ERR_FAIL_V(StringName()); } -void AnimationNodeStateMachine::get_child_nodes(List *r_child_nodes) { +void AnimationNodeStateMachine::get_child_nodes(LocalVector *r_child_nodes) { Vector nodes; for (const KeyValue &E : states) { @@ -1412,9 +1411,7 @@ void AnimationNodeStateMachine::remove_node(const StringName &p_name) { { Ref node = states[p_name].node; ERR_FAIL_COND(node.is_null()); - node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); - node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_renamed)); - node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed)); + _remove_node(node); } states.erase(p_name); @@ -1635,16 +1632,17 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { return graph_offset; } -AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - Ref playback_new = get_parameter(playback); +AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + Ref playback_new = p_instance.get_parameter(playback); ERR_FAIL_COND_V(playback_new.is_null(), AnimationNode::NodeTimeInfo()); - playback_new->_set_base_path(node_state.get_base_path()); + + playback_new->_set_base_path(p_instance.path); playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED); if (p_test_only) { playback_new = playback_new->duplicate(); // Don't process original when testing. } - return playback_new->process(this, p_playback_info, p_test_only); + return playback_new->process(p_process_state, p_instance, this, p_playback_info, p_test_only); } String AnimationNodeStateMachine::get_caption() const { @@ -1777,7 +1775,7 @@ void AnimationNodeStateMachine::reset_state() { states[SceneStringName(End)] = end; emit_changed(); - emit_signal(SNAME("tree_changed")); + _tree_changed(); } void AnimationNodeStateMachine::set_node_position(const StringName &p_name, const Vector2 &p_position) { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index d62f1ce8947d..c8ecb56984e5 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -164,7 +164,7 @@ class AnimationNodeStateMachine : public AnimationRootNode { virtual void reset_state() override; public: - virtual void get_parameter_list(List *r_list) 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 bool is_parameter_read_only(const StringName &p_parameter) const override; @@ -181,7 +181,7 @@ class AnimationNodeStateMachine : public AnimationRootNode { void set_node_position(const StringName &p_name, const Vector2 &p_position); Vector2 get_node_position(const StringName &p_name) const; - virtual void get_child_nodes(List *r_child_nodes) override; + virtual void get_child_nodes(LocalVector *r_child_nodes) override; bool has_transition(const StringName &p_from, const StringName &p_to) const; bool has_transition_from(const StringName &p_from) const; @@ -212,7 +212,7 @@ class AnimationNodeStateMachine : public AnimationRootNode { void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() 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; virtual Ref get_child_by_name(const StringName &p_name) const override; @@ -289,32 +289,32 @@ class AnimationNodeStateMachinePlayback : public Resource { bool is_grouped = false; - void _clear_fading(AnimationNodeStateMachine *p_state_machine, const StringName &p_state); + void _clear_fading(AnimationNode::ProcessState &p_process_state, AnimationNodeStateMachine *p_state_machine, const StringName &p_state); void _signal_state_change(AnimationTree *p_animation_tree, const StringName &p_state, bool p_started); void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true); void _start_main(const StringName &p_state, bool p_reset = true); void _next_main(); void _stop_main(); - bool _make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only); + bool _make_travel_path(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only); String _validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path); - bool _travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only); - void _start(AnimationNodeStateMachine *p_state_machine); + bool _travel(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only); + void _start(AnimationNode::ProcessState &p_process_state, AnimationNodeStateMachine *p_state_machine); - void _clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); - bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only); + void _clear_path_children(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); + bool _travel_children(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only); void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only); - AnimationNode::NodeTimeInfo process(AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); - AnimationNode::NodeTimeInfo _process(AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); + AnimationNode::NodeTimeInfo process(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); + AnimationNode::NodeTimeInfo _process(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); - bool _check_advance_condition(const Ref p_state_machine, const Ref p_transition) const; - bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, double p_delta, bool p_test_only); - NextInfo _find_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const; + bool _check_advance_condition(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, const Ref p_state_machine, const Ref p_transition) const; + bool _transition_to_next_recursive(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, double p_delta, bool p_test_only); + NextInfo _find_next(AnimationNode::ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const; Ref _check_group_transition(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const AnimationNodeStateMachine::Transition &p_transition, Ref &r_state_machine, bool &r_bypass) const; - bool _can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only); + bool _can_transition_to_next(AnimationNode::ProcessState &p_process_state, AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only); - void _set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state); + void _set_current(AnimationNode::ProcessState &p_process_state, AnimationNodeStateMachine *p_state_machine, const StringName &p_state); void _set_grouped(bool p_is_grouped); void _set_base_path(const String &p_base_path); Ref _get_parent_playback(AnimationTree *p_tree) const; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 2aaed507bede..31fdb785198e 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -282,20 +282,20 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) { } return; } - List::Element *> to_erase; - for (List::Element *E = c.blend.front(); E; E = E->next()) { - Blend &b = E->get(); + LocalVector to_erase; + for (uint32_t i = 0; i < c.blend.size(); i++) { + Blend &b = c.blend[i]; b.blend_left = MAX(0, b.blend_left - Math::abs(speed_scale * p_delta) / b.blend_time); if (Animation::is_less_or_equal_approx(b.blend_left, 0)) { - to_erase.push_back(E); + to_erase.push_back(i); b.blend_left = CMP_EPSILON; // May want to play last frame. } // Note: There may be issues if an animation event triggers an animation change while this blend is active, // so it is best to use "deferred" calls instead of "immediate" for animation events that can trigger new animations. _process_playback_data(b.data, p_delta, b.blend_left, false, false, false); } - for (List::Element *&E : to_erase) { - c.blend.erase(E); + for (int i = to_erase.size() - 1; i >= 0; i--) { + c.blend.remove_at(to_erase[i]); } } @@ -338,9 +338,9 @@ void AnimationPlayer::_blend_post_process() { if (!finished_anim.is_empty()) { emit_signal(SceneStringName(animation_finished), finished_anim); } - String old = playback.assigned; + const StringName old = playback.assigned; play(playback_queue.front()->get()); - String new_name = playback.assigned; + const StringName &new_name = playback.assigned; playback_queue.pop_front(); if (end_notify) { emit_signal(SceneStringName(animation_changed), old, new_name); @@ -419,9 +419,10 @@ void AnimationPlayer::play_section_with_markers(const StringName &p_name, const name = playback.assigned; } - ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); + AnimationData *ad = animation_set.getptr(name); + ERR_FAIL_NULL_MSG(ad, vformat("Animation not found: %s.", name)); - Ref animation = animation_set[name].animation; + const Ref &animation = ad->animation; ERR_FAIL_COND_MSG(p_start_marker == p_end_marker && p_start_marker, vformat("Start marker and end marker cannot be the same marker: %s.", p_start_marker)); ERR_FAIL_COND_MSG(p_start_marker && !animation->has_marker(p_start_marker), vformat("Marker %s not found in animation: %s.", p_start_marker, name)); @@ -467,12 +468,12 @@ void AnimationPlayer::play_section(const StringName &p_name, double p_start_time } else if (blend_times.has(bk)) { blend_time = blend_times[bk]; } else { - bk.from = "*"; + bk.from = SNAME("*"); if (blend_times.has(bk)) { blend_time = blend_times[bk]; } else { bk.from = c.current.from->name; - bk.to = "*"; + bk.to = SNAME("*"); if (blend_times.has(bk)) { blend_time = blend_times[bk]; @@ -660,8 +661,9 @@ void AnimationPlayer::seek_internal(double p_time, bool p_update, bool p_update_ playback.current.pos = p_time; if (!playback.current.from) { if (playback.assigned) { - ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); - playback.current.from = &animation_set[playback.assigned]; + AnimationData *ad = animation_set.getptr(playback.assigned); + ERR_FAIL_NULL_MSG(ad, vformat("Animation not found: %s.", playback.assigned)); + playback.current.from = ad; } if (!playback.current.from) { return; // There is no animation. @@ -804,10 +806,11 @@ void AnimationPlayer::animation_set_next(const StringName &p_animation, const St } StringName AnimationPlayer::animation_get_next(const StringName &p_animation) const { - if (!animation_next_set.has(p_animation)) { + const StringName *next = animation_next_set.getptr(p_animation); + if (!next) { return StringName(); } - return animation_next_set[p_animation]; + return *next; } void AnimationPlayer::set_default_blend_time(double p_default) { @@ -838,8 +841,8 @@ double AnimationPlayer::get_blend_time(const StringName &p_animation1, const Str bk.from = p_animation1; bk.to = p_animation2; - if (blend_times.has(bk)) { - return blend_times[bk]; + if (const double *blend_time = blend_times.getptr(bk)) { + return *blend_time; } else { return 0; } @@ -882,9 +885,7 @@ Tween::EaseType AnimationPlayer::get_auto_capture_ease_type() const { void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List *r_options) const { const String pf = p_function; if (p_idx == 0 && (pf == "play" || pf == "play_backwards" || pf == "has_animation" || pf == "queue")) { - List al; - get_animation_list(&al); - for (const StringName &name : al) { + for (const StringName &name : get_sorted_animation_list()) { r_options->push_back(String(name).quote()); } } @@ -895,7 +896,7 @@ void AnimationPlayer::get_argument_options(const StringName &p_function, int p_i void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) { AnimationMixer::_animation_removed(p_name, p_library); - StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); + const StringName &name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); if (!animation_set.has(name)) { return; // No need to update because not the one from the library being used. @@ -904,17 +905,16 @@ void AnimationPlayer::_animation_removed(const StringName &p_name, const StringN _animation_set_cache_update(); // Erase blends if needed - List to_erase; + LocalVector to_erase; for (const KeyValue &E : blend_times) { - BlendKey bk = E.key; + const BlendKey &bk = E.key; if (bk.from == name || bk.to == name) { to_erase.push_back(bk); } } - while (to_erase.size()) { - blend_times.erase(to_erase.front()->get()); - to_erase.pop_front(); + for (const BlendKey &bk : to_erase) { + blend_times.erase(bk); } } @@ -922,7 +922,7 @@ void AnimationPlayer::_rename_animation(const StringName &p_from_name, const Str AnimationMixer::_rename_animation(p_from_name, p_to_name); // Rename autoplay or blends if needed. - List to_erase; + LocalVector to_erase; HashMap to_insert; for (const KeyValue &E : blend_times) { BlendKey bk = E.key; @@ -943,9 +943,8 @@ void AnimationPlayer::_rename_animation(const StringName &p_from_name, const Str } } - while (to_erase.size()) { - blend_times.erase(to_erase.front()->get()); - to_erase.pop_front(); + for (const BlendKey &bk : to_erase) { + blend_times.erase(bk); } while (to_insert.size()) { diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 4aa2411d0a29..1ad7b1ec3227 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -96,7 +96,7 @@ class AnimationPlayer : public AnimationMixer { bool seeked = false; bool internal_seeked = false; bool started = false; - List blend; + LocalVector blend; } playback; struct BlendKey { diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index b1ba39e9782c..55e82d9200a6 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -34,7 +34,10 @@ #include "animation_blend_tree.h" #include "scene/animation/animation_player.h" -void AnimationNode::get_parameter_list(List *r_list) const { +thread_local AnimationNode::ProcessState *AnimationNode::tls_process_state = nullptr; +thread_local AnimationNodeInstance *AnimationNode::current_instance = nullptr; + +void AnimationNode::get_parameter_list(LocalVector *r_list) const { Array parameters; if (GDVIRTUAL_CALL(_get_parameter_list, parameters)) { @@ -72,60 +75,19 @@ bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const return false; } -void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { - ERR_FAIL_NULL(process_state); - if (process_state->is_testing) { - return; - } - - const AHashMap::Iterator it = property_cache.find(p_name); - if (it) { - Pair &prop = process_state->tree->property_map.get_by_index(it->value).value; - Variant value = p_value; - if (Animation::validate_type_match(prop.first, value)) { - prop.first = value; - } - return; - } - - ERR_FAIL_COND(!process_state->tree->property_parent_map.has(node_state.base_path)); - ERR_FAIL_COND(!process_state->tree->property_parent_map[node_state.base_path].has(p_name)); - StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name]; - int idx = process_state->tree->property_map.get_index(path); - property_cache.insert_new(p_name, idx); - process_state->tree->property_map.get_by_index(idx).value.first = p_value; -} - -Variant AnimationNode::get_parameter(const StringName &p_name) const { - ERR_FAIL_NULL_V(process_state, Variant()); - const AHashMap::ConstIterator it = property_cache.find(p_name); - if (it) { - return process_state->tree->property_map.get_by_index(it->value).value.first; - } - ERR_FAIL_COND_V(!process_state->tree->property_parent_map.has(node_state.base_path), Variant()); - ERR_FAIL_COND_V(!process_state->tree->property_parent_map[node_state.base_path].has(p_name), Variant()); - - StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name]; - int idx = process_state->tree->property_map.get_index(path); - property_cache.insert_new(p_name, idx); - return process_state->tree->property_map.get_by_index(idx).value.first; +void AnimationNode::set_parameter_ex(const StringName &p_name, const Variant &p_value) { + ERR_FAIL_NULL(tls_process_state); + ERR_FAIL_NULL(current_instance); + current_instance->set_parameter(p_name, p_value, tls_process_state->is_testing); } -void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) { - set_parameter(current_length, p_node_time_info.length); - set_parameter(current_position, p_node_time_info.position); - set_parameter(current_delta, p_node_time_info.delta); +Variant AnimationNode::get_parameter_ex(const StringName &p_name) const { + ERR_FAIL_NULL_V(tls_process_state, Variant()); + ERR_FAIL_NULL_V(current_instance, Variant()); + return current_instance->get_parameter(p_name); } -AnimationNode::NodeTimeInfo AnimationNode::get_node_time_info() const { - NodeTimeInfo nti; - nti.length = get_parameter(current_length); - nti.position = get_parameter(current_position); - nti.delta = get_parameter(current_delta); - return nti; -} - -void AnimationNode::get_child_nodes(List *r_child_nodes) { +void AnimationNode::get_child_nodes(LocalVector *r_child_nodes) { Dictionary cn; if (GDVIRTUAL_CALL(_get_child_nodes, cn)) { for (const KeyValue &kv : cn) { @@ -137,95 +99,81 @@ void AnimationNode::get_child_nodes(List *r_child_nodes) { } } -void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info) { - ERR_FAIL_NULL(process_state); - p_playback_info.track_weights = Vector(node_state.track_weights); - process_state->tree->make_animation_instance(p_animation, p_playback_info); +void AnimationNode::blend_animation(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const StringName &p_animation, AnimationMixer::PlaybackInfo &p_playback_info) { + p_playback_info.track_weights = &p_instance.track_weights; + p_process_state.tree->make_animation_instance(p_animation, p_playback_info); } -AnimationNode::NodeTimeInfo AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - process_state = p_process_state; - NodeTimeInfo nti = process(p_playback_info, p_test_only); - process_state = nullptr; +AnimationNode::NodeTimeInfo AnimationNode::_pre_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + ERR_FAIL_NULL_V(tls_process_state, NodeTimeInfo()); // Should not ever happen. + ERR_FAIL_COND_V_MSG(tls_process_state != &p_process_state, NodeTimeInfo(), "AnimationNodes can only be processed from within their own AnimationTree."); + + AnimationNodeInstance *prev_instance = current_instance; + + current_instance = &p_instance; + NodeTimeInfo nti = process(p_process_state, p_instance, p_playback_info, p_test_only); + current_instance = prev_instance; + return nti; } -void AnimationNode::make_invalid(const String &p_reason) { - ERR_FAIL_NULL(process_state); - process_state->valid = false; - if (!process_state->invalid_reasons.is_empty()) { - process_state->invalid_reasons += "\n"; - } - process_state->invalid_reasons += String::utf8("• ") + p_reason; +void AnimationNode::add_validation_error(const AnimationTree *p_tree, const StringName &p_path, const String &p_error, int p_input_index) const { + p_tree->_add_validation_error(p_path, p_error, p_input_index); } -AnimationTree *AnimationNode::get_animation_tree() const { - ERR_FAIL_NULL_V(process_state, nullptr); - return process_state->tree; +void AnimationNode::make_invalid(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const String &p_reason) { + p_process_state.valid = false; + + InvalidInstance &invalid_instance = p_process_state.invalid_instances[p_instance.path]; + invalid_instance.errors.push_back(p_reason); } -AnimationNode::NodeTimeInfo AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNode::blend_input(ProcessState &p_process_state, AnimationNodeInstance &p_instance, int p_input, const AnimationMixer::PlaybackInfo &p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { ERR_FAIL_INDEX_V(p_input, (int64_t)inputs.size(), NodeTimeInfo()); - AnimationNodeBlendTree *blend_tree = Object::cast_to(node_state.parent); - ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo()); - - // Update connections. - StringName current_name = blend_tree->get_node_name(Ref(this)); - node_state.connections = blend_tree->get_node_connection_array(current_name); - - // Get node which is connected input port. - StringName node_name = node_state.connections[p_input]; - if (!blend_tree->has_node(node_name)) { - make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name)); - return NodeTimeInfo(); + AnimationNodeInstance *node_instance = nullptr; + if (likely(p_instance.connection_instances.size() > 0)) { + node_instance = p_instance.connection_instances[p_input]; } - - Ref node = blend_tree->get_node(node_name); - ERR_FAIL_COND_V(node.is_null(), NodeTimeInfo()); + // Should not ever happen due to validation earlier. + ERR_FAIL_NULL_V(node_instance, NodeTimeInfo()); real_t activity = 0.0; - LocalVector *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path); - NodeTimeInfo nti = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity); - if (activity_ptr && p_input < (int64_t)activity_ptr->size()) { - (*activity_ptr)[p_input].last_pass = process_state->last_pass; - (*activity_ptr)[p_input].activity = activity; - } + NodeTimeInfo nti = _blend_node(p_process_state, p_instance, *node_instance, p_playback_info, p_filter, p_sync, p_test_only, &activity); + +#ifdef ENABLE_ACTIVITY_TRACKING + LocalVector &input_activity = p_instance.input_activity; + input_activity[p_input].last_pass = p_process_state.last_pass; + input_activity[p_input].activity = activity; +#endif + return nti; } -AnimationNode::NodeTimeInfo AnimationNode::blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { - ERR_FAIL_COND_V(p_node.is_null(), NodeTimeInfo()); - p_node->node_state.connections.clear(); - return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr); +AnimationNode::NodeTimeInfo AnimationNode::blend_node(ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeInstance *p_other, const AnimationMixer::PlaybackInfo &p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_NULL_V(p_other, NodeTimeInfo()); + return _blend_node(p_process_state, p_instance, *p_other, p_playback_info, p_filter, p_sync, p_test_only, nullptr); } -AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) { - ERR_FAIL_NULL_V(process_state, NodeTimeInfo()); - - int blend_count = node_state.track_weights.size(); +AnimationNode::NodeTimeInfo AnimationNode::_blend_node(ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeInstance &p_other, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) { + int blend_count = p_instance.track_weights.size(); - if ((int64_t)p_node->node_state.track_weights.size() != blend_count) { - p_node->node_state.track_weights.resize(blend_count); + if ((int64_t)p_other.track_weights.size() != blend_count) { + p_other.track_weights.resize(blend_count); } - real_t *blendw = p_node->node_state.track_weights.ptr(); - const real_t *blendr = node_state.track_weights.ptr(); + real_t *blendw = p_other.track_weights.ptr(); + const real_t *blendr = p_instance.track_weights.ptr(); bool any_valid = false; if (has_filter() && is_filter_enabled() && p_filter != FILTER_IGNORE) { - for (int i = 0; i < blend_count; i++) { - blendw[i] = 0.0; // All to zero by default. - } + _update_filter_cache(p_process_state, p_instance); + // All to zero by default. + memset(blendw, 0, sizeof(real_t) * blend_count); - for (const KeyValue &E : filter) { - const AHashMap &map = *process_state->track_map; - if (!map.has(E.key)) { - continue; - } - int idx = map[E.key]; + for (const int idx : p_instance.filtered_track_indices_cache) { blendw[idx] = 1.0; // Filtered goes to one. } @@ -290,33 +238,19 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref p_node if (r_activity) { *r_activity = 0; +#ifdef ENABLE_ACTIVITY_TRACKING for (int i = 0; i < blend_count; i++) { *r_activity = MAX(*r_activity, Math::abs(blendw[i])); } - } - - String new_path; - AnimationNode *new_parent; - - // This is the slowest part of processing, but as strings process in powers of 2, and the paths always exist, it will not result in that many allocations. - if (p_new_parent) { - new_parent = p_new_parent; - new_path = String(node_state.base_path) + String(p_subpath) + "/"; - } else { - ERR_FAIL_NULL_V(node_state.parent, NodeTimeInfo()); - new_parent = node_state.parent; - new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/"; +#endif } // This process, which depends on p_sync is needed to process sync correctly in the case of // that a synced AnimationNodeSync exists under the un-synced AnimationNodeSync. - p_node->set_node_state_base_path(new_path); - p_node->node_state.parent = new_parent; if (!p_playback_info.seeked && !p_sync && !any_valid) { p_playback_info.delta = 0.0; - return p_node->_pre_process(process_state, p_playback_info, p_test_only); } - return p_node->_pre_process(process_state, p_playback_info, p_test_only); + return p_other.resource->_pre_process(p_process_state, p_other, p_playback_info, p_test_only); } String AnimationNode::get_caption() const { @@ -370,28 +304,34 @@ int AnimationNode::find_input(const String &p_name) const { return idx; } -AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - process_state->is_testing = p_test_only; +AnimationNode::NodeTimeInfo AnimationNode::process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { + p_process_state.is_testing = p_test_only; + + double &length = *VariantInternal::get_float(p_instance.parameter_ptrs_by_slot[AnimationNodeInstance::SLOT_CURRENT_LENGTH]); + double &position = *VariantInternal::get_float(p_instance.parameter_ptrs_by_slot[AnimationNodeInstance::SLOT_CURRENT_POSITION]); + double &delta = *VariantInternal::get_float(p_instance.parameter_ptrs_by_slot[AnimationNodeInstance::SLOT_CURRENT_DELTA]); 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; + pi.delta = position - p_playback_info.time; } } else { - pi.time = get_node_time_info().position + p_playback_info.delta; + pi.time = position + get_process_delta(p_instance, p_playback_info); } - NodeTimeInfo nti = _process(pi, p_test_only); + NodeTimeInfo nti = _process(p_process_state, p_instance, pi, p_test_only); if (!p_test_only) { - set_node_time_info(nti); + length = nti.length; + position = nti.position; + delta = nti.delta; } return nti; } -AnimationNode::NodeTimeInfo AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNode::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) { double r_ret = 0.0; GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, r_ret); NodeTimeInfo nti; @@ -401,14 +341,17 @@ AnimationNode::NodeTimeInfo AnimationNode::_process(const AnimationMixer::Playba void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { if (p_enable) { - filter[p_path] = true; + (void)p_path.hash(); // Make sure the cache is valid. + filter.insert(p_path); } else { filter.erase(p_path); } + filters_dirty = true; } void AnimationNode::set_filter_enabled(bool p_enable) { filter_enabled = p_enable; + filters_dirty = true; } bool AnimationNode::is_filter_enabled() const { @@ -424,13 +367,13 @@ bool AnimationNode::is_deletable() const { } ObjectID AnimationNode::get_processing_animation_tree_instance_id() const { - ERR_FAIL_NULL_V(process_state, ObjectID()); - return process_state->tree->get_instance_id(); + ERR_FAIL_NULL_V(tls_process_state, ObjectID()); + return tls_process_state->tree->get_instance_id(); } bool AnimationNode::is_process_testing() const { - ERR_FAIL_NULL_V(process_state, false); - return process_state->is_testing; + ERR_FAIL_NULL_V(tls_process_state, false); + return tls_process_state->is_testing; } bool AnimationNode::is_path_filtered(const NodePath &p_path) const { @@ -446,8 +389,8 @@ bool AnimationNode::has_filter() const { Array AnimationNode::_get_filters() const { Array paths; - for (const KeyValue &E : filter) { - paths.push_back(String(E.key)); // Use strings, so sorting is possible. + for (const NodePath &E : filter) { + paths.push_back(String(E)); // Use strings, so sorting is possible. } paths.sort(); // Done so every time the scene is saved, it does not change. @@ -461,6 +404,24 @@ void AnimationNode::_set_filters(const Array &p_filters) { } } +void AnimationNode::_update_filter_cache(const ProcessState &p_process_state, const AnimationNodeInstance &p_instance) { + if (!p_process_state.track_map_updated && !filters_dirty) { + return; // Cache is valid. + } + + p_instance.filtered_track_indices_cache.clear(); + if (p_instance.filtered_track_indices_cache.size() < filter.size()) { + p_instance.filtered_track_indices_cache.reserve(filter.size()); + } + + for (const NodePath &path : filter) { + if (const int *p = p_process_state.track_map->getptr(path)) { + p_instance.filtered_track_indices_cache.push_back(*p); + } + } + filters_dirty = false; +} + void AnimationNode::_validate_property(PropertyInfo &p_property) const { if (!has_filter() && (p_property.name == "filter_enabled" || p_property.name == "filters")) { p_property.usage = PROPERTY_USAGE_NONE; @@ -486,6 +447,9 @@ Ref AnimationNode::find_node_by_path(const String &p_name) const } void AnimationNode::blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag) { + ERR_FAIL_NULL(tls_process_state); + ERR_FAIL_NULL(current_instance); + AnimationMixer::PlaybackInfo info; info.time = p_time; info.delta = p_delta; @@ -493,26 +457,38 @@ void AnimationNode::blend_animation_ex(const StringName &p_animation, double p_t info.is_external_seeking = p_is_external_seeking; info.weight = p_blend; info.looped_flag = p_looped_flag; - blend_animation(p_animation, info); + + blend_animation(*tls_process_state, *current_instance, p_animation, info); } -double AnimationNode::blend_node_ex(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { +double AnimationNode::blend_node_ex(const StringName &p_sub_path, const Ref &p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_NULL_V(tls_process_state, 0.0); + ERR_FAIL_NULL_V(current_instance, 0.0); + + AnimationNodeInstance *other_instance = current_instance->get_child_instance_by_path_or_null(p_sub_path); + ERR_FAIL_NULL_V_MSG(other_instance, 0.0, vformat("The sub-path '%s' does not exist under the current node instance.", String(p_sub_path))); + AnimationMixer::PlaybackInfo info; info.time = p_time; info.seeked = p_seek; info.is_external_seeking = p_is_external_seeking; info.weight = p_blend; - NodeTimeInfo nti = blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only); + + NodeTimeInfo nti = blend_node(*tls_process_state, *current_instance, other_instance, info, p_filter, p_sync, p_test_only); return nti.length - nti.position; } double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_NULL_V(tls_process_state, 0.0); + ERR_FAIL_NULL_V(current_instance, 0.0); + AnimationMixer::PlaybackInfo info; info.time = p_time; info.seeked = p_seek; info.is_external_seeking = p_is_external_seeking; info.weight = p_blend; - NodeTimeInfo nti = blend_input(p_input, info, p_filter, p_sync, p_test_only); + + NodeTimeInfo nti = blend_input(*tls_process_state, *current_instance, p_input, info, p_filter, p_sync, p_test_only); return nti.length - nti.position; } @@ -526,7 +502,7 @@ void AnimationNode::get_argument_options(const StringName &p_function, int p_idx } } else if (pf == "get_parameter" || pf == "set_parameter") { bool is_setter = pf == "set_parameter"; - List parameters; + LocalVector parameters; get_parameter_list(¶meters); for (const PropertyInfo &E : parameters) { if (is_setter && is_parameter_read_only(E.name)) { @@ -535,8 +511,8 @@ void AnimationNode::get_argument_options(const StringName &p_function, int p_idx r_options->push_back(E.name.quote()); } } else if (pf == "set_filter_path" || pf == "is_path_filtered") { - for (const KeyValue &E : filter) { - r_options->push_back(String(E.key).quote()); + for (const NodePath &E : filter) { + r_options->push_back(String(E).quote()); } } } @@ -569,8 +545,8 @@ void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_node_ex, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_input_ex, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter); - ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter); + ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter_ex); + ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter_ex); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_filter_enabled", "is_filter_enabled"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters"); @@ -584,7 +560,10 @@ void AnimationNode::_bind_methods() { GDVIRTUAL_BIND(_get_caption); GDVIRTUAL_BIND(_has_filter); + // For "tree_changed", wouldn't it be nice, if we could pass in the source? + // That way we would be able to partially rebuild instances. ADD_SIGNAL(MethodInfo("tree_changed")); + ADD_SIGNAL(MethodInfo("node_updated", PropertyInfo(Variant::INT, "object_id"))); ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name"))); ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name"))); @@ -599,10 +578,28 @@ AnimationNode::AnimationNode() { //////////////////// +void AnimationRootNode::_add_node(const Ref &p_node) { + p_node->connect(SNAME("tree_changed"), callable_mp(this, &AnimationRootNode::_tree_changed), CONNECT_REFERENCE_COUNTED); + p_node->connect(SNAME("node_updated"), callable_mp(this, &AnimationRootNode::_node_updated), CONNECT_REFERENCE_COUNTED); + p_node->connect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationRootNode::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + p_node->connect(SNAME("animation_node_removed"), callable_mp(this, &AnimationRootNode::_animation_node_removed), CONNECT_REFERENCE_COUNTED); +} + +void AnimationRootNode::_remove_node(const Ref &p_node) { + p_node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationRootNode::_tree_changed)); + p_node->disconnect(SNAME("node_updated"), callable_mp(this, &AnimationRootNode::_node_updated)); + p_node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationRootNode::_animation_node_renamed)); + p_node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationRootNode::_animation_node_removed)); +} + void AnimationRootNode::_tree_changed() { emit_signal(SNAME("tree_changed")); } +void AnimationRootNode::_node_updated(const ObjectID &p_oid) { + emit_signal(SNAME("node_updated"), p_oid); +} + void AnimationRootNode::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { emit_signal(SNAME("animation_node_renamed"), p_oid, p_old_name, p_new_name); } @@ -616,6 +613,7 @@ void AnimationRootNode::_animation_node_removed(const ObjectID &p_oid, const Str void AnimationTree::set_root_animation_node(const Ref &p_animation_node) { if (root_animation_node.is_valid()) { root_animation_node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationTree::_tree_changed)); + root_animation_node->disconnect(SNAME("node_updated"), callable_mp(this, &AnimationTree::_node_updated)); root_animation_node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationTree::_animation_node_renamed)); root_animation_node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationTree::_animation_node_removed)); } @@ -624,6 +622,7 @@ void AnimationTree::set_root_animation_node(const Ref &p_anim if (root_animation_node.is_valid()) { root_animation_node->connect(SNAME("tree_changed"), callable_mp(this, &AnimationTree::_tree_changed)); + root_animation_node->connect(SNAME("node_updated"), callable_mp(this, &AnimationTree::_node_updated)); root_animation_node->connect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationTree::_animation_node_renamed)); root_animation_node->connect(SNAME("animation_node_removed"), callable_mp(this, &AnimationTree::_animation_node_removed)); } @@ -640,46 +639,65 @@ Ref AnimationTree::get_root_animation_node() const { bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const AHashMap &p_track_map) { _update_properties(); // If properties need updating, update them. - if (root_animation_node.is_null()) { + bool was_validation_dirty = validation_dirty; + if (validation_dirty) { + process_state.invalid_instances.clear(); + validation_successful = true; + _validate_animation_graph(Animation::PARAMETERS_BASE_PATH, root_animation_node); + validation_dirty = false; + } + if (!validation_successful) { return false; } + if (was_validation_dirty) { + _update_connections(); + } + + AnimationNodeInstance &instance = get_node_instance_by_path(SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data())); { // Setup. process_pass++; + if (unlikely(process_pass == 0)) { + process_pass = 1; + } // Init process state. process_state = AnimationNode::ProcessState(); process_state.tree = this; process_state.valid = true; - process_state.invalid_reasons = ""; + process_state.invalid_instances.clear(); process_state.last_pass = process_pass; process_state.track_map = &p_track_map; + process_state.track_map_updated = track_map_version != last_track_map_version; + process_state.is_testing = false; + + last_track_map_version = track_map_version; // Init node state for root AnimationNode. - root_animation_node->node_state.track_weights.resize(p_track_count); - real_t *src_blendsw = root_animation_node->node_state.track_weights.ptr(); + instance.track_weights.resize(p_track_count); + real_t *src_blendsw = instance.track_weights.ptr(); for (int i = 0; i < p_track_count; i++) { src_blendsw[i] = 1.0; // By default all go to 1 for the root input. } - root_animation_node->set_node_state_base_path(SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data())); - root_animation_node->node_state.parent = nullptr; + instance.path = SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data()); } // Process. { PlaybackInfo pi; + pi.delta = p_delta; if (started) { + started = false; // If started, seek. pi.seeked = true; - pi.delta = p_delta; - root_animation_node->_pre_process(&process_state, pi, false); - started = false; } else { pi.seeked = false; - pi.delta = p_delta; - root_animation_node->_pre_process(&process_state, pi, false); } + + AnimationNode::tls_process_state = &process_state; + root_animation_node->_pre_process(process_state, instance, pi, false); + AnimationNode::tls_process_state = nullptr; } if (!process_state.valid) { @@ -706,12 +724,8 @@ bool AnimationTree::is_state_invalid() const { return !process_state.valid; } -String AnimationTree::get_invalid_state_reason() const { - return process_state.invalid_reasons; -} - -uint64_t AnimationTree::get_last_process_pass() const { - return process_pass; +const AHashMap &AnimationTree::get_invalid_instances() const { + return process_state.invalid_instances; } PackedStringArray AnimationTree::get_configuration_warnings() const { @@ -731,16 +745,29 @@ void AnimationTree::_tree_changed() { properties_dirty = true; } +void AnimationTree::_node_updated(const ObjectID &p_oid) { + // This is for when the animation in AnimationNodeAnimation changes. + // or a connection in AnimationNodeBlendTree changes. + + // Ideally, we would only validate relevant instances, but for now, revalidate all. + validation_dirty = true; +} + void AnimationTree::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { - ERR_FAIL_COND(!property_reference_map.has(p_oid)); - String base_path = property_reference_map[p_oid]; - String old_base = base_path + p_old_name; - String new_base = base_path + p_new_name; - for (const PropertyInfo &E : properties) { - if (E.name.begins_with(old_base)) { - String new_name = E.name.replace_first(old_base, new_base); - property_map[new_name] = property_map[E.name]; - property_map.erase(E.name); + //print_line("Node: " + ObjectDB::get_instance(p_oid)->get_class() + " (" + itos(p_oid) + ") renamed: " + p_old_name + " -> " + p_new_name); + for (const StringName &pp : instance_paths[p_oid]) { + String parent_path = pp; + String old_base = parent_path + p_old_name; + String new_base = parent_path + p_new_name; + //print_line(" - Updating " + pp + ": " + old_base + " -> " + new_base); + for (const PropertyInfo &E : properties) { + if (E.name.begins_with(old_base)) { + StringName old_name = E.name; + StringName new_name = E.name.replace_first(old_base, new_base); + //print_line(" - Property: " + String(old_name) + " -> " + String(new_name)); + property_map[new_name] = property_map[old_name]; + property_map.erase(old_name); + } } } @@ -750,11 +777,13 @@ void AnimationTree::_animation_node_renamed(const ObjectID &p_oid, const String } void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { - ERR_FAIL_COND(!property_reference_map.has(p_oid)); - String base_path = String(property_reference_map[p_oid]) + String(p_node); - for (const PropertyInfo &E : properties) { - if (E.name.begins_with(base_path)) { - property_map.erase(E.name); + for (const StringName &parent_path : instance_paths[p_oid]) { + String base_path = String(parent_path) + String(p_node); + + for (const PropertyInfo &E : properties) { + if (E.name.begins_with(base_path)) { + property_map.erase(E.name); + } } } @@ -763,50 +792,57 @@ void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringN _update_properties(); } -void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref p_node) const { +void AnimationTree::_update_properties_for_node(const StringName &p_base_path, const Ref &p_node) const { ERR_FAIL_COND(p_node.is_null()); - if (!property_parent_map.has(p_base_path)) { - property_parent_map[p_base_path] = AHashMap(); - } - if (!property_reference_map.has(p_node->get_instance_id())) { - property_reference_map[p_node->get_instance_id()] = p_base_path; - } - if (p_node->get_input_count() && !input_activity_map.has(p_base_path)) { - LocalVector activity; + instance_paths[p_node->get_instance_id()].insert(p_base_path); + + const String base_path_str = p_base_path; + + AnimationNodeInstance &instance = instance_map[p_base_path]; + instance.path = p_base_path; + instance.resource = p_node; + +#ifdef ENABLE_ACTIVITY_TRACKING + if (p_node->get_input_count()) { for (int i = 0; i < p_node->get_input_count(); i++) { - Activity a; + AnimationNodeInstance::Activity a; a.activity = 0; a.last_pass = 0; - activity.push_back(a); + instance.input_activity.push_back(a); } - input_activity_map[p_base_path] = activity; - input_activity_map_get[String(p_base_path).substr(0, String(p_base_path).length() - 1)] = input_activity_map.get_index(p_base_path); } +#endif - List plist; + LocalVector plist; p_node->get_parameter_list(&plist); for (PropertyInfo &pinfo : plist) { - StringName key = pinfo.name; - - if (!property_map.has(p_base_path + key)) { - Pair param; - param.first = p_node->get_parameter_default_value(key); - param.second = p_node->is_parameter_read_only(key); - property_map[p_base_path + key] = param; + StringName pname = pinfo.name; + StringName key = base_path_str + pname; + + bool property_was_added = false; + Pair ¶m = property_map.get_value_ref_or_add_default(key, property_was_added); + if (property_was_added) { + param.first = p_node->get_parameter_default_value(pname); + param.second = p_node->is_parameter_read_only(pname); } - property_parent_map[p_base_path][key] = p_base_path + key; - - pinfo.name = p_base_path + key; + pinfo.name = key; properties.push_back(pinfo); } - p_node->make_cache_dirty(); - List children; + + LocalVector children; p_node->get_child_nodes(&children); + // These have to be done in two passes, because _update_properties_for_node invalidates the instance reference. + for (const AnimationNode::ChildNode &E : children) { + ERR_CONTINUE(E.name.is_empty()); + instance.child_instances[E.name] = nullptr; // Will be set in _update_properties + } + for (const AnimationNode::ChildNode &E : children) { - _update_properties_for_node(p_base_path + E.name + "/", E.node); + const StringName child_path = base_path_str + E.name + "/"; + _update_properties_for_node(child_path, E.node); } } @@ -815,14 +851,46 @@ void AnimationTree::_update_properties() const { return; } + // if properties are dirty, so is the validation state. + validation_dirty = true; properties.clear(); - property_reference_map.clear(); - property_parent_map.clear(); - input_activity_map.clear(); - input_activity_map_get.clear(); + instance_map.clear(); + instance_paths.clear(); if (root_animation_node.is_valid()) { _update_properties_for_node(Animation::PARAMETERS_BASE_PATH, root_animation_node); + + // Now that the properties and instances are stable, we can update them. + for (KeyValue &E : instance_map) { + const String &instance_path = E.key; + AnimationNodeInstance &instance = E.value; + + // Update children. + for (KeyValue &kv : instance.child_instances) { + const StringName child_path = instance_path + kv.key + "/"; + kv.value = instance_map.getptr(child_path); + CRASH_COND(!kv.value); // Shouldn't ever happen. + } + + // Now properties. + const Ref &node = instance.resource; + ERR_FAIL_COND(node.is_null()); + + instance.parameter_ptrs_by_slot.resize_initialized(AnimationNodeInstance::SLOT_MAX); + LocalVector plist; + node->get_parameter_list(&plist); + for (const PropertyInfo &pinfo : plist) { + StringName pname = pinfo.name; + + Pair *pair = property_map.getptr(instance_path + pname); + CRASH_COND(!pair); // Shouldn't ever happen. + + instance.property_ptrs[pname] = &pair->first; + + // Some of these get special treatment. + instance.maybe_bind_slot_property(pname, &pair->first); + } + } } properties_dirty = false; @@ -830,6 +898,69 @@ void AnimationTree::_update_properties() const { const_cast(this)->notify_property_list_changed(); } +void AnimationTree::_validate_animation_graph(const StringName &p_path, const Ref &p_node) const { + if (p_node.is_null()) { + validation_successful = false; + return; + } + + p_node->validate_node(this, p_path); + // We will continue even if the validation is not successful, to gather all errors. + + LocalVector children; + p_node->get_child_nodes(&children); + + for (const AnimationNode::ChildNode &E : children) { + const StringName child_path = String(p_path) + E.name + "/"; + _validate_animation_graph(child_path, E.node); + } +} + +void AnimationTree::_update_connections() { + for (KeyValue &E : instance_map) { + AnimationNodeInstance &parent_instance = E.value; + + const AnimationNodeBlendTree *blend_tree = Object::cast_to(parent_instance.resource.ptr()); + + if (!blend_tree) { + continue; + } + + { + const LocalVector *output_connections = blend_tree->get_node_connection_array(SceneStringName(output)); + parent_instance.connection_instances.resize(1); + AnimationNodeInstance *connected_instance = parent_instance.get_child_instance_by_path_or_null(output_connections->operator[](0)); + parent_instance.connection_instances[0] = connected_instance; + CRASH_COND(!connected_instance); // Will never happen. + } + + for (const KeyValue &kv : parent_instance.child_instances) { + AnimationNodeInstance *child_instance = kv.value; + const LocalVector &child_connections = *blend_tree->get_node_connection_array(kv.key); + child_instance->connection_instances.clear(); + child_instance->connection_instances.resize(child_connections.size()); + for (uint32_t input = 0; input < child_connections.size(); input++) { + const StringName &connected_node_name = child_connections[input]; + AnimationNodeInstance *connected_instance = parent_instance.get_child_instance_by_path_or_null(connected_node_name); + child_instance->connection_instances[input] = connected_instance; + CRASH_COND(!connected_instance); // Will never happen. + } + } + } +} + +void AnimationTree::_add_validation_error(const StringName &p_path, const String &p_error, int p_input_index) const { + validation_successful = false; + + AnimationNode::InvalidInstance &invalid_instance = process_state.invalid_instances[p_path]; + + if (p_input_index == -1) { + invalid_instance.errors.push_back(p_error); + } else { + invalid_instance.input_errors.push_back({ p_input_index, p_error }); + } +} + void AnimationTree::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -886,7 +1017,7 @@ void AnimationTree::_setup_animation_player() { while (animation_libraries.size()) { remove_animation_library(animation_libraries[0].name); } - List list; + LocalVector list; player->get_animation_library_list(&list); for (const StringName &E : list) { Ref lib = player->get_animation_library(E); @@ -927,19 +1058,25 @@ bool AnimationTree::_set(const StringName &p_name, const Variant &p_value) { return true; } #endif // DISABLE_DEPRECATED - if (properties_dirty) { - _update_properties(); - } + _update_properties(); - if (property_map.has(p_name)) { - if (is_inside_tree() && property_map[p_name].second) { + if (Pair *property_ptr = property_map.getptr(p_name)) { + Pair *&pair = property_ptr; + if (is_inside_tree() && pair->second) { return false; // Prevent to set property by user. } - Pair &prop = property_map[p_name]; - Variant value = p_value; - if (Animation::validate_type_match(prop.first, value)) { - prop.first = value; + + Variant &prop = pair->first; + // Only copy variant if needed. + if (Animation::needs_type_cast(prop, p_value)) { + Variant value = p_value; + if (Animation::validate_type_match(prop, value)) { + prop = value; + } + } else { + prop = p_value; } + return true; } @@ -953,12 +1090,10 @@ bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const { return true; } #endif // DISABLE_DEPRECATED - if (properties_dirty) { - _update_properties(); - } + _update_properties(); - if (property_map.has(p_name)) { - r_ret = property_map[p_name].first; + if (const Pair *p = property_map.getptr(p_name)) { + r_ret = p->first; return true; } @@ -966,38 +1101,35 @@ bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const { } void AnimationTree::_get_property_list(List *p_list) const { - if (properties_dirty) { - _update_properties(); - } + _update_properties(); for (const PropertyInfo &E : properties) { p_list->push_back(E); } } +#ifdef ENABLE_ACTIVITY_TRACKING real_t AnimationTree::get_connection_activity(const StringName &p_path, int p_connection) const { - if (!input_activity_map_get.has(p_path)) { + const AnimationNodeInstance *a = get_node_instance_by_path_or_null(p_path); + if (!a) { return 0; } - int index = input_activity_map_get[p_path]; - const LocalVector &activity = input_activity_map.get_by_index(index).value; - + const LocalVector &activity = a->input_activity; if (p_connection < 0 || p_connection >= (int64_t)activity.size() || activity[p_connection].last_pass != process_pass) { return 0; } return activity[p_connection].activity; } +#endif #ifdef TOOLS_ENABLED String AnimationTree::get_editor_error_message() const { if (!is_active()) { return TTR("The AnimationTree is inactive.\nActivate it in the inspector to enable playback; check node warnings if activation fails."); } else if (!is_enabled()) { - return TTR("The AnimationTree node (or one of its parents) has its process mode set to Disabled.\nChange the process mode in the inspector to allow playback."); - } else if (is_state_invalid()) { - return get_invalid_state_reason(); + return TTR("The AnimationTree node (or one of its ancestors) has its process mode set to Disabled.\nChange the process mode in the inspector to allow playback."); } return ""; diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 5a612f80e9f0..f9531f0fa3e9 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -35,10 +35,15 @@ #define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion for AnimationNodeStateMachine. +#ifdef TOOLS_ENABLED +#define ENABLE_ACTIVITY_TRACKING +#endif + class AnimationNodeBlendTree; class AnimationNodeStartState; class AnimationNodeEndState; class AnimationTree; +struct AnimationNodeInstance; class AnimationNode : public Resource { GDCLASS(AnimationNode, Resource); @@ -59,8 +64,9 @@ class AnimationNode : public Resource { bool closable = false; LocalVector inputs; - AHashMap filter; + HashSet filter; bool filter_enabled = false; + bool filters_dirty = true; // To propagate information from upstream for use in estimation of playback progress. // These values must be taken from the result of blend_node() or blend_input() and must be essentially read-only. @@ -94,87 +100,72 @@ class AnimationNode : public Resource { } }; - // Temporary state for blending process which needs to be stored in each AnimationNodes. - struct NodeState { - friend AnimationNode; - - private: - StringName base_path; - - public: - AnimationNode *parent = nullptr; - Vector connections; - LocalVector track_weights; + struct InvalidInstance { + LocalVector errors; - const StringName get_base_path() const { - return base_path; - } + struct InputError { + int index; + String error; + }; - } node_state; + LocalVector input_errors; + }; // Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree. struct ProcessState { AnimationTree *tree = nullptr; const AHashMap *track_map; // TODO: Is there a better way to manage filter/tracks? + bool track_map_updated = false; bool is_testing = false; bool valid = false; - String invalid_reasons; + mutable AHashMap invalid_instances; uint64_t last_pass = 0; - } *process_state = nullptr; + }; -private: - mutable AHashMap property_cache; + // For performance ProcessState needs to be passed down, + // but the scripting api was already exposed before this optimization was made. + // So to keep compatibility, we need this internal state, so that the scripting api can continue working as before. + // It also must be thread_local, because multiple AnimationTrees can be processed in different threads. + static thread_local ProcessState *tls_process_state; + static thread_local AnimationNodeInstance *current_instance; public: - void set_node_state_base_path(const StringName p_base_path) { - if (p_base_path != node_state.base_path) { - node_state.base_path = p_base_path; - make_cache_dirty(); - } - } - - void set_node_state_base_path(const String p_base_path) { - if (p_base_path != node_state.base_path) { - node_state.base_path = p_base_path; - make_cache_dirty(); - } - } - - const StringName get_node_state_base_path() const { - return node_state.get_base_path(); - } - - void make_cache_dirty() { - property_cache.clear(); - } Array _get_filters() const; void _set_filters(const Array &p_filters); + + void _update_filter_cache(const ProcessState &p_process_state, const AnimationNodeInstance &p_instance); + friend class AnimationNodeBlendTree; + virtual void validate_node(const AnimationTree *p_tree, const StringName &p_path) const {} // The time information is passed from upstream to downstream by AnimationMixer::PlaybackInfo::p_playback_info until AnimationNodeAnimation processes it. // Conversely, AnimationNodeAnimation returns the processed result as NodeTimeInfo from downstream to upstream. - NodeTimeInfo _blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr); - NodeTimeInfo _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); + NodeTimeInfo _blend_node(ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeInstance &p_other, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr); + NodeTimeInfo _pre_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only = false); protected: StringName current_length = "current_length"; StringName current_position = "current_position"; StringName current_delta = "current_delta"; - virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // To organize time information. Virtualizing for especially AnimationNodeAnimation needs to take "backward" into account. - virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // Main process. + NodeTimeInfo process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only = false); + // Virtualizing for especially AnimationNodeAnimation needs to take "backward" into account. + _FORCE_INLINE_ virtual double get_process_delta(AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info) const { + return p_playback_info.delta; + } + virtual NodeTimeInfo _process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only = false); // Main process. - void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info); - NodeTimeInfo blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); - NodeTimeInfo blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + void blend_animation(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const StringName &p_animation, AnimationMixer::PlaybackInfo &p_playback_info); + NodeTimeInfo blend_node(ProcessState &p_process_state, AnimationNodeInstance &p_instance, AnimationNodeInstance *p_other, const AnimationMixer::PlaybackInfo &p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + NodeTimeInfo blend_input(ProcessState &p_process_state, AnimationNodeInstance &p_instance, int p_input, const AnimationMixer::PlaybackInfo &p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); // Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed. void blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE); - double blend_node_ex(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + double blend_node_ex(const StringName &p_sub_path, const Ref &p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); double blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); - void make_invalid(const String &p_reason); - AnimationTree *get_animation_tree() const; + void add_validation_error(const AnimationTree *p_tree, const StringName &p_path, const String &p_error, int p_input_index = -1) const; + void make_invalid(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const String &p_reason); static void _bind_methods(); @@ -190,22 +181,19 @@ class AnimationNode : public Resource { GDVIRTUAL0RC(bool, _has_filter) public: - virtual void get_parameter_list(List *r_list) const; + virtual void get_parameter_list(LocalVector *r_list) const; virtual Variant get_parameter_default_value(const StringName &p_parameter) const; virtual bool is_parameter_read_only(const StringName &p_parameter) const; - void set_parameter(const StringName &p_name, const Variant &p_value); - Variant get_parameter(const StringName &p_name) const; - - void set_node_time_info(const NodeTimeInfo &p_node_time_info); // Wrapper of set_parameter(). - virtual NodeTimeInfo get_node_time_info() const; // Wrapper of get_parameter(). + void set_parameter_ex(const StringName &p_name, const Variant &p_value); + Variant get_parameter_ex(const StringName &p_name) const; struct ChildNode { StringName name; Ref node; }; - virtual void get_child_nodes(List *r_child_nodes); + virtual void get_child_nodes(LocalVector *r_child_nodes); virtual String get_caption() const; @@ -248,7 +236,10 @@ class AnimationRootNode : public AnimationNode { GDCLASS(AnimationRootNode, AnimationNode); protected: + void _add_node(const Ref &p_node); + void _remove_node(const Ref &p_node); virtual void _tree_changed(); + void _node_updated(const ObjectID &p_oid); virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name); virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node); }; @@ -261,6 +252,181 @@ class AnimationNodeEndState : public AnimationRootNode { GDCLASS(AnimationNodeEndState, AnimationRootNode); }; +// Per instance data, for a node. +// These get created/destroyed when the AnimationRootNode is updated, so you cannot use it to store persistent data. +struct AnimationNodeInstance { + // This makes it faster to access the most commonly used parameters, since we just index an array instead of doing a hash lookup. + // But unfortunately, these are still Variants, which are quite slow, + // They are also pointers, which cause a level of indirection. + // + // In an ideal world, get_parameter and set_parameter are removed, and everything is made strongly typed (without variant). + // But that is a ton of work, and this is a good enough optimization for now. + LocalVector parameter_ptrs_by_slot; + + mutable LocalVector connection_instances; // AnimationNodeInstance* | nullptr + mutable LocalVector track_weights; + mutable LocalVector filtered_track_indices_cache; + mutable AHashMap child_instances; // Child Name -> AnimationNodeInstance* + + // Multiple AnimationNodeInstances can share the same resource btw. + Ref resource; + + // TODO: these fields are like only used during initialization, or editor, or already slow paths. Consider moving them out of this struct. + //AnimationNodeInstance *parent = nullptr; + StringName path; // e.g. "parameters/node_name/sub_node_name/" + + mutable AHashMap property_ptrs; + +#ifdef ENABLE_ACTIVITY_TRACKING + struct Activity { + uint64_t last_pass = 0; + real_t activity = 0.0; + }; + + LocalVector input_activity; +#endif + + // Cache for AnimationNodeAnimation + Ref cached_animation; + uint32_t cached_animation_version = 0; + + // Macro to define all slots. + // slot index, enum name, member name, variant type, native type + // you can reuse slot index for different nodes if they don't overlap. + // Just make sure you update SLOT_MAX accordingly. +#define ANIM_SLOT_LIST(X) \ + /* Core parameters. */ \ + X(0, CURRENT_LENGTH, current_length, Variant::FLOAT, double) \ + X(1, CURRENT_POSITION, current_position, Variant::FLOAT, double) \ + X(2, CURRENT_DELTA, current_delta, Variant::FLOAT, double) \ + /* AnimationNodeTimeScale. */ \ + X(3, TIME_SCALE, scale, Variant::FLOAT, double) \ + /* AnimationNodeTimeSeek. */ \ + X(3, SEEK_POS_REQUEST, seek_pos_request, Variant::FLOAT, double) \ + /* AnimationNodeAdd2, AnimationNodeAdd3. */ \ + X(3, ADD_AMOUNT, add_amount, Variant::FLOAT, double) \ + /* AnimationNodeBlend2, AnimationNodeBlend3. */ \ + X(3, BLEND_AMOUNT, blend_amount, Variant::FLOAT, double) \ + /* AnimationNodeSub2. */ \ + X(3, SUB_AMOUNT, sub_amount, Variant::FLOAT, double) \ + /* AnimationNodeOneShot. */ \ + X(3, ONESHOT_TIME_TO_RESTART, time_to_restart, Variant::FLOAT, double) \ + X(4, ONESHOT_FADE_IN_REMAINING, fade_in_remaining, Variant::FLOAT, double) \ + X(5, ONESHOT_FADE_OUT_REMAINING, fade_out_remaining, Variant::FLOAT, double) \ + X(6, ONESHOT_ACTIVE, active, Variant::BOOL, bool) \ + X(7, ONESHOT_INTERNAL_ACTIVE, internal_active, Variant::BOOL, bool) \ + X(8, ONESHOT_REQUEST, request, Variant::INT, int) \ + /* AnimationNodeAnimation */ \ + X(3, ANIMATION_BACKWARD, backward, Variant::BOOL, bool) \ + /* AnimationNodeTransition TODO: Make ref & */ \ + X(3, TRANSITION_REQUEST, transition_request, Variant::STRING, String) \ + X(4, CURRENT_INDEX, current_index, Variant::INT, int) \ + X(5, PREV_INDEX, prev_index, Variant::INT, int) \ + X(6, PREV_XFADING, prev_xfading, Variant::FLOAT, double) \ + X(7, CURRENT_STATE, current_state, Variant::STRING, String) \ + /* AnimationNodeBlendSpace1D and AnimationNodeBlendSpace2D, \ + We currently cannot do blend_position, due to type same name but different type */ \ + X(3, CLOSEST, closest, Variant::INT, int) + + enum Slot : uint8_t { +#define SLOT_ENUM(index, e, _member, _variant_type, _native_type) SLOT_##e = index, + ANIM_SLOT_LIST(SLOT_ENUM) +#undef SLOT_ENUM + SLOT_MAX = 9 + }; + + void maybe_bind_slot_property(const StringName &p_name, Variant *p_property) { +#define HANDLE_MAYBE_BIND_SLOT_PARAMETER(_index, slot, name, _variant_type, _native_type) \ + if (p_name == SNAME(#name)) { \ + parameter_ptrs_by_slot[SLOT_##slot] = p_property; \ + return; \ + } + ANIM_SLOT_LIST(HANDLE_MAYBE_BIND_SLOT_PARAMETER) +#undef HANDLE_MAYBE_BIND_SLOT_PARAMETER + } + +#define DEFINE_SET_PARAMETER_METHOD(_index, e, member, _variant_type, native_type) \ + _FORCE_INLINE_ void set_parameter_##member(const native_type p_value, bool p_test_only) { \ + if (p_test_only) { \ + return; \ + } \ + Variant &prop = *parameter_ptrs_by_slot[SLOT_##e]; \ + prop = p_value; \ + } + ANIM_SLOT_LIST(DEFINE_SET_PARAMETER_METHOD) +#undef DEFINE_SET_PARAMETER_METHOD + +#define DEFINE_GET_PARAMETER_METHOD(_index, e, member, _variant_type, native_type) \ + _FORCE_INLINE_ native_type get_parameter_##member() const { \ + return *parameter_ptrs_by_slot[SLOT_##e]; \ + } + ANIM_SLOT_LIST(DEFINE_GET_PARAMETER_METHOD) +#undef DEFINE_GET_PARAMETER_METHOD + + _FORCE_INLINE_ void set_parameter(const StringName &p_name, const Variant &p_value, bool p_test_only) { + if (p_test_only) { + return; + } + +#define HANDLE_SET_SLOT_PARAMETER(_index, slot, name, variant_type, _native_type) \ + if (p_name == SNAME(#name)) { \ + ERR_FAIL_COND(p_value.get_type() != Variant::variant_type); \ + Variant &prop = *parameter_ptrs_by_slot[SLOT_##slot]; \ + prop = p_value; \ + return; \ + } + ANIM_SLOT_LIST(HANDLE_SET_SLOT_PARAMETER) +#undef HANDLE_SET_SLOT_PARAMETER + + Variant **p = property_ptrs.getptr(p_name); + ERR_FAIL_NULL(p); + Variant &prop = **p; + + // Only copy variant if needed. + if (Animation::needs_type_cast(prop, p_value)) { + Variant value = p_value; + if (Animation::validate_type_match(prop, value)) { + prop = value; + } + } else { + prop = p_value; + } + } + + _FORCE_INLINE_ Variant &get_parameter(const StringName &p_name) { + static Variant dummy = Variant(); + +#define HANDLE_GET_SLOT_PARAMETER(_index, slot, name, _variant_type, _native_type) \ + if (p_name == SNAME(#name)) { \ + return *parameter_ptrs_by_slot[SLOT_##slot]; \ + } + ANIM_SLOT_LIST(HANDLE_GET_SLOT_PARAMETER) +#undef HANDLE_GET_SLOT_PARAMETER + + Variant **p = property_ptrs.getptr(p_name); + ERR_FAIL_NULL_V(p, dummy); + Variant &prop = **p; + return prop; + } + + _FORCE_INLINE_ AnimationNodeInstance *get_child_instance_by_path_or_null(const StringName &p_path) { + AnimationNodeInstance **instance_ptr = child_instances.getptr(p_path); + if (!instance_ptr) { + return nullptr; + } + AnimationNodeInstance *instance = *instance_ptr; + return instance; + } + + _FORCE_INLINE_ AnimationNodeInstance &get_child_instance_by_path(const StringName &p_path) { + AnimationNodeInstance **instance_ptr = child_instances.getptr(p_path); + CRASH_COND_MSG(!instance_ptr, "No child instance found for path: \"" + String(p_path) + "\"."); + AnimationNodeInstance *instance = *instance_ptr; + CRASH_COND_MSG(!instance, "Child instance pointer is null for path: \"" + String(p_path) + "\"."); + return *instance; + } +}; + class AnimationTree : public AnimationMixer { GDCLASS(AnimationTree, AnimationMixer); @@ -279,32 +445,32 @@ class AnimationTree : public AnimationMixer { AnimationNode::ProcessState process_state; uint64_t process_pass = 1; + uint64_t last_track_map_version = 0; bool started = true; friend class AnimationNode; - mutable List properties; - mutable AHashMap> property_parent_map; - mutable AHashMap property_reference_map; + mutable LocalVector properties; mutable AHashMap> property_map; // Property value and read-only flag. + mutable AHashMap instance_map; + mutable AHashMap> instance_paths; mutable bool properties_dirty = true; + mutable bool validation_dirty = true; + mutable bool validation_successful = false; void _update_properties() const; - void _update_properties_for_node(const String &p_base_path, Ref p_node) const; + void _validate_animation_graph(const StringName &p_path, const Ref &p_node) const; + void _update_connections(); + void _add_validation_error(const StringName &p_path, const String &p_error, int p_input_index = -1) const; + void _update_properties_for_node(const StringName &p_base_path, const Ref &p_node) const; void _tree_changed(); + void _node_updated(const ObjectID &p_oid); void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name); void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node); - struct Activity { - uint64_t last_pass = 0; - real_t activity = 0.0; - }; - mutable AHashMap> input_activity_map; - mutable AHashMap input_activity_map_get; - NodePath animation_player; void _setup_animation_player(); @@ -334,6 +500,36 @@ class AnimationTree : public AnimationMixer { #endif // DISABLE_DEPRECATED public: + AnimationNodeInstance &get_node_instance_by_path(const StringName &p_path) { + AnimationNodeInstance *instance = instance_map.getptr(p_path); + CRASH_COND_MSG(instance == nullptr, vformat(R"(No instance found for path "%s".)", String(p_path))); + return *instance; + } + + AnimationNodeInstance *get_node_instance_by_path_or_null(const StringName &p_path) const { + return instance_map.getptr(p_path); + } + +#if TOOLS_ENABLED + // Used in situations where instance_map is not built. + // Intended for the editor, because its very slow. + Ref get_animation_node_by_path(const StringName &p_path) const { + Ref current = root_animation_node; + + int name_count = String(p_path).substr(0, String(p_path).length() - 1).count("/"); + for (int i = 0; i < name_count; i++) { + if (current.is_null()) { + return Ref(); + } + + StringName child_name = StringName(String(p_path).get_slicec('/', i + 1)); + current = current->get_child_by_name(child_name); + } + + return current; + } +#endif + void set_animation_player(const NodePath &p_path); NodePath get_animation_player() const; @@ -346,12 +542,10 @@ class AnimationTree : public AnimationMixer { PackedStringArray get_configuration_warnings() const override; bool is_state_invalid() const; - String get_invalid_state_reason() const; + const AHashMap &get_invalid_instances() const; real_t get_connection_activity(const StringName &p_path, int p_connection) const; - uint64_t get_last_process_pass() const; - #ifdef TOOLS_ENABLED String get_editor_error_message() const; #endif diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index cf344895471c..933ee94dd5d6 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -1837,7 +1837,7 @@ void GraphEdit::_minimap_draw() { Vector2 node_size = minimap->_convert_from_graph_position(graph_frame->get_size() * zoom); Rect2 node_rect = Rect2(node_position, node_size); - Ref sb_minimap = minimap->theme_cache.node_style->duplicate(); + Ref sb_minimap = minimap->theme_cache.node_style; // Override default values with colors provided by the GraphNode's stylebox, if possible. Ref sb_frame = graph_frame->get_theme_stylebox(graph_frame->is_selected() ? SNAME("panel_selected") : SceneStringName(panel)); @@ -1846,6 +1846,7 @@ void GraphEdit::_minimap_draw() { if (graph_frame->is_tint_color_enabled()) { node_color = graph_frame->get_tint_color(); } + sb_minimap = sb_minimap->duplicate(); sb_minimap->set_bg_color(node_color); } @@ -1863,12 +1864,13 @@ void GraphEdit::_minimap_draw() { Vector2 node_size = minimap->_convert_from_graph_position(graph_node->get_size() * zoom); Rect2 node_rect = Rect2(node_position, node_size); - Ref sb_minimap = minimap->theme_cache.node_style->duplicate(); + Ref sb_minimap = minimap->theme_cache.node_style; // Override default values with colors provided by the GraphNode's stylebox, if possible. Ref sb_frame = graph_node->is_selected() ? graph_node->theme_cache.panel_selected : graph_node->theme_cache.panel; if (sb_frame.is_valid()) { Color node_color = sb_frame->get_bg_color(); + sb_minimap = sb_minimap->duplicate(); sb_minimap->set_bg_color(node_color); } @@ -2332,6 +2334,10 @@ void GraphEdit::key_input(const Ref &p_ev) { } void GraphEdit::_pan_callback(Vector2 p_scroll_vec, Ref p_event) { + pan_to(p_scroll_vec); +} + +void GraphEdit::pan_to(const Vector2 &p_scroll_vec) { ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); scroll_offset = (scroll_offset - p_scroll_vec).clamp(min_scroll_offset, max_scroll_offset - get_size()); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index d5f746904320..9603e51cdd02 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -529,6 +529,7 @@ class GraphEdit : public Control { bool is_connection_lines_antialiased() const; HBoxContainer *get_menu_hbox(); + void pan_to(const Vector2 &p_scroll_vec); Ref get_panner(); void set_warped_panning(bool p_warped); void update_warped_panning(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 66198b888c9b..7ec2b1ba4185 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -1090,7 +1090,7 @@ void Animation::track_set_interpolation_loop_wrap(int p_track, bool p_enable) { } bool Animation::track_get_interpolation_loop_wrap(int p_track) const { - ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_track, tracks.size(), INTERPOLATION_NEAREST); + ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_track, tracks.size(), false); return tracks[p_track]->loop_wrap; } @@ -1185,7 +1185,7 @@ Error Animation::position_track_get_key(int p_track, int p_key, Vector3 *r_posit return OK; } -Error Animation::try_position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward) const { +Error Animation::try_position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward, KeyCursor *r_cursor) const { ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_track, tracks.size(), ERR_INVALID_PARAMETER); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_POSITION_3D, ERR_INVALID_PARAMETER); @@ -1202,7 +1202,7 @@ Error Animation::try_position_track_interpolate(int p_track, double p_time, Vect bool ok = false; - Vector3 tk = _interpolate(tt->positions, p_time, tt->interpolation, tt->loop_wrap, &ok, p_backward); + Vector3 tk = _interpolate(tt->positions, p_time, tt->interpolation, tt->loop_wrap, &ok, p_backward, r_cursor); if (!ok) { return ERR_UNAVAILABLE; @@ -1265,7 +1265,7 @@ Error Animation::rotation_track_get_key(int p_track, int p_key, Quaternion *r_ro return OK; } -Error Animation::try_rotation_track_interpolate(int p_track, double p_time, Quaternion *r_interpolation, bool p_backward) const { +Error Animation::try_rotation_track_interpolate(int p_track, double p_time, Quaternion *r_interpolation, bool p_backward, KeyCursor *r_cursor) const { ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_track, tracks.size(), ERR_INVALID_PARAMETER); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_ROTATION_3D, ERR_INVALID_PARAMETER); @@ -1282,7 +1282,7 @@ Error Animation::try_rotation_track_interpolate(int p_track, double p_time, Quat bool ok = false; - Quaternion tk = _interpolate(rt->rotations, p_time, rt->interpolation, rt->loop_wrap, &ok, p_backward); + Quaternion tk = _interpolate(rt->rotations, p_time, rt->interpolation, rt->loop_wrap, &ok, p_backward, r_cursor); if (!ok) { return ERR_UNAVAILABLE; @@ -1294,8 +1294,8 @@ Error Animation::try_rotation_track_interpolate(int p_track, double p_time, Quat Quaternion Animation::rotation_track_interpolate(int p_track, double p_time, bool p_backward) const { Quaternion ret = Quaternion(0, 0, 0, 1); ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_track, tracks.size(), ret); - bool err = try_rotation_track_interpolate(p_track, p_time, &ret, p_backward); - ERR_FAIL_COND_V_MSG(err, ret, "3D Rotation Track: '" + String(tracks[p_track]->path) + "' is unavailable."); + Error err = try_rotation_track_interpolate(p_track, p_time, &ret, p_backward); + ERR_FAIL_COND_V_MSG(err != OK, ret, "3D Rotation Track: '" + String(tracks[p_track]->path) + "' is unavailable."); return ret; } @@ -1345,7 +1345,7 @@ Error Animation::scale_track_get_key(int p_track, int p_key, Vector3 *r_scale) c return OK; } -Error Animation::try_scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward) const { +Error Animation::try_scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward, KeyCursor *r_cursor) const { ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_track, tracks.size(), ERR_INVALID_PARAMETER); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_SCALE_3D, ERR_INVALID_PARAMETER); @@ -1362,7 +1362,7 @@ Error Animation::try_scale_track_interpolate(int p_track, double p_time, Vector3 bool ok = false; - Vector3 tk = _interpolate(st->scales, p_time, st->interpolation, st->loop_wrap, &ok, p_backward); + Vector3 tk = _interpolate(st->scales, p_time, st->interpolation, st->loop_wrap, &ok, p_backward, r_cursor); if (!ok) { return ERR_UNAVAILABLE; @@ -2452,6 +2452,60 @@ int Animation::_find(const LocalVector &p_keys, double p_time, bool p_backwar return middle; } +// returns -2 if no keys +// returns -1 if out of range +template +int Animation::_find_from_hint(const LocalVector &p_keys, double p_t, bool p_backward, int p_hint) const { + const int len = p_keys.size(); + if (len == 0) { + return -2; + } + + int i = CLAMP(p_hint, 0, len - 1); +#ifdef DEBUG_ENABLED + const int start = i; +#endif + if (!p_backward) { + if (p_keys[i].time <= p_t) { + while (i + 1 < len && p_keys[i + 1].time <= p_t) { + ++i; // move right + } + } else { + while (i >= 0 && p_keys[i].time > p_t) { + --i; // move left, may end at -1 + } + } + + if (i < 0) { +#ifdef DEBUG_ENABLED + _warn_find_from_hint_failed(p_backward, p_t, p_keys[0].time, p_keys[len - 1].time, len, p_hint, start, i); +#endif + return -1; + } + } else { + if (p_keys[i].time >= p_t) { + while (i - 1 >= 0 && p_keys[i - 1].time >= p_t) { + --i; // move left + } + } else { + while (i < len && p_keys[i].time < p_t) { + ++i; // move right, may become len + } + } + + if (i >= len) { +#ifdef DEBUG_ENABLED + _warn_find_from_hint_failed(p_backward, p_t, p_keys[0].time, p_keys[len - 1].time, len, p_hint, start, i); +#endif + return -1; + } + } + + // Maybe consider creating _warn_long_hint_scan, for successful scans, + // because long scans may be indicative of another issue. + return i; +} + // Linear interpolation for anytype. Vector3 Animation::_interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const { @@ -2521,8 +2575,13 @@ Variant Animation::_cubic_interpolate_angle_in_time(const Variant &p_pre_a, cons } template -T Animation::_interpolate(const LocalVector> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward) const { - int len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end) +T Animation::_interpolate(const LocalVector> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward, KeyCursor *r_cursor) const { + int len; + if (p_keys.size() > 0 && p_keys[p_keys.size() - 1].time <= length) { + len = p_keys.size(); // no keys past the end + } else { + len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end) + } if (len <= 0) { // (-1 or -2 returned originally) (plus one above) @@ -2539,7 +2598,15 @@ T Animation::_interpolate(const LocalVector> &p_keys, double p_time, Int return p_keys[0].value; } - int idx = _find(p_keys, p_time, p_backward); + int idx; + if (r_cursor && r_cursor->last_key_idx >= 0) { + idx = _find_from_hint(p_keys, p_time, p_backward, r_cursor->last_key_idx); + } else { + idx = _find(p_keys, p_time, p_backward); + } + if (r_cursor) { + r_cursor->last_key_idx = idx; + } ERR_FAIL_COND_V(idx == -2, T()); int maxi = len - 1; @@ -2765,7 +2832,7 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const } template -void Animation::_track_get_key_indices_in_range(const LocalVector &p_array, double from_time, double to_time, List *p_indices, bool p_is_backward) const { +void Animation::_track_get_key_indices_in_range(const LocalVector &p_array, double from_time, double to_time, LocalVector *r_indices, bool p_is_backward) const { int len = p_array.size(); if (len == 0) { return; @@ -2803,22 +2870,22 @@ void Animation::_track_get_key_indices_in_range(const LocalVector &p_array, d } if (from == to) { - p_indices->push_back(from); + r_indices->push_back(from); return; } if (!p_is_backward) { for (int i = from; i <= to; i++) { - p_indices->push_back(i); + r_indices->push_back(i); } } else { for (int i = to; i >= from; i--) { - p_indices->push_back(i); + r_indices->push_back(i); } } } -void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List *p_indices, Animation::LoopedFlag p_looped_flag) const { +void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, LocalVector *r_indices, Animation::LoopedFlag p_looped_flag) const { ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_track, tracks.size()); if (p_delta == 0) { @@ -2869,111 +2936,111 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl case TYPE_POSITION_3D: { const PositionTrack *tt = static_cast(t); if (tt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, r_indices); } else { if (!is_backward) { - _track_get_key_indices_in_range(tt->positions, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(tt->positions, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(tt->positions, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(tt->positions, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(tt->positions, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(tt->positions, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(tt->positions, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(tt->positions, from_time, anim_end, r_indices, is_backward); } } } break; case TYPE_ROTATION_3D: { const RotationTrack *rt = static_cast(t); if (rt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, r_indices); } else { if (!is_backward) { - _track_get_key_indices_in_range(rt->rotations, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(rt->rotations, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(rt->rotations, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(rt->rotations, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(rt->rotations, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(rt->rotations, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(rt->rotations, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(rt->rotations, from_time, anim_end, r_indices, is_backward); } } } break; case TYPE_SCALE_3D: { const ScaleTrack *st = static_cast(t); if (st->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, r_indices); } else { if (!is_backward) { - _track_get_key_indices_in_range(st->scales, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(st->scales, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(st->scales, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(st->scales, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(st->scales, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(st->scales, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(st->scales, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(st->scales, from_time, anim_end, r_indices, is_backward); } } } break; case TYPE_BLEND_SHAPE: { const BlendShapeTrack *bst = static_cast(t); if (bst->compressed_track >= 0) { - _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, r_indices); } else { if (!is_backward) { - _track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, r_indices, is_backward); } } } break; case TYPE_VALUE: { const ValueTrack *vt = static_cast(t); if (!is_backward) { - _track_get_key_indices_in_range(vt->values, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(vt->values, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(vt->values, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(vt->values, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(vt->values, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(vt->values, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(vt->values, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(vt->values, from_time, anim_end, r_indices, is_backward); } } break; case TYPE_METHOD: { const MethodTrack *mt = static_cast(t); if (!is_backward) { - _track_get_key_indices_in_range(mt->methods, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(mt->methods, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(mt->methods, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(mt->methods, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(mt->methods, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(mt->methods, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(mt->methods, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(mt->methods, from_time, anim_end, r_indices, is_backward); } } break; case TYPE_BEZIER: { const BezierTrack *bz = static_cast(t); if (!is_backward) { - _track_get_key_indices_in_range(bz->values, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(bz->values, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(bz->values, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(bz->values, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(bz->values, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(bz->values, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(bz->values, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(bz->values, from_time, anim_end, r_indices, is_backward); } } break; case TYPE_AUDIO: { const AudioTrack *ad = static_cast(t); if (!is_backward) { - _track_get_key_indices_in_range(ad->values, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(ad->values, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(ad->values, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(ad->values, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(ad->values, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(ad->values, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(ad->values, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(ad->values, from_time, anim_end, r_indices, is_backward); } } break; case TYPE_ANIMATION: { const AnimationTrack *an = static_cast(t); if (!is_backward) { - _track_get_key_indices_in_range(an->values, from_time, anim_end, p_indices, is_backward); - _track_get_key_indices_in_range(an->values, anim_start, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(an->values, from_time, anim_end, r_indices, is_backward); + _track_get_key_indices_in_range(an->values, anim_start, to_time, r_indices, is_backward); } else { - _track_get_key_indices_in_range(an->values, anim_start, to_time, p_indices, is_backward); - _track_get_key_indices_in_range(an->values, from_time, anim_end, p_indices, is_backward); + _track_get_key_indices_in_range(an->values, anim_start, to_time, r_indices, is_backward); + _track_get_key_indices_in_range(an->values, from_time, anim_end, r_indices, is_backward); } } break; } @@ -2985,12 +3052,12 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl if (!is_backward && Math::is_equal_approx(from_time, 0)) { int edge = track_find_key(p_track, 0, FIND_MODE_EXACT); if (edge >= 0) { - p_indices->push_back(edge); + r_indices->push_back(edge); } } else if (is_backward && Math::is_equal_approx(to_time, length)) { int edge = track_find_key(p_track, length, FIND_MODE_EXACT); if (edge >= 0) { - p_indices->push_back(edge); + r_indices->push_back(edge); } } } @@ -3009,67 +3076,67 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl case TYPE_POSITION_3D: { const PositionTrack *tt = static_cast(t); if (tt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices); - _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, r_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, r_indices); } else { - _track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(tt->positions, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(tt->positions, 0, to_time, r_indices, false); } } break; case TYPE_ROTATION_3D: { const RotationTrack *rt = static_cast(t); if (rt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices); - _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, r_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, r_indices); } else { - _track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(rt->rotations, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(rt->rotations, 0, to_time, r_indices, false); } } break; case TYPE_SCALE_3D: { const ScaleTrack *st = static_cast(t); if (st->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, p_indices); - _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, r_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, r_indices); } else { - _track_get_key_indices_in_range(st->scales, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(st->scales, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(st->scales, 0, to_time, r_indices, false); } } break; case TYPE_BLEND_SHAPE: { const BlendShapeTrack *bst = static_cast(t); if (bst->compressed_track >= 0) { - _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, p_indices); - _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, r_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, r_indices); } else { - _track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, r_indices, false); } } break; case TYPE_VALUE: { const ValueTrack *vt = static_cast(t); - _track_get_key_indices_in_range(vt->values, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(vt->values, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(vt->values, 0, to_time, r_indices, false); } break; case TYPE_METHOD: { const MethodTrack *mt = static_cast(t); - _track_get_key_indices_in_range(mt->methods, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(mt->methods, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(mt->methods, 0, to_time, r_indices, false); } break; case TYPE_BEZIER: { const BezierTrack *bz = static_cast(t); - _track_get_key_indices_in_range(bz->values, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(bz->values, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(bz->values, 0, to_time, r_indices, false); } break; case TYPE_AUDIO: { const AudioTrack *ad = static_cast(t); - _track_get_key_indices_in_range(ad->values, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(ad->values, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(ad->values, 0, to_time, r_indices, false); } break; case TYPE_ANIMATION: { const AnimationTrack *an = static_cast(t); - _track_get_key_indices_in_range(an->values, 0, from_time, p_indices, true); - _track_get_key_indices_in_range(an->values, 0, to_time, p_indices, false); + _track_get_key_indices_in_range(an->values, 0, from_time, r_indices, true); + _track_get_key_indices_in_range(an->values, 0, to_time, r_indices, false); } break; } return; @@ -3080,67 +3147,67 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl case TYPE_POSITION_3D: { const PositionTrack *tt = static_cast(t); if (tt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length, r_indices); } else { - _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices, false); - _track_get_key_indices_in_range(tt->positions, to_time, length, p_indices, true); + _track_get_key_indices_in_range(tt->positions, from_time, length, r_indices, false); + _track_get_key_indices_in_range(tt->positions, to_time, length, r_indices, true); } } break; case TYPE_ROTATION_3D: { const RotationTrack *rt = static_cast(t); if (rt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, r_indices); } else { - _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices, false); - _track_get_key_indices_in_range(rt->rotations, to_time, length, p_indices, true); + _track_get_key_indices_in_range(rt->rotations, from_time, length, r_indices, false); + _track_get_key_indices_in_range(rt->rotations, to_time, length, r_indices, true); } } break; case TYPE_SCALE_3D: { const ScaleTrack *st = static_cast(t); if (st->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, r_indices); } else { - _track_get_key_indices_in_range(st->scales, from_time, length, p_indices, false); - _track_get_key_indices_in_range(st->scales, to_time, length, p_indices, true); + _track_get_key_indices_in_range(st->scales, from_time, length, r_indices, false); + _track_get_key_indices_in_range(st->scales, to_time, length, r_indices, true); } } break; case TYPE_BLEND_SHAPE: { const BlendShapeTrack *bst = static_cast(t); if (bst->compressed_track >= 0) { - _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length, p_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, r_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length, r_indices); } else { - _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices, false); - _track_get_key_indices_in_range(bst->blend_shapes, to_time, length, p_indices, true); + _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, r_indices, false); + _track_get_key_indices_in_range(bst->blend_shapes, to_time, length, r_indices, true); } } break; case TYPE_VALUE: { const ValueTrack *vt = static_cast(t); - _track_get_key_indices_in_range(vt->values, from_time, length, p_indices, false); - _track_get_key_indices_in_range(vt->values, to_time, length, p_indices, true); + _track_get_key_indices_in_range(vt->values, from_time, length, r_indices, false); + _track_get_key_indices_in_range(vt->values, to_time, length, r_indices, true); } break; case TYPE_METHOD: { const MethodTrack *mt = static_cast(t); - _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices, false); - _track_get_key_indices_in_range(mt->methods, to_time, length, p_indices, true); + _track_get_key_indices_in_range(mt->methods, from_time, length, r_indices, false); + _track_get_key_indices_in_range(mt->methods, to_time, length, r_indices, true); } break; case TYPE_BEZIER: { const BezierTrack *bz = static_cast(t); - _track_get_key_indices_in_range(bz->values, from_time, length, p_indices, false); - _track_get_key_indices_in_range(bz->values, to_time, length, p_indices, true); + _track_get_key_indices_in_range(bz->values, from_time, length, r_indices, false); + _track_get_key_indices_in_range(bz->values, to_time, length, r_indices, true); } break; case TYPE_AUDIO: { const AudioTrack *ad = static_cast(t); - _track_get_key_indices_in_range(ad->values, from_time, length, p_indices, false); - _track_get_key_indices_in_range(ad->values, to_time, length, p_indices, true); + _track_get_key_indices_in_range(ad->values, from_time, length, r_indices, false); + _track_get_key_indices_in_range(ad->values, to_time, length, r_indices, true); } break; case TYPE_ANIMATION: { const AnimationTrack *an = static_cast(t); - _track_get_key_indices_in_range(an->values, from_time, length, p_indices, false); - _track_get_key_indices_in_range(an->values, to_time, length, p_indices, true); + _track_get_key_indices_in_range(an->values, from_time, length, r_indices, false); + _track_get_key_indices_in_range(an->values, to_time, length, r_indices, true); } break; } return; @@ -3158,54 +3225,54 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl case TYPE_POSITION_3D: { const PositionTrack *tt = static_cast(t); if (tt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, to_time - from_time, p_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, to_time - from_time, r_indices); } else { - _track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(tt->positions, from_time, to_time, r_indices, is_backward); } } break; case TYPE_ROTATION_3D: { const RotationTrack *rt = static_cast(t); if (rt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, to_time - from_time, p_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, to_time - from_time, r_indices); } else { - _track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(rt->rotations, from_time, to_time, r_indices, is_backward); } } break; case TYPE_SCALE_3D: { const ScaleTrack *st = static_cast(t); if (st->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, to_time - from_time, p_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, to_time - from_time, r_indices); } else { - _track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(st->scales, from_time, to_time, r_indices, is_backward); } } break; case TYPE_BLEND_SHAPE: { const BlendShapeTrack *bst = static_cast(t); if (bst->compressed_track >= 0) { - _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, to_time - from_time, p_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, to_time - from_time, r_indices); } else { - _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, r_indices, is_backward); } } break; case TYPE_VALUE: { const ValueTrack *vt = static_cast(t); - _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(vt->values, from_time, to_time, r_indices, is_backward); } break; case TYPE_METHOD: { const MethodTrack *mt = static_cast(t); - _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(mt->methods, from_time, to_time, r_indices, is_backward); } break; case TYPE_BEZIER: { const BezierTrack *bz = static_cast(t); - _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(bz->values, from_time, to_time, r_indices, is_backward); } break; case TYPE_AUDIO: { const AudioTrack *ad = static_cast(t); - _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(ad->values, from_time, to_time, r_indices, is_backward); } break; case TYPE_ANIMATION: { const AnimationTrack *an = static_cast(t); - _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices, is_backward); + _track_get_key_indices_in_range(an->values, from_time, to_time, r_indices, is_backward); } break; } } @@ -3844,7 +3911,7 @@ StringName Animation::animation_track_get_key_animation(int p_track, int p_key) return at->values[p_key].value; } -void Animation::set_length(real_t p_length) { +void Animation::set_length(double p_length) { if (p_length < ANIM_MIN_LENGTH) { p_length = ANIM_MIN_LENGTH; } @@ -3852,19 +3919,11 @@ void Animation::set_length(real_t p_length) { emit_changed(); } -real_t Animation::get_length() const { - return length; -} - void Animation::set_loop_mode(Animation::LoopMode p_loop_mode) { loop_mode = p_loop_mode; emit_changed(); } -Animation::LoopMode Animation::get_loop_mode() const { - return loop_mode; -} - void Animation::track_set_imported(int p_track, bool p_imported) { ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_track, tracks.size()); tracks[p_track]->imported = p_imported; @@ -5486,7 +5545,7 @@ bool Animation::_fetch_compressed(uint32_t p_compressed_track, double p_time, Ve } template -void Animation::_get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List *r_indices) const { +void Animation::_get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, LocalVector *r_indices) const { ERR_FAIL_COND(!compression.enabled); ERR_FAIL_UNSIGNED_INDEX(p_compressed_track, compression.bounds.size()); @@ -5704,6 +5763,24 @@ bool Animation::is_variant_interpolatable(const Variant p_value) { return (type >= Variant::BOOL && type <= Variant::STRING_NAME) || type == Variant::ARRAY || type >= Variant::PACKED_INT32_ARRAY; // PackedByteArray is unsigned, so it would be better to ignore since blending uses float. } +bool Animation::needs_type_cast(const Variant &p_from, const Variant &p_to) { + Variant::Type from_type = p_from.get_type(); + Variant::Type to_type = p_to.get_type(); + + if (from_type == to_type) { + return false; + } + + // Cast between double and int to avoid minor annoyances. + if (from_type == Variant::FLOAT && to_type == Variant::INT) { + return true; + } else if (from_type == Variant::INT && to_type == Variant::FLOAT) { + return true; + } + + return false; +} + bool Animation::validate_type_match(const Variant &p_from, Variant &r_to) { if (p_from.get_type() != r_to.get_type()) { // Cast r_to between double and int to avoid minor annoyances. diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 013d0b112384..59c3a8a54c64 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -115,6 +115,15 @@ class Animation : public Resource { virtual ~Track() {} }; + struct KeyCursor { + // Used by _interpolate to avoid binary searching every time. + int last_key_idx = -1; + + _ALWAYS_INLINE_ void reset() { + last_key_idx = -1; + } + }; + private: struct Key { real_t transition = 1.0; @@ -261,6 +270,30 @@ class Animation : public Resource { inline int _find(const LocalVector &p_keys, double p_time, bool p_backward = false, bool p_limit = false) const; + template + inline int _find_from_hint(const LocalVector &p_keys, double p_t, bool p_backward, int p_hint) const; +#ifdef DEBUG_ENABLED + _FORCE_INLINE_ static void _warn_find_from_hint_failed(bool p_backward, double p_time, double p_first_key_time, double p_last_key_time, int p_key_count, int p_hint, int p_start_index, int p_end_index) { + const int delta = p_end_index - p_start_index; + constexpr int WARN_KEYS = 200; + if (unlikely(Math::abs(delta) > WARN_KEYS)) { + static const char *const message = R"(Animation::_find_from_hint failed after large scan + direction: %s + time: %f + first_key_time: %f + last_key_time: %f + key_count: %d + hint: %d + start_index: %d + end_index: %d + delta: %d +)"; + const char *direction = p_backward ? "backward" : "forward"; + print_verbose(vformat(message, direction, p_time, p_first_key_time, p_last_key_time, p_key_count, p_hint, p_start_index, p_end_index, delta)); + } + } +#endif + _FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const; _FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const; _FORCE_INLINE_ Variant _interpolate(const Variant &p_a, const Variant &p_b, real_t p_c) const; @@ -274,10 +307,10 @@ class Animation : public Resource { _FORCE_INLINE_ Variant _cubic_interpolate_angle_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; template - _FORCE_INLINE_ T _interpolate(const LocalVector> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const; + _FORCE_INLINE_ T _interpolate(const LocalVector> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false, KeyCursor *r_cursor = nullptr) const; template - _FORCE_INLINE_ void _track_get_key_indices_in_range(const LocalVector &p_array, double from_time, double to_time, List *p_indices, bool p_is_backward) const; + _FORCE_INLINE_ void _track_get_key_indices_in_range(const LocalVector &p_array, double from_time, double to_time, LocalVector *r_indices, bool p_is_backward) const; double length = 1.0; real_t step = DEFAULT_STEP; @@ -365,7 +398,7 @@ class Animation : public Resource { bool _fetch_compressed_by_index(uint32_t p_compressed_track, int p_index, Vector3i &r_value, double &r_time) const; int _get_compressed_key_count(uint32_t p_compressed_track) const; template - void _get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List *r_indices) const; + void _get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, LocalVector *r_indices) const; _FORCE_INLINE_ Quaternion _uncompress_quaternion(const Vector3i &p_value) const; _FORCE_INLINE_ Vector3 _uncompress_pos_scale(uint32_t p_compressed_track, const Vector3i &p_value) const; _FORCE_INLINE_ float _uncompress_blend_shape(const Vector3i &p_value) const; @@ -449,17 +482,17 @@ class Animation : public Resource { int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position); Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const; - Error try_position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward = false) const; + Error try_position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward = false, KeyCursor *r_cursor = nullptr) const; Vector3 position_track_interpolate(int p_track, double p_time, bool p_backward = false) const; int rotation_track_insert_key(int p_track, double p_time, const Quaternion &p_rotation); Error rotation_track_get_key(int p_track, int p_key, Quaternion *r_rotation) const; - Error try_rotation_track_interpolate(int p_track, double p_time, Quaternion *r_interpolation, bool p_backward = false) const; + Error try_rotation_track_interpolate(int p_track, double p_time, Quaternion *r_interpolation, bool p_backward = false, KeyCursor *r_cursor = nullptr) const; Quaternion rotation_track_interpolate(int p_track, double p_time, bool p_backward = false) const; int scale_track_insert_key(int p_track, double p_time, const Vector3 &p_scale); Error scale_track_get_key(int p_track, int p_key, Vector3 *r_scale) const; - Error try_scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward = false) const; + Error try_scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward = false, KeyCursor *r_cursor = nullptr) const; Vector3 scale_track_interpolate(int p_track, double p_time, bool p_backward = false) const; int blend_shape_track_insert_key(int p_track, double p_time, float p_blend); @@ -513,7 +546,7 @@ class Animation : public Resource { void copy_track(int p_track, Ref p_to_animation); - void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List *p_indices, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE) const; + void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, LocalVector *r_indices, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE) const; void add_marker(const StringName &p_name, double p_time); void remove_marker(const StringName &p_name); @@ -526,11 +559,11 @@ class Animation : public Resource { Color get_marker_color(const StringName &p_name) const; void set_marker_color(const StringName &p_name, const Color &p_color); - void set_length(real_t p_length); - real_t get_length() const; + void set_length(double p_length); + _FORCE_INLINE_ double get_length() const { return length; } void set_loop_mode(LoopMode p_loop_mode); - LoopMode get_loop_mode() const; + _FORCE_INLINE_ LoopMode get_loop_mode() const { return loop_mode; } void set_step(real_t p_step); real_t get_step() const; @@ -546,6 +579,7 @@ class Animation : public Resource { // Helper functions for Variant. static bool is_variant_interpolatable(const Variant p_value); + static bool needs_type_cast(const Variant &p_from, const Variant &p_to); static bool validate_type_match(const Variant &p_from, Variant &r_to); static Variant cast_to_blendwise(const Variant p_value); diff --git a/tests/scene/test_animation_blend_tree.h b/tests/scene/test_animation_blend_tree.h index 3d0727d66595..2c4b362fda75 100644 --- a/tests/scene/test_animation_blend_tree.h +++ b/tests/scene/test_animation_blend_tree.h @@ -61,7 +61,7 @@ TEST_CASE("[SceneTree][AnimationBlendTree] Create AnimationBlendTree and add Ani CHECK_EQ(blend_tree->can_connect_node("output", 0, "test_node"), AnimationNodeBlendTree::CONNECTION_OK); blend_tree->connect_node("output", 0, "test_node"); - Vector connections = blend_tree->get_node_connection_array("output"); + LocalVector connections = *blend_tree->get_node_connection_array("output"); CHECK_EQ(connections.size(), 1); CHECK_EQ(connections[0], StringName("test_node")); @@ -70,14 +70,14 @@ TEST_CASE("[SceneTree][AnimationBlendTree] Create AnimationBlendTree and add Ani CHECK_FALSE(blend_tree->has_node("test_node")); CHECK(blend_tree->has_node("renamed_node")); - connections = blend_tree->get_node_connection_array("output"); + connections = *blend_tree->get_node_connection_array("output"); CHECK_EQ(connections[0], StringName("renamed_node")); // Test node removal. blend_tree->remove_node("renamed_node"); CHECK_FALSE(blend_tree->has_node("renamed_node")); - connections = blend_tree->get_node_connection_array("output"); + connections = *blend_tree->get_node_connection_array("output"); CHECK_EQ(connections[0], StringName()); }