Help support this project by starring the repo on GitHub!

Terrain Raycasting API

High-performance terrain picking with CPU raycasting

tiles0
max seen0
level0 / 0
buffer0 / 0
fill0.0%
fps
min
max

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 (default 128): linear march steps along the clipped ray segment
  • refinementSteps (default 8): binary search iterations after a sign change
  • maxDistance (default infinite): far clip for terrain intersection distance

Higher maxSteps / refinementSteps values improve precision on steep/complex terrain but increase query cost.