1 /*************************************************************************/
2 /* collision_shape_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 "collision_shape_2d_editor_plugin.h"
31
32 #include "canvas_item_editor_plugin.h"
33
34 #include "scene/resources/capsule_shape_2d.h"
35 #include "scene/resources/circle_shape_2d.h"
36 #include "scene/resources/concave_polygon_shape_2d.h"
37 #include "scene/resources/convex_polygon_shape_2d.h"
38 #include "scene/resources/rectangle_shape_2d.h"
39 #include "scene/resources/segment_shape_2d.h"
40 #include "scene/resources/shape_line_2d.h"
41
get_handle_value(int idx) const42 Variant CollisionShape2DEditor::get_handle_value(int idx) const {
43
44 switch (shape_type) {
45 case CAPSULE_SHAPE: {
46 Ref<CapsuleShape2D> capsule = node->get_shape();
47
48 if (idx == 0) {
49 return capsule->get_radius();
50 } else if (idx == 1) {
51 return capsule->get_height();
52 }
53
54 } break;
55
56 case CIRCLE_SHAPE: {
57 Ref<CircleShape2D> circle = node->get_shape();
58
59 if (idx == 0) {
60 return circle->get_radius();
61 }
62
63 } break;
64
65 case CONCAVE_POLYGON_SHAPE: {
66
67 } break;
68
69 case CONVEX_POLYGON_SHAPE: {
70
71 } break;
72
73 case LINE_SHAPE: {
74 Ref<LineShape2D> line = node->get_shape();
75
76 if (idx == 0) {
77 return line->get_d();
78 } else {
79 return line->get_normal();
80 }
81
82 } break;
83
84 case RAY_SHAPE: {
85 Ref<RayShape2D> ray = node->get_shape();
86
87 if (idx == 0) {
88 return ray->get_length();
89 }
90
91 } break;
92
93 case RECTANGLE_SHAPE: {
94 Ref<RectangleShape2D> rect = node->get_shape();
95
96 if (idx < 2) {
97 return rect->get_extents().abs();
98 }
99
100 } break;
101
102 case SEGMENT_SHAPE: {
103 Ref<SegmentShape2D> seg = node->get_shape();
104
105 if (idx == 0) {
106 return seg->get_a();
107 } else if (idx == 1) {
108 return seg->get_b();
109 }
110
111 } break;
112 }
113
114 return Variant();
115 }
116
set_handle(int idx,Point2 & p_point)117 void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
118
119 switch (shape_type) {
120 case CAPSULE_SHAPE: {
121 if (idx < 2) {
122 Ref<CapsuleShape2D> capsule = node->get_shape();
123
124 real_t parameter = Math::abs(p_point[idx]);
125
126 if (idx == 0) {
127 capsule->set_radius(parameter);
128 } else if (idx == 1) {
129 capsule->set_height(parameter * 2 - capsule->get_radius() * 2);
130 }
131
132 canvas_item_editor->get_viewport_control()->update();
133 }
134
135 } break;
136
137 case CIRCLE_SHAPE: {
138 Ref<CircleShape2D> circle = node->get_shape();
139 circle->set_radius(p_point.length());
140
141 canvas_item_editor->get_viewport_control()->update();
142
143 } break;
144
145 case CONCAVE_POLYGON_SHAPE: {
146
147 } break;
148
149 case CONVEX_POLYGON_SHAPE: {
150
151 } break;
152
153 case LINE_SHAPE: {
154 if (idx < 2) {
155 Ref<LineShape2D> line = node->get_shape();
156
157 if (idx == 0) {
158 line->set_d(p_point.length());
159 } else {
160 line->set_normal(p_point.normalized());
161 }
162
163 canvas_item_editor->get_viewport_control()->update();
164 }
165
166 } break;
167
168 case RAY_SHAPE: {
169 Ref<RayShape2D> ray = node->get_shape();
170
171 ray->set_length(Math::abs(p_point.y));
172
173 canvas_item_editor->get_viewport_control()->update();
174
175 } break;
176
177 case RECTANGLE_SHAPE: {
178 if (idx < 2) {
179 Ref<RectangleShape2D> rect = node->get_shape();
180
181 Vector2 extents = rect->get_extents();
182 extents[idx] = p_point[idx];
183
184 rect->set_extents(extents.abs());
185
186 canvas_item_editor->get_viewport_control()->update();
187 }
188
189 } break;
190
191 case SEGMENT_SHAPE: {
192 if (edit_handle < 2) {
193 Ref<SegmentShape2D> seg = node->get_shape();
194
195 if (idx == 0) {
196 seg->set_a(p_point);
197 } else if (idx == 1) {
198 seg->set_b(p_point);
199 }
200
201 canvas_item_editor->get_viewport_control()->update();
202 }
203
204 } break;
205 }
206 }
207
commit_handle(int idx,Variant & p_org)208 void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
209
210 Control *c = canvas_item_editor->get_viewport_control();
211 undo_redo->create_action(TTR("Set Handle"));
212
213 switch (shape_type) {
214 case CAPSULE_SHAPE: {
215 Ref<CapsuleShape2D> capsule = node->get_shape();
216
217 if (idx == 0) {
218 undo_redo->add_do_method(capsule.ptr(), "set_radius", capsule->get_radius());
219 undo_redo->add_do_method(c, "update");
220 undo_redo->add_undo_method(capsule.ptr(), "set_radius", p_org);
221 undo_redo->add_do_method(c, "update");
222 } else if (idx == 1) {
223 undo_redo->add_do_method(capsule.ptr(), "set_height", capsule->get_height());
224 undo_redo->add_do_method(c, "update");
225 undo_redo->add_undo_method(capsule.ptr(), "set_height", p_org);
226 undo_redo->add_undo_method(c, "update");
227 }
228
229 } break;
230
231 case CIRCLE_SHAPE: {
232 Ref<CircleShape2D> circle = node->get_shape();
233
234 undo_redo->add_do_method(circle.ptr(), "set_radius", circle->get_radius());
235 undo_redo->add_do_method(c, "update");
236 undo_redo->add_undo_method(circle.ptr(), "set_radius", p_org);
237 undo_redo->add_undo_method(c, "update");
238
239 } break;
240
241 case CONCAVE_POLYGON_SHAPE: {
242
243 } break;
244
245 case CONVEX_POLYGON_SHAPE: {
246
247 } break;
248
249 case LINE_SHAPE: {
250 Ref<LineShape2D> line = node->get_shape();
251
252 if (idx == 0) {
253 undo_redo->add_do_method(line.ptr(), "set_d", line->get_d());
254 undo_redo->add_do_method(c, "update");
255 undo_redo->add_undo_method(line.ptr(), "set_d", p_org);
256 undo_redo->add_undo_method(c, "update");
257 } else {
258 undo_redo->add_do_method(line.ptr(), "set_normal", line->get_normal());
259 undo_redo->add_do_method(c, "update");
260 undo_redo->add_undo_method(line.ptr(), "set_normal", p_org);
261 undo_redo->add_undo_method(c, "update");
262 }
263
264 } break;
265
266 case RAY_SHAPE: {
267 Ref<RayShape2D> ray = node->get_shape();
268
269 undo_redo->add_do_method(ray.ptr(), "set_length", ray->get_length());
270 undo_redo->add_do_method(c, "update");
271 undo_redo->add_undo_method(ray.ptr(), "set_length", p_org);
272 undo_redo->add_undo_method(c, "update");
273
274 } break;
275
276 case RECTANGLE_SHAPE: {
277 Ref<RectangleShape2D> rect = node->get_shape();
278
279 undo_redo->add_do_method(rect.ptr(), "set_extents", rect->get_extents());
280 undo_redo->add_do_method(c, "update");
281 undo_redo->add_undo_method(rect.ptr(), "set_extents", p_org);
282 undo_redo->add_undo_method(c, "update");
283
284 } break;
285
286 case SEGMENT_SHAPE: {
287 Ref<SegmentShape2D> seg = node->get_shape();
288 if (idx == 0) {
289 undo_redo->add_do_method(seg.ptr(), "set_a", seg->get_a());
290 undo_redo->add_do_method(c, "update");
291 undo_redo->add_undo_method(seg.ptr(), "set_a", p_org);
292 undo_redo->add_undo_method(c, "update");
293 } else if (idx == 1) {
294 undo_redo->add_do_method(seg.ptr(), "set_b", seg->get_b());
295 undo_redo->add_do_method(c, "update");
296 undo_redo->add_undo_method(seg.ptr(), "set_b", p_org);
297 undo_redo->add_undo_method(c, "update");
298 }
299
300 } break;
301 }
302
303 undo_redo->commit_action();
304 }
305
forward_input_event(const InputEvent & p_event)306 bool CollisionShape2DEditor::forward_input_event(const InputEvent &p_event) {
307
308 if (!node) {
309 return false;
310 }
311
312 if (!node->get_shape().is_valid()) {
313 return false;
314 }
315
316 if (shape_type == -1) {
317 return false;
318 }
319
320 switch (p_event.type) {
321 case InputEvent::MOUSE_BUTTON: {
322 const InputEventMouseButton &mb = p_event.mouse_button;
323
324 Matrix32 gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
325
326 Point2 gpoint(mb.x, mb.y);
327
328 if (mb.button_index == BUTTON_LEFT) {
329 if (mb.pressed) {
330 for (int i = 0; i < handles.size(); i++) {
331 if (gt.xform(handles[i]).distance_to(gpoint) < 8) {
332 edit_handle = i;
333
334 break;
335 }
336 }
337
338 if (edit_handle == -1) {
339 pressed = false;
340
341 return false;
342 }
343
344 original = get_handle_value(edit_handle);
345 pressed = true;
346
347 return true;
348
349 } else {
350 if (pressed) {
351 commit_handle(edit_handle, original);
352
353 edit_handle = -1;
354 pressed = false;
355
356 return true;
357 }
358 }
359 }
360
361 return false;
362
363 } break;
364
365 case InputEvent::MOUSE_MOTION: {
366 const InputEventMouseMotion &mm = p_event.mouse_motion;
367
368 if (edit_handle == -1 || !pressed) {
369 return false;
370 }
371
372 Point2 gpoint = Point2(mm.x, mm.y);
373 Point2 cpoint = canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint);
374 cpoint = canvas_item_editor->snap_point(cpoint);
375 cpoint = node->get_global_transform().affine_inverse().xform(cpoint);
376
377 set_handle(edit_handle, cpoint);
378
379 return true;
380
381 } break;
382 }
383
384 return false;
385 }
386
_get_current_shape_type()387 void CollisionShape2DEditor::_get_current_shape_type() {
388
389 if (!node) {
390 return;
391 }
392
393 Ref<Shape2D> s = node->get_shape();
394
395 if (!s.is_valid()) {
396 return;
397 }
398
399 if (s->cast_to<CapsuleShape2D>()) {
400 shape_type = CAPSULE_SHAPE;
401 } else if (s->cast_to<CircleShape2D>()) {
402 shape_type = CIRCLE_SHAPE;
403 } else if (s->cast_to<ConcavePolygonShape2D>()) {
404 shape_type = CONCAVE_POLYGON_SHAPE;
405 } else if (s->cast_to<ConvexPolygonShape2D>()) {
406 shape_type = CONVEX_POLYGON_SHAPE;
407 } else if (s->cast_to<LineShape2D>()) {
408 shape_type = LINE_SHAPE;
409 } else if (s->cast_to<RayShape2D>()) {
410 shape_type = RAY_SHAPE;
411 } else if (s->cast_to<RectangleShape2D>()) {
412 shape_type = RECTANGLE_SHAPE;
413 } else if (s->cast_to<SegmentShape2D>()) {
414 shape_type = SEGMENT_SHAPE;
415 } else {
416 shape_type = -1;
417 }
418
419 canvas_item_editor->get_viewport_control()->update();
420 }
421
_canvas_draw()422 void CollisionShape2DEditor::_canvas_draw() {
423
424 if (!node) {
425 return;
426 }
427
428 if (!node->get_shape().is_valid()) {
429 return;
430 }
431
432 _get_current_shape_type();
433
434 if (shape_type == -1) {
435 return;
436 }
437
438 Control *c = canvas_item_editor->get_viewport_control();
439 Matrix32 gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
440
441 Ref<Texture> h = get_icon("EditorHandle", "EditorIcons");
442 Vector2 size = h->get_size() * 0.5;
443
444 handles.clear();
445
446 switch (shape_type) {
447 case CAPSULE_SHAPE: {
448 Ref<CapsuleShape2D> shape = node->get_shape();
449
450 handles.resize(2);
451 float radius = shape->get_radius();
452 float height = shape->get_height() / 2;
453
454 handles[0] = Point2(radius, -height);
455 handles[1] = Point2(0, -(height + radius));
456
457 c->draw_texture(h, gt.xform(handles[0]) - size);
458 c->draw_texture(h, gt.xform(handles[1]) - size);
459
460 } break;
461
462 case CIRCLE_SHAPE: {
463 Ref<CircleShape2D> shape = node->get_shape();
464
465 handles.resize(1);
466 handles[0] = Point2(shape->get_radius(), 0);
467
468 c->draw_texture(h, gt.xform(handles[0]) - size);
469
470 } break;
471
472 case CONCAVE_POLYGON_SHAPE: {
473
474 } break;
475
476 case CONVEX_POLYGON_SHAPE: {
477
478 } break;
479
480 case LINE_SHAPE: {
481 Ref<LineShape2D> shape = node->get_shape();
482
483 handles.resize(2);
484 handles[0] = shape->get_normal() * shape->get_d();
485 handles[1] = shape->get_normal() * (shape->get_d() + 30.0);
486
487 c->draw_texture(h, gt.xform(handles[0]) - size);
488 c->draw_texture(h, gt.xform(handles[1]) - size);
489
490 } break;
491
492 case RAY_SHAPE: {
493 Ref<RayShape2D> shape = node->get_shape();
494
495 handles.resize(1);
496 handles[0] = Point2(0, shape->get_length());
497
498 c->draw_texture(h, gt.xform(handles[0]) - size);
499
500 } break;
501
502 case RECTANGLE_SHAPE: {
503 Ref<RectangleShape2D> shape = node->get_shape();
504
505 handles.resize(2);
506 Vector2 ext = shape->get_extents();
507 handles[0] = Point2(ext.x, 0);
508 handles[1] = Point2(0, -ext.y);
509
510 c->draw_texture(h, gt.xform(handles[0]) - size);
511 c->draw_texture(h, gt.xform(handles[1]) - size);
512
513 } break;
514
515 case SEGMENT_SHAPE: {
516 Ref<SegmentShape2D> shape = node->get_shape();
517
518 handles.resize(2);
519 handles[0] = shape->get_a();
520 handles[1] = shape->get_b();
521
522 c->draw_texture(h, gt.xform(handles[0]) - size);
523 c->draw_texture(h, gt.xform(handles[1]) - size);
524
525 } break;
526 }
527 }
528
edit(Node * p_node)529 void CollisionShape2DEditor::edit(Node *p_node) {
530
531 if (!canvas_item_editor) {
532 canvas_item_editor = CanvasItemEditor::get_singleton();
533 }
534
535 if (p_node) {
536 node = p_node->cast_to<CollisionShape2D>();
537
538 if (!canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw"))
539 canvas_item_editor->get_viewport_control()->connect("draw", this, "_canvas_draw");
540
541 _get_current_shape_type();
542
543 } else {
544 edit_handle = -1;
545 shape_type = -1;
546
547 if (canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw"))
548 canvas_item_editor->get_viewport_control()->disconnect("draw", this, "_canvas_draw");
549
550 node = NULL;
551 }
552
553 canvas_item_editor->get_viewport_control()->update();
554 }
555
_bind_methods()556 void CollisionShape2DEditor::_bind_methods() {
557
558 ObjectTypeDB::bind_method("_canvas_draw", &CollisionShape2DEditor::_canvas_draw);
559 ObjectTypeDB::bind_method("_get_current_shape_type", &CollisionShape2DEditor::_get_current_shape_type);
560 }
561
CollisionShape2DEditor(EditorNode * p_editor)562 CollisionShape2DEditor::CollisionShape2DEditor(EditorNode *p_editor) {
563
564 node = NULL;
565 canvas_item_editor = NULL;
566 editor = p_editor;
567
568 undo_redo = p_editor->get_undo_redo();
569
570 edit_handle = -1;
571 pressed = false;
572 }
573
edit(Object * p_obj)574 void CollisionShape2DEditorPlugin::edit(Object *p_obj) {
575
576 collision_shape_2d_editor->edit(p_obj->cast_to<Node>());
577 }
578
handles(Object * p_obj) const579 bool CollisionShape2DEditorPlugin::handles(Object *p_obj) const {
580
581 return p_obj->is_type("CollisionShape2D");
582 }
583
make_visible(bool visible)584 void CollisionShape2DEditorPlugin::make_visible(bool visible) {
585
586 if (!visible) {
587 edit(NULL);
588 }
589 }
590
CollisionShape2DEditorPlugin(EditorNode * p_node)591 CollisionShape2DEditorPlugin::CollisionShape2DEditorPlugin(EditorNode *p_node) {
592
593 editor = p_node;
594
595 collision_shape_2d_editor = memnew(CollisionShape2DEditor(p_node));
596 p_node->get_gui_base()->add_child(collision_shape_2d_editor);
597 }
598
~CollisionShape2DEditorPlugin()599 CollisionShape2DEditorPlugin::~CollisionShape2DEditorPlugin() {
600 }
601