Path: blob/master/editor/scene/2d/polygon_2d_editor_plugin.cpp
9904 views
/**************************************************************************/1/* polygon_2d_editor_plugin.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 "polygon_2d_editor_plugin.h"3132#include "core/input/input_event.h"33#include "core/math/geometry_2d.h"34#include "editor/editor_node.h"35#include "editor/editor_undo_redo_manager.h"36#include "editor/gui/editor_bottom_panel.h"37#include "editor/gui/editor_zoom_widget.h"38#include "editor/scene/canvas_item_editor_plugin.h"39#include "editor/settings/editor_command_palette.h"40#include "editor/settings/editor_settings.h"41#include "editor/themes/editor_scale.h"42#include "scene/2d/skeleton_2d.h"43#include "scene/gui/check_box.h"44#include "scene/gui/dialogs.h"45#include "scene/gui/label.h"46#include "scene/gui/menu_button.h"47#include "scene/gui/panel.h"48#include "scene/gui/scroll_container.h"49#include "scene/gui/separator.h"50#include "scene/gui/slider.h"51#include "scene/gui/spin_box.h"52#include "scene/gui/split_container.h"53#include "scene/gui/view_panner.h"5455Node2D *Polygon2DEditor::_get_node() const {56return node;57}5859void Polygon2DEditor::_set_node(Node *p_polygon) {60CanvasItem *draw = Object::cast_to<CanvasItem>(canvas);61if (node) {62node->disconnect(SceneStringName(draw), callable_mp(draw, &CanvasItem::queue_redraw));63node->disconnect(SceneStringName(draw), callable_mp(this, &Polygon2DEditor::_update_available_modes));64}65node = Object::cast_to<Polygon2D>(p_polygon);66_update_polygon_editing_state();67canvas->queue_redraw();68if (node) {69canvas->set_texture_filter(node->get_texture_filter_in_tree());7071_update_bone_list();72_update_available_modes();73if (current_mode == MODE_MAX) {74_select_mode(MODE_POINTS); // Initialize when opening the first time.75}76if (previous_node != node) {77_center_view_on_draw();78}79previous_node = node;80// Whenever polygon gets redrawn, there's possible changes for the editor as well.81node->connect(SceneStringName(draw), callable_mp(draw, &CanvasItem::queue_redraw));82node->connect(SceneStringName(draw), callable_mp(this, &Polygon2DEditor::_update_available_modes));83}84}8586Vector2 Polygon2DEditor::_get_offset(int p_idx) const {87return node->get_offset();88}8990int Polygon2DEditor::_get_polygon_count() const {91if (node->get_internal_vertex_count() > 0) {92return 0; //do not edit if internal vertices exist93} else {94return 1;95}96}9798void Polygon2DEditor::_notification(int p_what) {99switch (p_what) {100case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {101if (!EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {102break;103}104[[fallthrough]];105}106case NOTIFICATION_ENTER_TREE: {107panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));108panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));109} break;110111case NOTIFICATION_READY: {112action_buttons[ACTION_CREATE]->set_button_icon(get_editor_theme_icon(SNAME("Edit")));113action_buttons[ACTION_CREATE_INTERNAL]->set_button_icon(get_editor_theme_icon(SNAME("EditInternal")));114action_buttons[ACTION_REMOVE_INTERNAL]->set_button_icon(get_editor_theme_icon(SNAME("RemoveInternal")));115action_buttons[ACTION_EDIT_POINT]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));116action_buttons[ACTION_MOVE]->set_button_icon(get_editor_theme_icon(SNAME("ToolMove")));117action_buttons[ACTION_ROTATE]->set_button_icon(get_editor_theme_icon(SNAME("ToolRotate")));118action_buttons[ACTION_SCALE]->set_button_icon(get_editor_theme_icon(SNAME("ToolScale")));119action_buttons[ACTION_ADD_POLYGON]->set_button_icon(get_editor_theme_icon(SNAME("Edit")));120action_buttons[ACTION_REMOVE_POLYGON]->set_button_icon(get_editor_theme_icon(SNAME("Close")));121action_buttons[ACTION_PAINT_WEIGHT]->set_button_icon(get_editor_theme_icon(SNAME("Bucket")));122action_buttons[ACTION_CLEAR_WEIGHT]->set_button_icon(get_editor_theme_icon(SNAME("Clear")));123124b_snap_grid->set_button_icon(get_editor_theme_icon(SNAME("Grid")));125b_snap_enable->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));126127vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE);128hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE);129// Avoid scrollbar overlapping.130Size2 hmin = hscroll->get_combined_minimum_size();131Size2 vmin = vscroll->get_combined_minimum_size();132hscroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -vmin.width);133vscroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -hmin.height);134[[fallthrough]];135}136case NOTIFICATION_THEME_CHANGED: {137canvas->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));138bone_scroll->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));139} break;140case NOTIFICATION_VISIBILITY_CHANGED: {141if (is_visible()) {142dock_button->show();143EditorNode::get_bottom_panel()->make_item_visible(polygon_edit);144} else {145dock_button->hide();146if (polygon_edit->is_visible_in_tree()) {147EditorNode::get_bottom_panel()->hide_bottom_panel();148}149}150} break;151}152}153154void Polygon2DEditor::_sync_bones() {155Skeleton2D *skeleton = nullptr;156if (!node->has_node(node->get_skeleton())) {157error->set_text(TTR("The skeleton property of the Polygon2D does not point to a Skeleton2D node"));158error->popup_centered();159} else {160Node *sn = node->get_node(node->get_skeleton());161skeleton = Object::cast_to<Skeleton2D>(sn);162}163164Array prev_bones = node->call("_get_bones");165node->clear_bones();166167if (!skeleton) {168error->set_text(TTR("The skeleton property of the Polygon2D does not point to a Skeleton2D node"));169error->popup_centered();170} else {171for (int i = 0; i < skeleton->get_bone_count(); i++) {172NodePath path = skeleton->get_path_to(skeleton->get_bone(i));173Vector<float> weights;174int wc = node->get_polygon().size();175176for (int j = 0; j < prev_bones.size(); j += 2) {177NodePath pvp = prev_bones[j];178Vector<float> pv = prev_bones[j + 1];179if (pvp == path && pv.size() == wc) {180weights = pv;181}182}183184if (weights.is_empty()) { //create them185weights.resize(wc);186float *w = weights.ptrw();187for (int j = 0; j < wc; j++) {188w[j] = 0.0;189}190}191192node->add_bone(path, weights);193}194}195196Array new_bones = node->call("_get_bones");197198EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();199undo_redo->create_action(TTR("Sync Bones"));200undo_redo->add_do_method(node, "_set_bones", new_bones);201undo_redo->add_undo_method(node, "_set_bones", prev_bones);202undo_redo->add_do_method(this, "_update_bone_list");203undo_redo->add_undo_method(this, "_update_bone_list");204undo_redo->commit_action();205}206207void Polygon2DEditor::_update_bone_list() {208NodePath selected;209while (bone_scroll_vb->get_child_count()) {210CheckBox *cb = Object::cast_to<CheckBox>(bone_scroll_vb->get_child(0));211if (cb && cb->is_pressed()) {212selected = cb->get_meta("bone_path");213}214memdelete(bone_scroll_vb->get_child(0));215}216217Ref<ButtonGroup> bg;218bg.instantiate();219for (int i = 0; i < node->get_bone_count(); i++) {220CheckBox *cb = memnew(CheckBox);221NodePath np = node->get_bone_path(i);222String name;223if (np.get_name_count()) {224name = np.get_name(np.get_name_count() - 1);225}226if (name.is_empty()) {227name = "Bone " + itos(i);228}229cb->set_text(name);230cb->set_button_group(bg);231cb->set_meta("bone_path", np);232cb->set_focus_mode(FOCUS_NONE);233bone_scroll_vb->add_child(cb);234235if (np == selected || bone_scroll_vb->get_child_count() < 2) {236cb->set_pressed(true);237}238239cb->connect(SceneStringName(pressed), callable_mp(this, &Polygon2DEditor::_bone_paint_selected).bind(i));240}241242canvas->queue_redraw();243}244245void Polygon2DEditor::_bone_paint_selected(int p_index) {246canvas->queue_redraw();247}248249void Polygon2DEditor::_select_mode(int p_mode) {250current_mode = Mode(p_mode);251mode_buttons[current_mode]->set_pressed(true);252for (int i = 0; i < ACTION_MAX; i++) {253action_buttons[i]->hide();254}255bone_scroll_main_vb->hide();256bone_paint_strength->hide();257bone_paint_radius->hide();258bone_paint_radius_label->hide();259switch (current_mode) {260case MODE_POINTS: {261action_buttons[ACTION_CREATE]->show();262action_buttons[ACTION_CREATE_INTERNAL]->show();263action_buttons[ACTION_REMOVE_INTERNAL]->show();264action_buttons[ACTION_EDIT_POINT]->show();265action_buttons[ACTION_MOVE]->show();266action_buttons[ACTION_ROTATE]->show();267action_buttons[ACTION_SCALE]->show();268269if (node->get_polygon().is_empty()) {270_set_action(ACTION_CREATE);271} else {272_set_action(ACTION_EDIT_POINT);273}274} break;275case MODE_POLYGONS: {276action_buttons[ACTION_ADD_POLYGON]->show();277action_buttons[ACTION_REMOVE_POLYGON]->show();278_set_action(ACTION_ADD_POLYGON);279} break;280case MODE_UV: {281if (node->get_uv().size() != node->get_polygon().size()) {282_edit_menu_option(MENU_POLYGON_TO_UV);283}284action_buttons[ACTION_EDIT_POINT]->show();285action_buttons[ACTION_MOVE]->show();286action_buttons[ACTION_ROTATE]->show();287action_buttons[ACTION_SCALE]->show();288_set_action(ACTION_EDIT_POINT);289} break;290case MODE_BONES: {291action_buttons[ACTION_PAINT_WEIGHT]->show();292action_buttons[ACTION_CLEAR_WEIGHT]->show();293_set_action(ACTION_PAINT_WEIGHT);294295bone_scroll_main_vb->show();296bone_paint_strength->show();297bone_paint_radius->show();298bone_paint_radius_label->show();299_update_bone_list();300bone_paint_pos = Vector2(-100000, -100000); // Send brush away when switching.301} break;302default:303break;304}305canvas->queue_redraw();306}307308void Polygon2DEditor::_edit_menu_option(int p_option) {309EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();310switch (p_option) {311case MENU_POLYGON_TO_UV: {312Vector<Vector2> points = node->get_polygon();313if (points.is_empty()) {314break;315}316Vector<Vector2> uvs = node->get_uv();317undo_redo->create_action(TTR("Create UV Map"));318undo_redo->add_do_method(node, "set_uv", points);319undo_redo->add_undo_method(node, "set_uv", uvs);320undo_redo->commit_action();321} break;322case MENU_UV_TO_POLYGON: {323Vector<Vector2> points = node->get_polygon();324Vector<Vector2> uvs = node->get_uv();325if (uvs.is_empty()) {326break;327}328329undo_redo->create_action(TTR("Create Polygon"));330undo_redo->add_do_method(node, "set_polygon", uvs);331undo_redo->add_undo_method(node, "set_polygon", points);332undo_redo->commit_action();333} break;334case MENU_UV_CLEAR: {335Vector<Vector2> uvs = node->get_uv();336if (uvs.is_empty()) {337break;338}339undo_redo->create_action(TTR("Create UV Map"));340undo_redo->add_do_method(node, "set_uv", Vector<Vector2>());341undo_redo->add_undo_method(node, "set_uv", uvs);342undo_redo->commit_action();343} break;344case MENU_GRID_SETTINGS: {345grid_settings->popup_centered();346} break;347}348}349350void Polygon2DEditor::_cancel_editing() {351if (is_creating) {352is_dragging = false;353is_creating = false;354node->set_uv(previous_uv);355node->set_polygon(previous_polygon);356node->set_internal_vertex_count(previous_internal_vertices);357node->set_vertex_colors(previous_colors);358node->call("_set_bones", previous_bones);359node->set_polygons(previous_polygons);360361_update_polygon_editing_state();362_update_available_modes();363} else if (is_dragging) {364is_dragging = false;365if (current_mode == MODE_UV) {366node->set_uv(editing_points);367} else if (current_mode == MODE_POINTS) {368node->set_polygon(editing_points);369}370}371372polygon_create.clear();373}374375void Polygon2DEditor::_update_polygon_editing_state() {376if (!_get_node()) {377return;378}379380if (node->get_internal_vertex_count() > 0) {381disable_polygon_editing(true, TTR("Polygon 2D has internal vertices, so it can no longer be edited in the viewport."));382} else {383disable_polygon_editing(false, String());384}385}386387void Polygon2DEditor::_commit_action() {388// Makes that undo/redoing actions made outside of the UV editor still affect its polygon.389EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();390undo_redo->add_do_method(CanvasItemEditor::get_singleton(), "update_viewport");391undo_redo->add_undo_method(CanvasItemEditor::get_singleton(), "update_viewport");392undo_redo->commit_action();393}394395void Polygon2DEditor::_set_use_snap(bool p_use) {396use_snap = p_use;397EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_enabled", p_use);398}399400void Polygon2DEditor::_set_show_grid(bool p_show) {401snap_show_grid = p_show;402EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "show_grid", p_show);403canvas->queue_redraw();404}405406void Polygon2DEditor::_set_snap_off_x(real_t p_val) {407snap_offset.x = p_val;408EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_offset", snap_offset);409canvas->queue_redraw();410}411412void Polygon2DEditor::_set_snap_off_y(real_t p_val) {413snap_offset.y = p_val;414EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_offset", snap_offset);415canvas->queue_redraw();416}417418void Polygon2DEditor::_set_snap_step_x(real_t p_val) {419snap_step.x = p_val;420EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_step", snap_step);421canvas->queue_redraw();422}423424void Polygon2DEditor::_set_snap_step_y(real_t p_val) {425snap_step.y = p_val;426EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_step", snap_step);427canvas->queue_redraw();428}429430void Polygon2DEditor::_set_action(int p_action) {431polygon_create.clear();432is_dragging = false;433is_creating = false;434435selected_action = Action(p_action);436for (int i = 0; i < ACTION_MAX; i++) {437action_buttons[i]->set_pressed(p_action == i);438}439canvas->queue_redraw();440}441442void Polygon2DEditor::_canvas_input(const Ref<InputEvent> &p_input) {443if (!_get_node()) {444return;445}446447if (panner->gui_input(p_input, canvas->get_global_rect())) {448accept_event();449return;450}451452Transform2D mtx;453mtx.columns[2] = -draw_offset * draw_zoom;454mtx.scale_basis(Vector2(draw_zoom, draw_zoom));455456EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();457458Ref<InputEventMouseButton> mb = p_input;459if (mb.is_valid()) {460if (mb->get_button_index() == MouseButton::LEFT) {461if (mb->is_pressed()) {462drag_from = snap_point(mb->get_position());463is_dragging = true;464if (current_mode == MODE_UV) {465editing_points = node->get_uv();466} else {467editing_points = node->get_polygon();468}469470current_action = selected_action;471if (current_action == ACTION_CREATE) {472if (!is_creating) {473editing_points.clear();474Vector2 tuv = mtx.affine_inverse().xform(snap_point(mb->get_position()));475editing_points.push_back(tuv);476create_to = tuv;477point_drag_index = 0;478drag_from = tuv;479is_dragging = true;480is_creating = true;481previous_uv = node->get_uv();482previous_polygon = node->get_polygon();483previous_internal_vertices = node->get_internal_vertex_count();484previous_colors = node->get_vertex_colors();485previous_bones = node->call("_get_bones");486previous_polygons = node->get_polygons();487disable_polygon_editing(false, String());488node->set_polygon(editing_points);489node->set_uv(editing_points);490node->set_internal_vertex_count(0);491492canvas->queue_redraw();493} else {494Vector2 tuv = mtx.affine_inverse().xform(snap_point(mb->get_position()));495496// Close the polygon if selected point is near start. Threshold for closing scaled by zoom level497if (editing_points.size() > 2 && tuv.distance_to(editing_points[0]) < (8 / draw_zoom)) {498undo_redo->create_action(TTR("Create Polygon & UV"));499undo_redo->add_do_method(node, "set_uv", node->get_uv());500undo_redo->add_undo_method(node, "set_uv", previous_uv);501undo_redo->add_do_method(node, "set_polygon", node->get_polygon());502undo_redo->add_undo_method(node, "set_polygon", previous_polygon);503undo_redo->add_do_method(node, "set_internal_vertex_count", 0);504undo_redo->add_undo_method(node, "set_internal_vertex_count", previous_internal_vertices);505undo_redo->add_do_method(node, "set_vertex_colors", Vector<Color>());506undo_redo->add_undo_method(node, "set_vertex_colors", previous_colors);507undo_redo->add_do_method(node, "clear_bones");508undo_redo->add_undo_method(node, "_set_bones", previous_bones);509undo_redo->add_do_method(this, "_update_polygon_editing_state");510undo_redo->add_undo_method(this, "_update_polygon_editing_state");511undo_redo->commit_action();512is_dragging = false;513is_creating = false;514515_update_available_modes();516_set_action(ACTION_EDIT_POINT);517_menu_option(MODE_EDIT);518} else {519editing_points.push_back(tuv);520point_drag_index = editing_points.size() - 1;521drag_from = tuv;522}523node->set_polygon(editing_points);524node->set_uv(editing_points);525}526527CanvasItemEditor::get_singleton()->update_viewport();528}529530if (current_action == ACTION_CREATE_INTERNAL) {531previous_uv = node->get_uv();532previous_polygon = node->get_polygon();533previous_colors = node->get_vertex_colors();534previous_bones = node->call("_get_bones");535int internal_vertices = node->get_internal_vertex_count();536537Vector2 pos = mtx.affine_inverse().xform(snap_point(mb->get_position()));538539previous_polygon.push_back(pos);540previous_uv.push_back(pos);541if (previous_colors.size()) {542previous_colors.push_back(Color(1, 1, 1));543}544545undo_redo->create_action(TTR("Create Internal Vertex"));546undo_redo->add_do_method(node, "set_uv", previous_uv);547undo_redo->add_undo_method(node, "set_uv", node->get_uv());548undo_redo->add_do_method(node, "set_polygon", previous_polygon);549undo_redo->add_undo_method(node, "set_polygon", node->get_polygon());550undo_redo->add_do_method(node, "set_vertex_colors", previous_colors);551undo_redo->add_undo_method(node, "set_vertex_colors", node->get_vertex_colors());552for (int i = 0; i < node->get_bone_count(); i++) {553Vector<float> bonew = node->get_bone_weights(i);554bonew.push_back(0);555undo_redo->add_do_method(node, "set_bone_weights", i, bonew);556undo_redo->add_undo_method(node, "set_bone_weights", i, node->get_bone_weights(i));557}558undo_redo->add_do_method(node, "set_internal_vertex_count", internal_vertices + 1);559undo_redo->add_undo_method(node, "set_internal_vertex_count", internal_vertices);560undo_redo->add_do_method(this, "_update_polygon_editing_state");561undo_redo->add_undo_method(this, "_update_polygon_editing_state");562undo_redo->commit_action();563}564565if (current_action == ACTION_REMOVE_INTERNAL) {566previous_uv = node->get_uv();567previous_polygon = node->get_polygon();568previous_colors = node->get_vertex_colors();569previous_bones = node->call("_get_bones");570int internal_vertices = node->get_internal_vertex_count();571572if (internal_vertices <= 0) {573return;574}575576const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");577int closest = -1;578real_t closest_dist = Math::INF;579580for (int i = editing_points.size() - 1; i >= editing_points.size() - internal_vertices && closest_dist >= 8; i--) {581Vector2 tuv = mtx.xform(previous_polygon[i]);582const real_t dist = tuv.distance_to(mb->get_position());583if (dist < grab_threshold && dist < closest_dist) {584closest = i;585closest_dist = dist;586}587}588589if (closest == -1) {590return;591}592593previous_polygon.remove_at(closest);594previous_uv.remove_at(closest);595if (previous_colors.size()) {596previous_colors.remove_at(closest);597}598599undo_redo->create_action(TTR("Remove Internal Vertex"));600undo_redo->add_do_method(node, "set_uv", previous_uv);601undo_redo->add_undo_method(node, "set_uv", node->get_uv());602undo_redo->add_do_method(node, "set_polygon", previous_polygon);603undo_redo->add_undo_method(node, "set_polygon", node->get_polygon());604undo_redo->add_do_method(node, "set_vertex_colors", previous_colors);605undo_redo->add_undo_method(node, "set_vertex_colors", node->get_vertex_colors());606for (int i = 0; i < node->get_bone_count(); i++) {607Vector<float> bonew = node->get_bone_weights(i);608bonew.remove_at(closest);609undo_redo->add_do_method(node, "set_bone_weights", i, bonew);610undo_redo->add_undo_method(node, "set_bone_weights", i, node->get_bone_weights(i));611}612undo_redo->add_do_method(node, "set_internal_vertex_count", internal_vertices - 1);613undo_redo->add_undo_method(node, "set_internal_vertex_count", internal_vertices);614undo_redo->add_do_method(this, "_update_polygon_editing_state");615undo_redo->add_undo_method(this, "_update_polygon_editing_state");616undo_redo->commit_action();617}618619if (current_action == ACTION_EDIT_POINT) {620if (mb->is_shift_pressed() && mb->is_command_or_control_pressed()) {621current_action = ACTION_SCALE;622} else if (mb->is_shift_pressed()) {623current_action = ACTION_MOVE;624} else if (mb->is_command_or_control_pressed()) {625current_action = ACTION_ROTATE;626}627}628629if (current_action == ACTION_EDIT_POINT) {630const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");631point_drag_index = -1;632real_t closest_dist = Math::INF;633for (int i = editing_points.size() - 1; i >= 0 && closest_dist >= 8; i--) {634const real_t dist = mtx.xform(editing_points[i]).distance_to(mb->get_position());635if (dist < grab_threshold && dist < closest_dist) {636drag_from = mb->get_position();637point_drag_index = i;638closest_dist = dist;639}640}641642if (point_drag_index == -1) {643is_dragging = false;644}645}646647if (current_action == ACTION_ADD_POLYGON) {648const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");649int closest = -1;650real_t closest_dist = Math::INF;651652for (int i = editing_points.size() - 1; i >= 0 && closest_dist >= 8; i--) {653Vector2 tuv = mtx.xform(editing_points[i]);654const real_t dist = tuv.distance_to(mb->get_position());655if (dist < grab_threshold && dist < closest_dist) {656closest = i;657closest_dist = dist;658}659}660661if (closest != -1) {662if (polygon_create.size() && closest == polygon_create[0]) {663//close664if (polygon_create.size() < 3) {665error->set_text(TTR("Invalid Polygon (need 3 different vertices)"));666error->popup_centered();667} else {668Array polygons = node->get_polygons();669polygons = polygons.duplicate(); //copy because its a reference670671//todo, could check whether it already exists?672polygons.push_back(polygon_create);673undo_redo->create_action(TTR("Add Custom Polygon"));674undo_redo->add_do_method(node, "set_polygons", polygons);675undo_redo->add_undo_method(node, "set_polygons", node->get_polygons());676undo_redo->commit_action();677}678679polygon_create.clear();680} else if (!polygon_create.has(closest)) {681//add temporarily if not exists682polygon_create.push_back(closest);683}684}685}686687if (current_action == ACTION_REMOVE_POLYGON) {688Array polygons = node->get_polygons();689polygons = polygons.duplicate(); //copy because its a reference690691int erase_index = -1;692for (int i = polygons.size() - 1; i >= 0; i--) {693Vector<int> points = polygons[i];694Vector<Vector2> polys;695polys.resize(points.size());696for (int j = 0; j < polys.size(); j++) {697int idx = points[j];698if (idx < 0 || idx >= editing_points.size()) {699continue;700}701polys.write[j] = mtx.xform(editing_points[idx]);702}703704if (Geometry2D::is_point_in_polygon(mb->get_position(), polys)) {705erase_index = i;706break;707}708}709710if (erase_index != -1) {711polygons.remove_at(erase_index);712undo_redo->create_action(TTR("Remove Custom Polygon"));713undo_redo->add_do_method(node, "set_polygons", polygons);714undo_redo->add_undo_method(node, "set_polygons", node->get_polygons());715undo_redo->commit_action();716}717}718719if (current_action == ACTION_PAINT_WEIGHT || current_action == ACTION_CLEAR_WEIGHT) {720int bone_selected = -1;721for (int i = 0; i < bone_scroll_vb->get_child_count(); i++) {722CheckBox *c = Object::cast_to<CheckBox>(bone_scroll_vb->get_child(i));723if (c && c->is_pressed()) {724bone_selected = i;725break;726}727}728729if (bone_selected != -1 && node->get_bone_weights(bone_selected).size() == editing_points.size()) {730prev_weights = node->get_bone_weights(bone_selected);731bone_painting = true;732bone_painting_bone = bone_selected;733}734}735} else {736if (is_dragging && !is_creating) {737if (current_mode == MODE_UV) {738undo_redo->create_action(TTR("Transform UV Map"));739undo_redo->add_do_method(node, "set_uv", node->get_uv());740undo_redo->add_undo_method(node, "set_uv", editing_points);741undo_redo->commit_action();742} else if (current_mode == MODE_POINTS) {743switch (current_action) {744case ACTION_EDIT_POINT:745case ACTION_MOVE:746case ACTION_ROTATE:747case ACTION_SCALE: {748undo_redo->create_action(TTR("Transform Polygon"));749undo_redo->add_do_method(node, "set_polygon", node->get_polygon());750undo_redo->add_undo_method(node, "set_polygon", editing_points);751undo_redo->commit_action();752} break;753default: {754} break;755}756}757758is_dragging = false;759}760761if (bone_painting) {762undo_redo->create_action(TTR("Paint Bone Weights"));763undo_redo->add_do_method(node, "set_bone_weights", bone_painting_bone, node->get_bone_weights(bone_painting_bone));764undo_redo->add_undo_method(node, "set_bone_weights", bone_painting_bone, prev_weights);765undo_redo->commit_action();766bone_painting = false;767}768}769} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {770_cancel_editing();771772if (bone_painting) {773node->set_bone_weights(bone_painting_bone, prev_weights);774}775776canvas->queue_redraw();777}778}779780Ref<InputEventMouseMotion> mm = p_input;781782if (mm.is_valid()) {783if (is_dragging) {784Vector2 uv_drag_to = mm->get_position();785uv_drag_to = snap_point(uv_drag_to);786Vector2 drag = mtx.affine_inverse().basis_xform(uv_drag_to - drag_from);787788switch (current_action) {789case ACTION_CREATE: {790if (is_creating) {791create_to = mtx.affine_inverse().xform(snap_point(mm->get_position()));792}793} break;794case ACTION_EDIT_POINT: {795Vector<Vector2> uv_new = editing_points;796uv_new.set(point_drag_index, uv_new[point_drag_index] + drag);797798if (current_mode == MODE_UV) {799node->set_uv(uv_new);800} else if (current_mode == MODE_POINTS) {801node->set_polygon(uv_new);802}803} break;804case ACTION_MOVE: {805Vector<Vector2> uv_new = editing_points;806for (int i = 0; i < uv_new.size(); i++) {807uv_new.set(i, uv_new[i] + drag);808}809810if (current_mode == MODE_UV) {811node->set_uv(uv_new);812} else if (current_mode == MODE_POINTS) {813node->set_polygon(uv_new);814}815} break;816case ACTION_ROTATE: {817Vector2 center;818Vector<Vector2> uv_new = editing_points;819820for (int i = 0; i < uv_new.size(); i++) {821center += editing_points[i];822}823center /= uv_new.size();824825real_t angle = (drag_from - mtx.xform(center)).normalized().angle_to((uv_drag_to - mtx.xform(center)).normalized());826827for (int i = 0; i < uv_new.size(); i++) {828Vector2 rel = editing_points[i] - center;829rel = rel.rotated(angle);830uv_new.set(i, center + rel);831}832833if (current_mode == MODE_UV) {834node->set_uv(uv_new);835} else if (current_mode == MODE_POINTS) {836node->set_polygon(uv_new);837}838} break;839case ACTION_SCALE: {840Vector2 center;841Vector<Vector2> uv_new = editing_points;842843for (int i = 0; i < uv_new.size(); i++) {844center += editing_points[i];845}846center /= uv_new.size();847848real_t from_dist = drag_from.distance_to(mtx.xform(center));849real_t to_dist = uv_drag_to.distance_to(mtx.xform(center));850if (from_dist < 2) {851break;852}853854real_t scale = to_dist / from_dist;855856for (int i = 0; i < uv_new.size(); i++) {857Vector2 rel = editing_points[i] - center;858rel = rel * scale;859uv_new.set(i, center + rel);860}861862if (current_mode == MODE_UV) {863node->set_uv(uv_new);864} else if (current_mode == MODE_POINTS) {865node->set_polygon(uv_new);866}867} break;868case ACTION_PAINT_WEIGHT:869case ACTION_CLEAR_WEIGHT: {870bone_paint_pos = mm->get_position();871} break;872default: {873}874}875876if (bone_painting) {877Vector<float> painted_weights = node->get_bone_weights(bone_painting_bone);878879{880int pc = painted_weights.size();881real_t amount = bone_paint_strength->get_value();882real_t radius = bone_paint_radius->get_value() * EDSCALE;883884if (selected_action == ACTION_CLEAR_WEIGHT) {885amount = -amount;886}887888float *w = painted_weights.ptrw();889const float *r = prev_weights.ptr();890const Vector2 *rv = editing_points.ptr();891892for (int i = 0; i < pc; i++) {893if (mtx.xform(rv[i]).distance_to(bone_paint_pos) < radius) {894w[i] = CLAMP(r[i] + amount, 0, 1);895}896}897}898899node->set_bone_weights(bone_painting_bone, painted_weights);900}901902canvas->queue_redraw();903CanvasItemEditor::get_singleton()->update_viewport();904} else if (polygon_create.size()) {905create_to = mtx.affine_inverse().xform(mm->get_position());906canvas->queue_redraw();907} else if (selected_action == ACTION_PAINT_WEIGHT || selected_action == ACTION_CLEAR_WEIGHT) {908bone_paint_pos = mm->get_position();909canvas->queue_redraw();910}911}912}913914void Polygon2DEditor::_update_available_modes() {915// Force point editing mode if there's no polygon yet.916if (node->get_polygon().is_empty()) {917if (current_mode != MODE_POINTS) {918_select_mode(MODE_POINTS);919}920mode_buttons[MODE_UV]->set_disabled(true);921mode_buttons[MODE_POLYGONS]->set_disabled(true);922mode_buttons[MODE_BONES]->set_disabled(true);923} else {924mode_buttons[MODE_UV]->set_disabled(false);925mode_buttons[MODE_POLYGONS]->set_disabled(false);926mode_buttons[MODE_BONES]->set_disabled(false);927}928}929930void Polygon2DEditor::_center_view() {931Size2 texture_size;932if (node->get_texture().is_valid()) {933texture_size = node->get_texture()->get_size();934Vector2 zoom_factor = (canvas->get_size() - Vector2(1, 1) * 50 * EDSCALE) / texture_size;935zoom_widget->set_zoom(MIN(zoom_factor.x, zoom_factor.y));936} else {937zoom_widget->set_zoom(EDSCALE);938}939// Recalculate scroll limits.940_update_zoom_and_pan(false);941942Size2 offset = (texture_size - canvas->get_size() / draw_zoom) / 2;943hscroll->set_value_no_signal(offset.x);944vscroll->set_value_no_signal(offset.y);945_update_zoom_and_pan(false);946}947948void Polygon2DEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {949hscroll->set_value_no_signal(hscroll->get_value() - p_scroll_vec.x / draw_zoom);950vscroll->set_value_no_signal(vscroll->get_value() - p_scroll_vec.y / draw_zoom);951_update_zoom_and_pan(false);952}953954void Polygon2DEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {955zoom_widget->set_zoom(draw_zoom * p_zoom_factor);956draw_offset += p_origin / draw_zoom - p_origin / zoom_widget->get_zoom();957hscroll->set_value_no_signal(draw_offset.x);958vscroll->set_value_no_signal(draw_offset.y);959_update_zoom_and_pan(false);960}961962void Polygon2DEditor::_update_zoom_and_pan(bool p_zoom_at_center) {963draw_offset = Vector2(hscroll->get_value(), vscroll->get_value());964real_t previous_zoom = draw_zoom;965draw_zoom = zoom_widget->get_zoom();966if (p_zoom_at_center) {967Vector2 center = canvas->get_size() / 2;968draw_offset += center / previous_zoom - center / draw_zoom;969}970971Point2 min_corner;972Point2 max_corner;973if (node->get_texture().is_valid()) {974max_corner += node->get_texture()->get_size();975}976977Vector<Vector2> points = current_mode == MODE_UV ? node->get_uv() : node->get_polygon();978for (int i = 0; i < points.size(); i++) {979min_corner = min_corner.min(points[i]);980max_corner = max_corner.max(points[i]);981}982Size2 page_size = canvas->get_size() / draw_zoom;983Vector2 margin = Vector2(50, 50) * EDSCALE / draw_zoom;984min_corner -= page_size - margin;985max_corner += page_size - margin;986987hscroll->set_block_signals(true);988hscroll->set_min(min_corner.x);989hscroll->set_max(max_corner.x);990hscroll->set_page(page_size.x);991hscroll->set_value(draw_offset.x);992hscroll->set_block_signals(false);993994vscroll->set_block_signals(true);995vscroll->set_min(min_corner.y);996vscroll->set_max(max_corner.y);997vscroll->set_page(page_size.y);998vscroll->set_value(draw_offset.y);999vscroll->set_block_signals(false);10001001canvas->queue_redraw();1002}10031004void Polygon2DEditor::_center_view_on_draw(bool p_enabled) {1005if (center_view_on_draw == p_enabled) {1006return;1007}1008center_view_on_draw = p_enabled;1009if (center_view_on_draw) {1010// Ensure that the view is centered even if the canvas is redrawn multiple times in the frame.1011get_tree()->connect("process_frame", callable_mp(this, &Polygon2DEditor::_center_view_on_draw).bind(false), CONNECT_ONE_SHOT);1012}1013}10141015void Polygon2DEditor::_canvas_draw() {1016if (!polygon_edit->is_visible() || !_get_node()) {1017return;1018}1019if (center_view_on_draw) {1020_center_view();1021}10221023Ref<Texture2D> base_tex = node->get_texture();10241025String warning;10261027Transform2D mtx;1028mtx.columns[2] = -draw_offset * draw_zoom;1029mtx.scale_basis(Vector2(draw_zoom, draw_zoom));10301031// Draw texture as a background if editing uvs or no uv mapping exist.1032if (current_mode == MODE_UV || selected_action == ACTION_CREATE || node->get_polygon().is_empty() || node->get_uv().size() != node->get_polygon().size()) {1033if (base_tex.is_valid()) {1034Transform2D texture_transform = Transform2D(node->get_texture_rotation(), node->get_texture_offset());1035texture_transform.scale(node->get_texture_scale());1036texture_transform.affine_invert();1037RS::get_singleton()->canvas_item_add_set_transform(canvas->get_canvas_item(), mtx * texture_transform);1038canvas->draw_texture(base_tex, Point2());1039RS::get_singleton()->canvas_item_add_set_transform(canvas->get_canvas_item(), Transform2D());1040}1041preview_polygon->hide();1042} else {1043preview_polygon->set_transform(mtx);1044// Keep in sync with newly added Polygon2D properties (when relevant).1045preview_polygon->set_texture(node->get_texture());1046preview_polygon->set_texture_offset(node->get_texture_offset());1047preview_polygon->set_texture_rotation(node->get_texture_rotation());1048preview_polygon->set_texture_scale(node->get_texture_scale());1049preview_polygon->set_texture_filter(node->get_texture_filter_in_tree());1050preview_polygon->set_texture_repeat(node->get_texture_repeat_in_tree());1051preview_polygon->set_polygon(node->get_polygon());1052preview_polygon->set_uv(node->get_uv());1053preview_polygon->set_invert(node->get_invert());1054preview_polygon->set_invert_border(node->get_invert_border());1055preview_polygon->set_internal_vertex_count(node->get_internal_vertex_count());1056if (selected_action == ACTION_ADD_POLYGON) {1057preview_polygon->set_polygons(Array());1058} else {1059preview_polygon->set_polygons(node->get_polygons());1060}1061preview_polygon->show();1062}10631064if (snap_show_grid) {1065Color grid_color = Color(1.0, 1.0, 1.0, 0.15);1066Size2 s = canvas->get_size();1067int last_cell = 0;10681069if (snap_step.x != 0) {1070for (int i = 0; i < s.width; i++) {1071int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(i, 0)).x - snap_offset.x) / snap_step.x));1072if (i == 0) {1073last_cell = cell;1074}1075if (last_cell != cell) {1076canvas->draw_line(Point2(i, 0), Point2(i, s.height), grid_color, Math::round(EDSCALE));1077}1078last_cell = cell;1079}1080}10811082if (snap_step.y != 0) {1083for (int i = 0; i < s.height; i++) {1084int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(0, i)).y - snap_offset.y) / snap_step.y));1085if (i == 0) {1086last_cell = cell;1087}1088if (last_cell != cell) {1089canvas->draw_line(Point2(0, i), Point2(s.width, i), grid_color, Math::round(EDSCALE));1090}1091last_cell = cell;1092}1093}1094}10951096Array polygons = node->get_polygons();10971098Vector<Vector2> uvs;1099if (current_mode == MODE_UV) {1100uvs = node->get_uv();1101} else {1102uvs = node->get_polygon();1103}11041105const float *weight_r = nullptr;11061107if (current_mode == MODE_BONES) {1108int bone_selected = -1;1109for (int i = 0; i < bone_scroll_vb->get_child_count(); i++) {1110CheckBox *c = Object::cast_to<CheckBox>(bone_scroll_vb->get_child(i));1111if (c && c->is_pressed()) {1112bone_selected = i;1113break;1114}1115}11161117if (bone_selected != -1 && node->get_bone_weights(bone_selected).size() == uvs.size()) {1118weight_r = node->get_bone_weights(bone_selected).ptr();1119}1120}11211122// All UV points are sharp, so use the sharp handle icon1123Ref<Texture2D> handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));11241125Color poly_line_color = Color(0.9, 0.5, 0.5);1126if (polygons.size() || polygon_create.size()) {1127poly_line_color.a *= 0.25;1128}1129Color polygon_line_color = Color(0.5, 0.5, 0.9);1130Color polygon_fill_color = polygon_line_color;1131polygon_fill_color.a *= 0.5;1132Color prev_color = Color(0.5, 0.5, 0.5);11331134int uv_draw_max = uvs.size();11351136uv_draw_max -= node->get_internal_vertex_count();1137if (uv_draw_max < 0) {1138uv_draw_max = 0;1139}11401141for (int i = 0; i < uvs.size(); i++) {1142int next = uv_draw_max > 0 ? (i + 1) % uv_draw_max : 0;11431144if (i < uv_draw_max && is_dragging && current_action == ACTION_EDIT_POINT && EDITOR_GET("editors/polygon_editor/show_previous_outline")) {1145canvas->draw_line(mtx.xform(editing_points[i]), mtx.xform(editing_points[next]), prev_color, Math::round(EDSCALE));1146}11471148Vector2 next_point = uvs[next];1149if (is_creating && i == uvs.size() - 1) {1150next_point = create_to;1151}1152if (i < uv_draw_max) { // If using or creating polygons, do not show outline (will show polygons instead).1153canvas->draw_line(mtx.xform(uvs[i]), mtx.xform(next_point), poly_line_color, Math::round(EDSCALE));1154}1155}11561157for (int i = 0; i < polygons.size(); i++) {1158Vector<int> points = polygons[i];1159Vector<Vector2> polypoints;1160for (int j = 0; j < points.size(); j++) {1161int next = (j + 1) % points.size();11621163int idx = points[j];1164int idx_next = points[next];1165if (idx < 0 || idx >= uvs.size()) {1166continue;1167}1168polypoints.push_back(mtx.xform(uvs[idx]));11691170if (idx_next < 0 || idx_next >= uvs.size()) {1171continue;1172}1173canvas->draw_line(mtx.xform(uvs[idx]), mtx.xform(uvs[idx_next]), polygon_line_color, Math::round(EDSCALE));1174}1175if (points.size() >= 3) {1176canvas->draw_colored_polygon(polypoints, polygon_fill_color);1177}1178}11791180for (int i = 0; i < uvs.size(); i++) {1181if (weight_r) {1182Vector2 draw_pos = mtx.xform(uvs[i]);1183float weight = weight_r[i];1184canvas->draw_rect(Rect2(draw_pos - Vector2(2, 2) * EDSCALE, Vector2(5, 5) * EDSCALE), Color(weight, weight, weight, 1.0), Math::round(EDSCALE));1185} else {1186if (i < uv_draw_max) {1187canvas->draw_texture(handle, mtx.xform(uvs[i]) - handle->get_size() * 0.5);1188} else {1189// Internal vertex1190canvas->draw_texture(handle, mtx.xform(uvs[i]) - handle->get_size() * 0.5, Color(0.6, 0.8, 1));1191}1192}1193}11941195if (polygon_create.size()) {1196for (int i = 0; i < polygon_create.size(); i++) {1197Vector2 from = uvs[polygon_create[i]];1198Vector2 to = (i + 1) < polygon_create.size() ? uvs[polygon_create[i + 1]] : create_to;1199canvas->draw_line(mtx.xform(from), mtx.xform(to), polygon_line_color, Math::round(EDSCALE));1200}1201}12021203if (selected_action == ACTION_PAINT_WEIGHT || selected_action == ACTION_CLEAR_WEIGHT) {1204NodePath bone_path;1205for (int i = 0; i < bone_scroll_vb->get_child_count(); i++) {1206CheckBox *c = Object::cast_to<CheckBox>(bone_scroll_vb->get_child(i));1207if (c && c->is_pressed()) {1208bone_path = node->get_bone_path(i);1209break;1210}1211}12121213//draw skeleton1214NodePath skeleton_path = node->get_skeleton();1215Skeleton2D *skeleton = Object::cast_to<Skeleton2D>(node->get_node_or_null(skeleton_path));1216if (skeleton) {1217Transform2D skeleton_xform = node->get_global_transform().affine_inverse().translated(-node->get_offset()) * skeleton->get_global_transform();1218for (int i = 0; i < skeleton->get_bone_count(); i++) {1219Bone2D *bone = skeleton->get_bone(i);1220if (bone->get_rest() == Transform2D(0, 0, 0, 0, 0, 0)) {1221continue; //not set1222}12231224bool current = bone_path == skeleton->get_path_to(bone);12251226bool found_child = false;12271228for (int j = 0; j < bone->get_child_count(); j++) {1229Bone2D *n = Object::cast_to<Bone2D>(bone->get_child(j));1230if (!n) {1231continue;1232}12331234found_child = true;12351236Transform2D bone_xform = skeleton_xform * bone->get_skeleton_rest();1237Transform2D endpoint_xform = bone_xform * n->get_transform();12381239Color color = current ? Color(1, 1, 1) : Color(0.5, 0.5, 0.5);1240canvas->draw_line(mtx.xform(bone_xform.get_origin()), mtx.xform(endpoint_xform.get_origin()), Color(0, 0, 0), Math::round((current ? 5 : 4) * EDSCALE));1241canvas->draw_line(mtx.xform(bone_xform.get_origin()), mtx.xform(endpoint_xform.get_origin()), color, Math::round((current ? 3 : 2) * EDSCALE));1242}12431244if (!found_child) {1245//draw normally1246Transform2D bone_xform = skeleton_xform * bone->get_skeleton_rest();1247Transform2D endpoint_xform = bone_xform * Transform2D(0, Vector2(bone->get_length(), 0)).rotated(bone->get_bone_angle());12481249Color color = current ? Color(1, 1, 1) : Color(0.5, 0.5, 0.5);1250canvas->draw_line(mtx.xform(bone_xform.get_origin()), mtx.xform(endpoint_xform.get_origin()), Color(0, 0, 0), Math::round((current ? 5 : 4) * EDSCALE));1251canvas->draw_line(mtx.xform(bone_xform.get_origin()), mtx.xform(endpoint_xform.get_origin()), color, Math::round((current ? 3 : 2) * EDSCALE));1252}1253}1254}12551256//draw paint circle1257canvas->draw_circle(bone_paint_pos, bone_paint_radius->get_value() * EDSCALE, Color(1, 1, 1, 0.1));1258}1259}12601261void Polygon2DEditor::_bind_methods() {1262ClassDB::bind_method(D_METHOD("_update_bone_list"), &Polygon2DEditor::_update_bone_list);1263ClassDB::bind_method(D_METHOD("_update_polygon_editing_state"), &Polygon2DEditor::_update_polygon_editing_state);1264}12651266Vector2 Polygon2DEditor::snap_point(Vector2 p_target) const {1267if (use_snap) {1268p_target.x = Math::snap_scalar((snap_offset.x - draw_offset.x) * draw_zoom, snap_step.x * draw_zoom, p_target.x);1269p_target.y = Math::snap_scalar((snap_offset.y - draw_offset.y) * draw_zoom, snap_step.y * draw_zoom, p_target.y);1270}12711272return p_target;1273}12741275Polygon2DEditor::Polygon2DEditor() {1276snap_offset = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_offset", Vector2());1277// A power-of-two value works better as a default grid size.1278snap_step = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_step", Vector2(8, 8));1279use_snap = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_enabled", false);1280snap_show_grid = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "show_grid", false);12811282selected_action = ACTION_EDIT_POINT;1283polygon_edit = memnew(VBoxContainer);1284HBoxContainer *toolbar = memnew(HBoxContainer);12851286Ref<ButtonGroup> mode_button_group;1287mode_button_group.instantiate();1288for (int i = 0; i < MODE_MAX; i++) {1289mode_buttons[i] = memnew(Button);1290toolbar->add_child(mode_buttons[i]);1291mode_buttons[i]->set_toggle_mode(true);1292mode_buttons[i]->set_button_group(mode_button_group);1293mode_buttons[i]->connect(SceneStringName(pressed), callable_mp(this, &Polygon2DEditor::_select_mode).bind(i));1294}1295mode_buttons[MODE_POINTS]->set_text(TTR("Points"));1296mode_buttons[MODE_POLYGONS]->set_text(TTR("Polygons"));1297mode_buttons[MODE_UV]->set_text(TTR("UV"));1298mode_buttons[MODE_BONES]->set_text(TTR("Bones"));12991300toolbar->add_child(memnew(VSeparator));13011302polygon_edit->add_child(toolbar);1303for (int i = 0; i < ACTION_MAX; i++) {1304action_buttons[i] = memnew(Button);1305action_buttons[i]->set_theme_type_variation(SceneStringName(FlatButton));1306action_buttons[i]->set_toggle_mode(true);1307toolbar->add_child(action_buttons[i]);1308action_buttons[i]->connect(SceneStringName(pressed), callable_mp(this, &Polygon2DEditor::_set_action).bind(i));1309action_buttons[i]->set_focus_mode(FOCUS_ACCESSIBILITY);1310}13111312action_buttons[ACTION_CREATE]->set_tooltip_text(TTR("Create Polygon"));1313action_buttons[ACTION_CREATE_INTERNAL]->set_tooltip_text(TTR("Create Internal Vertex"));1314action_buttons[ACTION_REMOVE_INTERNAL]->set_tooltip_text(TTR("Remove Internal Vertex"));1315Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;1316// TRANSLATORS: %s is Control or Command key name.1317action_buttons[ACTION_EDIT_POINT]->set_tooltip_text(TTR("Move Points") + "\n" + vformat(TTR("%s: Rotate"), find_keycode_name(key)) + "\n" + TTR("Shift: Move All") + "\n" + vformat(TTR("%s + Shift: Scale"), find_keycode_name(key)));1318action_buttons[ACTION_MOVE]->set_tooltip_text(TTR("Move Polygon"));1319action_buttons[ACTION_ROTATE]->set_tooltip_text(TTR("Rotate Polygon"));1320action_buttons[ACTION_SCALE]->set_tooltip_text(TTR("Scale Polygon"));1321action_buttons[ACTION_ADD_POLYGON]->set_tooltip_text(TTR("Create a custom polygon. Enables custom polygon rendering."));1322action_buttons[ACTION_REMOVE_POLYGON]->set_tooltip_text(TTR("Remove a custom polygon. If none remain, custom polygon rendering is disabled."));1323action_buttons[ACTION_PAINT_WEIGHT]->set_tooltip_text(TTR("Paint weights with specified intensity."));1324action_buttons[ACTION_CLEAR_WEIGHT]->set_tooltip_text(TTR("Unpaint weights with specified intensity."));13251326action_buttons[ACTION_CREATE]->set_accessibility_name(TTRC("Create Polygon"));1327action_buttons[ACTION_CREATE_INTERNAL]->set_accessibility_name(TTRC("Create Internal Vertex"));1328action_buttons[ACTION_REMOVE_INTERNAL]->set_accessibility_name(TTRC("Remove Internal Vertex"));1329action_buttons[ACTION_EDIT_POINT]->set_accessibility_name(TTRC("Move Points"));1330action_buttons[ACTION_MOVE]->set_accessibility_name(TTRC("Move Polygon"));1331action_buttons[ACTION_ROTATE]->set_accessibility_name(TTRC("Rotate Polygon"));1332action_buttons[ACTION_SCALE]->set_accessibility_name(TTRC("Scale Polygon"));1333action_buttons[ACTION_ADD_POLYGON]->set_accessibility_name(TTRC("Create a custom polygon. Enables custom polygon rendering."));1334action_buttons[ACTION_REMOVE_POLYGON]->set_accessibility_name(TTRC("Remove a custom polygon. If none remain, custom polygon rendering is disabled."));1335action_buttons[ACTION_PAINT_WEIGHT]->set_accessibility_name(TTRC("Paint weights with specified intensity."));1336action_buttons[ACTION_CLEAR_WEIGHT]->set_accessibility_name(TTRC("Unpaint weights with specified intensity."));13371338bone_paint_strength = memnew(HSlider);1339toolbar->add_child(bone_paint_strength);1340bone_paint_strength->set_custom_minimum_size(Size2(75 * EDSCALE, 0));1341bone_paint_strength->set_v_size_flags(SIZE_SHRINK_CENTER);1342bone_paint_strength->set_min(0);1343bone_paint_strength->set_max(1);1344bone_paint_strength->set_step(0.01);1345bone_paint_strength->set_value(0.5);1346bone_paint_strength->set_accessibility_name(TTRC("Strength"));13471348bone_paint_radius_label = memnew(Label(TTR("Radius:")));1349toolbar->add_child(bone_paint_radius_label);1350bone_paint_radius = memnew(SpinBox);1351toolbar->add_child(bone_paint_radius);13521353bone_paint_radius->set_min(1);1354bone_paint_radius->set_max(100);1355bone_paint_radius->set_step(1);1356bone_paint_radius->set_value(32);1357bone_paint_radius->set_accessibility_name(TTRC("Radius:"));13581359HSplitContainer *uv_main_hsc = memnew(HSplitContainer);1360polygon_edit->add_child(uv_main_hsc);1361uv_main_hsc->set_v_size_flags(SIZE_EXPAND_FILL);13621363canvas_background = memnew(Panel);1364uv_main_hsc->add_child(canvas_background);1365canvas_background->set_h_size_flags(SIZE_EXPAND_FILL);1366canvas_background->set_custom_minimum_size(Size2(200, 200) * EDSCALE);1367canvas_background->set_clip_contents(true);13681369preview_polygon = memnew(Polygon2D);1370canvas_background->add_child(preview_polygon);13711372canvas = memnew(Control);1373canvas_background->add_child(canvas);1374canvas->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);13751376Control *space = memnew(Control);1377toolbar->add_child(space);1378space->set_h_size_flags(SIZE_EXPAND_FILL);13791380edit_menu = memnew(MenuButton);1381toolbar->add_child(edit_menu);1382edit_menu->set_flat(false);1383edit_menu->set_theme_type_variation("FlatMenuButton");1384edit_menu->set_text(TTR("Edit"));1385edit_menu->get_popup()->add_item(TTR("Copy Polygon to UV"), MENU_POLYGON_TO_UV);1386edit_menu->get_popup()->add_item(TTR("Copy UV to Polygon"), MENU_UV_TO_POLYGON);1387edit_menu->get_popup()->add_separator();1388edit_menu->get_popup()->add_item(TTR("Clear UV"), MENU_UV_CLEAR);1389edit_menu->get_popup()->add_separator();1390edit_menu->get_popup()->add_item(TTR("Grid Settings"), MENU_GRID_SETTINGS);1391edit_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Polygon2DEditor::_edit_menu_option));13921393toolbar->add_child(memnew(VSeparator));13941395b_snap_enable = memnew(Button);1396b_snap_enable->set_theme_type_variation(SceneStringName(FlatButton));1397toolbar->add_child(b_snap_enable);1398b_snap_enable->set_text(TTR("Snap"));1399b_snap_enable->set_focus_mode(FOCUS_ACCESSIBILITY);1400b_snap_enable->set_toggle_mode(true);1401b_snap_enable->set_pressed(use_snap);1402b_snap_enable->set_tooltip_text(TTR("Enable Snap"));1403b_snap_enable->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_use_snap));14041405b_snap_grid = memnew(Button);1406b_snap_grid->set_theme_type_variation(SceneStringName(FlatButton));1407toolbar->add_child(b_snap_grid);1408b_snap_grid->set_text(TTR("Grid"));1409b_snap_grid->set_focus_mode(FOCUS_ACCESSIBILITY);1410b_snap_grid->set_toggle_mode(true);1411b_snap_grid->set_pressed(snap_show_grid);1412b_snap_grid->set_tooltip_text(TTR("Show Grid"));1413b_snap_grid->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_show_grid));14141415grid_settings = memnew(AcceptDialog);1416grid_settings->set_title(TTR("Configure Grid:"));1417polygon_edit->add_child(grid_settings);1418VBoxContainer *grid_settings_vb = memnew(VBoxContainer);1419grid_settings->add_child(grid_settings_vb);14201421SpinBox *sb_off_x = memnew(SpinBox);1422sb_off_x->set_min(-256);1423sb_off_x->set_max(256);1424sb_off_x->set_step(1);1425sb_off_x->set_value(snap_offset.x);1426sb_off_x->set_suffix("px");1427sb_off_x->connect(SceneStringName(value_changed), callable_mp(this, &Polygon2DEditor::_set_snap_off_x));1428sb_off_x->set_accessibility_name(TTRC("Grid Offset X:"));1429grid_settings_vb->add_margin_child(TTR("Grid Offset X:"), sb_off_x);14301431SpinBox *sb_off_y = memnew(SpinBox);1432sb_off_y->set_min(-256);1433sb_off_y->set_max(256);1434sb_off_y->set_step(1);1435sb_off_y->set_value(snap_offset.y);1436sb_off_y->set_suffix("px");1437sb_off_y->connect(SceneStringName(value_changed), callable_mp(this, &Polygon2DEditor::_set_snap_off_y));1438sb_off_y->set_accessibility_name(TTRC("Grid Offset Y:"));1439grid_settings_vb->add_margin_child(TTR("Grid Offset Y:"), sb_off_y);14401441SpinBox *sb_step_x = memnew(SpinBox);1442sb_step_x->set_min(-256);1443sb_step_x->set_max(256);1444sb_step_x->set_step(1);1445sb_step_x->set_value(snap_step.x);1446sb_step_x->set_suffix("px");1447sb_step_x->connect(SceneStringName(value_changed), callable_mp(this, &Polygon2DEditor::_set_snap_step_x));1448sb_step_x->set_accessibility_name(TTRC("Grid Step X:"));1449grid_settings_vb->add_margin_child(TTR("Grid Step X:"), sb_step_x);14501451SpinBox *sb_step_y = memnew(SpinBox);1452sb_step_y->set_min(-256);1453sb_step_y->set_max(256);1454sb_step_y->set_step(1);1455sb_step_y->set_value(snap_step.y);1456sb_step_y->set_suffix("px");1457sb_step_y->connect(SceneStringName(value_changed), callable_mp(this, &Polygon2DEditor::_set_snap_step_y));1458sb_step_y->set_accessibility_name(TTRC("Grid Step Y:"));1459grid_settings_vb->add_margin_child(TTR("Grid Step Y:"), sb_step_y);14601461zoom_widget = memnew(EditorZoomWidget);1462canvas->add_child(zoom_widget);1463zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);1464zoom_widget->connect("zoom_changed", callable_mp(this, &Polygon2DEditor::_update_zoom_and_pan).unbind(1).bind(true));1465zoom_widget->set_shortcut_context(nullptr);14661467vscroll = memnew(VScrollBar);1468vscroll->set_step(0.001);1469canvas->add_child(vscroll);1470vscroll->connect(SceneStringName(value_changed), callable_mp(this, &Polygon2DEditor::_update_zoom_and_pan).unbind(1).bind(false));1471hscroll = memnew(HScrollBar);1472hscroll->set_step(0.001);1473canvas->add_child(hscroll);1474hscroll->connect(SceneStringName(value_changed), callable_mp(this, &Polygon2DEditor::_update_zoom_and_pan).unbind(1).bind(false));14751476bone_scroll_main_vb = memnew(VBoxContainer);1477bone_scroll_main_vb->set_custom_minimum_size(Size2(150 * EDSCALE, 0));1478sync_bones = memnew(Button(TTR("Sync Bones to Polygon")));1479bone_scroll_main_vb->add_child(sync_bones);1480sync_bones->set_h_size_flags(0);1481sync_bones->connect(SceneStringName(pressed), callable_mp(this, &Polygon2DEditor::_sync_bones));1482uv_main_hsc->add_child(bone_scroll_main_vb);1483bone_scroll = memnew(ScrollContainer);1484bone_scroll->set_v_scroll(true);1485bone_scroll->set_h_scroll(false);1486bone_scroll_main_vb->add_child(bone_scroll);1487bone_scroll->set_v_size_flags(SIZE_EXPAND_FILL);1488bone_scroll_vb = memnew(VBoxContainer);1489bone_scroll->add_child(bone_scroll_vb);14901491panner.instantiate();1492panner->set_callbacks(callable_mp(this, &Polygon2DEditor::_pan_callback), callable_mp(this, &Polygon2DEditor::_zoom_callback));14931494canvas->connect(SceneStringName(draw), callable_mp(this, &Polygon2DEditor::_canvas_draw));1495canvas->connect(SceneStringName(gui_input), callable_mp(this, &Polygon2DEditor::_canvas_input));1496canvas->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));1497canvas->set_focus_mode(FOCUS_CLICK);14981499error = memnew(AcceptDialog);1500add_child(error);15011502dock_button = EditorNode::get_bottom_panel()->add_item(TTRC("Polygon"), polygon_edit, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_polygon_2d_bottom_panel", TTR("Toggle Polygon Bottom Panel")));1503dock_button->hide();1504}15051506Polygon2DEditorPlugin::Polygon2DEditorPlugin() :1507AbstractPolygon2DEditorPlugin(memnew(Polygon2DEditor), "Polygon2D") {1508}150915101511