Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/scene/2d/abstract_polygon_2d_editor.cpp
9904 views
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 "core/math/geometry_2d.h"
34
#include "core/os/keyboard.h"
35
#include "editor/editor_node.h"
36
#include "editor/editor_string_names.h"
37
#include "editor/editor_undo_redo_manager.h"
38
#include "editor/scene/canvas_item_editor_plugin.h"
39
#include "editor/settings/editor_settings.h"
40
#include "editor/themes/editor_scale.h"
41
#include "scene/gui/button.h"
42
#include "scene/gui/dialogs.h"
43
44
bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const {
45
return polygon == p_vertex.polygon && vertex == p_vertex.vertex;
46
}
47
48
bool AbstractPolygon2DEditor::Vertex::operator!=(const AbstractPolygon2DEditor::Vertex &p_vertex) const {
49
return !(*this == p_vertex);
50
}
51
52
bool AbstractPolygon2DEditor::Vertex::valid() const {
53
return vertex >= 0;
54
}
55
56
bool AbstractPolygon2DEditor::_is_empty() const {
57
if (!_get_node()) {
58
return true;
59
}
60
61
const int n = _get_polygon_count();
62
63
for (int i = 0; i < n; i++) {
64
Vector<Vector2> vertices = _get_polygon(i);
65
66
if (vertices.size() != 0) {
67
return false;
68
}
69
}
70
71
return true;
72
}
73
74
bool AbstractPolygon2DEditor::_is_line() const {
75
return false;
76
}
77
78
bool AbstractPolygon2DEditor::_has_uv() const {
79
return false;
80
}
81
82
int AbstractPolygon2DEditor::_get_polygon_count() const {
83
return 1;
84
}
85
86
Variant AbstractPolygon2DEditor::_get_polygon(int p_idx) const {
87
return _get_node()->get("polygon");
88
}
89
90
void AbstractPolygon2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
91
_get_node()->set("polygon", p_polygon);
92
}
93
94
void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
95
Node2D *node = _get_node();
96
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
97
undo_redo->add_do_method(node, "set_polygon", p_polygon);
98
undo_redo->add_undo_method(node, "set_polygon", p_previous);
99
}
100
101
Vector2 AbstractPolygon2DEditor::_get_offset(int p_idx) const {
102
return Vector2(0, 0);
103
}
104
105
void AbstractPolygon2DEditor::_commit_action() {
106
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
107
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
108
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
109
undo_redo->commit_action();
110
}
111
112
void AbstractPolygon2DEditor::_action_add_polygon(const Variant &p_polygon) {
113
_action_set_polygon(0, p_polygon);
114
}
115
116
void AbstractPolygon2DEditor::_action_remove_polygon(int p_idx) {
117
_action_set_polygon(p_idx, _get_polygon(p_idx), Vector<Vector2>());
118
}
119
120
void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_polygon) {
121
_action_set_polygon(p_idx, _get_polygon(p_idx), p_polygon);
122
}
123
124
bool AbstractPolygon2DEditor::_has_resource() const {
125
return true;
126
}
127
128
void AbstractPolygon2DEditor::_create_resource() {
129
}
130
131
Vector2 AbstractPolygon2DEditor::_get_geometric_center() const {
132
int n_polygons = _get_polygon_count();
133
134
double cx = 0.0;
135
double cy = 0.0;
136
int n_subs = 0;
137
for (int i = 0; i < n_polygons; i++) {
138
const Vector<Vector2> &vertices = _get_polygon(i);
139
Vector<Vector<Point2>> decomp = ::Geometry2D::decompose_polygon_in_convex(vertices);
140
if (decomp.is_empty()) {
141
continue;
142
}
143
for (const Vector<Vector2> &sub : decomp) {
144
int sub_n_points = sub.size();
145
double sub_area2x = 0.0;
146
double sub_cx = 0.0;
147
double sub_cy = 0.0;
148
for (int n = 0; n < sub_n_points; n++) {
149
int next = (n + 1 < sub_n_points) ? n + 1 : 0;
150
sub_area2x += (sub[n].x * sub[next].y) - (sub[next].x * sub[n].y);
151
sub_cx += (sub[n].x + sub[next].x) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);
152
sub_cy += (sub[n].y + sub[next].y) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);
153
}
154
sub_cx /= (sub_area2x * 3);
155
sub_cy /= (sub_area2x * 3);
156
157
cx += sub_cx;
158
cy += sub_cy;
159
}
160
n_subs += decomp.size();
161
}
162
cx /= n_subs;
163
cy /= n_subs;
164
165
return Vector2(cx, cy);
166
}
167
168
void AbstractPolygon2DEditor::_menu_option(int p_option) {
169
switch (p_option) {
170
case MODE_CREATE: {
171
mode = MODE_CREATE;
172
button_create->set_pressed(true);
173
button_edit->set_pressed(false);
174
button_delete->set_pressed(false);
175
} break;
176
case MODE_EDIT: {
177
_wip_close();
178
mode = MODE_EDIT;
179
button_create->set_pressed(false);
180
button_edit->set_pressed(true);
181
button_delete->set_pressed(false);
182
} break;
183
case MODE_DELETE: {
184
_wip_close();
185
mode = MODE_DELETE;
186
button_create->set_pressed(false);
187
button_edit->set_pressed(false);
188
button_delete->set_pressed(true);
189
} break;
190
case CENTER_POLY: {
191
_wip_close();
192
193
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
194
undo_redo->create_action(TTR("Move Origin to Geometric Center"));
195
196
Vector2 center = _get_geometric_center();
197
198
int n_polygons = _get_polygon_count();
199
for (int i = 0; i < n_polygons; i++) {
200
const Vector<Vector2> &vertices = _get_polygon(i);
201
int n_points = vertices.size();
202
203
Vector<Vector2> new_vertices;
204
new_vertices.resize(n_points);
205
for (int n = 0; n < n_points; n++) {
206
new_vertices.write[n] = vertices[n] - center;
207
}
208
_action_set_polygon(i, vertices, new_vertices);
209
}
210
Node2D *node = _get_node();
211
Vector2 node_pos = node->get_position();
212
undo_redo->add_do_method(node, "set_position", node_pos + node->get_transform().basis_xform(center));
213
undo_redo->add_undo_method(node, "set_position", node_pos);
214
215
_commit_action();
216
} break;
217
}
218
}
219
220
void AbstractPolygon2DEditor::_notification(int p_what) {
221
switch (p_what) {
222
case NOTIFICATION_THEME_CHANGED: {
223
button_create->set_button_icon(get_editor_theme_icon(SNAME("CurveCreate")));
224
button_edit->set_button_icon(get_editor_theme_icon(SNAME("CurveEdit")));
225
button_delete->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete")));
226
button_center->set_button_icon(get_editor_theme_icon(SNAME("CurveCenter")));
227
} break;
228
229
case NOTIFICATION_READY: {
230
disable_polygon_editing(false, String());
231
232
button_edit->set_pressed(true);
233
234
get_tree()->connect("node_removed", callable_mp(this, &AbstractPolygon2DEditor::_node_removed));
235
create_resource->connect(SceneStringName(confirmed), callable_mp(this, &AbstractPolygon2DEditor::_create_resource));
236
} break;
237
}
238
}
239
240
void AbstractPolygon2DEditor::_node_removed(Node *p_node) {
241
if (p_node == _get_node()) {
242
edit(nullptr);
243
hide();
244
245
canvas_item_editor->update_viewport();
246
}
247
}
248
249
void AbstractPolygon2DEditor::_wip_changed() {
250
if (wip_active && _is_line()) {
251
_set_polygon(0, wip);
252
}
253
}
254
255
void AbstractPolygon2DEditor::_wip_cancel() {
256
wip.clear();
257
wip_active = false;
258
259
edited_point = PosVertex();
260
hover_point = Vertex();
261
selected_point = Vertex();
262
center_drag = false;
263
264
canvas_item_editor->update_viewport();
265
}
266
267
void AbstractPolygon2DEditor::_wip_close() {
268
if (!wip_active) {
269
return;
270
}
271
272
if (_is_line()) {
273
_set_polygon(0, wip);
274
} else if (wip.size() >= (_is_line() ? 2 : 3)) {
275
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
276
undo_redo->create_action(TTR("Create Polygon"));
277
_action_add_polygon(wip);
278
if (_has_uv()) {
279
undo_redo->add_do_method(_get_node(), "set_uv", Vector<Vector2>());
280
undo_redo->add_undo_method(_get_node(), "set_uv", _get_node()->get("uv"));
281
}
282
_commit_action();
283
} else {
284
return;
285
}
286
287
mode = MODE_EDIT;
288
button_edit->set_pressed(true);
289
button_create->set_pressed(false);
290
button_delete->set_pressed(false);
291
292
wip.clear();
293
wip_active = false;
294
295
edited_point = PosVertex();
296
hover_point = Vertex();
297
selected_point = Vertex();
298
center_drag = false;
299
}
300
301
void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, const String &p_reason) {
302
_polygon_editing_enabled = !p_disable;
303
304
button_create->set_disabled(p_disable);
305
button_edit->set_disabled(p_disable);
306
button_delete->set_disabled(p_disable);
307
button_center->set_disabled(p_disable);
308
309
if (p_disable) {
310
button_create->set_tooltip_text(p_reason);
311
button_edit->set_tooltip_text(p_reason);
312
button_delete->set_tooltip_text(p_reason);
313
button_center->set_tooltip_text(p_reason);
314
} else {
315
button_create->set_tooltip_text(TTRC("Create points."));
316
button_edit->set_tooltip_text(TTRC("Edit points.\nLMB: Move Point\nRMB: Erase Point"));
317
button_delete->set_tooltip_text(TTRC("Erase points."));
318
button_center->set_tooltip_text(TTRC("Move center of gravity to geometric center."));
319
}
320
}
321
322
bool AbstractPolygon2DEditor::_commit_drag() {
323
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
324
325
center_drag = false;
326
int n_polygons = _get_polygon_count();
327
ERR_FAIL_COND_V(pre_center_move_edit.size() != n_polygons, false);
328
undo_redo->create_action(TTR("Move Geometric Center"));
329
for (int i = 0; i < n_polygons; i++) {
330
_action_set_polygon(i, pre_center_move_edit[i], _get_polygon(i));
331
}
332
pre_center_move_edit.clear();
333
_commit_action();
334
return true;
335
}
336
337
bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
338
if (!_get_node() || !_polygon_editing_enabled) {
339
return false;
340
}
341
342
if (!_get_node()->is_visible_in_tree()) {
343
return false;
344
}
345
346
Viewport *vp = _get_node()->get_viewport();
347
if (vp && !vp->is_visible_subviewport()) {
348
return false;
349
}
350
351
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
352
Ref<InputEventMouseButton> mb = p_event;
353
354
if (!_has_resource()) {
355
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
356
create_resource->set_text(String("No polygon resource on this node.\nCreate and assign one?"));
357
create_resource->popup_centered();
358
}
359
return (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT);
360
}
361
362
CanvasItemEditor::Tool tool = CanvasItemEditor::get_singleton()->get_current_tool();
363
if (tool != CanvasItemEditor::TOOL_SELECT) {
364
return false;
365
}
366
367
if (mb.is_valid()) {
368
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
369
370
Vector2 gpoint = mb->get_position();
371
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
372
cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);
373
374
if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
375
if (mb->get_button_index() == MouseButton::LEFT) {
376
if (mb->is_pressed()) {
377
if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
378
return false;
379
}
380
381
const PosVertex closest = closest_point(gpoint);
382
if (closest.valid()) {
383
original_mouse_pos = gpoint;
384
pre_move_edit = _get_polygon(closest.polygon);
385
edited_point = PosVertex(closest, xform.affine_inverse().xform(closest.pos));
386
selected_point = closest;
387
edge_point = PosVertex();
388
canvas_item_editor->update_viewport();
389
return true;
390
} else {
391
selected_point = Vertex();
392
393
const PosVertex insert = closest_edge_point(gpoint);
394
if (insert.valid()) {
395
Vector<Vector2> vertices = _get_polygon(insert.polygon);
396
397
if (vertices.size() < (_is_line() ? 2 : 3)) {
398
vertices.push_back(cpoint);
399
undo_redo->create_action(TTR("Edit Polygon"));
400
selected_point = Vertex(insert.polygon, vertices.size());
401
_action_set_polygon(insert.polygon, vertices);
402
_commit_action();
403
return true;
404
} else {
405
edited_point = PosVertex(insert.polygon, insert.vertex + 1, xform.affine_inverse().xform(insert.pos));
406
vertices.insert(edited_point.vertex, edited_point.pos);
407
pre_move_edit = vertices;
408
selected_point = Vertex(edited_point.polygon, edited_point.vertex);
409
edge_point = PosVertex();
410
411
undo_redo->create_action(TTR("Insert Point"));
412
_action_set_polygon(insert.polygon, vertices);
413
_commit_action();
414
return true;
415
}
416
}
417
}
418
} else {
419
if (edited_point.valid()) {
420
if (original_mouse_pos != gpoint) {
421
Vector<Vector2> vertices = _get_polygon(edited_point.polygon);
422
ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);
423
vertices.write[edited_point.vertex] = edited_point.pos - _get_offset(edited_point.polygon);
424
425
undo_redo->create_action(TTR("Edit Polygon"));
426
_action_set_polygon(edited_point.polygon, pre_move_edit, vertices);
427
_commit_action();
428
}
429
430
edited_point = PosVertex();
431
return true;
432
}
433
}
434
} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && !edited_point.valid()) {
435
const PosVertex closest = closest_point(gpoint);
436
437
if (closest.valid()) {
438
remove_point(closest);
439
return true;
440
}
441
}
442
} else if (mode == MODE_DELETE) {
443
if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
444
const PosVertex closest = closest_point(gpoint);
445
446
if (closest.valid()) {
447
remove_point(closest);
448
return true;
449
}
450
}
451
}
452
453
if (mode == MODE_CREATE) {
454
if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
455
if (_is_line()) {
456
// for lines, we don't have a wip mode, and we can undo each single add point.
457
Vector<Vector2> vertices = _get_polygon(0);
458
vertices.push_back(cpoint);
459
undo_redo->create_action(TTR("Insert Point"));
460
_action_set_polygon(0, vertices);
461
_commit_action();
462
return true;
463
} else if (!wip_active) {
464
wip.clear();
465
wip.push_back(cpoint);
466
wip_active = true;
467
_wip_changed();
468
edited_point = PosVertex(-1, 1, cpoint);
469
canvas_item_editor->update_viewport();
470
hover_point = Vertex();
471
selected_point = Vertex(0);
472
edge_point = PosVertex();
473
return true;
474
} else {
475
const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
476
477
if (!_is_line() && wip.size() > 1 && xform.xform(wip[0]).distance_to(xform.xform(cpoint)) < grab_threshold) {
478
//wip closed
479
_wip_close();
480
481
return true;
482
} else {
483
//add wip point
484
wip.push_back(cpoint);
485
_wip_changed();
486
edited_point = PosVertex(-1, wip.size(), cpoint);
487
selected_point = Vertex(wip.size() - 1);
488
canvas_item_editor->update_viewport();
489
return true;
490
}
491
}
492
} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && wip_active) {
493
_wip_cancel();
494
}
495
}
496
497
// Center drag.
498
if (edit_origin_and_center) {
499
real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
500
501
if (mb->get_button_index() == MouseButton::LEFT) {
502
if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
503
return false;
504
}
505
if (mb->is_pressed() && !center_drag) {
506
Vector2 center_point = xform.xform(_get_geometric_center());
507
if ((gpoint - center_point).length() < grab_threshold) {
508
pre_center_move_edit.clear();
509
int n_polygons = _get_polygon_count();
510
for (int i = 0; i < n_polygons; i++) {
511
pre_center_move_edit.push_back(_get_polygon(i));
512
}
513
center_drag_origin = cpoint;
514
center_drag = true;
515
return true;
516
}
517
} else if (center_drag) {
518
return _commit_drag();
519
}
520
} else if (mb->get_button_index() == MouseButton::RIGHT && center_drag) {
521
_commit_drag();
522
}
523
}
524
}
525
526
Ref<InputEventMouseMotion> mm = p_event;
527
528
if (mm.is_valid()) {
529
Vector2 gpoint = mm->get_position();
530
531
if (center_drag) {
532
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
533
cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);
534
Vector2 delta = center_drag_origin - cpoint;
535
536
int n_polygons = _get_polygon_count();
537
for (int i = 0; i < n_polygons; i++) {
538
const Vector<Vector2> &vertices = _get_polygon(i);
539
int n_points = vertices.size();
540
541
Vector<Vector2> new_vertices;
542
new_vertices.resize(n_points);
543
for (int n = 0; n < n_points; n++) {
544
new_vertices.write[n] = vertices[n] - delta;
545
}
546
_set_polygon(i, new_vertices);
547
}
548
center_drag_origin = cpoint;
549
} else if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
550
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
551
cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);
552
553
//Move the point in a single axis. Should only work when editing a polygon and while holding shift.
554
if (mode == MODE_EDIT && mm->is_shift_pressed()) {
555
Vector2 old_point = pre_move_edit.get(selected_point.vertex);
556
if (Math::abs(cpoint.x - old_point.x) > Math::abs(cpoint.y - old_point.y)) {
557
cpoint.y = old_point.y;
558
} else {
559
cpoint.x = old_point.x;
560
}
561
}
562
563
edited_point = PosVertex(edited_point, cpoint);
564
565
if (!wip_active) {
566
Vector<Vector2> vertices = _get_polygon(edited_point.polygon);
567
ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);
568
vertices.write[edited_point.vertex] = cpoint - _get_offset(edited_point.polygon);
569
_set_polygon(edited_point.polygon, vertices);
570
}
571
572
canvas_item_editor->update_viewport();
573
} else if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
574
const PosVertex new_hover_point = closest_point(gpoint);
575
if (hover_point != new_hover_point) {
576
hover_point = new_hover_point;
577
canvas_item_editor->update_viewport();
578
}
579
580
bool edge_hover = false;
581
if (!hover_point.valid()) {
582
const PosVertex on_edge_vertex = closest_edge_point(gpoint);
583
584
if (on_edge_vertex.valid()) {
585
hover_point = Vertex();
586
edge_point = on_edge_vertex;
587
canvas_item_editor->update_viewport();
588
edge_hover = true;
589
}
590
}
591
592
if (!edge_hover && edge_point.valid()) {
593
edge_point = PosVertex();
594
canvas_item_editor->update_viewport();
595
}
596
}
597
}
598
599
Ref<InputEventKey> k = p_event;
600
601
if (k.is_valid() && k->is_pressed()) {
602
if (k->get_keycode() == Key::KEY_DELETE || k->get_keycode() == Key::BACKSPACE) {
603
if (wip_active && selected_point.polygon == -1) {
604
if (wip.size() > selected_point.vertex) {
605
wip.remove_at(selected_point.vertex);
606
_wip_changed();
607
selected_point = wip.size() - 1;
608
canvas_item_editor->update_viewport();
609
return true;
610
}
611
} else {
612
const Vertex active_point = get_active_point();
613
614
if (active_point.valid()) {
615
remove_point(active_point);
616
return true;
617
}
618
}
619
} else if (wip_active && k->get_keycode() == Key::ENTER) {
620
_wip_close();
621
} else if (wip_active && k->get_keycode() == Key::ESCAPE) {
622
_wip_cancel();
623
}
624
}
625
626
return false;
627
}
628
629
void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
630
if (!_get_node()) {
631
return;
632
}
633
634
if (!_get_node()->is_visible_in_tree()) {
635
return;
636
}
637
638
Viewport *vp = _get_node()->get_viewport();
639
if (vp && !vp->is_visible_subviewport()) {
640
return;
641
}
642
643
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
644
// All polygon points are sharp, so use the sharp handle icon
645
const Ref<Texture2D> handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
646
const Ref<Texture2D> nhandle = get_editor_theme_icon(SNAME("EditorPathNullHandle"));
647
648
Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
649
int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
650
const float outline_size = 4 * EDSCALE;
651
Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
652
Color outline_color = font_color.inverted();
653
654
const Vertex active_point = get_active_point();
655
const int n_polygons = _get_polygon_count();
656
const bool is_closed = !_is_line();
657
658
if (edit_origin_and_center) {
659
const Vector2 &center = _get_geometric_center();
660
if (!center.is_zero_approx()) {
661
const Vector2 point = xform.xform(center);
662
p_overlay->draw_texture(nhandle, point - nhandle->get_size() * 0.5, Color(1, 1, 0.4));
663
Size2 lbl_size = font->get_string_size("c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
664
p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
665
p_overlay->draw_string(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
666
}
667
{
668
const Vector2 point = xform.xform(Vector2());
669
p_overlay->draw_texture(nhandle, point - nhandle->get_size() * 0.5, center.is_equal_approx(Vector2()) ? Color(1, 1, 0.4) : Color(1, 0.4, 1));
670
Size2 lbl_size = font->get_string_size("o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
671
p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
672
p_overlay->draw_string(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
673
}
674
}
675
676
for (int j = -1; j < n_polygons; j++) {
677
if (wip_active && wip_destructive && j != -1) {
678
continue;
679
}
680
681
Vector<Vector2> points;
682
Vector2 offset;
683
684
if (wip_active && j == edited_point.polygon) {
685
points = Variant(wip);
686
offset = Vector2(0, 0);
687
} else {
688
if (j == -1) {
689
continue;
690
}
691
points = _get_polygon(j);
692
offset = _get_offset(j);
693
}
694
695
if (!wip_active && j == edited_point.polygon && EDITOR_GET("editors/polygon_editor/show_previous_outline")) {
696
const Color col = Color(0.5, 0.5, 0.5); // FIXME polygon->get_outline_color();
697
const int n = pre_move_edit.size();
698
for (int i = 0; i < n - (is_closed ? 0 : 1); i++) {
699
Vector2 p, p2;
700
p = pre_move_edit[i] + offset;
701
p2 = pre_move_edit[(i + 1) % n] + offset;
702
703
Vector2 point = xform.xform(p);
704
Vector2 next_point = xform.xform(p2);
705
706
p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));
707
}
708
}
709
710
const int n_points = points.size();
711
const Color col = Color(1, 0.3, 0.1, 0.8);
712
713
for (int i = 0; i < n_points; i++) {
714
const Vertex vertex(j, i);
715
716
const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);
717
const Vector2 point = xform.xform(p);
718
719
if (is_closed || i < n_points - 1) {
720
Vector2 p2;
721
if (j == edited_point.polygon &&
722
((wip_active && i == n_points - 1) || (((i + 1) % n_points) == edited_point.vertex))) {
723
p2 = edited_point.pos;
724
} else {
725
p2 = points[(i + 1) % n_points] + offset;
726
}
727
728
const Vector2 next_point = xform.xform(p2);
729
p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));
730
}
731
}
732
733
for (int i = 0; i < n_points; i++) {
734
const Vertex vertex(j, i);
735
736
const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);
737
const Vector2 point = xform.xform(p);
738
739
const Color overlay_modulate = vertex == active_point ? Color(0.4, 1, 1) : Color(1, 1, 1);
740
p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, overlay_modulate);
741
742
if (vertex == hover_point) {
743
String num = String::num_int64(vertex.vertex);
744
Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
745
p_overlay->draw_string_outline(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
746
p_overlay->draw_string(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
747
}
748
}
749
}
750
751
if (edge_point.valid()) {
752
Ref<Texture2D> add_handle = get_editor_theme_icon(SNAME("EditorHandleAdd"));
753
p_overlay->draw_texture(add_handle, edge_point.pos - add_handle->get_size() * 0.5);
754
}
755
}
756
757
void AbstractPolygon2DEditor::set_edit_origin_and_center(bool p_enabled) {
758
edit_origin_and_center = p_enabled;
759
if (button_center) {
760
button_center->set_visible(edit_origin_and_center);
761
}
762
}
763
764
void AbstractPolygon2DEditor::edit(Node *p_polygon) {
765
if (!canvas_item_editor) {
766
canvas_item_editor = CanvasItemEditor::get_singleton();
767
}
768
769
if (p_polygon) {
770
_set_node(p_polygon);
771
772
// Enable the pencil tool if the polygon is empty.
773
if (_is_empty()) {
774
_menu_option(MODE_CREATE);
775
} else {
776
_menu_option(MODE_EDIT);
777
}
778
779
wip.clear();
780
wip_active = false;
781
edited_point = PosVertex();
782
hover_point = Vertex();
783
selected_point = Vertex();
784
center_drag = false;
785
} else {
786
_set_node(nullptr);
787
}
788
789
canvas_item_editor->update_viewport();
790
}
791
792
void AbstractPolygon2DEditor::remove_point(const Vertex &p_vertex) {
793
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
794
Vector<Vector2> vertices = _get_polygon(p_vertex.polygon);
795
796
if (vertices.size() > (_is_line() ? 2 : 3)) {
797
vertices.remove_at(p_vertex.vertex);
798
799
undo_redo->create_action(TTR("Edit Polygon (Remove Point)"));
800
_action_set_polygon(p_vertex.polygon, vertices);
801
_commit_action();
802
} else {
803
undo_redo->create_action(TTR("Remove Polygon And Point"));
804
_action_remove_polygon(p_vertex.polygon);
805
_commit_action();
806
}
807
808
if (_is_empty()) {
809
_menu_option(MODE_CREATE);
810
}
811
812
hover_point = Vertex();
813
if (selected_point == p_vertex) {
814
selected_point = Vertex();
815
}
816
}
817
818
AbstractPolygon2DEditor::Vertex AbstractPolygon2DEditor::get_active_point() const {
819
return hover_point.valid() ? hover_point : selected_point;
820
}
821
822
AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_point(const Vector2 &p_pos) const {
823
const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
824
825
const int n_polygons = _get_polygon_count();
826
const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
827
828
PosVertex closest;
829
real_t closest_dist = 1e10;
830
831
for (int j = 0; j < n_polygons; j++) {
832
Vector<Vector2> points = _get_polygon(j);
833
const Vector2 offset = _get_offset(j);
834
const int n_points = points.size();
835
836
for (int i = 0; i < n_points; i++) {
837
Vector2 cp = xform.xform(points[i] + offset);
838
839
real_t d = cp.distance_to(p_pos);
840
if (d < closest_dist && d < grab_threshold) {
841
closest_dist = d;
842
closest = PosVertex(j, i, cp);
843
}
844
}
845
}
846
847
return closest;
848
}
849
850
AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(const Vector2 &p_pos) const {
851
const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
852
const real_t eps = grab_threshold * 2;
853
const real_t eps2 = eps * eps;
854
855
const int n_polygons = _get_polygon_count();
856
const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
857
858
PosVertex closest;
859
real_t closest_dist = 1e10;
860
861
for (int j = 0; j < n_polygons; j++) {
862
Vector<Vector2> points = _get_polygon(j);
863
const Vector2 offset = _get_offset(j);
864
const int n_points = points.size();
865
const int n_segments = n_points - (_is_line() ? 1 : 0);
866
867
for (int i = 0; i < n_segments; i++) {
868
const Vector2 segment_a = xform.xform(points[i] + offset);
869
const Vector2 segment_b = xform.xform(points[(i + 1) % n_points] + offset);
870
871
Vector2 cp = Geometry2D::get_closest_point_to_segment(p_pos, segment_a, segment_b);
872
873
if (cp.distance_squared_to(segment_a) < eps2 || cp.distance_squared_to(segment_b) < eps2) {
874
continue; //not valid to reuse point
875
}
876
877
real_t d = cp.distance_to(p_pos);
878
if (d < closest_dist && d < grab_threshold) {
879
closest_dist = d;
880
closest = PosVertex(j, i, cp);
881
}
882
}
883
}
884
885
return closest;
886
}
887
888
AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
889
edited_point = PosVertex();
890
center_drag = false;
891
wip_destructive = p_wip_destructive;
892
893
hover_point = Vertex();
894
selected_point = Vertex();
895
edge_point = PosVertex();
896
897
button_create = memnew(Button);
898
button_create->set_theme_type_variation(SceneStringName(FlatButton));
899
button_create->set_accessibility_name(TTRC("Create Polygon Points"));
900
add_child(button_create);
901
button_create->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_CREATE));
902
button_create->set_toggle_mode(true);
903
904
button_edit = memnew(Button);
905
button_edit->set_theme_type_variation(SceneStringName(FlatButton));
906
button_edit->set_accessibility_name(TTRC("Edit Polygon Points"));
907
add_child(button_edit);
908
button_edit->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_EDIT));
909
button_edit->set_toggle_mode(true);
910
911
button_delete = memnew(Button);
912
button_delete->set_theme_type_variation(SceneStringName(FlatButton));
913
button_delete->set_accessibility_name(TTRC("Delete Polygon Points"));
914
add_child(button_delete);
915
button_delete->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE));
916
button_delete->set_toggle_mode(true);
917
918
button_center = memnew(Button);
919
button_center->set_theme_type_variation(SceneStringName(FlatButton));
920
add_child(button_center);
921
button_center->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(CENTER_POLY));
922
button_center->set_visible(edit_origin_and_center);
923
924
create_resource = memnew(ConfirmationDialog);
925
add_child(create_resource);
926
create_resource->set_ok_button_text(TTR("Create"));
927
}
928
929
void AbstractPolygon2DEditorPlugin::edit(Object *p_object) {
930
Node *polygon_node = Object::cast_to<Node>(p_object);
931
polygon_editor->edit(polygon_node);
932
make_visible(polygon_node != nullptr);
933
}
934
935
bool AbstractPolygon2DEditorPlugin::handles(Object *p_object) const {
936
return p_object->is_class(klass);
937
}
938
939
void AbstractPolygon2DEditorPlugin::make_visible(bool p_visible) {
940
if (p_visible) {
941
polygon_editor->show();
942
} else {
943
polygon_editor->hide();
944
polygon_editor->edit(nullptr);
945
}
946
}
947
948
AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, const String &p_class) :
949
polygon_editor(p_polygon_editor),
950
klass(p_class) {
951
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(polygon_editor);
952
polygon_editor->hide();
953
}
954
955