1/**************************************************************************/
2/* abstract_polygon_2d_editor.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "abstract_polygon_2d_editor.h"
32
33#include "canvas_item_editor_plugin.h"
34#include "core/math/geometry_2d.h"
35#include "core/os/keyboard.h"
36#include "editor/editor_node.h"
37#include "editor/editor_scale.h"
38#include "editor/editor_settings.h"
39#include "editor/editor_string_names.h"
40#include "editor/editor_undo_redo_manager.h"
41#include "scene/gui/button.h"
42#include "scene/gui/dialogs.h"
43#include "scene/gui/separator.h"
44
45bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const {
46 return polygon == p_vertex.polygon && vertex == p_vertex.vertex;
47}
48
49bool AbstractPolygon2DEditor::Vertex::operator!=(const AbstractPolygon2DEditor::Vertex &p_vertex) const {
50 return !(*this == p_vertex);
51}
52
53bool AbstractPolygon2DEditor::Vertex::valid() const {
54 return vertex >= 0;
55}
56
57bool AbstractPolygon2DEditor::_is_empty() const {
58 if (!_get_node()) {
59 return true;
60 }
61
62 const int n = _get_polygon_count();
63
64 for (int i = 0; i < n; i++) {
65 Vector<Vector2> vertices = _get_polygon(i);
66
67 if (vertices.size() != 0) {
68 return false;
69 }
70 }
71
72 return true;
73}
74
75bool AbstractPolygon2DEditor::_is_line() const {
76 return false;
77}
78
79bool AbstractPolygon2DEditor::_has_uv() const {
80 return false;
81}
82
83int AbstractPolygon2DEditor::_get_polygon_count() const {
84 return 1;
85}
86
87Variant AbstractPolygon2DEditor::_get_polygon(int p_idx) const {
88 return _get_node()->get("polygon");
89}
90
91void AbstractPolygon2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
92 _get_node()->set("polygon", p_polygon);
93}
94
95void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
96 Node2D *node = _get_node();
97 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
98 undo_redo->add_do_method(node, "set_polygon", p_polygon);
99 undo_redo->add_undo_method(node, "set_polygon", p_previous);
100}
101
102Vector2 AbstractPolygon2DEditor::_get_offset(int p_idx) const {
103 return Vector2(0, 0);
104}
105
106void AbstractPolygon2DEditor::_commit_action() {
107 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
108 undo_redo->add_do_method(canvas_item_editor, "update_viewport");
109 undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
110 undo_redo->commit_action();
111}
112
113void AbstractPolygon2DEditor::_action_add_polygon(const Variant &p_polygon) {
114 _action_set_polygon(0, p_polygon);
115}
116
117void AbstractPolygon2DEditor::_action_remove_polygon(int p_idx) {
118 _action_set_polygon(p_idx, _get_polygon(p_idx), Vector<Vector2>());
119}
120
121void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_polygon) {
122 _action_set_polygon(p_idx, _get_polygon(p_idx), p_polygon);
123}
124
125bool AbstractPolygon2DEditor::_has_resource() const {
126 return true;
127}
128
129void AbstractPolygon2DEditor::_create_resource() {
130}
131
132void AbstractPolygon2DEditor::_menu_option(int p_option) {
133 switch (p_option) {
134 case MODE_CREATE: {
135 mode = MODE_CREATE;
136 button_create->set_pressed(true);
137 button_edit->set_pressed(false);
138 button_delete->set_pressed(false);
139 } break;
140 case MODE_EDIT: {
141 _wip_close();
142 mode = MODE_EDIT;
143 button_create->set_pressed(false);
144 button_edit->set_pressed(true);
145 button_delete->set_pressed(false);
146 } break;
147 case MODE_DELETE: {
148 _wip_close();
149 mode = MODE_DELETE;
150 button_create->set_pressed(false);
151 button_edit->set_pressed(false);
152 button_delete->set_pressed(true);
153 } break;
154 }
155}
156
157void AbstractPolygon2DEditor::_notification(int p_what) {
158 switch (p_what) {
159 case NOTIFICATION_ENTER_TREE:
160 case NOTIFICATION_THEME_CHANGED: {
161 button_create->set_icon(get_editor_theme_icon(SNAME("CurveCreate")));
162 button_edit->set_icon(get_editor_theme_icon(SNAME("CurveEdit")));
163 button_delete->set_icon(get_editor_theme_icon(SNAME("CurveDelete")));
164 } break;
165
166 case NOTIFICATION_READY: {
167 disable_polygon_editing(false, String());
168
169 button_edit->set_pressed(true);
170
171 get_tree()->connect("node_removed", callable_mp(this, &AbstractPolygon2DEditor::_node_removed));
172 create_resource->connect("confirmed", callable_mp(this, &AbstractPolygon2DEditor::_create_resource));
173 } break;
174 }
175}
176
177void AbstractPolygon2DEditor::_node_removed(Node *p_node) {
178 if (p_node == _get_node()) {
179 edit(nullptr);
180 hide();
181
182 canvas_item_editor->update_viewport();
183 }
184}
185
186void AbstractPolygon2DEditor::_wip_changed() {
187 if (wip_active && _is_line()) {
188 _set_polygon(0, wip);
189 }
190}
191
192void AbstractPolygon2DEditor::_wip_cancel() {
193 wip.clear();
194 wip_active = false;
195
196 edited_point = PosVertex();
197 hover_point = Vertex();
198 selected_point = Vertex();
199
200 canvas_item_editor->update_viewport();
201}
202
203void AbstractPolygon2DEditor::_wip_close() {
204 if (!wip_active) {
205 return;
206 }
207
208 if (_is_line()) {
209 _set_polygon(0, wip);
210 } else if (wip.size() >= (_is_line() ? 2 : 3)) {
211 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
212 undo_redo->create_action(TTR("Create Polygon"));
213 _action_add_polygon(wip);
214 if (_has_uv()) {
215 undo_redo->add_do_method(_get_node(), "set_uv", Vector<Vector2>());
216 undo_redo->add_undo_method(_get_node(), "set_uv", _get_node()->get("uv"));
217 }
218 _commit_action();
219 } else {
220 return;
221 }
222
223 mode = MODE_EDIT;
224 button_edit->set_pressed(true);
225 button_create->set_pressed(false);
226 button_delete->set_pressed(false);
227
228 wip.clear();
229 wip_active = false;
230
231 edited_point = PosVertex();
232 hover_point = Vertex();
233 selected_point = Vertex();
234}
235
236void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, String p_reason) {
237 _polygon_editing_enabled = !p_disable;
238
239 button_create->set_disabled(p_disable);
240 button_edit->set_disabled(p_disable);
241 button_delete->set_disabled(p_disable);
242
243 if (p_disable) {
244 button_create->set_tooltip_text(p_reason);
245 button_edit->set_tooltip_text(p_reason);
246 button_delete->set_tooltip_text(p_reason);
247 } else {
248 button_create->set_tooltip_text(TTR("Create points."));
249 button_edit->set_tooltip_text(TTR("Edit points.\nLMB: Move Point\nRMB: Erase Point"));
250 button_delete->set_tooltip_text(TTR("Erase points."));
251 }
252}
253
254bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
255 if (!_get_node() || !_polygon_editing_enabled) {
256 return false;
257 }
258
259 if (!_get_node()->is_visible_in_tree()) {
260 return false;
261 }
262
263 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
264 Ref<InputEventMouseButton> mb = p_event;
265
266 if (!_has_resource()) {
267 if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
268 create_resource->set_text(String("No polygon resource on this node.\nCreate and assign one?"));
269 create_resource->popup_centered();
270 }
271 return (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT);
272 }
273
274 CanvasItemEditor::Tool tool = CanvasItemEditor::get_singleton()->get_current_tool();
275 if (tool != CanvasItemEditor::TOOL_SELECT) {
276 return false;
277 }
278
279 if (mb.is_valid()) {
280 Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
281
282 Vector2 gpoint = mb->get_position();
283 Vector2 cpoint = _get_node()->to_local(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mb->get_position())));
284
285 if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
286 if (mb->get_button_index() == MouseButton::LEFT) {
287 if (mb->is_pressed()) {
288 if (mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
289 return false;
290 }
291
292 const PosVertex insert = closest_edge_point(gpoint);
293
294 if (insert.valid()) {
295 Vector<Vector2> vertices = _get_polygon(insert.polygon);
296
297 if (vertices.size() < (_is_line() ? 2 : 3)) {
298 vertices.push_back(cpoint);
299 undo_redo->create_action(TTR("Edit Polygon"));
300 selected_point = Vertex(insert.polygon, vertices.size());
301 _action_set_polygon(insert.polygon, vertices);
302 _commit_action();
303 return true;
304 } else {
305 edited_point = PosVertex(insert.polygon, insert.vertex + 1, xform.affine_inverse().xform(insert.pos));
306 vertices.insert(edited_point.vertex, edited_point.pos);
307 pre_move_edit = vertices;
308 selected_point = Vertex(edited_point.polygon, edited_point.vertex);
309 edge_point = PosVertex();
310
311 undo_redo->create_action(TTR("Insert Point"));
312 _action_set_polygon(insert.polygon, vertices);
313 _commit_action();
314 return true;
315 }
316 } else {
317 //look for points to move
318 const PosVertex closest = closest_point(gpoint);
319
320 if (closest.valid()) {
321 pre_move_edit = _get_polygon(closest.polygon);
322 edited_point = PosVertex(closest, xform.affine_inverse().xform(closest.pos));
323 selected_point = closest;
324 edge_point = PosVertex();
325 canvas_item_editor->update_viewport();
326 return true;
327 } else {
328 selected_point = Vertex();
329 }
330 }
331 } else {
332 if (edited_point.valid()) {
333 //apply
334
335 Vector<Vector2> vertices = _get_polygon(edited_point.polygon);
336 ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);
337 vertices.write[edited_point.vertex] = edited_point.pos - _get_offset(edited_point.polygon);
338
339 undo_redo->create_action(TTR("Edit Polygon"));
340 _action_set_polygon(edited_point.polygon, pre_move_edit, vertices);
341 _commit_action();
342
343 edited_point = PosVertex();
344 return true;
345 }
346 }
347 } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && !edited_point.valid()) {
348 const PosVertex closest = closest_point(gpoint);
349
350 if (closest.valid()) {
351 remove_point(closest);
352 return true;
353 }
354 }
355 } else if (mode == MODE_DELETE) {
356 if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
357 const PosVertex closest = closest_point(gpoint);
358
359 if (closest.valid()) {
360 remove_point(closest);
361 return true;
362 }
363 }
364 }
365
366 if (mode == MODE_CREATE) {
367 if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
368 if (_is_line()) {
369 // for lines, we don't have a wip mode, and we can undo each single add point.
370 Vector<Vector2> vertices = _get_polygon(0);
371 vertices.push_back(cpoint);
372 undo_redo->create_action(TTR("Insert Point"));
373 _action_set_polygon(0, vertices);
374 _commit_action();
375 return true;
376 } else if (!wip_active) {
377 wip.clear();
378 wip.push_back(cpoint);
379 wip_active = true;
380 _wip_changed();
381 edited_point = PosVertex(-1, 1, cpoint);
382 canvas_item_editor->update_viewport();
383 hover_point = Vertex();
384 selected_point = Vertex(0);
385 edge_point = PosVertex();
386 return true;
387 } else {
388 const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
389
390 if (!_is_line() && wip.size() > 1 && xform.xform(wip[0]).distance_to(xform.xform(cpoint)) < grab_threshold) {
391 //wip closed
392 _wip_close();
393
394 return true;
395 } else {
396 //add wip point
397 wip.push_back(cpoint);
398 _wip_changed();
399 edited_point = PosVertex(-1, wip.size(), cpoint);
400 selected_point = Vertex(wip.size() - 1);
401 canvas_item_editor->update_viewport();
402 return true;
403 }
404 }
405 } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && wip_active) {
406 _wip_cancel();
407 }
408 }
409 }
410
411 Ref<InputEventMouseMotion> mm = p_event;
412
413 if (mm.is_valid()) {
414 Vector2 gpoint = mm->get_position();
415
416 if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
417 Vector2 cpoint = _get_node()->to_local(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint)));
418
419 //Move the point in a single axis. Should only work when editing a polygon and while holding shift.
420 if (mode == MODE_EDIT && mm->is_shift_pressed()) {
421 Vector2 old_point = pre_move_edit.get(selected_point.vertex);
422 if (ABS(cpoint.x - old_point.x) > ABS(cpoint.y - old_point.y)) {
423 cpoint.y = old_point.y;
424 } else {
425 cpoint.x = old_point.x;
426 }
427 }
428
429 edited_point = PosVertex(edited_point, cpoint);
430
431 if (!wip_active) {
432 Vector<Vector2> vertices = _get_polygon(edited_point.polygon);
433 ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);
434 vertices.write[edited_point.vertex] = cpoint - _get_offset(edited_point.polygon);
435 _set_polygon(edited_point.polygon, vertices);
436 }
437
438 canvas_item_editor->update_viewport();
439 } else if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
440 const PosVertex onEdgeVertex = closest_edge_point(gpoint);
441
442 if (onEdgeVertex.valid()) {
443 hover_point = Vertex();
444 edge_point = onEdgeVertex;
445 canvas_item_editor->update_viewport();
446 } else {
447 if (edge_point.valid()) {
448 edge_point = PosVertex();
449 canvas_item_editor->update_viewport();
450 }
451
452 const PosVertex new_hover_point = closest_point(gpoint);
453 if (hover_point != new_hover_point) {
454 hover_point = new_hover_point;
455 canvas_item_editor->update_viewport();
456 }
457 }
458 }
459 }
460
461 Ref<InputEventKey> k = p_event;
462
463 if (k.is_valid() && k->is_pressed()) {
464 if (k->get_keycode() == Key::KEY_DELETE || k->get_keycode() == Key::BACKSPACE) {
465 if (wip_active && selected_point.polygon == -1) {
466 if (wip.size() > selected_point.vertex) {
467 wip.remove_at(selected_point.vertex);
468 _wip_changed();
469 selected_point = wip.size() - 1;
470 canvas_item_editor->update_viewport();
471 return true;
472 }
473 } else {
474 const Vertex active_point = get_active_point();
475
476 if (active_point.valid()) {
477 remove_point(active_point);
478 return true;
479 }
480 }
481 } else if (wip_active && k->get_keycode() == Key::ENTER) {
482 _wip_close();
483 } else if (wip_active && k->get_keycode() == Key::ESCAPE) {
484 _wip_cancel();
485 }
486 }
487
488 return false;
489}
490
491void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
492 if (!_get_node()) {
493 return;
494 }
495
496 if (!_get_node()->is_visible_in_tree()) {
497 return;
498 }
499
500 Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
501 // All polygon points are sharp, so use the sharp handle icon
502 const Ref<Texture2D> handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
503
504 const Vertex active_point = get_active_point();
505 const int n_polygons = _get_polygon_count();
506 const bool is_closed = !_is_line();
507
508 for (int j = -1; j < n_polygons; j++) {
509 if (wip_active && wip_destructive && j != -1) {
510 continue;
511 }
512
513 Vector<Vector2> points;
514 Vector2 offset;
515
516 if (wip_active && j == edited_point.polygon) {
517 points = Variant(wip);
518 offset = Vector2(0, 0);
519 } else {
520 if (j == -1) {
521 continue;
522 }
523 points = _get_polygon(j);
524 offset = _get_offset(j);
525 }
526
527 if (!wip_active && j == edited_point.polygon && EDITOR_GET("editors/polygon_editor/show_previous_outline")) {
528 const Color col = Color(0.5, 0.5, 0.5); // FIXME polygon->get_outline_color();
529 const int n = pre_move_edit.size();
530 for (int i = 0; i < n - (is_closed ? 0 : 1); i++) {
531 Vector2 p, p2;
532 p = pre_move_edit[i] + offset;
533 p2 = pre_move_edit[(i + 1) % n] + offset;
534
535 Vector2 point = xform.xform(p);
536 Vector2 next_point = xform.xform(p2);
537
538 p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));
539 }
540 }
541
542 const int n_points = points.size();
543 const Color col = Color(1, 0.3, 0.1, 0.8);
544
545 for (int i = 0; i < n_points; i++) {
546 const Vertex vertex(j, i);
547
548 const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);
549 const Vector2 point = xform.xform(p);
550
551 if (is_closed || i < n_points - 1) {
552 Vector2 p2;
553 if (j == edited_point.polygon &&
554 ((wip_active && i == n_points - 1) || (((i + 1) % n_points) == edited_point.vertex))) {
555 p2 = edited_point.pos;
556 } else {
557 p2 = points[(i + 1) % n_points] + offset;
558 }
559
560 const Vector2 next_point = xform.xform(p2);
561 p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));
562 }
563 }
564
565 for (int i = 0; i < n_points; i++) {
566 const Vertex vertex(j, i);
567
568 const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);
569 const Vector2 point = xform.xform(p);
570
571 const Color overlay_modulate = vertex == active_point ? Color(0.5, 1, 2) : Color(1, 1, 1);
572 p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, overlay_modulate);
573
574 if (vertex == hover_point) {
575 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
576 int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
577 String num = String::num(vertex.vertex);
578 Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
579 const float outline_size = 4;
580 Color font_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
581 Color outline_color = font_color.inverted();
582 p_overlay->draw_string_outline(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
583 p_overlay->draw_string(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
584 }
585 }
586 }
587
588 if (edge_point.valid()) {
589 Ref<Texture2D> add_handle = get_editor_theme_icon(SNAME("EditorHandleAdd"));
590 p_overlay->draw_texture(add_handle, edge_point.pos - add_handle->get_size() * 0.5);
591 }
592}
593
594void AbstractPolygon2DEditor::edit(Node *p_polygon) {
595 if (!canvas_item_editor) {
596 canvas_item_editor = CanvasItemEditor::get_singleton();
597 }
598
599 if (p_polygon) {
600 _set_node(p_polygon);
601
602 // Enable the pencil tool if the polygon is empty.
603 if (_is_empty()) {
604 _menu_option(MODE_CREATE);
605 } else {
606 _menu_option(MODE_EDIT);
607 }
608
609 wip.clear();
610 wip_active = false;
611 edited_point = PosVertex();
612 hover_point = Vertex();
613 selected_point = Vertex();
614 } else {
615 _set_node(nullptr);
616 }
617
618 canvas_item_editor->update_viewport();
619}
620
621void AbstractPolygon2DEditor::_bind_methods() {
622}
623
624void AbstractPolygon2DEditor::remove_point(const Vertex &p_vertex) {
625 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
626 Vector<Vector2> vertices = _get_polygon(p_vertex.polygon);
627
628 if (vertices.size() > (_is_line() ? 2 : 3)) {
629 vertices.remove_at(p_vertex.vertex);
630
631 undo_redo->create_action(TTR("Edit Polygon (Remove Point)"));
632 _action_set_polygon(p_vertex.polygon, vertices);
633 _commit_action();
634 } else {
635 undo_redo->create_action(TTR("Remove Polygon And Point"));
636 _action_remove_polygon(p_vertex.polygon);
637 _commit_action();
638 }
639
640 if (_is_empty()) {
641 _menu_option(MODE_CREATE);
642 }
643
644 hover_point = Vertex();
645 if (selected_point == p_vertex) {
646 selected_point = Vertex();
647 }
648}
649
650AbstractPolygon2DEditor::Vertex AbstractPolygon2DEditor::get_active_point() const {
651 return hover_point.valid() ? hover_point : selected_point;
652}
653
654AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_point(const Vector2 &p_pos) const {
655 const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
656
657 const int n_polygons = _get_polygon_count();
658 const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
659
660 PosVertex closest;
661 real_t closest_dist = 1e10;
662
663 for (int j = 0; j < n_polygons; j++) {
664 Vector<Vector2> points = _get_polygon(j);
665 const Vector2 offset = _get_offset(j);
666 const int n_points = points.size();
667
668 for (int i = 0; i < n_points; i++) {
669 Vector2 cp = xform.xform(points[i] + offset);
670
671 real_t d = cp.distance_to(p_pos);
672 if (d < closest_dist && d < grab_threshold) {
673 closest_dist = d;
674 closest = PosVertex(j, i, cp);
675 }
676 }
677 }
678
679 return closest;
680}
681
682AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(const Vector2 &p_pos) const {
683 const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
684 const real_t eps = grab_threshold * 2;
685 const real_t eps2 = eps * eps;
686
687 const int n_polygons = _get_polygon_count();
688 const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
689
690 PosVertex closest;
691 real_t closest_dist = 1e10;
692
693 for (int j = 0; j < n_polygons; j++) {
694 Vector<Vector2> points = _get_polygon(j);
695 const Vector2 offset = _get_offset(j);
696 const int n_points = points.size();
697 const int n_segments = n_points - (_is_line() ? 1 : 0);
698
699 for (int i = 0; i < n_segments; i++) {
700 Vector2 segment[2] = { xform.xform(points[i] + offset),
701 xform.xform(points[(i + 1) % n_points] + offset) };
702
703 Vector2 cp = Geometry2D::get_closest_point_to_segment(p_pos, segment);
704
705 if (cp.distance_squared_to(segment[0]) < eps2 || cp.distance_squared_to(segment[1]) < eps2) {
706 continue; //not valid to reuse point
707 }
708
709 real_t d = cp.distance_to(p_pos);
710 if (d < closest_dist && d < grab_threshold) {
711 closest_dist = d;
712 closest = PosVertex(j, i, cp);
713 }
714 }
715 }
716
717 return closest;
718}
719
720AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
721 edited_point = PosVertex();
722 wip_destructive = p_wip_destructive;
723
724 hover_point = Vertex();
725 selected_point = Vertex();
726 edge_point = PosVertex();
727
728 button_create = memnew(Button);
729 button_create->set_flat(true);
730 add_child(button_create);
731 button_create->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_CREATE));
732 button_create->set_toggle_mode(true);
733
734 button_edit = memnew(Button);
735 button_edit->set_flat(true);
736 add_child(button_edit);
737 button_edit->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_EDIT));
738 button_edit->set_toggle_mode(true);
739
740 button_delete = memnew(Button);
741 button_delete->set_flat(true);
742 add_child(button_delete);
743 button_delete->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE));
744 button_delete->set_toggle_mode(true);
745
746 create_resource = memnew(ConfirmationDialog);
747 add_child(create_resource);
748 create_resource->set_ok_button_text(TTR("Create"));
749}
750
751void AbstractPolygon2DEditorPlugin::edit(Object *p_object) {
752 polygon_editor->edit(Object::cast_to<Node>(p_object));
753}
754
755bool AbstractPolygon2DEditorPlugin::handles(Object *p_object) const {
756 return p_object->is_class(klass);
757}
758
759void AbstractPolygon2DEditorPlugin::make_visible(bool p_visible) {
760 if (p_visible) {
761 polygon_editor->show();
762 } else {
763 polygon_editor->hide();
764 polygon_editor->edit(nullptr);
765 }
766}
767
768AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, String p_class) :
769 polygon_editor(p_polygon_editor),
770 klass(p_class) {
771 CanvasItemEditor::get_singleton()->add_control_to_menu_panel(polygon_editor);
772 polygon_editor->hide();
773}
774
775AbstractPolygon2DEditorPlugin::~AbstractPolygon2DEditorPlugin() {
776}
777