Help support this project by starring the repo on GitHub!

Context and Runtime

Share terrain state with sibling systems using TerrainProvider and useTerrainContext

Why Context Exists

Terrain provides terrain context to its own subtree, but many real scenes need terrain access from sibling systems such as:

  • character controllers
  • camera rigs
  • raycast tools
  • gameplay systems

For those cases, create the terrain handle with useTerrain(), provide it with TerrainProvider, and read it with useTerrainContext().

Provider Pattern

import {
  Terrain,
  TerrainProvider,
  useTerrain,
  useTerrainContext,
} from "@hello-terrain/react";

function Scene() {
  const terrain = useTerrain({
    rootSize: 4096,
    maxLevel: 6,
    elevation,
    getCameraOrigin,
    cameraHysteresis: 0.35,
  });

  return (
    <TerrainProvider value={terrain}>
      <CharacterController />
      <Terrain terrain={terrain}>
        {({ positionNode }) => (
          <meshStandardNodeMaterial positionNode={positionNode} />
        )}
      </Terrain>
    </TerrainProvider>
  );
}

Reading The Terrain Handle

Any descendant of TerrainProvider can read the terrain handle:

function CharacterController() {
  const terrain = useTerrainContext();

  if (!terrain.ready) return null;

  return null;
}

Runtime Access

terrain.runtime contains the imperative runtime helpers that systems outside the material path usually care about:

const terrain = useTerrainContext();

const query = terrain.runtime.query;
const raycast = terrain.runtime.raycast;

These values are updated by the terrain graph. They may be null before the terrain is ready.

The safest pattern is to gate terrain-dependent behavior on terrain.ready:

const terrain = useTerrainContext();

useCharacterController({
  terrainRuntime: terrain.runtime,
  enabled: terrain.ready,
});

That is the pattern used by the React character controller example. It prevents cameras, movement code, or raycasts from running before the terrain graph has produced the required runtime state.

Render Subtree vs Siblings

There are two common shapes:

Terrain-only subtree

If everything that needs the handle lives under the Terrain subtree, the built-in provider inside Terrain is enough.

Shared scene systems

If a controller, camera, or gameplay system is a sibling of Terrain, wrap both in TerrainProvider so they share one TerrainHandle.

Example Use Cases

  • Third-person character movement that queries ground height.
  • Cameras that raycast against the current terrain tiles.
  • Debug panels that inspect the graph or runtime state.

See React Examples for full scene implementations.