Merge pull request #117275 from uno1982/feature/blend-space-sync-continuous
Add `SyncMode::CYCLIC` to BlendSpace1D and BlendSpace2D
This commit is contained in:
@@ -101,6 +101,9 @@
|
||||
<member name="blend_mode" type="int" setter="set_blend_mode" getter="get_blend_mode" enum="AnimationNodeBlendSpace1D.BlendMode" default="0">
|
||||
Controls the interpolation between animations.
|
||||
</member>
|
||||
<member name="cyclic_length" type="float" setter="set_cyclic_length" getter="get_cyclic_length" default="0.0">
|
||||
The cycle length in seconds used by [constant SYNC_MODE_CYCLIC_CONSTANT]. All animations are time-scaled so they complete one full cycle in this duration. Must be greater than [code]0[/code] for cyclic sync to take effect.
|
||||
</member>
|
||||
<member name="max_space" type="float" setter="set_max_space" getter="get_max_space" default="1.0">
|
||||
The blend space's axis's upper limit for the points' position. See [method add_blend_point].
|
||||
</member>
|
||||
@@ -110,9 +113,11 @@
|
||||
<member name="snap" type="float" setter="set_snap" getter="get_snap" default="0.1">
|
||||
Position increment to snap to when moving a point on the axis.
|
||||
</member>
|
||||
<member name="sync" type="bool" setter="set_use_sync" getter="is_using_sync" default="false">
|
||||
If [code]false[/code], the blended animations' frame are stopped when the blend value is [code]0[/code].
|
||||
If [code]true[/code], forcing the blended animations to advance frame.
|
||||
<member name="sync" type="bool" setter="set_use_sync" getter="is_using_sync" deprecated="Use [member sync_mode] instead.">
|
||||
If [code]true[/code], sync mode is enabled (equivalent to [constant SYNC_MODE_INDEPENDENT]). This property is kept for backward compatibility.
|
||||
</member>
|
||||
<member name="sync_mode" type="int" setter="set_sync_mode" getter="get_sync_mode" enum="AnimationNodeBlendSpace1D.SyncMode" default="0">
|
||||
Controls how animations are synced when blended. See [enum SyncMode] for available options.
|
||||
</member>
|
||||
<member name="value_label" type="String" setter="set_value_label" getter="get_value_label" default=""value"">
|
||||
Label of the virtual axis of the blend space.
|
||||
@@ -128,5 +133,19 @@
|
||||
<constant name="BLEND_MODE_DISCRETE_CARRY" value="2" enum="BlendMode">
|
||||
Similar to [constant BLEND_MODE_DISCRETE], but starts the new animation at the last animation's playback position.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_NONE" value="0" enum="SyncMode">
|
||||
Inactive animations are frozen and do not advance.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_INDEPENDENT" value="1" enum="SyncMode">
|
||||
Inactive animations advance with a weight of [code]0[/code]. This is equivalent to the previous [code]sync = true[/code] behavior.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_CYCLIC_MUTABLE" value="2" enum="SyncMode">
|
||||
All animations are time-scaled so they stay in sync, with the cycle length dynamically computed from active blend weights. This is self-normalizing: a solo animation plays at normal speed.
|
||||
[b]Note:[/b] If you apply [AnimationNodeTimeSeek] to the result when handling animations of different lengths, synchronization will be broken. In such cases, it is recommended to use [member AnimationNodeAnimation.use_custom_timeline] to align the animation lengths.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_CYCLIC_CONSTANT" value="3" enum="SyncMode">
|
||||
All animations are time-scaled so they complete one cycle in [member cyclic_length] seconds, keeping them in sync regardless of their individual lengths.
|
||||
[b]Note:[/b] If you apply [AnimationNodeTimeSeek] to the result when handling animations of different lengths, synchronization will be broken. In such cases, it is recommended to use [member AnimationNodeAnimation.use_custom_timeline] to align the animation lengths.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
||||
@@ -136,6 +136,9 @@
|
||||
<member name="blend_mode" type="int" setter="set_blend_mode" getter="get_blend_mode" enum="AnimationNodeBlendSpace2D.BlendMode" default="0">
|
||||
Controls the interpolation between animations.
|
||||
</member>
|
||||
<member name="cyclic_length" type="float" setter="set_cyclic_length" getter="get_cyclic_length" default="0.0">
|
||||
The cycle length in seconds used by [constant SYNC_MODE_CYCLIC_CONSTANT]. All animations are time-scaled so they complete one full cycle in this duration. Must be greater than [code]0[/code] for cyclic sync to take effect.
|
||||
</member>
|
||||
<member name="max_space" type="Vector2" setter="set_max_space" getter="get_max_space" default="Vector2(1, 1)">
|
||||
The blend space's X and Y axes' upper limit for the points' position. See [method add_blend_point].
|
||||
</member>
|
||||
@@ -145,9 +148,11 @@
|
||||
<member name="snap" type="Vector2" setter="set_snap" getter="get_snap" default="Vector2(0.1, 0.1)">
|
||||
Position increment to snap to when moving a point.
|
||||
</member>
|
||||
<member name="sync" type="bool" setter="set_use_sync" getter="is_using_sync" default="false">
|
||||
If [code]false[/code], the blended animations' frame are stopped when the blend value is [code]0[/code].
|
||||
If [code]true[/code], forcing the blended animations to advance frame.
|
||||
<member name="sync" type="bool" setter="set_use_sync" getter="is_using_sync" deprecated="Use [member sync_mode] instead.">
|
||||
If [code]true[/code], sync mode is enabled (equivalent to [constant SYNC_MODE_INDEPENDENT]). This property is kept for backward compatibility.
|
||||
</member>
|
||||
<member name="sync_mode" type="int" setter="set_sync_mode" getter="get_sync_mode" enum="AnimationNodeBlendSpace2D.SyncMode" default="0">
|
||||
Controls how animations are synced when blended. See [enum SyncMode] for available options.
|
||||
</member>
|
||||
<member name="x_label" type="String" setter="set_x_label" getter="get_x_label" default=""x"">
|
||||
Name of the blend space's X axis.
|
||||
@@ -173,5 +178,19 @@
|
||||
<constant name="BLEND_MODE_DISCRETE_CARRY" value="2" enum="BlendMode">
|
||||
Similar to [constant BLEND_MODE_DISCRETE], but starts the new animation at the last animation's playback position.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_NONE" value="0" enum="SyncMode">
|
||||
Inactive animations are frozen and do not advance.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_INDEPENDENT" value="1" enum="SyncMode">
|
||||
Inactive animations advance with a weight of [code]0[/code]. This is equivalent to the previous [code]sync = true[/code] behavior.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_CYCLIC_MUTABLE" value="2" enum="SyncMode">
|
||||
All animations are time-scaled so they stay in sync, with the cycle length dynamically computed from active blend weights. This is self-normalizing: a solo animation plays at normal speed.
|
||||
[b]Note:[/b] If you apply [AnimationNodeTimeSeek] to the result when handling animations of different lengths, synchronization will be broken. In such cases, it is recommended to use [member AnimationNodeAnimation.use_custom_timeline] to align the animation lengths.
|
||||
</constant>
|
||||
<constant name="SYNC_MODE_CYCLIC_CONSTANT" value="3" enum="SyncMode">
|
||||
All animations are time-scaled so they complete one cycle in [member cyclic_length] seconds, keeping them in sync regardless of their individual lengths.
|
||||
[b]Note:[/b] If you apply [AnimationNodeTimeSeek] to the result when handling animations of different lengths, synchronization will be broken. In such cases, it is recommended to use [member AnimationNodeAnimation.use_custom_timeline] to align the animation lengths.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/animation/animation_blend_tree.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
@@ -294,6 +293,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() {
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
|
||||
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("KeyValue"));
|
||||
Ref<Texture2D> icon_selected = get_editor_theme_icon(SNAME("KeySelected"));
|
||||
Ref<Texture2D> icon_invalid = get_editor_theme_icon(SNAME("KeyInvalid"));
|
||||
|
||||
const float pm = POINT_MARGIN * EDSCALE;
|
||||
|
||||
@@ -331,8 +331,8 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() {
|
||||
}
|
||||
}
|
||||
|
||||
bool does_include_invalid_key = false;
|
||||
points.clear();
|
||||
|
||||
for (int i = 0; i < blend_space->get_blend_point_count(); i++) {
|
||||
float point = blend_space->get_blend_point_position(i);
|
||||
if (!read_only && dragging_selected && selected_point == i) {
|
||||
@@ -348,8 +348,19 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() {
|
||||
|
||||
points.push_back(point + ofs.x);
|
||||
|
||||
// Draw × marker on non-AnimationNodeAnimation points when in cyclic mode.
|
||||
bool is_key_valid = true;
|
||||
AnimationNodeBlendSpace1D::SyncMode sync_mode = blend_space->get_sync_mode();
|
||||
if (sync_mode == AnimationNodeBlendSpace1D::SYNC_MODE_CYCLIC_MUTABLE || sync_mode == AnimationNodeBlendSpace1D::SYNC_MODE_CYCLIC_CONSTANT) {
|
||||
Ref<AnimationNode> node = blend_space->get_blend_point_node(i);
|
||||
Ref<AnimationNodeAnimation> anim_node = node;
|
||||
if (anim_node.is_null()) {
|
||||
is_key_valid = false;
|
||||
does_include_invalid_key = true;
|
||||
}
|
||||
}
|
||||
Vector2 gui_point = (ofs + Vector2(point, s.height / 2.0) - icon->get_size() / 2.0).floor();
|
||||
blend_space_draw->draw_texture(i == selected_point ? icon_selected : icon, gui_point);
|
||||
blend_space_draw->draw_texture(is_key_valid ? (i == selected_point ? icon_selected : icon) : icon_invalid, gui_point);
|
||||
|
||||
if (point >= 0.0 && point <= s.width && editing_point != i) {
|
||||
String name_text = show_indices ? itos(i) : String(blend_space->get_blend_point_name(i));
|
||||
@@ -366,6 +377,11 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() {
|
||||
text_rects.write[i] = Rect2(Vector2(text_pos.x, text_pos.y - font->get_ascent(font_size)), text_size);
|
||||
}
|
||||
}
|
||||
if (does_include_invalid_key) {
|
||||
invalid_point_warning_hb->show();
|
||||
} else {
|
||||
invalid_point_warning_hb->hide();
|
||||
}
|
||||
|
||||
// blend position
|
||||
{
|
||||
@@ -403,7 +419,10 @@ void AnimationNodeBlendSpace1DEditor::_update_space() {
|
||||
max_value->set_value(blend_space->get_max_space());
|
||||
min_value->set_value(blend_space->get_min_space());
|
||||
|
||||
sync->set_pressed(blend_space->is_using_sync());
|
||||
sync->select(blend_space->get_sync_mode());
|
||||
cyclic_length_value->set_value(blend_space->get_cyclic_length());
|
||||
cyclic_length_value->set_visible(blend_space->get_sync_mode() == AnimationNodeBlendSpace1D::SYNC_MODE_CYCLIC_CONSTANT);
|
||||
|
||||
interpolation->select(blend_space->get_blend_mode());
|
||||
|
||||
label_value->set_text(blend_space->get_value_label());
|
||||
@@ -433,8 +452,10 @@ void AnimationNodeBlendSpace1DEditor::_config_changed(double) {
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_snap", snap_value->get_value());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_use_sync", sync->is_pressed());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_use_sync", blend_space->is_using_sync());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_sync_mode", sync->get_selected());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_sync_mode", blend_space->get_sync_mode());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_cyclic_length", cyclic_length_value->get_value());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_cyclic_length", blend_space->get_cyclic_length());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_blend_mode", interpolation->get_selected());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_blend_mode", blend_space->get_blend_mode());
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
@@ -442,6 +463,9 @@ void AnimationNodeBlendSpace1DEditor::_config_changed(double) {
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
// Update cyclic_length visibility immediately (undo/redo calls _update_space while updating=true).
|
||||
cyclic_length_value->set_visible(sync->get_selected() == AnimationNodeBlendSpace1D::SYNC_MODE_CYCLIC_CONSTANT);
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
@@ -755,6 +779,7 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackContinuous")), TTR("Continuous"), 0);
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackDiscrete")), TTR("Discrete"), 1);
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackCapture")), TTR("Capture"), 2);
|
||||
invalid_point_warning->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_PROCESS: {
|
||||
@@ -772,6 +797,11 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_show_invalid_point_warning() {
|
||||
EditorNode::get_singleton()->show_warning(
|
||||
TTR("BlendSpace contains points that are not AnimationNodeAnimation.\nCyclic sync modes require that all blend points use AnimationNodeAnimation with a finite, immutable length."));
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_bind_methods() {
|
||||
ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace1DEditor::_update_space);
|
||||
ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace1DEditor::_update_tool_erase);
|
||||
@@ -804,6 +834,7 @@ void AnimationNodeBlendSpace1DEditor::edit(const Ref<AnimationNode> &p_node) {
|
||||
min_value->set_editable(!read_only);
|
||||
max_value->set_editable(!read_only);
|
||||
sync->set_disabled(read_only);
|
||||
cyclic_length_value->set_editable(!read_only);
|
||||
interpolation->set_disabled(read_only);
|
||||
}
|
||||
|
||||
@@ -999,9 +1030,24 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
|
||||
|
||||
top_hb->add_child(memnew(VSeparator));
|
||||
top_hb->add_child(memnew(Label(TTR("Sync"))));
|
||||
sync = memnew(CheckBox);
|
||||
sync = memnew(OptionButton);
|
||||
sync->add_item(TTR("None"));
|
||||
sync->add_item(TTR("Independent"));
|
||||
sync->add_item(TTR("Cyclic Mutable"));
|
||||
sync->add_item(TTR("Cyclic Constant"));
|
||||
top_hb->add_child(sync);
|
||||
sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
sync->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
|
||||
cyclic_length_value = memnew(SpinBox);
|
||||
cyclic_length_value->set_min(0.0);
|
||||
cyclic_length_value->set_max(99.0);
|
||||
cyclic_length_value->set_step(0.001);
|
||||
cyclic_length_value->set_allow_greater(true);
|
||||
cyclic_length_value->set_suffix("s");
|
||||
cyclic_length_value->set_accessibility_name(TTRC("Cyclic Length"));
|
||||
cyclic_length_value->set_tooltip_text(TTR("Cycle length in seconds for cyclic sync. All animations are time-scaled to complete one cycle in this duration."));
|
||||
top_hb->add_child(cyclic_length_value);
|
||||
cyclic_length_value->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
|
||||
top_hb->add_child(memnew(VSeparator));
|
||||
|
||||
@@ -1115,6 +1161,16 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
|
||||
error_panel->add_child(error_label);
|
||||
error_panel->hide();
|
||||
|
||||
invalid_point_warning_hb = memnew(HBoxContainer);
|
||||
add_child(invalid_point_warning_hb);
|
||||
invalid_point_warning_hb->hide();
|
||||
|
||||
invalid_point_warning = memnew(Button);
|
||||
invalid_point_warning->set_text(TTRC("Contains Invalid Point"));
|
||||
invalid_point_warning->set_tooltip_text(TTRC("Warning: BlendSpace contains invalid points for cyclic sync"));
|
||||
invalid_point_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_show_invalid_point_warning));
|
||||
invalid_point_warning_hb->add_child(invalid_point_warning);
|
||||
|
||||
menu = memnew(PopupMenu);
|
||||
add_child(menu);
|
||||
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_add_menu_type));
|
||||
|
||||
@@ -67,7 +67,8 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
|
||||
SpinBox *max_value = nullptr;
|
||||
SpinBox *min_value = nullptr;
|
||||
|
||||
CheckBox *sync = nullptr;
|
||||
OptionButton *sync = nullptr;
|
||||
SpinBox *cyclic_length_value = nullptr;
|
||||
OptionButton *interpolation = nullptr;
|
||||
|
||||
HBoxContainer *edit_hb = nullptr;
|
||||
@@ -83,6 +84,10 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
|
||||
PanelContainer *error_panel = nullptr;
|
||||
RichTextLabel *error_label = nullptr;
|
||||
|
||||
HBoxContainer *invalid_point_warning_hb = nullptr;
|
||||
Button *invalid_point_warning = nullptr;
|
||||
void _show_invalid_point_warning();
|
||||
|
||||
bool updating = false;
|
||||
|
||||
static AnimationNodeBlendSpace1DEditor *singleton;
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/animation/animation_blend_tree.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/grid_container.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
@@ -90,6 +89,7 @@ void AnimationNodeBlendSpace2DEditor::edit(const Ref<AnimationNode> &p_node) {
|
||||
tool_triangle->set_disabled(read_only);
|
||||
auto_triangles->set_disabled(read_only);
|
||||
sync->set_disabled(read_only);
|
||||
cyclic_length_value->set_editable(!read_only);
|
||||
interpolation->set_disabled(read_only);
|
||||
}
|
||||
|
||||
@@ -535,6 +535,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() {
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
|
||||
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("KeyValue"));
|
||||
Ref<Texture2D> icon_selected = get_editor_theme_icon(SNAME("KeySelected"));
|
||||
Ref<Texture2D> icon_invalid = get_editor_theme_icon(SNAME("KeyInvalid"));
|
||||
|
||||
const float pm = POINT_MARGIN * EDSCALE;
|
||||
|
||||
@@ -629,6 +630,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() {
|
||||
blend_space_draw->draw_primitive(bl_points, colors, Vector<Vector2>());
|
||||
}
|
||||
|
||||
bool does_include_invalid_key = false;
|
||||
points.clear();
|
||||
for (int i = 0; i < blend_space->get_blend_point_count(); i++) {
|
||||
Vector2 point = blend_space->get_blend_point_position(i);
|
||||
@@ -646,8 +648,19 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() {
|
||||
|
||||
points.push_back(ofs + point);
|
||||
|
||||
// Draw × marker on non-AnimationNodeAnimation points when in cyclic mode.
|
||||
bool is_key_valid = true;
|
||||
AnimationNodeBlendSpace2D::SyncMode sync_mode = blend_space->get_sync_mode();
|
||||
if (sync_mode == AnimationNodeBlendSpace2D::SYNC_MODE_CYCLIC_MUTABLE || sync_mode == AnimationNodeBlendSpace2D::SYNC_MODE_CYCLIC_CONSTANT) {
|
||||
Ref<AnimationNode> node = blend_space->get_blend_point_node(i);
|
||||
Ref<AnimationNodeAnimation> anim_node = node;
|
||||
if (anim_node.is_null()) {
|
||||
is_key_valid = false;
|
||||
does_include_invalid_key = true;
|
||||
}
|
||||
}
|
||||
Vector2 gui_point = (ofs + point - icon->get_size() / 2).floor();
|
||||
blend_space_draw->draw_texture(i == selected_point ? icon_selected : icon, gui_point);
|
||||
blend_space_draw->draw_texture(is_key_valid ? (i == selected_point ? icon_selected : icon) : icon_invalid, gui_point);
|
||||
|
||||
if (point.x >= 0.0 && point.x <= s.width && point.y >= 0.0 && point.y <= s.height && editing_point != i) {
|
||||
String name_text = show_indices ? itos(i) : String(blend_space->get_blend_point_name(i));
|
||||
@@ -669,6 +682,11 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() {
|
||||
text_rects.write[i] = Rect2(Vector2(text_pos.x, text_pos.y - font->get_ascent(font_size)), text_size);
|
||||
}
|
||||
}
|
||||
if (does_include_invalid_key) {
|
||||
invalid_point_warning_hb->show();
|
||||
} else {
|
||||
invalid_point_warning_hb->hide();
|
||||
}
|
||||
|
||||
if (making_triangle.size()) {
|
||||
Vector<Vector2> bl_points;
|
||||
@@ -745,7 +763,10 @@ void AnimationNodeBlendSpace2DEditor::_update_space() {
|
||||
|
||||
auto_triangles->set_pressed(blend_space->get_auto_triangles());
|
||||
|
||||
sync->set_pressed(blend_space->is_using_sync());
|
||||
sync->select(blend_space->get_sync_mode());
|
||||
cyclic_length_value->set_value(blend_space->get_cyclic_length());
|
||||
cyclic_length_value->set_visible(blend_space->get_sync_mode() == AnimationNodeBlendSpace2D::SYNC_MODE_CYCLIC_CONSTANT);
|
||||
|
||||
interpolation->select(blend_space->get_blend_mode());
|
||||
|
||||
max_x_value->set_value(blend_space->get_max_space().x);
|
||||
@@ -785,8 +806,10 @@ void AnimationNodeBlendSpace2DEditor::_config_changed(double) {
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_snap", Vector2(snap_x->get_value(), snap_y->get_value()));
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_use_sync", sync->is_pressed());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_use_sync", blend_space->is_using_sync());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_sync_mode", sync->get_selected());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_sync_mode", blend_space->get_sync_mode());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_cyclic_length", cyclic_length_value->get_value());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_cyclic_length", blend_space->get_cyclic_length());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_blend_mode", interpolation->get_selected());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_blend_mode", blend_space->get_blend_mode());
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
@@ -794,6 +817,9 @@ void AnimationNodeBlendSpace2DEditor::_config_changed(double) {
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
// Update cyclic_length visibility immediately (undo/redo calls _update_space while updating=true).
|
||||
cyclic_length_value->set_visible(sync->get_selected() == AnimationNodeBlendSpace2D::SYNC_MODE_CYCLIC_CONSTANT);
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
@@ -998,6 +1024,7 @@ void AnimationNodeBlendSpace2DEditor::_notification(int p_what) {
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackContinuous")), TTR("Continuous"), 0);
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackDiscrete")), TTR("Discrete"), 1);
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackCapture")), TTR("Capture"), 2);
|
||||
invalid_point_warning->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_PROCESS: {
|
||||
@@ -1015,6 +1042,11 @@ void AnimationNodeBlendSpace2DEditor::_notification(int p_what) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2DEditor::_show_invalid_point_warning() {
|
||||
EditorNode::get_singleton()->show_warning(
|
||||
TTR("BlendSpace contains points that are not AnimationNodeAnimation.\nCyclic sync modes require that all blend points use AnimationNodeAnimation with a finite, immutable length."));
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2DEditor::_open_editor() {
|
||||
if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
|
||||
Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
|
||||
@@ -1265,9 +1297,24 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
|
||||
top_hb->add_child(memnew(VSeparator));
|
||||
|
||||
top_hb->add_child(memnew(Label(TTR("Sync"))));
|
||||
sync = memnew(CheckBox);
|
||||
sync = memnew(OptionButton);
|
||||
sync->add_item(TTR("None"));
|
||||
sync->add_item(TTR("Independent"));
|
||||
sync->add_item(TTR("Cyclic Mutable"));
|
||||
sync->add_item(TTR("Cyclic Constant"));
|
||||
top_hb->add_child(sync);
|
||||
sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
|
||||
sync->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
|
||||
|
||||
cyclic_length_value = memnew(SpinBox);
|
||||
cyclic_length_value->set_min(0.0);
|
||||
cyclic_length_value->set_max(99.0);
|
||||
cyclic_length_value->set_step(0.001);
|
||||
cyclic_length_value->set_allow_greater(true);
|
||||
cyclic_length_value->set_suffix("s");
|
||||
cyclic_length_value->set_accessibility_name(TTRC("Cyclic Length"));
|
||||
cyclic_length_value->set_tooltip_text(TTR("Cycle length in seconds for cyclic sync. All animations are time-scaled to complete one cycle in this duration."));
|
||||
top_hb->add_child(cyclic_length_value);
|
||||
cyclic_length_value->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
|
||||
|
||||
top_hb->add_child(memnew(VSeparator));
|
||||
|
||||
@@ -1423,6 +1470,16 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
|
||||
error_panel->add_child(error_label);
|
||||
error_panel->hide();
|
||||
|
||||
invalid_point_warning_hb = memnew(HBoxContainer);
|
||||
add_child(invalid_point_warning_hb);
|
||||
invalid_point_warning_hb->hide();
|
||||
|
||||
invalid_point_warning = memnew(Button);
|
||||
invalid_point_warning->set_text(TTRC("Contains Invalid Point"));
|
||||
invalid_point_warning->set_tooltip_text(TTRC("Warning: BlendSpace contains invalid points for cyclic sync"));
|
||||
invalid_point_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_show_invalid_point_warning));
|
||||
invalid_point_warning_hb->add_child(invalid_point_warning);
|
||||
|
||||
set_custom_minimum_size(Size2(0, 300 * EDSCALE));
|
||||
|
||||
menu = memnew(PopupMenu);
|
||||
|
||||
@@ -64,7 +64,8 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin {
|
||||
Button *snap = nullptr;
|
||||
SpinBox *snap_x = nullptr;
|
||||
SpinBox *snap_y = nullptr;
|
||||
CheckBox *sync = nullptr;
|
||||
OptionButton *sync = nullptr;
|
||||
SpinBox *cyclic_length_value = nullptr;
|
||||
OptionButton *interpolation = nullptr;
|
||||
|
||||
Button *auto_triangles = nullptr;
|
||||
@@ -91,6 +92,10 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin {
|
||||
PanelContainer *error_panel = nullptr;
|
||||
RichTextLabel *error_label = nullptr;
|
||||
|
||||
HBoxContainer *invalid_point_warning_hb = nullptr;
|
||||
Button *invalid_point_warning = nullptr;
|
||||
void _show_invalid_point_warning();
|
||||
|
||||
bool updating;
|
||||
|
||||
static AnimationNodeBlendSpace2DEditor *singleton;
|
||||
|
||||
@@ -63,6 +63,7 @@ Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName
|
||||
|
||||
void AnimationNodeBlendSpace1D::_tree_changed() {
|
||||
AnimationRootNode::_tree_changed();
|
||||
_check_can_sync();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) {
|
||||
@@ -109,19 +110,38 @@ void AnimationNodeBlendSpace1D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace1D::set_blend_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace1D::get_blend_mode);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace1D::set_use_sync);
|
||||
ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace1D::is_using_sync);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_sync_mode", "sync_mode"), &AnimationNodeBlendSpace1D::set_sync_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_sync_mode"), &AnimationNodeBlendSpace1D::get_sync_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_cyclic_length", "length"), &AnimationNodeBlendSpace1D::set_cyclic_length);
|
||||
ClassDB::bind_method(D_METHOD("get_cyclic_length"), &AnimationNodeBlendSpace1D::get_cyclic_length);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_min_space", "get_min_space");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_value_label", "get_value_label");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync");
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
// Keep "sync" as bool with old getter/setter for GDExtension API backward compatibility (4.0-4.6).
|
||||
// Old scenes with "sync = true" still load via set_use_sync -> SYNC_MODE_INDEPENDENT.
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_use_sync", "is_using_sync");
|
||||
#endif // DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "sync_mode", PROPERTY_HINT_ENUM, "None,Independent,Cyclic Mutable,Cyclic Constant", PROPERTY_USAGE_NO_EDITOR), "set_sync_mode", "get_sync_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cyclic_length", PROPERTY_HINT_RANGE, "0.0,99.0,0.001,or_greater", PROPERTY_USAGE_NO_EDITOR), "set_cyclic_length", "get_cyclic_length");
|
||||
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY);
|
||||
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_NONE);
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_INDEPENDENT);
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_CYCLIC_MUTABLE);
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_CYCLIC_CONSTANT);
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::get_child_nodes(LocalVector<ChildNode> *r_child_nodes) {
|
||||
@@ -161,6 +181,7 @@ void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_
|
||||
_add_node(blend_points[p_at_index].node);
|
||||
|
||||
blend_points_used++;
|
||||
lengths_dirty = true;
|
||||
_tree_changed();
|
||||
}
|
||||
|
||||
@@ -181,6 +202,7 @@ void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<Anim
|
||||
blend_points[p_point].node = p_node;
|
||||
_add_node(blend_points[p_point].node);
|
||||
|
||||
lengths_dirty = true;
|
||||
_tree_changed();
|
||||
}
|
||||
|
||||
@@ -234,6 +256,7 @@ void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) {
|
||||
blend_points_used--;
|
||||
|
||||
blend_points[blend_points_used].name = StringName();
|
||||
lengths_dirty = true;
|
||||
|
||||
emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point));
|
||||
_tree_changed();
|
||||
@@ -307,12 +330,32 @@ AnimationNodeBlendSpace1D::BlendMode AnimationNodeBlendSpace1D::get_blend_mode()
|
||||
return blend_mode;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void AnimationNodeBlendSpace1D::set_use_sync(bool p_sync) {
|
||||
sync = p_sync;
|
||||
sync_mode = p_sync ? SYNC_MODE_INDEPENDENT : SYNC_MODE_NONE;
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace1D::is_using_sync() const {
|
||||
return sync;
|
||||
return sync_mode != SYNC_MODE_NONE;
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_sync_mode(SyncMode p_sync_mode) {
|
||||
sync_mode = p_sync_mode;
|
||||
_check_can_sync();
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace1D::SyncMode AnimationNodeBlendSpace1D::get_sync_mode() const {
|
||||
return sync_mode;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_cyclic_length(double p_length) {
|
||||
cyclic_length = p_length;
|
||||
inverted_cycle_length = (p_length > CMP_EPSILON) ? (1.0 / p_length) : 0.0;
|
||||
}
|
||||
|
||||
double AnimationNodeBlendSpace1D::get_cyclic_length() const {
|
||||
return cyclic_length;
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace1D::_set(const StringName &p_name, const Variant &p_value) {
|
||||
@@ -365,8 +408,22 @@ void AnimationNodeBlendSpace1D::_get_property_list(List<PropertyInfo> *p_list) c
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_check_can_sync() {
|
||||
is_contain_invalid_point = false;
|
||||
if (sync_mode < SYNC_MODE_CYCLIC_MUTABLE) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
Ref<AnimationNodeAnimation> na = static_cast<Ref<AnimationNodeAnimation>>(blend_points[i].node);
|
||||
if (na.is_null()) {
|
||||
is_contain_invalid_point = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(ProcessState &p_process_state, AnimationNodeInstance &p_instance, const AnimationMixer::PlaybackInfo &p_playback_info, bool p_test_only) {
|
||||
if (!blend_points_used) {
|
||||
if (!blend_points_used || is_contain_invalid_point) {
|
||||
return NodeTimeInfo();
|
||||
}
|
||||
|
||||
@@ -383,6 +440,18 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(ProcessState &p_
|
||||
int cur_closest = p_instance.get_parameter_closest();
|
||||
NodeTimeInfo mind;
|
||||
|
||||
// Build weights array for all blend points.
|
||||
LocalVector<float> weights;
|
||||
LocalVector<double> deltas;
|
||||
weights.resize_initialized(blend_points_used);
|
||||
deltas.resize_initialized(blend_points_used);
|
||||
if (sync_mode == SYNC_MODE_NONE) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
deltas[i] = -1; // Not process.
|
||||
}
|
||||
}
|
||||
|
||||
int new_closest = -1;
|
||||
if (blend_mode == BLEND_MODE_INTERPOLATED) {
|
||||
int point_lower = -1;
|
||||
float pos_lower = 0.0;
|
||||
@@ -405,57 +474,28 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(ProcessState &p_
|
||||
}
|
||||
|
||||
// fill in weights
|
||||
float weights[MAX_BLEND_POINTS] = {};
|
||||
if (point_lower == -1 && point_higher != -1) {
|
||||
// we are on the left side, no other point to the left
|
||||
// we just play the next point.
|
||||
|
||||
weights[point_higher] = 1.0;
|
||||
} else if (point_higher == -1) {
|
||||
// we are on the right side, no other point to the right
|
||||
// we just play the previous point
|
||||
|
||||
weights[point_lower] = 1.0;
|
||||
} else {
|
||||
// we are between two points.
|
||||
// figure out weights, then blend the animations
|
||||
|
||||
float distance_between_points = pos_higher - pos_lower;
|
||||
|
||||
float current_pos_inbetween = blend_pos - pos_lower;
|
||||
|
||||
float blend_percentage = current_pos_inbetween / distance_between_points;
|
||||
|
||||
float blend_lower = 1.0 - blend_percentage;
|
||||
float blend_higher = blend_percentage;
|
||||
|
||||
weights[point_lower] = blend_lower;
|
||||
weights[point_higher] = blend_higher;
|
||||
weights[point_lower] = 1.0 - blend_percentage;
|
||||
weights[point_higher] = blend_percentage;
|
||||
}
|
||||
|
||||
// actually blend the animations now
|
||||
bool first = true;
|
||||
double max_weight = 0.0;
|
||||
// Determine closest for return info.
|
||||
float max_weight = 0.0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i == point_lower || i == point_higher) {
|
||||
pi.weight = weights[i];
|
||||
AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(get_blend_point_name(i));
|
||||
NodeTimeInfo t = blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
|
||||
if (first || pi.weight > max_weight) {
|
||||
max_weight = pi.weight;
|
||||
mind = t;
|
||||
first = false;
|
||||
}
|
||||
} else if (sync) {
|
||||
pi.weight = 0;
|
||||
AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(get_blend_point_name(i));
|
||||
blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
|
||||
if (weights[i] >= max_weight) {
|
||||
max_weight = weights[i];
|
||||
new_closest = i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int new_closest = -1;
|
||||
double new_closest_dist = 1e20;
|
||||
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
double d = std::abs(blend_points[i].position - blend_pos);
|
||||
if (d < new_closest_dist) {
|
||||
@@ -463,49 +503,98 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(ProcessState &p_
|
||||
new_closest_dist = d;
|
||||
}
|
||||
}
|
||||
weights[new_closest] = 1.0;
|
||||
}
|
||||
|
||||
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) {
|
||||
AnimationNodeInstance *instance_new_closest = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(new_closest));
|
||||
|
||||
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
|
||||
NodeTimeInfo from;
|
||||
// For ping-pong loop.
|
||||
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
|
||||
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
|
||||
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(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(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(p_process_state, p_instance, instance_current_closest, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
pi = p_playback_info;
|
||||
pi.weight = 0;
|
||||
// Compute all deltas.
|
||||
if (sync_mode >= SYNC_MODE_CYCLIC_MUTABLE) {
|
||||
// Refresh cached lengths when blend points have changed.
|
||||
if (lengths_dirty) {
|
||||
cached_lengths.resize(blend_points_used);
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i != cur_closest) {
|
||||
AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(get_blend_point_name(i));
|
||||
blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
|
||||
AnimationMixer::PlaybackInfo test_pi = p_playback_info;
|
||||
test_pi.weight = 0;
|
||||
AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(get_blend_point_name(i));
|
||||
NodeTimeInfo info = blend_node(p_process_state, p_instance, &other_instance, test_pi, FILTER_IGNORE, true, true);
|
||||
cached_lengths[i] = (info.length > CMP_EPSILON) ? info.length : 0.0;
|
||||
}
|
||||
lengths_dirty = false;
|
||||
}
|
||||
double inv_target_length = 0.0;
|
||||
if (sync_mode == SYNC_MODE_CYCLIC_MUTABLE) {
|
||||
// Compute blended_len from active weights.
|
||||
double target_length = 0.0;
|
||||
double total_weight = 0.0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (weights[i] > 0.0f && cached_lengths[i] > CMP_EPSILON) {
|
||||
target_length += weights[i] * cached_lengths[i];
|
||||
total_weight += weights[i];
|
||||
}
|
||||
}
|
||||
if (total_weight > CMP_EPSILON) {
|
||||
target_length /= total_weight;
|
||||
}
|
||||
inv_target_length = (target_length > CMP_EPSILON) ? (1.0 / target_length) : 0.0;
|
||||
} else {
|
||||
// Use cached inverse of user-specified cyclic_length.
|
||||
inv_target_length = inverted_cycle_length;
|
||||
}
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
deltas[i] = pi.delta * cached_lengths[i] * inv_target_length;
|
||||
}
|
||||
} else if (sync_mode == SYNC_MODE_INDEPENDENT) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
deltas[i] = pi.delta;
|
||||
}
|
||||
} else {
|
||||
// SYNC_MODE_NONE: only active points get delta.
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (weights[i] >= CMP_EPSILON) {
|
||||
deltas[i] = pi.delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p_instance.set_parameter_closest(cur_closest, p_process_state.is_testing);
|
||||
// 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) {
|
||||
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.
|
||||
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
|
||||
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
|
||||
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(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(p_process_state, p_instance, instance_new_closest, pi, FILTER_IGNORE, true, p_test_only);
|
||||
deltas[new_closest] = -1; // No more blend new_closest point.
|
||||
}
|
||||
|
||||
// Normal case, blend all points.
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (std::signbit(deltas[i])) {
|
||||
continue;
|
||||
}
|
||||
pi = p_playback_info;
|
||||
pi.weight = weights[i];
|
||||
pi.delta = deltas[i];
|
||||
AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(get_blend_point_name(i));
|
||||
NodeTimeInfo t = blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
|
||||
if (i == new_closest) {
|
||||
mind = t;
|
||||
}
|
||||
}
|
||||
|
||||
p_instance.set_parameter_closest(new_closest, p_process_state.is_testing);
|
||||
return mind;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,13 @@ public:
|
||||
BLEND_MODE_DISCRETE_CARRY,
|
||||
};
|
||||
|
||||
enum SyncMode {
|
||||
SYNC_MODE_NONE, // Inactive animations are frozen (not advanced).
|
||||
SYNC_MODE_INDEPENDENT, // Inactive animations advance with weight=0 (previous "sync" behavior).
|
||||
SYNC_MODE_CYCLIC_MUTABLE, // Time-scaled with blend-weight-dependent cycle length (self-normalizing).
|
||||
SYNC_MODE_CYCLIC_CONSTANT, // Time-scaled to complete one cycle in cyclic_length seconds.
|
||||
};
|
||||
|
||||
protected:
|
||||
enum {
|
||||
MAX_BLEND_POINTS = 64
|
||||
@@ -72,7 +79,11 @@ protected:
|
||||
|
||||
BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
|
||||
|
||||
bool sync = false;
|
||||
SyncMode sync_mode = SYNC_MODE_NONE;
|
||||
double cyclic_length = 0.0;
|
||||
double inverted_cycle_length = 0.0; // Cached 1/cyclic_length.
|
||||
LocalVector<double> cached_lengths;
|
||||
bool lengths_dirty = true;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
@@ -80,6 +91,9 @@ protected:
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override;
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
|
||||
|
||||
bool is_contain_invalid_point = false;
|
||||
void _check_can_sync();
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _add_blend_point_bind_compat_110369(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index = -1);
|
||||
static void _bind_compatibility_methods();
|
||||
@@ -122,8 +136,16 @@ public:
|
||||
void set_blend_mode(BlendMode p_blend_mode);
|
||||
BlendMode get_blend_mode() const;
|
||||
|
||||
void set_use_sync(bool p_sync);
|
||||
bool is_using_sync() const;
|
||||
void set_cyclic_length(double p_length);
|
||||
double get_cyclic_length() const;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void set_use_sync(bool p_sync); // Compat: maps to SYNC_MODE_INDEPENDENT or SYNC_MODE_NONE.
|
||||
bool is_using_sync() const; // Compat: returns sync_mode != SYNC_MODE_NONE.
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void set_sync_mode(SyncMode p_sync_mode);
|
||||
SyncMode get_sync_mode() const;
|
||||
|
||||
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;
|
||||
@@ -132,3 +154,4 @@ public:
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeBlendSpace1D::BlendMode)
|
||||
VARIANT_ENUM_CAST(AnimationNodeBlendSpace1D::SyncMode)
|
||||
|
||||
@@ -99,6 +99,7 @@ void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_
|
||||
_add_node(blend_points[p_at_index].node);
|
||||
blend_points_used++;
|
||||
|
||||
lengths_dirty = true;
|
||||
_queue_auto_triangles();
|
||||
|
||||
_tree_changed();
|
||||
@@ -120,6 +121,7 @@ void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref<Anim
|
||||
blend_points[p_point].node = p_node;
|
||||
_add_node(blend_points[p_point].node);
|
||||
|
||||
lengths_dirty = true;
|
||||
_tree_changed();
|
||||
}
|
||||
|
||||
@@ -189,6 +191,7 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) {
|
||||
blend_points_used--;
|
||||
|
||||
blend_points[blend_points_used].name = StringName();
|
||||
lengths_dirty = true;
|
||||
|
||||
emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point));
|
||||
_tree_changed();
|
||||
@@ -506,7 +509,7 @@ Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) {
|
||||
return best_point;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) {
|
||||
void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const LocalVector<Vector2> &p_points, LocalVector<float> &r_weights) {
|
||||
if (p_pos.is_equal_approx(p_points[0])) {
|
||||
r_weights[0] = 1;
|
||||
r_weights[1] = 0;
|
||||
@@ -551,32 +554,61 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect
|
||||
r_weights[2] = w;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_check_can_sync() {
|
||||
is_contain_invalid_point = false;
|
||||
if (sync_mode < SYNC_MODE_CYCLIC_MUTABLE) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
Ref<AnimationNodeAnimation> na = static_cast<Ref<AnimationNodeAnimation>>(blend_points[i].node);
|
||||
if (na.is_null()) {
|
||||
is_contain_invalid_point = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!blend_points_used || is_contain_invalid_point) {
|
||||
return NodeTimeInfo();
|
||||
}
|
||||
|
||||
Vector2 blend_pos = p_instance.get_parameter(blend_position);
|
||||
int cur_closest = p_instance.get_parameter_closest();
|
||||
NodeTimeInfo mind; //time of min distance point
|
||||
constexpr int TRIANGLE_VERTS = 3;
|
||||
|
||||
AnimationMixer::PlaybackInfo pi = p_playback_info;
|
||||
Vector2 blend_pos = p_instance.get_parameter(blend_position);
|
||||
int cur_closest = p_instance.get_parameter_closest();
|
||||
NodeTimeInfo mind; // Time of closest point.
|
||||
|
||||
// Build weights array for all blend points.
|
||||
LocalVector<float> weights;
|
||||
LocalVector<double> deltas;
|
||||
weights.resize_initialized(blend_points_used);
|
||||
deltas.resize_initialized(blend_points_used);
|
||||
if (sync_mode == SYNC_MODE_NONE) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
deltas[i] = -1; // Not process.
|
||||
}
|
||||
}
|
||||
|
||||
int new_closest = -1;
|
||||
LocalVector<int> triangle_points;
|
||||
LocalVector<float> blend_weights;
|
||||
triangle_points.resize_initialized(TRIANGLE_VERTS);
|
||||
blend_weights.resize_initialized(TRIANGLE_VERTS);
|
||||
if (blend_mode == BLEND_MODE_INTERPOLATED) {
|
||||
if (triangles.is_empty()) {
|
||||
return NodeTimeInfo();
|
||||
}
|
||||
|
||||
Vector2 best_point;
|
||||
bool first = true;
|
||||
int blend_triangle = -1;
|
||||
float blend_weights[3] = { 0, 0, 0 };
|
||||
|
||||
bool first = true;
|
||||
LocalVector<Vector2> points;
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
Vector2 points[3];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
points.resize_initialized(TRIANGLE_VERTS);
|
||||
for (int j = 0; j < TRIANGLE_VERTS; j++) {
|
||||
points[j] = get_blend_point_position(get_triangle_point(i, j));
|
||||
}
|
||||
|
||||
@@ -586,9 +618,9 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(ProcessState &p_
|
||||
break;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int j = 0; j < TRIANGLE_VERTS; j++) {
|
||||
const Vector2 segment_a = points[j];
|
||||
const Vector2 segment_b = points[(j + 1) % 3];
|
||||
const Vector2 segment_b = points[(j + 1) % TRIANGLE_VERTS];
|
||||
Vector2 closest2 = Geometry2D::get_closest_point_to_segment(blend_pos, segment_a, segment_b);
|
||||
if (first || closest2.distance_to(blend_pos) < best_point.distance_to(blend_pos)) {
|
||||
best_point = closest2;
|
||||
@@ -597,57 +629,33 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(ProcessState &p_
|
||||
const real_t d = segment_a.distance_to(segment_b);
|
||||
if (d == 0.0) {
|
||||
blend_weights[j] = 1.0;
|
||||
blend_weights[(j + 1) % 3] = 0.0;
|
||||
blend_weights[(j + 2) % 3] = 0.0;
|
||||
blend_weights[(j + 1) % TRIANGLE_VERTS] = 0.0;
|
||||
blend_weights[(j + 2) % TRIANGLE_VERTS] = 0.0;
|
||||
} else {
|
||||
const real_t c = segment_a.distance_to(closest2) / d;
|
||||
|
||||
blend_weights[j] = 1.0 - c;
|
||||
blend_weights[(j + 1) % 3] = c;
|
||||
blend_weights[(j + 2) % 3] = 0.0;
|
||||
blend_weights[(j + 1) % TRIANGLE_VERTS] = c;
|
||||
blend_weights[(j + 2) % TRIANGLE_VERTS] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ERR_FAIL_COND_V(blend_triangle == -1, NodeTimeInfo()); // Should never reach here.
|
||||
|
||||
ERR_FAIL_COND_V(blend_triangle == -1, NodeTimeInfo()); //should never reach here
|
||||
|
||||
int triangle_points[3];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
triangle_points[j] = get_triangle_point(blend_triangle, j);
|
||||
for (int i = 0; i < TRIANGLE_VERTS; i++) {
|
||||
triangle_points[i] = get_triangle_point(blend_triangle, i);
|
||||
}
|
||||
|
||||
first = true;
|
||||
|
||||
double max_weight = 0.0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
bool found = false;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (i == triangle_points[j]) {
|
||||
//blend with the given weight
|
||||
pi.weight = blend_weights[j];
|
||||
AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(i));
|
||||
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;
|
||||
first = false;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sync && !found) {
|
||||
pi.weight = 0;
|
||||
AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(i));
|
||||
blend_node(p_process_state, p_instance, other_instance, pi, FILTER_IGNORE, true, p_test_only);
|
||||
float max_weight = 0.0;
|
||||
for (int i = 0; i < TRIANGLE_VERTS; i++) {
|
||||
weights[triangle_points[i]] = blend_weights[i];
|
||||
if (blend_weights[i] >= max_weight) {
|
||||
max_weight = blend_weights[i];
|
||||
new_closest = triangle_points[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int new_closest = -1;
|
||||
float new_closest_dist = 1e20;
|
||||
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
float d = blend_points[i].position.distance_squared_to(blend_pos);
|
||||
if (d < new_closest_dist) {
|
||||
@@ -655,49 +663,98 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(ProcessState &p_
|
||||
new_closest_dist = d;
|
||||
}
|
||||
}
|
||||
weights[new_closest] = 1.0;
|
||||
}
|
||||
|
||||
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) {
|
||||
AnimationNodeInstance *instance_new_closest = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(new_closest));
|
||||
|
||||
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
|
||||
NodeTimeInfo from;
|
||||
// For ping-pong loop.
|
||||
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
|
||||
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
|
||||
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(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(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(p_process_state, p_instance, instance_current_closest, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
pi = p_playback_info;
|
||||
pi.weight = 0;
|
||||
// Compute all deltas.
|
||||
if (sync_mode >= SYNC_MODE_CYCLIC_MUTABLE) {
|
||||
// Refresh cached lengths when blend points have changed.
|
||||
if (lengths_dirty) {
|
||||
cached_lengths.resize(blend_points_used);
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i != cur_closest) {
|
||||
AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(get_blend_point_name(i));
|
||||
blend_node(p_process_state, p_instance, &other_instance, pi, FILTER_IGNORE, true, p_test_only);
|
||||
AnimationMixer::PlaybackInfo test_pi = p_playback_info;
|
||||
test_pi.weight = 0;
|
||||
AnimationNodeInstance &other_instance = p_instance.get_child_instance_by_path(get_blend_point_name(i));
|
||||
NodeTimeInfo info = blend_node(p_process_state, p_instance, &other_instance, test_pi, FILTER_IGNORE, true, true);
|
||||
cached_lengths[i] = (info.length > CMP_EPSILON) ? info.length : 0.0;
|
||||
}
|
||||
lengths_dirty = false;
|
||||
}
|
||||
double inv_target_length = 0.0;
|
||||
if (sync_mode == SYNC_MODE_CYCLIC_MUTABLE) {
|
||||
// Compute blended_len from active weights.
|
||||
double target_length = 0.0;
|
||||
double total_weight = 0.0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (weights[i] > 0.0f && cached_lengths[i] > CMP_EPSILON) {
|
||||
target_length += weights[i] * cached_lengths[i];
|
||||
total_weight += weights[i];
|
||||
}
|
||||
}
|
||||
if (total_weight > CMP_EPSILON) {
|
||||
target_length /= total_weight;
|
||||
}
|
||||
inv_target_length = (target_length > CMP_EPSILON) ? (1.0 / target_length) : 0.0;
|
||||
} else {
|
||||
// Use cached inverse of user-specified cyclic_length.
|
||||
inv_target_length = inverted_cycle_length;
|
||||
}
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
deltas[i] = pi.delta * cached_lengths[i] * inv_target_length;
|
||||
}
|
||||
} else if (sync_mode == SYNC_MODE_INDEPENDENT) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
deltas[i] = pi.delta;
|
||||
}
|
||||
} else {
|
||||
// SYNC_MODE_NONE: only active points get delta.
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (weights[i] >= CMP_EPSILON) {
|
||||
deltas[i] = pi.delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p_instance.set_parameter_closest(cur_closest, p_process_state.is_testing);
|
||||
// Special case for discrete carry.
|
||||
if (new_closest != cur_closest && new_closest != -1 && cur_closest != -1 && blend_mode == BLEND_MODE_DISCRETE_CARRY && sync_mode < SYNC_MODE_CYCLIC_MUTABLE) {
|
||||
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.
|
||||
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
|
||||
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
|
||||
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(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(p_process_state, p_instance, instance_new_closest, pi, FILTER_IGNORE, true, p_test_only);
|
||||
deltas[new_closest] = -1; // No more blend new_closest point.
|
||||
}
|
||||
|
||||
// Normal case, blend all points.
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (std::signbit(deltas[i])) {
|
||||
continue;
|
||||
}
|
||||
pi = p_playback_info;
|
||||
pi.weight = weights[i];
|
||||
pi.delta = deltas[i];
|
||||
AnimationNodeInstance *other_instance = p_instance.get_child_instance_by_path_or_null(get_blend_point_name(i));
|
||||
NodeTimeInfo t = blend_node(p_process_state, p_instance, other_instance, pi, FILTER_IGNORE, true, p_test_only);
|
||||
if (i == new_closest) {
|
||||
mind = t;
|
||||
}
|
||||
}
|
||||
|
||||
p_instance.set_parameter_closest(new_closest, p_process_state.is_testing);
|
||||
return mind;
|
||||
}
|
||||
|
||||
@@ -740,16 +797,37 @@ AnimationNodeBlendSpace2D::BlendMode AnimationNodeBlendSpace2D::get_blend_mode()
|
||||
return blend_mode;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void AnimationNodeBlendSpace2D::set_use_sync(bool p_sync) {
|
||||
sync = p_sync;
|
||||
sync_mode = p_sync ? SYNC_MODE_INDEPENDENT : SYNC_MODE_NONE;
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace2D::is_using_sync() const {
|
||||
return sync;
|
||||
return sync_mode != SYNC_MODE_NONE;
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_sync_mode(SyncMode p_sync_mode) {
|
||||
sync_mode = p_sync_mode;
|
||||
_check_can_sync();
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace2D::SyncMode AnimationNodeBlendSpace2D::get_sync_mode() const {
|
||||
return sync_mode;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_cyclic_length(double p_length) {
|
||||
cyclic_length = p_length;
|
||||
inverted_cycle_length = (p_length > CMP_EPSILON) ? (1.0 / p_length) : 0.0;
|
||||
}
|
||||
|
||||
double AnimationNodeBlendSpace2D::get_cyclic_length() const {
|
||||
return cyclic_length;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_tree_changed() {
|
||||
AnimationRootNode::_tree_changed();
|
||||
_check_can_sync();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) {
|
||||
@@ -811,8 +889,16 @@ void AnimationNodeBlendSpace2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace2D::set_blend_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace2D::get_blend_mode);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace2D::set_use_sync);
|
||||
ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace2D::is_using_sync);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_sync_mode", "sync_mode"), &AnimationNodeBlendSpace2D::set_sync_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_sync_mode"), &AnimationNodeBlendSpace2D::get_sync_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_cyclic_length", "length"), &AnimationNodeBlendSpace2D::set_cyclic_length);
|
||||
ClassDB::bind_method(D_METHOD("get_cyclic_length"), &AnimationNodeBlendSpace2D::get_cyclic_length);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_triangles", "get_auto_triangles");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles");
|
||||
@@ -823,10 +909,19 @@ void AnimationNodeBlendSpace2D::_bind_methods() {
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_x_label", "get_x_label");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_y_label", "get_y_label");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync");
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_use_sync", "is_using_sync");
|
||||
#endif // DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "sync_mode", PROPERTY_HINT_ENUM, "None,Independent,Cyclic Mutable,Cyclic Constant", PROPERTY_USAGE_NO_EDITOR), "set_sync_mode", "get_sync_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cyclic_length", PROPERTY_HINT_RANGE, "0.0,99.0,0.001,or_greater", PROPERTY_USAGE_NO_EDITOR), "set_cyclic_length", "get_cyclic_length");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("triangles_updated"));
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY);
|
||||
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_NONE);
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_INDEPENDENT);
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_CYCLIC_MUTABLE);
|
||||
BIND_ENUM_CONSTANT(SYNC_MODE_CYCLIC_CONSTANT);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ public:
|
||||
BLEND_MODE_DISCRETE_CARRY,
|
||||
};
|
||||
|
||||
enum SyncMode {
|
||||
SYNC_MODE_NONE, // Inactive animations are frozen (not advanced).
|
||||
SYNC_MODE_INDEPENDENT, // Inactive animations advance with weight=0 (previous "sync" behavior).
|
||||
SYNC_MODE_CYCLIC_MUTABLE, // Time-scaled with blend-weight-dependent cycle length (self-normalizing).
|
||||
SYNC_MODE_CYCLIC_CONSTANT, // Time-scaled to complete one cycle in cyclic_length seconds.
|
||||
};
|
||||
|
||||
protected:
|
||||
enum {
|
||||
MAX_BLEND_POINTS = 64
|
||||
@@ -78,7 +85,7 @@ protected:
|
||||
void _set_triangles(const Vector<int> &p_triangles);
|
||||
Vector<int> _get_triangles() const;
|
||||
|
||||
void _blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights);
|
||||
void _blend_triangle(const Vector2 &p_pos, const LocalVector<Vector2> &p_points, LocalVector<float> &r_weights);
|
||||
|
||||
bool auto_triangles = true;
|
||||
bool triangles_dirty = false;
|
||||
@@ -86,7 +93,11 @@ protected:
|
||||
void _update_triangles();
|
||||
void _queue_auto_triangles();
|
||||
|
||||
bool sync = false;
|
||||
SyncMode sync_mode = SYNC_MODE_NONE;
|
||||
double cyclic_length = 0.0;
|
||||
double inverted_cycle_length = 0.0; // Cached 1/cyclic_length.
|
||||
LocalVector<double> cached_lengths;
|
||||
bool lengths_dirty = true;
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
static void _bind_methods();
|
||||
@@ -95,6 +106,9 @@ protected:
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override;
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
|
||||
|
||||
bool is_contain_invalid_point = false;
|
||||
void _check_can_sync();
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _add_blend_point_bind_compat_110369(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index = -1);
|
||||
static void _bind_compatibility_methods();
|
||||
@@ -153,10 +167,19 @@ public:
|
||||
void set_blend_mode(BlendMode p_blend_mode);
|
||||
BlendMode get_blend_mode() const;
|
||||
|
||||
void set_use_sync(bool p_sync);
|
||||
bool is_using_sync() const;
|
||||
void set_cyclic_length(double p_length);
|
||||
double get_cyclic_length() const;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void set_use_sync(bool p_sync); // Compat: maps to SYNC_MODE_INDEPENDENT or SYNC_MODE_NONE.
|
||||
bool is_using_sync() const; // Compat: returns sync_mode != SYNC_MODE_NONE.
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void set_sync_mode(SyncMode p_sync_mode);
|
||||
SyncMode get_sync_mode() const;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeBlendSpace2D::BlendMode)
|
||||
VARIANT_ENUM_CAST(AnimationNodeBlendSpace2D::SyncMode)
|
||||
|
||||
Reference in New Issue
Block a user