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