From 9f67b3b2975273bcec0987deef5044561d74ffbe Mon Sep 17 00:00:00 2001 From: christiano Date: Thu, 12 Feb 2026 17:42:12 +0000 Subject: [PATCH 01/42] wip curve smoothing --- editor/scene/3d/path_3d_editor_plugin.cpp | 36 +++++++++++++++++++++++ editor/scene/3d/path_3d_editor_plugin.h | 1 + 2 files changed, 37 insertions(+) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index cc3caea155d1..6fd02d36a8f8 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -857,6 +857,34 @@ void Path3DEditorPlugin::_confirm_clear_points() { clear_points_dialog->popup_centered(); } +void Path3DEditorPlugin::_smooth_points(){ + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + PackedVector3Array points = path->get_curve()->get_points().duplicate(); + + undo_redo->create_action(TTR("Smooth Curve Points")); + undo_redo->add_do_method(this, "_smooth_curve_points"); + undo_redo->add_undo_method(this, "_restore_curve_points", points); + undo_redo->commit_action(); +} + +void Path3DEditorPlugin::_smooth_curve_points(){ + if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() < 2) { + return; + } + Ref curve = path->get_curve(); + const smooth_ratio = 0.33; + for (int i = 0; i < curve.get_point_count(); i += 1) { + if i == 0 or i == curve.point_count-1: + continue + var previous_point = curve.samplef(i-smooth_ratio) + var next_point = curve.samplef(i+smooth_ratio) + var point_in = -(next_point-previous_point) + var point_out = -(previous_point-next_point) + curve.set_point_in(i,point_in) + curve.set_point_out(i,point_out) + } +} + void Path3DEditorPlugin::_clear_points() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector3Array points = path->get_curve()->get_points().duplicate(); @@ -899,6 +927,7 @@ void Path3DEditorPlugin::_update_theme() { curve_del->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete"))); curve_closed->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); curve_clear_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear"))); + curve_smooth_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); create_curve_button->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D"))); } @@ -1048,6 +1077,13 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_closed); curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve)); + smooth_points = memnew(Button); + smooth_points->set_theme_type_variation(SceneStringName(FlatButton)); + smooth_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + smooth_points->set_tooltip_text(TTR("Smooth Points")); + toolbar->add_child(smooth_points); + curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_smooth_points)); + curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); curve_clear_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index 7688fd919d23..a0b6b034f192 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -153,6 +153,7 @@ class Path3DEditorPlugin : public EditorPlugin { void _create_curve(); void _confirm_clear_points(); void _clear_points(); + void _smooth_points(); void _clear_curve_points(); void _restore_curve_points(const PackedVector3Array &p_points); From d157a0601be88c9cd8ec1f0ec85210cdcc29389f Mon Sep 17 00:00:00 2001 From: christiano Date: Mon, 16 Feb 2026 16:14:45 +0000 Subject: [PATCH 02/42] Wip curve smoothing pt2 --- editor/scene/3d/path_3d_editor_plugin.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 6fd02d36a8f8..366b4537fc48 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -868,20 +868,18 @@ void Path3DEditorPlugin::_smooth_points(){ } void Path3DEditorPlugin::_smooth_curve_points(){ - if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() < 2) { + if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { return; } Ref curve = path->get_curve(); - const smooth_ratio = 0.33; - for (int i = 0; i < curve.get_point_count(); i += 1) { - if i == 0 or i == curve.point_count-1: - continue - var previous_point = curve.samplef(i-smooth_ratio) - var next_point = curve.samplef(i+smooth_ratio) - var point_in = -(next_point-previous_point) - var point_out = -(previous_point-next_point) - curve.set_point_in(i,point_in) - curve.set_point_out(i,point_out) + const float smooth_ratio = 0.33; + for (int i = 1; i < curve.get_point_count() -1; i += 1) { + Vector3 previous_point = curve->samplef(i-smooth_ratio); + Vector3 next_point = curve.samplef(i+smooth_ratio); + Vector3 point_in = previous_point - next_point; + Vector3 point_out = next_point - previous_point; + curve -> set_point_in(i,point_in); + curve -> set_point_out(i,point_out); } } From 1bb3ce1ee26accd4e55efc584b684f027eb02cc4 Mon Sep 17 00:00:00 2001 From: Christiano Date: Mon, 16 Feb 2026 17:54:52 +0000 Subject: [PATCH 03/42] Add smooth curve button and implement curve smoothing functionality --- editor/scene/3d/path_3d_editor_plugin.cpp | 35 ++++++++++++++--------- editor/scene/3d/path_3d_editor_plugin.h | 2 ++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 366b4537fc48..d115da5e3d4e 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -873,13 +873,19 @@ void Path3DEditorPlugin::_smooth_curve_points(){ } Ref curve = path->get_curve(); const float smooth_ratio = 0.33; - for (int i = 1; i < curve.get_point_count() -1; i += 1) { - Vector3 previous_point = curve->samplef(i-smooth_ratio); - Vector3 next_point = curve.samplef(i+smooth_ratio); - Vector3 point_in = previous_point - next_point; - Vector3 point_out = next_point - previous_point; - curve -> set_point_in(i,point_in); - curve -> set_point_out(i,point_out); + for (int i = 0; i < curve->get_point_count(); i++) { + if (i > 0) { + Vector3 p_prev = curve->get_point_position(i - 1); + Vector3 p_curr = curve->get_point_position(i); + Vector3 direction = p_curr - p_prev; + curve->set_point_in(i, -direction * smoothness); + } + if (i < curve->get_point_count() - 1) { + Vector3 p_curr = curve->get_point_position(i); + Vector3 p_next = curve->get_point_position(i + 1); + Vector3 direction = p_next - p_curr; + curve->set_point_out(i, direction * smoothness); + } } } @@ -925,7 +931,7 @@ void Path3DEditorPlugin::_update_theme() { curve_del->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete"))); curve_closed->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); curve_clear_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear"))); - curve_smooth_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); + curve_smooth_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveSmooth"))); create_curve_button->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D"))); } @@ -1006,6 +1012,7 @@ void Path3DEditorPlugin::_notification(int p_what) { void Path3DEditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path3DEditorPlugin::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path3DEditorPlugin::_clear_curve_points); + ClassDB::bind_method(D_METHOD("_smooth_curve_points"), &Path3DEditorPlugin::_smooth_curve_points); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path3DEditorPlugin::_restore_curve_points); } @@ -1075,12 +1082,12 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_closed); curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve)); - smooth_points = memnew(Button); - smooth_points->set_theme_type_variation(SceneStringName(FlatButton)); - smooth_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - smooth_points->set_tooltip_text(TTR("Smooth Points")); - toolbar->add_child(smooth_points); - curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_smooth_points)); + curve_smooth_points = memnew(Button); + curve_smooth_points->set_theme_type_variation(SceneStringName(FlatButton)); + curve_smooth_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_smooth_points->set_tooltip_text(TTR("Smooth Points")); + toolbar->add_child(curve_smooth_points); + curve_smooth_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_smooth_points)); curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index a0b6b034f192..2ae9ab4e259a 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -123,6 +123,7 @@ class Path3DEditorPlugin : public EditorPlugin { Button *curve_del = nullptr; Button *curve_closed = nullptr; Button *curve_clear_points = nullptr; + Button *curve_smooth_points = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; @@ -154,6 +155,7 @@ class Path3DEditorPlugin : public EditorPlugin { void _confirm_clear_points(); void _clear_points(); void _smooth_points(); + void _smooth_curve_points(); void _clear_curve_points(); void _restore_curve_points(const PackedVector3Array &p_points); From ef74e1f25b2f574fff54fa00a7bf7607f5bb5bac Mon Sep 17 00:00:00 2001 From: Christiano Date: Mon, 16 Feb 2026 20:54:04 +0000 Subject: [PATCH 04/42] Use cat-mull rom smoothing to make it depend only on the point positions --- editor/scene/3d/path_3d_editor_plugin.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index d115da5e3d4e..9619a4b71d8d 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -871,21 +871,17 @@ void Path3DEditorPlugin::_smooth_curve_points(){ if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { return; } + // Catmull–Rom smoothing Ref curve = path->get_curve(); + int point_count = curve->get_point_count(); const float smooth_ratio = 0.33; - for (int i = 0; i < curve->get_point_count(); i++) { - if (i > 0) { - Vector3 p_prev = curve->get_point_position(i - 1); - Vector3 p_curr = curve->get_point_position(i); - Vector3 direction = p_curr - p_prev; - curve->set_point_in(i, -direction * smoothness); - } - if (i < curve->get_point_count() - 1) { - Vector3 p_curr = curve->get_point_position(i); - Vector3 p_next = curve->get_point_position(i + 1); - Vector3 direction = p_next - p_curr; - curve->set_point_out(i, direction * smoothness); - } + for (int i = 1; i < point_count - 1; i++) { + Vector3 next_p = curve->get_point_position(i-1); + Vector3 curr_p = curve->get_point_position(i); + Vector3 prev_p = curve->get_point_position(i+1); + Vector3 tangent = (next_p - prev_p) / 2.0f; + curve->set_point_in(i, tangent*smooth_ratio); + curve->set_point_out(i,-tangent*smooth_ratio); } } From 50d2408ec3ec456d3bdf37edf933447fc006ba0a Mon Sep 17 00:00:00 2001 From: Christiano Date: Mon, 16 Feb 2026 21:57:06 +0000 Subject: [PATCH 05/42] Fix some issues handling closed curves --- editor/scene/3d/path_3d_editor_plugin.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 9619a4b71d8d..cfed37215a41 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -874,14 +874,23 @@ void Path3DEditorPlugin::_smooth_curve_points(){ // Catmull–Rom smoothing Ref curve = path->get_curve(); int point_count = curve->get_point_count(); - const float smooth_ratio = 0.33; - for (int i = 1; i < point_count - 1; i++) { + const float smooth_ratio = 0.5; + for (int i = 1; i < point_count - 1; i++) { // Ignore first and last points unless it's closed. Vector3 next_p = curve->get_point_position(i-1); - Vector3 curr_p = curve->get_point_position(i); Vector3 prev_p = curve->get_point_position(i+1); - Vector3 tangent = (next_p - prev_p) / 2.0f; + Vector3 tangent = (next_p - prev_p) * smooth_ratio; curve->set_point_in(i, tangent*smooth_ratio); - curve->set_point_out(i,-tangent*smooth_ratio); + curve->set_point_out(i, -tangent*smooth_ratio); + } + if (curve->is_closed()){ + Vector3 first_p = curve->get_point_position(0); + Vector3 last_p = curve->get_point_position(point_count-1); + + Vector3 first_last_tangent = (last_p - first_p) * smooth_ratio; + curve->set_point_out(0, -first_p_tangent); + + Vector3 last_p_tangent = (curve->get_point_position(point_count-2) - first_p) * smooth_ratio; + curve->set_point_in(point_count-1, last_p_tangent); } } From f6c8bfdaf390a33592f4598b6e43b14428781990 Mon Sep 17 00:00:00 2001 From: Christiano Date: Mon, 16 Feb 2026 21:59:28 +0000 Subject: [PATCH 06/42] Stop multiplying by smoothing_ratio twice and adjust handling of closed points --- editor/scene/3d/path_3d_editor_plugin.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index cfed37215a41..cb473ef24b85 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -879,18 +879,17 @@ void Path3DEditorPlugin::_smooth_curve_points(){ Vector3 next_p = curve->get_point_position(i-1); Vector3 prev_p = curve->get_point_position(i+1); Vector3 tangent = (next_p - prev_p) * smooth_ratio; - curve->set_point_in(i, tangent*smooth_ratio); - curve->set_point_out(i, -tangent*smooth_ratio); + curve->set_point_in(i, tangent); + curve->set_point_out(i, -tangent); } if (curve->is_closed()){ Vector3 first_p = curve->get_point_position(0); Vector3 last_p = curve->get_point_position(point_count-1); Vector3 first_last_tangent = (last_p - first_p) * smooth_ratio; - curve->set_point_out(0, -first_p_tangent); + curve->set_point_out(0, -first_last_tangent); + curve->set_point_in(point_count-1, first_last_tangent); - Vector3 last_p_tangent = (curve->get_point_position(point_count-2) - first_p) * smooth_ratio; - curve->set_point_in(point_count-1, last_p_tangent); } } From 3c3c3473aecf43dbf7727e4d3b01609b953b0c68 Mon Sep 17 00:00:00 2001 From: Christiano Date: Mon, 16 Feb 2026 22:01:59 +0000 Subject: [PATCH 07/42] smooth_ratio looks better at 0.33 --- editor/scene/3d/path_3d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index cb473ef24b85..d52eb2457a78 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -874,7 +874,7 @@ void Path3DEditorPlugin::_smooth_curve_points(){ // Catmull–Rom smoothing Ref curve = path->get_curve(); int point_count = curve->get_point_count(); - const float smooth_ratio = 0.5; + const float smooth_ratio = 0.33; for (int i = 1; i < point_count - 1; i++) { // Ignore first and last points unless it's closed. Vector3 next_p = curve->get_point_position(i-1); Vector3 prev_p = curve->get_point_position(i+1); From 2b223ce1115b570857c2833d5bceb27e2d7eba2d Mon Sep 17 00:00:00 2001 From: Christiano Date: Tue, 17 Feb 2026 00:14:10 +0000 Subject: [PATCH 08/42] Make the in and out points depend on distance to next and previous points to avoid kinks --- editor/scene/3d/path_3d_editor_plugin.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index d52eb2457a78..e4bcfbb8b945 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -878,15 +878,16 @@ void Path3DEditorPlugin::_smooth_curve_points(){ for (int i = 1; i < point_count - 1; i++) { // Ignore first and last points unless it's closed. Vector3 next_p = curve->get_point_position(i-1); Vector3 prev_p = curve->get_point_position(i+1); - Vector3 tangent = (next_p - prev_p) * smooth_ratio; - curve->set_point_in(i, tangent); - curve->set_point_out(i, -tangent); + Vector3 curr_p = curve->get_point_position(i); + Vector3 tangent = (next_p - prev_p).normalized(); + curve->set_point_in(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); + curve->set_point_out(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); } if (curve->is_closed()){ Vector3 first_p = curve->get_point_position(0); Vector3 last_p = curve->get_point_position(point_count-1); - Vector3 first_last_tangent = (last_p - first_p) * smooth_ratio; + Vector3 first_last_tangent = (last_p - first_p).normalized(); curve->set_point_out(0, -first_last_tangent); curve->set_point_in(point_count-1, first_last_tangent); From d107ecc3327d5979a265e2b5f6dfc26ca58a5a1b Mon Sep 17 00:00:00 2001 From: Christiano Date: Tue, 17 Feb 2026 00:20:12 +0000 Subject: [PATCH 09/42] Reset point_in and point_out when curve is closed --- editor/scene/3d/path_3d_editor_plugin.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index e4bcfbb8b945..cf9a8a87acca 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -857,7 +857,7 @@ void Path3DEditorPlugin::_confirm_clear_points() { clear_points_dialog->popup_centered(); } -void Path3DEditorPlugin::_smooth_points(){ +void Path3DEditorPlugin::_smooth_points() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector3Array points = path->get_curve()->get_points().duplicate(); @@ -867,30 +867,33 @@ void Path3DEditorPlugin::_smooth_points(){ undo_redo->commit_action(); } -void Path3DEditorPlugin::_smooth_curve_points(){ +void Path3DEditorPlugin::_smooth_curve_points() { if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { return; } - // Catmull–Rom smoothing + // Catmull–Rom smoothing Ref curve = path->get_curve(); int point_count = curve->get_point_count(); const float smooth_ratio = 0.33; for (int i = 1; i < point_count - 1; i++) { // Ignore first and last points unless it's closed. - Vector3 next_p = curve->get_point_position(i-1); - Vector3 prev_p = curve->get_point_position(i+1); + Vector3 next_p = curve->get_point_position(i - 1); + Vector3 prev_p = curve->get_point_position(i + 1); Vector3 curr_p = curve->get_point_position(i); Vector3 tangent = (next_p - prev_p).normalized(); curve->set_point_in(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); curve->set_point_out(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); } - if (curve->is_closed()){ + if (curve->is_closed()) { Vector3 first_p = curve->get_point_position(0); - Vector3 last_p = curve->get_point_position(point_count-1); + Vector3 last_p = curve->get_point_position(point_count - 1); Vector3 first_last_tangent = (last_p - first_p).normalized(); curve->set_point_out(0, -first_last_tangent); - curve->set_point_in(point_count-1, first_last_tangent); + curve->set_point_in(point_count - 1, first_last_tangent); + } else { + curve->set_point_out(0, Vector3()); + curve->set_point_in(point_count - 1, Vector3()); } } From 90c521beaa3e8944c7ec740b7b788289591d879b Mon Sep 17 00:00:00 2001 From: Christiano Date: Tue, 17 Feb 2026 00:22:00 +0000 Subject: [PATCH 10/42] Remove outdated comment --- editor/scene/3d/path_3d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index cf9a8a87acca..d4964a5e2490 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -875,7 +875,7 @@ void Path3DEditorPlugin::_smooth_curve_points() { Ref curve = path->get_curve(); int point_count = curve->get_point_count(); const float smooth_ratio = 0.33; - for (int i = 1; i < point_count - 1; i++) { // Ignore first and last points unless it's closed. + for (int i = 1; i < point_count - 1; i++) { Vector3 next_p = curve->get_point_position(i - 1); Vector3 prev_p = curve->get_point_position(i + 1); Vector3 curr_p = curve->get_point_position(i); From 424eec71de175de3b69985e9f58b9aa2cfb553c5 Mon Sep 17 00:00:00 2001 From: Christiano Date: Wed, 18 Feb 2026 17:29:53 +0000 Subject: [PATCH 11/42] Update smooth_ratio to 0.5 for improved curve smoothing --- editor/scene/3d/path_3d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index d4964a5e2490..3064926be83b 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -874,7 +874,7 @@ void Path3DEditorPlugin::_smooth_curve_points() { // Catmull–Rom smoothing Ref curve = path->get_curve(); int point_count = curve->get_point_count(); - const float smooth_ratio = 0.33; + const float smooth_ratio = 0.5; for (int i = 1; i < point_count - 1; i++) { Vector3 next_p = curve->get_point_position(i - 1); Vector3 prev_p = curve->get_point_position(i + 1); From 981e82a69f7b0ad0a920a3f0aa4679aeff219e5c Mon Sep 17 00:00:00 2001 From: Christiano Date: Thu, 19 Feb 2026 01:14:33 +0000 Subject: [PATCH 12/42] Implement smoothing in Path2D (wip) --- editor/scene/2d/path_2d_editor_plugin.cpp | 49 +++++++++++++++++++++++ editor/scene/2d/path_2d_editor_plugin.h | 4 ++ 2 files changed, 53 insertions(+) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index f66520560d16..31a7aefc492d 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -659,6 +659,7 @@ void Path2DEditor::edit(Node *p_path2d) { void Path2DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points); + ClassDB::bind_method(D_METHOD("_smooth_curve_points"), &Path2DEditor::_smooth_curve_points); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points); } @@ -806,7 +807,48 @@ void Path2DEditor::_confirm_clear_points() { clear_points_dialog->reset_size(); clear_points_dialog->popup_centered(); } +void Path2DEditor::_smooth_points() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + PackedVector2Array points = node->get_curve()->get_points().duplicate(); + + undo_redo->create_action(TTR("Smooth Curve Points")); + undo_redo->add_do_method(this, "_smooth_curve_points"); + undo_redo->add_undo_method(this, "_restore_curve_points", node, points); + undo_redo->commit_action(); +} +void Path2DEditor::_smooth_curve_points() { + if (!node || node->get_curve().is_null() || node->get_curve()->get_point_count() <= 2) { + return; + } + // Catmull–Rom smoothing + Ref curve = node->get_curve(); + int point_count = curve->get_point_count(); + const float smooth_ratio = 0.5; + for (int i = 1; i < point_count - 1; i++) { + Vector2 next_p = curve->get_point_position(i - 1); + Vector2 prev_p = curve->get_point_position(i + 1); + Vector2 curr_p = curve->get_point_position(i); + Vector2 tangent = (next_p - prev_p).normalized(); + curve->set_point_in(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); + curve->set_point_out(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + } + Vector2 begin = node->get_curve()->get_point_position(0); + Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1); + + if (begin.is_equal_approx(end)) { + Vector2 first_p = curve->get_point_position(0); + Vector2 last_p = curve->get_point_position(point_count - 1); + + Vector2 first_last_tangent = (last_p - first_p).normalized(); + curve->set_point_out(0, -first_last_tangent); + curve->set_point_in(point_count - 1, first_last_tangent); + + } else { + curve->set_point_out(0, Vector2()); + curve->set_point_in(point_count - 1, Vector2()); + } +} void Path2DEditor::_clear_curve_points(Path2D *p_path2d) { if (!p_path2d || p_path2d->get_curve().is_null()) { return; @@ -888,6 +930,13 @@ Path2DEditor::Path2DEditor() { curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE)); toolbar->add_child(curve_close); + curve_smooth_points = memnew(Button); + curve_smooth_points->set_theme_type_variation(SceneStringName(FlatButton)); + curve_smooth_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_smooth_points->set_tooltip_text(TTR("Smooth Points")); + curve_smooth_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_smooth_points)); + toolbar->add_child(curve_smooth_points); + curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); curve_clear_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index 08e81d185f2d..889c3d08bd9b 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -64,6 +64,7 @@ class Path2DEditor : public HBoxContainer { Button *curve_del = nullptr; Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; + Button *curve_smooth_points = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; @@ -107,6 +108,9 @@ class Path2DEditor : public HBoxContainer { void _node_visibility_changed(); void _update_toolbar(); + void _smooth_curve_points(); + void _smooth_points(); + void _create_curve(); void _confirm_clear_points(); void _clear_curve_points(Path2D *p_path2d); From da5b31373513046e82b08d9be0abe617bd422f3c Mon Sep 17 00:00:00 2001 From: Christiano Date: Sat, 21 Feb 2026 15:18:38 +0000 Subject: [PATCH 13/42] Fix next_p and prev_p being swapped and improve handling for closed points in 2d curve --- editor/scene/2d/path_2d_editor_plugin.cpp | 24 +++++++++++------------ editor/scene/3d/path_3d_editor_plugin.cpp | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 31a7aefc492d..0ff78edc8809 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -826,23 +826,23 @@ void Path2DEditor::_smooth_curve_points() { int point_count = curve->get_point_count(); const float smooth_ratio = 0.5; for (int i = 1; i < point_count - 1; i++) { - Vector2 next_p = curve->get_point_position(i - 1); - Vector2 prev_p = curve->get_point_position(i + 1); + Vector2 prev_p = curve->get_point_position(i - 1); + Vector2 next_p = curve->get_point_position(i + 1); Vector2 curr_p = curve->get_point_position(i); Vector2 tangent = (next_p - prev_p).normalized(); - curve->set_point_in(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); - curve->set_point_out(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + curve->set_point_in(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + curve->set_point_out(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); } - Vector2 begin = node->get_curve()->get_point_position(0); - Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1); + Vector2 begin = curve->get_point_position(0); + Vector2 end = curve->get_point_position(point_count - 1); if (begin.is_equal_approx(end)) { - Vector2 first_p = curve->get_point_position(0); - Vector2 last_p = curve->get_point_position(point_count - 1); - - Vector2 first_last_tangent = (last_p - first_p).normalized(); - curve->set_point_out(0, -first_last_tangent); - curve->set_point_in(point_count - 1, first_last_tangent); + // handles closed curves in path2D having the end and start points at the same location + Vector2 next_p = curve->get_point_position(1); + Vector2 prev_p = curve->get_point_position(point_count - 2); + Vector2 first_last_tangent = (next_p - prev_p).normalized(); + curve->set_point_in(point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); + curve->set_point_out(0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); } else { curve->set_point_out(0, Vector2()); diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 3064926be83b..55ce7f773cd1 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -876,12 +876,12 @@ void Path3DEditorPlugin::_smooth_curve_points() { int point_count = curve->get_point_count(); const float smooth_ratio = 0.5; for (int i = 1; i < point_count - 1; i++) { - Vector3 next_p = curve->get_point_position(i - 1); - Vector3 prev_p = curve->get_point_position(i + 1); + Vector3 next_p = curve->get_point_position(i + 1); + Vector3 prev_p = curve->get_point_position(i - 1); Vector3 curr_p = curve->get_point_position(i); Vector3 tangent = (next_p - prev_p).normalized(); - curve->set_point_in(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); - curve->set_point_out(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + curve->set_point_in(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + curve->set_point_out(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); } if (curve->is_closed()) { Vector3 first_p = curve->get_point_position(0); From 7d07970e9d4672df5ed48dad03dbb8c7dbf4c28d Mon Sep 17 00:00:00 2001 From: Christiano Date: Sat, 21 Feb 2026 15:52:06 +0000 Subject: [PATCH 14/42] Rename to auto-tangent and add icon svg --- editor/icons/CurveAutoTangent.svg | 1 + editor/scene/2d/path_2d_editor_plugin.cpp | 24 +++++++++++------------ editor/scene/2d/path_2d_editor_plugin.h | 6 +++--- editor/scene/3d/path_3d_editor_plugin.cpp | 24 +++++++++++------------ editor/scene/3d/path_3d_editor_plugin.h | 6 +++--- 5 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 editor/icons/CurveAutoTangent.svg diff --git a/editor/icons/CurveAutoTangent.svg b/editor/icons/CurveAutoTangent.svg new file mode 100644 index 000000000000..1876563dd51d --- /dev/null +++ b/editor/icons/CurveAutoTangent.svg @@ -0,0 +1 @@ + diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 0ff78edc8809..2c8285579f50 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -49,7 +49,7 @@ void Path2DEditor::_notification(int p_what) { curve_del->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete"))); curve_close->set_button_icon(get_editor_theme_icon(SNAME("CurveClose"))); curve_clear_points->set_button_icon(get_editor_theme_icon(SNAME("Clear"))); - + curve_auto_tangent->set_button_icon(get_editor_theme_icon(SNAME("CurveAutoTangent"))); create_curve_button->set_button_icon(get_editor_theme_icon(SNAME("Curve2D"))); } break; } @@ -659,7 +659,7 @@ void Path2DEditor::edit(Node *p_path2d) { void Path2DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points); - ClassDB::bind_method(D_METHOD("_smooth_curve_points"), &Path2DEditor::_smooth_curve_points); + ClassDB::bind_method(D_METHOD("_auto_tangent_curve"), &Path2DEditor::_auto_tangent_curve); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points); } @@ -807,17 +807,17 @@ void Path2DEditor::_confirm_clear_points() { clear_points_dialog->reset_size(); clear_points_dialog->popup_centered(); } -void Path2DEditor::_smooth_points() { +void Path2DEditor::_auto_tangent() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector2Array points = node->get_curve()->get_points().duplicate(); - undo_redo->create_action(TTR("Smooth Curve Points")); - undo_redo->add_do_method(this, "_smooth_curve_points"); + undo_redo->create_action(TTR("Auto Tangent")); + undo_redo->add_do_method(this, "_auto_tangent_curve"); undo_redo->add_undo_method(this, "_restore_curve_points", node, points); undo_redo->commit_action(); } -void Path2DEditor::_smooth_curve_points() { +void Path2DEditor::_auto_tangent_curve() { if (!node || node->get_curve().is_null() || node->get_curve()->get_point_count() <= 2) { return; } @@ -930,12 +930,12 @@ Path2DEditor::Path2DEditor() { curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE)); toolbar->add_child(curve_close); - curve_smooth_points = memnew(Button); - curve_smooth_points->set_theme_type_variation(SceneStringName(FlatButton)); - curve_smooth_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_smooth_points->set_tooltip_text(TTR("Smooth Points")); - curve_smooth_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_smooth_points)); - toolbar->add_child(curve_smooth_points); + curve_auto_tangent = memnew(Button); + curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); + curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_auto_tangent->set_tooltip_text(TTR("Smooth Points")); + curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_auto_tangent)); + toolbar->add_child(curve_auto_tangent); curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index 889c3d08bd9b..c59a61a4f0af 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -64,7 +64,7 @@ class Path2DEditor : public HBoxContainer { Button *curve_del = nullptr; Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; - Button *curve_smooth_points = nullptr; + Button *curve_auto_tangent = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; @@ -108,8 +108,8 @@ class Path2DEditor : public HBoxContainer { void _node_visibility_changed(); void _update_toolbar(); - void _smooth_curve_points(); - void _smooth_points(); + void _auto_tangent(); + void _auto_tangent_curve(); void _create_curve(); void _confirm_clear_points(); diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 55ce7f773cd1..d365eeed3d0e 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -857,17 +857,17 @@ void Path3DEditorPlugin::_confirm_clear_points() { clear_points_dialog->popup_centered(); } -void Path3DEditorPlugin::_smooth_points() { +void Path3DEditorPlugin::_auto_tangent() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector3Array points = path->get_curve()->get_points().duplicate(); - undo_redo->create_action(TTR("Smooth Curve Points")); - undo_redo->add_do_method(this, "_smooth_curve_points"); + undo_redo->create_action(TTR("Auto Tangent")); + undo_redo->add_do_method(this, "_auto_tangent_curve"); undo_redo->add_undo_method(this, "_restore_curve_points", points); undo_redo->commit_action(); } -void Path3DEditorPlugin::_smooth_curve_points() { +void Path3DEditorPlugin::_auto_tangent_curve() { if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { return; } @@ -939,7 +939,7 @@ void Path3DEditorPlugin::_update_theme() { curve_del->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete"))); curve_closed->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); curve_clear_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear"))); - curve_smooth_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveSmooth"))); + curve_auto_tangent->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveAutoTangent"))); create_curve_button->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D"))); } @@ -1020,7 +1020,7 @@ void Path3DEditorPlugin::_notification(int p_what) { void Path3DEditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path3DEditorPlugin::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path3DEditorPlugin::_clear_curve_points); - ClassDB::bind_method(D_METHOD("_smooth_curve_points"), &Path3DEditorPlugin::_smooth_curve_points); + ClassDB::bind_method(D_METHOD("_auto_tangent_curve"), &Path3DEditorPlugin::_auto_tangent_curve); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path3DEditorPlugin::_restore_curve_points); } @@ -1090,12 +1090,12 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_closed); curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve)); - curve_smooth_points = memnew(Button); - curve_smooth_points->set_theme_type_variation(SceneStringName(FlatButton)); - curve_smooth_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_smooth_points->set_tooltip_text(TTR("Smooth Points")); - toolbar->add_child(curve_smooth_points); - curve_smooth_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_smooth_points)); + curve_auto_tangent = memnew(Button); + curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); + curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_auto_tangent->set_tooltip_text(TTR("Auto Tangent")); + toolbar->add_child(curve_auto_tangent); + curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_auto_tangent)); curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index 2ae9ab4e259a..4efcfcd06887 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -123,7 +123,7 @@ class Path3DEditorPlugin : public EditorPlugin { Button *curve_del = nullptr; Button *curve_closed = nullptr; Button *curve_clear_points = nullptr; - Button *curve_smooth_points = nullptr; + Button *curve_auto_tangent = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; @@ -154,8 +154,8 @@ class Path3DEditorPlugin : public EditorPlugin { void _create_curve(); void _confirm_clear_points(); void _clear_points(); - void _smooth_points(); - void _smooth_curve_points(); + void _auto_tangent(); + void _auto_tangent_curve(); void _clear_curve_points(); void _restore_curve_points(const PackedVector3Array &p_points); From 228dc9025dc7b379b7d074ae7e170f689c00d036 Mon Sep 17 00:00:00 2001 From: Christiano Date: Sat, 21 Feb 2026 16:04:47 +0000 Subject: [PATCH 15/42] Rename smooth points button to auto tangent and update tooltip --- editor/scene/2d/path_2d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 2c8285579f50..a139cc9e9cbc 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -933,7 +933,7 @@ Path2DEditor::Path2DEditor() { curve_auto_tangent = memnew(Button); curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_auto_tangent->set_tooltip_text(TTR("Smooth Points")); + curve_auto_tangent->set_tooltip_text(TTR("Auto Tangent")); curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_auto_tangent)); toolbar->add_child(curve_auto_tangent); From ccfe43ca1e58acaf6c7a03afbd26c7ed9dca3c31 Mon Sep 17 00:00:00 2001 From: Christiano Date: Sat, 21 Feb 2026 20:20:48 +0000 Subject: [PATCH 16/42] Address PR suggestions and rework closed curve logic in 3D to align with expectations for catrom --- editor/scene/2d/path_2d_editor_plugin.cpp | 46 +++++++++++++--------- editor/scene/2d/path_2d_editor_plugin.h | 1 - editor/scene/3d/path_3d_editor_plugin.cpp | 47 ++++++++++++++--------- editor/scene/3d/path_3d_editor_plugin.h | 1 - 4 files changed, 56 insertions(+), 39 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index a139cc9e9cbc..56a0926f2c9c 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -659,7 +659,6 @@ void Path2DEditor::edit(Node *p_path2d) { void Path2DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points); - ClassDB::bind_method(D_METHOD("_auto_tangent_curve"), &Path2DEditor::_auto_tangent_curve); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points); } @@ -808,21 +807,13 @@ void Path2DEditor::_confirm_clear_points() { clear_points_dialog->popup_centered(); } void Path2DEditor::_auto_tangent() { - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - PackedVector2Array points = node->get_curve()->get_points().duplicate(); - - undo_redo->create_action(TTR("Auto Tangent")); - undo_redo->add_do_method(this, "_auto_tangent_curve"); - undo_redo->add_undo_method(this, "_restore_curve_points", node, points); - undo_redo->commit_action(); -} - -void Path2DEditor::_auto_tangent_curve() { if (!node || node->get_curve().is_null() || node->get_curve()->get_point_count() <= 2) { return; } - // Catmull–Rom smoothing + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + PackedVector2Array points = node->get_curve()->get_points().duplicate(); Ref curve = node->get_curve(); + undo_redo->create_action(TTR("Auto Tangent")); int point_count = curve->get_point_count(); const float smooth_ratio = 0.5; for (int i = 1; i < point_count - 1; i++) { @@ -830,8 +821,14 @@ void Path2DEditor::_auto_tangent_curve() { Vector2 next_p = curve->get_point_position(i + 1); Vector2 curr_p = curve->get_point_position(i); Vector2 tangent = (next_p - prev_p).normalized(); - curve->set_point_in(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - curve->set_point_out(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); + + undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + // curve->set_point_in(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + + undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", i, tangent * curr_p.distance_to(next_p) * smooth_ratio); + //curve->set_point_out(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); } Vector2 begin = curve->get_point_position(0); Vector2 end = curve->get_point_position(point_count - 1); @@ -841,14 +838,27 @@ void Path2DEditor::_auto_tangent_curve() { Vector2 next_p = curve->get_point_position(1); Vector2 prev_p = curve->get_point_position(point_count - 2); Vector2 first_last_tangent = (next_p - prev_p).normalized(); - curve->set_point_in(point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); - curve->set_point_out(0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); + + undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); + // curve->set_point_in(point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); + + undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); + //curve->set_point_out(0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); } else { - curve->set_point_out(0, Vector2()); - curve->set_point_in(point_count - 1, Vector2()); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, Vector2()); + //curve->set_point_out(0, Vector2()); + + undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, Vector2()); + //curve->set_point_in(point_count - 1, Vector2()); } + undo_redo->commit_action(); } + void Path2DEditor::_clear_curve_points(Path2D *p_path2d) { if (!p_path2d || p_path2d->get_curve().is_null()) { return; diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index c59a61a4f0af..af9d7cb7d609 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -109,7 +109,6 @@ class Path2DEditor : public HBoxContainer { void _update_toolbar(); void _auto_tangent(); - void _auto_tangent_curve(); void _create_curve(); void _confirm_clear_points(); diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index d365eeed3d0e..3fb2fa404c35 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -858,21 +858,15 @@ void Path3DEditorPlugin::_confirm_clear_points() { } void Path3DEditorPlugin::_auto_tangent() { + if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { + return; + } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector3Array points = path->get_curve()->get_points().duplicate(); - + Ref curve = path->get_curve(); undo_redo->create_action(TTR("Auto Tangent")); - undo_redo->add_do_method(this, "_auto_tangent_curve"); - undo_redo->add_undo_method(this, "_restore_curve_points", points); - undo_redo->commit_action(); -} -void Path3DEditorPlugin::_auto_tangent_curve() { - if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { - return; - } // Catmull–Rom smoothing - Ref curve = path->get_curve(); int point_count = curve->get_point_count(); const float smooth_ratio = 0.5; for (int i = 1; i < point_count - 1; i++) { @@ -880,23 +874,39 @@ void Path3DEditorPlugin::_auto_tangent_curve() { Vector3 prev_p = curve->get_point_position(i - 1); Vector3 curr_p = curve->get_point_position(i); Vector3 tangent = (next_p - prev_p).normalized(); - curve->set_point_in(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - curve->set_point_out(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", i, tangent * curr_p.distance_to(next_p) * smooth_ratio); } if (curve->is_closed()) { Vector3 first_p = curve->get_point_position(0); + Vector3 second_p = curve->get_point_position(1); Vector3 last_p = curve->get_point_position(point_count - 1); + Vector3 tangent_first = (second_p - last_p).normalized(); + + undo_redo->add_undo_method(curve.ptr(), "set_point_in", 0, curve->get_point_in(0)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", 0, -tangent_first * first_p.distance_to(last_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, tangent_first * first_p.distance_to(second_p) * smooth_ratio); - Vector3 first_last_tangent = (last_p - first_p).normalized(); - curve->set_point_out(0, -first_last_tangent); - curve->set_point_in(point_count - 1, first_last_tangent); + Vector3 second_last_p = curve->get_point_position(point_count - 2); + Vector3 tangent_last = (first_p - second_last_p).normalized(); + + undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, -tangent_last * last_p.distance_to(second_last_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", point_count - 1, curve->get_point_out(point_count - 1)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", point_count - 1, tangent_last * last_p.distance_to(first_p) * smooth_ratio); } else { - curve->set_point_out(0, Vector3()); - curve->set_point_in(point_count - 1, Vector3()); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, Vector3()); + + undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, Vector3()); } + undo_redo->commit_action(); } - void Path3DEditorPlugin::_clear_points() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector3Array points = path->get_curve()->get_points().duplicate(); @@ -1020,7 +1030,6 @@ void Path3DEditorPlugin::_notification(int p_what) { void Path3DEditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path3DEditorPlugin::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path3DEditorPlugin::_clear_curve_points); - ClassDB::bind_method(D_METHOD("_auto_tangent_curve"), &Path3DEditorPlugin::_auto_tangent_curve); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path3DEditorPlugin::_restore_curve_points); } diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index 4efcfcd06887..adaee4dfd817 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -155,7 +155,6 @@ class Path3DEditorPlugin : public EditorPlugin { void _confirm_clear_points(); void _clear_points(); void _auto_tangent(); - void _auto_tangent_curve(); void _clear_curve_points(); void _restore_curve_points(const PackedVector3Array &p_points); From 19572393cf662a2d2fc17377f7b013beeede469a Mon Sep 17 00:00:00 2001 From: Christiano Date: Sat, 21 Feb 2026 20:29:05 +0000 Subject: [PATCH 17/42] Clean up left over comments --- editor/scene/2d/path_2d_editor_plugin.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 56a0926f2c9c..357118c31039 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -824,11 +824,9 @@ void Path2DEditor::_auto_tangent() { undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i)); undo_redo->add_do_method(curve.ptr(), "set_point_in", i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - // curve->set_point_in(i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i)); undo_redo->add_do_method(curve.ptr(), "set_point_out", i, tangent * curr_p.distance_to(next_p) * smooth_ratio); - //curve->set_point_out(i, tangent * curr_p.distance_to(next_p) * smooth_ratio); } Vector2 begin = curve->get_point_position(0); Vector2 end = curve->get_point_position(point_count - 1); @@ -841,20 +839,16 @@ void Path2DEditor::_auto_tangent() { undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); - // curve->set_point_in(point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); - //curve->set_point_out(0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); } else { undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, Vector2()); - //curve->set_point_out(0, Vector2()); undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, Vector2()); - //curve->set_point_in(point_count - 1, Vector2()); } undo_redo->commit_action(); } From 19a3c02240a4a7dbf1b4c84d83ec7a4f8d15ced4 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Thu, 26 Mar 2026 17:17:24 +0000 Subject: [PATCH 18/42] Add torsion spinbox for autosmooth button --- editor/scene/3d/path_3d_editor_plugin.cpp | 17 ++++++++++++++++- editor/scene/3d/path_3d_editor_plugin.h | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 3fb2fa404c35..e08820f05f6b 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -39,6 +39,7 @@ #include "editor/scene/3d/node_3d_editor_plugin.h" #include "editor/settings/editor_settings.h" #include "scene/gui/dialogs.h" +#include "scene/gui/margin_container.h" #include "scene/gui/menu_button.h" #include "scene/resources/curve.h" @@ -857,6 +858,7 @@ void Path3DEditorPlugin::_confirm_clear_points() { clear_points_dialog->popup_centered(); } + void Path3DEditorPlugin::_auto_tangent() { if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { return; @@ -868,7 +870,7 @@ void Path3DEditorPlugin::_auto_tangent() { // Catmull–Rom smoothing int point_count = curve->get_point_count(); - const float smooth_ratio = 0.5; + const float smooth_ratio = auto_tangent_torsion->get_value(); for (int i = 1; i < point_count - 1; i++) { Vector3 next_p = curve->get_point_position(i + 1); Vector3 prev_p = curve->get_point_position(i - 1); @@ -1099,6 +1101,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_closed); curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve)); + curve_auto_tangent = memnew(Button); curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); @@ -1106,6 +1109,18 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_auto_tangent); curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_auto_tangent)); + auto_tangent_torsion = memnew(SpinBox); + auto_tangent_torsion->set_min(0.00); + auto_tangent_torsion->set_max(1.0); + auto_tangent_torsion->set_step(0.1); + auto_tangent_torsion->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + auto_tangent_torsion->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + auto_tangent_torsion->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + auto_tangent_torsion->set_tooltip_text(TTR("Auto Tangent Torsion")); + auto_tangent_torsion->set_accessibility_name(TTRC("Auto Tangent Torsion")); + toolbar->add_child(auto_tangent_torsion); + auto_tangent_torsion->set_value(0.5); + curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); curve_clear_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index adaee4dfd817..e5c506b0150c 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -34,6 +34,7 @@ #include "editor/scene/3d/node_3d_editor_gizmos.h" #include "scene/3d/camera_3d.h" #include "scene/3d/path_3d.h" +#include "scene/gui/spin_box.h" class HBoxContainer; class MenuButton; @@ -124,6 +125,7 @@ class Path3DEditorPlugin : public EditorPlugin { Button *curve_closed = nullptr; Button *curve_clear_points = nullptr; Button *curve_auto_tangent = nullptr; + SpinBox *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; From 2e6772ac8f8b8a21a1239dc6c103f3fbb180fd18 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Thu, 26 Mar 2026 17:24:24 +0000 Subject: [PATCH 19/42] Implement auto-tangent and torsion controls for path2d --- editor/scene/2d/path_2d_editor_plugin.cpp | 15 ++++++++++++++- editor/scene/2d/path_2d_editor_plugin.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 357118c31039..924fbeb19a8e 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -38,6 +38,7 @@ #include "editor/themes/editor_scale.h" #include "scene/gui/dialogs.h" #include "scene/gui/menu_button.h" +#include "scene/gui/spin_box.h" #include "scene/resources/mesh.h" void Path2DEditor::_notification(int p_what) { @@ -815,7 +816,7 @@ void Path2DEditor::_auto_tangent() { Ref curve = node->get_curve(); undo_redo->create_action(TTR("Auto Tangent")); int point_count = curve->get_point_count(); - const float smooth_ratio = 0.5; + const float smooth_ratio = auto_tangent_torsion->get_value(); for (int i = 1; i < point_count - 1; i++) { Vector2 prev_p = curve->get_point_position(i - 1); Vector2 next_p = curve->get_point_position(i + 1); @@ -941,6 +942,18 @@ Path2DEditor::Path2DEditor() { curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_auto_tangent)); toolbar->add_child(curve_auto_tangent); + auto_tangent_torsion = memnew(SpinBox); + auto_tangent_torsion->set_min(0.0); + auto_tangent_torsion->set_max(1.0); + auto_tangent_torsion->set_step(0.1); + auto_tangent_torsion->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + auto_tangent_torsion->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + auto_tangent_torsion->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + auto_tangent_torsion->set_tooltip_text(TTR("Auto Tangent Torsion")); + auto_tangent_torsion->set_accessibility_name(TTRC("Auto Tangent Torsion")); + toolbar->add_child(auto_tangent_torsion); + auto_tangent_torsion->set_value(0.5); + curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); curve_clear_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index af9d7cb7d609..0553130fdd16 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -65,6 +65,7 @@ class Path2DEditor : public HBoxContainer { Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; Button *curve_auto_tangent = nullptr; + SpinBox *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; From 925a5668924de62d9ab84b19a07bf5a8474ff7c8 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Thu, 26 Mar 2026 17:28:48 +0000 Subject: [PATCH 20/42] Add spinbox declaration in header file --- editor/scene/2d/path_2d_editor_plugin.h | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index 0553130fdd16..4f4ecf4092cc 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -37,6 +37,7 @@ class CanvasItemEditor; class ConfirmationDialog; class MenuButton; +class SpinBox; class Path2DEditor : public HBoxContainer { GDCLASS(Path2DEditor, HBoxContainer); From c59bb474f274dc9f949852239c8177b421025be4 Mon Sep 17 00:00:00 2001 From: Chris -- <41486691+Christakou@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:46:15 +0000 Subject: [PATCH 21/42] Apply suggestion from @TokageItLab Co-authored-by: Silc Lizard (Tokage) Renew --- editor/scene/3d/path_3d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index e08820f05f6b..e14a4abb0a1a 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -868,7 +868,7 @@ void Path3DEditorPlugin::_auto_tangent() { Ref curve = path->get_curve(); undo_redo->create_action(TTR("Auto Tangent")); - // Catmull–Rom smoothing + // Smoothing. int point_count = curve->get_point_count(); const float smooth_ratio = auto_tangent_torsion->get_value(); for (int i = 1; i < point_count - 1; i++) { From 7fceb78c904fb9864ccebefd577b03c68d888fbc Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sat, 4 Apr 2026 16:38:07 +0100 Subject: [PATCH 22/42] Refactor autotangent to unify logic and support application of smoothing only to selection --- editor/scene/3d/path_3d_editor_plugin.cpp | 82 +++++++++++++---------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index e14a4abb0a1a..9db4027ca7d4 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -867,48 +867,56 @@ void Path3DEditorPlugin::_auto_tangent() { PackedVector3Array points = path->get_curve()->get_points().duplicate(); Ref curve = path->get_curve(); undo_redo->create_action(TTR("Auto Tangent")); - // Smoothing. int point_count = curve->get_point_count(); const float smooth_ratio = auto_tangent_torsion->get_value(); - for (int i = 1; i < point_count - 1; i++) { - Vector3 next_p = curve->get_point_position(i + 1); - Vector3 prev_p = curve->get_point_position(i - 1); - Vector3 curr_p = curve->get_point_position(i); - Vector3 tangent = (next_p - prev_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", i, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } - if (curve->is_closed()) { - Vector3 first_p = curve->get_point_position(0); - Vector3 second_p = curve->get_point_position(1); - Vector3 last_p = curve->get_point_position(point_count - 1); - Vector3 tangent_first = (second_p - last_p).normalized(); - - undo_redo->add_undo_method(curve.ptr(), "set_point_in", 0, curve->get_point_in(0)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", 0, -tangent_first * first_p.distance_to(last_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, tangent_first * first_p.distance_to(second_p) * smooth_ratio); - - Vector3 second_last_p = curve->get_point_position(point_count - 2); - Vector3 tangent_last = (first_p - second_last_p).normalized(); - - undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, -tangent_last * last_p.distance_to(second_last_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", point_count - 1, curve->get_point_out(point_count - 1)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", point_count - 1, tangent_last * last_p.distance_to(first_p) * smooth_ratio); - } else { - undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, Vector3()); - - undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, Vector3()); + Vector selection = Node3DEditor::get_singleton()->get_subgizmo_selection(); + Vector points_to_process;// This avoids duplicating the smoothing logic + if (selection.is_empty()) { + for (int i = 0; i < point_count; i++) { + points_to_process.push_back(i); + } + } else { + points_to_process = selection; + } + bool is_closed = curve->is_closed(); + for (const int idx : points_to_process) { + const bool has_prev = is_closed || idx > 0; + const bool has_next = is_closed || idx < point_count - 1; + if (!has_prev && !has_next) { + continue; // Single point curve is noop + } + Vector3 curr_p = curve->get_point_position(idx); + + if (has_prev && has_next) { + int prev_idx = is_closed ? (idx - 1 + point_count) % point_count : idx - 1; + int next_idx = is_closed ? (idx + 1) % point_count : idx + 1; + Vector3 prev_p = curve->get_point_position(prev_idx); + Vector3 next_p = curve->get_point_position(next_idx); + Vector3 tangent = (next_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", idx, curve->get_point_in(idx)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", idx, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", idx, curve->get_point_out(idx)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", idx, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent + Vector3 next_p = curve->get_point_position(idx + 1); + Vector3 tangent = (next_p - curr_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", idx, curve->get_point_in(idx)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", idx, Vector3()); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", idx, curve->get_point_out(idx)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", idx, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else {// last point of an open curve, only set the in tangent + Vector3 prev_p = curve->get_point_position(idx - 1); + Vector3 tangent = (curr_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", idx, curve->get_point_in(idx)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", idx, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", idx, curve->get_point_out(idx)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", idx, Vector3()); + } + } + undo_redo->commit_action(); } - undo_redo->commit_action(); -} void Path3DEditorPlugin::_clear_points() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector3Array points = path->get_curve()->get_points().duplicate(); From b97904c270b34781f8def8ba7015bf742b3e9d72 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Fri, 15 May 2026 13:55:43 +0100 Subject: [PATCH 23/42] Make autotangent a toggle like delete toggle --- editor/scene/3d/path_3d_editor_plugin.cpp | 98 ++++++++++++++--------- editor/scene/3d/path_3d_editor_plugin.h | 2 + 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 9db4027ca7d4..2e8085568e3c 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -719,6 +719,18 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p //add new at pos } + } + else if (mb-> is_pressed() && (mb->get_button_index()== MouseButton::LEFT && curve_auto_tangent_mode->is_pressed())) { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + for (int i = 0; i < c->get_point_count(); i++) { + real_t dist_to_p = viewport->point_to_screen(gt.xform(c->get_point_position(i))).distance_to(mbpos); + if (dist_to_p < click_dist) { + ur->create_action(TTR("Auto Tangent Point")); + _auto_tangent_point(i, c->is_closed(), c->get_point_count()); + ur->commit_action(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } } else if (mb->is_pressed() && ((mb->get_button_index() == MouseButton::LEFT && curve_del->is_pressed()) || (mb->get_button_index() == MouseButton::RIGHT && curve_edit->is_pressed()))) { const float disk_size = EDITOR_GET("editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size"); @@ -792,7 +804,6 @@ void Path3DEditorPlugin::_mode_changed(int p_mode) { curve_edit_tilt->set_pressed_no_signal(p_mode == MODE_EDIT_TILT); curve_edit->set_pressed_no_signal(p_mode == MODE_EDIT); curve_del->set_pressed_no_signal(p_mode == MODE_DELETE); - Node3DEditor::get_singleton()->clear_subgizmo_selection(); } @@ -858,6 +869,47 @@ void Path3DEditorPlugin::_confirm_clear_points() { clear_points_dialog->popup_centered(); } +void Path3DEditorPlugin::_auto_tangent_point(int p_index, bool is_closed, int point_count ) { + ERR_FAIL_NULL(path); + ERR_FAIL_NULL(path->get_curve()); + const Ref curve = path->get_curve(); + // modifies + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + const float smooth_ratio = auto_tangent_torsion->get_value(); + const bool has_prev = is_closed || p_index > 0; + const bool has_next = is_closed || p_index < point_count - 1; + if (!has_prev && !has_next) { + return; // Single point curve is noop + } + Vector3 curr_p = curve->get_point_position(p_index); + + if (has_prev && has_next) { + int prev_p_index = is_closed ? (p_index - 1 + point_count) % point_count : p_index - 1; + int next_p_index = is_closed ? (p_index + 1) % point_count : p_index + 1; + Vector3 prev_p = curve->get_point_position(prev_p_index); + Vector3 next_p = curve->get_point_position(next_p_index); + Vector3 tangent = (next_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent + Vector3 next_p = curve->get_point_position(p_index + 1); + Vector3 tangent = (next_p - curr_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, Vector3()); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else {// last point of an open curve, only set the in tangent + Vector3 prev_p = curve->get_point_position(p_index - 1); + Vector3 tangent = (curr_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, Vector3()); + } +} void Path3DEditorPlugin::_auto_tangent() { if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() <= 2) { @@ -869,8 +921,6 @@ void Path3DEditorPlugin::_auto_tangent() { undo_redo->create_action(TTR("Auto Tangent")); // Smoothing. int point_count = curve->get_point_count(); - const float smooth_ratio = auto_tangent_torsion->get_value(); - Vector selection = Node3DEditor::get_singleton()->get_subgizmo_selection(); Vector points_to_process;// This avoids duplicating the smoothing logic if (selection.is_empty()) { @@ -882,38 +932,7 @@ void Path3DEditorPlugin::_auto_tangent() { } bool is_closed = curve->is_closed(); for (const int idx : points_to_process) { - const bool has_prev = is_closed || idx > 0; - const bool has_next = is_closed || idx < point_count - 1; - if (!has_prev && !has_next) { - continue; // Single point curve is noop - } - Vector3 curr_p = curve->get_point_position(idx); - - if (has_prev && has_next) { - int prev_idx = is_closed ? (idx - 1 + point_count) % point_count : idx - 1; - int next_idx = is_closed ? (idx + 1) % point_count : idx + 1; - Vector3 prev_p = curve->get_point_position(prev_idx); - Vector3 next_p = curve->get_point_position(next_idx); - Vector3 tangent = (next_p - prev_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", idx, curve->get_point_in(idx)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", idx, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", idx, curve->get_point_out(idx)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", idx, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent - Vector3 next_p = curve->get_point_position(idx + 1); - Vector3 tangent = (next_p - curr_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", idx, curve->get_point_in(idx)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", idx, Vector3()); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", idx, curve->get_point_out(idx)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", idx, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } else {// last point of an open curve, only set the in tangent - Vector3 prev_p = curve->get_point_position(idx - 1); - Vector3 tangent = (curr_p - prev_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", idx, curve->get_point_in(idx)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", idx, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", idx, curve->get_point_out(idx)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", idx, Vector3()); - } + _auto_tangent_point(idx, is_closed, point_count); } undo_redo->commit_action(); } @@ -960,6 +979,7 @@ void Path3DEditorPlugin::_update_theme() { curve_closed->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); curve_clear_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear"))); curve_auto_tangent->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveAutoTangent"))); + curve_auto_tangent_mode->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveAutoTangentMode"))); create_curve_button->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D"))); } @@ -1109,11 +1129,17 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_closed); curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve)); + curve_auto_tangent_mode = memnew(Button); + curve_auto_tangent_mode->set_theme_type_variation(SceneStringName(FlatButton)); + curve_auto_tangent_mode->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_auto_tangent_mode->set_tooltip_text(TTR("Auto Tangent Mode")); + curve_auto_tangent_mode->set_toggle_mode(true); + toolbar->add_child(curve_auto_tangent_mode); curve_auto_tangent = memnew(Button); curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_auto_tangent->set_tooltip_text(TTR("Auto Tangent")); + curve_auto_tangent->set_tooltip_text(TTR("Apply Auto Tangent to all points")); toolbar->add_child(curve_auto_tangent); curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_auto_tangent)); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index e5c506b0150c..78c3a426f57e 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -125,6 +125,7 @@ class Path3DEditorPlugin : public EditorPlugin { Button *curve_closed = nullptr; Button *curve_clear_points = nullptr; Button *curve_auto_tangent = nullptr; + Button *curve_auto_tangent_mode = nullptr; SpinBox *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; @@ -155,6 +156,7 @@ class Path3DEditorPlugin : public EditorPlugin { void _create_curve(); void _confirm_clear_points(); + void _auto_tangent_point(int p_index, bool is_closed, int point_count); void _clear_points(); void _auto_tangent(); void _clear_curve_points(); From 4936f9acf622ce2d90c93842bb6ece5444f51756 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 11:45:03 +0100 Subject: [PATCH 24/42] Implement auto tangent toggle button for Path2D --- editor/scene/2d/path_2d_editor_plugin.cpp | 127 ++++++++++++++++------ editor/scene/2d/path_2d_editor_plugin.h | 3 + 2 files changed, 96 insertions(+), 34 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 924fbeb19a8e..07a658b6c98d 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -51,6 +51,7 @@ void Path2DEditor::_notification(int p_what) { curve_close->set_button_icon(get_editor_theme_icon(SNAME("CurveClose"))); curve_clear_points->set_button_icon(get_editor_theme_icon(SNAME("Clear"))); curve_auto_tangent->set_button_icon(get_editor_theme_icon(SNAME("CurveAutoTangent"))); + curve_auto_tangent_toggle->set_button_icon(get_editor_theme_icon(SNAME("CurveAutoTangentToggle"))); create_curve_button->set_button_icon(get_editor_theme_icon(SNAME("Curve2D"))); } break; } @@ -135,9 +136,15 @@ bool Path2DEditor::forward_gui_input(const Ref &p_event) { } } } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + // Check for auto tangent click. + if ((mb->get_button_index() == MouseButton::LEFT && mode ==MODE_AUTO_TANGENT) && dist_to_p < grab_threshold) { + undo_redo->create_action(TTR("Auto Tangent Point")); + _auto_tangent_point(i); + undo_redo->commit_action(); + } // Check for point deletion. - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if ((mb->get_button_index() == MouseButton::RIGHT && (mode == MODE_EDIT || mode == MODE_CREATE)) || (mb->get_button_index() == MouseButton::LEFT && mode == MODE_DELETE)) { if (dist_to_p < grab_threshold) { undo_redo->create_action(TTR("Remove Point from Curve")); @@ -684,7 +691,14 @@ void Path2DEditor::_mode_selected(int p_mode) { curve_edit->set_pressed(false); curve_edit_curve->set_pressed(false); curve_del->set_pressed(true); - } else if (p_mode == MODE_CLOSE) { + } else if (p_mode == MODE_AUTO_TANGENT) { + curve_edit->set_pressed(false); + curve_edit_curve->set_pressed(false); + curve_del->set_pressed(false); + curve_create->set_pressed(false); + curve_auto_tangent_toggle->set_pressed(true); + } + else if (p_mode == MODE_CLOSE) { if (node->get_curve().is_null()) { return; } @@ -807,49 +821,86 @@ void Path2DEditor::_confirm_clear_points() { clear_points_dialog->reset_size(); clear_points_dialog->popup_centered(); } + +void Path2DEditor::_auto_tangent_point(int p_index) { + ERR_FAIL_NULL(node); + ERR_FAIL_NULL(node->get_curve()); + const Ref curve = node->get_curve(); + int point_count = curve->get_point_count(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + Vector2 begin = node->get_curve()->get_point_position(0); + Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1); + bool is_closed = begin.is_equal_approx(end); + const float smooth_ratio = auto_tangent_torsion->get_value(); + const bool has_prev = is_closed || p_index > 0; + const bool has_next = is_closed || p_index < point_count - 1; + if (!has_prev && !has_next) { + return; // Single point curve is noop + } + Vector2 curr_p = curve->get_point_position(p_index); + + if (has_prev && has_next) { + if (p_index == point_count - 1 || p_index == 0) { + Vector2 next_p = curve->get_point_position(1); + Vector2 prev_p = curve->get_point_position(point_count - 2); + Vector2 first_last_tangent = (next_p - prev_p).normalized(); + + undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); + + undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); + } + else { + int prev_p_index = is_closed ? (p_index - 1 + point_count) % point_count : p_index - 1; + int next_p_index = is_closed ? (p_index + 1) % point_count : p_index + 1; + + Vector2 prev_p = curve->get_point_position(prev_p_index); + Vector2 next_p = curve->get_point_position(next_p_index); + Vector2 tangent = (next_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); + + } + + } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent + Vector2 next_p = curve->get_point_position(p_index + 1); + Vector2 tangent = (next_p - curr_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, Vector2()); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else {// last point of an open curve, only set the in tangent + Vector2 prev_p = curve->get_point_position(p_index - 1); + Vector2 tangent = (curr_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, Vector2()); + } +} + void Path2DEditor::_auto_tangent() { if (!node || node->get_curve().is_null() || node->get_curve()->get_point_count() <= 2) { return; } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - PackedVector2Array points = node->get_curve()->get_points().duplicate(); Ref curve = node->get_curve(); undo_redo->create_action(TTR("Auto Tangent")); int point_count = curve->get_point_count(); - const float smooth_ratio = auto_tangent_torsion->get_value(); - for (int i = 1; i < point_count - 1; i++) { - Vector2 prev_p = curve->get_point_position(i - 1); - Vector2 next_p = curve->get_point_position(i + 1); - Vector2 curr_p = curve->get_point_position(i); - Vector2 tangent = (next_p - prev_p).normalized(); - - undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", i, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", i, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } Vector2 begin = curve->get_point_position(0); Vector2 end = curve->get_point_position(point_count - 1); + bool is_closed = begin.is_equal_approx(end); + if (is_closed) { + _auto_tangent_point(0); + } - if (begin.is_equal_approx(end)) { - // handles closed curves in path2D having the end and start points at the same location - Vector2 next_p = curve->get_point_position(1); - Vector2 prev_p = curve->get_point_position(point_count - 2); - Vector2 first_last_tangent = (next_p - prev_p).normalized(); - - undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, -first_last_tangent * end.distance_to(prev_p) * smooth_ratio); - - undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); - - } else { - undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, Vector2()); - - undo_redo->add_undo_method(curve.ptr(), "set_point_in", point_count - 1, curve->get_point_in(point_count - 1)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", point_count - 1, Vector2()); + for (int i = 1; i < point_count - 1; i++) { + _auto_tangent_point(i); } undo_redo->commit_action(); } @@ -938,10 +989,18 @@ Path2DEditor::Path2DEditor() { curve_auto_tangent = memnew(Button); curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_auto_tangent->set_tooltip_text(TTR("Auto Tangent")); + curve_auto_tangent->set_tooltip_text(TTR("Apply Auto Tangent to all points")); curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_auto_tangent)); toolbar->add_child(curve_auto_tangent); + curve_auto_tangent_toggle = memnew(Button); + curve_auto_tangent_toggle->set_theme_type_variation(SceneStringName(FlatButton)); + curve_auto_tangent_toggle->set_toggle_mode(true); + curve_auto_tangent_toggle->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_auto_tangent_toggle->set_tooltip_text(TTR("Toggle Auto Tangent Mode")); + curve_auto_tangent_toggle->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_AUTO_TANGENT)); + toolbar->add_child(curve_auto_tangent_toggle); + auto_tangent_torsion = memnew(SpinBox); auto_tangent_torsion->set_min(0.0); auto_tangent_torsion->set_max(1.0); diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index 4f4ecf4092cc..fede1f8a9b88 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -55,6 +55,7 @@ class Path2DEditor : public HBoxContainer { MODE_DELETE, MODE_CLOSE, MODE_CLEAR_POINTS, + MODE_AUTO_TANGENT, }; Mode mode = MODE_EDIT; @@ -66,6 +67,7 @@ class Path2DEditor : public HBoxContainer { Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; Button *curve_auto_tangent = nullptr; + Button *curve_auto_tangent_toggle = nullptr; SpinBox *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; @@ -114,6 +116,7 @@ class Path2DEditor : public HBoxContainer { void _create_curve(); void _confirm_clear_points(); + void _auto_tangent_point(int p_index); void _clear_curve_points(Path2D *p_path2d); void _restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points); From 9fe92fda1bdfcaf0a9c74b5c163b81662a3ef99d Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 11:59:41 +0100 Subject: [PATCH 25/42] Tidy up logic and fix toggle bug --- editor/scene/2d/path_2d_editor_plugin.cpp | 50 +++++++++++------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 07a658b6c98d..ab7a8ad0e975 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -138,10 +138,11 @@ bool Path2DEditor::forward_gui_input(const Ref &p_event) { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); // Check for auto tangent click. - if ((mb->get_button_index() == MouseButton::LEFT && mode ==MODE_AUTO_TANGENT) && dist_to_p < grab_threshold) { + if ((mb->get_button_index() == MouseButton::LEFT && mode == MODE_AUTO_TANGENT) && dist_to_p < grab_threshold) { undo_redo->create_action(TTR("Auto Tangent Point")); _auto_tangent_point(i); undo_redo->commit_action(); + return true; } // Check for point deletion. @@ -676,29 +677,32 @@ void Path2DEditor::_mode_selected(int p_mode) { curve_edit->set_pressed(false); curve_edit_curve->set_pressed(false); curve_del->set_pressed(false); + curve_auto_tangent_toggle->set_pressed(false); } else if (p_mode == MODE_EDIT) { curve_create->set_pressed(false); curve_edit->set_pressed(true); curve_edit_curve->set_pressed(false); curve_del->set_pressed(false); + curve_auto_tangent_toggle->set_pressed(false); } else if (p_mode == MODE_EDIT_CURVE) { curve_create->set_pressed(false); curve_edit->set_pressed(false); curve_edit_curve->set_pressed(true); curve_del->set_pressed(false); + curve_auto_tangent_toggle->set_pressed(false); } else if (p_mode == MODE_DELETE) { curve_create->set_pressed(false); curve_edit->set_pressed(false); curve_edit_curve->set_pressed(false); curve_del->set_pressed(true); + curve_auto_tangent_toggle->set_pressed(false); } else if (p_mode == MODE_AUTO_TANGENT) { curve_edit->set_pressed(false); curve_edit_curve->set_pressed(false); curve_del->set_pressed(false); curve_create->set_pressed(false); curve_auto_tangent_toggle->set_pressed(true); - } - else if (p_mode == MODE_CLOSE) { + } else if (p_mode == MODE_CLOSE) { if (node->get_curve().is_null()) { return; } @@ -851,8 +855,7 @@ void Path2DEditor::_auto_tangent_point(int p_index) { undo_redo->add_undo_method(curve.ptr(), "set_point_out", 0, curve->get_point_out(0)); undo_redo->add_do_method(curve.ptr(), "set_point_out", 0, first_last_tangent * begin.distance_to(next_p) * smooth_ratio); - } - else { + } else { int prev_p_index = is_closed ? (p_index - 1 + point_count) % point_count : p_index - 1; int next_p_index = is_closed ? (p_index + 1) % point_count : p_index + 1; @@ -863,23 +866,21 @@ void Path2DEditor::_auto_tangent_point(int p_index) { undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } - - } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent - Vector2 next_p = curve->get_point_position(p_index + 1); - Vector2 tangent = (next_p - curr_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, Vector2()); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } else {// last point of an open curve, only set the in tangent - Vector2 prev_p = curve->get_point_position(p_index - 1); - Vector2 tangent = (curr_p - prev_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, Vector2()); + } else if (has_next) { + Vector2 next_p = curve->get_point_position(p_index + 1); + Vector2 tangent = (next_p - curr_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, Vector2()); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else { + Vector2 prev_p = curve->get_point_position(p_index - 1); + Vector2 tangent = (curr_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, Vector2()); } } @@ -895,11 +896,8 @@ void Path2DEditor::_auto_tangent() { Vector2 begin = curve->get_point_position(0); Vector2 end = curve->get_point_position(point_count - 1); bool is_closed = begin.is_equal_approx(end); - if (is_closed) { - _auto_tangent_point(0); - } - - for (int i = 1; i < point_count - 1; i++) { + int end_index = is_closed ? point_count - 1 : point_count; + for (int i = 0; i < end_index; i++) { _auto_tangent_point(i); } undo_redo->commit_action(); From 2dd1971c15b267351eabe6f6e0f846dcfcc3d2a9 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 12:15:08 +0100 Subject: [PATCH 26/42] Refactor and lint --- editor/scene/3d/path_3d_editor_plugin.cpp | 102 +++++++++++----------- editor/scene/3d/path_3d_editor_plugin.h | 2 +- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 2e8085568e3c..8eca0aa57645 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -726,7 +726,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p real_t dist_to_p = viewport->point_to_screen(gt.xform(c->get_point_position(i))).distance_to(mbpos); if (dist_to_p < click_dist) { ur->create_action(TTR("Auto Tangent Point")); - _auto_tangent_point(i, c->is_closed(), c->get_point_count()); + _auto_tangent_point(i); ur->commit_action(); return EditorPlugin::AFTER_GUI_INPUT_STOP; } @@ -869,46 +869,47 @@ void Path3DEditorPlugin::_confirm_clear_points() { clear_points_dialog->popup_centered(); } -void Path3DEditorPlugin::_auto_tangent_point(int p_index, bool is_closed, int point_count ) { +void Path3DEditorPlugin::_auto_tangent_point(int p_index) { ERR_FAIL_NULL(path); ERR_FAIL_NULL(path->get_curve()); const Ref curve = path->get_curve(); // modifies EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - - const float smooth_ratio = auto_tangent_torsion->get_value(); - const bool has_prev = is_closed || p_index > 0; - const bool has_next = is_closed || p_index < point_count - 1; - if (!has_prev && !has_next) { - return; // Single point curve is noop - } - Vector3 curr_p = curve->get_point_position(p_index); - - if (has_prev && has_next) { - int prev_p_index = is_closed ? (p_index - 1 + point_count) % point_count : p_index - 1; - int next_p_index = is_closed ? (p_index + 1) % point_count : p_index + 1; - Vector3 prev_p = curve->get_point_position(prev_p_index); - Vector3 next_p = curve->get_point_position(next_p_index); - Vector3 tangent = (next_p - prev_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent - Vector3 next_p = curve->get_point_position(p_index + 1); - Vector3 tangent = (next_p - curr_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, Vector3()); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } else {// last point of an open curve, only set the in tangent - Vector3 prev_p = curve->get_point_position(p_index - 1); - Vector3 tangent = (curr_p - prev_p).normalized(); - undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); - undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); - undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, Vector3()); - } + const float smooth_ratio = auto_tangent_torsion->get_value(); + int point_count = curve->get_point_count(); + bool is_closed = curve->is_closed(); + const bool has_prev = is_closed || p_index > 0; + const bool has_next = is_closed || p_index < point_count - 1; + if (!has_prev && !has_next) { + return; // Single point curve is noop + } + Vector3 curr_p = curve->get_point_position(p_index); + + if (has_prev && has_next) { + int prev_p_index = is_closed ? (p_index - 1 + point_count) % point_count : p_index - 1; + int next_p_index = is_closed ? (p_index + 1) % point_count : p_index + 1; + Vector3 prev_p = curve->get_point_position(prev_p_index); + Vector3 next_p = curve->get_point_position(next_p_index); + Vector3 tangent = (next_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent + Vector3 next_p = curve->get_point_position(p_index + 1); + Vector3 tangent = (next_p - curr_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, Vector3()); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); + } else { // last point of an open curve, only set the in tangent + Vector3 prev_p = curve->get_point_position(p_index - 1); + Vector3 tangent = (curr_p - prev_p).normalized(); + undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); + undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); + undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, Vector3()); + } } void Path3DEditorPlugin::_auto_tangent() { @@ -916,26 +917,25 @@ void Path3DEditorPlugin::_auto_tangent() { return; } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - PackedVector3Array points = path->get_curve()->get_points().duplicate(); Ref curve = path->get_curve(); undo_redo->create_action(TTR("Auto Tangent")); // Smoothing. int point_count = curve->get_point_count(); - Vector selection = Node3DEditor::get_singleton()->get_subgizmo_selection(); - Vector points_to_process;// This avoids duplicating the smoothing logic - if (selection.is_empty()) { - for (int i = 0; i < point_count; i++) { - points_to_process.push_back(i); - } - } else { - points_to_process = selection; - } - bool is_closed = curve->is_closed(); - for (const int idx : points_to_process) { - _auto_tangent_point(idx, is_closed, point_count); - } - undo_redo->commit_action(); + Vector selection = Node3DEditor::get_singleton()->get_subgizmo_selection(); + Vector points_to_process; // This avoids duplicating the smoothing logic + if (selection.is_empty()) { + for (int i = 0; i < point_count; i++) { + points_to_process.push_back(i); + } + } else { + points_to_process = selection; + } + for (const int idx : points_to_process) { + _auto_tangent_point(idx); } + undo_redo->commit_action(); +} + void Path3DEditorPlugin::_clear_points() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); PackedVector3Array points = path->get_curve()->get_points().duplicate(); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index 78c3a426f57e..825becab56b3 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -156,7 +156,7 @@ class Path3DEditorPlugin : public EditorPlugin { void _create_curve(); void _confirm_clear_points(); - void _auto_tangent_point(int p_index, bool is_closed, int point_count); + void _auto_tangent_point(int p_index); void _clear_points(); void _auto_tangent(); void _clear_curve_points(); From 93da14dc07bd20905e07dbce12c18f855e8e008b Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 12:27:21 +0100 Subject: [PATCH 27/42] Fix toggle buttons bug --- editor/scene/3d/path_3d_editor_plugin.cpp | 2 ++ editor/scene/3d/path_3d_editor_plugin.h | 1 + 2 files changed, 3 insertions(+) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 8eca0aa57645..07e943885134 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -804,6 +804,7 @@ void Path3DEditorPlugin::_mode_changed(int p_mode) { curve_edit_tilt->set_pressed_no_signal(p_mode == MODE_EDIT_TILT); curve_edit->set_pressed_no_signal(p_mode == MODE_EDIT); curve_del->set_pressed_no_signal(p_mode == MODE_DELETE); + curve_auto_tangent_mode->set_pressed_no_signal(p_mode == MODE_AUTO_TANGENT); Node3DEditor::get_singleton()->clear_subgizmo_selection(); } @@ -1134,6 +1135,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_auto_tangent_mode->set_focus_mode(Control::FOCUS_ACCESSIBILITY); curve_auto_tangent_mode->set_tooltip_text(TTR("Auto Tangent Mode")); curve_auto_tangent_mode->set_toggle_mode(true); + curve_auto_tangent_mode->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_AUTO_TANGENT)); toolbar->add_child(curve_auto_tangent_mode); curve_auto_tangent = memnew(Button); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index 825becab56b3..763c6f1c8605 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -138,6 +138,7 @@ class Path3DEditorPlugin : public EditorPlugin { MODE_EDIT_CURVE, MODE_EDIT_TILT, MODE_DELETE, + MODE_AUTO_TANGENT, ACTION_CLOSE }; From 1a9be5062e49048a62d4a0d3b8a3dbccb6705c2c Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 12:43:15 +0100 Subject: [PATCH 28/42] Move clear points and apply autotangent to all to options dropdown --- editor/scene/2d/path_2d_editor_plugin.cpp | 27 +++++++++-------------- editor/scene/2d/path_2d_editor_plugin.h | 4 ++-- editor/scene/3d/path_3d_editor_plugin.cpp | 27 +++++++++-------------- editor/scene/3d/path_3d_editor_plugin.h | 4 ++-- 4 files changed, 24 insertions(+), 38 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index ab7a8ad0e975..5e88b5d5882f 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -49,9 +49,7 @@ void Path2DEditor::_notification(int p_what) { curve_create->set_button_icon(get_editor_theme_icon(SNAME("CurveCreate"))); curve_del->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete"))); curve_close->set_button_icon(get_editor_theme_icon(SNAME("CurveClose"))); - curve_clear_points->set_button_icon(get_editor_theme_icon(SNAME("Clear"))); - curve_auto_tangent->set_button_icon(get_editor_theme_icon(SNAME("CurveAutoTangent"))); - curve_auto_tangent_toggle->set_button_icon(get_editor_theme_icon(SNAME("CurveAutoTangentToggle"))); + curve_auto_tangent_toggle->set_button_icon(get_editor_theme_icon(SNAME("CurveAutoTangent"))); create_curve_button->set_button_icon(get_editor_theme_icon(SNAME("Curve2D"))); } break; } @@ -761,6 +759,12 @@ void Path2DEditor::_handle_option_pressed(int p_option) { mirror_handle_length = !is_checked; pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); } break; + case HANDLE_OPTION_AUTO_TANGENT: { + _auto_tangent(); + } break; + case HANDLE_OPTION_CLEAR_POINTS: { + _confirm_clear_points(); + } break; } } @@ -984,13 +988,6 @@ Path2DEditor::Path2DEditor() { curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE)); toolbar->add_child(curve_close); - curve_auto_tangent = memnew(Button); - curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); - curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_auto_tangent->set_tooltip_text(TTR("Apply Auto Tangent to all points")); - curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_auto_tangent)); - toolbar->add_child(curve_auto_tangent); - curve_auto_tangent_toggle = memnew(Button); curve_auto_tangent_toggle->set_theme_type_variation(SceneStringName(FlatButton)); curve_auto_tangent_toggle->set_toggle_mode(true); @@ -1011,13 +1008,6 @@ Path2DEditor::Path2DEditor() { toolbar->add_child(auto_tangent_torsion); auto_tangent_torsion->set_value(0.5); - curve_clear_points = memnew(Button); - curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); - curve_clear_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_clear_points->set_tooltip_text(TTR("Clear Points")); - curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_confirm_clear_points)); - toolbar->add_child(curve_clear_points); - clear_points_dialog = memnew(ConfirmationDialog); clear_points_dialog->set_title(TTR("Please Confirm...")); clear_points_dialog->set_text(TTR("Remove all curve points?")); @@ -1035,6 +1025,9 @@ Path2DEditor::Path2DEditor() { menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle); menu->add_check_item(TTR("Mirror Handle Lengths")); menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); + menu->add_separator(); + menu->add_item(TTR("Apply Auto Tangent to All Points"), HANDLE_OPTION_AUTO_TANGENT); + menu->add_item(TTR("Clear Points"), HANDLE_OPTION_CLEAR_POINTS); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed)); add_child(toolbar); diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index fede1f8a9b88..5dcaf17dd3ba 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -60,13 +60,11 @@ class Path2DEditor : public HBoxContainer { Mode mode = MODE_EDIT; HBoxContainer *toolbar = nullptr; - Button *curve_clear_points = nullptr; Button *curve_close = nullptr; Button *curve_create = nullptr; Button *curve_del = nullptr; Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; - Button *curve_auto_tangent = nullptr; Button *curve_auto_tangent_toggle = nullptr; SpinBox *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; @@ -81,6 +79,8 @@ class Path2DEditor : public HBoxContainer { enum HandleOption { HANDLE_OPTION_ANGLE, HANDLE_OPTION_LENGTH, + HANDLE_OPTION_AUTO_TANGENT, + HANDLE_OPTION_CLEAR_POINTS, }; enum Action { diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 07e943885134..9df40338c618 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -844,6 +844,12 @@ void Path3DEditorPlugin::_handle_option_pressed(int p_option) { snap_to_collider = !is_checked; pm->set_item_checked(HANDLE_OPTION_SNAP_COLLIDER, snap_to_collider); } break; + case HANDLE_OPTION_AUTO_TANGENT: { + _auto_tangent(); + } break; + case HANDLE_OPTION_CLEAR_POINTS: { + _confirm_clear_points(); + } break; } } @@ -978,9 +984,7 @@ void Path3DEditorPlugin::_update_theme() { curve_create->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveCreate"))); curve_del->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete"))); curve_closed->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); - curve_clear_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear"))); - curve_auto_tangent->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveAutoTangent"))); - curve_auto_tangent_mode->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveAutoTangentMode"))); + curve_auto_tangent_mode->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveAutoTangent"))); create_curve_button->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D"))); } @@ -1138,13 +1142,6 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_auto_tangent_mode->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_AUTO_TANGENT)); toolbar->add_child(curve_auto_tangent_mode); - curve_auto_tangent = memnew(Button); - curve_auto_tangent->set_theme_type_variation(SceneStringName(FlatButton)); - curve_auto_tangent->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_auto_tangent->set_tooltip_text(TTR("Apply Auto Tangent to all points")); - toolbar->add_child(curve_auto_tangent); - curve_auto_tangent->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_auto_tangent)); - auto_tangent_torsion = memnew(SpinBox); auto_tangent_torsion->set_min(0.00); auto_tangent_torsion->set_max(1.0); @@ -1157,13 +1154,6 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(auto_tangent_torsion); auto_tangent_torsion->set_value(0.5); - curve_clear_points = memnew(Button); - curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton)); - curve_clear_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_clear_points->set_tooltip_text(TTR("Clear Points")); - curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_confirm_clear_points)); - toolbar->add_child(curve_clear_points); - clear_points_dialog = memnew(ConfirmationDialog); clear_points_dialog->set_title(TTR("Please Confirm...")); clear_points_dialog->set_text(TTR("Remove all curve points?")); @@ -1189,6 +1179,9 @@ Path3DEditorPlugin::Path3DEditorPlugin() { menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); menu->add_check_item(TTR("Snap to Colliders")); menu->set_item_checked(HANDLE_OPTION_SNAP_COLLIDER, snap_to_collider); + menu->add_separator(); + menu->add_item(TTR("Apply Auto Tangent to All Points"), HANDLE_OPTION_AUTO_TANGENT); + menu->add_item(TTR("Clear Points"), HANDLE_OPTION_CLEAR_POINTS); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path3DEditorPlugin::_handle_option_pressed)); curve_edit->set_pressed_no_signal(true); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index 763c6f1c8605..a0da134ffb65 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -123,8 +123,6 @@ class Path3DEditorPlugin : public EditorPlugin { Button *curve_edit_tilt = nullptr; Button *curve_del = nullptr; Button *curve_closed = nullptr; - Button *curve_clear_points = nullptr; - Button *curve_auto_tangent = nullptr; Button *curve_auto_tangent_mode = nullptr; SpinBox *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; @@ -167,6 +165,8 @@ class Path3DEditorPlugin : public EditorPlugin { HANDLE_OPTION_ANGLE, HANDLE_OPTION_LENGTH, HANDLE_OPTION_SNAP_COLLIDER, + HANDLE_OPTION_AUTO_TANGENT, + HANDLE_OPTION_CLEAR_POINTS, }; protected: From c9202d4898c1c5c48ffd56205b6994ea11b5eafb Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 12:51:18 +0100 Subject: [PATCH 29/42] Apply clang format --- editor/scene/3d/path_3d_editor_plugin.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 9df40338c618..2b8d77efb93f 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -719,8 +719,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p //add new at pos } - } - else if (mb-> is_pressed() && (mb->get_button_index()== MouseButton::LEFT && curve_auto_tangent_mode->is_pressed())) { + } else if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT && curve_auto_tangent_mode->is_pressed())) { EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); for (int i = 0; i < c->get_point_count(); i++) { real_t dist_to_p = viewport->point_to_screen(gt.xform(c->get_point_position(i))).distance_to(mbpos); From 95f44fcb926d26e605572a6feb51b19767f917cd Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 13:10:28 +0100 Subject: [PATCH 30/42] Early return if path or curve are null --- editor/scene/3d/path_3d_editor_plugin.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 2b8d77efb93f..831d43178d48 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -876,8 +876,9 @@ void Path3DEditorPlugin::_confirm_clear_points() { } void Path3DEditorPlugin::_auto_tangent_point(int p_index) { - ERR_FAIL_NULL(path); - ERR_FAIL_NULL(path->get_curve()); + if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() == 0) { + return; + } const Ref curve = path->get_curve(); // modifies EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); From b2138f86e41be6aeeb2cd6ddb474abf738b46bbb Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 13:11:45 +0100 Subject: [PATCH 31/42] Early return if path or curve are null --- editor/scene/2d/path_2d_editor_plugin.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 5e88b5d5882f..83e70ffb7424 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -831,8 +831,9 @@ void Path2DEditor::_confirm_clear_points() { } void Path2DEditor::_auto_tangent_point(int p_index) { - ERR_FAIL_NULL(node); - ERR_FAIL_NULL(node->get_curve()); + if (!node || node->get_curve().is_null() || node->get_curve()->get_point_count() == 0) { + return; + } const Ref curve = node->get_curve(); int point_count = curve->get_point_count(); From df83fa475a76d494714fc231487d69f4fb580da9 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Sun, 17 May 2026 13:40:00 +0100 Subject: [PATCH 32/42] Remove unused include --- editor/scene/3d/path_3d_editor_plugin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 831d43178d48..bf2a9a1a4e99 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -39,7 +39,6 @@ #include "editor/scene/3d/node_3d_editor_plugin.h" #include "editor/settings/editor_settings.h" #include "scene/gui/dialogs.h" -#include "scene/gui/margin_container.h" #include "scene/gui/menu_button.h" #include "scene/resources/curve.h" From 6892addf739f5f98c8755a97cacbab9b69ddcf8a Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Mon, 1 Jun 2026 14:10:18 +0100 Subject: [PATCH 33/42] Rearrange toolbar to use EditorSpinSlider for auto-tangent torsion and reorganise. --- editor/scene/2d/path_2d_editor_plugin.cpp | 27 +++++++++++++--------- editor/scene/2d/path_2d_editor_plugin.h | 5 +++- editor/scene/3d/path_3d_editor_plugin.cpp | 28 ++++++++++++++--------- editor/scene/3d/path_3d_editor_plugin.h | 5 +++- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 83e70ffb7424..9428e1bf5a1d 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "core/os/keyboard.h" #include "editor/editor_node.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_spin_slider.h" #include "editor/scene/canvas_item_editor_plugin.h" #include "editor/settings/editor_settings.h" #include "editor/themes/editor_scale.h" @@ -982,12 +983,7 @@ Path2DEditor::Path2DEditor() { curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE)); toolbar->add_child(curve_del); - curve_close = memnew(Button); - curve_close->set_theme_type_variation(SceneStringName(FlatButton)); - curve_close->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_close->set_tooltip_text(TTR("Close Curve")); - curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE)); - toolbar->add_child(curve_close); + curve_auto_tangent_toggle = memnew(Button); curve_auto_tangent_toggle->set_theme_type_variation(SceneStringName(FlatButton)); @@ -997,18 +993,27 @@ Path2DEditor::Path2DEditor() { curve_auto_tangent_toggle->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_AUTO_TANGENT)); toolbar->add_child(curve_auto_tangent_toggle); - auto_tangent_torsion = memnew(SpinBox); + auto_tangent_torsion = memnew(EditorSpinSlider); auto_tangent_torsion->set_min(0.0); auto_tangent_torsion->set_max(1.0); - auto_tangent_torsion->set_step(0.1); - auto_tangent_torsion->set_h_size_flags(Control::SIZE_SHRINK_CENTER); - auto_tangent_torsion->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + auto_tangent_torsion->set_step(0.05); + auto_tangent_torsion->set_h_size_flags(Control::SIZE_EXPAND); + auto_tangent_torsion->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); auto_tangent_torsion->set_focus_mode(Control::FOCUS_ACCESSIBILITY); auto_tangent_torsion->set_tooltip_text(TTR("Auto Tangent Torsion")); - auto_tangent_torsion->set_accessibility_name(TTRC("Auto Tangent Torsion")); toolbar->add_child(auto_tangent_torsion); auto_tangent_torsion->set_value(0.5); + v_separator = memnew(VSeparator); + toolbar->add_child(v_separator); + + curve_close = memnew(Button); + curve_close->set_theme_type_variation(SceneStringName(FlatButton)); + curve_close->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_close->set_tooltip_text(TTR("Close Curve")); + curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE)); + toolbar->add_child(curve_close); + clear_points_dialog = memnew(ConfirmationDialog); clear_points_dialog->set_title(TTR("Please Confirm...")); clear_points_dialog->set_text(TTR("Remove all curve points?")); diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index 5dcaf17dd3ba..6868fa666853 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -30,9 +30,11 @@ #pragma once +#include "editor/gui/editor_spin_slider.h" #include "editor/plugins/editor_plugin.h" #include "scene/2d/path_2d.h" #include "scene/gui/box_container.h" +#include "scene/gui/separator.h" class CanvasItemEditor; class ConfirmationDialog; @@ -66,7 +68,8 @@ class Path2DEditor : public HBoxContainer { Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; Button *curve_auto_tangent_toggle = nullptr; - SpinBox *auto_tangent_torsion = nullptr; + VSeparator *v_separator = nullptr; + EditorSpinSlider *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index bf2a9a1a4e99..cc5d564d157d 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -36,10 +36,12 @@ #include "editor/editor_node.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_spin_slider.h" #include "editor/scene/3d/node_3d_editor_plugin.h" #include "editor/settings/editor_settings.h" #include "scene/gui/dialogs.h" #include "scene/gui/menu_button.h" +#include "scene/gui/separator.h" #include "scene/resources/curve.h" String Path3DGizmo::get_handle_name(int p_id, bool p_secondary) const { @@ -1126,12 +1128,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_del); curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE)); - curve_closed = memnew(Button); - curve_closed->set_theme_type_variation(SceneStringName(FlatButton)); - curve_closed->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - curve_closed->set_tooltip_text(TTR("Close Curve")); - toolbar->add_child(curve_closed); - curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve)); + curve_auto_tangent_mode = memnew(Button); curve_auto_tangent_mode->set_theme_type_variation(SceneStringName(FlatButton)); @@ -1141,18 +1138,27 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_auto_tangent_mode->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_AUTO_TANGENT)); toolbar->add_child(curve_auto_tangent_mode); - auto_tangent_torsion = memnew(SpinBox); + auto_tangent_torsion = memnew(EditorSpinSlider); auto_tangent_torsion->set_min(0.00); auto_tangent_torsion->set_max(1.0); - auto_tangent_torsion->set_step(0.1); - auto_tangent_torsion->set_h_size_flags(Control::SIZE_SHRINK_CENTER); - auto_tangent_torsion->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + auto_tangent_torsion->set_step(0.05); + auto_tangent_torsion->set_h_size_flags(Control::SIZE_EXPAND); + auto_tangent_torsion->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); auto_tangent_torsion->set_focus_mode(Control::FOCUS_ACCESSIBILITY); auto_tangent_torsion->set_tooltip_text(TTR("Auto Tangent Torsion")); - auto_tangent_torsion->set_accessibility_name(TTRC("Auto Tangent Torsion")); toolbar->add_child(auto_tangent_torsion); auto_tangent_torsion->set_value(0.5); + v_separator = memnew(VSeparator); + toolbar-> add_child(v_separator); + + curve_closed = memnew(Button); + curve_closed->set_theme_type_variation(SceneStringName(FlatButton)); + curve_closed->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + curve_closed->set_tooltip_text(TTR("Close Curve")); + toolbar->add_child(curve_closed); + curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve)); + clear_points_dialog = memnew(ConfirmationDialog); clear_points_dialog->set_title(TTR("Please Confirm...")); clear_points_dialog->set_text(TTR("Remove all curve points?")); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index a0da134ffb65..ab662cf4c339 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -30,10 +30,12 @@ #pragma once +#include "editor/gui/editor_spin_slider.h" #include "editor/plugins/editor_plugin.h" #include "editor/scene/3d/node_3d_editor_gizmos.h" #include "scene/3d/camera_3d.h" #include "scene/3d/path_3d.h" +#include "scene/gui/separator.h" #include "scene/gui/spin_box.h" class HBoxContainer; @@ -124,7 +126,8 @@ class Path3DEditorPlugin : public EditorPlugin { Button *curve_del = nullptr; Button *curve_closed = nullptr; Button *curve_auto_tangent_mode = nullptr; - SpinBox *auto_tangent_torsion = nullptr; + VSeparator *v_separator = nullptr; + EditorSpinSlider *auto_tangent_torsion = nullptr; MenuButton *handle_menu = nullptr; Button *create_curve_button = nullptr; From aef6af577f7012f17b64fb6460bd8b5a5c4001df Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Mon, 1 Jun 2026 14:58:26 +0100 Subject: [PATCH 34/42] Add shortcuts for applying auto-tangent --- editor/scene/2d/path_2d_editor_plugin.cpp | 7 +++---- editor/scene/3d/path_3d_editor_plugin.cpp | 8 +++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 9428e1bf5a1d..69366fe9f016 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -983,8 +983,6 @@ Path2DEditor::Path2DEditor() { curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE)); toolbar->add_child(curve_del); - - curve_auto_tangent_toggle = memnew(Button); curve_auto_tangent_toggle->set_theme_type_variation(SceneStringName(FlatButton)); curve_auto_tangent_toggle->set_toggle_mode(true); @@ -1032,8 +1030,9 @@ Path2DEditor::Path2DEditor() { menu->add_check_item(TTR("Mirror Handle Lengths")); menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); menu->add_separator(); - menu->add_item(TTR("Apply Auto Tangent to All Points"), HANDLE_OPTION_AUTO_TANGENT); - menu->add_item(TTR("Clear Points"), HANDLE_OPTION_CLEAR_POINTS); + menu->add_shortcut(ED_SHORTCUT("path_editor/apply_auto_tangent_to_all_points", TTRC("Apply Auto Tangent to All Points"), KeyModifierMask::CMD_OR_CTRL | Key::T), HANDLE_OPTION_AUTO_TANGENT); + menu->add_shortcut(ED_SHORTCUT("path_editor/clear_points", TTRC("Clear All Points"), Key::NONE), HANDLE_OPTION_CLEAR_POINTS); + menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed)); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed)); add_child(toolbar); diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index cc5d564d157d..598623de7bae 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -1128,8 +1128,6 @@ Path3DEditorPlugin::Path3DEditorPlugin() { toolbar->add_child(curve_del); curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE)); - - curve_auto_tangent_mode = memnew(Button); curve_auto_tangent_mode->set_theme_type_variation(SceneStringName(FlatButton)); curve_auto_tangent_mode->set_focus_mode(Control::FOCUS_ACCESSIBILITY); @@ -1150,7 +1148,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { auto_tangent_torsion->set_value(0.5); v_separator = memnew(VSeparator); - toolbar-> add_child(v_separator); + toolbar->add_child(v_separator); curve_closed = memnew(Button); curve_closed->set_theme_type_variation(SceneStringName(FlatButton)); @@ -1185,8 +1183,8 @@ Path3DEditorPlugin::Path3DEditorPlugin() { menu->add_check_item(TTR("Snap to Colliders")); menu->set_item_checked(HANDLE_OPTION_SNAP_COLLIDER, snap_to_collider); menu->add_separator(); - menu->add_item(TTR("Apply Auto Tangent to All Points"), HANDLE_OPTION_AUTO_TANGENT); - menu->add_item(TTR("Clear Points"), HANDLE_OPTION_CLEAR_POINTS); + menu->add_shortcut(ED_SHORTCUT("path_editor/apply_auto_tangent_to_all_points", TTRC("Apply Auto Tangent to All / Selected Points"), KeyModifierMask::CMD_OR_CTRL | Key::T), HANDLE_OPTION_AUTO_TANGENT); + menu->add_shortcut(ED_SHORTCUT("path_editor/clear_points", TTRC("Clear All Points"), Key::NONE), HANDLE_OPTION_CLEAR_POINTS); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path3DEditorPlugin::_handle_option_pressed)); curve_edit->set_pressed_no_signal(true); From 1bbb1c95f1a8776abea32c64a98555587abfe9b2 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Mon, 1 Jun 2026 15:14:00 +0100 Subject: [PATCH 35/42] Add forward declarations for EditorSpinSlider and VSeparator --- editor/scene/2d/path_2d_editor_plugin.h | 4 ++-- editor/scene/3d/path_3d_editor_plugin.h | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index 4c8b5878c86b..5fea3bc1a9ce 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -34,12 +34,12 @@ #include "editor/plugins/editor_plugin.h" #include "scene/2d/path_2d.h" #include "scene/gui/box_container.h" -#include "scene/gui/separator.h" class CanvasItemEditor; class ConfirmationDialog; class MenuButton; -class SpinBox; +class EditorSpinSlider; +class VSeparator; class Path2DEditor : public HBoxContainer { GDCLASS(Path2DEditor, HBoxContainer); diff --git a/editor/scene/3d/path_3d_editor_plugin.h b/editor/scene/3d/path_3d_editor_plugin.h index 954cf52986f6..4d50e021bafa 100644 --- a/editor/scene/3d/path_3d_editor_plugin.h +++ b/editor/scene/3d/path_3d_editor_plugin.h @@ -30,17 +30,16 @@ #pragma once -#include "editor/gui/editor_spin_slider.h" #include "editor/plugins/editor_plugin.h" #include "editor/scene/3d/node_3d_editor_gizmos.h" #include "scene/3d/camera_3d.h" #include "scene/3d/path_3d.h" -#include "scene/gui/separator.h" -#include "scene/gui/spin_box.h" +class EditorSpinSlider; class HBoxContainer; class MenuButton; class ConfirmationDialog; +class VSeparator; class Path3DGizmo : public EditorNode3DGizmo { GDCLASS(Path3DGizmo, EditorNode3DGizmo); From f30b9cd72da7a16172e41dfe95724ee613c5596a Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Mon, 1 Jun 2026 15:21:56 +0100 Subject: [PATCH 36/42] Add missing includes --- editor/scene/2d/path_2d_editor_plugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index d4fe942ba7f0..85a22cb2946a 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -38,8 +38,10 @@ #include "editor/gui/editor_spin_slider.h" #include "editor/scene/canvas_item_editor_plugin.h" #include "editor/settings/editor_settings.h" +#include "editor/themes/editor_scale.h" #include "scene/gui/dialogs.h" #include "scene/gui/menu_button.h" +#include "scene/gui/separator.h" #include "scene/gui/spin_box.h" #include "scene/resources/mesh.h" #include "servers/rendering/rendering_server.h" From 6f6481a4302174ddef80a5cb47cd3b7cd74e0e69 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Mon, 1 Jun 2026 15:23:02 +0100 Subject: [PATCH 37/42] Remove unecessary include --- editor/scene/2d/path_2d_editor_plugin.h | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.h b/editor/scene/2d/path_2d_editor_plugin.h index 5fea3bc1a9ce..2251de8e14f9 100644 --- a/editor/scene/2d/path_2d_editor_plugin.h +++ b/editor/scene/2d/path_2d_editor_plugin.h @@ -30,7 +30,6 @@ #pragma once -#include "editor/gui/editor_spin_slider.h" #include "editor/plugins/editor_plugin.h" #include "scene/2d/path_2d.h" #include "scene/gui/box_container.h" From 6b139606cb358f21d3ba9ac661d318dbfc7337fe Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Mon, 1 Jun 2026 15:30:05 +0100 Subject: [PATCH 38/42] Remove another unecessary include, (missed by local prek?) --- editor/scene/2d/path_2d_editor_plugin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 85a22cb2946a..0fa0a3e1acd2 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -42,7 +42,6 @@ #include "scene/gui/dialogs.h" #include "scene/gui/menu_button.h" #include "scene/gui/separator.h" -#include "scene/gui/spin_box.h" #include "scene/resources/mesh.h" #include "servers/rendering/rendering_server.h" From c4653a405a809b711f75febd2220ff4a7acf4e21 Mon Sep 17 00:00:00 2001 From: "christiano.christakou" Date: Mon, 1 Jun 2026 15:52:58 +0100 Subject: [PATCH 39/42] Remove duplicate connection --- editor/scene/2d/path_2d_editor_plugin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 0fa0a3e1acd2..4941b07b8503 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -1036,7 +1036,6 @@ Path2DEditor::Path2DEditor() { menu->add_shortcut(ED_SHORTCUT("path_editor/apply_auto_tangent_to_all_points", TTRC("Apply Auto Tangent to All Points"), KeyModifierMask::CMD_OR_CTRL | Key::T), HANDLE_OPTION_AUTO_TANGENT); menu->add_shortcut(ED_SHORTCUT("path_editor/clear_points", TTRC("Clear All Points"), Key::NONE), HANDLE_OPTION_CLEAR_POINTS); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed)); - menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed)); add_child(toolbar); From 4ba1c2f202c5c5b15162b7381b16b206f0ee5ea1 Mon Sep 17 00:00:00 2001 From: Chris -- <41486691+Christakou@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:20:36 +0100 Subject: [PATCH 40/42] Update editor/scene/2d/path_2d_editor_plugin.cpp Co-authored-by: Tomasz Chabora --- editor/scene/2d/path_2d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 4941b07b8503..3d51ebaf4610 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -1034,7 +1034,7 @@ Path2DEditor::Path2DEditor() { menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); menu->add_separator(); menu->add_shortcut(ED_SHORTCUT("path_editor/apply_auto_tangent_to_all_points", TTRC("Apply Auto Tangent to All Points"), KeyModifierMask::CMD_OR_CTRL | Key::T), HANDLE_OPTION_AUTO_TANGENT); - menu->add_shortcut(ED_SHORTCUT("path_editor/clear_points", TTRC("Clear All Points"), Key::NONE), HANDLE_OPTION_CLEAR_POINTS); + menu->add_shortcut(ED_SHORTCUT("path_editor/clear_points", TTRC("Clear All Points")), HANDLE_OPTION_CLEAR_POINTS); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed)); add_child(toolbar); From ba06b8cffc6e518f700f3ca05f0ee4beb6f40214 Mon Sep 17 00:00:00 2001 From: Chris -- <41486691+Christakou@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:21:00 +0100 Subject: [PATCH 41/42] Update editor/scene/3d/path_3d_editor_plugin.cpp Co-authored-by: Tomasz Chabora --- editor/scene/3d/path_3d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/3d/path_3d_editor_plugin.cpp b/editor/scene/3d/path_3d_editor_plugin.cpp index 796c14a7862d..065d16614949 100644 --- a/editor/scene/3d/path_3d_editor_plugin.cpp +++ b/editor/scene/3d/path_3d_editor_plugin.cpp @@ -933,7 +933,7 @@ void Path3DEditorPlugin::_auto_tangent_point(int p_index) { undo_redo->add_do_method(curve.ptr(), "set_point_in", p_index, -tangent * curr_p.distance_to(prev_p) * smooth_ratio); undo_redo->add_undo_method(curve.ptr(), "set_point_out", p_index, curve->get_point_out(p_index)); undo_redo->add_do_method(curve.ptr(), "set_point_out", p_index, tangent * curr_p.distance_to(next_p) * smooth_ratio); - } else if (has_next) { // first point of an open curve set the out tangent and zero the in tangent + } else if (has_next) { // First point of an open curve set the out tangent and zero the in tangent. Vector3 next_p = curve->get_point_position(p_index + 1); Vector3 tangent = (next_p - curr_p).normalized(); undo_redo->add_undo_method(curve.ptr(), "set_point_in", p_index, curve->get_point_in(p_index)); From 1961ed0af16c0108c4d725536dd641af37c10f43 Mon Sep 17 00:00:00 2001 From: Chris -- <41486691+Christakou@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:21:25 +0100 Subject: [PATCH 42/42] Update editor/scene/2d/path_2d_editor_plugin.cpp Co-authored-by: Tomasz Chabora --- editor/scene/2d/path_2d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene/2d/path_2d_editor_plugin.cpp b/editor/scene/2d/path_2d_editor_plugin.cpp index 3d51ebaf4610..b8cb243f4929 100644 --- a/editor/scene/2d/path_2d_editor_plugin.cpp +++ b/editor/scene/2d/path_2d_editor_plugin.cpp @@ -1001,7 +1001,7 @@ Path2DEditor::Path2DEditor() { auto_tangent_torsion->set_h_size_flags(Control::SIZE_EXPAND); auto_tangent_torsion->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); auto_tangent_torsion->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - auto_tangent_torsion->set_tooltip_text(TTR("Auto Tangent Torsion")); + auto_tangent_torsion->set_tooltip_text(TTRC("Auto Tangent Torsion")); toolbar->add_child(auto_tangent_torsion); auto_tangent_torsion->set_value(0.5);