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