Path: blob/master/editor/scene/2d/abstract_polygon_2d_editor.cpp
9904 views
/**************************************************************************/1/* abstract_polygon_2d_editor.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "abstract_polygon_2d_editor.h"3132#include "core/math/geometry_2d.h"33#include "core/os/keyboard.h"34#include "editor/editor_node.h"35#include "editor/editor_string_names.h"36#include "editor/editor_undo_redo_manager.h"37#include "editor/scene/canvas_item_editor_plugin.h"38#include "editor/settings/editor_settings.h"39#include "editor/themes/editor_scale.h"40#include "scene/gui/button.h"41#include "scene/gui/dialogs.h"4243bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const {44return polygon == p_vertex.polygon && vertex == p_vertex.vertex;45}4647bool AbstractPolygon2DEditor::Vertex::operator!=(const AbstractPolygon2DEditor::Vertex &p_vertex) const {48return !(*this == p_vertex);49}5051bool AbstractPolygon2DEditor::Vertex::valid() const {52return vertex >= 0;53}5455bool AbstractPolygon2DEditor::_is_empty() const {56if (!_get_node()) {57return true;58}5960const int n = _get_polygon_count();6162for (int i = 0; i < n; i++) {63Vector<Vector2> vertices = _get_polygon(i);6465if (vertices.size() != 0) {66return false;67}68}6970return true;71}7273bool AbstractPolygon2DEditor::_is_line() const {74return false;75}7677bool AbstractPolygon2DEditor::_has_uv() const {78return false;79}8081int AbstractPolygon2DEditor::_get_polygon_count() const {82return 1;83}8485Variant AbstractPolygon2DEditor::_get_polygon(int p_idx) const {86return _get_node()->get("polygon");87}8889void AbstractPolygon2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {90_get_node()->set("polygon", p_polygon);91}9293void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {94Node2D *node = _get_node();95EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();96undo_redo->add_do_method(node, "set_polygon", p_polygon);97undo_redo->add_undo_method(node, "set_polygon", p_previous);98}99100Vector2 AbstractPolygon2DEditor::_get_offset(int p_idx) const {101return Vector2(0, 0);102}103104void AbstractPolygon2DEditor::_commit_action() {105EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();106undo_redo->add_do_method(canvas_item_editor, "update_viewport");107undo_redo->add_undo_method(canvas_item_editor, "update_viewport");108undo_redo->commit_action();109}110111void AbstractPolygon2DEditor::_action_add_polygon(const Variant &p_polygon) {112_action_set_polygon(0, p_polygon);113}114115void AbstractPolygon2DEditor::_action_remove_polygon(int p_idx) {116_action_set_polygon(p_idx, _get_polygon(p_idx), Vector<Vector2>());117}118119void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_polygon) {120_action_set_polygon(p_idx, _get_polygon(p_idx), p_polygon);121}122123bool AbstractPolygon2DEditor::_has_resource() const {124return true;125}126127void AbstractPolygon2DEditor::_create_resource() {128}129130Vector2 AbstractPolygon2DEditor::_get_geometric_center() const {131int n_polygons = _get_polygon_count();132133double cx = 0.0;134double cy = 0.0;135int n_subs = 0;136for (int i = 0; i < n_polygons; i++) {137const Vector<Vector2> &vertices = _get_polygon(i);138Vector<Vector<Point2>> decomp = ::Geometry2D::decompose_polygon_in_convex(vertices);139if (decomp.is_empty()) {140continue;141}142for (const Vector<Vector2> &sub : decomp) {143int sub_n_points = sub.size();144double sub_area2x = 0.0;145double sub_cx = 0.0;146double sub_cy = 0.0;147for (int n = 0; n < sub_n_points; n++) {148int next = (n + 1 < sub_n_points) ? n + 1 : 0;149sub_area2x += (sub[n].x * sub[next].y) - (sub[next].x * sub[n].y);150sub_cx += (sub[n].x + sub[next].x) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);151sub_cy += (sub[n].y + sub[next].y) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);152}153sub_cx /= (sub_area2x * 3);154sub_cy /= (sub_area2x * 3);155156cx += sub_cx;157cy += sub_cy;158}159n_subs += decomp.size();160}161cx /= n_subs;162cy /= n_subs;163164return Vector2(cx, cy);165}166167void AbstractPolygon2DEditor::_menu_option(int p_option) {168switch (p_option) {169case MODE_CREATE: {170mode = MODE_CREATE;171button_create->set_pressed(true);172button_edit->set_pressed(false);173button_delete->set_pressed(false);174} break;175case MODE_EDIT: {176_wip_close();177mode = MODE_EDIT;178button_create->set_pressed(false);179button_edit->set_pressed(true);180button_delete->set_pressed(false);181} break;182case MODE_DELETE: {183_wip_close();184mode = MODE_DELETE;185button_create->set_pressed(false);186button_edit->set_pressed(false);187button_delete->set_pressed(true);188} break;189case CENTER_POLY: {190_wip_close();191192EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();193undo_redo->create_action(TTR("Move Origin to Geometric Center"));194195Vector2 center = _get_geometric_center();196197int n_polygons = _get_polygon_count();198for (int i = 0; i < n_polygons; i++) {199const Vector<Vector2> &vertices = _get_polygon(i);200int n_points = vertices.size();201202Vector<Vector2> new_vertices;203new_vertices.resize(n_points);204for (int n = 0; n < n_points; n++) {205new_vertices.write[n] = vertices[n] - center;206}207_action_set_polygon(i, vertices, new_vertices);208}209Node2D *node = _get_node();210Vector2 node_pos = node->get_position();211undo_redo->add_do_method(node, "set_position", node_pos + node->get_transform().basis_xform(center));212undo_redo->add_undo_method(node, "set_position", node_pos);213214_commit_action();215} break;216}217}218219void AbstractPolygon2DEditor::_notification(int p_what) {220switch (p_what) {221case NOTIFICATION_THEME_CHANGED: {222button_create->set_button_icon(get_editor_theme_icon(SNAME("CurveCreate")));223button_edit->set_button_icon(get_editor_theme_icon(SNAME("CurveEdit")));224button_delete->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete")));225button_center->set_button_icon(get_editor_theme_icon(SNAME("CurveCenter")));226} break;227228case NOTIFICATION_READY: {229disable_polygon_editing(false, String());230231button_edit->set_pressed(true);232233get_tree()->connect("node_removed", callable_mp(this, &AbstractPolygon2DEditor::_node_removed));234create_resource->connect(SceneStringName(confirmed), callable_mp(this, &AbstractPolygon2DEditor::_create_resource));235} break;236}237}238239void AbstractPolygon2DEditor::_node_removed(Node *p_node) {240if (p_node == _get_node()) {241edit(nullptr);242hide();243244canvas_item_editor->update_viewport();245}246}247248void AbstractPolygon2DEditor::_wip_changed() {249if (wip_active && _is_line()) {250_set_polygon(0, wip);251}252}253254void AbstractPolygon2DEditor::_wip_cancel() {255wip.clear();256wip_active = false;257258edited_point = PosVertex();259hover_point = Vertex();260selected_point = Vertex();261center_drag = false;262263canvas_item_editor->update_viewport();264}265266void AbstractPolygon2DEditor::_wip_close() {267if (!wip_active) {268return;269}270271if (_is_line()) {272_set_polygon(0, wip);273} else if (wip.size() >= (_is_line() ? 2 : 3)) {274EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();275undo_redo->create_action(TTR("Create Polygon"));276_action_add_polygon(wip);277if (_has_uv()) {278undo_redo->add_do_method(_get_node(), "set_uv", Vector<Vector2>());279undo_redo->add_undo_method(_get_node(), "set_uv", _get_node()->get("uv"));280}281_commit_action();282} else {283return;284}285286mode = MODE_EDIT;287button_edit->set_pressed(true);288button_create->set_pressed(false);289button_delete->set_pressed(false);290291wip.clear();292wip_active = false;293294edited_point = PosVertex();295hover_point = Vertex();296selected_point = Vertex();297center_drag = false;298}299300void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, const String &p_reason) {301_polygon_editing_enabled = !p_disable;302303button_create->set_disabled(p_disable);304button_edit->set_disabled(p_disable);305button_delete->set_disabled(p_disable);306button_center->set_disabled(p_disable);307308if (p_disable) {309button_create->set_tooltip_text(p_reason);310button_edit->set_tooltip_text(p_reason);311button_delete->set_tooltip_text(p_reason);312button_center->set_tooltip_text(p_reason);313} else {314button_create->set_tooltip_text(TTRC("Create points."));315button_edit->set_tooltip_text(TTRC("Edit points.\nLMB: Move Point\nRMB: Erase Point"));316button_delete->set_tooltip_text(TTRC("Erase points."));317button_center->set_tooltip_text(TTRC("Move center of gravity to geometric center."));318}319}320321bool AbstractPolygon2DEditor::_commit_drag() {322EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();323324center_drag = false;325int n_polygons = _get_polygon_count();326ERR_FAIL_COND_V(pre_center_move_edit.size() != n_polygons, false);327undo_redo->create_action(TTR("Move Geometric Center"));328for (int i = 0; i < n_polygons; i++) {329_action_set_polygon(i, pre_center_move_edit[i], _get_polygon(i));330}331pre_center_move_edit.clear();332_commit_action();333return true;334}335336bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {337if (!_get_node() || !_polygon_editing_enabled) {338return false;339}340341if (!_get_node()->is_visible_in_tree()) {342return false;343}344345Viewport *vp = _get_node()->get_viewport();346if (vp && !vp->is_visible_subviewport()) {347return false;348}349350EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();351Ref<InputEventMouseButton> mb = p_event;352353if (!_has_resource()) {354if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {355create_resource->set_text(String("No polygon resource on this node.\nCreate and assign one?"));356create_resource->popup_centered();357}358return (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT);359}360361CanvasItemEditor::Tool tool = CanvasItemEditor::get_singleton()->get_current_tool();362if (tool != CanvasItemEditor::TOOL_SELECT) {363return false;364}365366if (mb.is_valid()) {367Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();368369Vector2 gpoint = mb->get_position();370Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));371cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);372373if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {374if (mb->get_button_index() == MouseButton::LEFT) {375if (mb->is_pressed()) {376if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {377return false;378}379380const PosVertex closest = closest_point(gpoint);381if (closest.valid()) {382original_mouse_pos = gpoint;383pre_move_edit = _get_polygon(closest.polygon);384edited_point = PosVertex(closest, xform.affine_inverse().xform(closest.pos));385selected_point = closest;386edge_point = PosVertex();387canvas_item_editor->update_viewport();388return true;389} else {390selected_point = Vertex();391392const PosVertex insert = closest_edge_point(gpoint);393if (insert.valid()) {394Vector<Vector2> vertices = _get_polygon(insert.polygon);395396if (vertices.size() < (_is_line() ? 2 : 3)) {397vertices.push_back(cpoint);398undo_redo->create_action(TTR("Edit Polygon"));399selected_point = Vertex(insert.polygon, vertices.size());400_action_set_polygon(insert.polygon, vertices);401_commit_action();402return true;403} else {404edited_point = PosVertex(insert.polygon, insert.vertex + 1, xform.affine_inverse().xform(insert.pos));405vertices.insert(edited_point.vertex, edited_point.pos);406pre_move_edit = vertices;407selected_point = Vertex(edited_point.polygon, edited_point.vertex);408edge_point = PosVertex();409410undo_redo->create_action(TTR("Insert Point"));411_action_set_polygon(insert.polygon, vertices);412_commit_action();413return true;414}415}416}417} else {418if (edited_point.valid()) {419if (original_mouse_pos != gpoint) {420Vector<Vector2> vertices = _get_polygon(edited_point.polygon);421ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);422vertices.write[edited_point.vertex] = edited_point.pos - _get_offset(edited_point.polygon);423424undo_redo->create_action(TTR("Edit Polygon"));425_action_set_polygon(edited_point.polygon, pre_move_edit, vertices);426_commit_action();427}428429edited_point = PosVertex();430return true;431}432}433} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && !edited_point.valid()) {434const PosVertex closest = closest_point(gpoint);435436if (closest.valid()) {437remove_point(closest);438return true;439}440}441} else if (mode == MODE_DELETE) {442if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {443const PosVertex closest = closest_point(gpoint);444445if (closest.valid()) {446remove_point(closest);447return true;448}449}450}451452if (mode == MODE_CREATE) {453if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {454if (_is_line()) {455// for lines, we don't have a wip mode, and we can undo each single add point.456Vector<Vector2> vertices = _get_polygon(0);457vertices.push_back(cpoint);458undo_redo->create_action(TTR("Insert Point"));459_action_set_polygon(0, vertices);460_commit_action();461return true;462} else if (!wip_active) {463wip.clear();464wip.push_back(cpoint);465wip_active = true;466_wip_changed();467edited_point = PosVertex(-1, 1, cpoint);468canvas_item_editor->update_viewport();469hover_point = Vertex();470selected_point = Vertex(0);471edge_point = PosVertex();472return true;473} else {474const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");475476if (!_is_line() && wip.size() > 1 && xform.xform(wip[0]).distance_to(xform.xform(cpoint)) < grab_threshold) {477//wip closed478_wip_close();479480return true;481} else {482//add wip point483wip.push_back(cpoint);484_wip_changed();485edited_point = PosVertex(-1, wip.size(), cpoint);486selected_point = Vertex(wip.size() - 1);487canvas_item_editor->update_viewport();488return true;489}490}491} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && wip_active) {492_wip_cancel();493}494}495496// Center drag.497if (edit_origin_and_center) {498real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");499500if (mb->get_button_index() == MouseButton::LEFT) {501if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {502return false;503}504if (mb->is_pressed() && !center_drag) {505Vector2 center_point = xform.xform(_get_geometric_center());506if ((gpoint - center_point).length() < grab_threshold) {507pre_center_move_edit.clear();508int n_polygons = _get_polygon_count();509for (int i = 0; i < n_polygons; i++) {510pre_center_move_edit.push_back(_get_polygon(i));511}512center_drag_origin = cpoint;513center_drag = true;514return true;515}516} else if (center_drag) {517return _commit_drag();518}519} else if (mb->get_button_index() == MouseButton::RIGHT && center_drag) {520_commit_drag();521}522}523}524525Ref<InputEventMouseMotion> mm = p_event;526527if (mm.is_valid()) {528Vector2 gpoint = mm->get_position();529530if (center_drag) {531Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));532cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);533Vector2 delta = center_drag_origin - cpoint;534535int n_polygons = _get_polygon_count();536for (int i = 0; i < n_polygons; i++) {537const Vector<Vector2> &vertices = _get_polygon(i);538int n_points = vertices.size();539540Vector<Vector2> new_vertices;541new_vertices.resize(n_points);542for (int n = 0; n < n_points; n++) {543new_vertices.write[n] = vertices[n] - delta;544}545_set_polygon(i, new_vertices);546}547center_drag_origin = cpoint;548} else if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {549Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));550cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);551552//Move the point in a single axis. Should only work when editing a polygon and while holding shift.553if (mode == MODE_EDIT && mm->is_shift_pressed()) {554Vector2 old_point = pre_move_edit.get(selected_point.vertex);555if (Math::abs(cpoint.x - old_point.x) > Math::abs(cpoint.y - old_point.y)) {556cpoint.y = old_point.y;557} else {558cpoint.x = old_point.x;559}560}561562edited_point = PosVertex(edited_point, cpoint);563564if (!wip_active) {565Vector<Vector2> vertices = _get_polygon(edited_point.polygon);566ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);567vertices.write[edited_point.vertex] = cpoint - _get_offset(edited_point.polygon);568_set_polygon(edited_point.polygon, vertices);569}570571canvas_item_editor->update_viewport();572} else if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {573const PosVertex new_hover_point = closest_point(gpoint);574if (hover_point != new_hover_point) {575hover_point = new_hover_point;576canvas_item_editor->update_viewport();577}578579bool edge_hover = false;580if (!hover_point.valid()) {581const PosVertex on_edge_vertex = closest_edge_point(gpoint);582583if (on_edge_vertex.valid()) {584hover_point = Vertex();585edge_point = on_edge_vertex;586canvas_item_editor->update_viewport();587edge_hover = true;588}589}590591if (!edge_hover && edge_point.valid()) {592edge_point = PosVertex();593canvas_item_editor->update_viewport();594}595}596}597598Ref<InputEventKey> k = p_event;599600if (k.is_valid() && k->is_pressed()) {601if (k->get_keycode() == Key::KEY_DELETE || k->get_keycode() == Key::BACKSPACE) {602if (wip_active && selected_point.polygon == -1) {603if (wip.size() > selected_point.vertex) {604wip.remove_at(selected_point.vertex);605_wip_changed();606selected_point = wip.size() - 1;607canvas_item_editor->update_viewport();608return true;609}610} else {611const Vertex active_point = get_active_point();612613if (active_point.valid()) {614remove_point(active_point);615return true;616}617}618} else if (wip_active && k->get_keycode() == Key::ENTER) {619_wip_close();620} else if (wip_active && k->get_keycode() == Key::ESCAPE) {621_wip_cancel();622}623}624625return false;626}627628void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {629if (!_get_node()) {630return;631}632633if (!_get_node()->is_visible_in_tree()) {634return;635}636637Viewport *vp = _get_node()->get_viewport();638if (vp && !vp->is_visible_subviewport()) {639return;640}641642Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();643// All polygon points are sharp, so use the sharp handle icon644const Ref<Texture2D> handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));645const Ref<Texture2D> nhandle = get_editor_theme_icon(SNAME("EditorPathNullHandle"));646647Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));648int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));649const float outline_size = 4 * EDSCALE;650Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));651Color outline_color = font_color.inverted();652653const Vertex active_point = get_active_point();654const int n_polygons = _get_polygon_count();655const bool is_closed = !_is_line();656657if (edit_origin_and_center) {658const Vector2 ¢er = _get_geometric_center();659if (!center.is_zero_approx()) {660const Vector2 point = xform.xform(center);661p_overlay->draw_texture(nhandle, point - nhandle->get_size() * 0.5, Color(1, 1, 0.4));662Size2 lbl_size = font->get_string_size("c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);663p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);664p_overlay->draw_string(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);665}666{667const Vector2 point = xform.xform(Vector2());668p_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));669Size2 lbl_size = font->get_string_size("o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);670p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);671p_overlay->draw_string(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);672}673}674675for (int j = -1; j < n_polygons; j++) {676if (wip_active && wip_destructive && j != -1) {677continue;678}679680Vector<Vector2> points;681Vector2 offset;682683if (wip_active && j == edited_point.polygon) {684points = Variant(wip);685offset = Vector2(0, 0);686} else {687if (j == -1) {688continue;689}690points = _get_polygon(j);691offset = _get_offset(j);692}693694if (!wip_active && j == edited_point.polygon && EDITOR_GET("editors/polygon_editor/show_previous_outline")) {695const Color col = Color(0.5, 0.5, 0.5); // FIXME polygon->get_outline_color();696const int n = pre_move_edit.size();697for (int i = 0; i < n - (is_closed ? 0 : 1); i++) {698Vector2 p, p2;699p = pre_move_edit[i] + offset;700p2 = pre_move_edit[(i + 1) % n] + offset;701702Vector2 point = xform.xform(p);703Vector2 next_point = xform.xform(p2);704705p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));706}707}708709const int n_points = points.size();710const Color col = Color(1, 0.3, 0.1, 0.8);711712for (int i = 0; i < n_points; i++) {713const Vertex vertex(j, i);714715const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);716const Vector2 point = xform.xform(p);717718if (is_closed || i < n_points - 1) {719Vector2 p2;720if (j == edited_point.polygon &&721((wip_active && i == n_points - 1) || (((i + 1) % n_points) == edited_point.vertex))) {722p2 = edited_point.pos;723} else {724p2 = points[(i + 1) % n_points] + offset;725}726727const Vector2 next_point = xform.xform(p2);728p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));729}730}731732for (int i = 0; i < n_points; i++) {733const Vertex vertex(j, i);734735const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);736const Vector2 point = xform.xform(p);737738const Color overlay_modulate = vertex == active_point ? Color(0.4, 1, 1) : Color(1, 1, 1);739p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, overlay_modulate);740741if (vertex == hover_point) {742String num = String::num_int64(vertex.vertex);743Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);744p_overlay->draw_string_outline(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);745p_overlay->draw_string(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);746}747}748}749750if (edge_point.valid()) {751Ref<Texture2D> add_handle = get_editor_theme_icon(SNAME("EditorHandleAdd"));752p_overlay->draw_texture(add_handle, edge_point.pos - add_handle->get_size() * 0.5);753}754}755756void AbstractPolygon2DEditor::set_edit_origin_and_center(bool p_enabled) {757edit_origin_and_center = p_enabled;758if (button_center) {759button_center->set_visible(edit_origin_and_center);760}761}762763void AbstractPolygon2DEditor::edit(Node *p_polygon) {764if (!canvas_item_editor) {765canvas_item_editor = CanvasItemEditor::get_singleton();766}767768if (p_polygon) {769_set_node(p_polygon);770771// Enable the pencil tool if the polygon is empty.772if (_is_empty()) {773_menu_option(MODE_CREATE);774} else {775_menu_option(MODE_EDIT);776}777778wip.clear();779wip_active = false;780edited_point = PosVertex();781hover_point = Vertex();782selected_point = Vertex();783center_drag = false;784} else {785_set_node(nullptr);786}787788canvas_item_editor->update_viewport();789}790791void AbstractPolygon2DEditor::remove_point(const Vertex &p_vertex) {792EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();793Vector<Vector2> vertices = _get_polygon(p_vertex.polygon);794795if (vertices.size() > (_is_line() ? 2 : 3)) {796vertices.remove_at(p_vertex.vertex);797798undo_redo->create_action(TTR("Edit Polygon (Remove Point)"));799_action_set_polygon(p_vertex.polygon, vertices);800_commit_action();801} else {802undo_redo->create_action(TTR("Remove Polygon And Point"));803_action_remove_polygon(p_vertex.polygon);804_commit_action();805}806807if (_is_empty()) {808_menu_option(MODE_CREATE);809}810811hover_point = Vertex();812if (selected_point == p_vertex) {813selected_point = Vertex();814}815}816817AbstractPolygon2DEditor::Vertex AbstractPolygon2DEditor::get_active_point() const {818return hover_point.valid() ? hover_point : selected_point;819}820821AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_point(const Vector2 &p_pos) const {822const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");823824const int n_polygons = _get_polygon_count();825const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();826827PosVertex closest;828real_t closest_dist = 1e10;829830for (int j = 0; j < n_polygons; j++) {831Vector<Vector2> points = _get_polygon(j);832const Vector2 offset = _get_offset(j);833const int n_points = points.size();834835for (int i = 0; i < n_points; i++) {836Vector2 cp = xform.xform(points[i] + offset);837838real_t d = cp.distance_to(p_pos);839if (d < closest_dist && d < grab_threshold) {840closest_dist = d;841closest = PosVertex(j, i, cp);842}843}844}845846return closest;847}848849AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(const Vector2 &p_pos) const {850const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");851const real_t eps = grab_threshold * 2;852const real_t eps2 = eps * eps;853854const int n_polygons = _get_polygon_count();855const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();856857PosVertex closest;858real_t closest_dist = 1e10;859860for (int j = 0; j < n_polygons; j++) {861Vector<Vector2> points = _get_polygon(j);862const Vector2 offset = _get_offset(j);863const int n_points = points.size();864const int n_segments = n_points - (_is_line() ? 1 : 0);865866for (int i = 0; i < n_segments; i++) {867const Vector2 segment_a = xform.xform(points[i] + offset);868const Vector2 segment_b = xform.xform(points[(i + 1) % n_points] + offset);869870Vector2 cp = Geometry2D::get_closest_point_to_segment(p_pos, segment_a, segment_b);871872if (cp.distance_squared_to(segment_a) < eps2 || cp.distance_squared_to(segment_b) < eps2) {873continue; //not valid to reuse point874}875876real_t d = cp.distance_to(p_pos);877if (d < closest_dist && d < grab_threshold) {878closest_dist = d;879closest = PosVertex(j, i, cp);880}881}882}883884return closest;885}886887AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {888edited_point = PosVertex();889center_drag = false;890wip_destructive = p_wip_destructive;891892hover_point = Vertex();893selected_point = Vertex();894edge_point = PosVertex();895896button_create = memnew(Button);897button_create->set_theme_type_variation(SceneStringName(FlatButton));898button_create->set_accessibility_name(TTRC("Create Polygon Points"));899add_child(button_create);900button_create->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_CREATE));901button_create->set_toggle_mode(true);902903button_edit = memnew(Button);904button_edit->set_theme_type_variation(SceneStringName(FlatButton));905button_edit->set_accessibility_name(TTRC("Edit Polygon Points"));906add_child(button_edit);907button_edit->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_EDIT));908button_edit->set_toggle_mode(true);909910button_delete = memnew(Button);911button_delete->set_theme_type_variation(SceneStringName(FlatButton));912button_delete->set_accessibility_name(TTRC("Delete Polygon Points"));913add_child(button_delete);914button_delete->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE));915button_delete->set_toggle_mode(true);916917button_center = memnew(Button);918button_center->set_theme_type_variation(SceneStringName(FlatButton));919add_child(button_center);920button_center->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(CENTER_POLY));921button_center->set_visible(edit_origin_and_center);922923create_resource = memnew(ConfirmationDialog);924add_child(create_resource);925create_resource->set_ok_button_text(TTR("Create"));926}927928void AbstractPolygon2DEditorPlugin::edit(Object *p_object) {929Node *polygon_node = Object::cast_to<Node>(p_object);930polygon_editor->edit(polygon_node);931make_visible(polygon_node != nullptr);932}933934bool AbstractPolygon2DEditorPlugin::handles(Object *p_object) const {935return p_object->is_class(klass);936}937938void AbstractPolygon2DEditorPlugin::make_visible(bool p_visible) {939if (p_visible) {940polygon_editor->show();941} else {942polygon_editor->hide();943polygon_editor->edit(nullptr);944}945}946947AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, const String &p_class) :948polygon_editor(p_polygon_editor),949klass(p_class) {950CanvasItemEditor::get_singleton()->add_control_to_menu_panel(polygon_editor);951polygon_editor->hide();952}953954955