Ray casting is often used to find out what objects are in a certain part of the world. A ray is just a straight line, and we can use a function provided by Box2D to check if the line crosses a shape. We can also find out what the normal is at the point the line hits the shape.

In Box2D v3.0, ray casting is done through the world object using these functions:

// Cast a ray and get all intersections
b2World_CastRay(worldId, origin, translation, filter, resultCallback, context);

// Cast a ray and get only the closest intersection
b2World_CastRayClosest(worldId, origin, translation, filter);

The ray is defined by an origin point and a translation vector. The filter parameter lets you control which shapes are tested against.

two points connected by a ray that extends further to intersect a polygon before it ends

If there is an intersection, the result contains the intersection point and normal.

two points connected by a ray with the intersection point and normal indicated

Example

To try out ray casting, let's set up a scene with a fenced area and some shapes floating inside in a zero-gravity environment:

// Create world
const gravity = new b2Vec2(0, 0);
const worldDef = b2DefaultWorldDef();
worldDef.gravity = gravity;
const worldId = b2CreateWorld(worldDef);

// Create static body for walls
const bodyDef = b2DefaultBodyDef();
bodyDef.type = STATIC;
bodyDef.position = new b2Vec2(0, 0);
const staticBodyId = b2CreateBody(worldId, bodyDef);

// Create wall shapes
const shapeDef = b2DefaultShapeDef();
shapeDef.density = 0;

// Ground
const groundSegment = new b2Segment(new b2Vec2(-20, 0), new b2Vec2(20, 0));
b2CreateSegmentShape(staticBodyId, shapeDef, groundSegment);

// Ceiling
const ceilingSegment = new b2Segment(new b2Vec2(-20, 40), new b2Vec2(20, 40));
b2CreateSegmentShape(staticBodyId, shapeDef, ceilingSegment);

// Left wall
const leftWallSegment = new b2Segment(new b2Vec2(-20, 0), new b2Vec2(-20, 40));
b2CreateSegmentShape(staticBodyId, shapeDef, leftWallSegment);

// Right wall
const rightWallSegment = new b2Segment(new b2Vec2(20, 0), new b2Vec2(20, 40));
b2CreateSegmentShape(staticBodyId, shapeDef, rightWallSegment);

// Create dynamic bodies
bodyDef.type = DYNAMIC;
bodyDef.position = new b2Vec2(0, 20);
shapeDef.density = 1;

// Create boxes
const boxShape = b2MakeBox(2, 2);
for (let i = 0; i < 5; i++) {
    const bodyId = b2CreateBody(worldId, bodyDef);
    b2CreatePolygonShape(bodyId, shapeDef, boxShape);
}

// Create circles
const circleShape = { radius: 2 };
for (let i = 0; i < 5; i++) {
    const bodyId = b2CreateBody(worldId, bodyDef);
    b2CreateCircleShape(bodyId, shapeDef, circleShape);
}

a bounded region with a number of shapes inside

Now let's create a rotating ray to cast through the scene:

let currentRayAngle = 0;

function update() {
    // Update ray angle (one revolution every 20 seconds)
    currentRayAngle += (Math.PI * 2) / (20 * 60);

    // Calculate ray points
    const rayLength = 25;
    const origin = new b2Vec2(0, 20);
    const translation = new b2Vec2(
        rayLength * Math.sin(currentRayAngle),
        rayLength * Math.cos(currentRayAngle)
    );

    // Set up query filter
    const filter = b2DefaultQueryFilter();

    // Cast ray and get closest hit
    const result = b2World_CastRayClosest(worldId, origin, translation, filter);

    if (result.hit) {
        // Calculate intersection point
        const hitFraction = result.fraction;
        const intersectionPoint = new b2Vec2(
            origin.x + hitFraction * translation.x,
            origin.y + hitFraction * translation.y
        );

        // Draw ray up to intersection
        drawLine(origin, intersectionPoint);
        drawPoint(intersectionPoint);

        // Draw normal at intersection point
        const normalEnd = new b2Vec2(
            intersectionPoint.x + result.normal.x,
            intersectionPoint.y + result.normal.y
        );
        drawLine(intersectionPoint, normalEnd);
    } else {
        // No hit - draw full ray
        const endpoint = new b2Vec2(
            origin.x + translation.x,
            origin.y + translation.y
        );
        drawLine(origin, endpoint);
    }
}

a white line enters the scene from outside the walls and intersects several shapes

To create reflected rays, we can use the normals at each intersection to bounce the ray repeatedly in this recursive function:

function drawReflectedRay(origin, translation, depth = 0) {
    if (depth > 8) return; // Limit reflection depth

    const filter = b2DefaultQueryFilter();
    const result = b2World_CastRayClosest(worldId, origin, translation, filter);

    if (result.hit) {
        // Calculate intersection point
        const hitFraction = result.fraction;
        const intersectionPoint = new b2Vec2(
            origin.x + hitFraction * translation.x,
            origin.y + hitFraction * translation.y
        );

        // Draw ray to intersection
        drawLine(origin, intersectionPoint);

        if (hitFraction < 1.0) {
            // Calculate reflection vector
            const dot = 2 * (
                translation.x * result.normal.x + 
                translation.y * result.normal.y
            );

            const remainingTranslation = new b2Vec2(
                translation.x - dot * result.normal.x,
                translation.y - dot * result.normal.y
            );

            // Recurse with reflected ray
            drawReflectedRay(
                intersectionPoint,
                remainingTranslation,
                depth + 1
            );
        }
    } else {
        // No hit - draw full ray
        const endpoint = new b2Vec2(
            origin.x + translation.x,
            origin.y + translation.y
        );
        drawLine(origin, endpoint);
    }
}

And use it in the update function:

function update() {
    currentRayAngle += (Math.PI * 2) / (20 * 60);

    const rayLength = 25;
    const origin = new b2Vec2(0, 20);
    const translation = new b2Vec2(
        rayLength * Math.sin(currentRayAngle),
        rayLength * Math.cos(currentRayAngle)
    );

    drawReflectedRay(origin, translation);
}

a white line bounces off a number of shapes before coming to an end

Credits

This tutorial is adapted from an original piece of work created by Chris Campbell and is used under license.