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 | |
45 | bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const { |
46 | return polygon == p_vertex.polygon && vertex == p_vertex.vertex; |
47 | } |
48 | |
49 | bool AbstractPolygon2DEditor::Vertex::operator!=(const AbstractPolygon2DEditor::Vertex &p_vertex) const { |
50 | return !(*this == p_vertex); |
51 | } |
52 | |
53 | bool AbstractPolygon2DEditor::Vertex::valid() const { |
54 | return vertex >= 0; |
55 | } |
56 | |
57 | bool 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 | |
75 | bool AbstractPolygon2DEditor::_is_line() const { |
76 | return false; |
77 | } |
78 | |
79 | bool AbstractPolygon2DEditor::_has_uv() const { |
80 | return false; |
81 | } |
82 | |
83 | int AbstractPolygon2DEditor::_get_polygon_count() const { |
84 | return 1; |
85 | } |
86 | |
87 | Variant AbstractPolygon2DEditor::_get_polygon(int p_idx) const { |
88 | return _get_node()->get("polygon" ); |
89 | } |
90 | |
91 | void AbstractPolygon2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const { |
92 | _get_node()->set("polygon" , p_polygon); |
93 | } |
94 | |
95 | void 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 | |
102 | Vector2 AbstractPolygon2DEditor::_get_offset(int p_idx) const { |
103 | return Vector2(0, 0); |
104 | } |
105 | |
106 | void 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 | |
113 | void AbstractPolygon2DEditor::_action_add_polygon(const Variant &p_polygon) { |
114 | _action_set_polygon(0, p_polygon); |
115 | } |
116 | |
117 | void AbstractPolygon2DEditor::_action_remove_polygon(int p_idx) { |
118 | _action_set_polygon(p_idx, _get_polygon(p_idx), Vector<Vector2>()); |
119 | } |
120 | |
121 | void 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 | |
125 | bool AbstractPolygon2DEditor::_has_resource() const { |
126 | return true; |
127 | } |
128 | |
129 | void AbstractPolygon2DEditor::_create_resource() { |
130 | } |
131 | |
132 | void AbstractPolygon2DEditor::(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 | |
157 | void 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 | |
177 | void 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 | |
186 | void AbstractPolygon2DEditor::_wip_changed() { |
187 | if (wip_active && _is_line()) { |
188 | _set_polygon(0, wip); |
189 | } |
190 | } |
191 | |
192 | void 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 | |
203 | void 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 | |
236 | void 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 | |
254 | bool 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 | |
491 | void 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 | |
594 | void 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 | |
621 | void AbstractPolygon2DEditor::_bind_methods() { |
622 | } |
623 | |
624 | void 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 | |
650 | AbstractPolygon2DEditor::Vertex AbstractPolygon2DEditor::get_active_point() const { |
651 | return hover_point.valid() ? hover_point : selected_point; |
652 | } |
653 | |
654 | AbstractPolygon2DEditor::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 | |
682 | AbstractPolygon2DEditor::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 | |
720 | AbstractPolygon2DEditor::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 | |
751 | void AbstractPolygon2DEditorPlugin::edit(Object *p_object) { |
752 | polygon_editor->edit(Object::cast_to<Node>(p_object)); |
753 | } |
754 | |
755 | bool AbstractPolygon2DEditorPlugin::handles(Object *p_object) const { |
756 | return p_object->is_class(klass); |
757 | } |
758 | |
759 | void 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 | |
768 | AbstractPolygon2DEditorPlugin::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 | |
775 | AbstractPolygon2DEditorPlugin::~AbstractPolygon2DEditorPlugin() { |
776 | } |
777 | |