@hello-terrain/three 0.0.0-alpha.10 → 0.0.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,11 +1,11 @@
1
- import { BufferGeometry, Ray, Vector3 as Vector3$1, Raycaster, Intersection } from 'three';
1
+ import { BufferGeometry, Vector3 as Vector3$1, Ray, Raycaster, Intersection } from 'three';
2
2
  import * as three_webgpu from 'three/webgpu';
3
3
  import { StorageArrayTexture, StorageTexture, Node, WebGPURenderer, StorageBufferAttribute, StorageBufferNode, UniformNode, Vector3, Vector3Like, InstancedMesh, NodeMaterial, ConstNode } from 'three/webgpu';
4
4
  import Node$1 from 'three/src/nodes/core/Node.js';
5
- import * as three_src_nodes_TSL_js from 'three/src/nodes/TSL.js';
6
- import { ShaderCallNodeInternal } from 'three/src/nodes/TSL.js';
7
5
  import * as _hello_terrain_work from '@hello-terrain/work';
8
6
  import { TaskRef, Graph } from '@hello-terrain/work';
7
+ import * as three_src_nodes_TSL_js from 'three/src/nodes/TSL.js';
8
+ import { ShaderCallNodeInternal } from 'three/src/nodes/TSL.js';
9
9
  import * as three_tsl from 'three/tsl';
10
10
 
11
11
  /**
@@ -13,7 +13,14 @@ import * as three_tsl from 'three/tsl';
13
13
  * This geometry ensures that corner triangles are subdivided correctly.
14
14
  */
15
15
  declare class TerrainGeometry extends BufferGeometry {
16
- constructor(innerSegments?: number, extendUV?: boolean);
16
+ /**
17
+ * @param flipWinding Reverse triangle winding so front faces point the
18
+ * opposite way. The default winding makes flat tiles front-face `+Y`; the
19
+ * cube-sphere maps `(u→right, v→up)`, which would otherwise leave the
20
+ * planet's outer shell back-facing, so it passes `flipWinding` to render
21
+ * the outer surface with `FrontSide`.
22
+ */
23
+ constructor(innerSegments?: number, extendUV?: boolean, flipWinding?: boolean);
17
24
  /**
18
25
  * Generate indices for terrain geometry with proper skirt corner handling.
19
26
  * The key improvement is in how corner triangles are subdivided.
@@ -116,11 +123,6 @@ interface TerrainFieldStorage {
116
123
  }
117
124
  declare function ArrayTextureBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
118
125
  declare function AtlasBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
119
- /**
120
- * Placeholder backend for future true 3D storage-texture support in Three.js.
121
- * We keep it present to preserve the backend API shape.
122
- */
123
- declare function Texture3DBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
124
126
  declare function createTerrainFieldStorage(edgeVertexCount: number, tileCount: number, renderer?: WebGPURenderer, options?: TerrainFieldStorageOptions): TerrainFieldStorage;
125
127
  declare function storeTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node, value: Node): Node;
126
128
  declare function loadTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
@@ -132,20 +134,14 @@ declare function loadTerrainFieldNormal(storage: TerrainFieldStorage, ix: Node,
132
134
  */
133
135
  declare function sampleTerrainField(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
134
136
  declare function sampleTerrainFieldElevation(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
135
- declare function sampleTerrainFieldNormal(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
136
- declare function packTerrainFieldSample(height: Node, normalXZ: Node, extra?: Node): Node;
137
-
138
- interface ElevationParams {
139
- worldPosition: Node$1;
140
- rootSize: Node$1;
141
- rootUV: Node$1;
142
- tileUV: Node$1;
143
- tileLevel: Node$1;
144
- tileSize: Node$1;
145
- tileOriginVec2: Node$1;
146
- nodeIndex: Node$1;
147
- }
148
- type ElevationCallback = (params: ElevationParams) => Node$1;
137
+ /**
138
+ * Pack a terrain field sample into RGBA: `[height, Nx, Ny, Nz]` where
139
+ * `(Nx, Ny, Nz)` is the unit world-space surface normal. Storing the full
140
+ * world normal (rather than a face-local tangent pair) keeps shading
141
+ * continuous across cube-face seams, since adjacent faces no longer rotate the
142
+ * normal through their own parametric tangent frame.
143
+ */
144
+ declare function packTerrainFieldSample(height: Node, normal: Node): Node;
149
145
 
150
146
  interface TerrainUniformsParams {
151
147
  rootSize: number;
@@ -153,6 +149,7 @@ interface TerrainUniformsParams {
153
149
  innerTileSegments: number;
154
150
  skirtScale: number;
155
151
  elevationScale: number;
152
+ radius: number;
156
153
  instanceId: string;
157
154
  }
158
155
  interface TerrainUniformsContext {
@@ -161,6 +158,7 @@ interface TerrainUniformsContext {
161
158
  uInnerTileSegments: UniformNode<number>;
162
159
  uSkirtScale: UniformNode<number>;
163
160
  uElevationScale: UniformNode<number>;
161
+ uRadius: UniformNode<number>;
164
162
  }
165
163
  interface LeafStorageState {
166
164
  data: Int32Array<ArrayBuffer>;
@@ -168,145 +166,36 @@ interface LeafStorageState {
168
166
  node: StorageBufferNode;
169
167
  }
170
168
 
171
- interface GpuSpatialIndexContext {
172
- data: Uint32Array<ArrayBuffer>;
173
- size: number;
174
- mask: number;
175
- stampGen: UniformNode<number>;
176
- attribute: StorageBufferAttribute;
177
- node: StorageBufferNode;
178
- }
179
- interface TerrainSampler {
180
- sampleElevation: (worldX: Node, worldZ: Node) => Node;
181
- sampleNormal: (worldX: Node, worldZ: Node) => Node;
182
- sampleTerrain: (worldX: Node, worldZ: Node) => Node;
183
- sampleValidity: (worldX: Node, worldZ: Node) => Node;
184
- evaluateElevation: (worldX: Node, worldZ: Node) => Node;
185
- evaluateNormal: (worldX: Node, worldZ: Node, epsilon?: Node) => Node;
186
- }
187
- interface CreateTerrainSamplerParams {
188
- terrainFieldStorage: TerrainFieldStorage;
189
- spatialIndex: GpuSpatialIndexContext;
169
+ /** Shared (projection-independent) tile-compute helpers. */
170
+ type SharedTileCompute = {
171
+ tileLevel: (nodeIndex: Node) => Node;
172
+ tileFace: (nodeIndex: Node) => Node;
173
+ tileOriginVec2: (nodeIndex: Node) => Node;
174
+ /** Face-local (u, v) in [0, 1] for a grid sample, including skirt border. */
175
+ tileFaceUV: (nodeIndex: Node, ix: Node, iy: Node) => Node;
176
+ };
177
+ /** Projection-specific tile-compute builders. */
178
+ type TileComputeParts = {
179
+ tileSize: (nodeIndex: Node) => Node;
180
+ rootUV: (nodeIndex: Node, ix: Node, iy: Node) => Node;
181
+ tileVertexWorldPosition: (nodeIndex: Node, ix: Node, iy: Node) => Node;
182
+ };
183
+ type TileComputePartsContext = {
184
+ leafStorage: LeafStorageState;
190
185
  uniforms: TerrainUniformsContext;
191
- elevationCallback: ElevationCallback;
192
- }
193
- interface TerrainSample {
194
- elevation: number;
195
- normal: Vector3$1;
196
- valid: boolean;
197
- }
198
- interface TerrainElevationSample {
199
- elevation: number;
200
- valid: boolean;
201
- }
202
- interface TerrainSampleBatch {
203
- elevations: Float32Array;
204
- normals: Float32Array;
205
- valid: Uint8Array;
206
- generation: number;
207
- }
208
- interface TerrainTile {
209
- level: number;
210
- x: number;
211
- y: number;
212
- index: number;
213
- }
214
- interface TerrainTileBounds extends TerrainTile {
215
- minElevation: number;
216
- maxElevation: number;
217
- }
218
- interface ElevationRange {
219
- min: number;
220
- max: number;
221
- }
222
- interface TerrainQuery {
223
- getElevation(worldX: number, worldZ: number): number | null;
224
- getNormal(worldX: number, worldZ: number): Vector3$1 | null;
225
- getTile(worldX: number, worldZ: number): TerrainTile | null;
226
- getTileBounds(worldX: number, worldZ: number): TerrainTileBounds | null;
227
- getGlobalElevationRange(): ElevationRange | null;
228
- sampleTerrain(worldX: number, worldZ: number): TerrainSample;
229
- sampleTerrainBatch(positions: Float32Array): TerrainSampleBatch;
230
- readonly generation: number;
231
- }
232
- interface RaycastOptions {
233
- maxSteps?: number;
234
- refinementSteps?: number;
235
- maxDistance?: number;
236
- }
237
- interface TerrainRaycastResult {
238
- position: Vector3$1;
239
- normal: Vector3$1;
240
- distance: number;
241
- }
242
- interface TerrainRaycast {
243
- pick(ray: Ray, options?: RaycastOptions): TerrainRaycastResult | null;
244
- }
245
-
246
- type TerrainMeshParams = {
247
- innerTileSegments: number;
248
- maxNodes: number;
249
- material: NodeMaterial;
186
+ shared: SharedTileCompute;
187
+ };
188
+ type TileCompute = SharedTileCompute & {
189
+ tileSize: (nodeIndex: Node) => Node;
190
+ rootUVCompute: (nodeIndex: Node, ix: Node, iy: Node) => Node;
191
+ tileVertexWorldPositionCompute: (nodeIndex: Node, ix: Node, iy: Node) => Node;
250
192
  };
251
- declare class TerrainMesh extends InstancedMesh {
252
- private _innerTileSegments;
253
- private _maxNodes;
254
- terrainRaycast: TerrainRaycast | null;
255
- constructor(params?: Partial<TerrainMeshParams>);
256
- get innerTileSegments(): number;
257
- set innerTileSegments(tileSegments: number);
258
- get maxNodes(): number;
259
- set maxNodes(maxNodes: number);
260
- raycast(raycaster: Raycaster, intersects: Intersection[]): void;
261
- }
262
-
263
- interface TileBoundsContext {
264
- data: Float32Array<ArrayBuffer>;
265
- attribute: StorageBufferAttribute;
266
- node: StorageBufferNode;
267
- }
268
-
269
- type ComputeStageCallback = (nodeIndex: Node, globalVertexIndex: Node, uv: Node, localCoordinates: Node, texelSize: Node) => void;
270
- type ComputePipeline = ComputeStageCallback[];
271
-
272
- /** Default compile task — uses terrainFieldStageTask as the leaf. */
273
- declare const compileComputeTask: _hello_terrain_work.Task<{
274
- execute: (renderer: WebGPURenderer, instanceCount: number) => void;
275
- }, string, unknown>;
276
- /** Default execute task — dispatches the compiled kernel. */
277
- declare const executeComputeTask: _hello_terrain_work.Task<any, string, {
278
- renderer: WebGPURenderer;
279
- }>;
280
193
  /**
281
- * Factory for user-extensible pipelines.
282
- *
283
- * Users who add custom compute stages create their own stage tasks using
284
- * the accumulation pattern (`get()` predecessor, spread, append), then pass
285
- * their leaf stage to this helper to get compile + execute tasks.
286
- *
287
- * @example
288
- * ```ts
289
- * const erosionStageTask = task((get, work) => {
290
- * const upstream = get(elevationFieldStageTask);
291
- * return work((): ComputePipeline => [
292
- * ...upstream,
293
- * (nodeIndex, globalVertexIndex, uv) => {
294
- * // custom erosion logic
295
- * },
296
- * ]);
297
- * });
298
- *
299
- * const { compile, execute } = createComputePipelineTasks(erosionStageTask);
300
- * ```
194
+ * Build the per-tile compute helpers, composing projection-independent
195
+ * decoders with the projection-specific position/size/uv builders supplied by
196
+ * `projection.gpu.createTileComputeParts`.
301
197
  */
302
- declare function createComputePipelineTasks(leafStageTask: TaskRef<ComputePipeline>): {
303
- compile: _hello_terrain_work.Task<{
304
- execute: (renderer: WebGPURenderer, instanceCount: number) => void;
305
- }, string, unknown>;
306
- execute: _hello_terrain_work.Task<any, string, {
307
- renderer: WebGPURenderer;
308
- }>;
309
- };
198
+ declare function createTileCompute(leafStorage: LeafStorageState, uniforms: TerrainUniformsContext, projection: SurfaceProjection): TileCompute;
310
199
 
311
200
  declare const Dir: {
312
201
  readonly LEFT: 0;
@@ -333,10 +222,32 @@ type TileBounds = {
333
222
  /** conservative radius */
334
223
  r: number;
335
224
  };
336
- type Surface = {
225
+ /** Scaled world-space elevation displacement range for a tile. */
226
+ type ElevationRangeOut = {
227
+ min: number;
228
+ max: number;
229
+ };
230
+ type Topology = {
337
231
  spaceCount: number;
338
232
  /** maximum number of roots returned by `rootTiles` */
339
233
  maxRootCount: number;
234
+ /**
235
+ * Injected surface projection strategy. Encapsulates the GPU position/normal
236
+ * assembly and the CPU query/raycast/LOD behavior for this topology, so the
237
+ * pipeline never branches on a projection kind.
238
+ */
239
+ projection: SurfaceProjection;
240
+ /** Representative surface radius in world units (curved projections only). */
241
+ radius?: number;
242
+ /**
243
+ * Surface center in world space (curved projections only). Used to apply the
244
+ * camera elevation offset along the surface up-direction during LOD.
245
+ */
246
+ center?: {
247
+ x: number;
248
+ y: number;
249
+ z: number;
250
+ };
340
251
  /**
341
252
  * Compute the same-level neighbor TileId in the requested direction.
342
253
  * Returns false if the neighbor is outside the valid topology.
@@ -347,12 +258,13 @@ type Surface = {
347
258
  /**
348
259
  * Conservative camera-relative bounds for LOD decisions.
349
260
  * Avoids absolute world coordinates so Earth-scale worlds remain stable.
261
+ * When `elevationRange` is provided, bounds should account for displaced geometry.
350
262
  */
351
263
  tileBounds(tile: TileId, cameraOrigin: {
352
264
  x: number;
353
265
  y: number;
354
266
  z: number;
355
- }, out: TileBounds): void;
267
+ }, out: TileBounds, elevationRange?: ElevationRangeOut): void;
356
268
  /**
357
269
  * Fill root tiles for the current frame and return the count.
358
270
  * Implementations should write level-0 tiles into `out[0..count)`.
@@ -400,9 +312,11 @@ type UpdateParams = {
400
312
  z: number;
401
313
  };
402
314
  /**
403
- * Terrain elevation at the camera's XZ position (from previous frame).
404
- * Subtracted from `cameraOrigin.y` during refinement so that LOD
405
- * distance is measured relative to the terrain surface, not the datum.
315
+ * Terrain elevation beneath the camera (from the previous frame). During
316
+ * refinement it offsets the camera toward the terrain surface so LOD distance
317
+ * is measured relative to the surface rather than the datum:
318
+ * - flat: subtracted from `cameraOrigin.y`.
319
+ * - cube-sphere: subtracted along the radial up-direction from the planet center.
406
320
  */
407
321
  elevationAtCameraXZ?: number;
408
322
  /**
@@ -421,6 +335,11 @@ type UpdateParams = {
421
335
  targetPixels?: number;
422
336
  /** Prevent flicker by separating split/merge thresholds (0..1 typical) */
423
337
  hysteresis?: number;
338
+ /**
339
+ * Previous-frame per-tile elevation range in world-space displacement units
340
+ * (already scaled by `elevationScale`). Returns false when no data is available.
341
+ */
342
+ tileElevationRange?: (space: number, level: number, x: number, y: number, out: ElevationRangeOut) => boolean;
424
343
  };
425
344
  type QuadtreeConfig = {
426
345
  maxNodes: number;
@@ -487,19 +406,20 @@ type QuadtreeState = {
487
406
  scratchTile: TileId;
488
407
  scratchNeighbor: TileId;
489
408
  scratchBounds: TileBounds;
409
+ scratchElevationRange: ElevationRangeOut;
490
410
  scratchRootTiles: TileId[];
491
- /** surface space count is fixed for a given state */
411
+ /** topology space count is fixed for a given state */
492
412
  spaceCount: number;
493
413
  };
494
- declare function createState(cfg: QuadtreeConfig, surface: Surface): QuadtreeState;
495
- declare function beginUpdate(state: QuadtreeState, surface: Surface, params: UpdateParams): void;
414
+ declare function createState(cfg: QuadtreeConfig, topology: Topology): QuadtreeState;
415
+ declare function beginUpdate(state: QuadtreeState, topology: Topology, params: UpdateParams): void;
496
416
 
497
417
  /**
498
- * Update the quadtree for the given surface + camera parameters.
418
+ * Update the quadtree for the given topology + camera parameters.
499
419
  *
500
420
  * Produces a LeafSet of TileIds (SoA typed arrays).
501
421
  */
502
- declare function update(state: QuadtreeState, surface: Surface, params: UpdateParams, outLeaves?: LeafSet): LeafSet;
422
+ declare function update(state: QuadtreeState, topology: Topology, params: UpdateParams, outLeaves?: LeafSet): LeafSet;
503
423
 
504
424
  /**
505
425
  * Build a fixed-width seam/neighbor table for balanced leaves (2:1).
@@ -507,9 +427,9 @@ declare function update(state: QuadtreeState, surface: Surface, params: UpdatePa
507
427
  * Output neighbors are leaf-list indices, with U32_EMPTY for missing entries.
508
428
  * Layout: neighbors[leafIndex * 8 + edge*2 + slot].
509
429
  */
510
- declare function buildSeams2to1(surface: Surface, leaves: LeafSet, outSeams: SeamTable, outIndex?: SpatialIndex): SeamTable;
430
+ declare function buildSeams2to1(topology: Topology, leaves: LeafSet, outSeams: SeamTable, outIndex?: SpatialIndex): SeamTable;
511
431
 
512
- type FlatSurfaceConfig = {
432
+ type FlatTopologyConfig = {
513
433
  /**
514
434
  * World-space size of the root tile edge.
515
435
  * The root tile covers [-rootSize/2, +rootSize/2] around origin in X/Z.
@@ -520,44 +440,157 @@ type FlatSurfaceConfig = {
520
440
  y: number;
521
441
  z: number;
522
442
  };
523
- /** optional conservative vertical extent, included in bounds radius */
524
- maxHeight?: number;
525
443
  };
526
- declare function createFlatSurface(cfg: FlatSurfaceConfig): Surface;
444
+ declare function createFlatTopology(cfg: FlatTopologyConfig): Topology;
527
445
 
528
- type InfiniteFlatSurfaceConfig = {
446
+ type InfiniteFlatTopologyConfig = {
529
447
  rootSize: number;
530
448
  origin: {
531
449
  x: number;
532
450
  y: number;
533
451
  z: number;
534
452
  };
535
- /** optional conservative vertical extent, included in bounds radius */
536
- maxHeight?: number;
537
453
  /** half-width of root grid in root tiles (1 => 3x3 roots) */
538
454
  rootGridRadius?: number;
539
455
  };
540
- declare function createInfiniteFlatSurface(cfg: InfiniteFlatSurfaceConfig): Surface;
456
+ declare function createInfiniteFlatTopology(cfg: InfiniteFlatTopologyConfig): Topology;
541
457
 
542
- type CubeSphereSurfaceConfig = {
458
+ type CubeSphereTopologyConfig = {
459
+ /** Sphere radius in world units. */
543
460
  radius: number;
544
- maxHeight?: number;
461
+ /** Planet center in world space (defaults to origin). */
462
+ center?: {
463
+ x: number;
464
+ y: number;
465
+ z: number;
466
+ };
467
+ /** When true, elevation displaces inward and skirts point outward. */
468
+ invert?: boolean;
469
+ };
470
+ /**
471
+ * Cube-sphere topology: six quadtree faces wrapped onto a sphere.
472
+ *
473
+ * Topology (`neighborSameLevel`) is derived numerically from the shared
474
+ * `CUBE_FACES` basis so cross-face edges (including rotated pole edges)
475
+ * resolve to the correct neighbor tile without hand-coded transforms.
476
+ */
477
+ declare function createCubeSphereTopology(cfg: CubeSphereTopologyConfig): Topology;
478
+
479
+ /**
480
+ * Canonical cube-sphere face basis.
481
+ *
482
+ * Shared single source of truth between the CPU surface topology
483
+ * (`cubeSphere.ts`) and the GPU position/normal assembly (`tsl/cubeSphere.ts`)
484
+ * so both agree on geometry and faces seam correctly.
485
+ *
486
+ * Each face maps a face-local coordinate (u, v) in [0, 1] to a point on the
487
+ * cube `[-1, 1]^3` via:
488
+ *
489
+ * s = 2u - 1, t = 2v - 1
490
+ * cube = forward + s * right + t * up
491
+ *
492
+ * Normalizing `cube` yields the unit-sphere direction for that vertex.
493
+ *
494
+ * Bases are right-handed (`forward = right x up`) and outward-facing.
495
+ * Space indices: 0:+X 1:-X 2:+Y 3:-Y 4:+Z 5:-Z.
496
+ */
497
+ type Vec3 = readonly [number, number, number];
498
+ type CubeFace = {
499
+ forward: Vec3;
500
+ right: Vec3;
501
+ up: Vec3;
545
502
  };
503
+ declare const CUBE_FACE_COUNT = 6;
504
+ declare const CUBE_FACES: readonly CubeFace[];
505
+
506
+ type Vec3Mutable$1 = [number, number, number];
507
+ /**
508
+ * Cube-space point for a face-local coordinate (u, v) in [0, 1]:
509
+ * cube = forward + (2u-1) * right + (2v-1) * up
510
+ * The result is unnormalized; normalize it to obtain the sphere direction.
511
+ */
512
+ declare function faceUVToCube(face: number, u: number, v: number, out: Vec3Mutable$1): void;
513
+ /** Pick the cube face whose normal axis dominates the direction. */
514
+ declare function directionToFace(d: Vec3): number;
515
+ /** Face-local (u, v) in [0, 1] for a direction known to fall on `face`. */
516
+ declare function directionToFaceUV(face: number, d: Vec3, out: [number, number]): void;
546
517
  /**
547
- * Placeholder cube-sphere surface.
518
+ * Convert latitude/longitude (degrees) to a unit sphere direction.
548
519
  *
549
- * This exists to localize future planet work behind the `Surface` interface.
550
- * Topology remapping across face edges is intentionally TODO.
520
+ * Convention matches `CUBE_FACES` (+Y is the north pole):
521
+ * - latitude is the angle above the equator, in `[-90, 90]`
522
+ * - longitude is the angle around the +Y axis, in `[-180, 180]`,
523
+ * measured from +Z toward +X (lon = 0 points along +Z).
551
524
  */
552
- declare function createCubeSphereSurface(_cfg: CubeSphereSurfaceConfig): Surface;
525
+ declare function latLongToDirection(latDeg: number, lonDeg: number, out: Vec3Mutable$1): void;
526
+ /** Inverse of {@link latLongToDirection}; returns degrees. */
527
+ declare function directionToLatLong(d: Vec3): {
528
+ latitude: number;
529
+ longitude: number;
530
+ };
553
531
 
554
- declare function createTileCompute(leafStorage: LeafStorageState, uniforms: TerrainUniformsContext): {
555
- tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
556
- tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
557
- tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
558
- rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node, number | Node, number | Node]>;
559
- tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node, number | Node, number | Node]>;
532
+ type TorusTopologyConfig = {
533
+ /** Distance from the torus center to the tube center (the donut radius). */
534
+ majorRadius: number;
535
+ /** Radius of the tube cross-section. */
536
+ minorRadius: number;
537
+ /** Torus center in world space (defaults to origin). */
538
+ center?: {
539
+ x: number;
540
+ y: number;
541
+ z: number;
542
+ };
543
+ /** When true, elevation displaces inward and skirts point outward. */
544
+ invert?: boolean;
560
545
  };
546
+ /**
547
+ * Torus (donut) topology: a single quadtree space whose `(u, v)` axes wrap
548
+ * around the major circle and the tube cross-section. Both axes are periodic,
549
+ * so every same-level neighbor exists (wrapping modulo the level resolution).
550
+ *
551
+ * Level-0 resolution is anisotropic: `baseU = round(major/minor)` tiles along
552
+ * `u` and `baseV = 1` along `v`, so root tiles are approximately square in
553
+ * world space before isotropic LOD subdivision.
554
+ */
555
+ declare function createTorusTopology(cfg: TorusTopologyConfig): Topology;
556
+
557
+ type Vec3Mutable = [number, number, number];
558
+ /** Wrap a value into [0, 1). */
559
+ declare function wrap01(t: number): number;
560
+ /**
561
+ * Torus surface point for parameters (u, v) in [0, 1].
562
+ *
563
+ * Convention (matches `latLongToDirection`'s longitude axis):
564
+ * - `theta = 2*pi*u` sweeps around the +Y axis, measured from +Z toward +X.
565
+ * - `phi = 2*pi*v` sweeps around the tube cross-section, `phi = 0` pointing
566
+ * radially outward in the XZ plane and `phi = pi/2` pointing toward +Y.
567
+ *
568
+ * `displacement` is the elevation added to the tube (minor) radius.
569
+ */
570
+ declare function torusUVToPoint(u: number, v: number, majorRadius: number, minorRadius: number, displacement: number, center: {
571
+ x: number;
572
+ y: number;
573
+ z: number;
574
+ }, out: Vec3Mutable, invert?: boolean): void;
575
+ /** Outward unit surface normal of the base (undisplaced) torus at (u, v). */
576
+ declare function torusOutwardNormal(u: number, v: number, out: Vec3Mutable, invert?: boolean): void;
577
+ type TorusSurfaceParams = {
578
+ /** Wrapped major-circle parameter in [0, 1). */
579
+ u: number;
580
+ /** Wrapped tube parameter in [0, 1). */
581
+ v: number;
582
+ /** Distance from the point to the tube center circle. */
583
+ tubeDistance: number;
584
+ };
585
+ /**
586
+ * Map a world point to torus surface parameters. `tubeDistance - minorRadius`
587
+ * is the signed radial displacement of the point relative to the base torus.
588
+ */
589
+ declare function positionToTorusParams(px: number, py: number, pz: number, majorRadius: number, center: {
590
+ x: number;
591
+ y: number;
592
+ z: number;
593
+ }, out: TorusSurfaceParams): void;
561
594
 
562
595
  type TerrainQueryConfig = {
563
596
  rootSize: number;
@@ -567,12 +600,20 @@ type TerrainQueryConfig = {
567
600
  innerTileSegments: number;
568
601
  elevationScale: number;
569
602
  maxLevel: number;
603
+ /** Representative surface radius (curved projections only). */
604
+ radius: number;
605
+ /** Level-0 tile count along u before LOD subdivision (defaults to 1). */
606
+ baseU?: number;
607
+ /** Level-0 tile count along v before LOD subdivision (defaults to 1). */
608
+ baseV?: number;
570
609
  };
571
610
  interface CpuTerrainCache {
572
611
  readonly generation: number;
573
612
  readonly ready: boolean;
574
613
  updateConfig(config: TerrainQueryConfig): void;
575
614
  triggerReadback(renderer: WebGPURenderer, attribute: StorageBufferAttribute, spatialIndex: SpatialIndex, boundsAttribute?: StorageBufferAttribute, activeLeafCount?: number): void;
615
+ /** Release GPU readback staging buffers owned by this cache. */
616
+ dispose(): void;
576
617
  getElevation(worldX: number, worldZ: number): number | null;
577
618
  getNormal(worldX: number, worldZ: number): Vector3$1 | null;
578
619
  getTile(worldX: number, worldZ: number): TerrainTile | null;
@@ -580,11 +621,412 @@ interface CpuTerrainCache {
580
621
  getGlobalElevationRange(): ElevationRange | null;
581
622
  sampleTerrainBatch(positions: Float32Array): TerrainSampleBatch;
582
623
  sampleTerrain(worldX: number, worldZ: number): TerrainSample;
624
+ /** True when the active projection supplies surface ops. */
625
+ readonly hasSurface: boolean;
626
+ sampleSurfaceByPosition(px: number, py: number, pz: number): TerrainSurfaceSample;
627
+ getElevationBySurfacePosition(px: number, py: number, pz: number): number | null;
628
+ getNormalBySurfacePosition(px: number, py: number, pz: number): Vector3$1 | null;
629
+ getTileBySurfacePosition(px: number, py: number, pz: number): TerrainTile | null;
630
+ getTileBoundsBySurfacePosition(px: number, py: number, pz: number): TerrainTileBounds | null;
631
+ sampleSurfaceBatchByPosition(positions: Float32Array): TerrainSurfaceSampleBatch;
632
+ /**
633
+ * Previous-frame raw elevation min/max for a tile (unscaled field values).
634
+ * Returns false when no snapshot data is available for the tile.
635
+ */
636
+ getTileElevationRange(space: number, level: number, x: number, y: number, out: {
637
+ min: number;
638
+ max: number;
639
+ }): boolean;
583
640
  }
584
641
 
642
+ /**
643
+ * CPU sampling over a snapshot elevation-field buffer laid out as
644
+ * `maxNodes × (edgeVertexCount × edgeVertexCount)` raw heights.
645
+ *
646
+ * Plain-number math only (no three.js, no TSL); callers build vectors at the
647
+ * consumer-facing boundary.
648
+ */
649
+ interface ElevationGridShape {
650
+ edgeVertexCount: number;
651
+ verticesPerNode: number;
652
+ }
653
+
654
+ /** Stable projection identifier — for debugging/telemetry only, never switched on. */
655
+ type ProjectionKind = "flat" | "cubeSphere" | "torus" | (string & {});
656
+ interface Vec3Like {
657
+ x: number;
658
+ y: number;
659
+ z: number;
660
+ }
661
+ interface RenderVertexPositionContext {
662
+ leafStorage: LeafStorageState;
663
+ uniforms: TerrainUniformsContext;
664
+ terrainFieldStorage?: TerrainFieldStorage;
665
+ }
666
+ interface FieldNormalContext {
667
+ elevationFieldNode: Node;
668
+ edgeVertexCount: number;
669
+ tile: TileCompute;
670
+ uniforms: TerrainUniformsContext;
671
+ }
672
+ /** Per-vertex field-stage normal: `(nodeIndex, ix, iy) => unit world normal`. */
673
+ type FieldNormalFn = (nodeIndex: Node, ix: Node, iy: Node) => Node;
674
+ interface SurfaceProjectionGpu {
675
+ /** Render-path world position; also assigns the vertex normal varying. */
676
+ renderVertexPosition(ctx: RenderVertexPositionContext): Node;
677
+ /** Projection-specific compute-stage tile builders (size / uv / position). */
678
+ createTileComputeParts(ctx: TileComputePartsContext): TileComputeParts;
679
+ /** Field-stage surface normal builder. */
680
+ createFieldNormal(ctx: FieldNormalContext): FieldNormalFn;
681
+ /** Optional extra GPU samplers (e.g. sphere ByDirection); no-op for flat. */
682
+ augmentSampler?(sampler: TerrainSampler, params: CreateTerrainSamplerParams): void;
683
+ }
684
+ /** A world point projected onto a closed surface: tile space + face-local uv. */
685
+ interface SurfaceKey {
686
+ /** Tile space/face index (0 for torus, 0..5 for cube-sphere faces). */
687
+ space: number;
688
+ /** Face-local u in [0, 1). */
689
+ u: number;
690
+ /** Face-local v in [0, 1). */
691
+ v: number;
692
+ /** Outward unit direction at the key (for the surface sample `direction`). */
693
+ dirX: number;
694
+ dirY: number;
695
+ dirZ: number;
696
+ }
697
+ /** Grid neighborhood passed to {@link CpuSurfaceOps.surfaceNormal}. */
698
+ interface SurfaceNormalContext {
699
+ elevation: Float32Array;
700
+ shape: ElevationGridShape;
701
+ leafIndex: number;
702
+ /** Fractional grid coords of the sample within the leaf. */
703
+ gx: number;
704
+ gy: number;
705
+ innerTileSegments: number;
706
+ elevationScale: number;
707
+ level: number;
708
+ }
709
+ /**
710
+ * Projection-specific CPU surface math, injected into the terrain cache so the
711
+ * cache stays projection-agnostic. Implementations own their scratch (no
712
+ * module-scope state).
713
+ */
714
+ interface CpuSurfaceOps {
715
+ /** Map a world position to a surface key; `false` if it has no projection. */
716
+ positionToKey(px: number, py: number, pz: number, out: SurfaceKey): boolean;
717
+ /** Displaced world position from a key + scaled elevation (writes `out`). */
718
+ surfacePosition(key: SurfaceKey, elevation: number, out: Vector3$1): void;
719
+ /** Unit world-space surface normal from a key + grid neighborhood. */
720
+ surfaceNormal(key: SurfaceKey, ctx: SurfaceNormalContext): Vector3$1;
721
+ }
722
+ interface ProjectionRaycastContext {
723
+ ray: Ray;
724
+ options?: RaycastOptions;
725
+ terrainQuery: TerrainQuery | null;
726
+ surfaceQuery: TerrainSurfaceQuery | null;
727
+ sphereQuery: TerrainSphereQuery | null;
728
+ config: TerrainRaycastConfig;
729
+ }
730
+ interface RuntimeQueries {
731
+ query: TerrainQuery;
732
+ /** Generic surface query (position/uv keyed); `null` on flat surfaces. */
733
+ surfaceQuery: TerrainSurfaceQuery | null;
734
+ /** Cube-sphere query (adds direction/lat-long); `null` unless cube-sphere. */
735
+ sphereQuery: TerrainSphereQuery | null;
736
+ }
737
+ interface SurfaceProjectionCpu {
738
+ /**
739
+ * Offset the camera toward the terrain surface so LOD distance is measured
740
+ * from the surface, not the datum. Mutates `cam` in place by `elevation`
741
+ * along the projection's up-direction.
742
+ */
743
+ cameraSurfaceOffset(cam: Vec3Like, elevation: number): void;
744
+ /**
745
+ * Surface sampling ops injected into the terrain cache; `null` for flat
746
+ * surfaces (which have no closed-surface query).
747
+ */
748
+ createSurfaceOps(): CpuSurfaceOps | null;
749
+ /** Build the runtime query objects the projection exposes. */
750
+ createRuntimeQueries(cache: CpuTerrainCache): RuntimeQueries;
751
+ /** Projection-specific CPU raycast (precise, falling back to bounds). */
752
+ raycast(ctx: ProjectionRaycastContext): TerrainRaycastResult | null;
753
+ }
754
+ interface SurfaceProjection {
755
+ /** Identifier; never switched on internally. */
756
+ readonly kind: ProjectionKind;
757
+ /** Representative radius (bounds/uniform helper); undefined for flat. */
758
+ readonly radius?: number;
759
+ /** Surface center in world space; undefined for flat. */
760
+ readonly center?: Vec3Like;
761
+ /** Closed surfaces face outward → flip triangle winding. */
762
+ readonly faceOutward: boolean;
763
+ /**
764
+ * Per-axis level-0 tile count before LOD subdivision. Defaults to `{ u: 1, v: 1 }`.
765
+ * At level `L`, resolution is `baseU * 2^L` by `baseV * 2^L` tiles.
766
+ */
767
+ readonly baseResolution?: {
768
+ u: number;
769
+ v: number;
770
+ };
771
+ gpu: SurfaceProjectionGpu;
772
+ cpu: SurfaceProjectionCpu;
773
+ }
774
+
775
+ interface ElevationParams {
776
+ worldPosition: Node$1;
777
+ rootSize: Node$1;
778
+ rootUV: Node$1;
779
+ tileUV: Node$1;
780
+ tileLevel: Node$1;
781
+ tileSize: Node$1;
782
+ tileOriginVec2: Node$1;
783
+ nodeIndex: Node$1;
784
+ }
785
+ type ElevationCallback = (params: ElevationParams) => Node$1;
786
+
787
+ interface GpuSpatialIndexContext {
788
+ data: Uint32Array<ArrayBuffer>;
789
+ size: number;
790
+ mask: number;
791
+ stampGen: UniformNode<number>;
792
+ attribute: StorageBufferAttribute;
793
+ node: StorageBufferNode;
794
+ }
795
+ interface TerrainSampler {
796
+ sampleElevation: (worldX: Node, worldZ: Node) => Node;
797
+ sampleNormal: (worldX: Node, worldZ: Node) => Node;
798
+ sampleTerrain: (worldX: Node, worldZ: Node) => Node;
799
+ sampleValidity: (worldX: Node, worldZ: Node) => Node;
800
+ evaluateElevation: (worldX: Node, worldZ: Node) => Node;
801
+ evaluateNormal: (worldX: Node, worldZ: Node, epsilon?: Node) => Node;
802
+ /** Packed `vec4(elevation, nx, ny, nz)` where the normal is tangent-space. */
803
+ sampleTerrainByDirection?: (direction: Node) => Node;
804
+ sampleElevationByDirection?: (direction: Node) => Node;
805
+ /** World-space surface normal reconstructed in the sphere tangent frame. */
806
+ sampleNormalByDirection?: (direction: Node) => Node;
807
+ sampleValidityByDirection?: (direction: Node) => Node;
808
+ }
809
+ interface CreateTerrainSamplerParams {
810
+ terrainFieldStorage: TerrainFieldStorage;
811
+ spatialIndex: GpuSpatialIndexContext;
812
+ uniforms: TerrainUniformsContext;
813
+ elevationCallback: ElevationCallback;
814
+ /** Maximum quadtree level to probe during tile lookup. */
815
+ maxLevel: number;
816
+ /** Active surface projection (drives optional GPU sampler augmentation). */
817
+ projection: SurfaceProjection;
818
+ }
819
+ interface TerrainSample {
820
+ elevation: number;
821
+ normal: Vector3$1;
822
+ valid: boolean;
823
+ }
824
+ interface TerrainSampleBatch {
825
+ elevations: Float32Array;
826
+ normals: Float32Array;
827
+ valid: Uint8Array;
828
+ generation: number;
829
+ }
830
+ /**
831
+ * Result of sampling a cube-sphere surface from a direction/position/lat-long.
832
+ *
833
+ * `elevation` is the radial displacement above the base radius (already scaled
834
+ * by `elevationScale`); `position` is the full world-space surface point
835
+ * `center + direction * (radius + elevation)`.
836
+ */
837
+ interface TerrainSurfaceSample {
838
+ position: Vector3$1;
839
+ normal: Vector3$1;
840
+ direction: Vector3$1;
841
+ elevation: number;
842
+ valid: boolean;
843
+ }
844
+ interface TerrainSurfaceSampleBatch {
845
+ positions: Float32Array;
846
+ normals: Float32Array;
847
+ elevations: Float32Array;
848
+ valid: Uint8Array;
849
+ generation: number;
850
+ }
851
+ interface TerrainTile {
852
+ /** Surface space index: 0 for flat terrain, 0..5 for cube-sphere faces. */
853
+ space: number;
854
+ level: number;
855
+ x: number;
856
+ y: number;
857
+ index: number;
858
+ }
859
+ interface TerrainTileBounds extends TerrainTile {
860
+ minElevation: number;
861
+ maxElevation: number;
862
+ }
863
+ interface ElevationRange {
864
+ min: number;
865
+ max: number;
866
+ }
867
+ /**
868
+ * Flat (heightfield) terrain query, keyed on world XZ. For cube-sphere
869
+ * surfaces use {@link TerrainSphereQuery} instead.
870
+ */
871
+ interface TerrainQuery {
872
+ getElevation(worldX: number, worldZ: number): number | null;
873
+ getNormal(worldX: number, worldZ: number): Vector3$1 | null;
874
+ getTile(worldX: number, worldZ: number): TerrainTile | null;
875
+ getTileBounds(worldX: number, worldZ: number): TerrainTileBounds | null;
876
+ getGlobalElevationRange(): ElevationRange | null;
877
+ sampleTerrain(worldX: number, worldZ: number): TerrainSample;
878
+ sampleTerrainBatch(positions: Float32Array): TerrainSampleBatch;
879
+ readonly generation: number;
880
+ }
881
+ /**
882
+ * Generic closed-surface terrain query, keyed on a world position projected
883
+ * onto the surface. Exposed for every non-flat projection (cube-sphere, torus,
884
+ * ...); `null` on flat surfaces. Elevation is the displacement above the base
885
+ * surface (already scaled by `elevationScale`); `position` is the full
886
+ * world-space surface point.
887
+ */
888
+ interface TerrainSurfaceQuery {
889
+ readonly generation: number;
890
+ getElevationByPosition(position: Vector3$1): number | null;
891
+ getNormalByPosition(position: Vector3$1): Vector3$1 | null;
892
+ sampleTerrainByPosition(position: Vector3$1): TerrainSurfaceSample;
893
+ getTileByPosition(position: Vector3$1): TerrainTile | null;
894
+ getTileBoundsByPosition(position: Vector3$1): TerrainTileBounds | null;
895
+ /** Batch sample; `positions` is a Float32Array of xyz triples. */
896
+ sampleTerrainBatchByPosition(positions: Float32Array): TerrainSurfaceSampleBatch;
897
+ }
898
+ /**
899
+ * Cube-sphere terrain query. Extends the generic surface query with the
900
+ * sphere-only direction/lat-long keys. A surface location is identified by a
901
+ * direction from the planet center (the canonical form); `ByPosition` projects
902
+ * any world point onto its direction, and `ByLatLong` takes degrees (latitude
903
+ * `[-90, 90]`, longitude `[-180, 180]`).
904
+ *
905
+ * Exposed only when the active surface uses the `cubeSphere` projection
906
+ * (otherwise `null` on the query context / runtime).
907
+ */
908
+ interface TerrainSphereQuery extends TerrainSurfaceQuery {
909
+ getElevationByDirection(direction: Vector3$1): number | null;
910
+ getElevationByLatLong(latitudeDeg: number, longitudeDeg: number): number | null;
911
+ getNormalByDirection(direction: Vector3$1): Vector3$1 | null;
912
+ getNormalByLatLong(latitudeDeg: number, longitudeDeg: number): Vector3$1 | null;
913
+ sampleTerrainByDirection(direction: Vector3$1): TerrainSurfaceSample;
914
+ sampleTerrainByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainSurfaceSample;
915
+ getTileByDirection(direction: Vector3$1): TerrainTile | null;
916
+ getTileByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainTile | null;
917
+ getTileBoundsByDirection(direction: Vector3$1): TerrainTileBounds | null;
918
+ getTileBoundsByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainTileBounds | null;
919
+ /** Batch sample; `directions` is a Float32Array of xyz triples. */
920
+ sampleTerrainBatchByDirection(directions: Float32Array): TerrainSurfaceSampleBatch;
921
+ }
922
+ interface RaycastOptions {
923
+ maxSteps?: number;
924
+ refinementSteps?: number;
925
+ maxDistance?: number;
926
+ }
927
+ /**
928
+ * Shared raycast bounds for the CPU marcher. Flat raycasts use the XZ extent +
929
+ * `[minY, maxY]`; curved projections derive their own radial shell from their
930
+ * geometry plus the query's global elevation range, and only read `center*`.
931
+ */
932
+ interface TerrainRaycastConfig {
933
+ rootSize: number;
934
+ originX: number;
935
+ originY: number;
936
+ originZ: number;
937
+ minY: number;
938
+ maxY: number;
939
+ centerX: number;
940
+ centerY: number;
941
+ centerZ: number;
942
+ }
943
+ interface TerrainRaycastResult {
944
+ position: Vector3$1;
945
+ normal: Vector3$1;
946
+ distance: number;
947
+ }
948
+ interface TerrainRaycast {
949
+ pick(ray: Ray, options?: RaycastOptions): TerrainRaycastResult | null;
950
+ }
951
+
952
+ type TerrainMeshParams = {
953
+ innerTileSegments: number;
954
+ maxNodes: number;
955
+ material: NodeMaterial;
956
+ /**
957
+ * Reverse tile triangle winding. Cube-sphere surfaces set this so the
958
+ * planet's outer shell is front-facing and renders with `FrontSide`.
959
+ */
960
+ flipWinding: boolean;
961
+ };
962
+ declare class TerrainMesh extends InstancedMesh {
963
+ private _innerTileSegments;
964
+ private _maxNodes;
965
+ private _flipWinding;
966
+ terrainRaycast: TerrainRaycast | null;
967
+ constructor(params?: Partial<TerrainMeshParams>);
968
+ get innerTileSegments(): number;
969
+ set innerTileSegments(tileSegments: number);
970
+ get flipWinding(): boolean;
971
+ set flipWinding(flip: boolean);
972
+ get maxNodes(): number;
973
+ set maxNodes(maxNodes: number);
974
+ raycast(raycaster: Raycaster, intersects: Intersection[]): void;
975
+ }
976
+
977
+ interface TileBoundsContext {
978
+ data: Float32Array<ArrayBuffer>;
979
+ attribute: StorageBufferAttribute;
980
+ node: StorageBufferNode;
981
+ }
982
+
983
+ type ComputeStageCallback = (nodeIndex: Node, globalVertexIndex: Node, uv: Node, localCoordinates: Node, texelSize: Node) => void;
984
+ type ComputePipeline = ComputeStageCallback[];
985
+
986
+ /**
987
+ * Default compile + execute tasks — uses terrainFieldStageTask as the leaf.
988
+ * Derived from the same factory as user pipelines to avoid duplicated logic.
989
+ */
990
+ declare const compileComputeTask: _hello_terrain_work.Task<{
991
+ execute: (renderer: WebGPURenderer, instanceCount: number) => void;
992
+ }, string, unknown>;
993
+ declare const executeComputeTask: _hello_terrain_work.Task<any, string, {
994
+ renderer: WebGPURenderer;
995
+ }>;
996
+ /**
997
+ * Factory for user-extensible pipelines.
998
+ *
999
+ * Users who add custom compute stages create their own stage tasks using
1000
+ * the accumulation pattern (`get()` predecessor, spread, append), then pass
1001
+ * their leaf stage to this helper to get compile + execute tasks.
1002
+ *
1003
+ * @example
1004
+ * ```ts
1005
+ * const erosionStageTask = task((get, work) => {
1006
+ * const upstream = get(elevationFieldStageTask);
1007
+ * return work((): ComputePipeline => [
1008
+ * ...upstream,
1009
+ * (nodeIndex, globalVertexIndex, uv) => {
1010
+ * // custom erosion logic
1011
+ * },
1012
+ * ]);
1013
+ * });
1014
+ *
1015
+ * const { compile, execute } = createComputePipelineTasks(erosionStageTask);
1016
+ * ```
1017
+ */
1018
+ declare function createComputePipelineTasks(leafStageTask: TaskRef<ComputePipeline>): {
1019
+ compile: _hello_terrain_work.Task<{
1020
+ execute: (renderer: WebGPURenderer, instanceCount: number) => void;
1021
+ }, string, unknown>;
1022
+ execute: _hello_terrain_work.Task<any, string, {
1023
+ renderer: WebGPURenderer;
1024
+ }>;
1025
+ };
1026
+
585
1027
  interface QuadtreeConfigState {
586
1028
  state: QuadtreeState;
587
- surface: Surface;
1029
+ topology: Topology;
588
1030
  }
589
1031
  interface LeafGpuBufferState extends LeafStorageState {
590
1032
  count: number;
@@ -597,6 +1039,10 @@ interface ElevationFieldContext {
597
1039
  interface TerrainQueryContext {
598
1040
  cache: CpuTerrainCache;
599
1041
  query: TerrainQuery;
1042
+ /** Generic closed-surface query; `null` on flat surfaces. */
1043
+ surfaceQuery: TerrainSurfaceQuery | null;
1044
+ /** Cube-sphere query; `null` unless the topology uses the cubeSphere projection. */
1045
+ sphereQuery: TerrainSphereQuery | null;
600
1046
  shapeKey: string;
601
1047
  }
602
1048
  /** Task refs for the standard terrain pipeline. */
@@ -604,7 +1050,7 @@ interface TerrainTasks {
604
1050
  instanceId: TaskRef<string>;
605
1051
  quadtreeConfig: TaskRef<QuadtreeConfigState>;
606
1052
  quadtreeUpdate: TaskRef<LeafSet>;
607
- surface: TaskRef<Surface>;
1053
+ topology: TaskRef<Topology>;
608
1054
  leafStorage: TaskRef<LeafStorageState>;
609
1055
  leafGpuBuffer: TaskRef<LeafGpuBufferState>;
610
1056
  gpuSpatialIndexStorage: TaskRef<GpuSpatialIndexContext>;
@@ -639,13 +1085,7 @@ declare const createElevationFieldContextTask: _hello_terrain_work.Task<{
639
1085
  attribute: StorageBufferAttribute;
640
1086
  node: three_webgpu.StorageBufferNode;
641
1087
  }, string, unknown>;
642
- declare const tileNodesTask: _hello_terrain_work.Task<{
643
- tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
644
- tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
645
- tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
646
- rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
647
- tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
648
- }, string, unknown>;
1088
+ declare const tileNodesTask: _hello_terrain_work.Task<TileCompute, string, unknown>;
649
1089
  /**
650
1090
  * Root compute stage — generates elevation data and writes to the
651
1091
  * elevation field storage buffer. Returns a single-element `ComputePipeline`.
@@ -689,22 +1129,24 @@ declare const origin: _hello_terrain_work.ParamRef<{
689
1129
  z: number;
690
1130
  }>;
691
1131
  /**
692
- * Number of segments per inner tile edge.
693
- * Effective edge vertex count is `innerTileSegments + 3`.
1132
+ * Default number of segments per inner tile edge. The effective edge vertex
1133
+ * count is `innerTileSegments + 3`
694
1134
  */
695
1135
  declare const innerTileSegments: _hello_terrain_work.ParamRef<number>;
696
1136
  /** Skirt scale factor. */
697
1137
  declare const skirtScale: _hello_terrain_work.ParamRef<number>;
698
1138
  /** Elevation vertical scale. */
699
1139
  declare const elevationScale: _hello_terrain_work.ParamRef<number>;
1140
+ /** Sphere radius in world units (cube-sphere projection only). */
1141
+ declare const radius: _hello_terrain_work.ParamRef<number>;
700
1142
  /** Maximum quadtree nodes. */
701
1143
  declare const maxNodes: _hello_terrain_work.ParamRef<number>;
702
1144
  /** Maximum quadtree subdivision level. */
703
1145
  declare const maxLevel: _hello_terrain_work.ParamRef<number>;
704
1146
  /** Quadtree update configuration (camera, mode, etc.). */
705
1147
  declare const quadtreeUpdate: _hello_terrain_work.ParamRef<UpdateParams>;
706
- /** Optional custom terrain surface; defaults to bounded flat surface when null. */
707
- declare const surface: _hello_terrain_work.ParamRef<Surface | null>;
1148
+ /** Optional custom terrain topology; defaults to bounded flat topology when null. */
1149
+ declare const topology: _hello_terrain_work.ParamRef<Topology | null>;
708
1150
  /** Terrain field texture filter mode. */
709
1151
  declare const terrainFieldFilter: _hello_terrain_work.ParamRef<"nearest" | "linear">;
710
1152
  /** Terrain elevation control function (per vertex, in gpu compute) */
@@ -724,14 +1166,14 @@ declare const elevationFn: _hello_terrain_work.ParamRef<ElevationCallback>;
724
1166
  * (e.g. buffer resize), so this task stays cached during normal quadtree
725
1167
  * updates — no unnecessary shader rebuilds.
726
1168
  */
727
- declare const positionNodeTask: _hello_terrain_work.Task<three_src_nodes_TSL_js.ShaderCallNodeInternal, string, unknown>;
1169
+ declare const positionNodeTask: _hello_terrain_work.Task<three_webgpu.Node, string, unknown>;
728
1170
 
729
1171
  /**
730
- * Derives the terrain surface from `rootSize` and `origin`.
1172
+ * Derives the terrain topology from `rootSize` and `origin`.
731
1173
  * Automatically recomputes when either param changes, keeping the
732
1174
  * quadtree refinement in sync with the GPU-side tile positioning.
733
1175
  */
734
- declare const surfaceTask: _hello_terrain_work.Task<Surface, string, unknown>;
1176
+ declare const topologyTask: _hello_terrain_work.Task<Topology, string, unknown>;
735
1177
  declare const quadtreeConfigTask: _hello_terrain_work.Task<QuadtreeConfigState, string, unknown>;
736
1178
  declare const quadtreeUpdateTask: _hello_terrain_work.Task<LeafSet, string, unknown>;
737
1179
  /**
@@ -763,14 +1205,13 @@ declare const createUniformsTask: _hello_terrain_work.Task<TerrainUniformsContex
763
1205
  */
764
1206
  declare const updateUniformsTask: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
765
1207
 
766
- declare function terrainGraph(): TerrainGraph;
767
1208
  /** All terrain task refs for direct access. */
768
1209
  declare const terrainTasks: {
769
1210
  readonly instanceId: _hello_terrain_work.Task<`${string}-${string}-${string}-${string}-${string}`, string, unknown>;
770
1211
  readonly quadtreeConfig: _hello_terrain_work.Task<QuadtreeConfigState, string, unknown>;
771
1212
  readonly quadtreeUpdate: _hello_terrain_work.Task<LeafSet, string, unknown>;
772
1213
  readonly leafStorage: _hello_terrain_work.Task<LeafStorageState, string, unknown>;
773
- readonly surface: _hello_terrain_work.Task<Surface, string, unknown>;
1214
+ readonly topology: _hello_terrain_work.Task<Topology, string, unknown>;
774
1215
  readonly leafGpuBuffer: _hello_terrain_work.Task<{
775
1216
  count: number;
776
1217
  data: Int32Array<ArrayBuffer>;
@@ -781,19 +1222,13 @@ declare const terrainTasks: {
781
1222
  readonly gpuSpatialIndexUpload: _hello_terrain_work.Task<GpuSpatialIndexContext, string, unknown>;
782
1223
  readonly createUniforms: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
783
1224
  readonly updateUniforms: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
784
- readonly positionNode: _hello_terrain_work.Task<three_src_nodes_TSL_js.ShaderCallNodeInternal, string, unknown>;
1225
+ readonly positionNode: _hello_terrain_work.Task<three_webgpu.Node, string, unknown>;
785
1226
  readonly createElevationFieldContext: _hello_terrain_work.Task<{
786
1227
  data: Float32Array<ArrayBuffer>;
787
1228
  attribute: three_webgpu.StorageBufferAttribute;
788
1229
  node: three_webgpu.StorageBufferNode;
789
1230
  }, string, unknown>;
790
- readonly createTileNodes: _hello_terrain_work.Task<{
791
- tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
792
- tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
793
- tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
794
- rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
795
- tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
796
- }, string, unknown>;
1231
+ readonly createTileNodes: _hello_terrain_work.Task<TileCompute, string, unknown>;
797
1232
  readonly createTerrainFieldTexture: _hello_terrain_work.Task<any, string, {
798
1233
  renderer: WebGPURenderer;
799
1234
  }>;
@@ -818,6 +1253,7 @@ declare const terrainTasks: {
818
1253
  }>;
819
1254
  readonly terrainRaycast: _hello_terrain_work.Task<TerrainRaycast, string, unknown>;
820
1255
  };
1256
+ declare function terrainGraph(): TerrainGraph;
821
1257
 
822
1258
  type ComputeDeviceLimits = {
823
1259
  maxWorkgroupSizeX: number;
@@ -826,24 +1262,75 @@ type ComputeDeviceLimits = {
826
1262
  };
827
1263
  declare function getDeviceComputeLimits(renderer: WebGPURenderer): ComputeDeviceLimits;
828
1264
 
1265
+ /**
1266
+ * Add the cube-sphere direction samplers to a base sampler. Called from the
1267
+ * cube-sphere projection's `gpu.augmentSampler` hook.
1268
+ */
1269
+ declare function augmentCubeSphereSampler(sampler: TerrainSampler, params: CreateTerrainSamplerParams): void;
829
1270
  declare function createTerrainSampler(params: CreateTerrainSamplerParams): TerrainSampler;
830
1271
 
1272
+ /** Flat (heightfield) query, keyed on world XZ. */
831
1273
  declare function createTerrainQuery(cache: CpuTerrainCache): TerrainQuery;
1274
+ /**
1275
+ * Generic closed-surface query, keyed on a world position projected onto the
1276
+ * surface. Cube-sphere extends this with direction/lat-long keys.
1277
+ */
1278
+ declare function createTerrainSurfaceQuery(cache: CpuTerrainCache): TerrainSurfaceQuery;
832
1279
 
833
- type CpuRaycastConfig = {
834
- rootSize: number;
835
- originX: number;
836
- originZ: number;
837
- minY: number;
838
- maxY: number;
839
- };
840
-
841
- type TerrainRaycastConfig = CpuRaycastConfig;
1280
+ /**
1281
+ * Build a terrain raycaster that delegates the projection-specific marching to
1282
+ * the active surface projection — no branching on a projection kind here.
1283
+ */
842
1284
  declare function createTerrainRaycast(params: {
1285
+ getProjection: () => SurfaceProjection;
843
1286
  getTerrainQuery: () => TerrainQuery | null;
1287
+ getSurfaceQuery: () => TerrainSurfaceQuery | null;
1288
+ getSphereQuery: () => TerrainSphereQuery | null;
844
1289
  getConfig: () => TerrainRaycastConfig;
845
1290
  }): TerrainRaycast;
846
1291
 
1292
+ type CubeFaceBasis = {
1293
+ forward: Node;
1294
+ right: Node;
1295
+ up: Node;
1296
+ };
1297
+ /** Per-face basis vectors selected by the dynamic face index. */
1298
+ declare function cubeFaceBasis(face: Node): CubeFaceBasis;
1299
+ /**
1300
+ * Cube-space point for face-local (u, v) in [0, 1]:
1301
+ * cube = forward + (2u-1) * right + (2v-1) * up
1302
+ */
1303
+ declare function cubeFacePoint(basis: CubeFaceBasis, u: Node, v: Node): Node;
1304
+ /** Unit-sphere direction for face-local (u, v). */
1305
+ declare function cubeFaceDirection(basis: CubeFaceBasis, u: Node, v: Node): Node;
1306
+ /**
1307
+ * Project a basis axis onto the tangent plane at `dir` and normalize.
1308
+ * Used to build the sphere tangent frame for normal reconstruction.
1309
+ */
1310
+ declare function tangentFromAxis(dir: Node, axis: Node): Node;
1311
+ /**
1312
+ * Reconstruct a unit tangent-space normal from its packed horizontal
1313
+ * components: `ny = sqrt(max(0, 1 - nx² - nz²))`.
1314
+ */
1315
+ declare function unpackTangentNormal(nx: Node, nz: Node): Node;
1316
+ /**
1317
+ * Rotate a tangent-space normal into the sphere tangent frame
1318
+ * `(tu, dir, tv)` at `dir` and normalize.
1319
+ *
1320
+ * Mirrors: the CPU `computeSphereNormal` in `query/cpu-terrain-cache.ts`.
1321
+ */
1322
+ declare function sphereTangentFrameNormal(dir: Node, basis: CubeFaceBasis, tangentNormal: Node): Node;
1323
+ /**
1324
+ * Pick the cube face index (0..5) whose normal axis dominates `dir`.
1325
+ * GPU mirror of `directionToFace` in `quadtree/topology/cubeSphereInverse.ts`.
1326
+ */
1327
+ declare function cubeFaceFromDirection(dir: Node): Node;
1328
+ /**
1329
+ * Face-local (u, v) in [0, 1] for a direction known to fall on `face`.
1330
+ * GPU mirror of `directionToFaceUV` in `quadtree/topology/cubeSphereInverse.ts`.
1331
+ */
1332
+ declare function cubeFaceUVFromDirection(basis: CubeFaceBasis, dir: Node): Node;
1333
+
847
1334
  /**
848
1335
  * Maps a value or node from texture space [0, 1] to vector space [-1, 1].
849
1336
  *
@@ -916,5 +1403,31 @@ declare const voronoiCells: three_src_nodes_TSL_js.ShaderNodeFn<[three_tsl.Proxi
916
1403
  uv: Node;
917
1404
  }>]>;
918
1405
 
919
- export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainQuery, createTerrainRaycast, createTerrainSampler, createTerrainSamplerTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, gpuSpatialIndexStorageTask, gpuSpatialIndexUploadTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, sampleTerrainFieldNormal, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
920
- export type { ComputePipeline, ComputeStageCallback, CreateTerrainSamplerParams, CubeSphereSurfaceConfig, ElevationCallback, ElevationFieldContext, ElevationParams, ElevationRange, FlatSurfaceConfig, GpuSpatialIndexContext, InfiniteFlatSurfaceConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodMode, QuadtreeConfig, QuadtreeConfigState, QuadtreeState, RaycastOptions, SeamTable, SpatialIndex, Surface, TerrainElevationSample, TerrainFieldStorage, TerrainFieldStorageBackendType, TerrainFieldStorageFormat, TerrainFieldStorageOptions, TerrainGraph, TerrainQuery, TerrainQueryContext, TerrainRaycast, TerrainRaycastConfig, TerrainRaycastResult, TerrainSample, TerrainSampleBatch, TerrainSampler, TerrainTasks, TerrainTile, TerrainTileBounds, TerrainUniformsContext, TerrainUniformsParams, TileBounds, TileId, UpdateParams };
1406
+ /** Flat heightfield projection: tiles in the XZ plane, elevation along +Y. */
1407
+ declare function createFlatProjection(): SurfaceProjection;
1408
+
1409
+ interface CubeSphereProjectionConfig {
1410
+ radius: number;
1411
+ center?: Vec3Like;
1412
+ /** When true, elevation displaces inward and skirts point outward. */
1413
+ invert?: boolean;
1414
+ }
1415
+ /** Cube-sphere projection: six faces wrapped radially onto a sphere. */
1416
+ declare function createCubeSphereProjection(config: CubeSphereProjectionConfig): SurfaceProjection;
1417
+
1418
+ interface TorusProjectionConfig {
1419
+ majorRadius: number;
1420
+ minorRadius: number;
1421
+ center?: Vec3Like;
1422
+ /** When true, elevation displaces inward and skirts point outward. */
1423
+ invert?: boolean;
1424
+ /** Level-0 tile count along u before LOD subdivision (defaults to 1). */
1425
+ baseU?: number;
1426
+ /** Level-0 tile count along v before LOD subdivision (defaults to 1). */
1427
+ baseV?: number;
1428
+ }
1429
+ /** Torus (donut) projection: a single closed surface periodic in both axes. */
1430
+ declare function createTorusProjection(config: TorusProjectionConfig): SurfaceProjection;
1431
+
1432
+ export { ArrayTextureBackend, AtlasBackend, CUBE_FACES, CUBE_FACE_COUNT, Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, augmentCubeSphereSampler, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereProjection, createCubeSphereTopology, createElevationFieldContextTask, createFlatProjection, createFlatTopology, createInfiniteFlatTopology, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainQuery, createTerrainRaycast, createTerrainSampler, createTerrainSamplerTask, createTerrainSurfaceQuery, createTerrainUniforms, createTorusProjection, createTorusTopology, createUniformsTask, cubeFaceBasis, cubeFaceDirection, cubeFaceFromDirection, cubeFacePoint, cubeFaceUVFromDirection, deriveNormalZ, directionToFace, directionToFaceUV, directionToLatLong, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, faceUVToCube, getDeviceComputeLimits, gpuSpatialIndexStorageTask, gpuSpatialIndexUploadTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, latLongToDirection, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, positionToTorusParams, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, radius, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, skirtScale, sphereTangentFrameNormal, storeTerrainField, tangentFromAxis, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, topology, topologyTask, torusOutwardNormal, torusUVToPoint, unpackTangentNormal, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells, wrap01 };
1433
+ export type { ComputePipeline, ComputeStageCallback, CpuSurfaceOps, CreateTerrainSamplerParams, CubeFace, CubeFaceBasis, CubeSphereProjectionConfig, CubeSphereTopologyConfig, ElevationCallback, ElevationFieldContext, ElevationParams, ElevationRange, ElevationRangeOut, FieldNormalContext, FieldNormalFn, FlatTopologyConfig, GpuSpatialIndexContext, InfiniteFlatTopologyConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodMode, ProjectionKind, ProjectionRaycastContext, QuadtreeConfig, QuadtreeConfigState, QuadtreeState, RaycastOptions, RenderVertexPositionContext, RuntimeQueries, SeamTable, SpatialIndex, SurfaceKey, SurfaceNormalContext, SurfaceProjection, SurfaceProjectionCpu, SurfaceProjectionGpu, TerrainFieldStorage, TerrainFieldStorageBackendType, TerrainFieldStorageFormat, TerrainFieldStorageOptions, TerrainGraph, TerrainQuery, TerrainQueryContext, TerrainRaycast, TerrainRaycastConfig, TerrainRaycastResult, TerrainSample, TerrainSampleBatch, TerrainSampler, TerrainSphereQuery, TerrainSurfaceQuery, TerrainSurfaceSample, TerrainSurfaceSampleBatch, TerrainTasks, TerrainTile, TerrainTileBounds, TerrainUniformsContext, TerrainUniformsParams, TileBounds, TileId, Topology, TorusProjectionConfig, TorusSurfaceParams, TorusTopologyConfig, UpdateParams, Vec3, Vec3Like, Vec3Mutable$1 as Vec3Mutable };