@hello-terrain/three 0.0.0-alpha.7 → 0.0.0-alpha.9
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 +372 -66
- package/dist/index.d.cts +60 -13
- package/dist/index.d.mts +60 -13
- package/dist/index.d.ts +60 -13
- package/dist/index.mjs +366 -68
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -411,25 +411,25 @@ function resolveType(format) {
|
|
|
411
411
|
function resolveFilter(mode) {
|
|
412
412
|
return mode === "linear" ? three.LinearFilter : three.NearestFilter;
|
|
413
413
|
}
|
|
414
|
-
function configureStorageTexture(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
423
|
}
|
|
424
424
|
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
425
425
|
let currentEdgeVertexCount = edgeVertexCount;
|
|
426
426
|
let currentTileCount = tileCount;
|
|
427
|
-
const
|
|
427
|
+
const tex = new webgpu.StorageArrayTexture(
|
|
428
428
|
edgeVertexCount,
|
|
429
429
|
edgeVertexCount,
|
|
430
430
|
tileCount
|
|
431
431
|
);
|
|
432
|
-
configureStorageTexture(
|
|
432
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
433
433
|
return {
|
|
434
434
|
backendType: "array-texture",
|
|
435
435
|
get edgeVertexCount() {
|
|
@@ -438,18 +438,21 @@ function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
|
438
438
|
get tileCount() {
|
|
439
439
|
return currentTileCount;
|
|
440
440
|
},
|
|
441
|
-
texture,
|
|
441
|
+
texture: tex,
|
|
442
442
|
uv(ix, iy, _tileIndex) {
|
|
443
443
|
return tsl.vec2(ix.toFloat(), iy.toFloat());
|
|
444
444
|
},
|
|
445
445
|
texel(ix, iy, tileIndex) {
|
|
446
446
|
return tsl.ivec3(ix, iy, tileIndex);
|
|
447
447
|
},
|
|
448
|
+
sample(u, v, tileIndex) {
|
|
449
|
+
return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
|
|
450
|
+
},
|
|
448
451
|
resize(width, height, nextTileCount) {
|
|
449
452
|
currentEdgeVertexCount = width;
|
|
450
453
|
currentTileCount = nextTileCount;
|
|
451
|
-
|
|
452
|
-
|
|
454
|
+
tex.setSize(width, height, nextTileCount);
|
|
455
|
+
tex.needsUpdate = true;
|
|
453
456
|
}
|
|
454
457
|
};
|
|
455
458
|
}
|
|
@@ -468,8 +471,8 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
|
468
471
|
let currentTileCount = tileCount;
|
|
469
472
|
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
470
473
|
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
471
|
-
const
|
|
472
|
-
configureStorageTexture(
|
|
474
|
+
const tex = new webgpu.StorageTexture(atlasSize, atlasSize);
|
|
475
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
473
476
|
return {
|
|
474
477
|
backendType: "atlas",
|
|
475
478
|
get edgeVertexCount() {
|
|
@@ -478,7 +481,7 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
|
478
481
|
get tileCount() {
|
|
479
482
|
return currentTileCount;
|
|
480
483
|
},
|
|
481
|
-
texture,
|
|
484
|
+
texture: tex,
|
|
482
485
|
uv(ix, iy, tileIndex) {
|
|
483
486
|
const { atlasX, atlasY } = atlasCoord(
|
|
484
487
|
tilesPerRow,
|
|
@@ -503,27 +506,37 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
|
503
506
|
);
|
|
504
507
|
return tsl.ivec2(atlasX, atlasY);
|
|
505
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
|
+
},
|
|
506
519
|
resize(width, height, nextTileCount) {
|
|
507
520
|
currentEdgeVertexCount = width;
|
|
508
521
|
currentTileCount = nextTileCount;
|
|
509
522
|
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
510
523
|
const nextAtlasSize = tilesPerRow * width;
|
|
511
|
-
const image =
|
|
524
|
+
const image = tex.image;
|
|
512
525
|
image.width = nextAtlasSize;
|
|
513
526
|
image.height = nextAtlasSize;
|
|
514
|
-
|
|
527
|
+
tex.needsUpdate = true;
|
|
515
528
|
}
|
|
516
529
|
};
|
|
517
530
|
}
|
|
518
531
|
function Texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
519
532
|
let currentEdgeVertexCount = edgeVertexCount;
|
|
520
533
|
let currentTileCount = tileCount;
|
|
521
|
-
const
|
|
534
|
+
const tex = new webgpu.StorageArrayTexture(
|
|
522
535
|
edgeVertexCount,
|
|
523
536
|
edgeVertexCount,
|
|
524
537
|
tileCount
|
|
525
538
|
);
|
|
526
|
-
configureStorageTexture(
|
|
539
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
527
540
|
return {
|
|
528
541
|
backendType: "texture-3d",
|
|
529
542
|
get edgeVertexCount() {
|
|
@@ -532,18 +545,21 @@ function Texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
|
532
545
|
get tileCount() {
|
|
533
546
|
return currentTileCount;
|
|
534
547
|
},
|
|
535
|
-
texture,
|
|
548
|
+
texture: tex,
|
|
536
549
|
uv(ix, iy, _tileIndex) {
|
|
537
550
|
return tsl.vec2(ix.toFloat(), iy.toFloat());
|
|
538
551
|
},
|
|
539
552
|
texel(ix, iy, tileIndex) {
|
|
540
553
|
return tsl.ivec3(ix, iy, tileIndex);
|
|
541
554
|
},
|
|
555
|
+
sample(u, v, tileIndex) {
|
|
556
|
+
return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
|
|
557
|
+
},
|
|
542
558
|
resize(width, height, nextTileCount) {
|
|
543
559
|
currentEdgeVertexCount = width;
|
|
544
560
|
currentTileCount = nextTileCount;
|
|
545
|
-
|
|
546
|
-
|
|
561
|
+
tex.setSize(width, height, nextTileCount);
|
|
562
|
+
tex.needsUpdate = true;
|
|
547
563
|
}
|
|
548
564
|
};
|
|
549
565
|
}
|
|
@@ -552,7 +568,7 @@ function tryGetDeviceLimits(renderer) {
|
|
|
552
568
|
return backend.backend?.device?.limits ?? {};
|
|
553
569
|
}
|
|
554
570
|
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
555
|
-
const filter = options.filter ?? "
|
|
571
|
+
const filter = options.filter ?? "linear";
|
|
556
572
|
const format = options.format ?? "rgba16float";
|
|
557
573
|
const forcedBackend = options.backend;
|
|
558
574
|
if (forcedBackend === "atlas") {
|
|
@@ -593,8 +609,18 @@ function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
|
593
609
|
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
594
610
|
}
|
|
595
611
|
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
596
|
-
const
|
|
597
|
-
return tsl.vec2(
|
|
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);
|
|
598
624
|
}
|
|
599
625
|
function packTerrainFieldSample(height, normalXZ, extra = tsl.float(0)) {
|
|
600
626
|
return tsl.vec4(height, normalXZ.x, normalXZ.y, extra);
|
|
@@ -621,25 +647,6 @@ const createElevation = (tile, uniforms, elevationFn) => {
|
|
|
621
647
|
});
|
|
622
648
|
};
|
|
623
649
|
};
|
|
624
|
-
const readElevationFieldAtPositionLocal = (terrainFieldStorage, edgeVertexCount, positionLocal) => tsl.Fn(() => {
|
|
625
|
-
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
626
|
-
const intEdge = tsl.int(edgeVertexCount);
|
|
627
|
-
const innerSegments = tsl.int(edgeVertexCount).sub(3);
|
|
628
|
-
const fInnerSegments = tsl.float(innerSegments);
|
|
629
|
-
const last = intEdge.sub(tsl.int(1));
|
|
630
|
-
const u = positionLocal.x.add(tsl.float(0.5));
|
|
631
|
-
const v = positionLocal.z.add(tsl.float(0.5));
|
|
632
|
-
const x = u.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
633
|
-
const y = v.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
634
|
-
const xClamped = tsl.min(tsl.max(x, tsl.int(0)), last);
|
|
635
|
-
const yClamped = tsl.min(tsl.max(y, tsl.int(0)), last);
|
|
636
|
-
return loadTerrainFieldElevation(
|
|
637
|
-
terrainFieldStorage,
|
|
638
|
-
xClamped,
|
|
639
|
-
yClamped,
|
|
640
|
-
nodeIndex
|
|
641
|
-
);
|
|
642
|
-
});
|
|
643
650
|
|
|
644
651
|
function createTileCompute(leafStorage, uniforms) {
|
|
645
652
|
const tileLevel = tsl.Fn(([nodeIndex]) => {
|
|
@@ -706,6 +713,10 @@ function createTileCompute(leafStorage, uniforms) {
|
|
|
706
713
|
tileVertexWorldPositionCompute
|
|
707
714
|
};
|
|
708
715
|
}
|
|
716
|
+
function tileLocalToFieldUV(localCoord, innerSegments) {
|
|
717
|
+
const edge = tsl.float(innerSegments).add(tsl.float(3));
|
|
718
|
+
return tsl.float(localCoord).mul(tsl.float(innerSegments)).add(tsl.float(1.5)).div(edge);
|
|
719
|
+
}
|
|
709
720
|
|
|
710
721
|
const rootSize = work.param(256).displayName("rootSize");
|
|
711
722
|
const origin = work.param({
|
|
@@ -724,6 +735,7 @@ const quadtreeUpdate = work.param({
|
|
|
724
735
|
distanceFactor: 1.5
|
|
725
736
|
}).displayName("quadtreeUpdate");
|
|
726
737
|
const surface = work.param(null).displayName("surface");
|
|
738
|
+
const terrainFieldFilter = work.param("linear").displayName("terrainFieldFilter");
|
|
727
739
|
const elevationFn = work.param(() => tsl.float(0));
|
|
728
740
|
|
|
729
741
|
function createLeafStorage(maxNodes) {
|
|
@@ -821,12 +833,12 @@ function ensureChildren(store, parentId) {
|
|
|
821
833
|
return childBase;
|
|
822
834
|
}
|
|
823
835
|
|
|
824
|
-
function nextPow2(n) {
|
|
836
|
+
function nextPow2$1(n) {
|
|
825
837
|
let x = 1;
|
|
826
838
|
while (x < n) x <<= 1;
|
|
827
839
|
return x;
|
|
828
840
|
}
|
|
829
|
-
function mix32(x) {
|
|
841
|
+
function mix32$1(x) {
|
|
830
842
|
x >>>= 0;
|
|
831
843
|
x ^= x >>> 16;
|
|
832
844
|
x = Math.imul(x, 2146121005) >>> 0;
|
|
@@ -835,12 +847,12 @@ function mix32(x) {
|
|
|
835
847
|
x ^= x >>> 16;
|
|
836
848
|
return x >>> 0;
|
|
837
849
|
}
|
|
838
|
-
function hashKey(space, level, x, y) {
|
|
839
|
-
const h = space & 255 ^ (level & 255) << 8 ^ mix32(x) >>> 0 ^ mix32(y) >>> 0;
|
|
840
|
-
return mix32(h);
|
|
850
|
+
function hashKey$1(space, level, x, y) {
|
|
851
|
+
const h = space & 255 ^ (level & 255) << 8 ^ mix32$1(x) >>> 0 ^ mix32$1(y) >>> 0;
|
|
852
|
+
return mix32$1(h);
|
|
841
853
|
}
|
|
842
854
|
function createSpatialIndex(maxEntries) {
|
|
843
|
-
const size = nextPow2(Math.max(2, maxEntries * 2));
|
|
855
|
+
const size = nextPow2$1(Math.max(2, maxEntries * 2));
|
|
844
856
|
return {
|
|
845
857
|
size,
|
|
846
858
|
mask: size - 1,
|
|
@@ -865,7 +877,7 @@ function insertSpatialIndexRaw(index, space, level, x, y, value) {
|
|
|
865
877
|
const l = level & 255;
|
|
866
878
|
const xx = x >>> 0;
|
|
867
879
|
const yy = y >>> 0;
|
|
868
|
-
let slot = hashKey(s, l, xx, yy) & index.mask;
|
|
880
|
+
let slot = hashKey$1(s, l, xx, yy) & index.mask;
|
|
869
881
|
for (let probes = 0; probes < index.size; probes++) {
|
|
870
882
|
if (index.stamp[slot] !== index.stampGen) {
|
|
871
883
|
index.stamp[slot] = index.stampGen;
|
|
@@ -889,7 +901,7 @@ function lookupSpatialIndexRaw(index, space, level, x, y) {
|
|
|
889
901
|
const l = level & 255;
|
|
890
902
|
const xx = x >>> 0;
|
|
891
903
|
const yy = y >>> 0;
|
|
892
|
-
let slot = hashKey(s, l, xx, yy) & index.mask;
|
|
904
|
+
let slot = hashKey$1(s, l, xx, yy) & index.mask;
|
|
893
905
|
for (let probes = 0; probes < index.size; probes++) {
|
|
894
906
|
if (index.stamp[slot] !== index.stampGen) return U32_EMPTY;
|
|
895
907
|
if (index.keysSpace[slot] === s && index.keysLevel[slot] === l && index.keysX[slot] === xx && index.keysY[slot] === yy) {
|
|
@@ -1535,11 +1547,13 @@ const createTerrainFieldTextureTask = work.task(
|
|
|
1535
1547
|
(get, work, { resources }) => {
|
|
1536
1548
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1537
1549
|
const maxNodesValue = get(maxNodes);
|
|
1550
|
+
const filter = get(terrainFieldFilter);
|
|
1538
1551
|
return work(
|
|
1539
1552
|
() => createTerrainFieldStorage(
|
|
1540
1553
|
edgeVertexCount,
|
|
1541
1554
|
maxNodesValue,
|
|
1542
|
-
resources?.renderer
|
|
1555
|
+
resources?.renderer,
|
|
1556
|
+
{ filter }
|
|
1543
1557
|
)
|
|
1544
1558
|
);
|
|
1545
1559
|
}
|
|
@@ -1648,6 +1662,275 @@ function createComputePipelineTasks(leafStageTask) {
|
|
|
1648
1662
|
return { compile, execute };
|
|
1649
1663
|
}
|
|
1650
1664
|
|
|
1665
|
+
const SLOT_STRIDE = 6;
|
|
1666
|
+
function nextPow2(n) {
|
|
1667
|
+
let x = 1;
|
|
1668
|
+
while (x < n) x <<= 1;
|
|
1669
|
+
return x;
|
|
1670
|
+
}
|
|
1671
|
+
function createGpuSpatialIndex(maxEntries) {
|
|
1672
|
+
const size = nextPow2(Math.max(2, maxEntries * 2));
|
|
1673
|
+
const data = new Uint32Array(size * SLOT_STRIDE);
|
|
1674
|
+
const attribute = new webgpu.StorageBufferAttribute(data, SLOT_STRIDE);
|
|
1675
|
+
const node = tsl.storage(attribute, "u32", 1).toReadOnly().setName("gpuSpatialIndex");
|
|
1676
|
+
const stampGen = tsl.uniform(tsl.uint(1)).setName("uGpuSpatialIndexStampGen");
|
|
1677
|
+
return {
|
|
1678
|
+
data,
|
|
1679
|
+
size,
|
|
1680
|
+
mask: size - 1,
|
|
1681
|
+
stampGen,
|
|
1682
|
+
attribute,
|
|
1683
|
+
node
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
function uploadGpuSpatialIndex(gpuIndex, cpuIndex) {
|
|
1687
|
+
if (gpuIndex.size !== cpuIndex.size) {
|
|
1688
|
+
throw new Error(
|
|
1689
|
+
`Spatial index size mismatch (gpu=${gpuIndex.size}, cpu=${cpuIndex.size}).`
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
for (let i = 0; i < cpuIndex.size; i += 1) {
|
|
1693
|
+
const base = i * SLOT_STRIDE;
|
|
1694
|
+
gpuIndex.data[base] = cpuIndex.stamp[i] ?? 0;
|
|
1695
|
+
gpuIndex.data[base + 1] = cpuIndex.keysSpace[i] ?? 0;
|
|
1696
|
+
gpuIndex.data[base + 2] = cpuIndex.keysLevel[i] ?? 0;
|
|
1697
|
+
gpuIndex.data[base + 3] = cpuIndex.keysX[i] ?? 0;
|
|
1698
|
+
gpuIndex.data[base + 4] = cpuIndex.keysY[i] ?? 0;
|
|
1699
|
+
gpuIndex.data[base + 5] = cpuIndex.values[i] ?? 0;
|
|
1700
|
+
}
|
|
1701
|
+
gpuIndex.stampGen.value = cpuIndex.stampGen >>> 0;
|
|
1702
|
+
gpuIndex.attribute.needsUpdate = true;
|
|
1703
|
+
gpuIndex.node.needsUpdate = true;
|
|
1704
|
+
}
|
|
1705
|
+
function readGpuSpatialIndexValue(spatialIndex, slot, fieldOffset) {
|
|
1706
|
+
const offset = tsl.int(slot).mul(tsl.int(SLOT_STRIDE)).add(tsl.int(fieldOffset));
|
|
1707
|
+
return spatialIndex.node.element(offset).toUint();
|
|
1708
|
+
}
|
|
1709
|
+
const mix32 = tsl.Fn(([x]) => {
|
|
1710
|
+
const v = tsl.uint(x).toVar();
|
|
1711
|
+
v.assign(v.bitXor(v.shiftRight(tsl.uint(16))));
|
|
1712
|
+
v.assign(v.mul(tsl.uint(2146121005)));
|
|
1713
|
+
v.assign(v.bitXor(v.shiftRight(tsl.uint(15))));
|
|
1714
|
+
v.assign(v.mul(tsl.uint(2221713035)));
|
|
1715
|
+
v.assign(v.bitXor(v.shiftRight(tsl.uint(16))));
|
|
1716
|
+
return v;
|
|
1717
|
+
});
|
|
1718
|
+
const hashKey = tsl.Fn(([space, level, x, y]) => {
|
|
1719
|
+
const s = tsl.uint(space).bitAnd(tsl.uint(255));
|
|
1720
|
+
const l = tsl.uint(level).bitAnd(tsl.uint(255));
|
|
1721
|
+
const h = s.bitXor(l.shiftLeft(tsl.uint(8))).bitXor(mix32(tsl.uint(x))).bitXor(mix32(tsl.uint(y)));
|
|
1722
|
+
return mix32(h);
|
|
1723
|
+
});
|
|
1724
|
+
const createGpuSpatialLookup = (spatialIndex) => {
|
|
1725
|
+
const slotCount = spatialIndex.size;
|
|
1726
|
+
const mask = tsl.uint(spatialIndex.mask);
|
|
1727
|
+
const stampGen = spatialIndex.stampGen.toUint();
|
|
1728
|
+
const emptyValue = tsl.int(-1);
|
|
1729
|
+
return tsl.Fn(([space, level, x, y]) => {
|
|
1730
|
+
const s = tsl.uint(space).bitAnd(tsl.uint(255));
|
|
1731
|
+
const l = tsl.uint(level).bitAnd(tsl.uint(255));
|
|
1732
|
+
const xx = tsl.uint(x);
|
|
1733
|
+
const yy = tsl.uint(y);
|
|
1734
|
+
const result = emptyValue.toVar();
|
|
1735
|
+
const slot = hashKey(s, l, xx, yy).bitAnd(mask).toVar();
|
|
1736
|
+
const probes = tsl.int(0).toVar();
|
|
1737
|
+
tsl.Loop(slotCount, () => {
|
|
1738
|
+
const stamp = readGpuSpatialIndexValue(spatialIndex, slot, 0);
|
|
1739
|
+
tsl.If(stamp.notEqual(stampGen), () => {
|
|
1740
|
+
tsl.Break();
|
|
1741
|
+
});
|
|
1742
|
+
const ks = readGpuSpatialIndexValue(spatialIndex, slot, 1);
|
|
1743
|
+
const kl = readGpuSpatialIndexValue(spatialIndex, slot, 2);
|
|
1744
|
+
const kx = readGpuSpatialIndexValue(spatialIndex, slot, 3);
|
|
1745
|
+
const ky = readGpuSpatialIndexValue(spatialIndex, slot, 4);
|
|
1746
|
+
tsl.If(
|
|
1747
|
+
ks.equal(s).and(kl.equal(l)).and(kx.equal(xx)).and(ky.equal(yy)),
|
|
1748
|
+
() => {
|
|
1749
|
+
result.assign(tsl.int(readGpuSpatialIndexValue(spatialIndex, slot, 5)));
|
|
1750
|
+
tsl.Break();
|
|
1751
|
+
}
|
|
1752
|
+
);
|
|
1753
|
+
slot.assign(slot.add(tsl.uint(1)).bitAnd(mask));
|
|
1754
|
+
probes.addAssign(1);
|
|
1755
|
+
});
|
|
1756
|
+
return result;
|
|
1757
|
+
});
|
|
1758
|
+
};
|
|
1759
|
+
const createTileIndexFromWorldPosition = (spatialIndex, uniforms, maxLevel) => {
|
|
1760
|
+
const lookup = createGpuSpatialLookup(spatialIndex);
|
|
1761
|
+
const levelCount = Math.max(1, maxLevel + 1);
|
|
1762
|
+
return tsl.Fn(([worldX, worldZ]) => {
|
|
1763
|
+
const rootOrigin = uniforms.uRootOrigin.toVar();
|
|
1764
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
1765
|
+
const halfRoot = rootSize.mul(tsl.float(0.5));
|
|
1766
|
+
const tileIndex = tsl.int(-1).toVar();
|
|
1767
|
+
const tileU = tsl.float(0).toVar();
|
|
1768
|
+
const tileV = tsl.float(0).toVar();
|
|
1769
|
+
const i = tsl.int(0).toVar();
|
|
1770
|
+
tsl.Loop(levelCount, () => {
|
|
1771
|
+
const level = tsl.int(maxLevel).sub(i).toVar();
|
|
1772
|
+
const scale = tsl.pow(tsl.float(2), level.toFloat());
|
|
1773
|
+
const tileSize = rootSize.div(scale);
|
|
1774
|
+
const tileX = worldX.sub(rootOrigin.x).add(halfRoot).div(tileSize).floor().toInt();
|
|
1775
|
+
const tileY = worldZ.sub(rootOrigin.z).add(halfRoot).div(tileSize).floor().toInt();
|
|
1776
|
+
const maybeIndex = lookup(tsl.int(0), level, tileX, tileY).toVar();
|
|
1777
|
+
tsl.If(maybeIndex.greaterThanEqual(tsl.int(0)), () => {
|
|
1778
|
+
const minX = rootOrigin.x.add(tileX.toFloat().mul(tileSize)).sub(halfRoot);
|
|
1779
|
+
const minZ = rootOrigin.z.add(tileY.toFloat().mul(tileSize)).sub(halfRoot);
|
|
1780
|
+
tileIndex.assign(maybeIndex);
|
|
1781
|
+
tileU.assign(worldX.sub(minX).div(tileSize));
|
|
1782
|
+
tileV.assign(worldZ.sub(minZ).div(tileSize));
|
|
1783
|
+
tsl.Break();
|
|
1784
|
+
});
|
|
1785
|
+
i.addAssign(1);
|
|
1786
|
+
});
|
|
1787
|
+
return tsl.vec3(tileIndex.toFloat(), tileU, tileV);
|
|
1788
|
+
});
|
|
1789
|
+
};
|
|
1790
|
+
|
|
1791
|
+
const gpuSpatialIndexStorageTask = work.task((get, work) => {
|
|
1792
|
+
const maxNodesValue = get(maxNodes);
|
|
1793
|
+
return work(() => createGpuSpatialIndex(maxNodesValue));
|
|
1794
|
+
}).displayName("gpuSpatialIndexStorageTask");
|
|
1795
|
+
const gpuSpatialIndexUploadTask = work.task((get, work) => {
|
|
1796
|
+
const quadtreeConfig = get(quadtreeConfigTask);
|
|
1797
|
+
get(quadtreeUpdateTask);
|
|
1798
|
+
const gpuSpatialIndex = get(gpuSpatialIndexStorageTask);
|
|
1799
|
+
return work(() => {
|
|
1800
|
+
uploadGpuSpatialIndex(gpuSpatialIndex, quadtreeConfig.state.leafIndex);
|
|
1801
|
+
return gpuSpatialIndex;
|
|
1802
|
+
});
|
|
1803
|
+
}).displayName("gpuSpatialIndexUploadTask");
|
|
1804
|
+
|
|
1805
|
+
function createTerrainSampleNode(params) {
|
|
1806
|
+
const tileLookup = createTileIndexFromWorldPosition(
|
|
1807
|
+
params.spatialIndex,
|
|
1808
|
+
params.uniforms,
|
|
1809
|
+
maxLevel.get()
|
|
1810
|
+
);
|
|
1811
|
+
return tsl.Fn(([worldX, worldZ]) => {
|
|
1812
|
+
const tileResult = tileLookup(worldX, worldZ).toVar();
|
|
1813
|
+
const tileIndex = tsl.int(tileResult.x).toVar();
|
|
1814
|
+
const safeTileIndex = tileIndex.max(tsl.int(0)).toVar();
|
|
1815
|
+
const u = tileResult.y.toVar();
|
|
1816
|
+
const v = tileResult.z.toVar();
|
|
1817
|
+
const fieldU = tileLocalToFieldUV(
|
|
1818
|
+
u,
|
|
1819
|
+
params.uniforms.uInnerTileSegments
|
|
1820
|
+
).toVar();
|
|
1821
|
+
const fieldV = tileLocalToFieldUV(
|
|
1822
|
+
v,
|
|
1823
|
+
params.uniforms.uInnerTileSegments
|
|
1824
|
+
).toVar();
|
|
1825
|
+
const found = tileIndex.greaterThanEqual(tsl.int(0)).toVar();
|
|
1826
|
+
const sampled = sampleTerrainField(
|
|
1827
|
+
params.terrainFieldStorage,
|
|
1828
|
+
fieldU,
|
|
1829
|
+
fieldV,
|
|
1830
|
+
safeTileIndex
|
|
1831
|
+
).toVar();
|
|
1832
|
+
const nx = sampled.g.toVar();
|
|
1833
|
+
const nz = sampled.b.toVar();
|
|
1834
|
+
const ny = tsl.float(1).sub(nx.mul(nx)).sub(nz.mul(nz)).max(0).sqrt();
|
|
1835
|
+
const valid = found.select(tsl.float(1), tsl.float(0)).toVar();
|
|
1836
|
+
return tsl.vec4(
|
|
1837
|
+
sampled.r.mul(valid),
|
|
1838
|
+
nx.mul(valid),
|
|
1839
|
+
ny.mul(valid),
|
|
1840
|
+
nz.mul(valid)
|
|
1841
|
+
);
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
function createTerrainSampler(params) {
|
|
1845
|
+
const elevationNode = createElevationFunction(params.elevationCallback);
|
|
1846
|
+
const terrainSampleAt = createTerrainSampleNode(params);
|
|
1847
|
+
const evaluateElevationAt = tsl.Fn(([worldX, worldZ]) => {
|
|
1848
|
+
const rootOrigin = params.uniforms.uRootOrigin.toVar();
|
|
1849
|
+
const rootSize = params.uniforms.uRootSize.toVar();
|
|
1850
|
+
const centeredX = worldX.sub(rootOrigin.x);
|
|
1851
|
+
const centeredZ = worldZ.sub(rootOrigin.z);
|
|
1852
|
+
const rootUV = tsl.vec2(
|
|
1853
|
+
centeredX.div(rootSize).add(0.5),
|
|
1854
|
+
centeredZ.div(rootSize).mul(tsl.float(-1)).add(0.5)
|
|
1855
|
+
).toVar();
|
|
1856
|
+
return elevationNode({
|
|
1857
|
+
worldPosition: tsl.vec3(worldX, rootOrigin.y, worldZ),
|
|
1858
|
+
rootSize,
|
|
1859
|
+
rootUV,
|
|
1860
|
+
tileUV: rootUV,
|
|
1861
|
+
tileLevel: tsl.int(0),
|
|
1862
|
+
tileSize: rootSize,
|
|
1863
|
+
tileOriginVec2: tsl.vec2(0, 0),
|
|
1864
|
+
nodeIndex: tsl.int(0)
|
|
1865
|
+
});
|
|
1866
|
+
});
|
|
1867
|
+
const sampleTerrain = tsl.Fn(
|
|
1868
|
+
([worldX, worldZ]) => terrainSampleAt(worldX, worldZ)
|
|
1869
|
+
);
|
|
1870
|
+
const sampleElevation = tsl.Fn(
|
|
1871
|
+
([worldX, worldZ]) => terrainSampleAt(worldX, worldZ).x
|
|
1872
|
+
);
|
|
1873
|
+
const sampleNormal = tsl.Fn(
|
|
1874
|
+
([worldX, worldZ]) => tsl.vec3(
|
|
1875
|
+
terrainSampleAt(worldX, worldZ).y,
|
|
1876
|
+
terrainSampleAt(worldX, worldZ).z,
|
|
1877
|
+
terrainSampleAt(worldX, worldZ).w
|
|
1878
|
+
)
|
|
1879
|
+
);
|
|
1880
|
+
const sampleValidity = tsl.Fn(
|
|
1881
|
+
([worldX, worldZ]) => terrainSampleAt(worldX, worldZ).y.abs().add(terrainSampleAt(worldX, worldZ).z.abs()).add(terrainSampleAt(worldX, worldZ).w.abs()).greaterThan(tsl.float(0)).select(tsl.float(1), tsl.float(0))
|
|
1882
|
+
);
|
|
1883
|
+
const evaluateElevation = tsl.Fn(
|
|
1884
|
+
([worldX, worldZ]) => evaluateElevationAt(worldX, worldZ)
|
|
1885
|
+
);
|
|
1886
|
+
const evaluateNormalNode = tsl.Fn(
|
|
1887
|
+
([worldX, worldZ, epsilon]) => {
|
|
1888
|
+
const eps = epsilon ?? tsl.float(0.1);
|
|
1889
|
+
const elevationScale = params.uniforms.uElevationScale.toVar();
|
|
1890
|
+
const hL = evaluateElevationAt(worldX.sub(eps), worldZ).mul(
|
|
1891
|
+
elevationScale
|
|
1892
|
+
);
|
|
1893
|
+
const hR = evaluateElevationAt(worldX.add(eps), worldZ).mul(
|
|
1894
|
+
elevationScale
|
|
1895
|
+
);
|
|
1896
|
+
const hD = evaluateElevationAt(worldX, worldZ.sub(eps)).mul(
|
|
1897
|
+
elevationScale
|
|
1898
|
+
);
|
|
1899
|
+
const hU = evaluateElevationAt(worldX, worldZ.add(eps)).mul(
|
|
1900
|
+
elevationScale
|
|
1901
|
+
);
|
|
1902
|
+
const inv2eps = tsl.float(0.5).div(eps);
|
|
1903
|
+
const dhdx = hR.sub(hL).mul(inv2eps);
|
|
1904
|
+
const dhdz = hU.sub(hD).mul(inv2eps);
|
|
1905
|
+
return tsl.vec3(dhdx.negate(), tsl.float(1), dhdz.negate()).normalize();
|
|
1906
|
+
}
|
|
1907
|
+
);
|
|
1908
|
+
const evaluateNormal = (worldX, worldZ, epsilon) => evaluateNormalNode(worldX, worldZ, epsilon ?? tsl.float(0.1));
|
|
1909
|
+
return {
|
|
1910
|
+
sampleElevation,
|
|
1911
|
+
sampleNormal,
|
|
1912
|
+
sampleTerrain,
|
|
1913
|
+
sampleValidity,
|
|
1914
|
+
evaluateElevation,
|
|
1915
|
+
evaluateNormal
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
const createTerrainSamplerTask = work.task((get, work) => {
|
|
1920
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1921
|
+
const spatialIndex = get(gpuSpatialIndexStorageTask);
|
|
1922
|
+
const uniforms = get(createUniformsTask);
|
|
1923
|
+
const elevationCallback = get(elevationFn);
|
|
1924
|
+
return work(
|
|
1925
|
+
() => createTerrainSampler({
|
|
1926
|
+
terrainFieldStorage,
|
|
1927
|
+
spatialIndex,
|
|
1928
|
+
uniforms,
|
|
1929
|
+
elevationCallback
|
|
1930
|
+
})
|
|
1931
|
+
);
|
|
1932
|
+
}).displayName("createTerrainSamplerTask");
|
|
1933
|
+
|
|
1651
1934
|
const isSkirtVertex = tsl.Fn(([segments]) => {
|
|
1652
1935
|
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1653
1936
|
const vIndex = tsl.int(tsl.vertexIndex);
|
|
@@ -1691,14 +1974,15 @@ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
|
1691
1974
|
}
|
|
1692
1975
|
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1693
1976
|
if (!terrainFieldStorage) return tsl.float(0);
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1977
|
+
const innerSegs = terrainUniforms.uInnerTileSegments;
|
|
1978
|
+
const u = tileLocalToFieldUV(tsl.positionLocal.x.add(tsl.float(0.5)), innerSegs);
|
|
1979
|
+
const v = tileLocalToFieldUV(tsl.positionLocal.z.add(tsl.float(0.5)), innerSegs);
|
|
1980
|
+
return sampleTerrainFieldElevation(
|
|
1696
1981
|
terrainFieldStorage,
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
);
|
|
1982
|
+
u,
|
|
1983
|
+
v,
|
|
1984
|
+
tsl.int(tsl.instanceIndex)
|
|
1985
|
+
).mul(terrainUniforms.uElevationScale);
|
|
1702
1986
|
}
|
|
1703
1987
|
function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
|
|
1704
1988
|
if (!terrainFieldStorage) return;
|
|
@@ -1707,7 +1991,12 @@ function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
|
|
|
1707
1991
|
const localVertexIndex = tsl.int(tsl.vertexIndex);
|
|
1708
1992
|
const ix = localVertexIndex.mod(edgeVertexCount);
|
|
1709
1993
|
const iy = localVertexIndex.div(edgeVertexCount);
|
|
1710
|
-
const normalXZ = loadTerrainFieldNormal(
|
|
1994
|
+
const normalXZ = loadTerrainFieldNormal(
|
|
1995
|
+
terrainFieldStorage,
|
|
1996
|
+
ix,
|
|
1997
|
+
iy,
|
|
1998
|
+
nodeIndex
|
|
1999
|
+
);
|
|
1711
2000
|
const nx = normalXZ.x;
|
|
1712
2001
|
const nz = normalXZ.y;
|
|
1713
2002
|
const nySq = tsl.float(1).sub(nx.mul(nx)).sub(nz.mul(nz)).max(tsl.float(0));
|
|
@@ -1715,10 +2004,16 @@ function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
|
|
|
1715
2004
|
tsl.normalLocal.assign(tsl.vec3(nx, ny, nz));
|
|
1716
2005
|
}
|
|
1717
2006
|
function createTileWorldPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1718
|
-
const baseWorldPosition = createTileBaseWorldPosition(
|
|
2007
|
+
const baseWorldPosition = createTileBaseWorldPosition(
|
|
2008
|
+
leafStorage,
|
|
2009
|
+
terrainUniforms
|
|
2010
|
+
);
|
|
1719
2011
|
return tsl.Fn(() => {
|
|
1720
2012
|
const base = baseWorldPosition();
|
|
1721
|
-
const yElevation = createTileElevation(
|
|
2013
|
+
const yElevation = createTileElevation(
|
|
2014
|
+
terrainUniforms,
|
|
2015
|
+
terrainFieldStorage
|
|
2016
|
+
);
|
|
1722
2017
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1723
2018
|
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1724
2019
|
const worldY = tsl.select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
@@ -1741,7 +2036,7 @@ const positionNodeTask = work.task((get, work) => {
|
|
|
1741
2036
|
}).displayName("positionNodeTask");
|
|
1742
2037
|
|
|
1743
2038
|
function terrainGraph() {
|
|
1744
|
-
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);
|
|
2039
|
+
return work.graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(gpuSpatialIndexStorageTask).add(gpuSpatialIndexUploadTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(createTerrainFieldTextureTask).add(createTerrainSamplerTask).add(elevationFieldStageTask).add(terrainFieldStageTask).add(compileComputeTask).add(executeComputeTask);
|
|
1745
2040
|
}
|
|
1746
2041
|
const terrainTasks = {
|
|
1747
2042
|
instanceId: instanceIdTask,
|
|
@@ -1750,12 +2045,15 @@ const terrainTasks = {
|
|
|
1750
2045
|
leafStorage: leafStorageTask,
|
|
1751
2046
|
surface: surfaceTask,
|
|
1752
2047
|
leafGpuBuffer: leafGpuBufferTask,
|
|
2048
|
+
gpuSpatialIndexStorage: gpuSpatialIndexStorageTask,
|
|
2049
|
+
gpuSpatialIndexUpload: gpuSpatialIndexUploadTask,
|
|
1753
2050
|
createUniforms: createUniformsTask,
|
|
1754
2051
|
updateUniforms: updateUniformsTask,
|
|
1755
2052
|
positionNode: positionNodeTask,
|
|
1756
2053
|
createElevationFieldContext: createElevationFieldContextTask,
|
|
1757
2054
|
createTileNodes: tileNodesTask,
|
|
1758
2055
|
createTerrainFieldTexture: createTerrainFieldTextureTask,
|
|
2056
|
+
createTerrainSampler: createTerrainSamplerTask,
|
|
1759
2057
|
elevationFieldStage: elevationFieldStageTask,
|
|
1760
2058
|
terrainFieldStage: terrainFieldStageTask,
|
|
1761
2059
|
compileCompute: compileComputeTask,
|
|
@@ -1837,6 +2135,8 @@ exports.createSpatialIndex = createSpatialIndex;
|
|
|
1837
2135
|
exports.createState = createState;
|
|
1838
2136
|
exports.createTerrainFieldStorage = createTerrainFieldStorage;
|
|
1839
2137
|
exports.createTerrainFieldTextureTask = createTerrainFieldTextureTask;
|
|
2138
|
+
exports.createTerrainSampler = createTerrainSampler;
|
|
2139
|
+
exports.createTerrainSamplerTask = createTerrainSamplerTask;
|
|
1840
2140
|
exports.createTerrainUniforms = createTerrainUniforms;
|
|
1841
2141
|
exports.createUniformsTask = createUniformsTask;
|
|
1842
2142
|
exports.deriveNormalZ = deriveNormalZ;
|
|
@@ -1845,6 +2145,8 @@ exports.elevationFn = elevationFn;
|
|
|
1845
2145
|
exports.elevationScale = elevationScale;
|
|
1846
2146
|
exports.executeComputeTask = executeComputeTask;
|
|
1847
2147
|
exports.getDeviceComputeLimits = getDeviceComputeLimits;
|
|
2148
|
+
exports.gpuSpatialIndexStorageTask = gpuSpatialIndexStorageTask;
|
|
2149
|
+
exports.gpuSpatialIndexUploadTask = gpuSpatialIndexUploadTask;
|
|
1848
2150
|
exports.innerTileSegments = innerTileSegments;
|
|
1849
2151
|
exports.instanceIdTask = instanceIdTask;
|
|
1850
2152
|
exports.isSkirtUV = isSkirtUV;
|
|
@@ -1865,10 +2167,14 @@ exports.quadtreeUpdateTask = quadtreeUpdateTask;
|
|
|
1865
2167
|
exports.resetLeafSet = resetLeafSet;
|
|
1866
2168
|
exports.resetSeamTable = resetSeamTable;
|
|
1867
2169
|
exports.rootSize = rootSize;
|
|
2170
|
+
exports.sampleTerrainField = sampleTerrainField;
|
|
2171
|
+
exports.sampleTerrainFieldElevation = sampleTerrainFieldElevation;
|
|
2172
|
+
exports.sampleTerrainFieldNormal = sampleTerrainFieldNormal;
|
|
1868
2173
|
exports.skirtScale = skirtScale;
|
|
1869
2174
|
exports.storeTerrainField = storeTerrainField;
|
|
1870
2175
|
exports.surface = surface;
|
|
1871
2176
|
exports.surfaceTask = surfaceTask;
|
|
2177
|
+
exports.terrainFieldFilter = terrainFieldFilter;
|
|
1872
2178
|
exports.terrainFieldStageTask = terrainFieldStageTask;
|
|
1873
2179
|
exports.terrainGraph = terrainGraph;
|
|
1874
2180
|
exports.terrainTasks = terrainTasks;
|