scene_queries_ray_casting
Ray-casting is a geometric query that finds one or several colliders intersecting a half-line. Ray-casting is an extremely common operation that covers a wide variety of use-cases: firing bullets, character controllers, rendering (for ray-tracing), etc.
A ray is defined by its origin and its direction: it can be interpreted as a single point moving in a straight line towards the ray direction.
In addition to the ray geometric information, ray-casting method allow additional control over the behavior of the ray cast like limiting the length of the ray and ignoring some colliders. See the detailed ray-cast arguments description after the next example.
There are multiple ray-casting methods yielding more or less detailed results (see example below). The more results you get, the more computationally expensive the ray-cast will be.
- Example 2D
- Example 3D
R2Vector ray_origin = r2Vector(1.0, 2.0);
R2Vector ray_dir = r2Vector(0.0, 1.0);
R2Real max_toi = 4.0;
R2Bool solid = 1;
R2QueryOptions options = r2DefaultQueryOptions();
R2RayToi toi = r2CastRayToi(world, &options, ray_origin, ray_dir, max_toi, solid);
if (toi.found) {
// The first collider hit has the handle `toi.collider` and it hit after
// the ray travelled a distance equal to `ray_dir * toi.toi`.
R2Vector hit_point = r2VectorAdd(ray_origin, r2VectorScale(ray_dir, toi.toi));
printf("Collider %u hit at point (%f, %f)\n", toi.collider.index, (double)hit_point.x,
(double)hit_point.y);
}
R2OptionalRayHit result = r2TryCastRay(world, &options, ray_origin, ray_dir, max_toi, solid);
if (result.found) {
R2RayHit hit = result.hit;
// This is similar to `r2CastRayToi` illustrated above except
// that it also returns the normal of the collider shape at the hit point.
R2Vector hit_point = r2VectorAdd(ray_origin, r2VectorScale(ray_dir, hit.time_of_impact));
R2Vector hit_normal = hit.normal;
printf("Collider %u hit at point (%f, %f) with normal (%f, %f)\n",
hit.collider.index, (double)hit_point.x, (double)hit_point.y,
(double)hit_normal.x, (double)hit_normal.y);
}
R3Vector ray_origin = r3Vector(1.0, 2.0, 3.0);
R3Vector ray_dir = r3Vector(0.0, 1.0, 0.0);
R3Real max_toi = 4.0;
R3Bool solid = 1;
R3QueryOptions options = r3DefaultQueryOptions();
R3RayToi toi = r3CastRayToi(world, &options, ray_origin, ray_dir, max_toi, solid);
if (toi.found) {
// The first collider hit has the handle `toi.collider` and it hit after
// the ray travelled a distance equal to `ray_dir * toi.toi`.
R3Vector hit_point = r3VectorAdd(ray_origin, r3VectorScale(ray_dir, toi.toi));
printf("Collider %u hit at point (%f, %f, %f)\n", toi.collider.index, (double)hit_point.x,
(double)hit_point.y, (double)hit_point.z);
}
R3OptionalRayHit result = r3TryCastRay(world, &options, ray_origin, ray_dir, max_toi, solid);
if (result.found) {
R3RayHit hit = result.hit;
// This is similar to `r3CastRayToi` illustrated above except
// that it also returns the normal of the collider shape at the hit point.
R3Vector hit_point = r3VectorAdd(ray_origin, r3VectorScale(ray_dir, hit.time_of_impact));
R3Vector hit_normal = hit.normal;
printf("Collider %u hit at point (%f, %f, %f) with normal (%f, %f, %f)\n",
hit.collider.index, (double)hit_point.x, (double)hit_point.y,
(double)hit_point.z, (double)hit_normal.x, (double)hit_normal.y,
(double)hit_normal.z);
}
r3CastRayToi only gives the handle of the first collider hit and the time-of-impact, whereas r3TryCastRay also
gives the world-space normal of the collider's shape at the hit point, as well as the feature of the shape that was
hit (a vertex, an edge, or a face, identified by feature_type and feature_id). Both set the found field of their
result to 0 if the ray doesn't hit anything. Note that r3CastRay gives the same result as r3TryCastRay but reports
a miss as the R3_NOT_FOUND error: it is only suitable if the ray is expected to always hit something.
Finally, r3IntersectRay gives the hits of every collider intersected by the ray (in no particular order), with the
same details as r3TryCastRay:
- Example 2D
- Example 3D
// Get the number of colliders hit by the ray, then copy all their hits.
size_t count = r2IntersectRay(world, &options, ray_origin, ray_dir, max_toi, solid, NULL, 0);
R2RayHit *hits = malloc(count * sizeof(*hits));
count = r2IntersectRay(world, &options, ray_origin, ray_dir, max_toi, solid, hits, count);
for (size_t i = 0; i < count; i++) {
// Loop on each collider hit by the ray.
R2Vector hit_point = r2VectorAdd(ray_origin, r2VectorScale(ray_dir, hits[i].time_of_impact));
R2Vector hit_normal = hits[i].normal;
printf("Collider %u hit at point (%f, %f) with normal (%f, %f)\n",
hits[i].collider.index, (double)hit_point.x, (double)hit_point.y,
(double)hit_normal.x, (double)hit_normal.y);
}
free(hits);
// Get the number of colliders hit by the ray, then copy all their hits.
size_t count = r3IntersectRay(world, &options, ray_origin, ray_dir, max_toi, solid, NULL, 0);
R3RayHit *hits = malloc(count * sizeof(*hits));
count = r3IntersectRay(world, &options, ray_origin, ray_dir, max_toi, solid, hits, count);
for (size_t i = 0; i < count; i++) {
// Loop on each collider hit by the ray.
R3Vector hit_point = r3VectorAdd(ray_origin, r3VectorScale(ray_dir, hits[i].time_of_impact));
R3Vector hit_normal = hits[i].normal;
printf("Collider %u hit at point (%f, %f, %f) with normal (%f, %f, %f)\n",
hits[i].collider.index, (double)hit_point.x, (double)hit_point.y,
(double)hit_point.z, (double)hit_normal.x, (double)hit_normal.y,
(double)hit_normal.z);
}
free(hits);
Aside from the ray being cast, all these ray-casting methods take a few extra parameters for controlling the behavior of the ray-cast:
max_toi: is the maximum "time-of-impact" that can be reported by the ray-cast. The notion of "time-of-impact" refer to the fact that a ray can be seen as a point starting atoriginmoving at a linear velocity equal todirection. Therefore,max_toilimits the ray-cast to the segment:[origin, origin + direction * max_toi].solid: this argument controls the behavior of the ray-cast iforiginis inside of a shape: ifsolidis1then the hit point will be the ray origin itself (toi = 0.0) because the interior of the shape will be assumed to be filled with material. Ifsolidis0then the shape will be assumed to have an empty interior and the hit point will be the first time the ray hits the shape's boundary. The following 2D example illustrates the difference between the two scenarios. The ray is in green and the resulting hit point circled in red:
In addition, it is possible to only apply the scene query to a subsets of the colliders using a query filter.