@hello-terrain/three 0.0.0-alpha.4 → 0.0.0-alpha.6

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.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import { BufferGeometry, BufferAttribute, Vector3 as Vector3$1 } from 'three';
2
- import { MeshStandardNodeMaterial, InstancedMesh, StorageBufferAttribute, Vector3 } from 'three/webgpu';
3
- import { Fn, remap, float, vec3, dot, int, vertexIndex, uv, instanceIndex, pow, positionLocal, select, storage, uniform } from 'three/tsl';
4
- import { task, param, graph } from '@hello-terrain/work';
2
+ import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageBufferAttribute, Vector3 } from 'three/webgpu';
3
+ import { param, task, graph } from '@hello-terrain/work';
4
+ import { uniform, Fn, float, globalId, int, vec2, uint, workgroupBarrier, If, instanceIndex, min, max, pow, vec3, storage, packHalf2x16, remap, dot, vertexIndex, uv, select, positionLocal, unpackHalf2x16, normalLocal, varyingProperty, mx_noise_float, Loop, mix } from 'three/tsl';
5
+ import { Fn as Fn$1 } from 'three/src/nodes/TSL.js';
5
6
 
6
7
  class TerrainGeometry extends BufferGeometry {
7
8
  constructor(innerSegments = 14, extendUV = false) {
@@ -61,17 +62,19 @@ class TerrainGeometry extends BufferGeometry {
61
62
  * | / | \ | / | \ |
62
63
  * o---o---o---o---o
63
64
  *
64
- * INNER GRID (consistent diagonal, no rotational symmetry):
65
- * o---o---o
66
- * | \ | \ |
67
- * o---o---o
68
- * | \ | \ |
69
- * o---o---o
65
+ * INNER GRID (alternating diagonals checkerboard pattern):
66
+ * o---o---o---o---o
67
+ * | \ | / | \ | / |
68
+ * o---o---o---o---o
69
+ * | / | \ | / | \ |
70
+ * o---o---o---o---o
71
+ * | \ | / | \ | / |
72
+ * o---o---o---o---o
70
73
  *
71
74
  * Where o = vertex
72
75
  * Each square cell is split into 2 triangles.
73
76
  * - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
74
- * - Inner cells: consistent diagonal direction (all triangles "point" the same way)
77
+ * - Inner cells: alternating diagonal via (x+y)%2 to reduce interpolation artifacts
75
78
  *
76
79
  * Vertex layout (for innerSegments = 2):
77
80
  *
@@ -117,7 +120,7 @@ class TerrainGeometry extends BufferGeometry {
117
120
  const topHalf = y < mid;
118
121
  useDefaultDiagonal = leftHalf && topHalf || !leftHalf && !topHalf;
119
122
  } else {
120
- useDefaultDiagonal = true;
123
+ useDefaultDiagonal = (x + y) % 2 === 0;
121
124
  }
122
125
  if (useDefaultDiagonal) {
123
126
  indices.push(a, d, b);
@@ -217,7 +220,7 @@ class TerrainGeometry extends BufferGeometry {
217
220
 
218
221
  const defaultTerrainMeshParams = {
219
222
  innerTileSegments: 14,
220
- maxNodes: 2048,
223
+ maxNodes: 1024,
221
224
  material: new MeshStandardNodeMaterial()
222
225
  };
223
226
  class TerrainMesh extends InstancedMesh {
@@ -228,6 +231,7 @@ class TerrainMesh extends InstancedMesh {
228
231
  const { innerTileSegments, maxNodes, material } = mergedParams;
229
232
  const geometry = new TerrainGeometry(innerTileSegments, true);
230
233
  super(geometry, material, maxNodes);
234
+ this.frustumCulled = false;
231
235
  this._innerTileSegments = innerTileSegments;
232
236
  this._maxNodes = maxNodes;
233
237
  }
@@ -244,72 +248,197 @@ class TerrainMesh extends InstancedMesh {
244
248
  return this._maxNodes;
245
249
  }
246
250
  set maxNodes(maxNodes) {
251
+ if (!Number.isInteger(maxNodes) || maxNodes < 1) {
252
+ throw new Error(`Invalid maxNodes: ${maxNodes}. Must be a positive integer.`);
253
+ }
254
+ if (maxNodes === this._maxNodes) return;
255
+ const oldMax = this._maxNodes;
256
+ const nextMatrix = new Float32Array(maxNodes * 16);
257
+ const oldMatrixArray = this.instanceMatrix.array;
258
+ nextMatrix.set(oldMatrixArray.subarray(0, Math.min(oldMatrixArray.length, nextMatrix.length)));
259
+ this.instanceMatrix = new InstancedBufferAttribute(nextMatrix, 16);
260
+ if (this.instanceColor) {
261
+ const itemSize = this.instanceColor.itemSize;
262
+ const nextColor = new Float32Array(maxNodes * itemSize);
263
+ const oldColorArray = this.instanceColor.array;
264
+ nextColor.set(oldColorArray.subarray(0, Math.min(oldColorArray.length, nextColor.length)));
265
+ this.instanceColor = new InstancedBufferAttribute(nextColor, itemSize);
266
+ }
247
267
  this._maxNodes = maxNodes;
268
+ this.count = Math.min(this.count, maxNodes);
269
+ this.instanceMatrix.needsUpdate = true;
270
+ if (this.instanceColor) this.instanceColor.needsUpdate = true;
271
+ if (maxNodes < oldMax && this.count >= maxNodes) {
272
+ this.count = maxNodes;
273
+ }
248
274
  }
249
275
  }
250
276
 
251
- const textureSpaceToVectorSpace = Fn(([value]) => {
252
- return remap(value, float(0), float(1), float(-1), float(1));
253
- });
254
- const vectorSpaceToTextureSpace = Fn(([value]) => {
255
- return remap(value, float(-1), float(1), float(0), float(1));
256
- });
257
- const blendAngleCorrectedNormals = Fn(([n1, n2]) => {
258
- const t = vec3(n1.x, n1.y, n1.z.add(1));
259
- const u = vec3(n2.x.negate(), n2.y.negate(), n2.z);
260
- const r = t.mul(dot(t, u)).sub(u.mul(t.z)).normalize();
261
- return r;
262
- });
263
- const deriveNormalZ = Fn(([normalXY]) => {
264
- const xy = normalXY.toVar();
265
- const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
266
- return vec3(xy.x, xy.y, z);
267
- });
277
+ const WORKGROUP_X = 16;
278
+ const WORKGROUP_Y = 16;
279
+ function compileComputePipeline(stages, width, bindings) {
280
+ const workgroupSize = [WORKGROUP_X, WORKGROUP_Y, 1];
281
+ const dispatchX = Math.ceil(width / WORKGROUP_X);
282
+ const dispatchY = Math.ceil(width / WORKGROUP_Y);
283
+ const uInstanceCount = uniform(0, "uint");
284
+ const computeShader = Fn(() => {
285
+ const fWidth = float(width);
286
+ const activeIndex = globalId.z;
287
+ const nodeIndex = int(activeIndex).toVar();
288
+ const iWidth = int(width);
289
+ const ix = int(globalId.x);
290
+ const iy = int(globalId.y);
291
+ const texelSize = vec2(1, 1).div(fWidth);
292
+ const localCoordinates = vec2(globalId.x, globalId.y);
293
+ const localUVCoords = localCoordinates.div(fWidth);
294
+ const verticesPerNode = iWidth.mul(iWidth);
295
+ const globalIndex = int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
296
+ const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(uint(activeIndex).lessThan(uInstanceCount)).toVar();
297
+ for (let i = 0; i < stages.length; i++) {
298
+ if (i > 0) {
299
+ workgroupBarrier();
300
+ }
301
+ If(inBounds, () => {
302
+ stages[i](nodeIndex, globalIndex, localUVCoords, localCoordinates, texelSize);
303
+ });
304
+ }
305
+ })().computeKernel(workgroupSize);
306
+ function execute(renderer, instanceCount) {
307
+ uInstanceCount.value = instanceCount;
308
+ renderer.compute(computeShader, [dispatchX, dispatchY, instanceCount]);
309
+ }
310
+ return { execute };
311
+ }
268
312
 
269
- const isSkirtVertex = Fn(([segments]) => {
270
- const segmentsNode = typeof segments === "number" ? int(segments) : segments;
271
- const vIndex = int(vertexIndex);
272
- const segmentEdges = int(segmentsNode.add(3));
273
- const vx = vIndex.mod(segmentEdges);
274
- const vy = vIndex.div(segmentEdges);
275
- const last = segmentEdges.sub(int(1));
276
- return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
277
- });
278
- const isSkirtUV = Fn(([segments]) => {
279
- const segmentsNode = typeof segments === "number" ? int(segments) : segments;
280
- const ux = uv().x;
281
- const uy = uv().y;
282
- const segmentCount = segmentsNode.add(2);
283
- const segmentStep = float(1).div(segmentCount);
284
- const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
285
- const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
286
- return innerX.and(innerY).not();
313
+ const createElevation = (tile, uniforms, elevationFn) => {
314
+ return function perVertexElevation(nodeIndex, localCoordinates) {
315
+ const ix = int(localCoordinates.x);
316
+ const iy = int(localCoordinates.y);
317
+ const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(int(3));
318
+ const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
319
+ const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
320
+ const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
321
+ const rootSize = uniforms.uRootSize.toVar();
322
+ return elevationFn({
323
+ worldPosition,
324
+ rootSize,
325
+ rootUV,
326
+ tileOriginVec2: tile.tileOriginVec2(nodeIndex),
327
+ tileSize: tile.tileSize(nodeIndex),
328
+ tileLevel: tile.tileLevel(nodeIndex),
329
+ nodeIndex: int(nodeIndex),
330
+ tileUV
331
+ });
332
+ };
333
+ };
334
+ const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount, positionLocal) => Fn(() => {
335
+ const nodeIndex = int(instanceIndex);
336
+ const intEdge = int(edgeVertexCount);
337
+ const innerSegments = int(edgeVertexCount).sub(3);
338
+ const fInnerSegments = float(innerSegments);
339
+ const last = intEdge.sub(int(1));
340
+ const u = positionLocal.x.add(float(0.5));
341
+ const v = positionLocal.z.add(float(0.5));
342
+ const x = u.mul(fInnerSegments).round().toInt().add(int(1));
343
+ const y = v.mul(fInnerSegments).round().toInt().add(int(1));
344
+ const xClamped = min(max(x, int(0)), last);
345
+ const yClamped = min(max(y, int(0)), last);
346
+ const verticesPerNode = intEdge.mul(intEdge);
347
+ const perNodeVertexIndex = yClamped.mul(intEdge).add(xClamped);
348
+ const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(perNodeVertexIndex);
349
+ return elevationFieldBuffer.element(globalVertexIndex);
287
350
  });
288
351
 
289
- function createTileWorldPosition(leafStorage, terrainUniforms) {
290
- return Fn(() => {
291
- const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
292
- const nodeIndex = int(instanceIndex);
352
+ function createTileCompute(leafStorage, uniforms) {
353
+ const tileLevel = Fn(([nodeIndex]) => {
354
+ const nodeOffset = nodeIndex.mul(int(4));
355
+ return leafStorage.node.element(nodeOffset).toInt();
356
+ });
357
+ const tileOriginVec2 = Fn(([nodeIndex]) => {
293
358
  const nodeOffset = nodeIndex.mul(int(4));
294
- const nodeLevel = leafStorage.node.element(nodeOffset).toInt();
295
359
  const nodeX = leafStorage.node.element(nodeOffset.add(int(1))).toFloat();
296
360
  const nodeY = leafStorage.node.element(nodeOffset.add(int(2))).toFloat();
297
- const rootSize = terrainUniforms.uRootSize.toVar();
298
- const rootOrigin = terrainUniforms.uRootOrigin.toVar();
361
+ return vec2(nodeX, nodeY);
362
+ });
363
+ const tileSize = Fn(([nodeIndex]) => {
364
+ const rootSize = uniforms.uRootSize.toVar();
365
+ const level = tileLevel(nodeIndex);
366
+ return float(rootSize).div(pow(float(2), level.toFloat()));
367
+ });
368
+ const rootUVCompute = Fn(([nodeIndex, ix, iy]) => {
369
+ const nodeVec2 = tileOriginVec2(nodeIndex);
370
+ const nodeX = nodeVec2.x;
371
+ const nodeY = nodeVec2.y;
372
+ const rootSize = uniforms.uRootSize.toVar();
373
+ const rootOrigin = uniforms.uRootOrigin.toVar();
374
+ const size = tileSize(nodeIndex);
299
375
  const half = float(0.5);
300
- const size = rootSize.div(pow(float(2), nodeLevel.toFloat()));
301
- const halfRoot = rootSize.mul(half);
302
- const centerX = rootOrigin.x.add(nodeX.add(half).mul(size)).sub(halfRoot);
303
- const centerZ = rootOrigin.z.add(nodeY.add(half).mul(size)).sub(halfRoot);
304
- const clampedX = positionLocal.x.max(half.negate()).min(half);
305
- const clampedZ = positionLocal.z.max(half.negate()).min(half);
306
- const worldX = centerX.add(clampedX.mul(size));
307
- const worldZ = centerZ.add(clampedZ.mul(size));
308
- const baseY = rootOrigin.y;
309
- const skirtY = baseY.sub(terrainUniforms.uSkirtScale.toVar());
310
- const worldY = select(skirtVertex, skirtY, baseY);
311
- return vec3(worldX, worldY, worldZ);
312
- })();
376
+ const halfRoot = float(rootSize).mul(half);
377
+ const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
378
+ const texelSpacing = size.div(fInnerSegments);
379
+ const absX = nodeX.mul(fInnerSegments).add(int(ix).toFloat().sub(float(1)));
380
+ const absY = nodeY.mul(fInnerSegments).add(int(iy).toFloat().sub(float(1)));
381
+ const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
382
+ const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
383
+ const centeredX = worldX.sub(rootOrigin.x);
384
+ const centeredZ = worldZ.sub(rootOrigin.z);
385
+ return vec2(
386
+ centeredX.div(rootSize).add(half),
387
+ centeredZ.div(rootSize).mul(float(-1)).add(half)
388
+ );
389
+ });
390
+ const tileVertexWorldPositionCompute = Fn(
391
+ ([nodeIndex, ix, iy]) => {
392
+ const rootOrigin = uniforms.uRootOrigin.toVar();
393
+ const nodeVec2 = tileOriginVec2(nodeIndex);
394
+ const nodeX = nodeVec2.x;
395
+ const nodeY = nodeVec2.y;
396
+ const rootSize = uniforms.uRootSize.toVar();
397
+ const size = tileSize(nodeIndex);
398
+ const half = float(0.5);
399
+ const halfRoot = float(rootSize).mul(half);
400
+ const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
401
+ const texelSpacing = size.div(fInnerSegments);
402
+ const absX = nodeX.mul(fInnerSegments).add(int(ix).toFloat().sub(float(1)));
403
+ const absY = nodeY.mul(fInnerSegments).add(int(iy).toFloat().sub(float(1)));
404
+ const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
405
+ const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
406
+ return vec3(worldX, rootOrigin.y, worldZ);
407
+ }
408
+ );
409
+ return {
410
+ tileLevel,
411
+ tileOriginVec2,
412
+ tileSize,
413
+ rootUVCompute,
414
+ tileVertexWorldPositionCompute
415
+ };
416
+ }
417
+
418
+ const rootSize = param(256).displayName("rootSize");
419
+ const origin = param({
420
+ x: 0,
421
+ y: 0,
422
+ z: 0
423
+ }).displayName("origin");
424
+ const innerTileSegments = param(13).displayName("innerTileSegments");
425
+ const skirtScale = param(100).displayName("skirtScale");
426
+ const elevationScale = param(1).displayName("elevationScale");
427
+ const maxNodes = param(1024).displayName("maxNodes");
428
+ const maxLevel = param(16).displayName("maxLevel");
429
+ const quadtreeUpdate = param({
430
+ cameraOrigin: { x: 0, y: 0, z: 0 },
431
+ mode: "distance",
432
+ distanceFactor: 1.5
433
+ }).displayName("quadtreeUpdate");
434
+ const surface = param(null).displayName("surface");
435
+ const elevationFn = param(() => float(0));
436
+
437
+ function createLeafStorage(maxNodes) {
438
+ const data = new Int32Array(maxNodes * 4);
439
+ const attribute = new StorageBufferAttribute(data, 4);
440
+ const node = storage(attribute, "i32", 1).toReadOnly().setName("leafStorage");
441
+ return { data, attribute, node };
313
442
  }
314
443
 
315
444
  const Dir = {
@@ -325,8 +454,8 @@ function allocLeafSet(capacity) {
325
454
  count: 0,
326
455
  space: new Uint8Array(capacity),
327
456
  level: new Uint8Array(capacity),
328
- x: new Uint32Array(capacity),
329
- y: new Uint32Array(capacity)
457
+ x: new Int32Array(capacity),
458
+ y: new Int32Array(capacity)
330
459
  };
331
460
  }
332
461
  function resetLeafSet(leaves) {
@@ -352,8 +481,8 @@ function createNodeStore(maxNodes, spaceCount) {
352
481
  gen: new Uint16Array(maxNodes),
353
482
  space: new Uint8Array(maxNodes),
354
483
  level: new Uint8Array(maxNodes),
355
- x: new Uint32Array(maxNodes),
356
- y: new Uint32Array(maxNodes),
484
+ x: new Int32Array(maxNodes),
485
+ y: new Int32Array(maxNodes),
357
486
  firstChild: new Uint32Array(maxNodes),
358
487
  flags: new Uint8Array(maxNodes),
359
488
  roots: new Uint32Array(spaceCount)
@@ -374,8 +503,8 @@ function allocNode(store, tile) {
374
503
  store.gen[id] = store.currentGen;
375
504
  store.space[id] = tile.space;
376
505
  store.level[id] = tile.level;
377
- store.x[id] = tile.x >>> 0;
378
- store.y[id] = tile.y >>> 0;
506
+ store.x[id] = tile.x;
507
+ store.y[id] = tile.y;
379
508
  store.firstChild[id] = U32_EMPTY;
380
509
  store.flags[id] = 0;
381
510
  return id;
@@ -490,6 +619,10 @@ function buildLeafIndex(leaves, out) {
490
619
 
491
620
  function createState(cfg, surface) {
492
621
  const store = createNodeStore(cfg.maxNodes, surface.spaceCount);
622
+ const scratchRootTiles = [];
623
+ for (let i = 0; i < surface.maxRootCount; i++) {
624
+ scratchRootTiles.push({ space: 0, level: 0, x: 0, y: 0 });
625
+ }
493
626
  return {
494
627
  cfg,
495
628
  store,
@@ -497,28 +630,42 @@ function createState(cfg, surface) {
497
630
  leafNodeIds: new Uint32Array(cfg.maxNodes),
498
631
  leafIndex: createSpatialIndex(cfg.maxNodes),
499
632
  stack: new Uint32Array(cfg.maxNodes),
633
+ rootNodeIds: new Uint32Array(surface.maxRootCount),
634
+ rootCount: 0,
500
635
  splitQueue: new Uint32Array(cfg.maxNodes),
501
636
  splitStamp: new Uint16Array(cfg.maxNodes),
502
637
  splitGen: 1,
503
638
  scratchTile: { space: 0, level: 0, x: 0, y: 0 },
504
639
  scratchNeighbor: { space: 0, level: 0, x: 0, y: 0 },
505
640
  scratchBounds: { cx: 0, cy: 0, cz: 0, r: 0 },
641
+ scratchRootTiles,
506
642
  spaceCount: surface.spaceCount
507
643
  };
508
644
  }
509
- function beginUpdate(state, surface) {
645
+ function beginUpdate(state, surface, params) {
510
646
  if (surface.spaceCount !== state.spaceCount) {
511
647
  throw new Error(
512
648
  `Surface spaceCount changed (${state.spaceCount} -> ${surface.spaceCount}). Create a new quadtree state.`
513
649
  );
514
650
  }
651
+ if (surface.maxRootCount !== state.rootNodeIds.length) {
652
+ throw new Error(
653
+ `Surface maxRootCount changed (${state.rootNodeIds.length} -> ${surface.maxRootCount}). Create a new quadtree state.`
654
+ );
655
+ }
515
656
  beginFrame(state.store);
516
- for (let s = 0; s < surface.spaceCount; s++) {
517
- const rootId = allocNode(state.store, { space: s, level: 0, x: 0, y: 0 });
657
+ state.rootCount = 0;
658
+ const rootCount = surface.rootTiles(params.cameraOrigin, state.scratchRootTiles);
659
+ if (rootCount < 0 || rootCount > surface.maxRootCount) {
660
+ throw new Error(`Surface returned invalid root count (${rootCount}).`);
661
+ }
662
+ for (let i = 0; i < rootCount; i++) {
663
+ const rootId = allocNode(state.store, state.scratchRootTiles[i]);
518
664
  if (rootId === U32_EMPTY) {
519
665
  throw new Error("Failed to allocate root node (maxNodes too small).");
520
666
  }
521
- state.store.roots[s] = rootId;
667
+ state.rootNodeIds[i] = rootId;
668
+ state.rootCount = i + 1;
522
669
  }
523
670
  }
524
671
 
@@ -553,8 +700,8 @@ function refineLeaves(state, surface, params, outLeaves) {
553
700
  const store = state.store;
554
701
  const stack = state.stack;
555
702
  let sp = 0;
556
- for (let s = 0; s < surface.spaceCount; s++) {
557
- stack[sp++] = store.roots[s];
703
+ for (let i = 0; i < state.rootCount; i++) {
704
+ stack[sp++] = state.rootNodeIds[i];
558
705
  }
559
706
  while (sp > 0) {
560
707
  const nodeId = stack[--sp];
@@ -667,7 +814,7 @@ function balance2to1(state, surface, params, leaves) {
667
814
  }
668
815
 
669
816
  function update(state, surface, params, outLeaves) {
670
- beginUpdate(state, surface);
817
+ beginUpdate(state, surface, params);
671
818
  const leaves = refineLeaves(state, surface, params, outLeaves);
672
819
  return balance2to1(state, surface, params, leaves);
673
820
  }
@@ -771,6 +918,7 @@ function createFlatSurface(cfg) {
771
918
  const maxHeight = cfg.maxHeight ?? 0;
772
919
  const surface = {
773
920
  spaceCount: 1,
921
+ maxRootCount: 1,
774
922
  neighborSameLevel(tile, dir, out) {
775
923
  const level = tile.level;
776
924
  const x = tile.x;
@@ -796,8 +944,8 @@ function createFlatSurface(cfg) {
796
944
  if (nx > maxCoord || ny > maxCoord) return false;
797
945
  out.space = 0;
798
946
  out.level = level;
799
- out.x = nx >>> 0;
800
- out.y = ny >>> 0;
947
+ out.x = nx;
948
+ out.y = ny;
801
949
  return true;
802
950
  },
803
951
  tileBounds(tile, cameraOrigin, out) {
@@ -813,14 +961,87 @@ function createFlatSurface(cfg) {
813
961
  out.cy = centerY - cameraOrigin.y;
814
962
  out.cz = centerZ - cameraOrigin.z;
815
963
  out.r = 0.7071067811865476 * size + maxHeight;
964
+ },
965
+ rootTiles(_cameraOrigin, out) {
966
+ const root = out[0];
967
+ root.space = 0;
968
+ root.level = 0;
969
+ root.x = 0;
970
+ root.y = 0;
971
+ return 1;
816
972
  }
817
973
  };
818
974
  return surface;
819
975
  }
820
976
 
977
+ function createInfiniteFlatSurface(cfg) {
978
+ const halfRoot = 0.5 * cfg.rootSize;
979
+ const maxHeight = cfg.maxHeight ?? 0;
980
+ const rootGridRadius = Math.max(0, Math.floor(cfg.rootGridRadius ?? 1));
981
+ const rootWidth = rootGridRadius * 2 + 1;
982
+ return {
983
+ spaceCount: 1,
984
+ maxRootCount: rootWidth * rootWidth,
985
+ neighborSameLevel(tile, dir, out) {
986
+ let nx = tile.x;
987
+ let ny = tile.y;
988
+ switch (dir) {
989
+ case Dir.LEFT:
990
+ nx = tile.x - 1;
991
+ break;
992
+ case Dir.RIGHT:
993
+ nx = tile.x + 1;
994
+ break;
995
+ case Dir.TOP:
996
+ ny = tile.y - 1;
997
+ break;
998
+ case Dir.BOTTOM:
999
+ ny = tile.y + 1;
1000
+ break;
1001
+ }
1002
+ out.space = tile.space;
1003
+ out.level = tile.level;
1004
+ out.x = nx;
1005
+ out.y = ny;
1006
+ return true;
1007
+ },
1008
+ tileBounds(tile, cameraOrigin, out) {
1009
+ const level = tile.level;
1010
+ const scale = 1 / (1 << level);
1011
+ const size = cfg.rootSize * scale;
1012
+ const minX = cfg.origin.x + (tile.x * size - halfRoot);
1013
+ const minZ = cfg.origin.z + (tile.y * size - halfRoot);
1014
+ const centerX = minX + 0.5 * size;
1015
+ const centerY = cfg.origin.y;
1016
+ const centerZ = minZ + 0.5 * size;
1017
+ out.cx = centerX - cameraOrigin.x;
1018
+ out.cy = centerY - cameraOrigin.y;
1019
+ out.cz = centerZ - cameraOrigin.z;
1020
+ out.r = 0.7071067811865476 * size + maxHeight;
1021
+ },
1022
+ rootTiles(cameraOrigin, out) {
1023
+ const camRootX = Math.floor((cameraOrigin.x - cfg.origin.x + halfRoot) / cfg.rootSize);
1024
+ const camRootY = Math.floor((cameraOrigin.z - cfg.origin.z + halfRoot) / cfg.rootSize);
1025
+ let index = 0;
1026
+ for (let dy = -rootGridRadius; dy <= rootGridRadius; dy++) {
1027
+ for (let dx = -rootGridRadius; dx <= rootGridRadius; dx++) {
1028
+ const root = out[index];
1029
+ root.space = 0;
1030
+ root.level = 0;
1031
+ root.x = camRootX + dx;
1032
+ root.y = camRootY + dy;
1033
+ index++;
1034
+ }
1035
+ }
1036
+ return index;
1037
+ }
1038
+ };
1039
+ }
1040
+
821
1041
  function createCubeSphereSurface(_cfg) {
822
1042
  return {
823
1043
  spaceCount: 6,
1044
+ maxRootCount: 6,
824
1045
  neighborSameLevel(_tile, _dir, _out) {
825
1046
  return false;
826
1047
  },
@@ -829,41 +1050,38 @@ function createCubeSphereSurface(_cfg) {
829
1050
  out.cy = 0;
830
1051
  out.cz = 0;
831
1052
  out.r = Number.MAX_VALUE;
1053
+ },
1054
+ rootTiles(_cameraOrigin, out) {
1055
+ for (let s = 0; s < 6; s++) {
1056
+ const root = out[s];
1057
+ root.space = s;
1058
+ root.level = 0;
1059
+ root.x = 0;
1060
+ root.y = 0;
1061
+ }
1062
+ return 6;
832
1063
  }
833
1064
  };
834
1065
  }
835
1066
 
836
- const instanceIdTask = task(() => crypto.randomUUID()).displayName("terrainInstanceIdTask").cache("once");
837
-
838
- const rootSize = param(256).displayName("rootSize");
839
- const origin = param({ x: 0, y: 0, z: 0 }).displayName(
840
- "origin"
841
- );
842
- const innerTileSegments = param(14).displayName("innerTileSegments");
843
- const skirtScale = param(100).displayName("skirtScale");
844
- const heightmapScale = param(1).displayName("heightmapScale");
845
- const maxNodes = param(1028).displayName("maxNodes");
846
- const maxLevel = param(16).displayName("maxLevel");
847
- const quadtreeUpdate = param({
848
- cameraOrigin: { x: 0, y: 0, z: 0 },
849
- mode: "distance",
850
- distanceFactor: 1.5
851
- }).displayName("quadtreeUpdate");
852
-
853
- const quadtreeConfigTask = task((get, work) => {
1067
+ const surfaceTask = task((get, work) => {
1068
+ const customSurface = get(surface);
854
1069
  const rootSizeVal = get(rootSize);
855
1070
  const originVal = get(origin);
1071
+ return work(() => {
1072
+ if (customSurface) return customSurface;
1073
+ return createFlatSurface({ rootSize: rootSizeVal, origin: originVal });
1074
+ });
1075
+ }).displayName("surfaceTask");
1076
+ const quadtreeConfigTask = task((get, work) => {
1077
+ const surfaceVal = get(surfaceTask);
856
1078
  const maxNodesVal = get(maxNodes);
857
1079
  const maxLevelVal = get(maxLevel);
858
1080
  return work(() => {
859
- const surface = createFlatSurface({
860
- rootSize: rootSizeVal,
861
- origin: originVal
862
- });
863
- const state = createState({ maxNodes: maxNodesVal, maxLevel: maxLevelVal }, surface);
1081
+ const state = createState({ maxNodes: maxNodesVal, maxLevel: maxLevelVal }, surfaceVal);
864
1082
  return {
865
1083
  state,
866
- surface
1084
+ surface: surfaceVal
867
1085
  };
868
1086
  });
869
1087
  }).displayName("quadtreeConfigTask");
@@ -883,12 +1101,7 @@ const quadtreeUpdateTask = task((get, work) => {
883
1101
  }).displayName("quadtreeUpdateTask");
884
1102
  const leafStorageTask = task((get, work) => {
885
1103
  const maxNodesVal = get(maxNodes);
886
- return work(() => {
887
- const data = new Int32Array(maxNodesVal * 4);
888
- const attribute = new StorageBufferAttribute(data, 4);
889
- const node = storage(attribute, "i32", 1).toReadOnly();
890
- return { data, attribute, node };
891
- });
1104
+ return work(() => createLeafStorage(maxNodesVal));
892
1105
  }).displayName("leafStorageTask");
893
1106
  const leafGpuBufferTask = task((get, work) => {
894
1107
  const leafSet = get(quadtreeUpdateTask);
@@ -914,6 +1127,23 @@ const leafGpuBufferTask = task((get, work) => {
914
1127
  });
915
1128
  }).displayName("leafGpuBufferTask");
916
1129
 
1130
+ function createElevationFunction(callback) {
1131
+ const tslFunction = (args) => {
1132
+ const params = {
1133
+ worldPosition: args.worldPosition,
1134
+ rootSize: args.rootSize,
1135
+ rootUV: args.rootUV,
1136
+ tileUV: args.tileUV,
1137
+ tileLevel: args.tileLevel,
1138
+ tileSize: args.tileSize,
1139
+ tileOriginVec2: args.tileOriginVec2,
1140
+ nodeIndex: args.nodeIndex
1141
+ };
1142
+ return callback(params);
1143
+ };
1144
+ return Fn$1(tslFunction);
1145
+ }
1146
+
917
1147
  function createTerrainUniforms(params) {
918
1148
  const sanitizedId = params.instanceId?.replace(/-/g, "_");
919
1149
  const suffix = sanitizedId ? `_${sanitizedId}` : "";
@@ -925,16 +1155,18 @@ function createTerrainUniforms(params) {
925
1155
  `uInnerTileSegments${suffix}`
926
1156
  );
927
1157
  const uSkirtScale = uniform(float(params.skirtScale)).setName(`uSkirtScale${suffix}`);
928
- const uHeightmapScale = uniform(float(params.heightmapScale)).setName(`uHeightmapScale${suffix}`);
1158
+ const uElevationScale = uniform(float(params.elevationScale)).setName(`uElevationScale${suffix}`);
929
1159
  return {
930
1160
  uRootOrigin,
931
1161
  uRootSize,
932
1162
  uInnerTileSegments,
933
1163
  uSkirtScale,
934
- uHeightmapScale
1164
+ uElevationScale
935
1165
  };
936
1166
  }
937
1167
 
1168
+ const instanceIdTask = task(() => crypto.randomUUID()).displayName("instanceIdTask").cache("once");
1169
+
938
1170
  const scratchVector3 = new Vector3$1();
939
1171
  const createUniformsTask = task((get, work) => {
940
1172
  const uniformParams = {
@@ -942,18 +1174,18 @@ const createUniformsTask = task((get, work) => {
942
1174
  rootSize: get(rootSize),
943
1175
  innerTileSegments: get(innerTileSegments),
944
1176
  skirtScale: get(skirtScale),
945
- heightmapScale: get(heightmapScale),
1177
+ elevationScale: get(elevationScale),
946
1178
  instanceId: get(instanceIdTask)
947
1179
  };
948
1180
  return work(() => createTerrainUniforms(uniformParams));
949
- }).displayName("createTerrainUniformsTask").cache("once");
1181
+ }).displayName("createUniformsTask").cache("once");
950
1182
  const updateUniformsTask = task((get, work) => {
951
1183
  const terrainUniformsContext = get(createUniformsTask);
952
1184
  const rootSizeVal = get(rootSize);
953
1185
  const rootOrigin = get(origin);
954
1186
  const innerTileSegmentsVal = get(innerTileSegments);
955
1187
  const skirtScaleVal = get(skirtScale);
956
- const heightmapScaleVal = get(heightmapScale);
1188
+ const elevationScaleVal = get(elevationScale);
957
1189
  return work(() => {
958
1190
  terrainUniformsContext.uRootSize.value = rootSizeVal;
959
1191
  terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
@@ -963,29 +1195,311 @@ const updateUniformsTask = task((get, work) => {
963
1195
  );
964
1196
  terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
965
1197
  terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
966
- terrainUniformsContext.uHeightmapScale.value = heightmapScaleVal;
1198
+ terrainUniformsContext.uElevationScale.value = elevationScaleVal;
967
1199
  return terrainUniformsContext;
968
1200
  });
969
- }).displayName("updateTerrainUniformsTask");
1201
+ }).displayName("updateUniformsTask");
1202
+
1203
+ const createElevationFieldContextTask = task((get, work) => {
1204
+ const edgeVertexCount = get(innerTileSegments) + 3;
1205
+ const verticesPerNode = edgeVertexCount * edgeVertexCount;
1206
+ const totalElements = get(maxNodes) * verticesPerNode;
1207
+ return work(() => {
1208
+ const data = new Float32Array(totalElements);
1209
+ const attribute = new StorageBufferAttribute(data, 1);
1210
+ const node = storage(attribute, "float", totalElements);
1211
+ return {
1212
+ data,
1213
+ attribute,
1214
+ node
1215
+ };
1216
+ });
1217
+ }).displayName("createElevationFieldContextTask");
1218
+ const tileNodesTask = task((get, work) => {
1219
+ const leafStorage = get(leafStorageTask);
1220
+ const uniforms = get(createUniformsTask);
1221
+ return work(() => {
1222
+ return createTileCompute(leafStorage, uniforms);
1223
+ });
1224
+ }).displayName("tileNodesTask");
1225
+ const elevationFieldStageTask = task((get, work) => {
1226
+ const tile = get(tileNodesTask);
1227
+ const uniforms = get(createUniformsTask);
1228
+ const elevationFieldContext = get(createElevationFieldContextTask);
1229
+ const userElevationFn = get(elevationFn);
1230
+ return work(() => {
1231
+ const heightFn = createElevationFunction(userElevationFn);
1232
+ const heightWriteFn = createElevation(tile, uniforms, heightFn);
1233
+ return [
1234
+ (nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
1235
+ const height = heightWriteFn(nodeIndex, localCoordinates);
1236
+ elevationFieldContext.node.element(globalVertexIndex).assign(height);
1237
+ }
1238
+ ];
1239
+ });
1240
+ }).displayName("elevationFieldStageTask");
1241
+
1242
+ const createNormalFieldContextTask = task((get, work) => {
1243
+ const edgeVertexCount = get(innerTileSegments) + 3;
1244
+ const verticesPerNode = edgeVertexCount * edgeVertexCount;
1245
+ const totalElements = get(maxNodes) * verticesPerNode;
1246
+ return work(() => {
1247
+ const data = new Uint32Array(totalElements);
1248
+ const attribute = new StorageBufferAttribute(data, 1);
1249
+ const node = storage(attribute, "uint", totalElements);
1250
+ return {
1251
+ data,
1252
+ attribute,
1253
+ node
1254
+ };
1255
+ });
1256
+ }).displayName("createNormalFieldContextTask");
1257
+ function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
1258
+ return Fn(
1259
+ ([nodeIndex, tileSize, ix, iy, elevationScale]) => {
1260
+ const iEdge = int(edgeVertexCount);
1261
+ const verticesPerNode = iEdge.mul(iEdge);
1262
+ const baseOffset = int(nodeIndex).mul(verticesPerNode);
1263
+ const xLeft = int(ix).sub(int(1));
1264
+ const xRight = int(ix).add(int(1));
1265
+ const yUp = int(iy).sub(int(1));
1266
+ const yDown = int(iy).add(int(1));
1267
+ const hLeft = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
1268
+ const hRight = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
1269
+ const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(int(ix)))).mul(elevationScale);
1270
+ const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(int(ix)))).mul(elevationScale);
1271
+ const innerSegments = float(iEdge).sub(float(3));
1272
+ const stepWorld = tileSize.div(innerSegments);
1273
+ const inv2Step = float(0.5).div(stepWorld);
1274
+ const dhdx = float(hRight).sub(float(hLeft)).mul(inv2Step);
1275
+ const dhdz = float(hDown).sub(float(hUp)).mul(inv2Step);
1276
+ const normal = vec3(dhdx.negate(), float(1), dhdz.negate()).normalize();
1277
+ return vec2(normal.x, normal.z);
1278
+ }
1279
+ );
1280
+ }
1281
+ const normalFieldStageTask = task((get, work) => {
1282
+ const upstream = get(elevationFieldStageTask);
1283
+ const elevationFieldContext = get(createElevationFieldContextTask);
1284
+ const normalFieldContext = get(createNormalFieldContextTask);
1285
+ const tileEdgeVertexCount = get(innerTileSegments) + 3;
1286
+ const tile = get(tileNodesTask);
1287
+ const uniforms = get(createUniformsTask);
1288
+ return work(() => {
1289
+ const computeNormal = createNormalFromElevationField(
1290
+ elevationFieldContext.node,
1291
+ tileEdgeVertexCount
1292
+ );
1293
+ return [
1294
+ ...upstream,
1295
+ (nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
1296
+ const ix = int(localCoordinates.x);
1297
+ const iy = int(localCoordinates.y);
1298
+ const tileSize = tile.tileSize(nodeIndex);
1299
+ const normalXZ = computeNormal(
1300
+ nodeIndex,
1301
+ tileSize,
1302
+ ix,
1303
+ iy,
1304
+ uniforms.uElevationScale
1305
+ );
1306
+ normalFieldContext.node.element(globalVertexIndex).assign(packHalf2x16(normalXZ));
1307
+ }
1308
+ ];
1309
+ });
1310
+ }).displayName("normalFieldStageTask");
1311
+
1312
+ const compileComputeTask = task((get, work) => {
1313
+ const pipeline = get(normalFieldStageTask);
1314
+ const edgeVertexCount = get(innerTileSegments) + 3;
1315
+ return work(() => compileComputePipeline(pipeline, edgeVertexCount));
1316
+ }).displayName("compileComputeTask");
1317
+ const executeComputeTask = task((get, work, { resources }) => {
1318
+ const { execute } = get(compileComputeTask);
1319
+ const leafState = get(leafGpuBufferTask);
1320
+ return work(
1321
+ () => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
1322
+ }
1323
+ );
1324
+ }).displayName("executeComputeTask").lane("gpu");
1325
+ function createComputePipelineTasks(leafStageTask) {
1326
+ const compile = task((get, work) => {
1327
+ const pipeline = get(leafStageTask);
1328
+ const edgeVertexCount = get(innerTileSegments) + 3;
1329
+ return work(() => compileComputePipeline(pipeline, edgeVertexCount));
1330
+ }).displayName("compileComputeTask");
1331
+ const execute = task((get, work, { resources }) => {
1332
+ const { execute: run } = get(compile);
1333
+ const leafState = get(leafGpuBufferTask);
1334
+ return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
1335
+ });
1336
+ }).displayName("executeComputeTask").lane("gpu");
1337
+ return { compile, execute };
1338
+ }
1339
+
1340
+ const textureSpaceToVectorSpace = Fn(([value]) => {
1341
+ return remap(value, float(0), float(1), float(-1), float(1));
1342
+ });
1343
+ const vectorSpaceToTextureSpace = Fn(([value]) => {
1344
+ return remap(value, float(-1), float(1), float(0), float(1));
1345
+ });
1346
+ const blendAngleCorrectedNormals = Fn(([n1, n2]) => {
1347
+ const t = vec3(n1.x, n1.y, n1.z.add(1));
1348
+ const u = vec3(n2.x.negate(), n2.y.negate(), n2.z);
1349
+ const r = t.mul(dot(t, u)).sub(u.mul(t.z)).normalize();
1350
+ return r;
1351
+ });
1352
+ const deriveNormalZ = Fn(([normalXY]) => {
1353
+ const xy = normalXY.toVar();
1354
+ const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
1355
+ return vec3(xy.x, xy.y, z);
1356
+ });
1357
+
1358
+ const isSkirtVertex = Fn(([segments]) => {
1359
+ const segmentsNode = typeof segments === "number" ? int(segments) : segments;
1360
+ const vIndex = int(vertexIndex);
1361
+ const segmentEdges = int(segmentsNode.add(3));
1362
+ const vx = vIndex.mod(segmentEdges);
1363
+ const vy = vIndex.div(segmentEdges);
1364
+ const last = segmentEdges.sub(int(1));
1365
+ return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
1366
+ });
1367
+ const isSkirtUV = Fn(([segments]) => {
1368
+ const segmentsNode = typeof segments === "number" ? int(segments) : segments;
1369
+ const ux = uv().x;
1370
+ const uy = uv().y;
1371
+ const segmentCount = segmentsNode.add(2);
1372
+ const segmentStep = float(1).div(segmentCount);
1373
+ const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
1374
+ const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
1375
+ return innerX.and(innerY).not();
1376
+ });
1377
+
1378
+ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
1379
+ return Fn(() => {
1380
+ const nodeIndex = int(instanceIndex);
1381
+ const nodeOffset = nodeIndex.mul(int(4));
1382
+ const nodeLevel = leafStorage.node.element(nodeOffset).toInt();
1383
+ const nodeX = leafStorage.node.element(nodeOffset.add(int(1))).toFloat();
1384
+ const nodeY = leafStorage.node.element(nodeOffset.add(int(2))).toFloat();
1385
+ const rootSize = terrainUniforms.uRootSize.toVar();
1386
+ const rootOrigin = terrainUniforms.uRootOrigin.toVar();
1387
+ const half = float(0.5);
1388
+ const size = rootSize.div(pow(float(2), nodeLevel.toFloat()));
1389
+ const halfRoot = rootSize.mul(half);
1390
+ const centerX = rootOrigin.x.add(nodeX.add(half).mul(size)).sub(halfRoot);
1391
+ const centerZ = rootOrigin.z.add(nodeY.add(half).mul(size)).sub(halfRoot);
1392
+ const clampedX = positionLocal.x.max(half.negate()).min(half);
1393
+ const clampedZ = positionLocal.z.max(half.negate()).min(half);
1394
+ const worldX = centerX.add(clampedX.mul(size));
1395
+ const worldZ = centerZ.add(clampedZ.mul(size));
1396
+ return vec3(worldX, rootOrigin.y, worldZ);
1397
+ });
1398
+ }
1399
+ function createTileElevation(terrainUniforms, elevationFieldBufferNode) {
1400
+ if (!elevationFieldBufferNode) return float(0);
1401
+ const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
1402
+ return readElevationFieldAtPositionLocal(
1403
+ elevationFieldBufferNode,
1404
+ edgeVertexCount,
1405
+ positionLocal
1406
+ )().mul(
1407
+ terrainUniforms.uElevationScale
1408
+ );
1409
+ }
1410
+ function createNormalAssignment(terrainUniforms, normalFieldBufferNode) {
1411
+ if (!normalFieldBufferNode) return;
1412
+ const nodeIndex = int(instanceIndex);
1413
+ const intEdge = int(terrainUniforms.uInnerTileSegments.add(3));
1414
+ const verticesPerNode = intEdge.mul(intEdge);
1415
+ const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(int(vertexIndex));
1416
+ const packed = normalFieldBufferNode.element(globalVertexIndex);
1417
+ const normalXZ = unpackHalf2x16(packed);
1418
+ const reconstructed = deriveNormalZ(normalXZ);
1419
+ normalLocal.assign(vec3(reconstructed.x, reconstructed.z, reconstructed.y));
1420
+ }
1421
+ function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBufferNode, normalFieldBufferNode) {
1422
+ const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
1423
+ return Fn(() => {
1424
+ const base = baseWorldPosition();
1425
+ const yElevation = createTileElevation(terrainUniforms, elevationFieldBufferNode);
1426
+ const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
1427
+ const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
1428
+ const worldY = select(skirtVertex, skirtY, base.y.add(yElevation));
1429
+ createNormalAssignment(terrainUniforms, normalFieldBufferNode);
1430
+ return vec3(base.x, worldY, base.z);
1431
+ })();
1432
+ }
970
1433
 
971
1434
  const positionNodeTask = task((get, work) => {
972
1435
  const leafStorage = get(leafStorageTask);
973
1436
  const terrainUniforms = get(createUniformsTask);
974
- return work(() => createTileWorldPosition(leafStorage, terrainUniforms));
975
- }).displayName("terrainVertextPositionNodeTask");
1437
+ const elevationFieldContext = get(createElevationFieldContextTask);
1438
+ const normalFieldContext = get(createNormalFieldContextTask);
1439
+ return work(
1440
+ () => createTileWorldPosition(
1441
+ leafStorage,
1442
+ terrainUniforms,
1443
+ elevationFieldContext.node,
1444
+ normalFieldContext.node
1445
+ )
1446
+ );
1447
+ }).displayName("positionNodeTask");
976
1448
 
977
1449
  function terrainGraph() {
978
- return graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask);
1450
+ return graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(createNormalFieldContextTask).add(elevationFieldStageTask).add(normalFieldStageTask).add(compileComputeTask).add(executeComputeTask);
979
1451
  }
980
1452
  const terrainTasks = {
981
1453
  instanceId: instanceIdTask,
982
1454
  quadtreeConfig: quadtreeConfigTask,
983
1455
  quadtreeUpdate: quadtreeUpdateTask,
984
1456
  leafStorage: leafStorageTask,
1457
+ surface: surfaceTask,
985
1458
  leafGpuBuffer: leafGpuBufferTask,
986
1459
  createUniforms: createUniformsTask,
987
1460
  updateUniforms: updateUniformsTask,
988
- positionNode: positionNodeTask
1461
+ positionNode: positionNodeTask,
1462
+ createElevationFieldContext: createElevationFieldContextTask,
1463
+ createTileNodes: tileNodesTask,
1464
+ createNormalFieldContext: createNormalFieldContextTask,
1465
+ elevationFieldStage: elevationFieldStageTask,
1466
+ normalFieldStage: normalFieldStageTask,
1467
+ compileCompute: compileComputeTask,
1468
+ executeCompute: executeComputeTask
989
1469
  };
990
1470
 
991
- export { Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, createCubeSphereSurface, createFlatSurface, createSpatialIndex, createState, createTerrainUniforms, createTileWorldPosition, createUniformsTask, deriveNormalZ, heightmapScale, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, maxLevel, maxNodes, origin, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, terrainGraph, terrainTasks, textureSpaceToVectorSpace, update, updateUniformsTask, vectorSpaceToTextureSpace };
1471
+ const vGlobalVertexIndex = /* @__PURE__ */ varyingProperty("int", "vGlobalVertexIndex");
1472
+ const vElevation = /* @__PURE__ */ varyingProperty("f32", "vElevation");
1473
+
1474
+ const cellCenter = Fn(({ cell }) => {
1475
+ return cell.add(mx_noise_float(cell.mul(Math.PI)));
1476
+ });
1477
+ const voronoiCells = Fn((params) => {
1478
+ const scale = float(params.scale);
1479
+ const facet = float(params.facet);
1480
+ const seed = float(params.seed);
1481
+ const pos = params.uv.mul(scale).add(seed);
1482
+ const midCell = pos.round().toVar();
1483
+ const minCell = midCell.toVar();
1484
+ const minDist = float(1).toVar();
1485
+ const cell = vec3(0, 0, 0).toVar();
1486
+ const dist = float().toVar();
1487
+ const i = float(0).toVar();
1488
+ Loop(27, () => {
1489
+ const ix = i.mod(3).sub(1);
1490
+ const iy = i.div(3).floor().mod(3).sub(1);
1491
+ const iz = i.div(9).floor().sub(1);
1492
+ cell.assign(midCell.add(vec3(ix, iy, iz)));
1493
+ dist.assign(pos.distance(cellCenter({ cell })).add(mx_noise_float(pos).div(5)));
1494
+ If(dist.lessThan(minDist), () => {
1495
+ minDist.assign(dist);
1496
+ minCell.assign(cell);
1497
+ });
1498
+ i.addAssign(1);
1499
+ });
1500
+ const n = mx_noise_float(minCell.mul(Math.PI)).toVar();
1501
+ const k = mix(minDist, n.add(1).div(2), facet);
1502
+ return k;
1503
+ });
1504
+
1505
+ 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 };