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