Skip to main content

Ray Casting

The PlayCanvas physics engine allows you to perform ray casts. A ray cast is a query that determines whether a straight line between two 3D points intersects any collision shapes, and where. Ray casts are methods on the rigid body component system, app.systems.rigidbody, so they work the same wherever you build: you only need the application and two points.

Picking with a Ray Cast

One application of ray casting is picking, where the user can touch or click the screen to select an entity. The example below casts a ray from the camera through the pointer position and reports the closest body it hits:

Raycast

The ray starts at the camera and ends where the pointer position lands on the camera's far clip plane, so it passes through whatever is under the pointer:

function pick(screenX, screenY) {
// From the camera position...
const from = cameraEntity.getPosition();

// ...to the pointer position projected onto the far clip plane
const to = cameraEntity.camera.screenToWorld(screenX, screenY, cameraEntity.camera.farClip);

// Return the closest hit, or null
const result = app.systems.rigidbody.raycastFirst(from, to);
if (result) {
console.log(`You selected ${result.entity.name}`);
}
}

app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => pick(event.x, event.y));
app.touch?.on(pc.EVENT_TOUCHSTART, (event) => pick(event.touches[0].x, event.touches[0].y));

In the Editor this lives in a script on the camera entity, where this.app and this.entity stand in for app and cameraEntity; the Entity picking using physics tutorial has the complete script. In React, useApp() returns the application; in Web Components, whenReady('pc-app') does. See Moving Bodies for those patterns.

Raycast Results

The rigid body component system provides two query methods:

Each hit is a RaycastResult:

PropertyDescription
entityThe entity whose collision shape was hit
pointThe world space position of the hit
normalThe world space surface normal at the hit
hitFractionHow far along the ray the hit is, from 0 at start to 1 at end

Ray casts hit trigger volumes as well as rigid bodies. Use one of the filters below to skip them.

Filtering Ray Casts

Both methods accept an options object:

OptionDescription
filterTagsOnly report hits on entities whose tags match. Written like the arguments to Tags#has, inside an array: ['enemy'] requires the tag, ['enemy', 'boss'] requires both, [['red', 'blue']] requires either
filterCallbackA function that receives each hit entity and returns true to keep the hit
filterCollisionGroupThe collision group of the ray, tested against the mask of each body
filterCollisionMaskThe collision mask of the ray, tested against the group of each body
sortraycastAll only. Sort the hits from nearest to farthest
// Find every enemy the shot passes through, nearest first
const hits = app.systems.rigidbody.raycastAll(from, to, {
filterTags: ['enemy'],
sort: true
});
for (const hit of hits) {
hit.entity.script.health.damage(10 * (1 - hit.hitFraction));
}

// Pick anything except the player's own body
const result = app.systems.rigidbody.raycastFirst(from, to, {
filterCallback: (entity) => entity !== player
});

Filtering by tag or callback needs every hit along the ray, so raycastFirst with either option does the same work as raycastAll. Group and mask filtering happens inside the physics engine and is cheaper. The Physics raycasting by tag tutorial shows tag filtering in a full project.

Probing the Environment

Ray casting has other applications too. An entity can probe its surroundings by firing ray casts. For example, to determine whether a character is standing on the ground, cast a short ray straight down from its position and see whether anything other than the character itself is hit:

const from = player.getPosition();
const to = new pc.Vec3(from.x, from.y - 1.1, from.z);
const hit = app.systems.rigidbody.raycastFirst(from, to, {
filterCallback: (entity) => entity !== player
});
const onGround = hit !== null;

See Also