1 /*************************************************************************/
2 /* collision_polygon_editor_plugin.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 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
31 #include "collision_polygon_editor_plugin.h"
32
33 #include "canvas_item_editor_plugin.h"
34 #include "core/os/file_access.h"
35 #include "core/os/input.h"
36 #include "core/os/keyboard.h"
37 #include "editor/editor_settings.h"
38 #include "scene/3d/camera.h"
39 #include "spatial_editor_plugin.h"
40
_notification(int p_what)41 void Polygon3DEditor::_notification(int p_what) {
42
43 switch (p_what) {
44
45 case NOTIFICATION_READY: {
46
47 button_create->set_icon(get_icon("Edit", "EditorIcons"));
48 button_edit->set_icon(get_icon("MovePoint", "EditorIcons"));
49 button_edit->set_pressed(true);
50 get_tree()->connect("node_removed", this, "_node_removed");
51
52 } break;
53 case NOTIFICATION_PROCESS: {
54 if (!node) {
55 return;
56 }
57
58 if (_get_depth() != prev_depth) {
59 _polygon_draw();
60 prev_depth = _get_depth();
61 }
62
63 } break;
64 }
65 }
_node_removed(Node * p_node)66 void Polygon3DEditor::_node_removed(Node *p_node) {
67
68 if (p_node == node) {
69 node = NULL;
70 if (imgeom->get_parent() == p_node)
71 p_node->remove_child(imgeom);
72 hide();
73 set_process(false);
74 }
75 }
76
_menu_option(int p_option)77 void Polygon3DEditor::_menu_option(int p_option) {
78
79 switch (p_option) {
80
81 case MODE_CREATE: {
82
83 mode = MODE_CREATE;
84 button_create->set_pressed(true);
85 button_edit->set_pressed(false);
86 } break;
87 case MODE_EDIT: {
88
89 mode = MODE_EDIT;
90 button_create->set_pressed(false);
91 button_edit->set_pressed(true);
92 } break;
93 }
94 }
95
_wip_close()96 void Polygon3DEditor::_wip_close() {
97
98 undo_redo->create_action(TTR("Create Polygon3D"));
99 undo_redo->add_undo_method(node, "set_polygon", node->call("get_polygon"));
100 undo_redo->add_do_method(node, "set_polygon", wip);
101 undo_redo->add_do_method(this, "_polygon_draw");
102 undo_redo->add_undo_method(this, "_polygon_draw");
103 wip.clear();
104 wip_active = false;
105 mode = MODE_EDIT;
106 button_edit->set_pressed(true);
107 button_create->set_pressed(false);
108 edited_point = -1;
109 undo_redo->commit_action();
110 }
111
forward_spatial_gui_input(Camera * p_camera,const Ref<InputEvent> & p_event)112 bool Polygon3DEditor::forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event) {
113
114 if (!node)
115 return false;
116
117 Transform gt = node->get_global_transform();
118 Transform gi = gt.affine_inverse();
119 float depth = _get_depth() * 0.5;
120 Vector3 n = gt.basis.get_axis(2).normalized();
121 Plane p(gt.origin + n * depth, n);
122
123 Ref<InputEventMouseButton> mb = p_event;
124
125 if (mb.is_valid()) {
126
127 Vector2 gpoint = mb->get_position();
128 Vector3 ray_from = p_camera->project_ray_origin(gpoint);
129 Vector3 ray_dir = p_camera->project_ray_normal(gpoint);
130
131 Vector3 spoint;
132
133 if (!p.intersects_ray(ray_from, ray_dir, &spoint))
134 return false;
135
136 spoint = gi.xform(spoint);
137
138 Vector2 cpoint(spoint.x, spoint.y);
139
140 //DO NOT snap here, it's confusing in 3D for adding points.
141 //Let the snap happen when the point is being moved, instead.
142 //cpoint = CanvasItemEditor::get_singleton()->snap_point(cpoint);
143
144 Vector<Vector2> poly = node->call("get_polygon");
145
146 //first check if a point is to be added (segment split)
147 real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
148
149 switch (mode) {
150
151 case MODE_CREATE: {
152
153 if (mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) {
154
155 if (!wip_active) {
156
157 wip.clear();
158 wip.push_back(cpoint);
159 wip_active = true;
160 edited_point_pos = cpoint;
161 snap_ignore = false;
162 _polygon_draw();
163 edited_point = 1;
164 return true;
165 } else {
166
167 if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, wip[0].y, depth))).distance_to(gpoint) < grab_threshold) {
168 //wip closed
169 _wip_close();
170
171 return true;
172 } else {
173
174 wip.push_back(cpoint);
175 edited_point = wip.size();
176 snap_ignore = false;
177 _polygon_draw();
178 return true;
179 }
180 }
181 } else if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed() && wip_active) {
182 _wip_close();
183 }
184
185 } break;
186
187 case MODE_EDIT: {
188
189 if (mb->get_button_index() == BUTTON_LEFT) {
190 if (mb->is_pressed()) {
191
192 if (mb->get_control()) {
193
194 if (poly.size() < 3) {
195
196 undo_redo->create_action(TTR("Edit Poly"));
197 undo_redo->add_undo_method(node, "set_polygon", poly);
198 poly.push_back(cpoint);
199 undo_redo->add_do_method(node, "set_polygon", poly);
200 undo_redo->add_do_method(this, "_polygon_draw");
201 undo_redo->add_undo_method(this, "_polygon_draw");
202 undo_redo->commit_action();
203 return true;
204 }
205
206 //search edges
207 int closest_idx = -1;
208 Vector2 closest_pos;
209 real_t closest_dist = 1e10;
210 for (int i = 0; i < poly.size(); i++) {
211
212 Vector2 points[2] = {
213 p_camera->unproject_position(gt.xform(Vector3(poly[i].x, poly[i].y, depth))),
214 p_camera->unproject_position(gt.xform(Vector3(poly[(i + 1) % poly.size()].x, poly[(i + 1) % poly.size()].y, depth)))
215 };
216
217 Vector2 cp = Geometry::get_closest_point_to_segment_2d(gpoint, points);
218 if (cp.distance_squared_to(points[0]) < CMP_EPSILON2 || cp.distance_squared_to(points[1]) < CMP_EPSILON2)
219 continue; //not valid to reuse point
220
221 real_t d = cp.distance_to(gpoint);
222 if (d < closest_dist && d < grab_threshold) {
223 closest_dist = d;
224 closest_pos = cp;
225 closest_idx = i;
226 }
227 }
228
229 if (closest_idx >= 0) {
230
231 pre_move_edit = poly;
232 poly.insert(closest_idx + 1, cpoint);
233 edited_point = closest_idx + 1;
234 edited_point_pos = cpoint;
235 node->call("set_polygon", poly);
236 _polygon_draw();
237 snap_ignore = true;
238
239 return true;
240 }
241 } else {
242
243 //look for points to move
244
245 int closest_idx = -1;
246 Vector2 closest_pos;
247 real_t closest_dist = 1e10;
248 for (int i = 0; i < poly.size(); i++) {
249
250 Vector2 cp = p_camera->unproject_position(gt.xform(Vector3(poly[i].x, poly[i].y, depth)));
251
252 real_t d = cp.distance_to(gpoint);
253 if (d < closest_dist && d < grab_threshold) {
254 closest_dist = d;
255 closest_pos = cp;
256 closest_idx = i;
257 }
258 }
259
260 if (closest_idx >= 0) {
261
262 pre_move_edit = poly;
263 edited_point = closest_idx;
264 edited_point_pos = poly[closest_idx];
265 _polygon_draw();
266 snap_ignore = false;
267 return true;
268 }
269 }
270 } else {
271
272 snap_ignore = false;
273
274 if (edited_point != -1) {
275
276 //apply
277
278 ERR_FAIL_INDEX_V(edited_point, poly.size(), false);
279 poly.write[edited_point] = edited_point_pos;
280 undo_redo->create_action(TTR("Edit Poly"));
281 undo_redo->add_do_method(node, "set_polygon", poly);
282 undo_redo->add_undo_method(node, "set_polygon", pre_move_edit);
283 undo_redo->add_do_method(this, "_polygon_draw");
284 undo_redo->add_undo_method(this, "_polygon_draw");
285 undo_redo->commit_action();
286
287 edited_point = -1;
288 return true;
289 }
290 }
291 }
292 if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed() && edited_point == -1) {
293
294 int closest_idx = -1;
295 Vector2 closest_pos;
296 real_t closest_dist = 1e10;
297 for (int i = 0; i < poly.size(); i++) {
298
299 Vector2 cp = p_camera->unproject_position(gt.xform(Vector3(poly[i].x, poly[i].y, depth)));
300
301 real_t d = cp.distance_to(gpoint);
302 if (d < closest_dist && d < grab_threshold) {
303 closest_dist = d;
304 closest_pos = cp;
305 closest_idx = i;
306 }
307 }
308
309 if (closest_idx >= 0) {
310
311 undo_redo->create_action(TTR("Edit Poly (Remove Point)"));
312 undo_redo->add_undo_method(node, "set_polygon", poly);
313 poly.remove(closest_idx);
314 undo_redo->add_do_method(node, "set_polygon", poly);
315 undo_redo->add_do_method(this, "_polygon_draw");
316 undo_redo->add_undo_method(this, "_polygon_draw");
317 undo_redo->commit_action();
318 return true;
319 }
320 }
321
322 } break;
323 }
324 }
325
326 Ref<InputEventMouseMotion> mm = p_event;
327
328 if (mm.is_valid()) {
329 if (edited_point != -1 && (wip_active || mm->get_button_mask() & BUTTON_MASK_LEFT)) {
330
331 Vector2 gpoint = mm->get_position();
332
333 Vector3 ray_from = p_camera->project_ray_origin(gpoint);
334 Vector3 ray_dir = p_camera->project_ray_normal(gpoint);
335
336 Vector3 spoint;
337
338 if (!p.intersects_ray(ray_from, ray_dir, &spoint))
339 return false;
340
341 spoint = gi.xform(spoint);
342
343 Vector2 cpoint(spoint.x, spoint.y);
344
345 if (snap_ignore && !Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
346 snap_ignore = false;
347 }
348
349 if (!snap_ignore && SpatialEditor::get_singleton()->is_snap_enabled()) {
350 cpoint = cpoint.snapped(Vector2(
351 SpatialEditor::get_singleton()->get_translate_snap(),
352 SpatialEditor::get_singleton()->get_translate_snap()));
353 }
354 edited_point_pos = cpoint;
355
356 _polygon_draw();
357 }
358 }
359
360 return false;
361 }
362
_get_depth()363 float Polygon3DEditor::_get_depth() {
364
365 if (bool(node->call("_has_editable_3d_polygon_no_depth")))
366 return 0;
367
368 return float(node->call("get_depth"));
369 }
370
_polygon_draw()371 void Polygon3DEditor::_polygon_draw() {
372
373 if (!node)
374 return;
375
376 Vector<Vector2> poly;
377
378 if (wip_active)
379 poly = wip;
380 else
381 poly = node->call("get_polygon");
382
383 float depth = _get_depth() * 0.5;
384
385 imgeom->clear();
386 imgeom->set_material_override(line_material);
387 imgeom->begin(Mesh::PRIMITIVE_LINES, Ref<Texture>());
388
389 Rect2 rect;
390
391 for (int i = 0; i < poly.size(); i++) {
392
393 Vector2 p, p2;
394 p = i == edited_point ? edited_point_pos : poly[i];
395 if ((wip_active && i == poly.size() - 1) || (((i + 1) % poly.size()) == edited_point))
396 p2 = edited_point_pos;
397 else
398 p2 = poly[(i + 1) % poly.size()];
399
400 if (i == 0)
401 rect.position = p;
402 else
403 rect.expand_to(p);
404
405 Vector3 point = Vector3(p.x, p.y, depth);
406 Vector3 next_point = Vector3(p2.x, p2.y, depth);
407
408 imgeom->set_color(Color(1, 0.3, 0.1, 0.8));
409 imgeom->add_vertex(point);
410 imgeom->set_color(Color(1, 0.3, 0.1, 0.8));
411 imgeom->add_vertex(next_point);
412
413 //Color col=Color(1,0.3,0.1,0.8);
414 //vpc->draw_line(point,next_point,col,2);
415 //vpc->draw_texture(handle,point-handle->get_size()*0.5);
416 }
417
418 rect = rect.grow(1);
419
420 AABB r;
421 r.position.x = rect.position.x;
422 r.position.y = rect.position.y;
423 r.position.z = depth;
424 r.size.x = rect.size.x;
425 r.size.y = rect.size.y;
426 r.size.z = 0;
427
428 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
429 imgeom->add_vertex(r.position);
430 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
431 imgeom->add_vertex(r.position + Vector3(0.3, 0, 0));
432 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
433 imgeom->add_vertex(r.position);
434 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
435 imgeom->add_vertex(r.position + Vector3(0.0, 0.3, 0));
436
437 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
438 imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0));
439 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
440 imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0) - Vector3(0.3, 0, 0));
441 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
442 imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0));
443 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
444 imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0) + Vector3(0, 0.3, 0));
445
446 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
447 imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0));
448 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
449 imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0) - Vector3(0, 0.3, 0));
450 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
451 imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0));
452 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
453 imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0) + Vector3(0.3, 0, 0));
454
455 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
456 imgeom->add_vertex(r.position + r.size);
457 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
458 imgeom->add_vertex(r.position + r.size - Vector3(0.3, 0, 0));
459 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
460 imgeom->add_vertex(r.position + r.size);
461 imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
462 imgeom->add_vertex(r.position + r.size - Vector3(0.0, 0.3, 0));
463
464 imgeom->end();
465
466 while (m->get_surface_count()) {
467 m->surface_remove(0);
468 }
469
470 if (poly.size() == 0)
471 return;
472
473 Array a;
474 a.resize(Mesh::ARRAY_MAX);
475 PoolVector<Vector3> va;
476 {
477
478 va.resize(poly.size());
479 PoolVector<Vector3>::Write w = va.write();
480 for (int i = 0; i < poly.size(); i++) {
481
482 Vector2 p, p2;
483 p = i == edited_point ? edited_point_pos : poly[i];
484
485 Vector3 point = Vector3(p.x, p.y, depth);
486 w[i] = point;
487 }
488 }
489 a[Mesh::ARRAY_VERTEX] = va;
490 m->add_surface_from_arrays(Mesh::PRIMITIVE_POINTS, a);
491 m->surface_set_material(0, handle_material);
492 }
493
edit(Node * p_collision_polygon)494 void Polygon3DEditor::edit(Node *p_collision_polygon) {
495
496 if (p_collision_polygon) {
497
498 node = Object::cast_to<Spatial>(p_collision_polygon);
499 //Enable the pencil tool if the polygon is empty
500 if (Vector<Vector2>(node->call("get_polygon")).size() == 0) {
501 _menu_option(MODE_CREATE);
502 }
503 wip.clear();
504 wip_active = false;
505 edited_point = -1;
506 p_collision_polygon->add_child(imgeom);
507 _polygon_draw();
508 set_process(true);
509 prev_depth = -1;
510
511 } else {
512 node = NULL;
513
514 if (imgeom->get_parent())
515 imgeom->get_parent()->remove_child(imgeom);
516
517 set_process(false);
518 }
519 }
520
_bind_methods()521 void Polygon3DEditor::_bind_methods() {
522
523 ClassDB::bind_method(D_METHOD("_menu_option"), &Polygon3DEditor::_menu_option);
524 ClassDB::bind_method(D_METHOD("_polygon_draw"), &Polygon3DEditor::_polygon_draw);
525 ClassDB::bind_method(D_METHOD("_node_removed"), &Polygon3DEditor::_node_removed);
526 }
527
Polygon3DEditor(EditorNode * p_editor)528 Polygon3DEditor::Polygon3DEditor(EditorNode *p_editor) {
529
530 node = NULL;
531 editor = p_editor;
532 undo_redo = EditorNode::get_undo_redo();
533
534 add_child(memnew(VSeparator));
535 button_create = memnew(ToolButton);
536 add_child(button_create);
537 button_create->connect("pressed", this, "_menu_option", varray(MODE_CREATE));
538 button_create->set_toggle_mode(true);
539
540 button_edit = memnew(ToolButton);
541 add_child(button_edit);
542 button_edit->connect("pressed", this, "_menu_option", varray(MODE_EDIT));
543 button_edit->set_toggle_mode(true);
544
545 mode = MODE_EDIT;
546 wip_active = false;
547 imgeom = memnew(ImmediateGeometry);
548 imgeom->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001)));
549
550 line_material = Ref<SpatialMaterial>(memnew(SpatialMaterial));
551 line_material->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
552 line_material->set_line_width(3.0);
553 line_material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
554 line_material->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
555 line_material->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
556 line_material->set_albedo(Color(1, 1, 1));
557
558 handle_material = Ref<SpatialMaterial>(memnew(SpatialMaterial));
559 handle_material->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
560 handle_material->set_flag(SpatialMaterial::FLAG_USE_POINT_SIZE, true);
561 handle_material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
562 handle_material->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
563 handle_material->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
564 Ref<Texture> handle = editor->get_gui_base()->get_icon("Editor3DHandle", "EditorIcons");
565 handle_material->set_point_size(handle->get_width());
566 handle_material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, handle);
567
568 pointsm = memnew(MeshInstance);
569 imgeom->add_child(pointsm);
570 m.instance();
571 pointsm->set_mesh(m);
572 pointsm->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001)));
573
574 snap_ignore = false;
575 }
576
~Polygon3DEditor()577 Polygon3DEditor::~Polygon3DEditor() {
578
579 memdelete(imgeom);
580 }
581
edit(Object * p_object)582 void Polygon3DEditorPlugin::edit(Object *p_object) {
583
584 collision_polygon_editor->edit(Object::cast_to<Node>(p_object));
585 }
586
handles(Object * p_object) const587 bool Polygon3DEditorPlugin::handles(Object *p_object) const {
588
589 return Object::cast_to<Spatial>(p_object) && bool(p_object->call("_is_editable_3d_polygon"));
590 }
591
make_visible(bool p_visible)592 void Polygon3DEditorPlugin::make_visible(bool p_visible) {
593
594 if (p_visible) {
595 collision_polygon_editor->show();
596 } else {
597
598 collision_polygon_editor->hide();
599 collision_polygon_editor->edit(NULL);
600 }
601 }
602
Polygon3DEditorPlugin(EditorNode * p_node)603 Polygon3DEditorPlugin::Polygon3DEditorPlugin(EditorNode *p_node) {
604
605 editor = p_node;
606 collision_polygon_editor = memnew(Polygon3DEditor(p_node));
607 SpatialEditor::get_singleton()->add_control_to_menu_panel(collision_polygon_editor);
608
609 collision_polygon_editor->hide();
610 }
611
~Polygon3DEditorPlugin()612 Polygon3DEditorPlugin::~Polygon3DEditorPlugin() {
613 }
614