@hello-terrain/three 0.0.0-alpha.11 → 0.0.0-alpha.13

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
  /**
@@ -103,6 +103,100 @@ declare class TerrainGeometry extends BufferGeometry {
103
103
  private generateNormals;
104
104
  }
105
105
 
106
+ type TerrainFieldStorageBackendType = "array-texture" | "atlas" | "texture-3d";
107
+ type TerrainFieldStorageFormat = "rgba16float" | "rgba32float";
108
+ type TerrainFieldStorageOptions = {
109
+ backend?: TerrainFieldStorageBackendType;
110
+ filter?: "nearest" | "linear";
111
+ format?: TerrainFieldStorageFormat;
112
+ };
113
+ interface TerrainFieldStorage {
114
+ readonly backendType: TerrainFieldStorageBackendType;
115
+ readonly edgeVertexCount: number;
116
+ readonly tileCount: number;
117
+ readonly texture: StorageArrayTexture | StorageTexture;
118
+ uv(ix: Node, iy: Node, tileIndex: Node): Node;
119
+ texel(ix: Node, iy: Node, tileIndex: Node): Node;
120
+ /** UV-based filtered sample. `u, v` in [0, 1] tile-local space. */
121
+ sample(u: Node, v: Node, tileIndex: Node): Node;
122
+ resize(width: number, height: number, tileCount: number): void;
123
+ }
124
+ declare function ArrayTextureBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
125
+ declare function AtlasBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
126
+ declare function createTerrainFieldStorage(edgeVertexCount: number, tileCount: number, renderer?: WebGPURenderer, options?: TerrainFieldStorageOptions): TerrainFieldStorage;
127
+ declare function storeTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node, value: Node): Node;
128
+ declare function loadTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
129
+ declare function loadTerrainFieldElevation(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
130
+ declare function loadTerrainFieldNormal(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
131
+ /**
132
+ * UV-based filtered sample. `u, v` are in [0, 1] tile-local space.
133
+ * Respects the filter mode (nearest / linear) set on the storage.
134
+ */
135
+ declare function sampleTerrainField(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
136
+ declare function sampleTerrainFieldElevation(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
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;
145
+
146
+ interface TerrainUniformsParams {
147
+ rootSize: number;
148
+ rootOrigin: Vector3Like;
149
+ innerTileSegments: number;
150
+ skirtScale: number;
151
+ elevationScale: number;
152
+ radius: number;
153
+ instanceId: string;
154
+ }
155
+ interface TerrainUniformsContext {
156
+ uRootOrigin: UniformNode<Vector3>;
157
+ uRootSize: UniformNode<number>;
158
+ uInnerTileSegments: UniformNode<number>;
159
+ uSkirtScale: UniformNode<number>;
160
+ uElevationScale: UniformNode<number>;
161
+ uRadius: UniformNode<number>;
162
+ }
163
+ interface LeafStorageState {
164
+ data: Int32Array<ArrayBuffer>;
165
+ attribute: StorageBufferAttribute;
166
+ node: StorageBufferNode;
167
+ }
168
+
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;
185
+ uniforms: TerrainUniformsContext;
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;
192
+ };
193
+ /**
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`.
197
+ */
198
+ declare function createTileCompute(leafStorage: LeafStorageState, uniforms: TerrainUniformsContext, projection: SurfaceProjection): TileCompute;
199
+
106
200
  declare const Dir: {
107
201
  readonly LEFT: 0;
108
202
  readonly RIGHT: 1;
@@ -128,27 +222,21 @@ type TileBounds = {
128
222
  /** conservative radius */
129
223
  r: number;
130
224
  };
131
- type TopologyProjection = "flat" | "cubeSphere";
225
+ /** Scaled world-space elevation displacement range for a tile. */
226
+ type ElevationRangeOut = {
227
+ min: number;
228
+ max: number;
229
+ };
132
230
  type Topology = {
133
231
  spaceCount: number;
134
232
  /** maximum number of roots returned by `rootTiles` */
135
233
  maxRootCount: number;
136
234
  /**
137
- * GPU position/normal assembly projection. Defaults to `flat` when absent.
138
- * `cubeSphere` selects radial sphere mapping from cube faces.
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.
139
238
  */
140
- projection?: TopologyProjection;
141
- /** Sphere radius in world units (cube-sphere projection only). */
142
- radius?: number;
143
- /**
144
- * Planet center in world space (cube-sphere projection only). Used to apply
145
- * the camera elevation offset along the radial up-direction during LOD.
146
- */
147
- center?: {
148
- x: number;
149
- y: number;
150
- z: number;
151
- };
239
+ projection: SurfaceProjection;
152
240
  /**
153
241
  * Compute the same-level neighbor TileId in the requested direction.
154
242
  * Returns false if the neighbor is outside the valid topology.
@@ -159,12 +247,13 @@ type Topology = {
159
247
  /**
160
248
  * Conservative camera-relative bounds for LOD decisions.
161
249
  * Avoids absolute world coordinates so Earth-scale worlds remain stable.
250
+ * When `elevationRange` is provided, bounds should account for displaced geometry.
162
251
  */
163
252
  tileBounds(tile: TileId, cameraOrigin: {
164
253
  x: number;
165
254
  y: number;
166
255
  z: number;
167
- }, out: TileBounds): void;
256
+ }, out: TileBounds, elevationRange?: ElevationRangeOut): void;
168
257
  /**
169
258
  * Fill root tiles for the current frame and return the count.
170
259
  * Implementations should write level-0 tiles into `out[0..count)`.
@@ -205,37 +294,36 @@ type SeamTable = {
205
294
  declare function allocSeamTable(capacity: number): SeamTable;
206
295
  declare function resetSeamTable(seams: SeamTable): void;
207
296
  type LodMode = "distance" | "screen";
297
+ /**
298
+ * How subdivision decisions are made. A discriminated union so each mode only
299
+ * carries (and requires) its own thresholds.
300
+ * - `distance` (default): split when the camera is within `distanceFactor × r`.
301
+ * - `screen`: split when the projected pixel radius exceeds `targetPixels`.
302
+ */
303
+ type LodCriteria = {
304
+ mode?: "distance";
305
+ distanceFactor?: number;
306
+ } | {
307
+ mode: "screen";
308
+ /** Screen-space projection factor = screenHeight / (2*tan(fovY/2)). */
309
+ projectionFactor: number;
310
+ /** Target pixel radius/size threshold for screen-space refinement. */
311
+ targetPixels: number;
312
+ };
313
+ /**
314
+ * Per-tile elevation range provider: previous-frame world-space displacement
315
+ * min/max (already scaled by `elevationScale`). Returns false when no data is
316
+ * available yet. Allocation-free via the `out` scratch.
317
+ */
318
+ type TileElevationRangeFn = (tile: TileId, out: ElevationRangeOut) => boolean;
208
319
  type UpdateParams = {
209
320
  cameraOrigin: {
210
321
  x: number;
211
322
  y: number;
212
323
  z: number;
213
324
  };
214
- /**
215
- * Terrain elevation beneath the camera (from the previous frame). During
216
- * refinement it offsets the camera toward the terrain surface so LOD distance
217
- * is measured relative to the surface rather than the datum:
218
- * - flat: subtracted from `cameraOrigin.y`.
219
- * - cube-sphere: subtracted along the radial up-direction from the planet center.
220
- */
221
- elevationAtCameraXZ?: number;
222
- /**
223
- * Controls how subdivision decisions are made.
224
- * `distance` is the initial focus; `screen` is supported for future parity.
225
- */
226
- mode?: LodMode;
227
- /**
228
- * Distance-based refinement threshold.
229
- * Interpretation is criteria-dependent; keep it stable across surfaces by using bounds.
230
- */
231
- distanceFactor?: number;
232
- /** Screen-space projection factor = screenHeight / (2*tan(fovY/2)) */
233
- projectionFactor?: number;
234
- /** Target pixel radius/size threshold for screen-space refinement */
235
- targetPixels?: number;
236
- /** Prevent flicker by separating split/merge thresholds (0..1 typical) */
237
- hysteresis?: number;
238
- };
325
+ tileElevationRange?: TileElevationRangeFn;
326
+ } & LodCriteria;
239
327
  type QuadtreeConfig = {
240
328
  maxNodes: number;
241
329
  maxLevel: number;
@@ -301,6 +389,7 @@ type QuadtreeState = {
301
389
  scratchTile: TileId;
302
390
  scratchNeighbor: TileId;
303
391
  scratchBounds: TileBounds;
392
+ scratchElevationRange: ElevationRangeOut;
304
393
  scratchRootTiles: TileId[];
305
394
  /** topology space count is fixed for a given state */
306
395
  spaceCount: number;
@@ -334,8 +423,6 @@ type FlatTopologyConfig = {
334
423
  y: number;
335
424
  z: number;
336
425
  };
337
- /** optional conservative vertical extent, included in bounds radius */
338
- maxHeight?: number;
339
426
  };
340
427
  declare function createFlatTopology(cfg: FlatTopologyConfig): Topology;
341
428
 
@@ -346,8 +433,6 @@ type InfiniteFlatTopologyConfig = {
346
433
  y: number;
347
434
  z: number;
348
435
  };
349
- /** optional conservative vertical extent, included in bounds radius */
350
- maxHeight?: number;
351
436
  /** half-width of root grid in root tiles (1 => 3x3 roots) */
352
437
  rootGridRadius?: number;
353
438
  };
@@ -362,8 +447,8 @@ type CubeSphereTopologyConfig = {
362
447
  y: number;
363
448
  z: number;
364
449
  };
365
- /** Optional conservative vertical extent, included in bounds radius. */
366
- maxHeight?: number;
450
+ /** When true, elevation displaces inward and skirts point outward. */
451
+ invert?: boolean;
367
452
  };
368
453
  /**
369
454
  * Cube-sphere topology: six quadtree faces wrapped onto a sphere.
@@ -401,13 +486,13 @@ type CubeFace = {
401
486
  declare const CUBE_FACE_COUNT = 6;
402
487
  declare const CUBE_FACES: readonly CubeFace[];
403
488
 
404
- type Vec3Mutable = [number, number, number];
489
+ type Vec3Mutable$1 = [number, number, number];
405
490
  /**
406
491
  * Cube-space point for a face-local coordinate (u, v) in [0, 1]:
407
492
  * cube = forward + (2u-1) * right + (2v-1) * up
408
493
  * The result is unnormalized; normalize it to obtain the sphere direction.
409
494
  */
410
- declare function faceUVToCube(face: number, u: number, v: number, out: Vec3Mutable): void;
495
+ declare function faceUVToCube(face: number, u: number, v: number, out: Vec3Mutable$1): void;
411
496
  /** Pick the cube face whose normal axis dominates the direction. */
412
497
  declare function directionToFace(d: Vec3): number;
413
498
  /** Face-local (u, v) in [0, 1] for a direction known to fall on `face`. */
@@ -420,45 +505,258 @@ declare function directionToFaceUV(face: number, d: Vec3, out: [number, number])
420
505
  * - longitude is the angle around the +Y axis, in `[-180, 180]`,
421
506
  * measured from +Z toward +X (lon = 0 points along +Z).
422
507
  */
423
- declare function latLongToDirection(latDeg: number, lonDeg: number, out: Vec3Mutable): void;
508
+ declare function latLongToDirection(latDeg: number, lonDeg: number, out: Vec3Mutable$1): void;
424
509
  /** Inverse of {@link latLongToDirection}; returns degrees. */
425
510
  declare function directionToLatLong(d: Vec3): {
426
511
  latitude: number;
427
512
  longitude: number;
428
513
  };
429
514
 
430
- type TerrainFieldStorageBackendType = "array-texture" | "atlas" | "texture-3d";
431
- type TerrainFieldStorageFormat = "rgba16float" | "rgba32float";
432
- type TerrainFieldStorageOptions = {
433
- backend?: TerrainFieldStorageBackendType;
434
- filter?: "nearest" | "linear";
435
- format?: TerrainFieldStorageFormat;
515
+ type TorusTopologyConfig = {
516
+ /** Distance from the torus center to the tube center (the donut radius). */
517
+ majorRadius: number;
518
+ /** Radius of the tube cross-section. */
519
+ minorRadius: number;
520
+ /** Torus center in world space (defaults to origin). */
521
+ center?: {
522
+ x: number;
523
+ y: number;
524
+ z: number;
525
+ };
526
+ /** When true, elevation displaces inward and skirts point outward. */
527
+ invert?: boolean;
436
528
  };
437
- interface TerrainFieldStorage {
438
- readonly backendType: TerrainFieldStorageBackendType;
439
- readonly edgeVertexCount: number;
440
- readonly tileCount: number;
441
- readonly texture: StorageArrayTexture | StorageTexture;
442
- uv(ix: Node, iy: Node, tileIndex: Node): Node;
443
- texel(ix: Node, iy: Node, tileIndex: Node): Node;
444
- /** UV-based filtered sample. `u, v` in [0, 1] tile-local space. */
445
- sample(u: Node, v: Node, tileIndex: Node): Node;
446
- resize(width: number, height: number, tileCount: number): void;
529
+ /**
530
+ * Torus (donut) topology: a single quadtree space whose `(u, v)` axes wrap
531
+ * around the major circle and the tube cross-section. Both axes are periodic,
532
+ * so every same-level neighbor exists (wrapping modulo the level resolution).
533
+ *
534
+ * Level-0 resolution is anisotropic: `baseU = round(major/minor)` tiles along
535
+ * `u` and `baseV = 1` along `v`, so root tiles are approximately square in
536
+ * world space before isotropic LOD subdivision.
537
+ */
538
+ declare function createTorusTopology(cfg: TorusTopologyConfig): Topology;
539
+
540
+ type Vec3Mutable = [number, number, number];
541
+ /** Wrap a value into [0, 1). */
542
+ declare function wrap01(t: number): number;
543
+ /**
544
+ * Torus surface point for parameters (u, v) in [0, 1].
545
+ *
546
+ * Convention (matches `latLongToDirection`'s longitude axis):
547
+ * - `theta = 2*pi*u` sweeps around the +Y axis, measured from +Z toward +X.
548
+ * - `phi = 2*pi*v` sweeps around the tube cross-section, `phi = 0` pointing
549
+ * radially outward in the XZ plane and `phi = pi/2` pointing toward +Y.
550
+ *
551
+ * `displacement` is the elevation added to the tube (minor) radius.
552
+ */
553
+ declare function torusUVToPoint(u: number, v: number, majorRadius: number, minorRadius: number, displacement: number, center: {
554
+ x: number;
555
+ y: number;
556
+ z: number;
557
+ }, out: Vec3Mutable, invert?: boolean): void;
558
+ /** Outward unit surface normal of the base (undisplaced) torus at (u, v). */
559
+ declare function torusOutwardNormal(u: number, v: number, out: Vec3Mutable, invert?: boolean): void;
560
+ type TorusSurfaceParams = {
561
+ /** Wrapped major-circle parameter in [0, 1). */
562
+ u: number;
563
+ /** Wrapped tube parameter in [0, 1). */
564
+ v: number;
565
+ /** Distance from the point to the tube center circle. */
566
+ tubeDistance: number;
567
+ };
568
+ /**
569
+ * Map a world point to torus surface parameters. `tubeDistance - minorRadius`
570
+ * is the signed radial displacement of the point relative to the base torus.
571
+ */
572
+ declare function positionToTorusParams(px: number, py: number, pz: number, majorRadius: number, center: {
573
+ x: number;
574
+ y: number;
575
+ z: number;
576
+ }, out: TorusSurfaceParams): void;
577
+
578
+ type TerrainQueryConfig = {
579
+ rootSize: number;
580
+ originX: number;
581
+ originY: number;
582
+ originZ: number;
583
+ innerTileSegments: number;
584
+ elevationScale: number;
585
+ maxLevel: number;
586
+ /** Representative surface radius (curved projections only). */
587
+ radius: number;
588
+ /** Level-0 tile count along u before LOD subdivision (defaults to 1). */
589
+ baseU?: number;
590
+ /** Level-0 tile count along v before LOD subdivision (defaults to 1). */
591
+ baseV?: number;
592
+ };
593
+ interface CpuTerrainCache {
594
+ readonly generation: number;
595
+ readonly ready: boolean;
596
+ updateConfig(config: TerrainQueryConfig): void;
597
+ triggerReadback(renderer: WebGPURenderer, attribute: StorageBufferAttribute, spatialIndex: SpatialIndex, boundsAttribute?: StorageBufferAttribute, activeLeafCount?: number): void;
598
+ /** Release GPU readback staging buffers owned by this cache. */
599
+ dispose(): void;
600
+ getElevation(worldX: number, worldZ: number): number | null;
601
+ getNormal(worldX: number, worldZ: number): Vector3$1 | null;
602
+ getTile(worldX: number, worldZ: number): TerrainTile | null;
603
+ getTileBounds(worldX: number, worldZ: number): TerrainTileBounds | null;
604
+ getGlobalElevationRange(): ElevationRange | null;
605
+ sampleTerrainBatch(positions: Float32Array): TerrainSampleBatch;
606
+ sampleTerrain(worldX: number, worldZ: number): TerrainSample;
607
+ /** True when the active projection supplies surface ops. */
608
+ readonly hasSurface: boolean;
609
+ /**
610
+ * Swap the projection surface ops (CPU position/normal math). Used when the
611
+ * surface geometry changes (e.g. cube-sphere radius/center) without changing
612
+ * the buffer shape, so picks/queries stay in sync without reallocating.
613
+ */
614
+ setSurfaceOps(surfaceOps: CpuSurfaceOps | null): void;
615
+ sampleSurfaceByPosition(px: number, py: number, pz: number): TerrainSurfaceSample;
616
+ getElevationBySurfacePosition(px: number, py: number, pz: number): number | null;
617
+ getNormalBySurfacePosition(px: number, py: number, pz: number): Vector3$1 | null;
618
+ getTileBySurfacePosition(px: number, py: number, pz: number): TerrainTile | null;
619
+ getTileBoundsBySurfacePosition(px: number, py: number, pz: number): TerrainTileBounds | null;
620
+ sampleSurfaceBatchByPosition(positions: Float32Array): TerrainSurfaceSampleBatch;
621
+ /**
622
+ * Previous-frame raw elevation min/max for a tile (unscaled field values).
623
+ * Returns false when no snapshot data is available for the tile.
624
+ */
625
+ getTileElevationRange(space: number, level: number, x: number, y: number, out: {
626
+ min: number;
627
+ max: number;
628
+ }): boolean;
447
629
  }
448
- declare function ArrayTextureBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
449
- declare function AtlasBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
450
- declare function createTerrainFieldStorage(edgeVertexCount: number, tileCount: number, renderer?: WebGPURenderer, options?: TerrainFieldStorageOptions): TerrainFieldStorage;
451
- declare function storeTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node, value: Node): Node;
452
- declare function loadTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
453
- declare function loadTerrainFieldElevation(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
454
- declare function loadTerrainFieldNormal(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
630
+
455
631
  /**
456
- * UV-based filtered sample. `u, v` are in [0, 1] tile-local space.
457
- * Respects the filter mode (nearest / linear) set on the storage.
632
+ * CPU sampling over a snapshot elevation-field buffer laid out as
633
+ * `maxNodes × (edgeVertexCount × edgeVertexCount)` raw heights.
634
+ *
635
+ * Plain-number math only (no three.js, no TSL); callers build vectors at the
636
+ * consumer-facing boundary.
458
637
  */
459
- declare function sampleTerrainField(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
460
- declare function sampleTerrainFieldElevation(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
461
- declare function packTerrainFieldSample(height: Node, normalXZ: Node, extra?: Node): Node;
638
+ interface ElevationGridShape {
639
+ edgeVertexCount: number;
640
+ verticesPerNode: number;
641
+ }
642
+
643
+ /** Stable projection identifier — for debugging/telemetry only, never switched on. */
644
+ type ProjectionKind = "flat" | "cubeSphere" | "torus" | (string & {});
645
+ interface Vec3Like {
646
+ x: number;
647
+ y: number;
648
+ z: number;
649
+ }
650
+ interface RenderVertexPositionContext {
651
+ leafStorage: LeafStorageState;
652
+ uniforms: TerrainUniformsContext;
653
+ terrainFieldStorage?: TerrainFieldStorage;
654
+ }
655
+ interface FieldNormalContext {
656
+ elevationFieldNode: Node;
657
+ edgeVertexCount: number;
658
+ tile: TileCompute;
659
+ uniforms: TerrainUniformsContext;
660
+ }
661
+ /** Per-vertex field-stage normal: `(nodeIndex, ix, iy) => unit world normal`. */
662
+ type FieldNormalFn = (nodeIndex: Node, ix: Node, iy: Node) => Node;
663
+ interface SurfaceProjectionGpu {
664
+ /** Render-path world position; also assigns the vertex normal varying. */
665
+ renderVertexPosition(ctx: RenderVertexPositionContext): Node;
666
+ /** Projection-specific compute-stage tile builders (size / uv / position). */
667
+ createTileComputeParts(ctx: TileComputePartsContext): TileComputeParts;
668
+ /** Field-stage surface normal builder. */
669
+ createFieldNormal(ctx: FieldNormalContext): FieldNormalFn;
670
+ /** Optional extra GPU samplers (e.g. sphere ByDirection); no-op for flat. */
671
+ augmentSampler?(sampler: TerrainSampler, params: CreateTerrainSamplerParams): void;
672
+ }
673
+ /** A world point projected onto a closed surface: tile space + face-local uv. */
674
+ interface SurfaceKey {
675
+ /** Tile space/face index (0 for torus, 0..5 for cube-sphere faces). */
676
+ space: number;
677
+ /** Face-local u in [0, 1). */
678
+ u: number;
679
+ /** Face-local v in [0, 1). */
680
+ v: number;
681
+ /** Outward unit direction at the key (for the surface sample `direction`). */
682
+ dirX: number;
683
+ dirY: number;
684
+ dirZ: number;
685
+ }
686
+ /** Grid neighborhood passed to {@link CpuSurfaceOps.surfaceNormal}. */
687
+ interface SurfaceNormalContext {
688
+ elevation: Float32Array;
689
+ shape: ElevationGridShape;
690
+ leafIndex: number;
691
+ /** Fractional grid coords of the sample within the leaf. */
692
+ gx: number;
693
+ gy: number;
694
+ innerTileSegments: number;
695
+ elevationScale: number;
696
+ level: number;
697
+ }
698
+ /**
699
+ * Projection-specific CPU surface math, injected into the terrain cache so the
700
+ * cache stays projection-agnostic. Implementations own their scratch (no
701
+ * module-scope state).
702
+ */
703
+ interface CpuSurfaceOps {
704
+ /** Map a world position to a surface key; `false` if it has no projection. */
705
+ positionToKey(px: number, py: number, pz: number, out: SurfaceKey): boolean;
706
+ /** Displaced world position from a key + scaled elevation (writes `out`). */
707
+ surfacePosition(key: SurfaceKey, elevation: number, out: Vector3$1): void;
708
+ /** Unit world-space surface normal from a key + grid neighborhood. */
709
+ surfaceNormal(key: SurfaceKey, ctx: SurfaceNormalContext): Vector3$1;
710
+ }
711
+ interface ProjectionRaycastContext {
712
+ ray: Ray;
713
+ options?: RaycastOptions;
714
+ terrainQuery: TerrainQuery | null;
715
+ surfaceQuery: TerrainSurfaceQuery | null;
716
+ sphereQuery: TerrainSphereQuery | null;
717
+ config: TerrainRaycastConfig;
718
+ }
719
+ interface RuntimeQueries {
720
+ query: TerrainQuery;
721
+ /** Generic surface query (position/uv keyed); `null` on flat surfaces. */
722
+ surfaceQuery: TerrainSurfaceQuery | null;
723
+ /** Cube-sphere query (adds direction/lat-long); `null` unless cube-sphere. */
724
+ sphereQuery: TerrainSphereQuery | null;
725
+ }
726
+ interface SurfaceProjectionCpu {
727
+ /**
728
+ * Surface sampling ops injected into the terrain cache; `null` for flat
729
+ * surfaces (which have no closed-surface query).
730
+ */
731
+ createSurfaceOps(): CpuSurfaceOps | null;
732
+ /** Build the runtime query objects the projection exposes. */
733
+ createRuntimeQueries(cache: CpuTerrainCache): RuntimeQueries;
734
+ /**
735
+ * Projection-specific CPU raycast against the displaced surface. Returns
736
+ * `null` when the ray misses the surface or the query snapshot isn't ready.
737
+ */
738
+ raycast(ctx: ProjectionRaycastContext): TerrainRaycastResult | null;
739
+ }
740
+ interface SurfaceProjection {
741
+ /** Identifier; never switched on internally. */
742
+ readonly kind: ProjectionKind;
743
+ /** Representative radius (bounds/uniform helper); undefined for flat. */
744
+ readonly radius?: number;
745
+ /** Surface center in world space; undefined for flat. */
746
+ readonly center?: Vec3Like;
747
+ /** Closed surfaces face outward → flip triangle winding. */
748
+ readonly faceOutward: boolean;
749
+ /**
750
+ * Per-axis level-0 tile count before LOD subdivision. Defaults to `{ u: 1, v: 1 }`.
751
+ * At level `L`, resolution is `baseU * 2^L` by `baseV * 2^L` tiles.
752
+ */
753
+ readonly baseResolution?: {
754
+ u: number;
755
+ v: number;
756
+ };
757
+ gpu: SurfaceProjectionGpu;
758
+ cpu: SurfaceProjectionCpu;
759
+ }
462
760
 
463
761
  interface ElevationParams {
464
762
  worldPosition: Node$1;
@@ -472,29 +770,6 @@ interface ElevationParams {
472
770
  }
473
771
  type ElevationCallback = (params: ElevationParams) => Node$1;
474
772
 
475
- interface TerrainUniformsParams {
476
- rootSize: number;
477
- rootOrigin: Vector3Like;
478
- innerTileSegments: number;
479
- skirtScale: number;
480
- elevationScale: number;
481
- radius: number;
482
- instanceId: string;
483
- }
484
- interface TerrainUniformsContext {
485
- uRootOrigin: UniformNode<Vector3>;
486
- uRootSize: UniformNode<number>;
487
- uInnerTileSegments: UniformNode<number>;
488
- uSkirtScale: UniformNode<number>;
489
- uElevationScale: UniformNode<number>;
490
- uRadius: UniformNode<number>;
491
- }
492
- interface LeafStorageState {
493
- data: Int32Array<ArrayBuffer>;
494
- attribute: StorageBufferAttribute;
495
- node: StorageBufferNode;
496
- }
497
-
498
773
  interface GpuSpatialIndexContext {
499
774
  data: Uint32Array<ArrayBuffer>;
500
775
  size: number;
@@ -524,7 +799,8 @@ interface CreateTerrainSamplerParams {
524
799
  elevationCallback: ElevationCallback;
525
800
  /** Maximum quadtree level to probe during tile lookup. */
526
801
  maxLevel: number;
527
- projection?: TopologyProjection;
802
+ /** Active surface projection (drives optional GPU sampler augmentation). */
803
+ projection: SurfaceProjection;
528
804
  }
529
805
  interface TerrainSample {
530
806
  elevation: number;
@@ -589,31 +865,42 @@ interface TerrainQuery {
589
865
  readonly generation: number;
590
866
  }
591
867
  /**
592
- * Cube-sphere terrain query. A surface location is identified by a direction
593
- * from the planet center (the canonical form); `ByPosition` projects any world
594
- * point onto its direction, and `ByLatLong` takes degrees (latitude
595
- * `[-90, 90]`, longitude `[-180, 180]`). Elevation is the radial displacement
596
- * above the base radius.
868
+ * Generic closed-surface terrain query, keyed on a world position projected
869
+ * onto the surface. Exposed for every non-flat projection (cube-sphere, torus,
870
+ * ...); `null` on flat surfaces. Elevation is the displacement above the base
871
+ * surface (already scaled by `elevationScale`); `position` is the full
872
+ * world-space surface point.
873
+ */
874
+ interface TerrainSurfaceQuery {
875
+ readonly generation: number;
876
+ getElevationByPosition(position: Vector3$1): number | null;
877
+ getNormalByPosition(position: Vector3$1): Vector3$1 | null;
878
+ sampleTerrainByPosition(position: Vector3$1): TerrainSurfaceSample;
879
+ getTileByPosition(position: Vector3$1): TerrainTile | null;
880
+ getTileBoundsByPosition(position: Vector3$1): TerrainTileBounds | null;
881
+ /** Batch sample; `positions` is a Float32Array of xyz triples. */
882
+ sampleTerrainBatchByPosition(positions: Float32Array): TerrainSurfaceSampleBatch;
883
+ }
884
+ /**
885
+ * Cube-sphere terrain query. Extends the generic surface query with the
886
+ * sphere-only direction/lat-long keys. A surface location is identified by a
887
+ * direction from the planet center (the canonical form); `ByPosition` projects
888
+ * any world point onto its direction, and `ByLatLong` takes degrees (latitude
889
+ * `[-90, 90]`, longitude `[-180, 180]`).
597
890
  *
598
891
  * Exposed only when the active surface uses the `cubeSphere` projection
599
892
  * (otherwise `null` on the query context / runtime).
600
893
  */
601
- interface TerrainSphereQuery {
602
- readonly generation: number;
894
+ interface TerrainSphereQuery extends TerrainSurfaceQuery {
603
895
  getElevationByDirection(direction: Vector3$1): number | null;
604
- getElevationByPosition(position: Vector3$1): number | null;
605
896
  getElevationByLatLong(latitudeDeg: number, longitudeDeg: number): number | null;
606
897
  getNormalByDirection(direction: Vector3$1): Vector3$1 | null;
607
- getNormalByPosition(position: Vector3$1): Vector3$1 | null;
608
898
  getNormalByLatLong(latitudeDeg: number, longitudeDeg: number): Vector3$1 | null;
609
899
  sampleTerrainByDirection(direction: Vector3$1): TerrainSurfaceSample;
610
- sampleTerrainByPosition(position: Vector3$1): TerrainSurfaceSample;
611
900
  sampleTerrainByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainSurfaceSample;
612
901
  getTileByDirection(direction: Vector3$1): TerrainTile | null;
613
- getTileByPosition(position: Vector3$1): TerrainTile | null;
614
902
  getTileByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainTile | null;
615
903
  getTileBoundsByDirection(direction: Vector3$1): TerrainTileBounds | null;
616
- getTileBoundsByPosition(position: Vector3$1): TerrainTileBounds | null;
617
904
  getTileBoundsByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainTileBounds | null;
618
905
  /** Batch sample; `directions` is a Float32Array of xyz triples. */
619
906
  sampleTerrainBatchByDirection(directions: Float32Array): TerrainSurfaceSampleBatch;
@@ -623,6 +910,22 @@ interface RaycastOptions {
623
910
  refinementSteps?: number;
624
911
  maxDistance?: number;
625
912
  }
913
+ /**
914
+ * Shared raycast bounds for the CPU marcher. Flat raycasts use the XZ extent +
915
+ * `[minY, maxY]`; curved projections derive their own radial shell from their
916
+ * geometry plus the query's global elevation range, and only read `center*`.
917
+ */
918
+ interface TerrainRaycastConfig {
919
+ rootSize: number;
920
+ originX: number;
921
+ originY: number;
922
+ originZ: number;
923
+ minY: number;
924
+ maxY: number;
925
+ centerX: number;
926
+ centerY: number;
927
+ centerZ: number;
928
+ }
626
929
  interface TerrainRaycastResult {
627
930
  position: Vector3$1;
628
931
  normal: Vector3$1;
@@ -707,59 +1010,6 @@ declare function createComputePipelineTasks(leafStageTask: TaskRef<ComputePipeli
707
1010
  }>;
708
1011
  };
709
1012
 
710
- declare function createTileCompute(leafStorage: LeafStorageState, uniforms: TerrainUniformsContext, projection?: TopologyProjection): {
711
- tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
712
- tileFace: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
713
- tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
714
- tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
715
- tileFaceUV: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node, number | Node, number | Node]>;
716
- rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node, number | Node, number | Node]>;
717
- tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node, number | Node, number | Node]>;
718
- };
719
-
720
- type TerrainQueryConfig = {
721
- rootSize: number;
722
- originX: number;
723
- originY: number;
724
- originZ: number;
725
- innerTileSegments: number;
726
- elevationScale: number;
727
- maxLevel: number;
728
- /** Topology projection; `cubeSphere` enables the direction/lat-long queries. */
729
- projection: TopologyProjection;
730
- /** Sphere radius in world units (cube-sphere projection only). */
731
- radius: number;
732
- };
733
- interface CpuTerrainCache {
734
- readonly generation: number;
735
- readonly ready: boolean;
736
- updateConfig(config: TerrainQueryConfig): void;
737
- triggerReadback(renderer: WebGPURenderer, attribute: StorageBufferAttribute, spatialIndex: SpatialIndex, boundsAttribute?: StorageBufferAttribute, activeLeafCount?: number): void;
738
- getElevation(worldX: number, worldZ: number): number | null;
739
- getNormal(worldX: number, worldZ: number): Vector3$1 | null;
740
- getTile(worldX: number, worldZ: number): TerrainTile | null;
741
- getTileBounds(worldX: number, worldZ: number): TerrainTileBounds | null;
742
- getGlobalElevationRange(): ElevationRange | null;
743
- sampleTerrainBatch(positions: Float32Array): TerrainSampleBatch;
744
- sampleTerrain(worldX: number, worldZ: number): TerrainSample;
745
- getElevationByDirection(direction: Vector3$1): number | null;
746
- getElevationByPosition(position: Vector3$1): number | null;
747
- getElevationByLatLong(latitudeDeg: number, longitudeDeg: number): number | null;
748
- getNormalByDirection(direction: Vector3$1): Vector3$1 | null;
749
- getNormalByPosition(position: Vector3$1): Vector3$1 | null;
750
- getNormalByLatLong(latitudeDeg: number, longitudeDeg: number): Vector3$1 | null;
751
- sampleTerrainByDirection(direction: Vector3$1): TerrainSurfaceSample;
752
- sampleTerrainByPosition(position: Vector3$1): TerrainSurfaceSample;
753
- sampleTerrainByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainSurfaceSample;
754
- getTileByDirection(direction: Vector3$1): TerrainTile | null;
755
- getTileByPosition(position: Vector3$1): TerrainTile | null;
756
- getTileByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainTile | null;
757
- getTileBoundsByDirection(direction: Vector3$1): TerrainTileBounds | null;
758
- getTileBoundsByPosition(position: Vector3$1): TerrainTileBounds | null;
759
- getTileBoundsByLatLong(latitudeDeg: number, longitudeDeg: number): TerrainTileBounds | null;
760
- sampleTerrainBatchByDirection(directions: Float32Array): TerrainSurfaceSampleBatch;
761
- }
762
-
763
1013
  interface QuadtreeConfigState {
764
1014
  state: QuadtreeState;
765
1015
  topology: Topology;
@@ -775,9 +1025,18 @@ interface ElevationFieldContext {
775
1025
  interface TerrainQueryContext {
776
1026
  cache: CpuTerrainCache;
777
1027
  query: TerrainQuery;
1028
+ /** Generic closed-surface query; `null` on flat surfaces. */
1029
+ surfaceQuery: TerrainSurfaceQuery | null;
778
1030
  /** Cube-sphere query; `null` unless the topology uses the cubeSphere projection. */
779
1031
  sphereQuery: TerrainSphereQuery | null;
1032
+ /** Buffer-shape identity (maxNodes/segments/projection kind); change recreates the cache. */
780
1033
  shapeKey: string;
1034
+ /**
1035
+ * The projection these queries close over. Recreated on any geometry change
1036
+ * (e.g. cube-sphere radius, torus major/minor); an identity change rebuilds
1037
+ * the surface ops + queries so picks/markers stay in sync.
1038
+ */
1039
+ projection: SurfaceProjection;
781
1040
  }
782
1041
  /** Task refs for the standard terrain pipeline. */
783
1042
  interface TerrainTasks {
@@ -819,15 +1078,7 @@ declare const createElevationFieldContextTask: _hello_terrain_work.Task<{
819
1078
  attribute: StorageBufferAttribute;
820
1079
  node: three_webgpu.StorageBufferNode;
821
1080
  }, string, unknown>;
822
- declare const tileNodesTask: _hello_terrain_work.Task<{
823
- tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
824
- tileFace: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
825
- tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
826
- tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
827
- tileFaceUV: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
828
- rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
829
- tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
830
- }, string, unknown>;
1081
+ declare const tileNodesTask: _hello_terrain_work.Task<TileCompute, string, unknown>;
831
1082
  /**
832
1083
  * Root compute stage — generates elevation data and writes to the
833
1084
  * elevation field storage buffer. Returns a single-element `ComputePipeline`.
@@ -908,7 +1159,7 @@ declare const elevationFn: _hello_terrain_work.ParamRef<ElevationCallback>;
908
1159
  * (e.g. buffer resize), so this task stays cached during normal quadtree
909
1160
  * updates — no unnecessary shader rebuilds.
910
1161
  */
911
- declare const positionNodeTask: _hello_terrain_work.Task<three_src_nodes_TSL_js.ShaderCallNodeInternal, string, unknown>;
1162
+ declare const positionNodeTask: _hello_terrain_work.Task<three_webgpu.Node, string, unknown>;
912
1163
 
913
1164
  /**
914
1165
  * Derives the terrain topology from `rootSize` and `origin`.
@@ -964,21 +1215,13 @@ declare const terrainTasks: {
964
1215
  readonly gpuSpatialIndexUpload: _hello_terrain_work.Task<GpuSpatialIndexContext, string, unknown>;
965
1216
  readonly createUniforms: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
966
1217
  readonly updateUniforms: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
967
- readonly positionNode: _hello_terrain_work.Task<three_src_nodes_TSL_js.ShaderCallNodeInternal, string, unknown>;
1218
+ readonly positionNode: _hello_terrain_work.Task<three_webgpu.Node, string, unknown>;
968
1219
  readonly createElevationFieldContext: _hello_terrain_work.Task<{
969
1220
  data: Float32Array<ArrayBuffer>;
970
1221
  attribute: three_webgpu.StorageBufferAttribute;
971
1222
  node: three_webgpu.StorageBufferNode;
972
1223
  }, string, unknown>;
973
- readonly createTileNodes: _hello_terrain_work.Task<{
974
- tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
975
- tileFace: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
976
- tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
977
- tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
978
- tileFaceUV: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
979
- rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
980
- tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
981
- }, string, unknown>;
1224
+ readonly createTileNodes: _hello_terrain_work.Task<TileCompute, string, unknown>;
982
1225
  readonly createTerrainFieldTexture: _hello_terrain_work.Task<any, string, {
983
1226
  renderer: WebGPURenderer;
984
1227
  }>;
@@ -992,7 +1235,7 @@ declare const terrainTasks: {
992
1235
  renderer: WebGPURenderer;
993
1236
  }>;
994
1237
  readonly tileBoundsContext: _hello_terrain_work.Task<TileBoundsContext & {
995
- kernel: ReturnType<(elevationFieldNode: three_webgpu.StorageBufferNode, boundsNode: three_webgpu.StorageBufferNode, verticesPerNode: number) => three_webgpu.ComputeNode>;
1238
+ kernel: ReturnType<(elevationFieldNode: three_webgpu.StorageBufferNode, boundsNode: three_webgpu.StorageBufferNode, verticesPerNode: number, edgeVertexCount: number) => three_webgpu.ComputeNode>;
996
1239
  }, string, unknown>;
997
1240
  readonly tileBoundsReduction: _hello_terrain_work.Task<any, string, {
998
1241
  renderer: WebGPURenderer;
@@ -1012,35 +1255,29 @@ type ComputeDeviceLimits = {
1012
1255
  };
1013
1256
  declare function getDeviceComputeLimits(renderer: WebGPURenderer): ComputeDeviceLimits;
1014
1257
 
1258
+ /**
1259
+ * Add the cube-sphere direction samplers to a base sampler. Called from the
1260
+ * cube-sphere projection's `gpu.augmentSampler` hook.
1261
+ */
1262
+ declare function augmentCubeSphereSampler(sampler: TerrainSampler, params: CreateTerrainSamplerParams): void;
1015
1263
  declare function createTerrainSampler(params: CreateTerrainSamplerParams): TerrainSampler;
1016
1264
 
1017
1265
  /** Flat (heightfield) query, keyed on world XZ. */
1018
1266
  declare function createTerrainQuery(cache: CpuTerrainCache): TerrainQuery;
1019
- /** Cube-sphere query, keyed on a direction / position / lat-long. */
1020
- declare function createTerrainSphereQuery(cache: CpuTerrainCache): TerrainSphereQuery;
1021
-
1022
- type CpuRaycastConfig = {
1023
- rootSize: number;
1024
- originX: number;
1025
- originZ: number;
1026
- minY: number;
1027
- maxY: number;
1028
- /** Topology projection; `cubeSphere` selects the radial sphere raycast. */
1029
- projection?: TopologyProjection;
1030
- /** Planet center (cube-sphere only). */
1031
- centerX?: number;
1032
- centerY?: number;
1033
- centerZ?: number;
1034
- /** Base sphere radius (cube-sphere only). */
1035
- radius?: number;
1036
- /** Inner/outer radial bounds of the terrain shell (cube-sphere only). */
1037
- minRadius?: number;
1038
- maxRadius?: number;
1039
- };
1267
+ /**
1268
+ * Generic closed-surface query, keyed on a world position projected onto the
1269
+ * surface. Cube-sphere extends this with direction/lat-long keys.
1270
+ */
1271
+ declare function createTerrainSurfaceQuery(cache: CpuTerrainCache): TerrainSurfaceQuery;
1040
1272
 
1041
- type TerrainRaycastConfig = CpuRaycastConfig;
1273
+ /**
1274
+ * Build a terrain raycaster that delegates the projection-specific marching to
1275
+ * the active surface projection — no branching on a projection kind here.
1276
+ */
1042
1277
  declare function createTerrainRaycast(params: {
1278
+ getProjection: () => SurfaceProjection;
1043
1279
  getTerrainQuery: () => TerrainQuery | null;
1280
+ getSurfaceQuery: () => TerrainSurfaceQuery | null;
1044
1281
  getSphereQuery: () => TerrainSphereQuery | null;
1045
1282
  getConfig: () => TerrainRaycastConfig;
1046
1283
  }): TerrainRaycast;
@@ -1159,5 +1396,31 @@ declare const voronoiCells: three_src_nodes_TSL_js.ShaderNodeFn<[three_tsl.Proxi
1159
1396
  uv: Node;
1160
1397
  }>]>;
1161
1398
 
1162
- export { ArrayTextureBackend, AtlasBackend, CUBE_FACES, CUBE_FACE_COUNT, Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereTopology, createElevationFieldContextTask, createFlatTopology, createInfiniteFlatTopology, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainQuery, createTerrainRaycast, createTerrainSampler, createTerrainSamplerTask, createTerrainSphereQuery, createTerrainUniforms, 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, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, radius, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, skirtScale, sphereTangentFrameNormal, storeTerrainField, tangentFromAxis, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, topology, topologyTask, unpackTangentNormal, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
1163
- export type { ComputePipeline, ComputeStageCallback, CreateTerrainSamplerParams, CubeFace, CubeFaceBasis, CubeSphereTopologyConfig, ElevationCallback, ElevationFieldContext, ElevationParams, ElevationRange, FlatTopologyConfig, GpuSpatialIndexContext, InfiniteFlatTopologyConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodMode, QuadtreeConfig, QuadtreeConfigState, QuadtreeState, RaycastOptions, SeamTable, SpatialIndex, TerrainFieldStorage, TerrainFieldStorageBackendType, TerrainFieldStorageFormat, TerrainFieldStorageOptions, TerrainGraph, TerrainQuery, TerrainQueryContext, TerrainRaycast, TerrainRaycastConfig, TerrainRaycastResult, TerrainSample, TerrainSampleBatch, TerrainSampler, TerrainSphereQuery, TerrainSurfaceSample, TerrainSurfaceSampleBatch, TerrainTasks, TerrainTile, TerrainTileBounds, TerrainUniformsContext, TerrainUniformsParams, TileBounds, TileId, Topology, TopologyProjection, UpdateParams, Vec3, Vec3Mutable };
1399
+ /** Flat heightfield projection: tiles in the XZ plane, elevation along +Y. */
1400
+ declare function createFlatProjection(): SurfaceProjection;
1401
+
1402
+ interface CubeSphereProjectionConfig {
1403
+ radius: number;
1404
+ center?: Vec3Like;
1405
+ /** When true, elevation displaces inward and skirts point outward. */
1406
+ invert?: boolean;
1407
+ }
1408
+ /** Cube-sphere projection: six faces wrapped radially onto a sphere. */
1409
+ declare function createCubeSphereProjection(config: CubeSphereProjectionConfig): SurfaceProjection;
1410
+
1411
+ interface TorusProjectionConfig {
1412
+ majorRadius: number;
1413
+ minorRadius: number;
1414
+ center?: Vec3Like;
1415
+ /** When true, elevation displaces inward and skirts point outward. */
1416
+ invert?: boolean;
1417
+ /** Level-0 tile count along u before LOD subdivision (defaults to 1). */
1418
+ baseU?: number;
1419
+ /** Level-0 tile count along v before LOD subdivision (defaults to 1). */
1420
+ baseV?: number;
1421
+ }
1422
+ /** Torus (donut) projection: a single closed surface periodic in both axes. */
1423
+ declare function createTorusProjection(config: TorusProjectionConfig): SurfaceProjection;
1424
+
1425
+ 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 };
1426
+ export type { ComputePipeline, ComputeStageCallback, CpuSurfaceOps, CreateTerrainSamplerParams, CubeFace, CubeFaceBasis, CubeSphereProjectionConfig, CubeSphereTopologyConfig, ElevationCallback, ElevationFieldContext, ElevationParams, ElevationRange, ElevationRangeOut, FieldNormalContext, FieldNormalFn, FlatTopologyConfig, GpuSpatialIndexContext, InfiniteFlatTopologyConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodCriteria, 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, TileElevationRangeFn, TileId, Topology, TorusProjectionConfig, TorusSurfaceParams, TorusTopologyConfig, UpdateParams, Vec3, Vec3Like, Vec3Mutable$1 as Vec3Mutable };