1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

[Polygon2DEditor] Add tool to automatically move center of gravity to origin.

This commit is contained in:
Pāvels Nadtočajevs
2025-03-12 11:59:49 +02:00
parent 215acd52e8
commit df321eb135
6 changed files with 196 additions and 9 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fff" d="M13 1c-2.667 0-2.667 4 0 4s2.667-4 0-4M3 11c-2.667 0-2.667 4 0 4s2.667-4 0-4m10 0c-2.667 0-2.667 4 0 4s2.667-4 0-4M3 1C.333 1 .333 5 3 5s2.667-4 0-4"/><path fill="#e0e0e0" fill-opacity=".4" d="M1.836 1.836v4.53h2v-2.53h2.537v-2zm7.791 0v2h2.537v2.53h2v-4.53zM1.836 9.619v4.545h4.537v-2H3.836V9.62zm10.328 0v2.545H9.627v2h4.537V9.62z"/><path fill="#5fff97" d="M7.999 6.06c-2.667 0-2.667 4 0 4s2.667-4 0-4M6.999 1.95v3a4 4 0 0 1 2 0v-3zM1.95 7v2h3a4 4 0 0 1 0-2zm9.1 0a4 4 0 0 1 0 2h3V7Zm-4.051 4.05v3h2v-3a4 4 0 0 1-2 0"/></svg>

After

Width:  |  Height:  |  Size: 612 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="5.764" fill="#fff" stroke="#000" stroke-width="1.6"/></svg>

After

Width:  |  Height:  |  Size: 148 B

View File

@@ -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<Vector2> &vertices = _get_polygon(i);
Vector<Vector<Point2>> decomp = ::Geometry2D::decompose_polygon_in_convex(vertices);
if (decomp.is_empty()) {
continue;
}
for (const Vector<Vector2> &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<Vector2> &vertices = _get_polygon(i);
int n_points = vertices.size();
Vector<Vector2> 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<InputEvent> &p_event) {
if (!_get_node() || !_polygon_editing_enabled) {
return false;
@@ -409,6 +494,34 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &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<InputEventMouseMotion> mm = p_event;
@@ -416,7 +529,25 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &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<Vector2> &vertices = _get_polygon(i);
int n_points = vertices.size();
Vector<Vector2> 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<Texture2D> handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
const Ref<Texture2D> nhandle = get_editor_theme_icon(SNAME("EditorPathNullHandle"));
Ref<Font> 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 &center = _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> 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"));

View File

@@ -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<Vector<Vector2>> 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<InputEvent> &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);

View File

@@ -38,6 +38,10 @@ void CollisionPolygon2DEditor::_set_node(Node *p_polygon) {
node = Object::cast_to<CollisionPolygon2D>(p_polygon);
}
CollisionPolygon2DEditor::CollisionPolygon2DEditor() {
set_edit_origin_and_center(true);
}
CollisionPolygon2DEditorPlugin::CollisionPolygon2DEditorPlugin() :
AbstractPolygon2DEditorPlugin(memnew(CollisionPolygon2DEditor), "CollisionPolygon2D") {
}

View File

@@ -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 {