1/**************************************************************************/
2/* shape_cast_2d.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
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 "shape_cast_2d.h"
32
33#include "core/config/engine.h"
34#include "scene/2d/collision_object_2d.h"
35#include "scene/2d/physics_body_2d.h"
36#include "scene/resources/circle_shape_2d.h"
37#include "servers/physics_2d/godot_physics_server_2d.h"
38
39void ShapeCast2D::set_target_position(const Vector2 &p_point) {
40 target_position = p_point;
41 if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) {
42 queue_redraw();
43 }
44}
45
46Vector2 ShapeCast2D::get_target_position() const {
47 return target_position;
48}
49
50void ShapeCast2D::set_margin(real_t p_margin) {
51 margin = p_margin;
52}
53
54real_t ShapeCast2D::get_margin() const {
55 return margin;
56}
57
58void ShapeCast2D::set_max_results(int p_max_results) {
59 max_results = p_max_results;
60}
61
62int ShapeCast2D::get_max_results() const {
63 return max_results;
64}
65
66void ShapeCast2D::set_collision_mask(uint32_t p_mask) {
67 collision_mask = p_mask;
68}
69
70uint32_t ShapeCast2D::get_collision_mask() const {
71 return collision_mask;
72}
73
74void ShapeCast2D::set_collision_mask_value(int p_layer_number, bool p_value) {
75 ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
76 ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
77 uint32_t mask = get_collision_mask();
78 if (p_value) {
79 mask |= 1 << (p_layer_number - 1);
80 } else {
81 mask &= ~(1 << (p_layer_number - 1));
82 }
83 set_collision_mask(mask);
84}
85
86bool ShapeCast2D::get_collision_mask_value(int p_layer_number) const {
87 ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive.");
88 ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive.");
89 return get_collision_mask() & (1 << (p_layer_number - 1));
90}
91
92int ShapeCast2D::get_collision_count() const {
93 return result.size();
94}
95
96bool ShapeCast2D::is_colliding() const {
97 return collided;
98}
99
100Object *ShapeCast2D::get_collider(int p_idx) const {
101 ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), nullptr, "No collider found.");
102
103 if (result[p_idx].collider_id.is_null()) {
104 return nullptr;
105 }
106 return ObjectDB::get_instance(result[p_idx].collider_id);
107}
108
109RID ShapeCast2D::get_collider_rid(int p_idx) const {
110 ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), RID(), "No collider RID found.");
111 return result[p_idx].rid;
112}
113
114int ShapeCast2D::get_collider_shape(int p_idx) const {
115 ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found.");
116 return result[p_idx].shape;
117}
118
119Vector2 ShapeCast2D::get_collision_point(int p_idx) const {
120 ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision point found.");
121 return result[p_idx].point;
122}
123
124Vector2 ShapeCast2D::get_collision_normal(int p_idx) const {
125 ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision normal found.");
126 return result[p_idx].normal;
127}
128
129real_t ShapeCast2D::get_closest_collision_safe_fraction() const {
130 return collision_safe_fraction;
131}
132
133real_t ShapeCast2D::get_closest_collision_unsafe_fraction() const {
134 return collision_unsafe_fraction;
135}
136
137void ShapeCast2D::set_enabled(bool p_enabled) {
138 enabled = p_enabled;
139 queue_redraw();
140 if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
141 set_physics_process_internal(p_enabled);
142 }
143 if (!p_enabled) {
144 collided = false;
145 }
146}
147
148bool ShapeCast2D::is_enabled() const {
149 return enabled;
150}
151
152void ShapeCast2D::set_shape(const Ref<Shape2D> &p_shape) {
153 if (p_shape == shape) {
154 return;
155 }
156 if (shape.is_valid()) {
157 shape->disconnect_changed(callable_mp(this, &ShapeCast2D::_shape_changed));
158 }
159 shape = p_shape;
160 if (shape.is_valid()) {
161 shape->connect_changed(callable_mp(this, &ShapeCast2D::_shape_changed));
162 shape_rid = shape->get_rid();
163 }
164
165 update_configuration_warnings();
166 queue_redraw();
167}
168
169Ref<Shape2D> ShapeCast2D::get_shape() const {
170 return shape;
171}
172
173void ShapeCast2D::set_exclude_parent_body(bool p_exclude_parent_body) {
174 if (exclude_parent_body == p_exclude_parent_body) {
175 return;
176 }
177 exclude_parent_body = p_exclude_parent_body;
178
179 if (!is_inside_tree()) {
180 return;
181 }
182 if (Object::cast_to<CollisionObject2D>(get_parent())) {
183 if (exclude_parent_body) {
184 exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
185 } else {
186 exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
187 }
188 }
189}
190
191bool ShapeCast2D::get_exclude_parent_body() const {
192 return exclude_parent_body;
193}
194
195void ShapeCast2D::_shape_changed() {
196 queue_redraw();
197}
198
199void ShapeCast2D::_notification(int p_what) {
200 switch (p_what) {
201 case NOTIFICATION_ENTER_TREE: {
202 if (enabled && !Engine::get_singleton()->is_editor_hint()) {
203 set_physics_process_internal(true);
204 } else {
205 set_physics_process_internal(false);
206 }
207 if (Object::cast_to<CollisionObject2D>(get_parent())) {
208 if (exclude_parent_body) {
209 exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
210 } else {
211 exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
212 }
213 }
214 } break;
215
216 case NOTIFICATION_EXIT_TREE: {
217 if (enabled) {
218 set_physics_process_internal(false);
219 }
220 } break;
221
222 case NOTIFICATION_DRAW: {
223#ifdef TOOLS_ENABLED
224 ERR_FAIL_COND(!is_inside_tree());
225 if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
226 break;
227 }
228 if (shape.is_null()) {
229 break;
230 }
231 Color draw_col = collided ? Color(1.0, 0.01, 0) : get_tree()->get_debug_collisions_color();
232 if (!enabled) {
233 float g = draw_col.get_v();
234 draw_col.r = g;
235 draw_col.g = g;
236 draw_col.b = g;
237 }
238 // Draw continuous chain of shapes along the cast.
239 const int steps = MAX(2, target_position.length() / shape->get_rect().get_size().length() * 4);
240 for (int i = 0; i <= steps; ++i) {
241 Vector2 t = (real_t(i) / steps) * target_position;
242 draw_set_transform(t, 0.0, Size2(1, 1));
243 shape->draw(get_canvas_item(), draw_col);
244 }
245 draw_set_transform(Vector2(), 0.0, Size2(1, 1));
246
247 // Draw an arrow indicating where the ShapeCast is pointing to.
248 if (target_position != Vector2()) {
249 const real_t max_arrow_size = 6;
250 const real_t line_width = 1.4;
251 bool no_line = target_position.length() < line_width;
252 real_t arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size);
253
254 if (no_line) {
255 arrow_size = target_position.length();
256 } else {
257 draw_line(Vector2(), target_position - target_position.normalized() * arrow_size, draw_col, line_width);
258 }
259
260 Transform2D xf;
261 xf.rotate(target_position.angle());
262 xf.translate_local(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0));
263
264 Vector<Vector2> pts = {
265 xf.xform(Vector2(arrow_size, 0)),
266 xf.xform(Vector2(0, 0.5 * arrow_size)),
267 xf.xform(Vector2(0, -0.5 * arrow_size))
268 };
269
270 Vector<Color> cols = { draw_col, draw_col, draw_col };
271
272 draw_primitive(pts, cols, Vector<Vector2>());
273 }
274#endif
275 } break;
276
277 case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
278 if (!enabled) {
279 break;
280 }
281 _update_shapecast_state();
282 } break;
283 }
284}
285
286void ShapeCast2D::_update_shapecast_state() {
287 result.clear();
288
289 ERR_FAIL_COND_MSG(shape.is_null(), "Invalid shape.");
290
291 Ref<World2D> w2d = get_world_2d();
292 ERR_FAIL_COND(w2d.is_null());
293
294 PhysicsDirectSpaceState2D *dss = PhysicsServer2D::get_singleton()->space_get_direct_state(w2d->get_space());
295 ERR_FAIL_NULL(dss);
296
297 Transform2D gt = get_global_transform();
298
299 PhysicsDirectSpaceState2D::ShapeParameters params;
300 params.shape_rid = shape_rid;
301 params.transform = gt;
302 params.motion = gt.basis_xform(target_position);
303 params.margin = margin;
304 params.exclude = exclude;
305 params.collision_mask = collision_mask;
306 params.collide_with_bodies = collide_with_bodies;
307 params.collide_with_areas = collide_with_areas;
308
309 collision_safe_fraction = 0.0;
310 collision_unsafe_fraction = 0.0;
311
312 bool prev_collision_state = collided;
313
314 if (target_position != Vector2()) {
315 dss->cast_motion(params, collision_safe_fraction, collision_unsafe_fraction);
316 if (collision_unsafe_fraction < 1.0) {
317 // Move shape transform to the point of impact,
318 // so we can collect contact info at that point.
319 gt.set_origin(gt.get_origin() + params.motion * (collision_unsafe_fraction + CMP_EPSILON));
320 params.transform = gt;
321 }
322 }
323 // Regardless of whether the shape is stuck or it's moved along
324 // the motion vector, we'll only consider static collisions from now on.
325 params.motion = Vector2();
326
327 bool intersected = true;
328 while (intersected && result.size() < max_results) {
329 PhysicsDirectSpaceState2D::ShapeRestInfo info;
330 intersected = dss->rest_info(params, &info);
331 if (intersected) {
332 result.push_back(info);
333 params.exclude.insert(info.rid);
334 }
335 }
336 collided = !result.is_empty();
337
338 if (prev_collision_state != collided) {
339 queue_redraw();
340 }
341}
342
343void ShapeCast2D::force_shapecast_update() {
344 _update_shapecast_state();
345}
346
347void ShapeCast2D::add_exception_rid(const RID &p_rid) {
348 exclude.insert(p_rid);
349}
350
351void ShapeCast2D::add_exception(const CollisionObject2D *p_node) {
352 ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D.");
353 add_exception_rid(p_node->get_rid());
354}
355
356void ShapeCast2D::remove_exception_rid(const RID &p_rid) {
357 exclude.erase(p_rid);
358}
359
360void ShapeCast2D::remove_exception(const CollisionObject2D *p_node) {
361 ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject2D.");
362 remove_exception_rid(p_node->get_rid());
363}
364
365void ShapeCast2D::clear_exceptions() {
366 exclude.clear();
367}
368
369void ShapeCast2D::set_collide_with_areas(bool p_clip) {
370 collide_with_areas = p_clip;
371}
372
373bool ShapeCast2D::is_collide_with_areas_enabled() const {
374 return collide_with_areas;
375}
376
377void ShapeCast2D::set_collide_with_bodies(bool p_clip) {
378 collide_with_bodies = p_clip;
379}
380
381bool ShapeCast2D::is_collide_with_bodies_enabled() const {
382 return collide_with_bodies;
383}
384
385Array ShapeCast2D::_get_collision_result() const {
386 Array ret;
387
388 for (int i = 0; i < result.size(); ++i) {
389 const PhysicsDirectSpaceState2D::ShapeRestInfo &sri = result[i];
390
391 Dictionary col;
392 col["point"] = sri.point;
393 col["normal"] = sri.normal;
394 col["rid"] = sri.rid;
395 col["collider"] = ObjectDB::get_instance(sri.collider_id);
396 col["collider_id"] = sri.collider_id;
397 col["shape"] = sri.shape;
398 col["linear_velocity"] = sri.linear_velocity;
399
400 ret.push_back(col);
401 }
402 return ret;
403}
404
405PackedStringArray ShapeCast2D::get_configuration_warnings() const {
406 PackedStringArray warnings = Node2D::get_configuration_warnings();
407
408 if (shape.is_null()) {
409 warnings.push_back(RTR("This node cannot interact with other objects unless a Shape2D is assigned."));
410 }
411 return warnings;
412}
413
414void ShapeCast2D::_bind_methods() {
415 ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &ShapeCast2D::set_enabled);
416 ClassDB::bind_method(D_METHOD("is_enabled"), &ShapeCast2D::is_enabled);
417
418 ClassDB::bind_method(D_METHOD("set_shape", "shape"), &ShapeCast2D::set_shape);
419 ClassDB::bind_method(D_METHOD("get_shape"), &ShapeCast2D::get_shape);
420
421 ClassDB::bind_method(D_METHOD("set_target_position", "local_point"), &ShapeCast2D::set_target_position);
422 ClassDB::bind_method(D_METHOD("get_target_position"), &ShapeCast2D::get_target_position);
423
424 ClassDB::bind_method(D_METHOD("set_margin", "margin"), &ShapeCast2D::set_margin);
425 ClassDB::bind_method(D_METHOD("get_margin"), &ShapeCast2D::get_margin);
426
427 ClassDB::bind_method(D_METHOD("set_max_results", "max_results"), &ShapeCast2D::set_max_results);
428 ClassDB::bind_method(D_METHOD("get_max_results"), &ShapeCast2D::get_max_results);
429
430 ClassDB::bind_method(D_METHOD("is_colliding"), &ShapeCast2D::is_colliding);
431 ClassDB::bind_method(D_METHOD("get_collision_count"), &ShapeCast2D::get_collision_count);
432
433 ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast2D::force_shapecast_update);
434
435 ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast2D::get_collider);
436 ClassDB::bind_method(D_METHOD("get_collider_rid", "index"), &ShapeCast2D::get_collider_rid);
437 ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast2D::get_collider_shape);
438 ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast2D::get_collision_point);
439 ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast2D::get_collision_normal);
440
441 ClassDB::bind_method(D_METHOD("get_closest_collision_safe_fraction"), &ShapeCast2D::get_closest_collision_safe_fraction);
442 ClassDB::bind_method(D_METHOD("get_closest_collision_unsafe_fraction"), &ShapeCast2D::get_closest_collision_unsafe_fraction);
443
444 ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &ShapeCast2D::add_exception_rid);
445 ClassDB::bind_method(D_METHOD("add_exception", "node"), &ShapeCast2D::add_exception);
446
447 ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &ShapeCast2D::remove_exception_rid);
448 ClassDB::bind_method(D_METHOD("remove_exception", "node"), &ShapeCast2D::remove_exception);
449
450 ClassDB::bind_method(D_METHOD("clear_exceptions"), &ShapeCast2D::clear_exceptions);
451
452 ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &ShapeCast2D::set_collision_mask);
453 ClassDB::bind_method(D_METHOD("get_collision_mask"), &ShapeCast2D::get_collision_mask);
454
455 ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &ShapeCast2D::set_collision_mask_value);
456 ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &ShapeCast2D::get_collision_mask_value);
457
458 ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &ShapeCast2D::set_exclude_parent_body);
459 ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &ShapeCast2D::get_exclude_parent_body);
460
461 ClassDB::bind_method(D_METHOD("set_collide_with_areas", "enable"), &ShapeCast2D::set_collide_with_areas);
462 ClassDB::bind_method(D_METHOD("is_collide_with_areas_enabled"), &ShapeCast2D::is_collide_with_areas_enabled);
463
464 ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast2D::set_collide_with_bodies);
465 ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast2D::is_collide_with_bodies_enabled);
466
467 ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast2D::_get_collision_result);
468
469 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
470 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape");
471 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
472 ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position", PROPERTY_HINT_NONE, "suffix:px"), "set_target_position", "get_target_position");
473 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:px"), "set_margin", "get_margin");
474 ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results");
475 ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
476 ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result");
477 ADD_GROUP("Collide With", "collide_with");
478 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
479 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");
480}
481
482ShapeCast2D::ShapeCast2D() {
483 set_hide_clip_children(true);
484}
485