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