1/**************************************************************************/
2/* ray_cast_3d.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 "ray_cast_3d.h"
32
33#include "collision_object_3d.h"
34#include "mesh_instance_3d.h"
35
36void RayCast3D::set_target_position(const Vector3 &p_point) {
37 target_position = p_point;
38 update_gizmos();
39
40 if (Engine::get_singleton()->is_editor_hint()) {
41 if (is_inside_tree()) {
42 _update_debug_shape_vertices();
43 }
44 } else if (debug_shape) {
45 _update_debug_shape();
46 }
47}
48
49Vector3 RayCast3D::get_target_position() const {
50 return target_position;
51}
52
53void RayCast3D::set_collision_mask(uint32_t p_mask) {
54 collision_mask = p_mask;
55}
56
57uint32_t RayCast3D::get_collision_mask() const {
58 return collision_mask;
59}
60
61void RayCast3D::set_collision_mask_value(int p_layer_number, bool p_value) {
62 ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
63 ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
64 uint32_t mask = get_collision_mask();
65 if (p_value) {
66 mask |= 1 << (p_layer_number - 1);
67 } else {
68 mask &= ~(1 << (p_layer_number - 1));
69 }
70 set_collision_mask(mask);
71}
72
73bool RayCast3D::get_collision_mask_value(int p_layer_number) const {
74 ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive.");
75 ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive.");
76 return get_collision_mask() & (1 << (p_layer_number - 1));
77}
78
79bool RayCast3D::is_colliding() const {
80 return collided;
81}
82
83Object *RayCast3D::get_collider() const {
84 if (against.is_null()) {
85 return nullptr;
86 }
87
88 return ObjectDB::get_instance(against);
89}
90
91RID RayCast3D::get_collider_rid() const {
92 return against_rid;
93}
94
95int RayCast3D::get_collider_shape() const {
96 return against_shape;
97}
98
99Vector3 RayCast3D::get_collision_point() const {
100 return collision_point;
101}
102
103Vector3 RayCast3D::get_collision_normal() const {
104 return collision_normal;
105}
106
107int RayCast3D::get_collision_face_index() const {
108 return collision_face_index;
109}
110
111void RayCast3D::set_enabled(bool p_enabled) {
112 enabled = p_enabled;
113 update_gizmos();
114
115 if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
116 set_physics_process_internal(p_enabled);
117 }
118 if (!p_enabled) {
119 collided = false;
120 }
121
122 if (is_inside_tree() && get_tree()->is_debugging_collisions_hint()) {
123 if (p_enabled) {
124 _update_debug_shape();
125 } else {
126 _clear_debug_shape();
127 }
128 }
129}
130
131bool RayCast3D::is_enabled() const {
132 return enabled;
133}
134
135void RayCast3D::set_exclude_parent_body(bool p_exclude_parent_body) {
136 if (exclude_parent_body == p_exclude_parent_body) {
137 return;
138 }
139
140 exclude_parent_body = p_exclude_parent_body;
141
142 if (!is_inside_tree()) {
143 return;
144 }
145
146 if (Object::cast_to<CollisionObject3D>(get_parent())) {
147 if (exclude_parent_body) {
148 exclude.insert(Object::cast_to<CollisionObject3D>(get_parent())->get_rid());
149 } else {
150 exclude.erase(Object::cast_to<CollisionObject3D>(get_parent())->get_rid());
151 }
152 }
153}
154
155bool RayCast3D::get_exclude_parent_body() const {
156 return exclude_parent_body;
157}
158
159void RayCast3D::_notification(int p_what) {
160 switch (p_what) {
161 case NOTIFICATION_ENTER_TREE: {
162 if (Engine::get_singleton()->is_editor_hint()) {
163 _update_debug_shape_vertices();
164 }
165 if (enabled && !Engine::get_singleton()->is_editor_hint()) {
166 set_physics_process_internal(true);
167 } else {
168 set_physics_process_internal(false);
169 }
170
171 if (get_tree()->is_debugging_collisions_hint()) {
172 _update_debug_shape();
173 }
174
175 if (Object::cast_to<CollisionObject3D>(get_parent())) {
176 if (exclude_parent_body) {
177 exclude.insert(Object::cast_to<CollisionObject3D>(get_parent())->get_rid());
178 } else {
179 exclude.erase(Object::cast_to<CollisionObject3D>(get_parent())->get_rid());
180 }
181 }
182 } break;
183
184 case NOTIFICATION_EXIT_TREE: {
185 if (enabled) {
186 set_physics_process_internal(false);
187 }
188
189 if (debug_shape) {
190 _clear_debug_shape();
191 }
192 } break;
193
194 case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
195 if (!enabled) {
196 break;
197 }
198
199 bool prev_collision_state = collided;
200 _update_raycast_state();
201 if (prev_collision_state != collided && get_tree()->is_debugging_collisions_hint()) {
202 _update_debug_shape_material(true);
203 }
204 } break;
205 }
206}
207
208void RayCast3D::_update_raycast_state() {
209 Ref<World3D> w3d = get_world_3d();
210 ERR_FAIL_COND(w3d.is_null());
211
212 PhysicsDirectSpaceState3D *dss = PhysicsServer3D::get_singleton()->space_get_direct_state(w3d->get_space());
213 ERR_FAIL_NULL(dss);
214
215 Transform3D gt = get_global_transform();
216
217 Vector3 to = target_position;
218 if (to == Vector3()) {
219 to = Vector3(0, 0.01, 0);
220 }
221
222 PhysicsDirectSpaceState3D::RayParameters ray_params;
223 ray_params.from = gt.get_origin();
224 ray_params.to = gt.xform(to);
225 ray_params.exclude = exclude;
226 ray_params.collision_mask = collision_mask;
227 ray_params.collide_with_bodies = collide_with_bodies;
228 ray_params.collide_with_areas = collide_with_areas;
229 ray_params.hit_from_inside = hit_from_inside;
230 ray_params.hit_back_faces = hit_back_faces;
231
232 PhysicsDirectSpaceState3D::RayResult rr;
233 if (dss->intersect_ray(ray_params, rr)) {
234 collided = true;
235 against = rr.collider_id;
236 against_rid = rr.rid;
237 collision_point = rr.position;
238 collision_normal = rr.normal;
239 collision_face_index = rr.face_index;
240 against_shape = rr.shape;
241 } else {
242 collided = false;
243 against = ObjectID();
244 against_rid = RID();
245 against_shape = 0;
246 }
247}
248
249void RayCast3D::force_raycast_update() {
250 _update_raycast_state();
251}
252
253void RayCast3D::add_exception_rid(const RID &p_rid) {
254 exclude.insert(p_rid);
255}
256
257void RayCast3D::add_exception(const CollisionObject3D *p_node) {
258 ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject3D.");
259 add_exception_rid(p_node->get_rid());
260}
261
262void RayCast3D::remove_exception_rid(const RID &p_rid) {
263 exclude.erase(p_rid);
264}
265
266void RayCast3D::remove_exception(const CollisionObject3D *p_node) {
267 ERR_FAIL_NULL_MSG(p_node, "The passed Node must be an instance of CollisionObject3D.");
268 remove_exception_rid(p_node->get_rid());
269}
270
271void RayCast3D::clear_exceptions() {
272 exclude.clear();
273
274 if (exclude_parent_body && is_inside_tree()) {
275 CollisionObject3D *parent = Object::cast_to<CollisionObject3D>(get_parent());
276 if (parent) {
277 exclude.insert(parent->get_rid());
278 }
279 }
280}
281
282void RayCast3D::set_collide_with_areas(bool p_enabled) {
283 collide_with_areas = p_enabled;
284}
285
286bool RayCast3D::is_collide_with_areas_enabled() const {
287 return collide_with_areas;
288}
289
290void RayCast3D::set_collide_with_bodies(bool p_enabled) {
291 collide_with_bodies = p_enabled;
292}
293
294bool RayCast3D::is_collide_with_bodies_enabled() const {
295 return collide_with_bodies;
296}
297
298void RayCast3D::set_hit_from_inside(bool p_enabled) {
299 hit_from_inside = p_enabled;
300}
301
302bool RayCast3D::is_hit_from_inside_enabled() const {
303 return hit_from_inside;
304}
305
306void RayCast3D::set_hit_back_faces(bool p_enabled) {
307 hit_back_faces = p_enabled;
308}
309
310bool RayCast3D::is_hit_back_faces_enabled() const {
311 return hit_back_faces;
312}
313
314void RayCast3D::_bind_methods() {
315 ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast3D::set_enabled);
316 ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast3D::is_enabled);
317
318 ClassDB::bind_method(D_METHOD("set_target_position", "local_point"), &RayCast3D::set_target_position);
319 ClassDB::bind_method(D_METHOD("get_target_position"), &RayCast3D::get_target_position);
320
321 ClassDB::bind_method(D_METHOD("is_colliding"), &RayCast3D::is_colliding);
322 ClassDB::bind_method(D_METHOD("force_raycast_update"), &RayCast3D::force_raycast_update);
323
324 ClassDB::bind_method(D_METHOD("get_collider"), &RayCast3D::get_collider);
325 ClassDB::bind_method(D_METHOD("get_collider_rid"), &RayCast3D::get_collider_rid);
326 ClassDB::bind_method(D_METHOD("get_collider_shape"), &RayCast3D::get_collider_shape);
327 ClassDB::bind_method(D_METHOD("get_collision_point"), &RayCast3D::get_collision_point);
328 ClassDB::bind_method(D_METHOD("get_collision_normal"), &RayCast3D::get_collision_normal);
329 ClassDB::bind_method(D_METHOD("get_collision_face_index"), &RayCast3D::get_collision_face_index);
330
331 ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &RayCast3D::add_exception_rid);
332 ClassDB::bind_method(D_METHOD("add_exception", "node"), &RayCast3D::add_exception);
333
334 ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &RayCast3D::remove_exception_rid);
335 ClassDB::bind_method(D_METHOD("remove_exception", "node"), &RayCast3D::remove_exception);
336
337 ClassDB::bind_method(D_METHOD("clear_exceptions"), &RayCast3D::clear_exceptions);
338
339 ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &RayCast3D::set_collision_mask);
340 ClassDB::bind_method(D_METHOD("get_collision_mask"), &RayCast3D::get_collision_mask);
341
342 ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &RayCast3D::set_collision_mask_value);
343 ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &RayCast3D::get_collision_mask_value);
344
345 ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &RayCast3D::set_exclude_parent_body);
346 ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &RayCast3D::get_exclude_parent_body);
347
348 ClassDB::bind_method(D_METHOD("set_collide_with_areas", "enable"), &RayCast3D::set_collide_with_areas);
349 ClassDB::bind_method(D_METHOD("is_collide_with_areas_enabled"), &RayCast3D::is_collide_with_areas_enabled);
350
351 ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &RayCast3D::set_collide_with_bodies);
352 ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &RayCast3D::is_collide_with_bodies_enabled);
353
354 ClassDB::bind_method(D_METHOD("set_hit_from_inside", "enable"), &RayCast3D::set_hit_from_inside);
355 ClassDB::bind_method(D_METHOD("is_hit_from_inside_enabled"), &RayCast3D::is_hit_from_inside_enabled);
356
357 ClassDB::bind_method(D_METHOD("set_hit_back_faces", "enable"), &RayCast3D::set_hit_back_faces);
358 ClassDB::bind_method(D_METHOD("is_hit_back_faces_enabled"), &RayCast3D::is_hit_back_faces_enabled);
359
360 ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &RayCast3D::set_debug_shape_custom_color);
361 ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &RayCast3D::get_debug_shape_custom_color);
362
363 ClassDB::bind_method(D_METHOD("set_debug_shape_thickness", "debug_shape_thickness"), &RayCast3D::set_debug_shape_thickness);
364 ClassDB::bind_method(D_METHOD("get_debug_shape_thickness"), &RayCast3D::get_debug_shape_thickness);
365
366 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
367 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
368 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_position", PROPERTY_HINT_NONE, "suffix:m"), "set_target_position", "get_target_position");
369 ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
370 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_from_inside"), "set_hit_from_inside", "is_hit_from_inside_enabled");
371 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_back_faces"), "set_hit_back_faces", "is_hit_back_faces_enabled");
372
373 ADD_GROUP("Collide With", "collide_with");
374 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
375 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");
376
377 ADD_GROUP("Debug Shape", "debug_shape");
378 ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_shape_custom_color"), "set_debug_shape_custom_color", "get_debug_shape_custom_color");
379 ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_shape_thickness", PROPERTY_HINT_RANGE, "1,5"), "set_debug_shape_thickness", "get_debug_shape_thickness");
380}
381
382int RayCast3D::get_debug_shape_thickness() const {
383 return debug_shape_thickness;
384}
385
386void RayCast3D::_update_debug_shape_vertices() {
387 debug_shape_vertices.clear();
388 debug_line_vertices.clear();
389
390 if (target_position == Vector3()) {
391 return;
392 }
393
394 debug_line_vertices.push_back(Vector3());
395 debug_line_vertices.push_back(target_position);
396
397 if (debug_shape_thickness > 1) {
398 float scale_factor = 100.0;
399 Vector3 dir = Vector3(target_position).normalized();
400 // Draw truncated pyramid
401 Vector3 normal = (fabs(dir.x) + fabs(dir.y) > CMP_EPSILON) ? Vector3(-dir.y, dir.x, 0).normalized() : Vector3(0, -dir.z, dir.y).normalized();
402 normal *= debug_shape_thickness / scale_factor;
403 int vertices_strip_order[14] = { 4, 5, 0, 1, 2, 5, 6, 4, 7, 0, 3, 2, 7, 6 };
404 for (int v = 0; v < 14; v++) {
405 Vector3 vertex = vertices_strip_order[v] < 4 ? normal : normal / 3.0 + target_position;
406 debug_shape_vertices.push_back(vertex.rotated(dir, Math_PI * (0.5 * (vertices_strip_order[v] % 4) + 0.25)));
407 }
408 }
409}
410
411void RayCast3D::set_debug_shape_thickness(const int p_debug_shape_thickness) {
412 debug_shape_thickness = p_debug_shape_thickness;
413 update_gizmos();
414
415 if (Engine::get_singleton()->is_editor_hint()) {
416 if (is_inside_tree()) {
417 _update_debug_shape_vertices();
418 }
419 } else if (debug_shape) {
420 _update_debug_shape();
421 }
422}
423
424const Vector<Vector3> &RayCast3D::get_debug_shape_vertices() const {
425 return debug_shape_vertices;
426}
427
428const Vector<Vector3> &RayCast3D::get_debug_line_vertices() const {
429 return debug_line_vertices;
430}
431
432void RayCast3D::set_debug_shape_custom_color(const Color &p_color) {
433 debug_shape_custom_color = p_color;
434 if (debug_material.is_valid()) {
435 _update_debug_shape_material();
436 }
437}
438
439Ref<StandardMaterial3D> RayCast3D::get_debug_material() {
440 _update_debug_shape_material();
441 return debug_material;
442}
443
444const Color &RayCast3D::get_debug_shape_custom_color() const {
445 return debug_shape_custom_color;
446}
447
448void RayCast3D::_create_debug_shape() {
449 _update_debug_shape_material();
450
451 Ref<ArrayMesh> mesh = memnew(ArrayMesh);
452
453 MeshInstance3D *mi = memnew(MeshInstance3D);
454 mi->set_mesh(mesh);
455
456 add_child(mi);
457 debug_shape = mi;
458}
459
460void RayCast3D::_update_debug_shape_material(bool p_check_collision) {
461 if (!debug_material.is_valid()) {
462 Ref<StandardMaterial3D> material = memnew(StandardMaterial3D);
463 debug_material = material;
464
465 material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
466 // Use double-sided rendering so that the RayCast can be seen if the camera is inside.
467 material->set_cull_mode(BaseMaterial3D::CULL_DISABLED);
468 material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);
469 }
470
471 Color color = debug_shape_custom_color;
472 if (color == Color(0.0, 0.0, 0.0)) {
473 // Use the default debug shape color defined in the Project Settings.
474 color = get_tree()->get_debug_collisions_color();
475 }
476
477 if (p_check_collision && collided) {
478 if ((color.get_h() < 0.055 || color.get_h() > 0.945) && color.get_s() > 0.5 && color.get_v() > 0.5) {
479 // If base color is already quite reddish, highlight collision with green color
480 color = Color(0.0, 1.0, 0.0, color.a);
481 } else {
482 // Else, highlight collision with red color
483 color = Color(1.0, 0, 0, color.a);
484 }
485 }
486
487 Ref<StandardMaterial3D> material = static_cast<Ref<StandardMaterial3D>>(debug_material);
488 material->set_albedo(color);
489}
490
491void RayCast3D::_update_debug_shape() {
492 if (!enabled) {
493 return;
494 }
495
496 if (!debug_shape) {
497 _create_debug_shape();
498 }
499
500 MeshInstance3D *mi = static_cast<MeshInstance3D *>(debug_shape);
501 Ref<ArrayMesh> mesh = mi->get_mesh();
502 if (!mesh.is_valid()) {
503 return;
504 }
505
506 _update_debug_shape_vertices();
507
508 mesh->clear_surfaces();
509
510 Array a;
511 a.resize(Mesh::ARRAY_MAX);
512
513 uint32_t flags = 0;
514 int surface_count = 0;
515
516 if (!debug_line_vertices.is_empty()) {
517 a[Mesh::ARRAY_VERTEX] = debug_line_vertices;
518 mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, a, Array(), Dictionary(), flags);
519 mesh->surface_set_material(surface_count, debug_material);
520 ++surface_count;
521 }
522
523 if (!debug_shape_vertices.is_empty()) {
524 a[Mesh::ARRAY_VERTEX] = debug_shape_vertices;
525 mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLE_STRIP, a, Array(), Dictionary(), flags);
526 mesh->surface_set_material(surface_count, debug_material);
527 ++surface_count;
528 }
529}
530
531void RayCast3D::_clear_debug_shape() {
532 if (!debug_shape) {
533 return;
534 }
535
536 MeshInstance3D *mi = static_cast<MeshInstance3D *>(debug_shape);
537 if (mi->is_inside_tree()) {
538 mi->queue_free();
539 } else {
540 memdelete(mi);
541 }
542
543 debug_shape = nullptr;
544}
545
546RayCast3D::RayCast3D() {
547}
548