@hello-terrain/three 0.0.0-alpha.3 → 0.0.0-alpha.5

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,7 +1,12 @@
1
- import * as THREE from 'three';
2
1
  import { BufferGeometry } from 'three';
2
+ import * as three_webgpu from 'three/webgpu';
3
+ import { InstancedMesh, NodeMaterial, Node, WebGPURenderer, StorageBufferAttribute, StorageBufferNode, UniformNode, Vector3, Vector3Like, ConstNode } from 'three/webgpu';
3
4
  import * as three_src_nodes_TSL_js from 'three/src/nodes/TSL.js';
4
- import { Node } from 'three/webgpu';
5
+ import { ShaderCallNodeInternal } from 'three/src/nodes/TSL.js';
6
+ import * as _hello_terrain_work from '@hello-terrain/work';
7
+ import { TaskRef } from '@hello-terrain/work';
8
+ import Node$1 from 'three/src/nodes/core/Node.js';
9
+ import * as three_tsl from 'three/tsl';
5
10
 
6
11
  /**
7
12
  * Custom geometry for terrain tiles with properly handled skirts.
@@ -89,286 +94,621 @@ declare class TerrainGeometry extends BufferGeometry {
89
94
  private generateNormals;
90
95
  }
91
96
 
97
+ type TerrainMeshParams = {
98
+ innerTileSegments: number;
99
+ maxNodes: number;
100
+ material: NodeMaterial;
101
+ };
102
+ declare class TerrainMesh extends InstancedMesh {
103
+ private _innerTileSegments;
104
+ private _maxNodes;
105
+ constructor(params?: Partial<TerrainMeshParams>);
106
+ get innerTileSegments(): number;
107
+ set innerTileSegments(tileSegments: number);
108
+ get maxNodes(): number;
109
+ set maxNodes(maxNodes: number);
110
+ }
111
+
112
+ type ComputeStageCallback = (nodeIndex: Node, globalVertexIndex: Node, uv: Node, localCoordinates: Node, texelSize: Node) => void;
113
+ type ComputePipeline = ComputeStageCallback[];
114
+
115
+ /** Default compile task — uses normalFieldStageTask as the leaf. */
116
+ declare const compileComputeTask: _hello_terrain_work.Task<{
117
+ execute: (renderer: WebGPURenderer, instanceCount: number) => void;
118
+ }, string, unknown>;
119
+ /** Default execute task — dispatches the compiled kernel. */
120
+ declare const executeComputeTask: _hello_terrain_work.Task<any, string, {
121
+ renderer: WebGPURenderer;
122
+ }>;
92
123
  /**
93
- * Returns a node that is true for skirt vertices in the vertex stage.
124
+ * Factory for user-extensible pipelines.
94
125
  *
95
- * @remarks
96
- * Only valid in the vertex shader. A vertex belongs to the skirt if it is on
97
- * the outermost ring of the tile grid (first/last column or row). The grid
98
- * resolution is derived from `segments`.
99
- *
100
- * @param segments - The number of inner segments in the terrain grid.
101
- * @returns A node resolving to a boolean indicating a skirt vertex.
102
- */
103
- declare const isSkirtVertex: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
104
- /**
105
- * Returns a node that is true for skirt UVs.
126
+ * Users who add custom compute stages create their own stage tasks using
127
+ * the accumulation pattern (`get()` predecessor, spread, append), then pass
128
+ * their leaf stage to this helper to get compile + execute tasks.
106
129
  *
107
- * @remarks
108
- * Uses interpolated UVs and the grid size
109
- * from `segments` to mark fragments outside the inner range
110
- * `(step, 1 - step)` on either axis as skirt, where `step = 1 / (segments + 2)`.
130
+ * @example
131
+ * ```ts
132
+ * const erosionStageTask = task((get, work) => {
133
+ * const upstream = get(elevationFieldStageTask);
134
+ * return work((): ComputePipeline => [
135
+ * ...upstream,
136
+ * (nodeIndex, globalVertexIndex, uv) => {
137
+ * // custom erosion logic
138
+ * },
139
+ * ]);
140
+ * });
111
141
  *
112
- * @param segments - The number of inner segments in the terrain grid.
113
- * @returns A node resolving to a boolean indicating a skirt fragment.
142
+ * const { compile, execute } = createComputePipelineTasks(erosionStageTask);
143
+ * ```
114
144
  */
115
- declare const isSkirtUV: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
145
+ declare function createComputePipelineTasks(leafStageTask: TaskRef<ComputePipeline>): {
146
+ compile: _hello_terrain_work.Task<{
147
+ execute: (renderer: WebGPURenderer, instanceCount: number) => void;
148
+ }, string, unknown>;
149
+ execute: _hello_terrain_work.Task<any, string, {
150
+ renderer: WebGPURenderer;
151
+ }>;
152
+ };
116
153
 
117
- type NeighborIndices = [number, number, number, number];
118
- type ChildIndices = [number, number, number, number];
119
- /**
120
- * Class that manages all node-related buffer arrays and provides access methods
121
- */
122
- declare class QuadtreeNodeView {
123
- private maxNodeCount;
124
- private childrenIndicesBuffer;
125
- private neighborsIndicesBuffer;
126
- private nodeBuffer;
127
- private leafNodeMask;
128
- private leafNodeCountBuffer;
129
- private activeLeafIndices;
130
- private activeLeafCount;
131
- constructor(maxNodeCount: number, childrenIndicesBuffer?: Uint16Array, neighborsIndicesBuffer?: Uint16Array, nodeBuffer?: Int32Array, leafNodeMask?: Uint8Array, leafNodeCountBuffer?: Uint16Array);
154
+ declare const Dir: {
155
+ readonly LEFT: 0;
156
+ readonly RIGHT: 1;
157
+ readonly TOP: 2;
158
+ readonly BOTTOM: 3;
159
+ };
160
+ type Dir = (typeof Dir)[keyof typeof Dir];
161
+ declare const U32_EMPTY = 4294967295;
162
+ type TileId = {
163
+ /** 0 for flat terrain; 0..5 for cube-sphere faces */
164
+ space: number;
165
+ level: number;
166
+ /** tile coordinate at this level (signed to support infinite surfaces) */
167
+ x: number;
168
+ /** tile coordinate at this level (signed to support infinite surfaces) */
169
+ y: number;
170
+ };
171
+ type TileBounds = {
172
+ /** camera-relative center */
173
+ cx: number;
174
+ cy: number;
175
+ cz: number;
176
+ /** conservative radius */
177
+ r: number;
178
+ };
179
+ type Surface = {
180
+ spaceCount: number;
181
+ /** maximum number of roots returned by `rootTiles` */
182
+ maxRootCount: number;
183
+ /**
184
+ * Compute the same-level neighbor TileId in the requested direction.
185
+ * Returns false if the neighbor is outside the valid topology.
186
+ *
187
+ * IMPORTANT: This must handle cross-space edges in the future (cube-sphere).
188
+ */
189
+ neighborSameLevel(tile: TileId, dir: Dir, out: TileId): boolean;
132
190
  /**
133
- * Clear all buffers
191
+ * Conservative camera-relative bounds for LOD decisions.
192
+ * Avoids absolute world coordinates so Earth-scale worlds remain stable.
134
193
  */
135
- clear(): void;
194
+ tileBounds(tile: TileId, cameraOrigin: {
195
+ x: number;
196
+ y: number;
197
+ z: number;
198
+ }, out: TileBounds): void;
136
199
  /**
137
- * Get buffer references for direct access (useful for GPU operations)
200
+ * Fill root tiles for the current frame and return the count.
201
+ * Implementations should write level-0 tiles into `out[0..count)`.
138
202
  */
139
- getBuffers(): {
140
- childrenIndicesBuffer: Uint16Array<ArrayBufferLike>;
141
- neighborsIndicesBuffer: Uint16Array<ArrayBufferLike>;
142
- nodeBuffer: Int32Array<ArrayBufferLike>;
143
- leafNodeMask: Uint8Array<ArrayBufferLike>;
203
+ rootTiles(cameraOrigin: {
204
+ x: number;
205
+ y: number;
206
+ z: number;
207
+ }, out: TileId[]): number;
208
+ };
209
+ type LeafSet = {
210
+ /** maximum number of leaves that fit in the buffers */
211
+ capacity: number;
212
+ /** number of valid leaf entries in this frame */
213
+ count: number;
214
+ space: Uint8Array;
215
+ level: Uint8Array;
216
+ x: Int32Array;
217
+ y: Int32Array;
218
+ };
219
+ declare function allocLeafSet(capacity: number): LeafSet;
220
+ declare function resetLeafSet(leaves: LeafSet): void;
221
+ type SeamTable = {
222
+ /** maximum number of leaves the table can describe */
223
+ capacity: number;
224
+ /** number of leaves described (typically equals leaves.count) */
225
+ count: number;
226
+ /** fixed stride per leaf, in u32 entries */
227
+ stride: 8;
228
+ /**
229
+ * neighbors in leaf-list index space
230
+ * layout: neighbors[leafIndex * 8 + edge*2 + slot]
231
+ * edge order: LEFT, RIGHT, TOP, BOTTOM
232
+ * slot: 0..1 (at most 2 neighbors per edge under 2:1 balance)
233
+ */
234
+ neighbors: Uint32Array;
235
+ };
236
+ declare function allocSeamTable(capacity: number): SeamTable;
237
+ declare function resetSeamTable(seams: SeamTable): void;
238
+ type LodMode = "distance" | "screen";
239
+ type UpdateParams = {
240
+ cameraOrigin: {
241
+ x: number;
242
+ y: number;
243
+ z: number;
144
244
  };
145
245
  /**
146
- * Get the maximum node count
246
+ * Controls how subdivision decisions are made.
247
+ * `distance` is the initial focus; `screen` is supported for future parity.
147
248
  */
148
- getMaxNodeCount(): number;
149
- getLevel(index: number): number;
150
- getX(index: number): number;
151
- getY(index: number): number;
152
- getLeafNodeCount(): number;
153
- getLeaf(index: number): boolean;
154
- getChildren(index: number): ChildIndices;
155
- getNeighbors(index: number): NeighborIndices;
156
- setLevel(index: number, level: number): void;
157
- setX(index: number, x: number): void;
158
- setY(index: number, y: number): void;
159
- setLeaf(index: number, leaf: boolean): void;
160
- setChildren(index: number, children: ChildIndices): void;
161
- setNeighbors(index: number, neighbors: NeighborIndices): void;
249
+ mode?: LodMode;
162
250
  /**
163
- * Get array of active leaf node indices with count (zero-copy, no allocation)
251
+ * Distance-based refinement threshold.
252
+ * Interpretation is criteria-dependent; keep it stable across surfaces by using bounds.
164
253
  */
165
- getActiveLeafNodeIndices(): {
166
- indices: Uint16Array;
167
- count: number;
168
- };
254
+ distanceFactor?: number;
255
+ /** Screen-space projection factor = screenHeight / (2*tan(fovY/2)) */
256
+ projectionFactor?: number;
257
+ /** Target pixel radius/size threshold for screen-space refinement */
258
+ targetPixels?: number;
259
+ /** Prevent flicker by separating split/merge thresholds (0..1 typical) */
260
+ hysteresis?: number;
261
+ };
262
+ type QuadtreeConfig = {
263
+ maxNodes: number;
264
+ maxLevel: number;
265
+ };
266
+
267
+ type NodeStore = {
268
+ maxNodes: number;
269
+ nodesUsed: number;
270
+ /** generation stamping to avoid clearing buffers */
271
+ currentGen: number;
272
+ gen: Uint16Array;
273
+ space: Uint8Array;
274
+ level: Uint8Array;
275
+ x: Int32Array;
276
+ y: Int32Array;
277
+ /** sentinel U32_EMPTY means no children; otherwise children are [firstChild..firstChild+3] */
278
+ firstChild: Uint32Array;
279
+ flags: Uint8Array;
280
+ /** root node id per space */
281
+ roots: Uint32Array;
282
+ };
283
+
284
+ type SpatialIndex = {
285
+ size: number;
286
+ mask: number;
287
+ stampGen: number;
288
+ stamp: Uint16Array;
289
+ keysSpace: Uint8Array;
290
+ keysLevel: Uint8Array;
291
+ keysX: Uint32Array;
292
+ keysY: Uint32Array;
293
+ values: Uint32Array;
294
+ };
295
+ declare function createSpatialIndex(maxEntries: number): SpatialIndex;
296
+
297
+ /**
298
+ * Build a spatial index for the current LeafSet.
299
+ * Maps (space, level, x, y) -> leafIndex.
300
+ *
301
+ * Allocation-free if `out` is provided.
302
+ */
303
+ declare function buildLeafIndex(leaves: LeafSet, out?: SpatialIndex): SpatialIndex;
304
+
305
+ type QuadtreeState = {
306
+ cfg: QuadtreeConfig;
307
+ store: NodeStore;
308
+ /** default reusable leaf buffers (capacity = cfg.maxNodes) */
309
+ leaves: LeafSet;
310
+ /** internal: node id per leaf entry (parallel to leaves.* arrays) */
311
+ leafNodeIds: Uint32Array;
312
+ /** reusable leaf spatial index (capacity = cfg.maxNodes) */
313
+ leafIndex: SpatialIndex;
314
+ /** traversal scratch */
315
+ stack: Uint32Array;
316
+ /** root nodes for this frame */
317
+ rootNodeIds: Uint32Array;
318
+ rootCount: number;
319
+ /** split scheduling scratch (dedupe without allocations) */
320
+ splitQueue: Uint32Array;
321
+ splitStamp: Uint16Array;
322
+ splitGen: number;
323
+ /** scratch objects to avoid allocations */
324
+ scratchTile: TileId;
325
+ scratchNeighbor: TileId;
326
+ scratchBounds: TileBounds;
327
+ scratchRootTiles: TileId[];
328
+ /** surface space count is fixed for a given state */
329
+ spaceCount: number;
330
+ };
331
+ declare function createState(cfg: QuadtreeConfig, surface: Surface): QuadtreeState;
332
+ declare function beginUpdate(state: QuadtreeState, surface: Surface, params: UpdateParams): void;
333
+
334
+ /**
335
+ * Update the quadtree for the given surface + camera parameters.
336
+ *
337
+ * Produces a LeafSet of TileIds (SoA typed arrays).
338
+ */
339
+ declare function update(state: QuadtreeState, surface: Surface, params: UpdateParams, outLeaves?: LeafSet): LeafSet;
340
+
341
+ /**
342
+ * Build a fixed-width seam/neighbor table for balanced leaves (2:1).
343
+ *
344
+ * Output neighbors are leaf-list indices, with U32_EMPTY for missing entries.
345
+ * Layout: neighbors[leafIndex * 8 + edge*2 + slot].
346
+ */
347
+ declare function buildSeams2to1(surface: Surface, leaves: LeafSet, outSeams: SeamTable, outIndex?: SpatialIndex): SeamTable;
348
+
349
+ type FlatSurfaceConfig = {
169
350
  /**
170
- * Release internal buffers and mark this view as destroyed
351
+ * World-space size of the root tile edge.
352
+ * The root tile covers [-rootSize/2, +rootSize/2] around origin in X/Z.
171
353
  */
172
- destroy(): void;
173
- clone(): QuadtreeNodeView;
174
- }
354
+ rootSize: number;
355
+ origin: {
356
+ x: number;
357
+ y: number;
358
+ z: number;
359
+ };
360
+ /** optional conservative vertical extent, included in bounds radius */
361
+ maxHeight?: number;
362
+ };
363
+ declare function createFlatSurface(cfg: FlatSurfaceConfig): Surface;
175
364
 
365
+ type InfiniteFlatSurfaceConfig = {
366
+ rootSize: number;
367
+ origin: {
368
+ x: number;
369
+ y: number;
370
+ z: number;
371
+ };
372
+ /** optional conservative vertical extent, included in bounds radius */
373
+ maxHeight?: number;
374
+ /** half-width of root grid in root tiles (1 => 3x3 roots) */
375
+ rootGridRadius?: number;
376
+ };
377
+ declare function createInfiniteFlatSurface(cfg: InfiniteFlatSurfaceConfig): Surface;
378
+
379
+ type CubeSphereSurfaceConfig = {
380
+ radius: number;
381
+ maxHeight?: number;
382
+ };
176
383
  /**
177
- * Context passed to subdivision strategy functions.
178
- * Contains all information needed to make subdivision decisions.
384
+ * Placeholder cube-sphere surface.
385
+ *
386
+ * This exists to localize future planet work behind the `Surface` interface.
387
+ * Topology remapping across face edges is intentionally TODO.
179
388
  */
180
- interface SubdivisionContext {
181
- /** Current node's quadtree level (0 = root) */
182
- level: number;
183
- /** Distance from camera/position to node center in world units */
184
- distance: number;
185
- /** World-space size of the node (edge length) */
186
- nodeSize: number;
187
- /** Minimum allowed node size from config */
188
- minNodeSize: number;
189
- /** Root terrain size from config */
389
+ declare function createCubeSphereSurface(_cfg: CubeSphereSurfaceConfig): Surface;
390
+
391
+ interface TerrainUniformsParams {
190
392
  rootSize: number;
393
+ rootOrigin: Vector3Like;
394
+ innerTileSegments: number;
395
+ skirtScale: number;
396
+ elevationScale: number;
397
+ instanceId: string;
398
+ }
399
+ interface TerrainUniformsContext {
400
+ uRootOrigin: UniformNode<Vector3>;
401
+ uRootSize: UniformNode<number>;
402
+ uInnerTileSegments: UniformNode<number>;
403
+ uSkirtScale: UniformNode<number>;
404
+ uElevationScale: UniformNode<number>;
405
+ }
406
+ interface LeafStorageState {
407
+ data: Int32Array<ArrayBuffer>;
408
+ attribute: StorageBufferAttribute;
409
+ node: StorageBufferNode;
410
+ }
411
+
412
+ declare function createTileCompute(leafStorage: LeafStorageState, uniforms: TerrainUniformsContext): {
413
+ tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
414
+ tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
415
+ tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node]>;
416
+ rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node, number | Node, number | Node]>;
417
+ tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node, number | Node, number | Node]>;
418
+ };
419
+
420
+ interface QuadtreeConfigState {
421
+ state: QuadtreeState;
422
+ surface: Surface;
191
423
  }
424
+ interface LeafGpuBufferState extends LeafStorageState {
425
+ count: number;
426
+ }
427
+ interface ElevationFieldContext {
428
+ data: Float32Array<ArrayBuffer>;
429
+ attribute: StorageBufferAttribute;
430
+ node: StorageBufferNode;
431
+ }
432
+ interface NormalFieldContext {
433
+ data: Uint32Array<ArrayBuffer>;
434
+ attribute: StorageBufferAttribute;
435
+ node: StorageBufferNode;
436
+ }
437
+ /** Task refs for the standard terrain pipeline. */
438
+ interface TerrainTasks {
439
+ instanceId: TaskRef<string>;
440
+ quadtreeConfig: TaskRef<QuadtreeConfigState>;
441
+ quadtreeUpdate: TaskRef<LeafSet>;
442
+ surface: TaskRef<Surface>;
443
+ leafStorage: TaskRef<LeafStorageState>;
444
+ leafGpuBuffer: TaskRef<LeafGpuBufferState>;
445
+ createUniforms: TaskRef<TerrainUniformsContext>;
446
+ updateUniforms: TaskRef<TerrainUniformsContext>;
447
+ positionNode: TaskRef<ShaderCallNodeInternal>;
448
+ createElevationFieldContext: TaskRef<ElevationFieldContext>;
449
+ createTileNodes: TaskRef<ReturnType<typeof createTileCompute>>;
450
+ createNormalFieldContext: TaskRef<NormalFieldContext>;
451
+ elevationFieldStage: TaskRef<ComputePipeline>;
452
+ normalFieldStage: TaskRef<ComputePipeline>;
453
+ compileCompute: TaskRef<{
454
+ execute: (renderer: WebGPURenderer, instanceCount: number) => void;
455
+ }>;
456
+ executeCompute: TaskRef<void | (() => void)>;
457
+ }
458
+
459
+ declare const createElevationFieldContextTask: _hello_terrain_work.Task<{
460
+ data: Float32Array<ArrayBuffer>;
461
+ attribute: StorageBufferAttribute;
462
+ node: three_webgpu.StorageBufferNode;
463
+ }, string, unknown>;
464
+ declare const tileNodesTask: _hello_terrain_work.Task<{
465
+ tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
466
+ tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
467
+ tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
468
+ rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
469
+ tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
470
+ }, string, unknown>;
192
471
  /**
193
- * Function type for subdivision strategies.
194
- * Returns true if the node should be subdivided, false otherwise.
472
+ * Root compute stage generates elevation data and writes to the
473
+ * elevation field storage buffer. Returns a single-element `ComputePipeline`.
195
474
  */
196
- type SubdivisionStrategy = (context: SubdivisionContext) => boolean;
475
+ declare const elevationFieldStageTask: _hello_terrain_work.Task<ComputePipeline, string, unknown>;
476
+
477
+ /** Generates a unique instance ID per graph (cached once). */
478
+ declare const instanceIdTask: _hello_terrain_work.Task<`${string}-${string}-${string}-${string}-${string}`, string, unknown>;
479
+
480
+ declare const createNormalFieldContextTask: _hello_terrain_work.Task<{
481
+ data: Uint32Array<ArrayBuffer>;
482
+ attribute: StorageBufferAttribute;
483
+ node: three_webgpu.StorageBufferNode;
484
+ }, string, unknown>;
197
485
  /**
198
- * Screen-space projection info for LOD calculations.
199
- * Computed from camera properties each frame.
486
+ * Normal field compute stage reads height neighbors from the elevation field
487
+ * buffer, computes surface normals via central differences, packs XZ
488
+ * components into a u32 via `packHalf2x16`, and writes to the normal field
489
+ * storage buffer.
490
+ *
491
+ * Accumulates the upstream elevation pipeline via `get(elevationFieldStageTask)`.
200
492
  */
201
- interface ScreenSpaceInfo {
202
- /**
203
- * Projection factor: screenHeight / (2 * tan(fovY / 2))
204
- * This converts world-space size to screen-space pixels at distance 1.
205
- */
206
- projectionFactor: number;
207
- /**
208
- * Screen height in pixels (for reference/debugging)
209
- */
210
- screenHeight: number;
493
+ declare const normalFieldStageTask: _hello_terrain_work.Task<ComputePipeline, string, unknown>;
494
+
495
+ interface ElevationParams {
496
+ worldPosition: Node$1;
497
+ rootSize: Node$1;
498
+ rootUV: Node$1;
499
+ tileUV: Node$1;
500
+ tileLevel: Node$1;
501
+ tileSize: Node$1;
502
+ tileOriginVec2: Node$1;
503
+ nodeIndex: Node$1;
211
504
  }
505
+ type ElevationCallback = (params: ElevationParams) => Node$1;
506
+
507
+ /** Root tile size in world units. */
508
+ declare const rootSize: _hello_terrain_work.ParamRef<number>;
509
+ /** World-space origin of the terrain. */
510
+ declare const origin: _hello_terrain_work.ParamRef<{
511
+ x: number;
512
+ y: number;
513
+ z: number;
514
+ }>;
212
515
  /**
213
- * Distance-based subdivision strategy with hysteresis to prevent LOD flickering.
214
- * Subdivides when: distance < nodeSize * factor
215
- *
216
- * @param factor Multiplier for subdivision threshold (default: 2)
217
- * @returns SubdivisionStrategy function
516
+ * Number of segments per inner tile edge.
517
+ * 13 is the max tiles we can support for 256 workgroups (13 + 3 === 16.. 16x16)
218
518
  */
219
- declare function distanceBasedSubdivision(factor?: number): SubdivisionStrategy;
519
+ declare const innerTileSegments: _hello_terrain_work.ParamRef<number>;
520
+ /** Skirt scale factor. */
521
+ declare const skirtScale: _hello_terrain_work.ParamRef<number>;
522
+ /** Elevation vertical scale. */
523
+ declare const elevationScale: _hello_terrain_work.ParamRef<number>;
524
+ /** Maximum quadtree nodes. */
525
+ declare const maxNodes: _hello_terrain_work.ParamRef<number>;
526
+ /** Maximum quadtree subdivision level. */
527
+ declare const maxLevel: _hello_terrain_work.ParamRef<number>;
528
+ /** Quadtree update configuration (camera, mode, etc.). */
529
+ declare const quadtreeUpdate: _hello_terrain_work.ParamRef<UpdateParams>;
530
+ /** Optional custom terrain surface; defaults to bounded flat surface when null. */
531
+ declare const surface: _hello_terrain_work.ParamRef<Surface | null>;
532
+ /** Terrain elevation control function (per vertex, in gpu compute) */
533
+ declare const elevationFn: _hello_terrain_work.ParamRef<ElevationCallback>;
534
+
220
535
  /**
221
- * Screen-space subdivision strategy.
222
- * Subdivides based on projected triangle size in pixels.
223
- * Ensures triangles don't exceed a target pixel size on screen.
536
+ * Builds the TSL position node for the terrain shader.
537
+ *
538
+ * Depends on leafStorageTask (buffer objects), createUniformsTask
539
+ * (uniform nodes), createElevationFieldContextTask (elevation field storage),
540
+ * and createNormalFieldContextTask (normal field storage).
541
+ *
542
+ * The position node also reads normals from the normal field buffer
543
+ * per-vertex (using vertexIndex) and assigns them to the vNormal
544
+ * varying for use in the fragment shader.
224
545
  *
225
- * @param options Configuration for screen-space subdivision
226
- * @returns SubdivisionStrategy function
546
+ * These only change when their GPU resources are recreated
547
+ * (e.g. buffer resize), so this task stays cached during normal quadtree
548
+ * updates — no unnecessary shader rebuilds.
227
549
  */
228
- declare function screenSpaceSubdivision(options: {
229
- /**
230
- * Target triangle size in screen pixels.
231
- * Subdivide when triangles would be larger than this.
232
- * Recommended: 4-8 pixels
233
- * @default 6
234
- */
235
- targetTrianglePixels?: number;
236
- /**
237
- * Number of segments per tile edge.
238
- * Should match TerrainMesh.innerTileSegments.
239
- * @default 13
240
- */
241
- tileSegments?: number;
242
- /**
243
- * Function that returns current screen-space projection info.
244
- * Called each time subdivision is evaluated.
245
- */
246
- getScreenSpaceInfo: () => ScreenSpaceInfo | null;
247
- }): SubdivisionStrategy;
550
+ declare const positionNodeTask: _hello_terrain_work.Task<three_src_nodes_TSL_js.ShaderCallNodeInternal, string, unknown>;
551
+
248
552
  /**
249
- * Compute screen-space info from camera parameters.
250
- * Helper function to create ScreenSpaceInfo from typical Three.js camera values.
251
- *
252
- * @param fovY Vertical field of view in radians
253
- * @param screenHeight Screen height in pixels
254
- * @returns ScreenSpaceInfo for use with screenSpaceSubdivision
553
+ * Derives the terrain surface from `rootSize` and `origin`.
554
+ * Automatically recomputes when either param changes, keeping the
555
+ * quadtree refinement in sync with the GPU-side tile positioning.
556
+ */
557
+ declare const surfaceTask: _hello_terrain_work.Task<Surface, string, unknown>;
558
+ declare const quadtreeConfigTask: _hello_terrain_work.Task<{
559
+ state: QuadtreeState;
560
+ surface: Surface;
561
+ }, string, unknown>;
562
+ declare const quadtreeUpdateTask: _hello_terrain_work.Task<LeafSet, string, unknown>;
563
+ /**
564
+ * Creates the GPU storage buffer objects. Recreated when maxNodes changes.
255
565
  *
256
- * @example
257
- * ```ts
258
- * // In your render loop:
259
- * const fovRadians = camera.fov * Math.PI / 180;
260
- * const screenInfo = computeScreenSpaceInfo(fovRadians, renderer.domElement.height);
261
- * ```
566
+ * positionNodeTask depends on this (not leafGpuBufferTask) so
567
+ * the shader is only rebuilt when the buffer is resized, not on every
568
+ * quadtree update.
262
569
  */
263
- declare function computeScreenSpaceInfo(fovY: number, screenHeight: number): ScreenSpaceInfo;
570
+ declare const leafStorageTask: _hello_terrain_work.Task<LeafStorageState, string, unknown>;
571
+ declare const leafGpuBufferTask: _hello_terrain_work.Task<{
572
+ count: number;
573
+ data: Int32Array<ArrayBuffer>;
574
+ attribute: three_webgpu.StorageBufferAttribute;
575
+ node: three_webgpu.StorageBufferNode;
576
+ }, string, unknown>;
264
577
 
265
- interface QuadtreeParams {
266
- maxLevel: number;
267
- rootSize: number;
268
- minNodeSize: number;
269
- origin: THREE.Vector3;
270
- maxNodes: number;
271
- }
272
- declare class Quadtree {
273
- private nodeCount;
274
- private deepestLevel;
275
- private config;
276
- private nodeView;
277
- private subdivisionStrategy;
278
- private tempChildIndices;
279
- private tempNeighborIndices;
280
- /**
281
- * Create a new Quadtree.
282
- *
283
- * @param config Quadtree configuration parameters
284
- * @param subdivisionStrategy Strategy function for subdivision decisions.
285
- * Defaults to distanceBasedSubdivision(2).
286
- * @param nodeView Optional pre-allocated NodeView for buffer reuse
287
- */
288
- constructor(config: QuadtreeParams, subdivisionStrategy?: SubdivisionStrategy, nodeView?: QuadtreeNodeView);
289
- /**
290
- * Set the subdivision strategy.
291
- * Use this to change LOD behavior at runtime.
292
- *
293
- * @param strategy The subdivision strategy function
294
- */
295
- setSubdivisionStrategy(strategy: SubdivisionStrategy): void;
296
- /**
297
- * Get the current subdivision strategy
298
- */
299
- getSubdivisionStrategy(): SubdivisionStrategy;
300
- private initialize;
301
- /**
302
- * Update the quadtree based on the given position and return the index
303
- * of the leaf node that best corresponds to the position (closest leaf).
304
- */
305
- update(position: THREE.Vector3, frustum?: THREE.Frustum): number;
306
- /**
307
- * Recursively update a node and its children based on distance and size criteria
308
- * and return the closest leaf node index to the provided position.
309
- */
310
- private updateNode;
311
- /**
312
- * Determine if a node should be subdivided using the configured strategy
313
- */
314
- private shouldSubdivide;
315
- /**
316
- * Create a new node and return its index
317
- */
318
- private createNode;
319
- /**
320
- * Subdivide a node by creating its four children
321
- */
322
- private subdivideNode;
323
- /**
324
- * Update neighbor relationships for child nodes
325
- */
326
- private updateChildNeighbors;
327
- /**
328
- * Get the deepest subdivision level currently in the quadtree
329
- */
330
- getDeepestLevel(): number;
331
- /**
332
- * Get the total number of nodes
333
- */
334
- getNodeCount(): number;
335
- getLeafNodeCount(): number;
336
- /**
337
- * Get active leaf node indices for efficient GPU processing
338
- */
339
- getActiveLeafNodeIndices(): {
340
- indices: Uint16Array;
578
+ declare function createTerrainUniforms(params: TerrainUniformsParams): TerrainUniformsContext;
579
+
580
+ /**
581
+ * Creates the terrain uniform nodes once. Downstream tasks capture
582
+ * references to these nodes in shader graphs, so the same instances
583
+ * must persist across runs.
584
+ */
585
+ declare const createUniformsTask: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
586
+ /**
587
+ * Updates the terrain uniform values each run. Reads the persisted uniform
588
+ * nodes from createUniformsTask and writes the latest param values.
589
+ */
590
+ declare const updateUniformsTask: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
591
+
592
+ declare function terrainGraph(): _hello_terrain_work.Graph<string, {
593
+ renderer: WebGPURenderer;
594
+ }>;
595
+ /** All terrain task refs for direct access. */
596
+ declare const terrainTasks: {
597
+ readonly instanceId: _hello_terrain_work.Task<`${string}-${string}-${string}-${string}-${string}`, string, unknown>;
598
+ readonly quadtreeConfig: _hello_terrain_work.Task<{
599
+ state: QuadtreeState;
600
+ surface: Surface;
601
+ }, string, unknown>;
602
+ readonly quadtreeUpdate: _hello_terrain_work.Task<LeafSet, string, unknown>;
603
+ readonly leafStorage: _hello_terrain_work.Task<LeafStorageState, string, unknown>;
604
+ readonly surface: _hello_terrain_work.Task<Surface, string, unknown>;
605
+ readonly leafGpuBuffer: _hello_terrain_work.Task<{
341
606
  count: number;
342
- };
343
- /**
344
- * Get the configuration
345
- */
346
- getConfig(): QuadtreeParams;
347
- /**
348
- * Get all leaf nodes as an array of node objects
349
- */
350
- getLeafNodes(): Array<{
351
- level: number;
352
- x: number;
353
- y: number;
607
+ data: Int32Array<ArrayBuffer>;
608
+ attribute: three_webgpu.StorageBufferAttribute;
609
+ node: three_webgpu.StorageBufferNode;
610
+ }, string, unknown>;
611
+ readonly createUniforms: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
612
+ readonly updateUniforms: _hello_terrain_work.Task<TerrainUniformsContext, string, unknown>;
613
+ readonly positionNode: _hello_terrain_work.Task<three_src_nodes_TSL_js.ShaderCallNodeInternal, string, unknown>;
614
+ readonly createElevationFieldContext: _hello_terrain_work.Task<{
615
+ data: Float32Array<ArrayBuffer>;
616
+ attribute: three_webgpu.StorageBufferAttribute;
617
+ node: three_webgpu.StorageBufferNode;
618
+ }, string, unknown>;
619
+ readonly createTileNodes: _hello_terrain_work.Task<{
620
+ tileLevel: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
621
+ tileOriginVec2: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
622
+ tileSize: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node]>;
623
+ rootUVCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
624
+ tileVertexWorldPositionCompute: three_src_nodes_TSL_js.ShaderNodeFn<[number | three_webgpu.Node, number | three_webgpu.Node, number | three_webgpu.Node]>;
625
+ }, string, unknown>;
626
+ readonly createNormalFieldContext: _hello_terrain_work.Task<{
627
+ data: Uint32Array<ArrayBuffer>;
628
+ attribute: three_webgpu.StorageBufferAttribute;
629
+ node: three_webgpu.StorageBufferNode;
630
+ }, string, unknown>;
631
+ readonly elevationFieldStage: _hello_terrain_work.Task<ComputePipeline, string, unknown>;
632
+ readonly normalFieldStage: _hello_terrain_work.Task<ComputePipeline, string, unknown>;
633
+ readonly compileCompute: _hello_terrain_work.Task<{
634
+ execute: (renderer: WebGPURenderer, instanceCount: number) => void;
635
+ }, string, unknown>;
636
+ readonly executeCompute: _hello_terrain_work.Task<any, string, {
637
+ renderer: WebGPURenderer;
354
638
  }>;
355
- /**
356
- * Reset the quadtree
357
- */
358
- reset(): void;
359
- /**
360
- * Get the NodeView instance for direct access
361
- */
362
- getNodeView(): QuadtreeNodeView;
363
- /**
364
- * Release internal resources associated with this quadtree
365
- */
366
- destroy(): void;
367
- /**
368
- * Set the configuration
369
- */
370
- setConfig(config: QuadtreeParams, reset?: boolean): void;
371
- }
639
+ };
640
+
641
+ /**
642
+ * Maps a value or node from texture space [0, 1] to vector space [-1, 1].
643
+ *
644
+ * @param value - The node or value in the range [0, 1].
645
+ * @returns A node mapping the input value to the range [-1, 1].
646
+ */
647
+ declare const textureSpaceToVectorSpace: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node$1]>;
648
+ /**
649
+ * Maps a value or node from vector space [-1, 1] to texture space [0, 1].
650
+ *
651
+ * @param value - The node or value in the range [-1, 1].
652
+ * @returns A node mapping the input value to the range [0, 1].
653
+ */
654
+ declare const vectorSpaceToTextureSpace: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node$1]>;
655
+ /**
656
+ * Blends two normal maps using the Reoriented Normal Mapping technique.
657
+ * This is the same algorithm used by Unreal Engine's BlendAngleCorrectedNormals node.
658
+ *
659
+ * Both inputs should be in vector space [-1, 1].
660
+ *
661
+ * @see https://blog.selfshadow.com/publications/blending-in-detail/
662
+ */
663
+ declare const blendAngleCorrectedNormals: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node$1, number | Node$1]>;
664
+ /**
665
+ * Reconstructs the Z component of a normal from the X and Y components.
666
+ *
667
+ * @param normalXY - A vec2 containing the X and Y components of the normal
668
+ * @returns A vec3 with the reconstructed normal (X, Y, derived Z)
669
+ */
670
+ declare const deriveNormalZ: three_src_nodes_TSL_js.ShaderNodeFn<[number | Node$1]>;
671
+
672
+ /**
673
+ * Input type for segment count: either a JS number or a TSL integer node.
674
+ * When a number is provided, it's automatically converted to an int node.
675
+ * When a Node is provided, it should resolve to an integer value.
676
+ */
677
+ type IntNodeInput = number | ConstNode<number> | Node;
678
+ /**
679
+ * Returns a node that is true for skirt vertices in the vertex stage.
680
+ *
681
+ * @remarks
682
+ * Only valid in the vertex shader. A vertex belongs to the skirt if it is on
683
+ * the outermost ring of the tile grid (first/last column or row). The grid
684
+ * resolution is derived from `segments`.
685
+ *
686
+ * @param segments - The number of inner segments in the terrain grid.
687
+ * @returns A node resolving to a boolean indicating a skirt vertex.
688
+ */
689
+ declare const isSkirtVertex: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
690
+ /**
691
+ * Returns a node that is true for skirt UVs.
692
+ *
693
+ * @remarks
694
+ * Uses interpolated UVs and the grid size
695
+ * from `segments` to mark fragments outside the inner range
696
+ * `(step, 1 - step)` on either axis as skirt, where `step = 1 / (segments + 2)`.
697
+ *
698
+ * @param segments - The number of inner segments in the terrain grid.
699
+ * @returns A node resolving to a boolean indicating a skirt fragment.
700
+ */
701
+ declare const isSkirtUV: three_src_nodes_TSL_js.ShaderNodeFn<[segments: number | Node]>;
702
+
703
+ declare const vGlobalVertexIndex: three_webgpu.PropertyNode;
704
+ declare const vElevation: three_webgpu.PropertyNode;
705
+
706
+ declare const voronoiCells: three_src_nodes_TSL_js.ShaderNodeFn<[three_tsl.ProxiedObject<{
707
+ scale: number;
708
+ facet: number;
709
+ seed: number;
710
+ uv: Node;
711
+ }>]>;
372
712
 
373
- export { Quadtree, TerrainGeometry, computeScreenSpaceInfo, distanceBasedSubdivision, isSkirtUV, isSkirtVertex, screenSpaceSubdivision };
374
- export type { QuadtreeParams, ScreenSpaceInfo, SubdivisionContext, SubdivisionStrategy };
713
+ export { Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createNormalFieldContextTask, createSpatialIndex, createState, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, maxLevel, maxNodes, normalFieldStageTask, origin, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, surface, surfaceTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
714
+ export type { ComputePipeline, ComputeStageCallback, CubeSphereSurfaceConfig, ElevationCallback, ElevationFieldContext, ElevationParams, FlatSurfaceConfig, InfiniteFlatSurfaceConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodMode, NormalFieldContext, QuadtreeConfig, QuadtreeConfigState, QuadtreeState, SeamTable, SpatialIndex, Surface, TerrainTasks, TerrainUniformsContext, TerrainUniformsParams, TileBounds, TileId, UpdateParams };