Help support this project by starring the repo on GitHub!

Surface Projection

The injected SurfaceProjection strategy that powers each topology's GPU position/normal assembly and CPU query/raycast/LOD — and how to add a new surface shape

A surface projection is the strategy a topology injects to describe how its surface is shaped and queried. It is the single extension point for adding new surface shapes: every built-in topology carries a SurfaceProjection, and the rest of the pipeline — the GPU vertex/normal stages, the CPU terrain cache, the query objects, the raycaster, and the LOD camera offset — calls into it rather than branching on a projection kind.

This is dependency injection rather than a switch/if (projection === ...) ladder: adding the torus required zero changes to the GPU pipeline, the CPU query/raycast machinery, or the React runtime.

The interface

interface SurfaceProjection {
  /** Identifier for debugging/telemetry only — never switched on internally. */
  readonly kind: "flat" | "cubeSphere" | "torus" | (string & {});
  /** Representative radius (bounds/uniform helper); undefined for flat. */
  readonly radius?: number;
  /** Surface center in world space; undefined for flat. */
  readonly center?: { x: number; y: number; z: number };
  /** Closed surfaces face outward → the mesh flips its triangle winding. */
  readonly faceOutward: boolean;
  /**
   * Per-axis level-0 tile count before LOD subdivision. Defaults to `{ u: 1, v: 1 }`.
   * At level `L`, resolution is `baseU × 2^L` by `baseV × 2^L` tiles.
   */
  readonly baseResolution?: { u: number; v: number };

  gpu: SurfaceProjectionGpu; // TSL builders for the render + compute stages
  cpu: SurfaceProjectionCpu; // query / raycast / LOD on the CPU snapshot
}

GPU hooks (gpu)

HookPurpose
renderVertexPosition(ctx)Render-stage world position for a vertex; also assigns the vertex normal varying.
createTileComputeParts(ctx)Projection-specific compute-stage builders: tile size, root UV, and the tile vertex world position.
createFieldNormal(ctx)The field-stage surface-normal reconstruction.
augmentSampler?(sampler, params)Optional extra GPU samplers (e.g. the cube-sphere's ByDirection samplers).

CPU hooks (cpu)

HookPurpose
createSurfaceOps()The surface math injected into the terrain cache (position → (space, u, v), displaced position, normal); null for flat.
createRuntimeQueries(cache)Build the runtime query objects: the flat query, the generic surfaceQuery, and (cube-sphere) the sphereQuery.
raycast(ctx)The projection-specific CPU ray pick against the displaced surface; returns null when the ray misses or the query snapshot isn't ready.

Built-in projections

FactoryKindNotes
createFlatProjection()flatXZ heightfield; no surface query (surfaceQuery is null).
createCubeSphereProjection({ radius, center, invert })cubeSphereRadial sphere mapping; exposes surfaceQuery and sphereQuery.
createTorusProjection({ majorRadius, minorRadius, center, invert, baseU, baseV })torusClosed donut; exposes surfaceQuery. baseU/baseV default to 1; the topology auto-sets baseU = round(major/minor).

You normally never construct these directly — the topology factories (createFlatTopology, createCubeSphereTopology, createTorusTopology, …) wire the matching projection for you.

Adding a new surface shape

To add a new closed surface you implement one SurfaceProjection and a small Topology:

  1. Map math — write the forward map (u, v) → world position (GPU + CPU) and the inverse world position → (u, v), plus an outward normal.
  2. gpu hooks — assemble the displaced vertex position and reconstruct the normal from displaced neighbor positions (curvature-correct, frame-independent).
  3. cpu hooks — provide createSurfaceOps() (used by the terrain cache), createRuntimeQueries() (wraps the cache as query/surfaceQuery), and raycast().
  4. Topology — emit root tiles, resolve same-level neighbors (wrap for periodic axes), and write conservative tileBounds. Set projection to your new strategy.

The torus source is the reference implementation; it reuses the shared curved-surface render/normal helpers, so the projection itself is small.

  • Topology Types — the topologies that inject these projections
  • Terrain Query — the runtime query / surfaceQuery / sphereQuery objects
  • Torus example — a new surface added purely via projection injection