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