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

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.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { BufferGeometry, Vector3 as Vector3$1, Ray, Raycaster, Intersection } from 'three';
1
+ import { BufferGeometry, Vector3 as Vector3$1, Ray, Raycaster, Intersection, Texture } from 'three';
2
2
  import * as three_webgpu from 'three/webgpu';
3
- import { StorageArrayTexture, StorageTexture, Node, WebGPURenderer, StorageBufferAttribute, StorageBufferNode, UniformNode, Vector3, Vector3Like, InstancedMesh, NodeMaterial, ConstNode } from 'three/webgpu';
3
+ import { StorageArrayTexture, StorageTexture, Node, WebGPURenderer, StorageBufferNode, StorageBufferAttribute, UniformNode, Vector3, Vector3Like, InstancedMesh, NodeMaterial, ConstNode } from 'three/webgpu';
4
4
  import Node$1 from 'three/src/nodes/core/Node.js';
5
5
  import * as _hello_terrain_work from '@hello-terrain/work';
6
6
  import { TaskRef, Graph } from '@hello-terrain/work';
@@ -105,6 +105,14 @@ declare class TerrainGeometry extends BufferGeometry {
105
105
 
106
106
  type TerrainFieldStorageBackendType = "array-texture" | "atlas" | "texture-3d";
107
107
  type TerrainFieldStorageFormat = "rgba16float" | "rgba32float";
108
+ /** Floats stored per tile in the tile bounds GPU buffer. */
109
+ declare const TILE_BOUNDS_FLOATS_PER_TILE = 4;
110
+ declare const TILE_BOUNDS_LOD_MIN_OFFSET = 0;
111
+ declare const TILE_BOUNDS_LOD_MAX_OFFSET = 1;
112
+ declare const TILE_BOUNDS_PACK_MIN_OFFSET = 2;
113
+ declare const TILE_BOUNDS_PACK_MAX_OFFSET = 3;
114
+ /** Minimum elevation span (meters) when normalizing flat tiles. */
115
+ declare const TERRAIN_FIELD_PACK_EPSILON = 0.0001;
108
116
  type TerrainFieldStorageOptions = {
109
117
  backend?: TerrainFieldStorageBackendType;
110
118
  filter?: "nearest" | "linear";
@@ -142,6 +150,14 @@ declare function sampleTerrainFieldElevation(storage: TerrainFieldStorage, u: No
142
150
  * normal through their own parametric tangent frame.
143
151
  */
144
152
  declare function packTerrainFieldSample(height: Node, normal: Node): Node;
153
+ declare function loadTilePackBounds(boundsNode: StorageBufferNode, tileIndex: Node): {
154
+ packMin: three_webgpu.StorageArrayElementNode;
155
+ packMax: three_webgpu.StorageArrayElementNode;
156
+ };
157
+ /** Normalize elevation into [0, 1] for rgba16float terrain-field storage. */
158
+ declare function packNormalizedTerrainFieldSample(height: Node, normal: Node, packMin: Node, packMax: Node): Node;
159
+ /** Restore absolute elevation (meters) from a normalized terrain-field sample. */
160
+ declare function denormalizeTerrainFieldElevation(normalized: Node, packMin: Node, packMax: Node): Node;
145
161
 
146
162
  interface TerrainUniformsParams {
147
163
  rootSize: number;
@@ -237,17 +253,6 @@ type Topology = {
237
253
  * pipeline never branches on a projection kind.
238
254
  */
239
255
  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
- };
251
256
  /**
252
257
  * Compute the same-level neighbor TileId in the requested direction.
253
258
  * Returns false if the neighbor is outside the valid topology.
@@ -305,64 +310,41 @@ type SeamTable = {
305
310
  declare function allocSeamTable(capacity: number): SeamTable;
306
311
  declare function resetSeamTable(seams: SeamTable): void;
307
312
  type LodMode = "distance" | "screen";
313
+ /**
314
+ * How subdivision decisions are made. A discriminated union so each mode only
315
+ * carries (and requires) its own thresholds.
316
+ * - `distance` (default): split when the camera is within `distanceFactor × r`.
317
+ * - `screen`: split when the projected pixel radius exceeds `targetPixels`.
318
+ */
319
+ type LodCriteria = {
320
+ mode?: "distance";
321
+ distanceFactor?: number;
322
+ } | {
323
+ mode: "screen";
324
+ /** Screen-space projection factor = screenHeight / (2*tan(fovY/2)). */
325
+ projectionFactor: number;
326
+ /** Target pixel radius/size threshold for screen-space refinement. */
327
+ targetPixels: number;
328
+ };
329
+ /**
330
+ * Per-tile elevation range provider: previous-frame world-space displacement
331
+ * min/max (already scaled by `elevationScale`). Returns false when no data is
332
+ * available yet. Allocation-free via the `out` scratch.
333
+ */
334
+ type TileElevationRangeFn = (tile: TileId, out: ElevationRangeOut) => boolean;
308
335
  type UpdateParams = {
309
336
  cameraOrigin: {
310
337
  x: number;
311
338
  y: number;
312
339
  z: number;
313
340
  };
314
- /**
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.
320
- */
321
- elevationAtCameraXZ?: number;
322
- /**
323
- * Controls how subdivision decisions are made.
324
- * `distance` is the initial focus; `screen` is supported for future parity.
325
- */
326
- mode?: LodMode;
327
- /**
328
- * Distance-based refinement threshold.
329
- * Interpretation is criteria-dependent; keep it stable across surfaces by using bounds.
330
- */
331
- distanceFactor?: number;
332
- /** Screen-space projection factor = screenHeight / (2*tan(fovY/2)) */
333
- projectionFactor?: number;
334
- /** Target pixel radius/size threshold for screen-space refinement */
335
- targetPixels?: number;
336
- /** Prevent flicker by separating split/merge thresholds (0..1 typical) */
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;
343
- };
341
+ tileElevationRange?: TileElevationRangeFn;
342
+ } & LodCriteria;
344
343
  type QuadtreeConfig = {
345
344
  maxNodes: number;
346
345
  maxLevel: number;
347
346
  };
348
347
 
349
- type NodeStore = {
350
- maxNodes: number;
351
- nodesUsed: number;
352
- /** generation stamping to avoid clearing buffers */
353
- currentGen: number;
354
- gen: Uint16Array;
355
- space: Uint8Array;
356
- level: Uint8Array;
357
- x: Int32Array;
358
- y: Int32Array;
359
- /** sentinel U32_EMPTY means no children; otherwise children are [firstChild..firstChild+3] */
360
- firstChild: Uint32Array;
361
- flags: Uint8Array;
362
- /** root node id per space */
363
- roots: Uint32Array;
364
- };
365
-
366
348
  type SpatialIndex = {
367
349
  size: number;
368
350
  mask: number;
@@ -376,222 +358,6 @@ type SpatialIndex = {
376
358
  };
377
359
  declare function createSpatialIndex(maxEntries: number): SpatialIndex;
378
360
 
379
- /**
380
- * Build a spatial index for the current LeafSet.
381
- * Maps (space, level, x, y) -> leafIndex.
382
- *
383
- * Allocation-free if `out` is provided.
384
- */
385
- declare function buildLeafIndex(leaves: LeafSet, out?: SpatialIndex): SpatialIndex;
386
-
387
- type QuadtreeState = {
388
- cfg: QuadtreeConfig;
389
- store: NodeStore;
390
- /** default reusable leaf buffers (capacity = cfg.maxNodes) */
391
- leaves: LeafSet;
392
- /** internal: node id per leaf entry (parallel to leaves.* arrays) */
393
- leafNodeIds: Uint32Array;
394
- /** reusable leaf spatial index (capacity = cfg.maxNodes) */
395
- leafIndex: SpatialIndex;
396
- /** traversal scratch */
397
- stack: Uint32Array;
398
- /** root nodes for this frame */
399
- rootNodeIds: Uint32Array;
400
- rootCount: number;
401
- /** split scheduling scratch (dedupe without allocations) */
402
- splitQueue: Uint32Array;
403
- splitStamp: Uint16Array;
404
- splitGen: number;
405
- /** scratch objects to avoid allocations */
406
- scratchTile: TileId;
407
- scratchNeighbor: TileId;
408
- scratchBounds: TileBounds;
409
- scratchElevationRange: ElevationRangeOut;
410
- scratchRootTiles: TileId[];
411
- /** topology space count is fixed for a given state */
412
- spaceCount: number;
413
- };
414
- declare function createState(cfg: QuadtreeConfig, topology: Topology): QuadtreeState;
415
- declare function beginUpdate(state: QuadtreeState, topology: Topology, params: UpdateParams): void;
416
-
417
- /**
418
- * Update the quadtree for the given topology + camera parameters.
419
- *
420
- * Produces a LeafSet of TileIds (SoA typed arrays).
421
- */
422
- declare function update(state: QuadtreeState, topology: Topology, params: UpdateParams, outLeaves?: LeafSet): LeafSet;
423
-
424
- /**
425
- * Build a fixed-width seam/neighbor table for balanced leaves (2:1).
426
- *
427
- * Output neighbors are leaf-list indices, with U32_EMPTY for missing entries.
428
- * Layout: neighbors[leafIndex * 8 + edge*2 + slot].
429
- */
430
- declare function buildSeams2to1(topology: Topology, leaves: LeafSet, outSeams: SeamTable, outIndex?: SpatialIndex): SeamTable;
431
-
432
- type FlatTopologyConfig = {
433
- /**
434
- * World-space size of the root tile edge.
435
- * The root tile covers [-rootSize/2, +rootSize/2] around origin in X/Z.
436
- */
437
- rootSize: number;
438
- origin: {
439
- x: number;
440
- y: number;
441
- z: number;
442
- };
443
- };
444
- declare function createFlatTopology(cfg: FlatTopologyConfig): Topology;
445
-
446
- type InfiniteFlatTopologyConfig = {
447
- rootSize: number;
448
- origin: {
449
- x: number;
450
- y: number;
451
- z: number;
452
- };
453
- /** half-width of root grid in root tiles (1 => 3x3 roots) */
454
- rootGridRadius?: number;
455
- };
456
- declare function createInfiniteFlatTopology(cfg: InfiniteFlatTopologyConfig): Topology;
457
-
458
- type CubeSphereTopologyConfig = {
459
- /** Sphere radius in world units. */
460
- radius: 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;
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;
517
- /**
518
- * Convert latitude/longitude (degrees) to a unit sphere direction.
519
- *
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).
524
- */
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
- };
531
-
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;
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;
594
-
595
361
  type TerrainQueryConfig = {
596
362
  rootSize: number;
597
363
  originX: number;
@@ -623,6 +389,12 @@ interface CpuTerrainCache {
623
389
  sampleTerrain(worldX: number, worldZ: number): TerrainSample;
624
390
  /** True when the active projection supplies surface ops. */
625
391
  readonly hasSurface: boolean;
392
+ /**
393
+ * Swap the projection surface ops (CPU position/normal math). Used when the
394
+ * surface geometry changes (e.g. cube-sphere radius/center) without changing
395
+ * the buffer shape, so picks/queries stay in sync without reallocating.
396
+ */
397
+ setSurfaceOps(surfaceOps: CpuSurfaceOps | null): void;
626
398
  sampleSurfaceByPosition(px: number, py: number, pz: number): TerrainSurfaceSample;
627
399
  getElevationBySurfacePosition(px: number, py: number, pz: number): number | null;
628
400
  getNormalBySurfacePosition(px: number, py: number, pz: number): Vector3$1 | null;
@@ -662,6 +434,7 @@ interface RenderVertexPositionContext {
662
434
  leafStorage: LeafStorageState;
663
435
  uniforms: TerrainUniformsContext;
664
436
  terrainFieldStorage?: TerrainFieldStorage;
437
+ tileBoundsNode?: StorageBufferNode;
665
438
  }
666
439
  interface FieldNormalContext {
667
440
  elevationFieldNode: Node;
@@ -735,12 +508,6 @@ interface RuntimeQueries {
735
508
  sphereQuery: TerrainSphereQuery | null;
736
509
  }
737
510
  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
511
  /**
745
512
  * Surface sampling ops injected into the terrain cache; `null` for flat
746
513
  * surfaces (which have no closed-surface query).
@@ -748,7 +515,10 @@ interface SurfaceProjectionCpu {
748
515
  createSurfaceOps(): CpuSurfaceOps | null;
749
516
  /** Build the runtime query objects the projection exposes. */
750
517
  createRuntimeQueries(cache: CpuTerrainCache): RuntimeQueries;
751
- /** Projection-specific CPU raycast (precise, falling back to bounds). */
518
+ /**
519
+ * Projection-specific CPU raycast against the displaced surface. Returns
520
+ * `null` when the ray misses the surface or the query snapshot isn't ready.
521
+ */
752
522
  raycast(ctx: ProjectionRaycastContext): TerrainRaycastResult | null;
753
523
  }
754
524
  interface SurfaceProjection {
@@ -808,6 +578,7 @@ interface TerrainSampler {
808
578
  }
809
579
  interface CreateTerrainSamplerParams {
810
580
  terrainFieldStorage: TerrainFieldStorage;
581
+ tileBoundsNode: StorageBufferNode;
811
582
  spatialIndex: GpuSpatialIndexContext;
812
583
  uniforms: TerrainUniformsContext;
813
584
  elevationCallback: ElevationCallback;
@@ -979,6 +750,13 @@ interface TileBoundsContext {
979
750
  attribute: StorageBufferAttribute;
980
751
  node: StorageBufferNode;
981
752
  }
753
+ declare function buildReductionKernel(elevationFieldNode: StorageBufferNode, boundsNode: StorageBufferNode, verticesPerNode: number, edgeVertexCount: number): three_webgpu.ComputeNode;
754
+ declare function runTileBoundsReduction(renderer: WebGPURenderer, boundsContext: TileBoundsContext & {
755
+ kernel: ReturnType<typeof buildReductionKernel>;
756
+ }, leafCount: number): void;
757
+ declare const tileBoundsContextTask: _hello_terrain_work.Task<TileBoundsContext & {
758
+ kernel: ReturnType<typeof buildReductionKernel>;
759
+ }, string, unknown>;
982
760
 
983
761
  type ComputeStageCallback = (nodeIndex: Node, globalVertexIndex: Node, uv: Node, localCoordinates: Node, texelSize: Node) => void;
984
762
  type ComputePipeline = ComputeStageCallback[];
@@ -1000,6 +778,10 @@ declare const executeComputeTask: _hello_terrain_work.Task<any, string, {
1000
778
  * the accumulation pattern (`get()` predecessor, spread, append), then pass
1001
779
  * their leaf stage to this helper to get compile + execute tasks.
1002
780
  *
781
+ * When the pipeline has multiple stages, elevation (and any custom upstream
782
+ * stages) run first, tile bounds are reduced, then the final stage (typically
783
+ * terrain-field pack) runs. Keep terrain-field pack as the last stage.
784
+ *
1003
785
  * @example
1004
786
  * ```ts
1005
787
  * const erosionStageTask = task((get, work) => {
@@ -1023,6 +805,243 @@ declare function createComputePipelineTasks(leafStageTask: TaskRef<ComputePipeli
1023
805
  renderer: WebGPURenderer;
1024
806
  }>;
1025
807
  };
808
+ /** Bounds are reduced mid-pipeline inside {@link executeComputeTask}. */
809
+ declare const tileBoundsReductionTask: _hello_terrain_work.Task<any, string, {
810
+ renderer: WebGPURenderer;
811
+ }>;
812
+
813
+ type NodeStore = {
814
+ maxNodes: number;
815
+ nodesUsed: number;
816
+ /** generation stamping to avoid clearing buffers */
817
+ currentGen: number;
818
+ gen: Uint16Array;
819
+ space: Uint8Array;
820
+ level: Uint8Array;
821
+ x: Int32Array;
822
+ y: Int32Array;
823
+ /** sentinel U32_EMPTY means no children; otherwise children are [firstChild..firstChild+3] */
824
+ firstChild: Uint32Array;
825
+ flags: Uint8Array;
826
+ /** root node id per space */
827
+ roots: Uint32Array;
828
+ };
829
+
830
+ /**
831
+ * Build a spatial index for the current LeafSet.
832
+ * Maps (space, level, x, y) -> leafIndex.
833
+ *
834
+ * Allocation-free if `out` is provided.
835
+ */
836
+ declare function buildLeafIndex(leaves: LeafSet, out?: SpatialIndex): SpatialIndex;
837
+
838
+ type QuadtreeState = {
839
+ cfg: QuadtreeConfig;
840
+ store: NodeStore;
841
+ /** default reusable leaf buffers (capacity = cfg.maxNodes) */
842
+ leaves: LeafSet;
843
+ /** internal: node id per leaf entry (parallel to leaves.* arrays) */
844
+ leafNodeIds: Uint32Array;
845
+ /** reusable leaf spatial index (capacity = cfg.maxNodes) */
846
+ leafIndex: SpatialIndex;
847
+ /** traversal scratch */
848
+ stack: Uint32Array;
849
+ /** root nodes for this frame */
850
+ rootNodeIds: Uint32Array;
851
+ rootCount: number;
852
+ /** split scheduling scratch (dedupe without allocations) */
853
+ splitQueue: Uint32Array;
854
+ splitStamp: Uint16Array;
855
+ splitGen: number;
856
+ /** scratch objects to avoid allocations */
857
+ scratchTile: TileId;
858
+ scratchNeighbor: TileId;
859
+ scratchBounds: TileBounds;
860
+ scratchElevationRange: ElevationRangeOut;
861
+ scratchRootTiles: TileId[];
862
+ /** topology space count is fixed for a given state */
863
+ spaceCount: number;
864
+ };
865
+ declare function createState(cfg: QuadtreeConfig, topology: Topology): QuadtreeState;
866
+ declare function beginUpdate(state: QuadtreeState, topology: Topology, params: UpdateParams): void;
867
+
868
+ /**
869
+ * Update the quadtree for the given topology + camera parameters.
870
+ *
871
+ * Produces a LeafSet of TileIds (SoA typed arrays).
872
+ */
873
+ declare function update(state: QuadtreeState, topology: Topology, params: UpdateParams, outLeaves?: LeafSet): LeafSet;
874
+
875
+ /**
876
+ * Build a fixed-width seam/neighbor table for balanced leaves (2:1).
877
+ *
878
+ * Output neighbors are leaf-list indices, with U32_EMPTY for missing entries.
879
+ * Layout: neighbors[leafIndex * 8 + edge*2 + slot].
880
+ */
881
+ declare function buildSeams2to1(topology: Topology, leaves: LeafSet, outSeams: SeamTable, outIndex?: SpatialIndex): SeamTable;
882
+
883
+ type FlatTopologyConfig = {
884
+ /**
885
+ * World-space size of the root tile edge.
886
+ * The root tile covers [-rootSize/2, +rootSize/2] around origin in X/Z.
887
+ */
888
+ rootSize: number;
889
+ origin: {
890
+ x: number;
891
+ y: number;
892
+ z: number;
893
+ };
894
+ };
895
+ declare function createFlatTopology(cfg: FlatTopologyConfig): Topology;
896
+
897
+ type InfiniteFlatTopologyConfig = {
898
+ rootSize: number;
899
+ origin: {
900
+ x: number;
901
+ y: number;
902
+ z: number;
903
+ };
904
+ /** half-width of root grid in root tiles (1 => 3x3 roots) */
905
+ rootGridRadius?: number;
906
+ };
907
+ declare function createInfiniteFlatTopology(cfg: InfiniteFlatTopologyConfig): Topology;
908
+
909
+ type CubeSphereTopologyConfig = {
910
+ /** Sphere radius in world units. */
911
+ radius: number;
912
+ /** Planet center in world space (defaults to origin). */
913
+ center?: {
914
+ x: number;
915
+ y: number;
916
+ z: number;
917
+ };
918
+ /** When true, elevation displaces inward and skirts point outward. */
919
+ invert?: boolean;
920
+ };
921
+ /**
922
+ * Cube-sphere topology: six quadtree faces wrapped onto a sphere.
923
+ *
924
+ * Topology (`neighborSameLevel`) is derived numerically from the shared
925
+ * `CUBE_FACES` basis so cross-face edges (including rotated pole edges)
926
+ * resolve to the correct neighbor tile without hand-coded transforms.
927
+ */
928
+ declare function createCubeSphereTopology(cfg: CubeSphereTopologyConfig): Topology;
929
+
930
+ /**
931
+ * Canonical cube-sphere face basis.
932
+ *
933
+ * Shared single source of truth between the CPU surface topology
934
+ * (`cubeSphere.ts`) and the GPU position/normal assembly (`tsl/cubeSphere.ts`)
935
+ * so both agree on geometry and faces seam correctly.
936
+ *
937
+ * Each face maps a face-local coordinate (u, v) in [0, 1] to a point on the
938
+ * cube `[-1, 1]^3` via:
939
+ *
940
+ * s = 2u - 1, t = 2v - 1
941
+ * cube = forward + s * right + t * up
942
+ *
943
+ * Normalizing `cube` yields the unit-sphere direction for that vertex.
944
+ *
945
+ * Bases are right-handed (`forward = right x up`) and outward-facing.
946
+ * Space indices: 0:+X 1:-X 2:+Y 3:-Y 4:+Z 5:-Z.
947
+ */
948
+ type Vec3 = readonly [number, number, number];
949
+ type CubeFace = {
950
+ forward: Vec3;
951
+ right: Vec3;
952
+ up: Vec3;
953
+ };
954
+ declare const CUBE_FACE_COUNT = 6;
955
+ declare const CUBE_FACES: readonly CubeFace[];
956
+
957
+ type Vec3Mutable$1 = [number, number, number];
958
+ /**
959
+ * Cube-space point for a face-local coordinate (u, v) in [0, 1]:
960
+ * cube = forward + (2u-1) * right + (2v-1) * up
961
+ * The result is unnormalized; normalize it to obtain the sphere direction.
962
+ */
963
+ declare function faceUVToCube(face: number, u: number, v: number, out: Vec3Mutable$1): void;
964
+ /** Pick the cube face whose normal axis dominates the direction. */
965
+ declare function directionToFace(d: Vec3): number;
966
+ /** Face-local (u, v) in [0, 1] for a direction known to fall on `face`. */
967
+ declare function directionToFaceUV(face: number, d: Vec3, out: [number, number]): void;
968
+ /**
969
+ * Convert latitude/longitude (degrees) to a unit sphere direction.
970
+ *
971
+ * Convention matches `CUBE_FACES` (+Y is the north pole):
972
+ * - latitude is the angle above the equator, in `[-90, 90]`
973
+ * - longitude is the angle around the +Y axis, in `[-180, 180]`,
974
+ * measured from +Z toward +X (lon = 0 points along +Z).
975
+ */
976
+ declare function latLongToDirection(latDeg: number, lonDeg: number, out: Vec3Mutable$1): void;
977
+ /** Inverse of {@link latLongToDirection}; returns degrees. */
978
+ declare function directionToLatLong(d: Vec3): {
979
+ latitude: number;
980
+ longitude: number;
981
+ };
982
+
983
+ type TorusTopologyConfig = {
984
+ /** Distance from the torus center to the tube center (the donut radius). */
985
+ majorRadius: number;
986
+ /** Radius of the tube cross-section. */
987
+ minorRadius: number;
988
+ /** Torus center in world space (defaults to origin). */
989
+ center?: {
990
+ x: number;
991
+ y: number;
992
+ z: number;
993
+ };
994
+ /** When true, elevation displaces inward and skirts point outward. */
995
+ invert?: boolean;
996
+ };
997
+ /**
998
+ * Torus (donut) topology: a single quadtree space whose `(u, v)` axes wrap
999
+ * around the major circle and the tube cross-section. Both axes are periodic,
1000
+ * so every same-level neighbor exists (wrapping modulo the level resolution).
1001
+ *
1002
+ * Level-0 resolution is anisotropic: `baseU = round(major/minor)` tiles along
1003
+ * `u` and `baseV = 1` along `v`, so root tiles are approximately square in
1004
+ * world space before isotropic LOD subdivision.
1005
+ */
1006
+ declare function createTorusTopology(cfg: TorusTopologyConfig): Topology;
1007
+
1008
+ type Vec3Mutable = [number, number, number];
1009
+ /** Wrap a value into [0, 1). */
1010
+ declare function wrap01(t: number): number;
1011
+ /**
1012
+ * Torus surface point for parameters (u, v) in [0, 1].
1013
+ *
1014
+ * Convention (matches `latLongToDirection`'s longitude axis):
1015
+ * - `theta = 2*pi*u` sweeps around the +Y axis, measured from +Z toward +X.
1016
+ * - `phi = 2*pi*v` sweeps around the tube cross-section, `phi = 0` pointing
1017
+ * radially outward in the XZ plane and `phi = pi/2` pointing toward +Y.
1018
+ *
1019
+ * `displacement` is the elevation added to the tube (minor) radius.
1020
+ */
1021
+ declare function torusUVToPoint(u: number, v: number, majorRadius: number, minorRadius: number, displacement: number, center: {
1022
+ x: number;
1023
+ y: number;
1024
+ z: number;
1025
+ }, out: Vec3Mutable, invert?: boolean): void;
1026
+ /** Outward unit surface normal of the base (undisplaced) torus at (u, v). */
1027
+ declare function torusOutwardNormal(u: number, v: number, out: Vec3Mutable, invert?: boolean): void;
1028
+ type TorusSurfaceParams = {
1029
+ /** Wrapped major-circle parameter in [0, 1). */
1030
+ u: number;
1031
+ /** Wrapped tube parameter in [0, 1). */
1032
+ v: number;
1033
+ /** Distance from the point to the tube center circle. */
1034
+ tubeDistance: number;
1035
+ };
1036
+ /**
1037
+ * Map a world point to torus surface parameters. `tubeDistance - minorRadius`
1038
+ * is the signed radial displacement of the point relative to the base torus.
1039
+ */
1040
+ declare function positionToTorusParams(px: number, py: number, pz: number, majorRadius: number, center: {
1041
+ x: number;
1042
+ y: number;
1043
+ z: number;
1044
+ }, out: TorusSurfaceParams): void;
1026
1045
 
1027
1046
  interface QuadtreeConfigState {
1028
1047
  state: QuadtreeState;
@@ -1043,7 +1062,14 @@ interface TerrainQueryContext {
1043
1062
  surfaceQuery: TerrainSurfaceQuery | null;
1044
1063
  /** Cube-sphere query; `null` unless the topology uses the cubeSphere projection. */
1045
1064
  sphereQuery: TerrainSphereQuery | null;
1065
+ /** Buffer-shape identity (maxNodes/segments/projection kind); change recreates the cache. */
1046
1066
  shapeKey: string;
1067
+ /**
1068
+ * The projection these queries close over. Recreated on any geometry change
1069
+ * (e.g. cube-sphere radius, torus major/minor); an identity change rebuilds
1070
+ * the surface ops + queries so picks/markers stay in sync.
1071
+ */
1072
+ projection: SurfaceProjection;
1047
1073
  }
1048
1074
  /** Task refs for the standard terrain pipeline. */
1049
1075
  interface TerrainTasks {
@@ -1156,9 +1182,10 @@ declare const elevationFn: _hello_terrain_work.ParamRef<ElevationCallback>;
1156
1182
  * Builds the TSL position node for the terrain shader.
1157
1183
  *
1158
1184
  * Depends on leafStorageTask (buffer objects), updateUniformsTask
1159
- * (uniform nodes), and createTerrainFieldTextureTask (combined terrain field storage).
1185
+ * (uniform nodes), createTerrainFieldTextureTask (combined terrain field storage),
1186
+ * and tileBoundsContextTask (per-tile pack bounds for denormalization).
1160
1187
  *
1161
- * The position node reads packed terrain samples (height + normal.xz)
1188
+ * The position node reads packed terrain samples (normalized height + normal)
1162
1189
  * per-vertex and assigns them to the vNormal
1163
1190
  * varying for use in the fragment shader.
1164
1191
  *
@@ -1242,7 +1269,7 @@ declare const terrainTasks: {
1242
1269
  renderer: WebGPURenderer;
1243
1270
  }>;
1244
1271
  readonly tileBoundsContext: _hello_terrain_work.Task<TileBoundsContext & {
1245
- kernel: ReturnType<(elevationFieldNode: three_webgpu.StorageBufferNode, boundsNode: three_webgpu.StorageBufferNode, verticesPerNode: number) => three_webgpu.ComputeNode>;
1272
+ kernel: ReturnType<(elevationFieldNode: three_webgpu.StorageBufferNode, boundsNode: three_webgpu.StorageBufferNode, verticesPerNode: number, edgeVertexCount: number) => three_webgpu.ComputeNode>;
1246
1273
  }, string, unknown>;
1247
1274
  readonly tileBoundsReduction: _hello_terrain_work.Task<any, string, {
1248
1275
  renderer: WebGPURenderer;
@@ -1331,6 +1358,22 @@ declare function cubeFaceFromDirection(dir: Node): Node;
1331
1358
  */
1332
1359
  declare function cubeFaceUVFromDirection(basis: CubeFaceBasis, dir: Node): Node;
1333
1360
 
1361
+ /**
1362
+ * Decode an RG-packed 16-bit heightmap sample (R/G in [0, 1]) to a normalized
1363
+ * value in [0, 1].
1364
+ */
1365
+ declare const decodeUint16RG: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
1366
+ /**
1367
+ * Bilinearly sample an RG-packed 16-bit heightmap and return elevation in meters.
1368
+ *
1369
+ * @param heightmapTexture - Texture or texture node (RG encoding).
1370
+ * @param uv - Sample coordinates in [0, 1].
1371
+ * @param minM - Minimum elevation in meters.
1372
+ * @param maxM - Maximum elevation in meters (unused; kept for API symmetry with range-based callers).
1373
+ * @param rangeM - Elevation span in meters (`maxM - minM`).
1374
+ */
1375
+ declare const sampleHeightmapMeters: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node | Texture<unknown>, number | Node, number | Node, number | Node, number | Node]>;
1376
+
1334
1377
  /**
1335
1378
  * Maps a value or node from texture space [0, 1] to vector space [-1, 1].
1336
1379
  *
@@ -1429,5 +1472,5 @@ interface TorusProjectionConfig {
1429
1472
  /** Torus (donut) projection: a single closed surface periodic in both axes. */
1430
1473
  declare function createTorusProjection(config: TorusProjectionConfig): SurfaceProjection;
1431
1474
 
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 };
1475
+ export { ArrayTextureBackend, AtlasBackend, CUBE_FACES, CUBE_FACE_COUNT, Dir, TERRAIN_FIELD_PACK_EPSILON, TILE_BOUNDS_FLOATS_PER_TILE, TILE_BOUNDS_LOD_MAX_OFFSET, TILE_BOUNDS_LOD_MIN_OFFSET, TILE_BOUNDS_PACK_MAX_OFFSET, TILE_BOUNDS_PACK_MIN_OFFSET, 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, decodeUint16RG, denormalizeTerrainFieldElevation, deriveNormalZ, directionToFace, directionToFaceUV, directionToLatLong, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, faceUVToCube, getDeviceComputeLimits, gpuSpatialIndexStorageTask, gpuSpatialIndexUploadTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, latLongToDirection, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, loadTilePackBounds, maxLevel, maxNodes, origin, packNormalizedTerrainFieldSample, packTerrainFieldSample, positionNodeTask, positionToTorusParams, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, radius, resetLeafSet, resetSeamTable, rootSize, runTileBoundsReduction, sampleHeightmapMeters, sampleTerrainField, sampleTerrainFieldElevation, skirtScale, sphereTangentFrameNormal, storeTerrainField, tangentFromAxis, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileBoundsContextTask, tileBoundsReductionTask, tileNodesTask, topology, topologyTask, torusOutwardNormal, torusUVToPoint, unpackTangentNormal, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells, wrap01 };
1476
+ 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, TileBoundsContext, TileElevationRangeFn, TileId, Topology, TorusProjectionConfig, TorusSurfaceParams, TorusTopologyConfig, UpdateParams, Vec3, Vec3Like, Vec3Mutable$1 as Vec3Mutable };