@@ -128,6 +128,43 @@ bool AbstractPolygon2DEditor::_has_resource() const {
128128void AbstractPolygon2DEditor::_create_resource () {
129129}
130130
131+ Vector2 AbstractPolygon2DEditor::_get_geometric_center () const {
132+ int n_polygons = _get_polygon_count ();
133+
134+ double cx = 0.0 ;
135+ double cy = 0.0 ;
136+ int n_subs = 0 ;
137+ for (int i = 0 ; i < n_polygons; i++) {
138+ const Vector<Vector2> &vertices = _get_polygon (i);
139+ Vector<Vector<Point2>> decomp = ::Geometry2D::decompose_polygon_in_convex (vertices);
140+ if (decomp.is_empty ()) {
141+ continue ;
142+ }
143+ for (const Vector<Vector2> &sub : decomp) {
144+ int sub_n_points = sub.size ();
145+ double sub_area2x = 0.0 ;
146+ double sub_cx = 0.0 ;
147+ double sub_cy = 0.0 ;
148+ for (int n = 0 ; n < sub_n_points; n++) {
149+ int next = (n + 1 < sub_n_points) ? n + 1 : 0 ;
150+ sub_area2x += (sub[n].x * sub[next].y ) - (sub[next].x * sub[n].y );
151+ sub_cx += (sub[n].x + sub[next].x ) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y );
152+ sub_cy += (sub[n].y + sub[next].y ) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y );
153+ }
154+ sub_cx /= (sub_area2x * 3 );
155+ sub_cy /= (sub_area2x * 3 );
156+
157+ cx += sub_cx;
158+ cy += sub_cy;
159+ }
160+ n_subs += decomp.size ();
161+ }
162+ cx /= n_subs;
163+ cy /= n_subs;
164+
165+ return Vector2 (cx, cy);
166+ }
167+
131168void AbstractPolygon2DEditor::_menu_option (int p_option) {
132169 switch (p_option) {
133170 case MODE_CREATE: {
@@ -150,6 +187,33 @@ void AbstractPolygon2DEditor::_menu_option(int p_option) {
150187 button_edit->set_pressed (false );
151188 button_delete->set_pressed (true );
152189 } break ;
190+ case CENTER_POLY: {
191+ _wip_close ();
192+
193+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton ();
194+ undo_redo->create_action (TTR (" Move Origin to Geometric Center" ));
195+
196+ Vector2 center = _get_geometric_center ();
197+
198+ int n_polygons = _get_polygon_count ();
199+ for (int i = 0 ; i < n_polygons; i++) {
200+ const Vector<Vector2> &vertices = _get_polygon (i);
201+ int n_points = vertices.size ();
202+
203+ Vector<Vector2> new_vertices;
204+ new_vertices.resize (n_points);
205+ for (int n = 0 ; n < n_points; n++) {
206+ new_vertices.write [n] = vertices[n] - center;
207+ }
208+ _action_set_polygon (i, vertices, new_vertices);
209+ }
210+ Node2D *node = _get_node ();
211+ Vector2 node_pos = node->get_position ();
212+ undo_redo->add_do_method (node, " set_position" , node_pos + node->get_transform ().basis_xform (center));
213+ undo_redo->add_undo_method (node, " set_position" , node_pos);
214+
215+ _commit_action ();
216+ } break ;
153217 }
154218}
155219
@@ -159,6 +223,7 @@ void AbstractPolygon2DEditor::_notification(int p_what) {
159223 button_create->set_button_icon (get_editor_theme_icon (SNAME (" CurveCreate" )));
160224 button_edit->set_button_icon (get_editor_theme_icon (SNAME (" CurveEdit" )));
161225 button_delete->set_button_icon (get_editor_theme_icon (SNAME (" CurveDelete" )));
226+ button_center->set_button_icon (get_editor_theme_icon (SNAME (" CurveCenter" )));
162227 } break ;
163228
164229 case NOTIFICATION_READY: {
@@ -194,6 +259,7 @@ void AbstractPolygon2DEditor::_wip_cancel() {
194259 edited_point = PosVertex ();
195260 hover_point = Vertex ();
196261 selected_point = Vertex ();
262+ center_drag = false ;
197263
198264 canvas_item_editor->update_viewport ();
199265}
@@ -229,6 +295,7 @@ void AbstractPolygon2DEditor::_wip_close() {
229295 edited_point = PosVertex ();
230296 hover_point = Vertex ();
231297 selected_point = Vertex ();
298+ center_drag = false ;
232299}
233300
234301void AbstractPolygon2DEditor::disable_polygon_editing (bool p_disable, const String &p_reason) {
@@ -237,16 +304,34 @@ void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, const Stri
237304 button_create->set_disabled (p_disable);
238305 button_edit->set_disabled (p_disable);
239306 button_delete->set_disabled (p_disable);
307+ button_center->set_disabled (p_disable);
240308
241309 if (p_disable) {
242310 button_create->set_tooltip_text (p_reason);
243311 button_edit->set_tooltip_text (p_reason);
244312 button_delete->set_tooltip_text (p_reason);
313+ button_center->set_tooltip_text (p_reason);
245314 } else {
246- button_create->set_tooltip_text (TTR (" Create points." ));
247- button_edit->set_tooltip_text (TTR (" Edit points.\n LMB: Move Point\n RMB: Erase Point" ));
248- button_delete->set_tooltip_text (TTR (" Erase points." ));
315+ button_create->set_tooltip_text (TTRC (" Create points." ));
316+ button_edit->set_tooltip_text (TTRC (" Edit points.\n LMB: Move Point\n RMB: Erase Point" ));
317+ button_delete->set_tooltip_text (TTRC (" Erase points." ));
318+ button_center->set_tooltip_text (TTRC (" Move center of gravity to geometric center." ));
319+ }
320+ }
321+
322+ bool AbstractPolygon2DEditor::_commit_drag () {
323+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton ();
324+
325+ center_drag = false ;
326+ int n_polygons = _get_polygon_count ();
327+ ERR_FAIL_COND_V (pre_center_move_edit.size () != n_polygons, false );
328+ undo_redo->create_action (TTR (" Move Geometric Center" ));
329+ for (int i = 0 ; i < n_polygons; i++) {
330+ _action_set_polygon (i, pre_center_move_edit[i], _get_polygon (i));
249331 }
332+ pre_center_move_edit.clear ();
333+ _commit_action ();
334+ return true ;
250335}
251336
252337bool AbstractPolygon2DEditor::forward_gui_input (const Ref<InputEvent> &p_event) {
@@ -408,14 +493,60 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
408493 _wip_cancel ();
409494 }
410495 }
496+
497+ // Center drag.
498+ if (edit_origin_and_center) {
499+ real_t grab_threshold = EDITOR_GET (" editors/polygon_editor/point_grab_radius" );
500+
501+ if (mb->get_button_index () == MouseButton::LEFT) {
502+ if (mb->is_meta_pressed () || mb->is_ctrl_pressed () || mb->is_shift_pressed () || mb->is_alt_pressed ()) {
503+ return false ;
504+ }
505+ if (mb->is_pressed () && !center_drag) {
506+ Vector2 center_point = xform.xform (_get_geometric_center ());
507+ if ((gpoint - center_point).length () < grab_threshold) {
508+ pre_center_move_edit.clear ();
509+ int n_polygons = _get_polygon_count ();
510+ for (int i = 0 ; i < n_polygons; i++) {
511+ pre_center_move_edit.push_back (_get_polygon (i));
512+ }
513+ center_drag_origin = cpoint;
514+ center_drag = true ;
515+ return true ;
516+ }
517+ } else if (center_drag) {
518+ return _commit_drag ();
519+ }
520+ } else if (mb->get_button_index () == MouseButton::RIGHT && center_drag) {
521+ _commit_drag ();
522+ }
523+ }
411524 }
412525
413526 Ref<InputEventMouseMotion> mm = p_event;
414527
415528 if (mm.is_valid ()) {
416529 Vector2 gpoint = mm->get_position ();
417530
418- if (edited_point.valid () && (wip_active || mm->get_button_mask ().has_flag (MouseButtonMask::LEFT))) {
531+ if (center_drag) {
532+ Vector2 cpoint = canvas_item_editor->snap_point (canvas_item_editor->get_canvas_transform ().affine_inverse ().xform (gpoint));
533+ cpoint = _get_node ()->get_screen_transform ().affine_inverse ().xform (cpoint);
534+ Vector2 delta = center_drag_origin - cpoint;
535+
536+ int n_polygons = _get_polygon_count ();
537+ for (int i = 0 ; i < n_polygons; i++) {
538+ const Vector<Vector2> &vertices = _get_polygon (i);
539+ int n_points = vertices.size ();
540+
541+ Vector<Vector2> new_vertices;
542+ new_vertices.resize (n_points);
543+ for (int n = 0 ; n < n_points; n++) {
544+ new_vertices.write [n] = vertices[n] - delta;
545+ }
546+ _set_polygon (i, new_vertices);
547+ }
548+ center_drag_origin = cpoint;
549+ } else if (edited_point.valid () && (wip_active || mm->get_button_mask ().has_flag (MouseButtonMask::LEFT))) {
419550 Vector2 cpoint = canvas_item_editor->snap_point (canvas_item_editor->get_canvas_transform ().affine_inverse ().xform (gpoint));
420551 cpoint = _get_node ()->get_screen_transform ().affine_inverse ().xform (cpoint);
421552
@@ -512,11 +643,36 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
512643 Transform2D xform = canvas_item_editor->get_canvas_transform () * _get_node ()->get_screen_transform ();
513644 // All polygon points are sharp, so use the sharp handle icon
514645 const Ref<Texture2D> handle = get_editor_theme_icon (SNAME (" EditorPathSharpHandle" ));
646+ const Ref<Texture2D> nhandle = get_editor_theme_icon (SNAME (" EditorPathNullHandle" ));
647+
648+ Ref<Font> font = get_theme_font (SNAME (" bold" ), EditorStringName (EditorFonts));
649+ int font_size = 1.3 * get_theme_font_size (SNAME (" bold_size" ), EditorStringName (EditorFonts));
650+ const float outline_size = 4 * EDSCALE;
651+ Color font_color = get_theme_color (SceneStringName (font_color), EditorStringName (Editor));
652+ Color outline_color = font_color.inverted ();
515653
516654 const Vertex active_point = get_active_point ();
517655 const int n_polygons = _get_polygon_count ();
518656 const bool is_closed = !_is_line ();
519657
658+ if (edit_origin_and_center) {
659+ const Vector2 ¢er = _get_geometric_center ();
660+ if (!center.is_zero_approx ()) {
661+ const Vector2 point = xform.xform (center);
662+ p_overlay->draw_texture (nhandle, point - nhandle->get_size () * 0.5 , Color (1 , 1 , 0.4 ));
663+ Size2 lbl_size = font->get_string_size (" c" , HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size);
664+ p_overlay->draw_string_outline (font, point - lbl_size * 0.5 , " c" , HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size, outline_size, outline_color);
665+ p_overlay->draw_string (font, point - lbl_size * 0.5 , " c" , HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size, font_color);
666+ }
667+ {
668+ const Vector2 point = xform.xform (Vector2 ());
669+ 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 ));
670+ Size2 lbl_size = font->get_string_size (" o" , HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size);
671+ p_overlay->draw_string_outline (font, point - lbl_size * 0.5 , " o" , HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size, outline_size, outline_color);
672+ p_overlay->draw_string (font, point - lbl_size * 0.5 , " o" , HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size, font_color);
673+ }
674+ }
675+
520676 for (int j = -1 ; j < n_polygons; j++) {
521677 if (wip_active && wip_destructive && j != -1 ) {
522678 continue ;
@@ -584,13 +740,8 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
584740 p_overlay->draw_texture (handle, point - handle->get_size () * 0.5 , overlay_modulate);
585741
586742 if (vertex == hover_point) {
587- Ref<Font> font = get_theme_font (SNAME (" bold" ), EditorStringName (EditorFonts));
588- int font_size = 1.3 * get_theme_font_size (SNAME (" bold_size" ), EditorStringName (EditorFonts));
589743 String num = String::num_int64 (vertex.vertex );
590744 Size2 num_size = font->get_string_size (num, HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size);
591- const float outline_size = 4 ;
592- Color font_color = get_theme_color (SceneStringName (font_color), EditorStringName (Editor));
593- Color outline_color = font_color.inverted ();
594745 p_overlay->draw_string_outline (font, point - num_size * 0.5 , num, HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size, outline_size, outline_color);
595746 p_overlay->draw_string (font, point - num_size * 0.5 , num, HORIZONTAL_ALIGNMENT_LEFT, -1 , font_size, font_color);
596747 }
@@ -603,6 +754,13 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
603754 }
604755}
605756
757+ void AbstractPolygon2DEditor::set_edit_origin_and_center (bool p_enabled) {
758+ edit_origin_and_center = p_enabled;
759+ if (button_center) {
760+ button_center->set_visible (edit_origin_and_center);
761+ }
762+ }
763+
606764void AbstractPolygon2DEditor::edit (Node *p_polygon) {
607765 if (!canvas_item_editor) {
608766 canvas_item_editor = CanvasItemEditor::get_singleton ();
@@ -623,6 +781,7 @@ void AbstractPolygon2DEditor::edit(Node *p_polygon) {
623781 edited_point = PosVertex ();
624782 hover_point = Vertex ();
625783 selected_point = Vertex ();
784+ center_drag = false ;
626785 } else {
627786 _set_node (nullptr );
628787 }
@@ -728,6 +887,7 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(c
728887
729888AbstractPolygon2DEditor::AbstractPolygon2DEditor (bool p_wip_destructive) {
730889 edited_point = PosVertex ();
890+ center_drag = false ;
731891 wip_destructive = p_wip_destructive;
732892
733893 hover_point = Vertex ();
@@ -755,6 +915,12 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
755915 button_delete->connect (SceneStringName (pressed), callable_mp (this , &AbstractPolygon2DEditor::_menu_option).bind (MODE_DELETE));
756916 button_delete->set_toggle_mode (true );
757917
918+ button_center = memnew (Button);
919+ button_center->set_theme_type_variation (SceneStringName (FlatButton));
920+ add_child (button_center);
921+ button_center->connect (SceneStringName (pressed), callable_mp (this , &AbstractPolygon2DEditor::_menu_option).bind (CENTER_POLY));
922+ button_center->set_visible (edit_origin_and_center);
923+
758924 create_resource = memnew (ConfirmationDialog);
759925 add_child (create_resource);
760926 create_resource->set_ok_button_text (TTR (" Create" ));
0 commit comments