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 {