1 /*************************************************************************/
2 /*  path_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 "path_editor_plugin.h"
32 
33 #include "core/os/keyboard.h"
34 #include "scene/resources/curve.h"
35 #include "spatial_editor_plugin.h"
36 
get_handle_name(int p_idx) const37 String PathSpatialGizmo::get_handle_name(int p_idx) const {
38 
39 	Ref<Curve3D> c = path->get_curve();
40 	if (c.is_null())
41 		return "";
42 
43 	if (p_idx < c->get_point_count()) {
44 
45 		return TTR("Curve Point #") + itos(p_idx);
46 	}
47 
48 	p_idx = p_idx - c->get_point_count() + 1;
49 
50 	int idx = p_idx / 2;
51 	int t = p_idx % 2;
52 	String n = TTR("Curve Point #") + itos(idx);
53 	if (t == 0)
54 		n += " In";
55 	else
56 		n += " Out";
57 
58 	return n;
59 }
get_handle_value(int p_idx)60 Variant PathSpatialGizmo::get_handle_value(int p_idx) {
61 
62 	Ref<Curve3D> c = path->get_curve();
63 	if (c.is_null())
64 		return Variant();
65 
66 	if (p_idx < c->get_point_count()) {
67 
68 		original = c->get_point_position(p_idx);
69 		return original;
70 	}
71 
72 	p_idx = p_idx - c->get_point_count() + 1;
73 
74 	int idx = p_idx / 2;
75 	int t = p_idx % 2;
76 
77 	Vector3 ofs;
78 	if (t == 0)
79 		ofs = c->get_point_in(idx);
80 	else
81 		ofs = c->get_point_out(idx);
82 
83 	original = ofs + c->get_point_position(idx);
84 
85 	return ofs;
86 }
set_handle(int p_idx,Camera * p_camera,const Point2 & p_point)87 void PathSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) {
88 
89 	Ref<Curve3D> c = path->get_curve();
90 	if (c.is_null())
91 		return;
92 
93 	Transform gt = path->get_global_transform();
94 	Transform gi = gt.affine_inverse();
95 	Vector3 ray_from = p_camera->project_ray_origin(p_point);
96 	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
97 
98 	// Setting curve point positions
99 	if (p_idx < c->get_point_count()) {
100 
101 		Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2));
102 
103 		Vector3 inters;
104 
105 		if (p.intersects_ray(ray_from, ray_dir, &inters)) {
106 
107 			if (SpatialEditor::get_singleton()->is_snap_enabled()) {
108 				float snap = SpatialEditor::get_singleton()->get_translate_snap();
109 				inters.snap(Vector3(snap, snap, snap));
110 			}
111 
112 			Vector3 local = gi.xform(inters);
113 			c->set_point_position(p_idx, local);
114 		}
115 
116 		return;
117 	}
118 
119 	p_idx = p_idx - c->get_point_count() + 1;
120 
121 	int idx = p_idx / 2;
122 	int t = p_idx % 2;
123 
124 	Vector3 base = c->get_point_position(idx);
125 
126 	Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2));
127 
128 	Vector3 inters;
129 
130 	// Setting curve in/out positions
131 	if (p.intersects_ray(ray_from, ray_dir, &inters)) {
132 
133 		if (!PathEditorPlugin::singleton->is_handle_clicked()) {
134 			orig_in_length = c->get_point_in(idx).length();
135 			orig_out_length = c->get_point_out(idx).length();
136 			PathEditorPlugin::singleton->set_handle_clicked(true);
137 		}
138 
139 		Vector3 local = gi.xform(inters) - base;
140 		if (SpatialEditor::get_singleton()->is_snap_enabled()) {
141 			float snap = SpatialEditor::get_singleton()->get_translate_snap();
142 			local.snap(Vector3(snap, snap, snap));
143 		}
144 
145 		if (t == 0) {
146 			c->set_point_in(idx, local);
147 			if (PathEditorPlugin::singleton->mirror_angle_enabled())
148 				c->set_point_out(idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -local : (-local.normalized() * orig_out_length));
149 		} else {
150 			c->set_point_out(idx, local);
151 			if (PathEditorPlugin::singleton->mirror_angle_enabled())
152 				c->set_point_in(idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -local : (-local.normalized() * orig_in_length));
153 		}
154 	}
155 }
156 
commit_handle(int p_idx,const Variant & p_restore,bool p_cancel)157 void PathSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
158 
159 	Ref<Curve3D> c = path->get_curve();
160 	if (c.is_null())
161 		return;
162 
163 	UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
164 
165 	if (p_idx < c->get_point_count()) {
166 
167 		if (p_cancel) {
168 
169 			c->set_point_position(p_idx, p_restore);
170 			return;
171 		}
172 		ur->create_action(TTR("Set Curve Point Position"));
173 		ur->add_do_method(c.ptr(), "set_point_position", p_idx, c->get_point_position(p_idx));
174 		ur->add_undo_method(c.ptr(), "set_point_position", p_idx, p_restore);
175 		ur->commit_action();
176 
177 		return;
178 	}
179 
180 	p_idx = p_idx - c->get_point_count() + 1;
181 
182 	int idx = p_idx / 2;
183 	int t = p_idx % 2;
184 
185 	if (t == 0) {
186 		if (p_cancel) {
187 			c->set_point_in(p_idx, p_restore);
188 			return;
189 		}
190 
191 		ur->create_action(TTR("Set Curve In Position"));
192 		ur->add_do_method(c.ptr(), "set_point_in", idx, c->get_point_in(idx));
193 		ur->add_undo_method(c.ptr(), "set_point_in", idx, p_restore);
194 
195 		if (PathEditorPlugin::singleton->mirror_angle_enabled()) {
196 			ur->add_do_method(c.ptr(), "set_point_out", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_in(idx) : (-c->get_point_in(idx).normalized() * orig_out_length));
197 			ur->add_undo_method(c.ptr(), "set_point_out", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_out_length));
198 		}
199 		ur->commit_action();
200 
201 	} else {
202 		if (p_cancel) {
203 			c->set_point_out(idx, p_restore);
204 
205 			return;
206 		}
207 
208 		ur->create_action(TTR("Set Curve Out Position"));
209 		ur->add_do_method(c.ptr(), "set_point_out", idx, c->get_point_out(idx));
210 		ur->add_undo_method(c.ptr(), "set_point_out", idx, p_restore);
211 
212 		if (PathEditorPlugin::singleton->mirror_angle_enabled()) {
213 			ur->add_do_method(c.ptr(), "set_point_in", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_out(idx) : (-c->get_point_out(idx).normalized() * orig_in_length));
214 			ur->add_undo_method(c.ptr(), "set_point_in", idx, PathEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_in_length));
215 		}
216 		ur->commit_action();
217 	}
218 }
219 
redraw()220 void PathSpatialGizmo::redraw() {
221 
222 	clear();
223 
224 	Ref<SpatialMaterial> path_material = gizmo_plugin->get_material("path_material", this);
225 	Ref<SpatialMaterial> path_thin_material = gizmo_plugin->get_material("path_thin_material", this);
226 	Ref<SpatialMaterial> handles_material = gizmo_plugin->get_material("handles");
227 
228 	Ref<Curve3D> c = path->get_curve();
229 	if (c.is_null())
230 		return;
231 
232 	PoolVector<Vector3> v3a = c->tessellate();
233 	//PoolVector<Vector3> v3a=c->get_baked_points();
234 
235 	int v3s = v3a.size();
236 	if (v3s == 0)
237 		return;
238 	Vector<Vector3> v3p;
239 	PoolVector<Vector3>::Read r = v3a.read();
240 
241 	// BUG: the following won't work when v3s, avoid drawing as a temporary workaround.
242 	for (int i = 0; i < v3s - 1; i++) {
243 
244 		v3p.push_back(r[i]);
245 		v3p.push_back(r[i + 1]);
246 		//v3p.push_back(r[i]);
247 		//v3p.push_back(r[i]+Vector3(0,0.2,0));
248 	}
249 
250 	if (v3p.size() > 1) {
251 		add_lines(v3p, path_material);
252 		add_collision_segments(v3p);
253 	}
254 
255 	if (PathEditorPlugin::singleton->get_edited_path() == path) {
256 		v3p.clear();
257 		Vector<Vector3> handles;
258 		Vector<Vector3> sec_handles;
259 
260 		for (int i = 0; i < c->get_point_count(); i++) {
261 
262 			Vector3 p = c->get_point_position(i);
263 			handles.push_back(p);
264 			if (i > 0) {
265 				v3p.push_back(p);
266 				v3p.push_back(p + c->get_point_in(i));
267 				sec_handles.push_back(p + c->get_point_in(i));
268 			}
269 
270 			if (i < c->get_point_count() - 1) {
271 				v3p.push_back(p);
272 				v3p.push_back(p + c->get_point_out(i));
273 				sec_handles.push_back(p + c->get_point_out(i));
274 			}
275 		}
276 
277 		if (v3p.size() > 1) {
278 			add_lines(v3p, path_thin_material);
279 		}
280 		if (handles.size()) {
281 			add_handles(handles, handles_material);
282 		}
283 		if (sec_handles.size()) {
284 			add_handles(sec_handles, handles_material, false, true);
285 		}
286 	}
287 }
288 
PathSpatialGizmo(Path * p_path)289 PathSpatialGizmo::PathSpatialGizmo(Path *p_path) {
290 
291 	path = p_path;
292 	set_spatial_node(p_path);
293 }
294 
forward_spatial_gui_input(Camera * p_camera,const Ref<InputEvent> & p_event)295 bool PathEditorPlugin::forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event) {
296 
297 	if (!path)
298 		return false;
299 	Ref<Curve3D> c = path->get_curve();
300 	if (c.is_null())
301 		return false;
302 	Transform gt = path->get_global_transform();
303 	Transform it = gt.affine_inverse();
304 
305 	static const int click_dist = 10; //should make global
306 
307 	Ref<InputEventMouseButton> mb = p_event;
308 
309 	if (mb.is_valid()) {
310 
311 		Point2 mbpos(mb->get_position().x, mb->get_position().y);
312 
313 		if (!mb->is_pressed())
314 			set_handle_clicked(false);
315 
316 		if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->get_control()))) {
317 			//click into curve, break it down
318 			PoolVector<Vector3> v3a = c->tessellate();
319 			int idx = 0;
320 			int rc = v3a.size();
321 			int closest_seg = -1;
322 			Vector3 closest_seg_point;
323 			float closest_d = 1e20;
324 
325 			if (rc >= 2) {
326 				PoolVector<Vector3>::Read r = v3a.read();
327 
328 				if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist)
329 					return false; //nope, existing
330 
331 				for (int i = 0; i < c->get_point_count() - 1; i++) {
332 					//find the offset and point index of the place to break up
333 					int j = idx;
334 					if (p_camera->unproject_position(gt.xform(c->get_point_position(i + 1))).distance_to(mbpos) < click_dist)
335 						return false; //nope, existing
336 
337 					while (j < rc && c->get_point_position(i + 1) != r[j]) {
338 
339 						Vector3 from = r[j];
340 						Vector3 to = r[j + 1];
341 						real_t cdist = from.distance_to(to);
342 						from = gt.xform(from);
343 						to = gt.xform(to);
344 						if (cdist > 0) {
345 							Vector2 s[2];
346 							s[0] = p_camera->unproject_position(from);
347 							s[1] = p_camera->unproject_position(to);
348 							Vector2 inters = Geometry::get_closest_point_to_segment_2d(mbpos, s);
349 							float d = inters.distance_to(mbpos);
350 
351 							if (d < 10 && d < closest_d) {
352 
353 								closest_d = d;
354 								closest_seg = i;
355 								Vector3 ray_from = p_camera->project_ray_origin(mbpos);
356 								Vector3 ray_dir = p_camera->project_ray_normal(mbpos);
357 
358 								Vector3 ra, rb;
359 								Geometry::get_closest_points_between_segments(ray_from, ray_from + ray_dir * 4096, from, to, ra, rb);
360 
361 								closest_seg_point = it.xform(rb);
362 							}
363 						}
364 						j++;
365 					}
366 					if (idx == j)
367 						idx++; //force next
368 					else
369 						idx = j; //swap
370 
371 					if (j == rc)
372 						break;
373 				}
374 			}
375 
376 			UndoRedo *ur = editor->get_undo_redo();
377 			if (closest_seg != -1) {
378 				//subdivide
379 
380 				ur->create_action(TTR("Split Path"));
381 				ur->add_do_method(c.ptr(), "add_point", closest_seg_point, Vector3(), Vector3(), closest_seg + 1);
382 				ur->add_undo_method(c.ptr(), "remove_point", closest_seg + 1);
383 				ur->commit_action();
384 				return true;
385 
386 			} else {
387 
388 				Vector3 org;
389 				if (c->get_point_count() == 0)
390 					org = path->get_transform().get_origin();
391 				else
392 					org = gt.xform(c->get_point_position(c->get_point_count() - 1));
393 				Plane p(org, p_camera->get_transform().basis.get_axis(2));
394 				Vector3 ray_from = p_camera->project_ray_origin(mbpos);
395 				Vector3 ray_dir = p_camera->project_ray_normal(mbpos);
396 
397 				Vector3 inters;
398 				if (p.intersects_ray(ray_from, ray_dir, &inters)) {
399 
400 					ur->create_action(TTR("Add Point to Curve"));
401 					ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1);
402 					ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count());
403 					ur->commit_action();
404 					return true;
405 				}
406 
407 				//add new at pos
408 			}
409 
410 		} else if (mb->is_pressed() && ((mb->get_button_index() == BUTTON_LEFT && curve_del->is_pressed()) || (mb->get_button_index() == BUTTON_RIGHT && curve_edit->is_pressed()))) {
411 
412 			for (int i = 0; i < c->get_point_count(); i++) {
413 				real_t dist_to_p = p_camera->unproject_position(gt.xform(c->get_point_position(i))).distance_to(mbpos);
414 				real_t dist_to_p_out = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_out(i))).distance_to(mbpos);
415 				real_t dist_to_p_in = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_in(i))).distance_to(mbpos);
416 
417 				// Find the offset and point index of the place to break up.
418 				// Also check for the control points.
419 				if (dist_to_p < click_dist) {
420 
421 					UndoRedo *ur = editor->get_undo_redo();
422 					ur->create_action(TTR("Remove Path Point"));
423 					ur->add_do_method(c.ptr(), "remove_point", i);
424 					ur->add_undo_method(c.ptr(), "add_point", c->get_point_position(i), c->get_point_in(i), c->get_point_out(i), i);
425 					ur->commit_action();
426 					return true;
427 				} else if (dist_to_p_out < click_dist) {
428 
429 					UndoRedo *ur = editor->get_undo_redo();
430 					ur->create_action(TTR("Remove Out-Control Point"));
431 					ur->add_do_method(c.ptr(), "set_point_out", i, Vector3());
432 					ur->add_undo_method(c.ptr(), "set_point_out", i, c->get_point_out(i));
433 					ur->commit_action();
434 					return true;
435 				} else if (dist_to_p_in < click_dist) {
436 
437 					UndoRedo *ur = editor->get_undo_redo();
438 					ur->create_action(TTR("Remove In-Control Point"));
439 					ur->add_do_method(c.ptr(), "set_point_in", i, Vector3());
440 					ur->add_undo_method(c.ptr(), "set_point_in", i, c->get_point_in(i));
441 					ur->commit_action();
442 					return true;
443 				}
444 			}
445 		}
446 	}
447 
448 	return false;
449 }
450 
edit(Object * p_object)451 void PathEditorPlugin::edit(Object *p_object) {
452 
453 	if (p_object) {
454 		path = Object::cast_to<Path>(p_object);
455 		if (path) {
456 
457 			if (path->get_curve().is_valid()) {
458 				path->get_curve()->emit_signal("changed");
459 			}
460 		}
461 	} else {
462 		Path *pre = path;
463 		path = NULL;
464 		if (pre) {
465 			pre->get_curve()->emit_signal("changed");
466 		}
467 	}
468 	//collision_polygon_editor->edit(Object::cast_to<Node>(p_object));
469 }
470 
handles(Object * p_object) const471 bool PathEditorPlugin::handles(Object *p_object) const {
472 
473 	return p_object->is_class("Path");
474 }
475 
make_visible(bool p_visible)476 void PathEditorPlugin::make_visible(bool p_visible) {
477 
478 	if (p_visible) {
479 
480 		curve_create->show();
481 		curve_edit->show();
482 		curve_del->show();
483 		curve_close->show();
484 		handle_menu->show();
485 		sep->show();
486 	} else {
487 
488 		curve_create->hide();
489 		curve_edit->hide();
490 		curve_del->hide();
491 		curve_close->hide();
492 		handle_menu->hide();
493 		sep->hide();
494 
495 		{
496 			Path *pre = path;
497 			path = NULL;
498 			if (pre && pre->get_curve().is_valid()) {
499 				pre->get_curve()->emit_signal("changed");
500 			}
501 		}
502 	}
503 }
504 
_mode_changed(int p_idx)505 void PathEditorPlugin::_mode_changed(int p_idx) {
506 
507 	curve_create->set_pressed(p_idx == 0);
508 	curve_edit->set_pressed(p_idx == 1);
509 	curve_del->set_pressed(p_idx == 2);
510 }
511 
_close_curve()512 void PathEditorPlugin::_close_curve() {
513 
514 	Ref<Curve3D> c = path->get_curve();
515 	if (c.is_null())
516 		return;
517 	if (c->get_point_count() < 2)
518 		return;
519 	c->add_point(c->get_point_position(0), c->get_point_in(0), c->get_point_out(0));
520 }
521 
_handle_option_pressed(int p_option)522 void PathEditorPlugin::_handle_option_pressed(int p_option) {
523 
524 	PopupMenu *pm;
525 	pm = handle_menu->get_popup();
526 
527 	switch (p_option) {
528 		case HANDLE_OPTION_ANGLE: {
529 			bool is_checked = pm->is_item_checked(HANDLE_OPTION_ANGLE);
530 			mirror_handle_angle = !is_checked;
531 			pm->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
532 			pm->set_item_disabled(HANDLE_OPTION_LENGTH, !mirror_handle_angle);
533 		} break;
534 		case HANDLE_OPTION_LENGTH: {
535 			bool is_checked = pm->is_item_checked(HANDLE_OPTION_LENGTH);
536 			mirror_handle_length = !is_checked;
537 			pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
538 		} break;
539 	}
540 }
541 
_notification(int p_what)542 void PathEditorPlugin::_notification(int p_what) {
543 
544 	if (p_what == NOTIFICATION_ENTER_TREE) {
545 
546 		curve_create->connect("pressed", this, "_mode_changed", make_binds(0));
547 		curve_edit->connect("pressed", this, "_mode_changed", make_binds(1));
548 		curve_del->connect("pressed", this, "_mode_changed", make_binds(2));
549 		curve_close->connect("pressed", this, "_close_curve");
550 	}
551 }
552 
_bind_methods()553 void PathEditorPlugin::_bind_methods() {
554 
555 	ClassDB::bind_method(D_METHOD("_mode_changed"), &PathEditorPlugin::_mode_changed);
556 	ClassDB::bind_method(D_METHOD("_close_curve"), &PathEditorPlugin::_close_curve);
557 	ClassDB::bind_method(D_METHOD("_handle_option_pressed"), &PathEditorPlugin::_handle_option_pressed);
558 }
559 
560 PathEditorPlugin *PathEditorPlugin::singleton = NULL;
561 
PathEditorPlugin(EditorNode * p_node)562 PathEditorPlugin::PathEditorPlugin(EditorNode *p_node) {
563 
564 	path = NULL;
565 	editor = p_node;
566 	singleton = this;
567 	mirror_handle_angle = true;
568 	mirror_handle_length = true;
569 
570 	Ref<PathSpatialGizmoPlugin> gizmo_plugin;
571 	gizmo_plugin.instance();
572 	SpatialEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
573 
574 	sep = memnew(VSeparator);
575 	sep->hide();
576 	SpatialEditor::get_singleton()->add_control_to_menu_panel(sep);
577 	curve_edit = memnew(ToolButton);
578 	curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("CurveEdit", "EditorIcons"));
579 	curve_edit->set_toggle_mode(true);
580 	curve_edit->hide();
581 	curve_edit->set_focus_mode(Control::FOCUS_NONE);
582 	curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string(KEY_MASK_CMD) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point"));
583 	SpatialEditor::get_singleton()->add_control_to_menu_panel(curve_edit);
584 	curve_create = memnew(ToolButton);
585 	curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("CurveCreate", "EditorIcons"));
586 	curve_create->set_toggle_mode(true);
587 	curve_create->hide();
588 	curve_create->set_focus_mode(Control::FOCUS_NONE);
589 	curve_create->set_tooltip(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)"));
590 	SpatialEditor::get_singleton()->add_control_to_menu_panel(curve_create);
591 	curve_del = memnew(ToolButton);
592 	curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("CurveDelete", "EditorIcons"));
593 	curve_del->set_toggle_mode(true);
594 	curve_del->hide();
595 	curve_del->set_focus_mode(Control::FOCUS_NONE);
596 	curve_del->set_tooltip(TTR("Delete Point"));
597 	SpatialEditor::get_singleton()->add_control_to_menu_panel(curve_del);
598 	curve_close = memnew(ToolButton);
599 	curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("CurveClose", "EditorIcons"));
600 	curve_close->hide();
601 	curve_close->set_focus_mode(Control::FOCUS_NONE);
602 	curve_close->set_tooltip(TTR("Close Curve"));
603 	SpatialEditor::get_singleton()->add_control_to_menu_panel(curve_close);
604 
605 	PopupMenu *menu;
606 
607 	handle_menu = memnew(MenuButton);
608 	handle_menu->set_text(TTR("Options"));
609 	handle_menu->hide();
610 	SpatialEditor::get_singleton()->add_control_to_menu_panel(handle_menu);
611 
612 	menu = handle_menu->get_popup();
613 	menu->add_check_item(TTR("Mirror Handle Angles"));
614 	menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
615 	menu->add_check_item(TTR("Mirror Handle Lengths"));
616 	menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
617 	menu->connect("id_pressed", this, "_handle_option_pressed");
618 
619 	curve_edit->set_pressed(true);
620 	/*
621     collision_polygon_editor = memnew( PathEditor(p_node) );
622     editor->get_viewport()->add_child(collision_polygon_editor);
623     collision_polygon_editor->set_margin(MARGIN_LEFT,200);
624     collision_polygon_editor->set_margin(MARGIN_RIGHT,230);
625     collision_polygon_editor->set_margin(MARGIN_TOP,0);
626     collision_polygon_editor->set_margin(MARGIN_BOTTOM,10);
627     collision_polygon_editor->hide();
628     */
629 }
630 
~PathEditorPlugin()631 PathEditorPlugin::~PathEditorPlugin() {
632 }
633 
create_gizmo(Spatial * p_spatial)634 Ref<EditorSpatialGizmo> PathSpatialGizmoPlugin::create_gizmo(Spatial *p_spatial) {
635 	Ref<PathSpatialGizmo> ref;
636 
637 	Path *path = Object::cast_to<Path>(p_spatial);
638 	if (path) ref = Ref<PathSpatialGizmo>(memnew(PathSpatialGizmo(path)));
639 
640 	return ref;
641 }
642 
get_name() const643 String PathSpatialGizmoPlugin::get_name() const {
644 	return "Path";
645 }
646 
get_priority() const647 int PathSpatialGizmoPlugin::get_priority() const {
648 	return -1;
649 }
650 
PathSpatialGizmoPlugin()651 PathSpatialGizmoPlugin::PathSpatialGizmoPlugin() {
652 
653 	Color path_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/path", Color(0.5, 0.5, 1.0, 0.8));
654 	create_material("path_material", path_color);
655 	create_material("path_thin_material", Color(0.5, 0.5, 0.5));
656 	create_handle_material("handles");
657 }
658