| 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 | |