Terrain Raycasting API
High-performance terrain picking with CPU raycasting
Overview
terrainTasks.terrainRaycast provides terrain hit testing via pick(ray, options?) — a synchronous CPU raycast built on the terrain query foundation, so elevation sampling and ray intersections stay consistent.
Get the raycast context
import { terrainGraph, terrainTasks } from "@hello-terrain/three";
const graph = terrainGraph();
await graph.run({ resources: { renderer } });
const terrainRaycast = graph.get(terrainTasks.terrainRaycast);
if (!terrainRaycast) {
// Query cache is not ready yet.
return;
}Direct raycasting
import { Ray } from "three";
const ray = new Ray(camera.position.clone(), camera.getWorldDirection(new Vector3()));
const hit = terrainRaycast.pick(ray, {
maxSteps: 128,
refinementSteps: 8,
maxDistance: 5000,
});
if (hit) {
marker.position.copy(hit.position);
marker.lookAt(hit.position.clone().add(hit.normal));
}Cube-sphere raycasting
pick(ray, options?) is projection-aware and needs no special call site. When
the active topology is a cube sphere, the ray is intersected with the planet's
bounding shell and marched in radial signed distance (|p - center| - (radius + elevation)) rather than against a horizontal heightfield. The
returned position lies on the displaced sphere surface and normal is the
world-space surface normal.
import { Ray, Vector3 } from "three";
// e.g. from an R3F pointer event you can use event.ray directly.
const ray = new Ray(camera.position.clone(), camera.getWorldDirection(new Vector3()));
const hit = terrainRaycast.pick(ray);
if (hit) {
marker.position.copy(hit.position);
}Three.js Raycaster integration
TerrainMesh supports standard Three.js / React Three Fiber ray interactions once a terrain raycast context is attached.
terrainMesh.terrainRaycast = terrainRaycast;After this, default raycasting pipelines can hit the terrain mesh and receive terrain-aware intersection data.
React: pointer events on <Terrain>
Because the terrain mesh raycasts against the real surface, you can attach R3F
pointer handlers (onPointerDown, onPointerMove, …) directly to the
<Terrain> component — no invisible proxy mesh required. The handler fires only
when the ray meets the actual terrain, and event.point is the precise world-space
hit (event.ray is also available if you want to re-pick with custom options).
<Terrain
terrain={terrain}
onPointerDown={(event) => {
event.stopPropagation();
// Only fires on a real surface hit; clicks that miss the terrain do nothing.
dropMarker(event.point);
}}
>
{({ positionNode }) => <meshStandardNodeMaterial positionNode={positionNode} />}
</Terrain>pick is precise: it intersects the actual displaced surface and returns null
when the ray misses (or before the query snapshot is ready), so clicks/hovers off
the terrain report nothing.
Options
RaycastOptions:
maxSteps(default128): linear march steps along the clipped ray segmentrefinementSteps(default8): binary search iterations after a sign changemaxDistance(default infinite): far clip for terrain intersection distance
Higher maxSteps / refinementSteps values improve precision on steep/complex terrain but increase query cost.