Skip to content

Support multiple intersections for ray casting #108022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/classes/PhysicsDirectSpaceState2D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,23 @@
If the ray did not intersect anything, then an empty dictionary is returned instead.
</description>
</method>
<method name="intersect_ray_multiple">
<return type="Dictionary[]" />
<param index="0" name="parameters" type="PhysicsRayQueryParameters2D" />
<param index="1" name="max_results" type="int" default="32" />
<description>
Intersects a ray in a given space yielding all intersected objects up to the number specified by the [code skip-lint]max_results[/code] parameter. Ray position and other parameters are defined through [PhysicsRayQueryParameters2D]. The returned object is an array of dictionaries with the following fields:
[code]collider[/code]: The colliding object.
[code]collider_id[/code]: The colliding object's ID.
[code]normal[/code]: The object's surface normal at the intersection point, or [code]Vector2(0, 0)[/code] if the ray starts inside the shape and [member PhysicsRayQueryParameters2D.hit_from_inside] is [code]true[/code].
[code]position[/code]: The intersection point.
[code]rid[/code]: The intersecting object's [RID].
[code]shape[/code]: The shape index of the colliding shape.
If the ray did not intersect anything, then an empty array is returned instead.
If [code skip-lint]max_results[/code] is set to [code]1[/code], the method will behave as [code skip-lint]intersect_ray[/code].
[b]Note:[/b] The results array is not guaranteed to contain all possible intersected objects if the returned array has a size of [code skip-lint]max_results[/code]. If all elements are required it is recommended to increase the size of [code skip-lint]max_results[/code] (default [code]32[/code]) to compensate. This does not apply to [code]max_results = 1[/code].
</description>
</method>
<method name="intersect_shape">
<return type="Dictionary[]" />
<param index="0" name="parameters" type="PhysicsShapeQueryParameters2D" />
Expand Down
13 changes: 13 additions & 0 deletions doc/classes/PhysicsDirectSpaceState2DExtension.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@
<description>
</description>
</method>
<method name="_intersect_ray_multiple" qualifiers="virtual required">
<return type="int" />
<param index="0" name="from" type="Vector2" />
<param index="1" name="to" type="Vector2" />
<param index="2" name="collision_mask" type="int" />
<param index="3" name="collide_with_bodies" type="bool" />
<param index="4" name="collide_with_areas" type="bool" />
<param index="5" name="hit_from_inside" type="bool" />
<param index="6" name="result" type="PhysicsServer2DExtensionRayResult*" />
<param index="7" name="max_results" type="int" />
<description>
</description>
</method>
<method name="_intersect_shape" qualifiers="virtual required">
<return type="int" />
<param index="0" name="shape_rid" type="RID" />
Expand Down
19 changes: 19 additions & 0 deletions doc/classes/PhysicsDirectSpaceState3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@
If the ray did not intersect anything, then an empty dictionary is returned instead.
</description>
</method>
<method name="intersect_ray_multiple">
<return type="Dictionary[]" />
<param index="0" name="parameters" type="PhysicsRayQueryParameters3D" />
<param index="1" name="max_results" type="int" default="32" />
<description>
Intersects a ray in a given space yielding all intersected objects up to the number specified by the [code skip-lint]max_results[/code] parameter. Ray position and other parameters are defined through [PhysicsRayQueryParameters2D]. The returned object is an array of dictionaries with the following fields:
[code]collider[/code]: The colliding object.
[code]collider_id[/code]: The colliding object's ID.
[code]normal[/code]: The object's surface normal at the intersection point, or [code]Vector3(0, 0, 0)[/code] if the ray starts inside the shape and [member PhysicsRayQueryParameters3D.hit_from_inside] is [code]true[/code].
[code]position[/code]: The intersection point.
[code]face_index[/code]: The face index at the intersection point.
[b]Note:[/b] Returns a valid number only if the intersected shape is a [ConcavePolygonShape3D]. Otherwise, [code]-1[/code] is returned.
[code]rid[/code]: The intersecting object's [RID].
[code]shape[/code]: The shape index of the colliding shape.
If the ray did not intersect anything, then an empty array is returned instead.
If [code skip-lint]max_results[/code] is set to [code]1[/code], the method will behave as [code skip-lint]intersect_ray[/code].
[b]Note:[/b] The results array is not guaranteed to contain all possible intersected objects if the returned array has a size of [code skip-lint]max_results[/code]. If all elements are required it is recommended to increase the size of [code skip-lint]max_results[/code] (default [code]32[/code]) to compensate. This does not apply to [code]max_results = 1[/code].
</description>
</method>
<method name="intersect_shape">
<return type="Dictionary[]" />
<param index="0" name="parameters" type="PhysicsShapeQueryParameters3D" />
Expand Down
15 changes: 15 additions & 0 deletions doc/classes/PhysicsDirectSpaceState3DExtension.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@
<description>
</description>
</method>
<method name="_intersect_ray_multiple" qualifiers="virtual required">
<return type="int" />
<param index="0" name="from" type="Vector3" />
<param index="1" name="to" type="Vector3" />
<param index="2" name="collision_mask" type="int" />
<param index="3" name="collide_with_bodies" type="bool" />
<param index="4" name="collide_with_areas" type="bool" />
<param index="5" name="hit_from_inside" type="bool" />
<param index="6" name="hit_back_faces" type="bool" />
<param index="7" name="pick_ray" type="bool" />
<param index="8" name="results" type="PhysicsServer3DExtensionRayResult*" />
<param index="9" name="max_results" type="int" />
<description>
</description>
</method>
<method name="_intersect_shape" qualifiers="virtual required">
<return type="int" />
<param index="0" name="shape_rid" type="RID" />
Expand Down
88 changes: 64 additions & 24 deletions modules/godot_physics_2d/godot_space_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ int GodotPhysicsDirectSpaceState2D::intersect_point(const PointParameters &p_par
return cc;
}

bool GodotPhysicsDirectSpaceState2D::intersect_ray(const RayParameters &p_parameters, RayResult &r_result) {
int GodotPhysicsDirectSpaceState2D::intersect_ray_multiple(const RayParameters &p_parameters, RayResult *r_results, int p_result_max) {
ERR_FAIL_COND_V(space->locked, false);

Vector2 begin, end;
Expand All @@ -134,7 +134,15 @@ bool GodotPhysicsDirectSpaceState2D::intersect_ray(const RayParameters &p_parame
const GodotCollisionObject2D *res_obj = nullptr;
real_t min_d = 1e10;

int r_idx = 0;
bool choose_closest = (p_result_max == 1);

for (int i = 0; i < amount; i++) {
collided = false;
if (r_idx >= p_result_max) {
break;
}

if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) {
continue;
}
Expand Down Expand Up @@ -164,45 +172,77 @@ bool GodotPhysicsDirectSpaceState2D::intersect_ray(const RayParameters &p_parame
res_shape = shape_idx;
res_obj = col_obj;
collided = true;
break;
} else {
// Ignore shape when starting inside.
continue;
}
}

if (shape->intersect_segment(local_from, local_to, shape_point, shape_normal)) {
} else if (shape->intersect_segment(local_from, local_to, shape_point, shape_normal)) {
Transform2D xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx);
shape_point = xform.xform(shape_point);

real_t ld = normal.dot(shape_point);
res_point = shape_point;
res_normal = inv_xform.basis_xform_inv(shape_normal).normalized();
res_shape = shape_idx;
res_obj = col_obj;
collided = true;
}

if (ld < min_d) {
if (collided) {
ERR_FAIL_NULL_V(res_obj, 0); // Shouldn't happen but silences warning.

// Filter to closest hit if only one return is allowed
if (choose_closest) {
real_t ld = normal.dot(shape_point);
if (ld > min_d) {
continue;
}
min_d = ld;
res_point = shape_point;
res_normal = inv_xform.basis_xform_inv(shape_normal).normalized();
res_shape = shape_idx;
res_obj = col_obj;
collided = true;
}

if (r_results) {
r_results[r_idx].collider_id = res_obj->get_instance_id();
if (r_results[r_idx].collider_id.is_valid()) {
r_results[r_idx].collider = ObjectDB::get_instance(r_results[r_idx].collider_id);
} else {
r_results[r_idx].collider = nullptr;
}
r_results[r_idx].normal = res_normal;
r_results[r_idx].position = res_point;
r_results[r_idx].rid = res_obj->get_self();
r_results[r_idx].shape = res_shape;
}

collided = false;

if (p_result_max > 1) {
r_idx += 1;
}
}
}

if (!collided) {
return false;
// Indicate a successful return if restricting to closest and a hit was found
if (choose_closest && res_obj != nullptr) {
r_idx = 1;
}
ERR_FAIL_NULL_V(res_obj, false); // Shouldn't happen but silences warning.

r_result.collider_id = res_obj->get_instance_id();
if (r_result.collider_id.is_valid()) {
r_result.collider = ObjectDB::get_instance(r_result.collider_id);
}
r_result.normal = res_normal;
r_result.position = res_point;
r_result.rid = res_obj->get_self();
r_result.shape = res_shape;
return r_idx;
}

return true;
bool GodotPhysicsDirectSpaceState2D::intersect_ray(const RayParameters &p_parameters, RayResult &r_result) {
LocalVector<RayResult> results;
results.resize(1);

int n_collisions = intersect_ray_multiple(p_parameters, results.ptr(), 1);
bool hit = n_collisions > 0;
if (hit) {
r_result.collider_id = results[0].collider_id;
r_result.collider = results[0].collider;
r_result.normal = results[0].normal;
r_result.position = results[0].position;
r_result.rid = results[0].rid;
r_result.shape = results[0].shape;
}
return hit;
}

int GodotPhysicsDirectSpaceState2D::intersect_shape(const ShapeParameters &p_parameters, ShapeResult *r_results, int p_result_max) {
Expand Down
1 change: 1 addition & 0 deletions modules/godot_physics_2d/godot_space_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class GodotPhysicsDirectSpaceState2D : public PhysicsDirectSpaceState2D {
GodotSpace2D *space = nullptr;

virtual int intersect_point(const PointParameters &p_parameters, ShapeResult *r_results, int p_result_max) override;
virtual int intersect_ray_multiple(const RayParameters &p_parameters, RayResult *r_results, int p_result_max) override;
virtual bool intersect_ray(const RayParameters &p_parameters, RayResult &r_result) override;
virtual int intersect_shape(const ShapeParameters &p_parameters, ShapeResult *r_results, int p_result_max) override;
virtual bool cast_motion(const ShapeParameters &p_parameters, real_t &p_closest_safe, real_t &p_closest_unsafe) override;
Expand Down
92 changes: 64 additions & 28 deletions modules/godot_physics_3d/godot_space_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ int GodotPhysicsDirectSpaceState3D::intersect_point(const PointParameters &p_par
return cc;
}

bool GodotPhysicsDirectSpaceState3D::intersect_ray(const RayParameters &p_parameters, RayResult &r_result) {
int GodotPhysicsDirectSpaceState3D::intersect_ray_multiple(const RayParameters &p_parameters, RayResult *r_results, int p_result_max) {
ERR_FAIL_COND_V(space->locked, false);

Vector3 begin, end;
Expand All @@ -127,7 +127,13 @@ bool GodotPhysicsDirectSpaceState3D::intersect_ray(const RayParameters &p_parame
const GodotCollisionObject3D *res_obj = nullptr;
real_t min_d = 1e10;

int r_idx = 0;
bool choose_closest = (p_result_max == 1);
for (int i = 0; i < amount; i++) {
if (r_idx >= p_result_max) {
break;
}

if (!_can_collide_with(space->intersection_query_results[i], p_parameters.collision_mask, p_parameters.collide_with_bodies, p_parameters.collide_with_areas)) {
continue;
}
Expand Down Expand Up @@ -162,49 +168,79 @@ bool GodotPhysicsDirectSpaceState3D::intersect_ray(const RayParameters &p_parame
res_shape = shape_idx;
res_obj = col_obj;
collided = true;
break;
} else {
// Ignore shape when starting inside.
continue;
}
}

if (shape->intersect_segment(local_from, local_to, shape_point, shape_normal, shape_face_index, p_parameters.hit_back_faces)) {
} else if (shape->intersect_segment(local_from, local_to, shape_point, shape_normal, shape_face_index, p_parameters.hit_back_faces)) {
Transform3D xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx);
shape_point = xform.xform(shape_point);
res_point = shape_point;
res_normal = inv_xform.basis.xform_inv(shape_normal).normalized();
res_face_index = shape_face_index;
res_shape = shape_idx;
res_obj = col_obj;
collided = true;
}

real_t ld = normal.dot(shape_point);
if (collided) {
ERR_FAIL_NULL_V(res_obj, 0); // Shouldn't happen but silences warning.

if (ld < min_d) {
// Filter to closest hit if only one return is allowed
if (choose_closest) {
real_t ld = normal.dot(shape_point);
if (ld > min_d) {
continue;
}
min_d = ld;
res_point = shape_point;
res_normal = inv_xform.basis.xform_inv(shape_normal).normalized();
res_face_index = shape_face_index;
res_shape = shape_idx;
res_obj = col_obj;
collided = true;
}

if (r_results) {
r_results[r_idx].collider_id = res_obj->get_instance_id();
if (r_results[r_idx].collider_id.is_valid()) {
r_results[r_idx].collider = ObjectDB::get_instance(r_results[r_idx].collider_id);
} else {
r_results[r_idx].collider = nullptr;
}
r_results[r_idx].normal = res_normal;
r_results[r_idx].face_index = res_face_index;
r_results[r_idx].position = res_point;
r_results[r_idx].rid = res_obj->get_self();
r_results[r_idx].shape = res_shape;
}

collided = false;

if (p_result_max > 1) {
r_idx += 1;
}
}
}

if (!collided) {
return false;
// Indicate a successful return if restricting to closest and a hit was found
if (choose_closest && res_obj != nullptr) {
r_idx = 1;
}
ERR_FAIL_NULL_V(res_obj, false); // Shouldn't happen but silences warning.

r_result.collider_id = res_obj->get_instance_id();
if (r_result.collider_id.is_valid()) {
r_result.collider = ObjectDB::get_instance(r_result.collider_id);
} else {
r_result.collider = nullptr;
}
r_result.normal = res_normal;
r_result.face_index = res_face_index;
r_result.position = res_point;
r_result.rid = res_obj->get_self();
r_result.shape = res_shape;
return r_idx;
}

return true;
bool GodotPhysicsDirectSpaceState3D::intersect_ray(const RayParameters &p_parameters, RayResult &r_result) {
LocalVector<RayResult> results;
results.resize(1);

int n_collisions = intersect_ray_multiple(p_parameters, results.ptr(), 1);
bool hit = n_collisions > 0;
if (hit) {
r_result.collider_id = results[0].collider_id;
r_result.collider = results[0].collider;
r_result.normal = results[0].normal;
r_result.face_index = results[0].face_index;
r_result.position = results[0].position;
r_result.rid = results[0].rid;
r_result.shape = results[0].shape;
}
return hit;
}

int GodotPhysicsDirectSpaceState3D::intersect_shape(const ShapeParameters &p_parameters, ShapeResult *r_results, int p_result_max) {
Expand Down
1 change: 1 addition & 0 deletions modules/godot_physics_3d/godot_space_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class GodotPhysicsDirectSpaceState3D : public PhysicsDirectSpaceState3D {
GodotSpace3D *space = nullptr;

virtual int intersect_point(const PointParameters &p_parameters, ShapeResult *r_results, int p_result_max) override;
virtual int intersect_ray_multiple(const RayParameters &p_parameters, RayResult *r_results, int p_result_max) override;
virtual bool intersect_ray(const RayParameters &p_parameters, RayResult &r_result) override;
virtual int intersect_shape(const ShapeParameters &p_parameters, ShapeResult *r_results, int p_result_max) override;
virtual bool cast_motion(const ShapeParameters &p_parameters, real_t &p_closest_safe, real_t &p_closest_unsafe, ShapeRestInfo *r_info = nullptr) override;
Expand Down
Loading