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

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.cjs CHANGED
@@ -276,42 +276,356 @@ class TerrainMesh extends webgpu.InstancedMesh {
276
276
  }
277
277
  }
278
278
 
279
+ function getDeviceComputeLimits(renderer) {
280
+ const backend = renderer.backend;
281
+ const limits = backend?.device?.limits;
282
+ return {
283
+ maxWorkgroupSizeX: limits?.maxComputeWorkgroupSizeX ?? 256,
284
+ maxWorkgroupSizeY: limits?.maxComputeWorkgroupSizeY ?? 256,
285
+ maxWorkgroupInvocations: limits?.maxComputeWorkgroupInvocations ?? 256
286
+ };
287
+ }
288
+
279
289
  const WORKGROUP_X = 16;
280
290
  const WORKGROUP_Y = 16;
281
- function compileComputePipeline(stages, width, bindings) {
282
- const workgroupSize = [WORKGROUP_X, WORKGROUP_Y, 1];
283
- const dispatchX = Math.ceil(width / WORKGROUP_X);
284
- const dispatchY = Math.ceil(width / WORKGROUP_Y);
291
+ function compileComputePipeline(stages, width, options) {
292
+ const bindings = options?.bindings;
293
+ const preferredWorkgroup = options?.workgroupSize ?? [
294
+ WORKGROUP_X,
295
+ WORKGROUP_Y
296
+ ];
297
+ const preferSingleKernelWhenPossible = options?.preferSingleKernelWhenPossible ?? true;
285
298
  const uInstanceCount = tsl.uniform(0, "uint");
286
- const computeShader = tsl.Fn(() => {
287
- const fWidth = tsl.float(width);
288
- const activeIndex = tsl.globalId.z;
289
- const nodeIndex = tsl.int(activeIndex).toVar();
290
- const iWidth = tsl.int(width);
291
- const ix = tsl.int(tsl.globalId.x);
292
- const iy = tsl.int(tsl.globalId.y);
293
- const texelSize = tsl.vec2(1, 1).div(fWidth);
294
- const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
295
- const localUVCoords = localCoordinates.div(fWidth);
296
- const verticesPerNode = iWidth.mul(iWidth);
297
- const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
298
- const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
299
- for (let i = 0; i < stages.length; i++) {
300
- if (i > 0) {
301
- tsl.workgroupBarrier();
299
+ let singleKernel;
300
+ const stagedKernelCache = /* @__PURE__ */ new Map();
301
+ function canRunSingleKernel(widthValue, limits) {
302
+ return widthValue <= limits.maxWorkgroupSizeX && widthValue <= limits.maxWorkgroupSizeY && widthValue * widthValue <= limits.maxWorkgroupInvocations;
303
+ }
304
+ function clampWorkgroupToLimits(requested, limits) {
305
+ let x = Math.max(1, Math.floor(requested[0]));
306
+ let y = Math.max(1, Math.floor(requested[1]));
307
+ x = Math.min(x, limits.maxWorkgroupSizeX);
308
+ y = Math.min(y, limits.maxWorkgroupSizeY);
309
+ y = Math.min(
310
+ y,
311
+ Math.max(1, Math.floor(limits.maxWorkgroupInvocations / x))
312
+ );
313
+ x = Math.min(
314
+ x,
315
+ Math.max(1, Math.floor(limits.maxWorkgroupInvocations / y))
316
+ );
317
+ return [x, y];
318
+ }
319
+ function buildSingleKernel(workgroupSize) {
320
+ return tsl.Fn(() => {
321
+ bindings?.forEach((b) => b.toVar());
322
+ const fWidth = tsl.float(width);
323
+ const activeIndex = tsl.globalId.z;
324
+ const nodeIndex = tsl.int(activeIndex).toVar();
325
+ const iWidth = tsl.int(width);
326
+ const ix = tsl.int(tsl.globalId.x);
327
+ const iy = tsl.int(tsl.globalId.y);
328
+ const texelSize = tsl.vec2(1, 1).div(fWidth);
329
+ const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
330
+ const localUVCoords = localCoordinates.div(fWidth);
331
+ const verticesPerNode = iWidth.mul(iWidth);
332
+ const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
333
+ const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
334
+ for (let i = 0; i < stages.length; i++) {
335
+ if (i > 0) {
336
+ tsl.workgroupBarrier();
337
+ }
338
+ tsl.If(inBounds, () => {
339
+ stages[i](
340
+ nodeIndex,
341
+ globalIndex,
342
+ localUVCoords,
343
+ localCoordinates,
344
+ texelSize
345
+ );
346
+ });
302
347
  }
303
- tsl.If(inBounds, () => {
304
- stages[i](nodeIndex, globalIndex, localUVCoords, localCoordinates, texelSize);
305
- });
306
- }
307
- })().computeKernel(workgroupSize);
348
+ })().computeKernel(workgroupSize);
349
+ }
350
+ function buildStagedKernels(workgroupSize) {
351
+ return stages.map(
352
+ (stage) => tsl.Fn(() => {
353
+ bindings?.forEach((b) => b.toVar());
354
+ const fWidth = tsl.float(width);
355
+ const activeIndex = tsl.globalId.z;
356
+ const nodeIndex = tsl.int(activeIndex).toVar();
357
+ const iWidth = tsl.int(width);
358
+ const ix = tsl.int(tsl.globalId.x);
359
+ const iy = tsl.int(tsl.globalId.y);
360
+ const texelSize = tsl.vec2(1, 1).div(fWidth);
361
+ const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
362
+ const localUVCoords = localCoordinates.div(fWidth);
363
+ const verticesPerNode = iWidth.mul(iWidth);
364
+ const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
365
+ const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
366
+ tsl.If(inBounds, () => {
367
+ stage(
368
+ nodeIndex,
369
+ globalIndex,
370
+ localUVCoords,
371
+ localCoordinates,
372
+ texelSize
373
+ );
374
+ });
375
+ })().computeKernel(workgroupSize)
376
+ );
377
+ }
308
378
  function execute(renderer, instanceCount) {
379
+ const limits = getDeviceComputeLimits(renderer);
380
+ const canUseSingleKernel = preferSingleKernelWhenPossible && canRunSingleKernel(width, limits);
309
381
  uInstanceCount.value = instanceCount;
310
- renderer.compute(computeShader, [dispatchX, dispatchY, instanceCount]);
382
+ if (canUseSingleKernel) {
383
+ if (!singleKernel) {
384
+ singleKernel = buildSingleKernel([width, width, 1]);
385
+ }
386
+ renderer.compute(singleKernel, [1, 1, instanceCount]);
387
+ return;
388
+ }
389
+ const [workgroupX, workgroupY] = clampWorkgroupToLimits(
390
+ preferredWorkgroup,
391
+ limits
392
+ );
393
+ const cacheKey = `${workgroupX}x${workgroupY}`;
394
+ let stagedKernels = stagedKernelCache.get(cacheKey);
395
+ if (!stagedKernels) {
396
+ stagedKernels = buildStagedKernels([workgroupX, workgroupY, 1]);
397
+ stagedKernelCache.set(cacheKey, stagedKernels);
398
+ }
399
+ const dispatchX = Math.ceil(width / workgroupX);
400
+ const dispatchY = Math.ceil(width / workgroupY);
401
+ for (const kernel of stagedKernels) {
402
+ renderer.compute(kernel, [dispatchX, dispatchY, instanceCount]);
403
+ }
311
404
  }
312
405
  return { execute };
313
406
  }
314
407
 
408
+ function resolveType(format) {
409
+ return format === "rgba16float" ? three.HalfFloatType : three.FloatType;
410
+ }
411
+ function resolveFilter(mode) {
412
+ return mode === "linear" ? three.LinearFilter : three.NearestFilter;
413
+ }
414
+ function configureStorageTexture(texture2, format, filter) {
415
+ texture2.format = three.RGBAFormat;
416
+ texture2.type = resolveType(format);
417
+ texture2.magFilter = resolveFilter(filter);
418
+ texture2.minFilter = resolveFilter(filter);
419
+ texture2.wrapS = three.ClampToEdgeWrapping;
420
+ texture2.wrapT = three.ClampToEdgeWrapping;
421
+ texture2.generateMipmaps = false;
422
+ texture2.needsUpdate = true;
423
+ }
424
+ function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
425
+ let currentEdgeVertexCount = edgeVertexCount;
426
+ let currentTileCount = tileCount;
427
+ const tex = new webgpu.StorageArrayTexture(
428
+ edgeVertexCount,
429
+ edgeVertexCount,
430
+ tileCount
431
+ );
432
+ configureStorageTexture(tex, options.format, options.filter);
433
+ return {
434
+ backendType: "array-texture",
435
+ get edgeVertexCount() {
436
+ return currentEdgeVertexCount;
437
+ },
438
+ get tileCount() {
439
+ return currentTileCount;
440
+ },
441
+ texture: tex,
442
+ uv(ix, iy, _tileIndex) {
443
+ return tsl.vec2(ix.toFloat(), iy.toFloat());
444
+ },
445
+ texel(ix, iy, tileIndex) {
446
+ return tsl.ivec3(ix, iy, tileIndex);
447
+ },
448
+ sample(u, v, tileIndex) {
449
+ return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
450
+ },
451
+ resize(width, height, nextTileCount) {
452
+ currentEdgeVertexCount = width;
453
+ currentTileCount = nextTileCount;
454
+ tex.setSize(width, height, nextTileCount);
455
+ tex.needsUpdate = true;
456
+ }
457
+ };
458
+ }
459
+ function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
460
+ const tilesPerRowNode = tsl.int(tilesPerRow);
461
+ const edge = tsl.int(edgeVertexCount);
462
+ const tile = tsl.int(tileIndex);
463
+ const col = tile.mod(tilesPerRowNode);
464
+ const row = tile.div(tilesPerRowNode);
465
+ const atlasX = col.mul(edge).add(tsl.int(ix));
466
+ const atlasY = row.mul(edge).add(tsl.int(iy));
467
+ return { atlasX, atlasY };
468
+ }
469
+ function AtlasBackend(edgeVertexCount, tileCount, options) {
470
+ let currentEdgeVertexCount = edgeVertexCount;
471
+ let currentTileCount = tileCount;
472
+ let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
473
+ const atlasSize = tilesPerRow * edgeVertexCount;
474
+ const tex = new webgpu.StorageTexture(atlasSize, atlasSize);
475
+ configureStorageTexture(tex, options.format, options.filter);
476
+ return {
477
+ backendType: "atlas",
478
+ get edgeVertexCount() {
479
+ return currentEdgeVertexCount;
480
+ },
481
+ get tileCount() {
482
+ return currentTileCount;
483
+ },
484
+ texture: tex,
485
+ uv(ix, iy, tileIndex) {
486
+ const { atlasX, atlasY } = atlasCoord(
487
+ tilesPerRow,
488
+ currentEdgeVertexCount,
489
+ ix,
490
+ iy,
491
+ tileIndex
492
+ );
493
+ const currentAtlasSize = tsl.float(tilesPerRow * currentEdgeVertexCount);
494
+ return tsl.vec2(
495
+ atlasX.toFloat().add(0.5).div(currentAtlasSize),
496
+ atlasY.toFloat().add(0.5).div(currentAtlasSize)
497
+ );
498
+ },
499
+ texel(ix, iy, tileIndex) {
500
+ const { atlasX, atlasY } = atlasCoord(
501
+ tilesPerRow,
502
+ currentEdgeVertexCount,
503
+ ix,
504
+ iy,
505
+ tileIndex
506
+ );
507
+ return tsl.ivec2(atlasX, atlasY);
508
+ },
509
+ sample(u, v, tileIndex) {
510
+ const tile = tsl.int(tileIndex);
511
+ const tilesPerRowNode = tsl.int(tilesPerRow);
512
+ const col = tile.mod(tilesPerRowNode);
513
+ const row = tile.div(tilesPerRowNode);
514
+ const invTilesPerRow = tsl.float(1 / tilesPerRow);
515
+ const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
516
+ const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
517
+ return tsl.texture(tex, tsl.vec2(atlasU, atlasV));
518
+ },
519
+ resize(width, height, nextTileCount) {
520
+ currentEdgeVertexCount = width;
521
+ currentTileCount = nextTileCount;
522
+ tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
523
+ const nextAtlasSize = tilesPerRow * width;
524
+ const image = tex.image;
525
+ image.width = nextAtlasSize;
526
+ image.height = nextAtlasSize;
527
+ tex.needsUpdate = true;
528
+ }
529
+ };
530
+ }
531
+ function Texture3DBackend(edgeVertexCount, tileCount, options) {
532
+ let currentEdgeVertexCount = edgeVertexCount;
533
+ let currentTileCount = tileCount;
534
+ const tex = new webgpu.StorageArrayTexture(
535
+ edgeVertexCount,
536
+ edgeVertexCount,
537
+ tileCount
538
+ );
539
+ configureStorageTexture(tex, options.format, options.filter);
540
+ return {
541
+ backendType: "texture-3d",
542
+ get edgeVertexCount() {
543
+ return currentEdgeVertexCount;
544
+ },
545
+ get tileCount() {
546
+ return currentTileCount;
547
+ },
548
+ texture: tex,
549
+ uv(ix, iy, _tileIndex) {
550
+ return tsl.vec2(ix.toFloat(), iy.toFloat());
551
+ },
552
+ texel(ix, iy, tileIndex) {
553
+ return tsl.ivec3(ix, iy, tileIndex);
554
+ },
555
+ sample(u, v, tileIndex) {
556
+ return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
557
+ },
558
+ resize(width, height, nextTileCount) {
559
+ currentEdgeVertexCount = width;
560
+ currentTileCount = nextTileCount;
561
+ tex.setSize(width, height, nextTileCount);
562
+ tex.needsUpdate = true;
563
+ }
564
+ };
565
+ }
566
+ function tryGetDeviceLimits(renderer) {
567
+ const backend = renderer;
568
+ return backend.backend?.device?.limits ?? {};
569
+ }
570
+ function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
571
+ const filter = options.filter ?? "linear";
572
+ const format = options.format ?? "rgba16float";
573
+ const forcedBackend = options.backend;
574
+ if (forcedBackend === "atlas") {
575
+ return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
576
+ }
577
+ if (forcedBackend === "texture-3d") {
578
+ return Texture3DBackend(edgeVertexCount, tileCount, { filter, format });
579
+ }
580
+ if (forcedBackend === "array-texture") {
581
+ return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
582
+ }
583
+ const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
584
+ const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
585
+ if (tileCount > maxLayers) {
586
+ return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
587
+ }
588
+ return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
589
+ }
590
+ function storeTerrainField(storage, ix, iy, tileIndex, value) {
591
+ if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
592
+ return tsl.textureStore(
593
+ storage.texture,
594
+ tsl.uvec3(tsl.int(ix), tsl.int(iy), tsl.int(tileIndex)),
595
+ value
596
+ );
597
+ }
598
+ return tsl.textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
599
+ }
600
+ function loadTerrainField(storage, ix, iy, tileIndex) {
601
+ if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
602
+ return tsl.textureLoad(storage.texture, tsl.ivec2(tsl.int(ix), tsl.int(iy)), tsl.int(0)).depth(
603
+ tsl.int(tileIndex)
604
+ );
605
+ }
606
+ return tsl.textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), tsl.int(0));
607
+ }
608
+ function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
609
+ return loadTerrainField(storage, ix, iy, tileIndex).r;
610
+ }
611
+ function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
612
+ const raw = loadTerrainField(storage, ix, iy, tileIndex);
613
+ return tsl.vec2(raw.g, raw.b);
614
+ }
615
+ function sampleTerrainField(storage, u, v, tileIndex) {
616
+ return storage.sample(u, v, tileIndex);
617
+ }
618
+ function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
619
+ return sampleTerrainField(storage, u, v, tileIndex).r;
620
+ }
621
+ function sampleTerrainFieldNormal(storage, u, v, tileIndex) {
622
+ const raw = sampleTerrainField(storage, u, v, tileIndex);
623
+ return tsl.vec2(raw.g, raw.b);
624
+ }
625
+ function packTerrainFieldSample(height, normalXZ, extra = tsl.float(0)) {
626
+ return tsl.vec4(height, normalXZ.x, normalXZ.y, extra);
627
+ }
628
+
315
629
  const createElevation = (tile, uniforms, elevationFn) => {
316
630
  return function perVertexElevation(nodeIndex, localCoordinates) {
317
631
  const ix = tsl.int(localCoordinates.x);
@@ -333,7 +647,7 @@ const createElevation = (tile, uniforms, elevationFn) => {
333
647
  });
334
648
  };
335
649
  };
336
- const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount, positionLocal) => tsl.Fn(() => {
650
+ const readElevationFieldAtPositionLocal = (terrainFieldStorage, edgeVertexCount, positionLocal) => tsl.Fn(() => {
337
651
  const nodeIndex = tsl.int(tsl.instanceIndex);
338
652
  const intEdge = tsl.int(edgeVertexCount);
339
653
  const innerSegments = tsl.int(edgeVertexCount).sub(3);
@@ -345,10 +659,12 @@ const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount
345
659
  const y = v.mul(fInnerSegments).round().toInt().add(tsl.int(1));
346
660
  const xClamped = tsl.min(tsl.max(x, tsl.int(0)), last);
347
661
  const yClamped = tsl.min(tsl.max(y, tsl.int(0)), last);
348
- const verticesPerNode = intEdge.mul(intEdge);
349
- const perNodeVertexIndex = yClamped.mul(intEdge).add(xClamped);
350
- const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(perNodeVertexIndex);
351
- return elevationFieldBuffer.element(globalVertexIndex);
662
+ return loadTerrainFieldElevation(
663
+ terrainFieldStorage,
664
+ xClamped,
665
+ yClamped,
666
+ nodeIndex
667
+ );
352
668
  });
353
669
 
354
670
  function createTileCompute(leafStorage, uniforms) {
@@ -434,6 +750,7 @@ const quadtreeUpdate = work.param({
434
750
  distanceFactor: 1.5
435
751
  }).displayName("quadtreeUpdate");
436
752
  const surface = work.param(null).displayName("surface");
753
+ const terrainFieldFilter = work.param("linear").displayName("terrainFieldFilter");
437
754
  const elevationFn = work.param(() => tsl.float(0));
438
755
 
439
756
  function createLeafStorage(maxNodes) {
@@ -1241,21 +1558,21 @@ const elevationFieldStageTask = work.task((get, work) => {
1241
1558
  });
1242
1559
  }).displayName("elevationFieldStageTask");
1243
1560
 
1244
- const createNormalFieldContextTask = work.task((get, work) => {
1245
- const edgeVertexCount = get(innerTileSegments) + 3;
1246
- const verticesPerNode = edgeVertexCount * edgeVertexCount;
1247
- const totalElements = get(maxNodes) * verticesPerNode;
1248
- return work(() => {
1249
- const data = new Uint32Array(totalElements);
1250
- const attribute = new webgpu.StorageBufferAttribute(data, 1);
1251
- const node = tsl.storage(attribute, "uint", totalElements);
1252
- return {
1253
- data,
1254
- attribute,
1255
- node
1256
- };
1257
- });
1258
- }).displayName("createNormalFieldContextTask");
1561
+ const createTerrainFieldTextureTask = work.task(
1562
+ (get, work, { resources }) => {
1563
+ const edgeVertexCount = get(innerTileSegments) + 3;
1564
+ const maxNodesValue = get(maxNodes);
1565
+ const filter = get(terrainFieldFilter);
1566
+ return work(
1567
+ () => createTerrainFieldStorage(
1568
+ edgeVertexCount,
1569
+ maxNodesValue,
1570
+ resources?.renderer,
1571
+ { filter }
1572
+ )
1573
+ );
1574
+ }
1575
+ ).displayName("createTerrainFieldTextureTask");
1259
1576
  function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
1260
1577
  return tsl.Fn(
1261
1578
  ([nodeIndex, tileSize, ix, iy, elevationScale]) => {
@@ -1280,10 +1597,10 @@ function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
1280
1597
  }
1281
1598
  );
1282
1599
  }
1283
- const normalFieldStageTask = work.task((get, work) => {
1600
+ const terrainFieldStageTask = work.task((get, work) => {
1284
1601
  const upstream = get(elevationFieldStageTask);
1285
1602
  const elevationFieldContext = get(createElevationFieldContextTask);
1286
- const normalFieldContext = get(createNormalFieldContextTask);
1603
+ const terrainFieldStorage = get(createTerrainFieldTextureTask);
1287
1604
  const tileEdgeVertexCount = get(innerTileSegments) + 3;
1288
1605
  const tile = get(tileNodesTask);
1289
1606
  const uniforms = get(createUniformsTask);
@@ -1298,6 +1615,7 @@ const normalFieldStageTask = work.task((get, work) => {
1298
1615
  const ix = tsl.int(localCoordinates.x);
1299
1616
  const iy = tsl.int(localCoordinates.y);
1300
1617
  const tileSize = tile.tileSize(nodeIndex);
1618
+ const height = elevationFieldContext.node.element(globalVertexIndex);
1301
1619
  const normalXZ = computeNormal(
1302
1620
  nodeIndex,
1303
1621
  tileSize,
@@ -1305,58 +1623,60 @@ const normalFieldStageTask = work.task((get, work) => {
1305
1623
  iy,
1306
1624
  uniforms.uElevationScale
1307
1625
  );
1308
- normalFieldContext.node.element(globalVertexIndex).assign(tsl.packHalf2x16(normalXZ));
1626
+ storeTerrainField(
1627
+ terrainFieldStorage,
1628
+ ix,
1629
+ iy,
1630
+ nodeIndex,
1631
+ packTerrainFieldSample(height, normalXZ)
1632
+ );
1309
1633
  }
1310
1634
  ];
1311
1635
  });
1312
- }).displayName("normalFieldStageTask");
1636
+ }).displayName("terrainFieldStageTask");
1313
1637
 
1314
1638
  const compileComputeTask = work.task((get, work) => {
1315
- const pipeline = get(normalFieldStageTask);
1639
+ const pipeline = get(terrainFieldStageTask);
1316
1640
  const edgeVertexCount = get(innerTileSegments) + 3;
1317
- return work(() => compileComputePipeline(pipeline, edgeVertexCount));
1318
- }).displayName("compileComputeTask");
1319
- const executeComputeTask = work.task((get, work, { resources }) => {
1320
- const { execute } = get(compileComputeTask);
1321
- const leafState = get(leafGpuBufferTask);
1322
1641
  return work(
1323
- () => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
1324
- }
1642
+ () => compileComputePipeline(pipeline, edgeVertexCount, {
1643
+ preferSingleKernelWhenPossible: false
1644
+ })
1325
1645
  );
1326
- }).displayName("executeComputeTask").lane("gpu");
1646
+ }).displayName("compileComputeTask");
1647
+ const executeComputeTask = work.task(
1648
+ (get, work, { resources }) => {
1649
+ const { execute } = get(compileComputeTask);
1650
+ const leafState = get(leafGpuBufferTask);
1651
+ return work(
1652
+ () => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
1653
+ }
1654
+ );
1655
+ }
1656
+ ).displayName("executeComputeTask").lane("gpu");
1327
1657
  function createComputePipelineTasks(leafStageTask) {
1328
1658
  const compile = work.task((get, work) => {
1329
1659
  const pipeline = get(leafStageTask);
1330
1660
  const edgeVertexCount = get(innerTileSegments) + 3;
1331
- return work(() => compileComputePipeline(pipeline, edgeVertexCount));
1661
+ return work(
1662
+ () => compileComputePipeline(pipeline, edgeVertexCount, {
1663
+ preferSingleKernelWhenPossible: false
1664
+ })
1665
+ );
1332
1666
  }).displayName("compileComputeTask");
1333
- const execute = work.task((get, work, { resources }) => {
1334
- const { execute: run } = get(compile);
1335
- const leafState = get(leafGpuBufferTask);
1336
- return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
1337
- });
1338
- }).displayName("executeComputeTask").lane("gpu");
1667
+ const execute = work.task(
1668
+ (get, work, { resources }) => {
1669
+ const { execute: run } = get(compile);
1670
+ const leafState = get(leafGpuBufferTask);
1671
+ return work(
1672
+ () => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
1673
+ }
1674
+ );
1675
+ }
1676
+ ).displayName("executeComputeTask").lane("gpu");
1339
1677
  return { compile, execute };
1340
1678
  }
1341
1679
 
1342
- const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
1343
- return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
1344
- });
1345
- const vectorSpaceToTextureSpace = tsl.Fn(([value]) => {
1346
- return tsl.remap(value, tsl.float(-1), tsl.float(1), tsl.float(0), tsl.float(1));
1347
- });
1348
- const blendAngleCorrectedNormals = tsl.Fn(([n1, n2]) => {
1349
- const t = tsl.vec3(n1.x, n1.y, n1.z.add(1));
1350
- const u = tsl.vec3(n2.x.negate(), n2.y.negate(), n2.z);
1351
- const r = t.mul(tsl.dot(t, u)).sub(u.mul(t.z)).normalize();
1352
- return r;
1353
- });
1354
- const deriveNormalZ = tsl.Fn(([normalXY]) => {
1355
- const xy = normalXY.toVar();
1356
- const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
1357
- return tsl.vec3(xy.x, xy.y, z);
1358
- });
1359
-
1360
1680
  const isSkirtVertex = tsl.Fn(([segments]) => {
1361
1681
  const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
1362
1682
  const vIndex = tsl.int(tsl.vertexIndex);
@@ -1398,37 +1718,40 @@ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
1398
1718
  return tsl.vec3(worldX, rootOrigin.y, worldZ);
1399
1719
  });
1400
1720
  }
1401
- function createTileElevation(terrainUniforms, elevationFieldBufferNode) {
1402
- if (!elevationFieldBufferNode) return tsl.float(0);
1721
+ function createTileElevation(terrainUniforms, terrainFieldStorage) {
1722
+ if (!terrainFieldStorage) return tsl.float(0);
1403
1723
  const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
1404
1724
  return readElevationFieldAtPositionLocal(
1405
- elevationFieldBufferNode,
1725
+ terrainFieldStorage,
1406
1726
  edgeVertexCount,
1407
1727
  tsl.positionLocal
1408
1728
  )().mul(
1409
1729
  terrainUniforms.uElevationScale
1410
1730
  );
1411
1731
  }
1412
- function createNormalAssignment(terrainUniforms, normalFieldBufferNode) {
1413
- if (!normalFieldBufferNode) return;
1732
+ function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
1733
+ if (!terrainFieldStorage) return;
1414
1734
  const nodeIndex = tsl.int(tsl.instanceIndex);
1415
- const intEdge = tsl.int(terrainUniforms.uInnerTileSegments.add(3));
1416
- const verticesPerNode = intEdge.mul(intEdge);
1417
- const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(tsl.int(tsl.vertexIndex));
1418
- const packed = normalFieldBufferNode.element(globalVertexIndex);
1419
- const normalXZ = tsl.unpackHalf2x16(packed);
1420
- const reconstructed = deriveNormalZ(normalXZ);
1421
- tsl.normalLocal.assign(tsl.vec3(reconstructed.x, reconstructed.z, reconstructed.y));
1735
+ const edgeVertexCount = tsl.int(terrainUniforms.uInnerTileSegments.add(3));
1736
+ const localVertexIndex = tsl.int(tsl.vertexIndex);
1737
+ const ix = localVertexIndex.mod(edgeVertexCount);
1738
+ const iy = localVertexIndex.div(edgeVertexCount);
1739
+ const normalXZ = loadTerrainFieldNormal(terrainFieldStorage, ix, iy, nodeIndex);
1740
+ const nx = normalXZ.x;
1741
+ const nz = normalXZ.y;
1742
+ const nySq = tsl.float(1).sub(nx.mul(nx)).sub(nz.mul(nz)).max(tsl.float(0));
1743
+ const ny = nySq.sqrt();
1744
+ tsl.normalLocal.assign(tsl.vec3(nx, ny, nz));
1422
1745
  }
1423
- function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBufferNode, normalFieldBufferNode) {
1746
+ function createTileWorldPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
1424
1747
  const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
1425
1748
  return tsl.Fn(() => {
1426
1749
  const base = baseWorldPosition();
1427
- const yElevation = createTileElevation(terrainUniforms, elevationFieldBufferNode);
1750
+ const yElevation = createTileElevation(terrainUniforms, terrainFieldStorage);
1428
1751
  const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
1429
1752
  const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
1430
1753
  const worldY = tsl.select(skirtVertex, skirtY, base.y.add(yElevation));
1431
- createNormalAssignment(terrainUniforms, normalFieldBufferNode);
1754
+ createNormalAssignment(terrainUniforms, terrainFieldStorage);
1432
1755
  return tsl.vec3(base.x, worldY, base.z);
1433
1756
  })();
1434
1757
  }
@@ -1436,20 +1759,18 @@ function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBuf
1436
1759
  const positionNodeTask = work.task((get, work) => {
1437
1760
  const leafStorage = get(leafStorageTask);
1438
1761
  const terrainUniforms = get(createUniformsTask);
1439
- const elevationFieldContext = get(createElevationFieldContextTask);
1440
- const normalFieldContext = get(createNormalFieldContextTask);
1762
+ const terrainFieldStorage = get(createTerrainFieldTextureTask);
1441
1763
  return work(
1442
1764
  () => createTileWorldPosition(
1443
1765
  leafStorage,
1444
1766
  terrainUniforms,
1445
- elevationFieldContext.node,
1446
- normalFieldContext.node
1767
+ terrainFieldStorage
1447
1768
  )
1448
1769
  );
1449
1770
  }).displayName("positionNodeTask");
1450
1771
 
1451
1772
  function terrainGraph() {
1452
- return work.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);
1773
+ return work.graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(createTerrainFieldTextureTask).add(elevationFieldStageTask).add(terrainFieldStageTask).add(compileComputeTask).add(executeComputeTask);
1453
1774
  }
1454
1775
  const terrainTasks = {
1455
1776
  instanceId: instanceIdTask,
@@ -1463,13 +1784,31 @@ const terrainTasks = {
1463
1784
  positionNode: positionNodeTask,
1464
1785
  createElevationFieldContext: createElevationFieldContextTask,
1465
1786
  createTileNodes: tileNodesTask,
1466
- createNormalFieldContext: createNormalFieldContextTask,
1787
+ createTerrainFieldTexture: createTerrainFieldTextureTask,
1467
1788
  elevationFieldStage: elevationFieldStageTask,
1468
- normalFieldStage: normalFieldStageTask,
1789
+ terrainFieldStage: terrainFieldStageTask,
1469
1790
  compileCompute: compileComputeTask,
1470
1791
  executeCompute: executeComputeTask
1471
1792
  };
1472
1793
 
1794
+ const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
1795
+ return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
1796
+ });
1797
+ const vectorSpaceToTextureSpace = tsl.Fn(([value]) => {
1798
+ return tsl.remap(value, tsl.float(-1), tsl.float(1), tsl.float(0), tsl.float(1));
1799
+ });
1800
+ const blendAngleCorrectedNormals = tsl.Fn(([n1, n2]) => {
1801
+ const t = tsl.vec3(n1.x, n1.y, n1.z.add(1));
1802
+ const u = tsl.vec3(n2.x.negate(), n2.y.negate(), n2.z);
1803
+ const r = t.mul(tsl.dot(t, u)).sub(u.mul(t.z)).normalize();
1804
+ return r;
1805
+ });
1806
+ const deriveNormalZ = tsl.Fn(([normalXY]) => {
1807
+ const xy = normalXY.toVar();
1808
+ const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
1809
+ return tsl.vec3(xy.x, xy.y, z);
1810
+ });
1811
+
1473
1812
  const vGlobalVertexIndex = /* @__PURE__ */ tsl.varyingProperty("int", "vGlobalVertexIndex");
1474
1813
  const vElevation = /* @__PURE__ */ tsl.varyingProperty("f32", "vElevation");
1475
1814
 
@@ -1504,9 +1843,12 @@ const voronoiCells = tsl.Fn((params) => {
1504
1843
  return k;
1505
1844
  });
1506
1845
 
1846
+ exports.ArrayTextureBackend = ArrayTextureBackend;
1847
+ exports.AtlasBackend = AtlasBackend;
1507
1848
  exports.Dir = Dir;
1508
1849
  exports.TerrainGeometry = TerrainGeometry;
1509
1850
  exports.TerrainMesh = TerrainMesh;
1851
+ exports.Texture3DBackend = Texture3DBackend;
1510
1852
  exports.U32_EMPTY = U32_EMPTY;
1511
1853
  exports.allocLeafSet = allocLeafSet;
1512
1854
  exports.allocSeamTable = allocSeamTable;
@@ -1520,9 +1862,10 @@ exports.createCubeSphereSurface = createCubeSphereSurface;
1520
1862
  exports.createElevationFieldContextTask = createElevationFieldContextTask;
1521
1863
  exports.createFlatSurface = createFlatSurface;
1522
1864
  exports.createInfiniteFlatSurface = createInfiniteFlatSurface;
1523
- exports.createNormalFieldContextTask = createNormalFieldContextTask;
1524
1865
  exports.createSpatialIndex = createSpatialIndex;
1525
1866
  exports.createState = createState;
1867
+ exports.createTerrainFieldStorage = createTerrainFieldStorage;
1868
+ exports.createTerrainFieldTextureTask = createTerrainFieldTextureTask;
1526
1869
  exports.createTerrainUniforms = createTerrainUniforms;
1527
1870
  exports.createUniformsTask = createUniformsTask;
1528
1871
  exports.deriveNormalZ = deriveNormalZ;
@@ -1530,16 +1873,20 @@ exports.elevationFieldStageTask = elevationFieldStageTask;
1530
1873
  exports.elevationFn = elevationFn;
1531
1874
  exports.elevationScale = elevationScale;
1532
1875
  exports.executeComputeTask = executeComputeTask;
1876
+ exports.getDeviceComputeLimits = getDeviceComputeLimits;
1533
1877
  exports.innerTileSegments = innerTileSegments;
1534
1878
  exports.instanceIdTask = instanceIdTask;
1535
1879
  exports.isSkirtUV = isSkirtUV;
1536
1880
  exports.isSkirtVertex = isSkirtVertex;
1537
1881
  exports.leafGpuBufferTask = leafGpuBufferTask;
1538
1882
  exports.leafStorageTask = leafStorageTask;
1883
+ exports.loadTerrainField = loadTerrainField;
1884
+ exports.loadTerrainFieldElevation = loadTerrainFieldElevation;
1885
+ exports.loadTerrainFieldNormal = loadTerrainFieldNormal;
1539
1886
  exports.maxLevel = maxLevel;
1540
1887
  exports.maxNodes = maxNodes;
1541
- exports.normalFieldStageTask = normalFieldStageTask;
1542
1888
  exports.origin = origin;
1889
+ exports.packTerrainFieldSample = packTerrainFieldSample;
1543
1890
  exports.positionNodeTask = positionNodeTask;
1544
1891
  exports.quadtreeConfigTask = quadtreeConfigTask;
1545
1892
  exports.quadtreeUpdate = quadtreeUpdate;
@@ -1547,9 +1894,15 @@ exports.quadtreeUpdateTask = quadtreeUpdateTask;
1547
1894
  exports.resetLeafSet = resetLeafSet;
1548
1895
  exports.resetSeamTable = resetSeamTable;
1549
1896
  exports.rootSize = rootSize;
1897
+ exports.sampleTerrainField = sampleTerrainField;
1898
+ exports.sampleTerrainFieldElevation = sampleTerrainFieldElevation;
1899
+ exports.sampleTerrainFieldNormal = sampleTerrainFieldNormal;
1550
1900
  exports.skirtScale = skirtScale;
1901
+ exports.storeTerrainField = storeTerrainField;
1551
1902
  exports.surface = surface;
1552
1903
  exports.surfaceTask = surfaceTask;
1904
+ exports.terrainFieldFilter = terrainFieldFilter;
1905
+ exports.terrainFieldStageTask = terrainFieldStageTask;
1553
1906
  exports.terrainGraph = terrainGraph;
1554
1907
  exports.terrainTasks = terrainTasks;
1555
1908
  exports.textureSpaceToVectorSpace = textureSpaceToVectorSpace;