From df321eb13570627423f3ea8e2977dc07fb875401 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?=
<7645683+bruvzg@users.noreply.github.com>
Date: Wed, 12 Mar 2025 11:59:49 +0200
Subject: [PATCH] [Polygon2DEditor] Add tool to automatically move center of
gravity to origin.
---
editor/icons/CurveCenter.svg | 1 +
editor/icons/EditorPathNullHandle.svg | 1 +
editor/plugins/abstract_polygon_2d_editor.cpp | 184 +++++++++++++++++-
editor/plugins/abstract_polygon_2d_editor.h | 12 ++
.../collision_polygon_2d_editor_plugin.cpp | 4 +
.../collision_polygon_2d_editor_plugin.h | 3 +
6 files changed, 196 insertions(+), 9 deletions(-)
create mode 100644 editor/icons/CurveCenter.svg
create mode 100644 editor/icons/EditorPathNullHandle.svg
diff --git a/editor/icons/CurveCenter.svg b/editor/icons/CurveCenter.svg
new file mode 100644
index 00000000000..3272ca3f214
--- /dev/null
+++ b/editor/icons/CurveCenter.svg
@@ -0,0 +1 @@
+
diff --git a/editor/icons/EditorPathNullHandle.svg b/editor/icons/EditorPathNullHandle.svg
new file mode 100644
index 00000000000..b3d997f5410
--- /dev/null
+++ b/editor/icons/EditorPathNullHandle.svg
@@ -0,0 +1 @@
+
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp
index 3d5ffa0c0e2..4fdbf336ca0 100644
--- a/editor/plugins/abstract_polygon_2d_editor.cpp
+++ b/editor/plugins/abstract_polygon_2d_editor.cpp
@@ -128,6 +128,43 @@ bool AbstractPolygon2DEditor::_has_resource() const {
void AbstractPolygon2DEditor::_create_resource() {
}
+Vector2 AbstractPolygon2DEditor::_get_geometric_center() const {
+ int n_polygons = _get_polygon_count();
+
+ double cx = 0.0;
+ double cy = 0.0;
+ int n_subs = 0;
+ for (int i = 0; i < n_polygons; i++) {
+ const Vector &vertices = _get_polygon(i);
+ Vector> decomp = ::Geometry2D::decompose_polygon_in_convex(vertices);
+ if (decomp.is_empty()) {
+ continue;
+ }
+ for (const Vector &sub : decomp) {
+ int sub_n_points = sub.size();
+ double sub_area2x = 0.0;
+ double sub_cx = 0.0;
+ double sub_cy = 0.0;
+ for (int n = 0; n < sub_n_points; n++) {
+ int next = (n + 1 < sub_n_points) ? n + 1 : 0;
+ sub_area2x += (sub[n].x * sub[next].y) - (sub[next].x * sub[n].y);
+ sub_cx += (sub[n].x + sub[next].x) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);
+ sub_cy += (sub[n].y + sub[next].y) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);
+ }
+ sub_cx /= (sub_area2x * 3);
+ sub_cy /= (sub_area2x * 3);
+
+ cx += sub_cx;
+ cy += sub_cy;
+ }
+ n_subs += decomp.size();
+ }
+ cx /= n_subs;
+ cy /= n_subs;
+
+ return Vector2(cx, cy);
+}
+
void AbstractPolygon2DEditor::_menu_option(int p_option) {
switch (p_option) {
case MODE_CREATE: {
@@ -150,6 +187,33 @@ void AbstractPolygon2DEditor::_menu_option(int p_option) {
button_edit->set_pressed(false);
button_delete->set_pressed(true);
} break;
+ case CENTER_POLY: {
+ _wip_close();
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Move Origin to Geometric Center"));
+
+ Vector2 center = _get_geometric_center();
+
+ int n_polygons = _get_polygon_count();
+ for (int i = 0; i < n_polygons; i++) {
+ const Vector &vertices = _get_polygon(i);
+ int n_points = vertices.size();
+
+ Vector new_vertices;
+ new_vertices.resize(n_points);
+ for (int n = 0; n < n_points; n++) {
+ new_vertices.write[n] = vertices[n] - center;
+ }
+ _action_set_polygon(i, vertices, new_vertices);
+ }
+ Node2D *node = _get_node();
+ Vector2 node_pos = node->get_position();
+ undo_redo->add_do_method(node, "set_position", node_pos + node->get_transform().basis_xform(center));
+ undo_redo->add_undo_method(node, "set_position", node_pos);
+
+ _commit_action();
+ } break;
}
}
@@ -160,6 +224,7 @@ void AbstractPolygon2DEditor::_notification(int p_what) {
button_create->set_button_icon(get_editor_theme_icon(SNAME("CurveCreate")));
button_edit->set_button_icon(get_editor_theme_icon(SNAME("CurveEdit")));
button_delete->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete")));
+ button_center->set_button_icon(get_editor_theme_icon(SNAME("CurveCenter")));
} break;
case NOTIFICATION_READY: {
@@ -195,6 +260,7 @@ void AbstractPolygon2DEditor::_wip_cancel() {
edited_point = PosVertex();
hover_point = Vertex();
selected_point = Vertex();
+ center_drag = false;
canvas_item_editor->update_viewport();
}
@@ -230,6 +296,7 @@ void AbstractPolygon2DEditor::_wip_close() {
edited_point = PosVertex();
hover_point = Vertex();
selected_point = Vertex();
+ center_drag = false;
}
void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, const String &p_reason) {
@@ -238,18 +305,36 @@ void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, const Stri
button_create->set_disabled(p_disable);
button_edit->set_disabled(p_disable);
button_delete->set_disabled(p_disable);
+ button_center->set_disabled(p_disable);
if (p_disable) {
button_create->set_tooltip_text(p_reason);
button_edit->set_tooltip_text(p_reason);
button_delete->set_tooltip_text(p_reason);
+ button_center->set_tooltip_text(p_reason);
} else {
- button_create->set_tooltip_text(TTR("Create points."));
- button_edit->set_tooltip_text(TTR("Edit points.\nLMB: Move Point\nRMB: Erase Point"));
- button_delete->set_tooltip_text(TTR("Erase points."));
+ button_create->set_tooltip_text(TTRC("Create points."));
+ button_edit->set_tooltip_text(TTRC("Edit points.\nLMB: Move Point\nRMB: Erase Point"));
+ button_delete->set_tooltip_text(TTRC("Erase points."));
+ button_center->set_tooltip_text(TTRC("Move center of gravity to geometric center."));
}
}
+bool AbstractPolygon2DEditor::_commit_drag() {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+
+ center_drag = false;
+ int n_polygons = _get_polygon_count();
+ ERR_FAIL_COND_V(pre_center_move_edit.size() != n_polygons, false);
+ undo_redo->create_action(TTR("Move Geometric Center"));
+ for (int i = 0; i < n_polygons; i++) {
+ _action_set_polygon(i, pre_center_move_edit[i], _get_polygon(i));
+ }
+ pre_center_move_edit.clear();
+ _commit_action();
+ return true;
+}
+
bool AbstractPolygon2DEditor::forward_gui_input(const Ref &p_event) {
if (!_get_node() || !_polygon_editing_enabled) {
return false;
@@ -409,6 +494,34 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref &p_event)
_wip_cancel();
}
}
+
+ // Center drag.
+ if (edit_origin_and_center) {
+ real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
+
+ if (mb->get_button_index() == MouseButton::LEFT) {
+ if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
+ return false;
+ }
+ if (mb->is_pressed() && !center_drag) {
+ Vector2 center_point = xform.xform(_get_geometric_center());
+ if ((gpoint - center_point).length() < grab_threshold) {
+ pre_center_move_edit.clear();
+ int n_polygons = _get_polygon_count();
+ for (int i = 0; i < n_polygons; i++) {
+ pre_center_move_edit.push_back(_get_polygon(i));
+ }
+ center_drag_origin = cpoint;
+ center_drag = true;
+ return true;
+ }
+ } else if (center_drag) {
+ return _commit_drag();
+ }
+ } else if (mb->get_button_index() == MouseButton::RIGHT && center_drag) {
+ _commit_drag();
+ }
+ }
}
Ref mm = p_event;
@@ -416,7 +529,25 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref &p_event)
if (mm.is_valid()) {
Vector2 gpoint = mm->get_position();
- if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
+ if (center_drag) {
+ Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
+ cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);
+ Vector2 delta = center_drag_origin - cpoint;
+
+ int n_polygons = _get_polygon_count();
+ for (int i = 0; i < n_polygons; i++) {
+ const Vector &vertices = _get_polygon(i);
+ int n_points = vertices.size();
+
+ Vector new_vertices;
+ new_vertices.resize(n_points);
+ for (int n = 0; n < n_points; n++) {
+ new_vertices.write[n] = vertices[n] - delta;
+ }
+ _set_polygon(i, new_vertices);
+ }
+ center_drag_origin = cpoint;
+ } else if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);
@@ -513,11 +644,36 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
// All polygon points are sharp, so use the sharp handle icon
const Ref handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
+ const Ref nhandle = get_editor_theme_icon(SNAME("EditorPathNullHandle"));
+
+ Ref font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
+ int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
+ const float outline_size = 4 * EDSCALE;
+ Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
+ Color outline_color = font_color.inverted();
const Vertex active_point = get_active_point();
const int n_polygons = _get_polygon_count();
const bool is_closed = !_is_line();
+ if (edit_origin_and_center) {
+ const Vector2 ¢er = _get_geometric_center();
+ if (!center.is_zero_approx()) {
+ const Vector2 point = xform.xform(center);
+ p_overlay->draw_texture(nhandle, point - nhandle->get_size() * 0.5, Color(1, 1, 0.4));
+ Size2 lbl_size = font->get_string_size("c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
+ p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
+ p_overlay->draw_string(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
+ }
+ {
+ const Vector2 point = xform.xform(Vector2());
+ p_overlay->draw_texture(nhandle, point - nhandle->get_size() * 0.5, center.is_equal_approx(Vector2()) ? Color(1, 1, 0.4) : Color(1, 0.4, 1));
+ Size2 lbl_size = font->get_string_size("o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
+ p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
+ p_overlay->draw_string(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
+ }
+ }
+
for (int j = -1; j < n_polygons; j++) {
if (wip_active && wip_destructive && j != -1) {
continue;
@@ -585,13 +741,8 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, overlay_modulate);
if (vertex == hover_point) {
- Ref font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
- int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
String num = String::num_int64(vertex.vertex);
Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
- const float outline_size = 4;
- Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
- Color outline_color = font_color.inverted();
p_overlay->draw_string_outline(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
p_overlay->draw_string(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
}
@@ -604,6 +755,13 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
}
}
+void AbstractPolygon2DEditor::set_edit_origin_and_center(bool p_enabled) {
+ edit_origin_and_center = p_enabled;
+ if (button_center) {
+ button_center->set_visible(edit_origin_and_center);
+ }
+}
+
void AbstractPolygon2DEditor::edit(Node *p_polygon) {
if (!canvas_item_editor) {
canvas_item_editor = CanvasItemEditor::get_singleton();
@@ -624,6 +782,7 @@ void AbstractPolygon2DEditor::edit(Node *p_polygon) {
edited_point = PosVertex();
hover_point = Vertex();
selected_point = Vertex();
+ center_drag = false;
} else {
_set_node(nullptr);
}
@@ -729,6 +888,7 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(c
AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
edited_point = PosVertex();
+ center_drag = false;
wip_destructive = p_wip_destructive;
hover_point = Vertex();
@@ -756,6 +916,12 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
button_delete->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE));
button_delete->set_toggle_mode(true);
+ button_center = memnew(Button);
+ button_center->set_theme_type_variation(SceneStringName(FlatButton));
+ add_child(button_center);
+ button_center->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(CENTER_POLY));
+ button_center->set_visible(edit_origin_and_center);
+
create_resource = memnew(ConfirmationDialog);
add_child(create_resource);
create_resource->set_ok_button_text(TTR("Create"));
diff --git a/editor/plugins/abstract_polygon_2d_editor.h b/editor/plugins/abstract_polygon_2d_editor.h
index 7fd9bee4909..25068cd4a24 100644
--- a/editor/plugins/abstract_polygon_2d_editor.h
+++ b/editor/plugins/abstract_polygon_2d_editor.h
@@ -44,6 +44,7 @@ class AbstractPolygon2DEditor : public HBoxContainer {
Button *button_create = nullptr;
Button *button_edit = nullptr;
Button *button_delete = nullptr;
+ Button *button_center = nullptr;
struct Vertex {
Vertex() {}
@@ -85,6 +86,11 @@ class AbstractPolygon2DEditor : public HBoxContainer {
bool wip_active = false;
bool wip_destructive = false;
+ Vector> pre_center_move_edit;
+ bool center_drag = false;
+ Vector2 center_drag_origin;
+ bool edit_origin_and_center = false;
+
bool _polygon_editing_enabled = false;
CanvasItemEditor *canvas_item_editor = nullptr;
@@ -97,6 +103,7 @@ protected:
MODE_EDIT,
MODE_DELETE,
MODE_CONT,
+ CENTER_POLY,
};
int mode = MODE_EDIT;
@@ -109,6 +116,8 @@ protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
+ bool _commit_drag();
+
void remove_point(const Vertex &p_vertex);
Vertex get_active_point() const;
PosVertex closest_point(const Vector2 &p_pos) const;
@@ -132,6 +141,8 @@ protected:
virtual void _action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon);
virtual void _commit_action();
+ virtual Vector2 _get_geometric_center() const;
+
virtual bool _has_resource() const;
virtual void _create_resource();
@@ -140,6 +151,7 @@ public:
bool forward_gui_input(const Ref &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
+ void set_edit_origin_and_center(bool p_enabled);
void edit(Node *p_polygon);
AbstractPolygon2DEditor(bool p_wip_destructive = true);
diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.cpp b/editor/plugins/collision_polygon_2d_editor_plugin.cpp
index e4e6a07358b..f58c59761f1 100644
--- a/editor/plugins/collision_polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/collision_polygon_2d_editor_plugin.cpp
@@ -38,6 +38,10 @@ void CollisionPolygon2DEditor::_set_node(Node *p_polygon) {
node = Object::cast_to(p_polygon);
}
+CollisionPolygon2DEditor::CollisionPolygon2DEditor() {
+ set_edit_origin_and_center(true);
+}
+
CollisionPolygon2DEditorPlugin::CollisionPolygon2DEditorPlugin() :
AbstractPolygon2DEditorPlugin(memnew(CollisionPolygon2DEditor), "CollisionPolygon2D") {
}
diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.h b/editor/plugins/collision_polygon_2d_editor_plugin.h
index ba2d07b7c41..34b7ca0e7d0 100644
--- a/editor/plugins/collision_polygon_2d_editor_plugin.h
+++ b/editor/plugins/collision_polygon_2d_editor_plugin.h
@@ -41,6 +41,9 @@ class CollisionPolygon2DEditor : public AbstractPolygon2DEditor {
protected:
virtual Node2D *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
+
+public:
+ CollisionPolygon2DEditor();
};
class CollisionPolygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin {