From aee80858fdd421599637b73b99fb892d76edb15b Mon Sep 17 00:00:00 2001 From: Ryan <73148864+Ryan-000@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:28:41 -0400 Subject: [PATCH] Fix numerous blend space issues --- .../animation_blend_space_1d_editor.cpp | 3 ++- .../animation_blend_space_2d_editor.cpp | 15 +++++++------ scene/animation/animation_blend_space_1d.cpp | 18 ++++++++++------ scene/animation/animation_blend_space_1d.h | 6 ++++++ scene/animation/animation_blend_space_2d.cpp | 21 ++++++++++++++----- scene/animation/animation_blend_space_2d.h | 6 ++++++ scene/animation/animation_tree.cpp | 18 +++++++++++++++- 7 files changed, 68 insertions(+), 19 deletions(-) diff --git a/editor/animation/animation_blend_space_1d_editor.cpp b/editor/animation/animation_blend_space_1d_editor.cpp index cf6c92f7b5d0..409ac52171cb 100644 --- a/editor/animation/animation_blend_space_1d_editor.cpp +++ b/editor/animation/animation_blend_space_1d_editor.cpp @@ -408,7 +408,8 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { } void AnimationNodeBlendSpace1DEditor::_update_space() { - if (updating) { + // edge case when undoing action after editor has changed + if (updating || !blend_space.is_valid()) { return; } diff --git a/editor/animation/animation_blend_space_2d_editor.cpp b/editor/animation/animation_blend_space_2d_editor.cpp index 530812a0be8b..9415d829cd00 100644 --- a/editor/animation/animation_blend_space_2d_editor.cpp +++ b/editor/animation/animation_blend_space_2d_editor.cpp @@ -747,7 +747,7 @@ void AnimationNodeBlendSpace2DEditor::_snap_toggled() { } void AnimationNodeBlendSpace2DEditor::_update_space() { - if (updating) { + if (updating || !blend_space.is_valid()) { return; } @@ -852,12 +852,15 @@ void AnimationNodeBlendSpace2DEditor::_erase_selected() { undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point); undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", node, position, selected_point, point_name); + // if auto triangles is off //restore triangles using this point - for (int i = 0; i < blend_space->get_triangle_count(); i++) { - for (int j = 0; j < 3; j++) { - if (blend_space->get_triangle_point(i, j) == selected_point) { - undo_redo->add_undo_method(blend_space.ptr(), "add_triangle", blend_space->get_triangle_point(i, 0), blend_space->get_triangle_point(i, 1), blend_space->get_triangle_point(i, 2), i); - break; + if (!blend_space->get_auto_triangles()) { + for (int i = 0; i < blend_space->get_triangle_count(); i++) { + for (int j = 0; j < 3; j++) { + if (blend_space->get_triangle_point(i, j) == selected_point) { + undo_redo->add_undo_method(blend_space.ptr(), "add_triangle", blend_space->get_triangle_point(i, 0), blend_space->get_triangle_point(i, 1), blend_space->get_triangle_point(i, 2), i); + break; + } } } } diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 6128ecb7895f..e294e326b2e1 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -166,10 +166,12 @@ void AnimationNodeBlendSpace1D::add_blend_point(const Ref &p_ #else ERR_FAIL_COND(p_name == StringName()); #endif + ERR_FAIL_COND(p_name.is_empty() || String(p_name).contains_char('.') || String(p_name).contains_char('/')); + ERR_FAIL_COND_MSG(find_blend_point_by_name(p_name) != -1, "Blend point name must be unique."); if (p_at_index == -1 || p_at_index == blend_points_used) { p_at_index = blend_points_used; } else { - for (int i = blend_points_used - 1; i > p_at_index; i--) { + for (int i = blend_points_used; i > p_at_index; i--) { blend_points[i] = blend_points[i - 1]; } } @@ -207,12 +209,12 @@ void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref AnimationNodeBlendSpace1D::get_blend_point_node(int p_point) const { - ERR_FAIL_INDEX_V(p_point, MAX_BLEND_POINTS, Ref()); + ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref()); return blend_points[p_point].node; } @@ -220,6 +222,8 @@ void AnimationNodeBlendSpace1D::set_blend_point_name(int p_point, const StringNa ERR_FAIL_INDEX(p_point, blend_points_used); String new_name = p_name; ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/')); + int existing_index = find_blend_point_by_name(p_name); + ERR_FAIL_COND_MSG(existing_index != -1 && existing_index != p_point, "Blend point name must be unique."); String old_name = blend_points[p_point].name; if (new_name != old_name) { @@ -247,6 +251,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()); + String removed_name = blend_points[p_point].name; _remove_node(blend_points[p_point].node); for (int i = p_point; i < blend_points_used - 1; i++) { @@ -255,10 +260,10 @@ void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { blend_points_used--; - blend_points[blend_points_used].name = StringName(); + blend_points[blend_points_used].reset(); lengths_dirty = true; - emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point)); + emit_signal(SNAME("animation_node_removed"), get_instance_id(), removed_name); _tree_changed(); } @@ -556,8 +561,9 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(ProcessState &p_ } // Special case for discrete carry. - AnimationNodeInstance *instance_current_closest = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(cur_closest)); if (new_closest != cur_closest && new_closest != -1 && cur_closest != -1 && blend_mode == BLEND_MODE_DISCRETE_CARRY && sync_mode < SYNC_MODE_CYCLIC_MUTABLE) { + // I don't even think these can (or should) ever be null. + AnimationNodeInstance *instance_current_closest = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(cur_closest)); AnimationNodeInstance *instance_new_closest = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(new_closest)); NodeTimeInfo from; // For ping-pong loop. diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 8329b753f035..998b8f65bfd1 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -58,6 +58,12 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { StringName name; Ref node; float position = 0.0; + + void reset() { + name = StringName(); + node = Ref(); + position = 0.0; + } }; BlendPoint blend_points[MAX_BLEND_POINTS]; diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index befe42ad0b1b..8a11a01c49b3 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -78,10 +78,12 @@ void AnimationNodeBlendSpace2D::add_blend_point(const Ref &p_ ERR_FAIL_COND(p_name == StringName()); #endif + ERR_FAIL_COND(p_name.is_empty() || String(p_name).contains_char('.') || String(p_name).contains_char('/')); + ERR_FAIL_COND_MSG(find_blend_point_by_name(p_name) != -1, "Blend point name must be unique."); if (p_at_index == -1 || p_at_index == blend_points_used) { p_at_index = blend_points_used; } else { - for (int i = blend_points_used - 1; i > p_at_index; i--) { + for (int i = blend_points_used; i > p_at_index; i--) { blend_points[i] = blend_points[i - 1]; } for (int i = 0; i < triangles.size(); i++) { @@ -126,12 +128,12 @@ void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref AnimationNodeBlendSpace2D::get_blend_point_node(int p_point) const { - ERR_FAIL_INDEX_V(p_point, MAX_BLEND_POINTS, Ref()); + ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref()); return blend_points[p_point].node; } @@ -139,6 +141,8 @@ void AnimationNodeBlendSpace2D::set_blend_point_name(int p_point, const StringNa ERR_FAIL_INDEX(p_point, blend_points_used); String new_name = p_name; ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/')); + int existing_index = find_blend_point_by_name(p_name); + ERR_FAIL_COND_MSG(existing_index != -1 && existing_index != p_point, "Blend point name must be unique."); String old_name = blend_points[p_point].name; if (new_name != old_name) { @@ -166,6 +170,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()); + String removed_name = blend_points[p_point].name; _remove_node(blend_points[p_point].node); for (int i = 0; i < triangles.size(); i++) { @@ -190,10 +195,12 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { } blend_points_used--; - blend_points[blend_points_used].name = StringName(); + blend_points[blend_points_used].reset(); lengths_dirty = true; - emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point)); + _queue_auto_triangles(); + + emit_signal(SNAME("animation_node_removed"), get_instance_id(), removed_name); _tree_changed(); } @@ -293,6 +300,7 @@ void AnimationNodeBlendSpace2D::add_triangle(int p_x, int p_y, int p_z, int p_at } else { triangles.insert(p_at_index, t); } + _node_updated(get_instance_id()); } int AnimationNodeBlendSpace2D::get_triangle_point(int p_triangle, int p_point) { @@ -307,6 +315,7 @@ void AnimationNodeBlendSpace2D::remove_triangle(int p_triangle) { ERR_FAIL_INDEX(p_triangle, triangles.size()); triangles.remove_at(p_triangle); + _node_updated(get_instance_id()); } int AnimationNodeBlendSpace2D::get_triangle_count() const { @@ -458,6 +467,7 @@ void AnimationNodeBlendSpace2D::_update_triangles() { triangles.clear(); if (blend_points_used < 3) { emit_signal(SNAME("triangles_updated")); + _node_updated(get_instance_id()); return; } @@ -473,6 +483,7 @@ void AnimationNodeBlendSpace2D::_update_triangles() { add_triangle(tr[i].points[0], tr[i].points[1], tr[i].points[2]); } emit_signal(SNAME("triangles_updated")); + _node_updated(get_instance_id()); } Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) { diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index f7f9839004cb..593d7edba6f8 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -58,6 +58,12 @@ class AnimationNodeBlendSpace2D : public AnimationRootNode { StringName name; Ref node; Vector2 position; + + void reset() { + name = StringName(); + node = Ref(); + position = Vector2(); + } }; BlendPoint blend_points[MAX_BLEND_POINTS]; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 7addfb62523a..7926c99e0ff5 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -781,14 +781,30 @@ void AnimationTree::_animation_node_renamed(const ObjectID &p_oid, const String } void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + Object *obj = ObjectDB::get_instance(p_oid); + bool is_blend_space = obj && (obj->is_class("AnimationNodeBlendSpace1D") || obj->is_class("AnimationNodeBlendSpace2D")); + for (const StringName &parent_path : instance_paths[p_oid]) { - String base_path = String(parent_path) + String(p_node); + 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); } } + + // When a child is removed from the blend space, the indices may shift, + // so the "closest" parameter may point to the wrong child or be out of bounds. + // + // The proper fix is to change the "closest" parameter from the child's index to the child's name. + // At the moment, we have to reset it even if the closest child was not the one that changed. + // + // TODO: In a future release consider removing "closest" (int) and replacing it with "closest_name" (StringName). + if (is_blend_space) { + if (Pair *closest_param = property_map.getptr(String(parent_path) + "closest")) { + closest_param->first = -1; + } + } } // Update tree second.