Raycasting
Raycasting casts a ray into the scene and reports what it meets — the shape, the exact point, the face, its normal and its texture coordinate. Pointer events raycast too, but only ever answer which shape; this is how you get the rest.
NOTE
For the full API, see the 3D API Reference.
Demo
Casting a ray
context.raycast(x, y) builds the world-space ray through a point on the surface, in the same logical CSS pixels the pointer reports. It is correct under both perspective and orthographic projection — an orthographic ray is parallel to the view direction rather than fanning from an eye point.
const ray = context.raycast(pointerX, pointerY);context.raycastAll(scene, x, y) casts that ray and returns every shape it meets, nearest first, reaching through nested groups.
const hits = context.raycastAll(scene, pointerX, pointerY);A single shape can be tested directly:
const hit = torus.raycast(ray, { backFaces: false });What a hit reports
element: the shape that was hitdistance: distance along the ray, in world unitspoint: the world-space point of the hitface: the face that was hitfaceIndex: its index within the shape's face listnormal: the world-space surface normal, interpolated when the face carries vertex normalsuv: the texture coordinate at the hit, when the face carries UVsbackFacing: whether the triangle was met from behind
Why not just use pointer events
3D shapes support the ordinary pointer events, and for most interactions those are the right tool — they need no wiring, and they raycast too: a pointer over the hole of a torus passes through it, and where two parts overlap the nearer one wins.
What they cannot give you is the hit itself. mouseenter says that a shape was hit, not where, on which face, at what texture coordinate, or how far along the ray. Reach for raycast when you need any of that — placing a marker on a surface, reading a value off a plot, aligning something to the normal at the hit — or when you want to query the scene without a pointer at all.
Two things pointer events do not honour, because they have no meaning for a solid: pointerEvents: 'fill' and 'stroke' are both treated as 'all'. 'none' still opts a shape out entirely.