1 /*************************************************************************/
2 /* navigation_polygon_editor_plugin.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
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 #include "navigation_polygon_editor_plugin.h"
31
32 #include "canvas_item_editor_plugin.h"
33 #include "editor/editor_settings.h"
34 #include "os/file_access.h"
35
_notification(int p_what)36 void NavigationPolygonEditor::_notification(int p_what) {
37
38 switch (p_what) {
39
40 case NOTIFICATION_READY: {
41
42 button_create->set_icon(get_icon("Edit", "EditorIcons"));
43 button_edit->set_icon(get_icon("MovePoint", "EditorIcons"));
44 button_edit->set_pressed(true);
45 get_tree()->connect("node_removed", this, "_node_removed");
46 create_nav->connect("confirmed", this, "_create_nav");
47
48 } break;
49 case NOTIFICATION_FIXED_PROCESS: {
50
51 } break;
52 }
53 }
_node_removed(Node * p_node)54 void NavigationPolygonEditor::_node_removed(Node *p_node) {
55
56 if (p_node == node) {
57 node = NULL;
58 hide();
59 canvas_item_editor->get_viewport_control()->update();
60 }
61 }
62
_create_nav()63 void NavigationPolygonEditor::_create_nav() {
64
65 if (!node)
66 return;
67
68 undo_redo->create_action(TTR("Create Navigation Polygon"));
69 undo_redo->add_do_method(node, "set_navigation_polygon", Ref<NavigationPolygon>(memnew(NavigationPolygon)));
70 undo_redo->add_undo_method(node, "set_navigation_polygon", Variant(REF()));
71 undo_redo->commit_action();
72 }
73
_menu_option(int p_option)74 void NavigationPolygonEditor::_menu_option(int p_option) {
75
76 switch (p_option) {
77
78 case MODE_CREATE: {
79
80 mode = MODE_CREATE;
81 button_create->set_pressed(true);
82 button_edit->set_pressed(false);
83 } break;
84 case MODE_EDIT: {
85
86 mode = MODE_EDIT;
87 button_create->set_pressed(false);
88 button_edit->set_pressed(true);
89 } break;
90 }
91 }
92
_wip_close()93 void NavigationPolygonEditor::_wip_close() {
94
95 if (wip.size() >= 3) {
96
97 undo_redo->create_action(TTR("Create Poly"));
98 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "remove_outline", node->get_navigation_polygon()->get_outline_count());
99 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "add_outline", wip);
100 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
101 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
102 undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
103 undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
104 undo_redo->commit_action();
105 mode = MODE_EDIT;
106 button_edit->set_pressed(true);
107 button_create->set_pressed(false);
108 }
109
110 wip.clear();
111 wip_active = false;
112 edited_point = -1;
113 }
114
forward_input_event(const InputEvent & p_event)115 bool NavigationPolygonEditor::forward_input_event(const InputEvent &p_event) {
116
117 if (!node)
118 return false;
119
120 if (node->get_navigation_polygon().is_null()) {
121 if (p_event.type == InputEvent::MOUSE_BUTTON && p_event.mouse_button.button_index == 1 && p_event.mouse_button.pressed) {
122 create_nav->set_text("No NavigationPolygon resource on this node.\nCreate and assign one?");
123 create_nav->popup_centered_minsize();
124 }
125 return (p_event.type == InputEvent::MOUSE_BUTTON && p_event.mouse_button.button_index == 1);
126 }
127
128 switch (p_event.type) {
129
130 case InputEvent::MOUSE_BUTTON: {
131
132 const InputEventMouseButton &mb = p_event.mouse_button;
133
134 Matrix32 xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
135
136 Vector2 gpoint = Point2(mb.x, mb.y);
137 Vector2 cpoint = canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint);
138 cpoint = canvas_item_editor->snap_point(cpoint);
139 cpoint = node->get_global_transform().affine_inverse().xform(cpoint);
140
141 //first check if a point is to be added (segment split)
142 real_t grab_treshold = EDITOR_DEF("poly_editor/point_grab_radius", 8);
143
144 switch (mode) {
145
146 case MODE_CREATE: {
147
148 if (mb.button_index == BUTTON_LEFT && mb.pressed) {
149
150 if (!wip_active) {
151
152 wip.clear();
153 wip.push_back(cpoint);
154 wip_active = true;
155 edited_point_pos = cpoint;
156 edited_outline = -1;
157 canvas_item_editor->get_viewport_control()->update();
158 edited_point = 1;
159 return true;
160 } else {
161
162 if (wip.size() > 1 && xform.xform(wip[0]).distance_to(gpoint) < grab_treshold) {
163 //wip closed
164 _wip_close();
165
166 return true;
167 } else {
168
169 wip.push_back(cpoint);
170 edited_point = wip.size();
171 canvas_item_editor->get_viewport_control()->update();
172 return true;
173
174 //add wip point
175 }
176 }
177 } else if (mb.button_index == BUTTON_RIGHT && mb.pressed && wip_active) {
178 _wip_close();
179 }
180
181 } break;
182
183 case MODE_EDIT: {
184
185 if (mb.button_index == BUTTON_LEFT) {
186 if (mb.pressed) {
187
188 if (mb.mod.control) {
189
190 //search edges
191 int closest_outline = -1;
192 int closest_idx = -1;
193 Vector2 closest_pos;
194 real_t closest_dist = 1e10;
195
196 for (int j = 0; j < node->get_navigation_polygon()->get_outline_count(); j++) {
197
198 DVector<Vector2> points = node->get_navigation_polygon()->get_outline(j);
199
200 int pc = points.size();
201 DVector<Vector2>::Read poly = points.read();
202
203 for (int i = 0; i < pc; i++) {
204
205 Vector2 points[2] = { xform.xform(poly[i]),
206 xform.xform(poly[(i + 1) % pc]) };
207
208 Vector2 cp = Geometry::get_closest_point_to_segment_2d(gpoint, points);
209 if (cp.distance_squared_to(points[0]) < CMP_EPSILON2 || cp.distance_squared_to(points[1]) < CMP_EPSILON2)
210 continue; //not valid to reuse point
211
212 real_t d = cp.distance_to(gpoint);
213 if (d < closest_dist && d < grab_treshold) {
214 closest_dist = d;
215 closest_outline = j;
216 closest_pos = cp;
217 closest_idx = i;
218 }
219 }
220 }
221
222 if (closest_idx >= 0) {
223
224 pre_move_edit = node->get_navigation_polygon()->get_outline(closest_outline);
225 DVector<Point2> poly = pre_move_edit;
226 poly.insert(closest_idx + 1, xform.affine_inverse().xform(closest_pos));
227 edited_point = closest_idx + 1;
228 edited_outline = closest_outline;
229 edited_point_pos = xform.affine_inverse().xform(closest_pos);
230 node->get_navigation_polygon()->set_outline(closest_outline, poly);
231 canvas_item_editor->get_viewport_control()->update();
232 return true;
233 }
234 } else {
235
236 //look for points to move
237 int closest_outline = -1;
238 int closest_idx = -1;
239 Vector2 closest_pos;
240 real_t closest_dist = 1e10;
241
242 for (int j = 0; j < node->get_navigation_polygon()->get_outline_count(); j++) {
243
244 DVector<Vector2> points = node->get_navigation_polygon()->get_outline(j);
245
246 int pc = points.size();
247 DVector<Vector2>::Read poly = points.read();
248
249 for (int i = 0; i < pc; i++) {
250
251 Vector2 cp = xform.xform(poly[i]);
252
253 real_t d = cp.distance_to(gpoint);
254 if (d < closest_dist && d < grab_treshold) {
255 closest_dist = d;
256 closest_pos = cp;
257 closest_outline = j;
258 closest_idx = i;
259 }
260 }
261 }
262
263 if (closest_idx >= 0) {
264
265 pre_move_edit = node->get_navigation_polygon()->get_outline(closest_outline);
266 edited_point = closest_idx;
267 edited_outline = closest_outline;
268 edited_point_pos = xform.affine_inverse().xform(closest_pos);
269 canvas_item_editor->get_viewport_control()->update();
270 return true;
271 }
272 }
273 } else {
274
275 if (edited_point != -1) {
276
277 //apply
278
279 DVector<Vector2> poly = node->get_navigation_polygon()->get_outline(edited_outline);
280 ERR_FAIL_INDEX_V(edited_point, poly.size(), false);
281 poly.set(edited_point, edited_point_pos);
282 undo_redo->create_action(TTR("Edit Poly"));
283 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "set_outline", edited_outline, poly);
284 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "set_outline", edited_outline, pre_move_edit);
285 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
286 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
287 undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
288 undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
289 undo_redo->commit_action();
290
291 edited_point = -1;
292 return true;
293 }
294 }
295 }
296 if (mb.button_index == BUTTON_RIGHT && mb.pressed && edited_point == -1) {
297
298 int closest_outline = -1;
299 int closest_idx = -1;
300 Vector2 closest_pos;
301 real_t closest_dist = 1e10;
302
303 for (int j = 0; j < node->get_navigation_polygon()->get_outline_count(); j++) {
304
305 DVector<Vector2> points = node->get_navigation_polygon()->get_outline(j);
306
307 int pc = points.size();
308 DVector<Vector2>::Read poly = points.read();
309
310 for (int i = 0; i < pc; i++) {
311
312 Vector2 cp = xform.xform(poly[i]);
313
314 real_t d = cp.distance_to(gpoint);
315 if (d < closest_dist && d < grab_treshold) {
316 closest_dist = d;
317 closest_pos = cp;
318 closest_outline = j;
319 closest_idx = i;
320 }
321 }
322 }
323
324 if (closest_idx >= 0) {
325
326 DVector<Vector2> poly = node->get_navigation_polygon()->get_outline(closest_outline);
327
328 if (poly.size() > 3) {
329 undo_redo->create_action(TTR("Edit Poly (Remove Point)"));
330 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "set_outline", closest_outline, poly);
331 poly.remove(closest_idx);
332 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "set_outline", closest_outline, poly);
333 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
334 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
335 undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
336 undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
337 undo_redo->commit_action();
338 } else {
339
340 undo_redo->create_action(TTR("Remove Poly And Point"));
341 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "add_outline_at_index", poly, closest_outline);
342 poly.remove(closest_idx);
343 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "remove_outline", closest_outline);
344 undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
345 undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
346 undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
347 undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
348 undo_redo->commit_action();
349 }
350 return true;
351 }
352 }
353
354 } break;
355 }
356
357 } break;
358 case InputEvent::MOUSE_MOTION: {
359
360 const InputEventMouseMotion &mm = p_event.mouse_motion;
361
362 if (edited_point != -1 && (wip_active || mm.button_mask & BUTTON_MASK_LEFT)) {
363
364 Vector2 gpoint = Point2(mm.x, mm.y);
365 Vector2 cpoint = canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint);
366 cpoint = canvas_item_editor->snap_point(cpoint);
367 edited_point_pos = node->get_global_transform().affine_inverse().xform(cpoint);
368
369 canvas_item_editor->get_viewport_control()->update();
370 }
371
372 } break;
373 }
374
375 return false;
376 }
_canvas_draw()377 void NavigationPolygonEditor::_canvas_draw() {
378
379 if (!node)
380 return;
381
382 Control *vpc = canvas_item_editor->get_viewport_control();
383 if (node->get_navigation_polygon().is_null())
384 return;
385
386 Matrix32 xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
387 Ref<Texture> handle = get_icon("EditorHandle", "EditorIcons");
388
389 for (int j = -1; j < node->get_navigation_polygon()->get_outline_count(); j++) {
390 Vector<Vector2> poly;
391
392 if (wip_active && j == edited_outline) {
393 poly = wip;
394 } else {
395 if (j == -1)
396 continue;
397 poly = Variant(node->get_navigation_polygon()->get_outline(j));
398 }
399
400 for (int i = 0; i < poly.size(); i++) {
401
402 Vector2 p, p2;
403 p = (j == edited_outline && i == edited_point) ? edited_point_pos : poly[i];
404 if (j == edited_outline && ((wip_active && i == poly.size() - 1) || (((i + 1) % poly.size()) == edited_point)))
405 p2 = edited_point_pos;
406 else
407 p2 = poly[(i + 1) % poly.size()];
408
409 Vector2 point = xform.xform(p);
410 Vector2 next_point = xform.xform(p2);
411
412 Color col = Color(1, 0.3, 0.1, 0.8);
413 vpc->draw_line(point, next_point, col, 2);
414 vpc->draw_texture(handle, point - handle->get_size() * 0.5);
415 }
416 }
417 }
418
edit(Node * p_collision_polygon)419 void NavigationPolygonEditor::edit(Node *p_collision_polygon) {
420
421 if (!canvas_item_editor) {
422 canvas_item_editor = CanvasItemEditor::get_singleton();
423 }
424
425 if (p_collision_polygon) {
426
427 node = p_collision_polygon->cast_to<NavigationPolygonInstance>();
428 if (!canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw"))
429 canvas_item_editor->get_viewport_control()->connect("draw", this, "_canvas_draw");
430 wip.clear();
431 wip_active = false;
432 edited_point = -1;
433 canvas_item_editor->get_viewport_control()->update();
434
435 } else {
436 node = NULL;
437
438 if (canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw"))
439 canvas_item_editor->get_viewport_control()->disconnect("draw", this, "_canvas_draw");
440 }
441 }
442
_bind_methods()443 void NavigationPolygonEditor::_bind_methods() {
444
445 ObjectTypeDB::bind_method(_MD("_menu_option"), &NavigationPolygonEditor::_menu_option);
446 ObjectTypeDB::bind_method(_MD("_canvas_draw"), &NavigationPolygonEditor::_canvas_draw);
447 ObjectTypeDB::bind_method(_MD("_node_removed"), &NavigationPolygonEditor::_node_removed);
448 ObjectTypeDB::bind_method(_MD("_create_nav"), &NavigationPolygonEditor::_create_nav);
449 }
450
NavigationPolygonEditor(EditorNode * p_editor)451 NavigationPolygonEditor::NavigationPolygonEditor(EditorNode *p_editor) {
452 node = NULL;
453 canvas_item_editor = NULL;
454 editor = p_editor;
455 undo_redo = editor->get_undo_redo();
456
457 add_child(memnew(VSeparator));
458 button_create = memnew(ToolButton);
459 add_child(button_create);
460 button_create->connect("pressed", this, "_menu_option", varray(MODE_CREATE));
461 button_create->set_toggle_mode(true);
462 button_create->set_tooltip(TTR("Create a new polygon from scratch."));
463
464 button_edit = memnew(ToolButton);
465 add_child(button_edit);
466 button_edit->connect("pressed", this, "_menu_option", varray(MODE_EDIT));
467 button_edit->set_toggle_mode(true);
468 button_edit->set_tooltip(TTR("Edit existing polygon:") + "\n" + TTR("LMB: Move Point.") + "\n" + TTR("Ctrl+LMB: Split Segment.") + "\n" + TTR("RMB: Erase Point."));
469 create_nav = memnew(ConfirmationDialog);
470 add_child(create_nav);
471 create_nav->get_ok()->set_text(TTR("Create"));
472
473 //add_constant_override("separation",0);
474
475 #if 0
476 options = memnew( MenuButton );
477 add_child(options);
478 options->set_area_as_parent_rect();
479 options->set_text("Polygon");
480 //options->get_popup()->add_item("Parse BBCode",PARSE_BBCODE);
481 options->get_popup()->connect("item_pressed", this,"_menu_option");
482 #endif
483
484 mode = MODE_EDIT;
485 wip_active = false;
486 edited_outline = -1;
487 }
488
edit(Object * p_object)489 void NavigationPolygonEditorPlugin::edit(Object *p_object) {
490
491 collision_polygon_editor->edit(p_object->cast_to<Node>());
492 }
493
handles(Object * p_object) const494 bool NavigationPolygonEditorPlugin::handles(Object *p_object) const {
495
496 return p_object->is_type("NavigationPolygonInstance");
497 }
498
make_visible(bool p_visible)499 void NavigationPolygonEditorPlugin::make_visible(bool p_visible) {
500
501 if (p_visible) {
502 collision_polygon_editor->show();
503 } else {
504
505 collision_polygon_editor->hide();
506 collision_polygon_editor->edit(NULL);
507 }
508 }
509
NavigationPolygonEditorPlugin(EditorNode * p_node)510 NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin(EditorNode *p_node) {
511
512 editor = p_node;
513 collision_polygon_editor = memnew(NavigationPolygonEditor(p_node));
514 CanvasItemEditor::get_singleton()->add_control_to_menu_panel(collision_polygon_editor);
515
516 collision_polygon_editor->hide();
517 }
518
~NavigationPolygonEditorPlugin()519 NavigationPolygonEditorPlugin::~NavigationPolygonEditorPlugin() {
520 }
521