@hello-terrain/three 0.0.0-alpha.13 → 0.0.0-alpha.14
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 +637 -543
- package/dist/index.d.cts +289 -239
- package/dist/index.d.mts +289 -239
- package/dist/index.d.ts +289 -239
- package/dist/index.mjs +626 -546
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BufferGeometry, BufferAttribute, RGBAFormat, ClampToEdgeWrapping, HalfFloatType, FloatType, LinearFilter, NearestFilter, Vector3 } from 'three';
|
|
2
|
-
import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageTexture, StorageArrayTexture,
|
|
2
|
+
import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageBufferAttribute, StorageTexture, StorageArrayTexture, Vector3 as Vector3$1 } from 'three/webgpu';
|
|
3
3
|
import { param, task, graph } from '@hello-terrain/work';
|
|
4
|
-
import { float, uniform, Fn, globalId, int, vec2, uint, If, vec3,
|
|
4
|
+
import { float, uniform, Fn, globalId, int, vec2, uint, If, storage, vec3, cross, vertexIndex, uv, max, textureStore, uvec3, vec4, texture, ivec2, ivec3, textureLoad, pow, instanceIndex, positionLocal, select, normalLocal, Loop, Break, sin, cos, bool, workgroupArray, localId, workgroupId, min, workgroupBarrier, remap, dot as dot$1, 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 {
|
|
@@ -359,6 +359,7 @@ function compileComputePipeline(stages, width, options) {
|
|
|
359
359
|
WORKGROUP_X,
|
|
360
360
|
WORKGROUP_Y
|
|
361
361
|
];
|
|
362
|
+
const midPipelineExecute = options?.midPipelineExecute;
|
|
362
363
|
const uInstanceCount = uniform(0, "uint").setName("uInstanceCount");
|
|
363
364
|
const stagedKernelCache = /* @__PURE__ */ new Map();
|
|
364
365
|
function clampWorkgroupToLimits(requested, limits) {
|
|
@@ -419,283 +420,16 @@ function compileComputePipeline(stages, width, options) {
|
|
|
419
420
|
}
|
|
420
421
|
const dispatchX = Math.ceil(width / workgroupX);
|
|
421
422
|
const dispatchY = Math.ceil(width / workgroupY);
|
|
422
|
-
for (
|
|
423
|
-
renderer.compute(
|
|
423
|
+
for (let stageIndex = 0; stageIndex < stagedKernels.length; stageIndex += 1) {
|
|
424
|
+
renderer.compute(stagedKernels[stageIndex], [dispatchX, dispatchY, instanceCount]);
|
|
425
|
+
if (midPipelineExecute && stagedKernels.length > 1 && stageIndex === stagedKernels.length - 2) {
|
|
426
|
+
midPipelineExecute(renderer, instanceCount);
|
|
427
|
+
}
|
|
424
428
|
}
|
|
425
429
|
}
|
|
426
430
|
return { execute };
|
|
427
431
|
}
|
|
428
432
|
|
|
429
|
-
function resolveType(format) {
|
|
430
|
-
return format === "rgba16float" ? HalfFloatType : FloatType;
|
|
431
|
-
}
|
|
432
|
-
function resolveFilter(mode) {
|
|
433
|
-
return mode === "linear" ? LinearFilter : NearestFilter;
|
|
434
|
-
}
|
|
435
|
-
function configureStorageTexture(texture2, format, filter) {
|
|
436
|
-
texture2.format = RGBAFormat;
|
|
437
|
-
texture2.type = resolveType(format);
|
|
438
|
-
texture2.magFilter = resolveFilter(filter);
|
|
439
|
-
texture2.minFilter = resolveFilter(filter);
|
|
440
|
-
texture2.wrapS = ClampToEdgeWrapping;
|
|
441
|
-
texture2.wrapT = ClampToEdgeWrapping;
|
|
442
|
-
texture2.generateMipmaps = false;
|
|
443
|
-
texture2.needsUpdate = true;
|
|
444
|
-
}
|
|
445
|
-
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
446
|
-
let currentEdgeVertexCount = edgeVertexCount;
|
|
447
|
-
let currentTileCount = tileCount;
|
|
448
|
-
const tex = new StorageArrayTexture(
|
|
449
|
-
edgeVertexCount,
|
|
450
|
-
edgeVertexCount,
|
|
451
|
-
tileCount
|
|
452
|
-
);
|
|
453
|
-
tex.name = "terrainField";
|
|
454
|
-
configureStorageTexture(tex, options.format, options.filter);
|
|
455
|
-
return {
|
|
456
|
-
backendType: "array-texture",
|
|
457
|
-
get edgeVertexCount() {
|
|
458
|
-
return currentEdgeVertexCount;
|
|
459
|
-
},
|
|
460
|
-
get tileCount() {
|
|
461
|
-
return currentTileCount;
|
|
462
|
-
},
|
|
463
|
-
texture: tex,
|
|
464
|
-
uv(ix, iy, _tileIndex) {
|
|
465
|
-
return vec2(ix.toFloat(), iy.toFloat());
|
|
466
|
-
},
|
|
467
|
-
texel(ix, iy, tileIndex) {
|
|
468
|
-
return ivec3(ix, iy, tileIndex);
|
|
469
|
-
},
|
|
470
|
-
sample(u, v, tileIndex) {
|
|
471
|
-
return texture(tex, vec2(u, v)).depth(int(tileIndex));
|
|
472
|
-
},
|
|
473
|
-
resize(width, height, nextTileCount) {
|
|
474
|
-
currentEdgeVertexCount = width;
|
|
475
|
-
currentTileCount = nextTileCount;
|
|
476
|
-
tex.setSize(width, height, nextTileCount);
|
|
477
|
-
tex.needsUpdate = true;
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
|
|
482
|
-
const tilesPerRowNode = int(tilesPerRow);
|
|
483
|
-
const edge = int(edgeVertexCount);
|
|
484
|
-
const tile = int(tileIndex);
|
|
485
|
-
const col = tile.mod(tilesPerRowNode);
|
|
486
|
-
const row = tile.div(tilesPerRowNode);
|
|
487
|
-
const atlasX = col.mul(edge).add(int(ix));
|
|
488
|
-
const atlasY = row.mul(edge).add(int(iy));
|
|
489
|
-
return { atlasX, atlasY };
|
|
490
|
-
}
|
|
491
|
-
function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
492
|
-
let currentEdgeVertexCount = edgeVertexCount;
|
|
493
|
-
let currentTileCount = tileCount;
|
|
494
|
-
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
495
|
-
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
496
|
-
const tex = new StorageTexture(atlasSize, atlasSize);
|
|
497
|
-
tex.name = "terrainFieldAtlas";
|
|
498
|
-
configureStorageTexture(tex, options.format, options.filter);
|
|
499
|
-
return {
|
|
500
|
-
backendType: "atlas",
|
|
501
|
-
get edgeVertexCount() {
|
|
502
|
-
return currentEdgeVertexCount;
|
|
503
|
-
},
|
|
504
|
-
get tileCount() {
|
|
505
|
-
return currentTileCount;
|
|
506
|
-
},
|
|
507
|
-
texture: tex,
|
|
508
|
-
uv(ix, iy, tileIndex) {
|
|
509
|
-
const { atlasX, atlasY } = atlasCoord(
|
|
510
|
-
tilesPerRow,
|
|
511
|
-
currentEdgeVertexCount,
|
|
512
|
-
ix,
|
|
513
|
-
iy,
|
|
514
|
-
tileIndex
|
|
515
|
-
);
|
|
516
|
-
const currentAtlasSize = float(tilesPerRow * currentEdgeVertexCount);
|
|
517
|
-
return vec2(
|
|
518
|
-
atlasX.toFloat().add(0.5).div(currentAtlasSize),
|
|
519
|
-
atlasY.toFloat().add(0.5).div(currentAtlasSize)
|
|
520
|
-
);
|
|
521
|
-
},
|
|
522
|
-
texel(ix, iy, tileIndex) {
|
|
523
|
-
const { atlasX, atlasY } = atlasCoord(
|
|
524
|
-
tilesPerRow,
|
|
525
|
-
currentEdgeVertexCount,
|
|
526
|
-
ix,
|
|
527
|
-
iy,
|
|
528
|
-
tileIndex
|
|
529
|
-
);
|
|
530
|
-
return ivec2(atlasX, atlasY);
|
|
531
|
-
},
|
|
532
|
-
sample(u, v, tileIndex) {
|
|
533
|
-
const tile = int(tileIndex);
|
|
534
|
-
const tilesPerRowNode = int(tilesPerRow);
|
|
535
|
-
const col = tile.mod(tilesPerRowNode);
|
|
536
|
-
const row = tile.div(tilesPerRowNode);
|
|
537
|
-
const invTilesPerRow = float(1 / tilesPerRow);
|
|
538
|
-
const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
|
|
539
|
-
const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
|
|
540
|
-
return texture(tex, vec2(atlasU, atlasV));
|
|
541
|
-
},
|
|
542
|
-
resize(width, height, nextTileCount) {
|
|
543
|
-
currentEdgeVertexCount = width;
|
|
544
|
-
currentTileCount = nextTileCount;
|
|
545
|
-
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
546
|
-
const nextAtlasSize = tilesPerRow * width;
|
|
547
|
-
const image = tex.image;
|
|
548
|
-
image.width = nextAtlasSize;
|
|
549
|
-
image.height = nextAtlasSize;
|
|
550
|
-
tex.needsUpdate = true;
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
function texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
555
|
-
const storage = ArrayTextureBackend(edgeVertexCount, tileCount, options);
|
|
556
|
-
return { ...storage, backendType: "texture-3d" };
|
|
557
|
-
}
|
|
558
|
-
function tryGetDeviceLimits(renderer) {
|
|
559
|
-
const backend = renderer;
|
|
560
|
-
return backend.backend?.device?.limits ?? {};
|
|
561
|
-
}
|
|
562
|
-
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
563
|
-
const filter = options.filter ?? "linear";
|
|
564
|
-
const format = options.format ?? "rgba16float";
|
|
565
|
-
const forcedBackend = options.backend;
|
|
566
|
-
if (forcedBackend === "atlas") {
|
|
567
|
-
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
568
|
-
}
|
|
569
|
-
if (forcedBackend === "texture-3d") {
|
|
570
|
-
return texture3DBackend(edgeVertexCount, tileCount, { filter, format });
|
|
571
|
-
}
|
|
572
|
-
if (forcedBackend === "array-texture") {
|
|
573
|
-
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
574
|
-
}
|
|
575
|
-
const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
|
|
576
|
-
const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
|
|
577
|
-
if (tileCount > maxLayers) {
|
|
578
|
-
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
579
|
-
}
|
|
580
|
-
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
581
|
-
}
|
|
582
|
-
function storeTerrainField(storage, ix, iy, tileIndex, value) {
|
|
583
|
-
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
584
|
-
return textureStore(
|
|
585
|
-
storage.texture,
|
|
586
|
-
uvec3(int(ix), int(iy), int(tileIndex)),
|
|
587
|
-
value
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
return textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
|
|
591
|
-
}
|
|
592
|
-
function loadTerrainField(storage, ix, iy, tileIndex) {
|
|
593
|
-
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
594
|
-
return textureLoad(storage.texture, ivec2(int(ix), int(iy)), int(0)).depth(
|
|
595
|
-
int(tileIndex)
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
return textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), int(0));
|
|
599
|
-
}
|
|
600
|
-
function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
601
|
-
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
602
|
-
}
|
|
603
|
-
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
604
|
-
const raw = loadTerrainField(storage, ix, iy, tileIndex);
|
|
605
|
-
return vec3(raw.g, raw.b, raw.a);
|
|
606
|
-
}
|
|
607
|
-
function sampleTerrainField(storage, u, v, tileIndex) {
|
|
608
|
-
return storage.sample(u, v, tileIndex);
|
|
609
|
-
}
|
|
610
|
-
function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
|
|
611
|
-
return sampleTerrainField(storage, u, v, tileIndex).r;
|
|
612
|
-
}
|
|
613
|
-
function packTerrainFieldSample(height, normal) {
|
|
614
|
-
return vec4(height, normal.x, normal.y, normal.z);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const createElevation = (tile, uniforms, elevationFn) => {
|
|
618
|
-
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
619
|
-
const ix = int(localCoordinates.x);
|
|
620
|
-
const iy = int(localCoordinates.y);
|
|
621
|
-
const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(int(3));
|
|
622
|
-
const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
|
|
623
|
-
const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
|
|
624
|
-
const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
|
|
625
|
-
const rootSize = uniforms.uRootSize.toVar();
|
|
626
|
-
return elevationFn({
|
|
627
|
-
worldPosition,
|
|
628
|
-
rootSize,
|
|
629
|
-
rootUV,
|
|
630
|
-
tileOriginVec2: tile.tileOriginVec2(nodeIndex),
|
|
631
|
-
tileSize: tile.tileSize(nodeIndex),
|
|
632
|
-
tileLevel: tile.tileLevel(nodeIndex),
|
|
633
|
-
nodeIndex: int(nodeIndex),
|
|
634
|
-
tileUV
|
|
635
|
-
});
|
|
636
|
-
};
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const HALF_PI = Math.PI * 0.5;
|
|
640
|
-
const FIELD_INNER_TEXEL_OFFSET = 1.5;
|
|
641
|
-
const FIELD_EDGE_EXTRA_TEXELS = 3;
|
|
642
|
-
function sphereTileArcLength(radius, levelDivisor) {
|
|
643
|
-
return radius * HALF_PI / levelDivisor;
|
|
644
|
-
}
|
|
645
|
-
function decodeLeafTile(leafStorage, nodeIndex) {
|
|
646
|
-
const nodeOffset = int(nodeIndex).mul(int(4));
|
|
647
|
-
return {
|
|
648
|
-
level: leafStorage.node.element(nodeOffset).toInt(),
|
|
649
|
-
x: leafStorage.node.element(nodeOffset.add(int(1))).toFloat(),
|
|
650
|
-
y: leafStorage.node.element(nodeOffset.add(int(2))).toFloat(),
|
|
651
|
-
face: leafStorage.node.element(nodeOffset.add(int(3))).toInt()
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
function faceUVFromTileLocal(tile, localU, localV, baseU = float(1), baseV = float(1)) {
|
|
655
|
-
const levelScale = pow(float(2), tile.level.toFloat());
|
|
656
|
-
const nU = baseU.mul(levelScale);
|
|
657
|
-
const nV = baseV.mul(levelScale);
|
|
658
|
-
return vec2(tile.x.add(localU).div(nU), tile.y.add(localV).div(nV));
|
|
659
|
-
}
|
|
660
|
-
function createTileCompute(leafStorage, uniforms, projection) {
|
|
661
|
-
const baseU = float(projection.baseResolution?.u ?? 1);
|
|
662
|
-
const baseV = float(projection.baseResolution?.v ?? 1);
|
|
663
|
-
const tileLevel = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).level);
|
|
664
|
-
const tileFace = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).face);
|
|
665
|
-
const tileOriginVec2 = Fn(([nodeIndex]) => {
|
|
666
|
-
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
667
|
-
return vec2(tile.x, tile.y);
|
|
668
|
-
});
|
|
669
|
-
const tileFaceUV = Fn(([nodeIndex, ix, iy]) => {
|
|
670
|
-
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
671
|
-
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
672
|
-
const localU = int(ix).toFloat().sub(float(1)).div(fInnerSegments);
|
|
673
|
-
const localV = int(iy).toFloat().sub(float(1)).div(fInnerSegments);
|
|
674
|
-
return faceUVFromTileLocal(tile, localU, localV, baseU, baseV);
|
|
675
|
-
});
|
|
676
|
-
const shared = {
|
|
677
|
-
tileLevel: (nodeIndex) => tileLevel(nodeIndex),
|
|
678
|
-
tileFace: (nodeIndex) => tileFace(nodeIndex),
|
|
679
|
-
tileOriginVec2: (nodeIndex) => tileOriginVec2(nodeIndex),
|
|
680
|
-
tileFaceUV: (nodeIndex, ix, iy) => tileFaceUV(nodeIndex, ix, iy)
|
|
681
|
-
};
|
|
682
|
-
const parts = projection.gpu.createTileComputeParts({ leafStorage, uniforms, shared });
|
|
683
|
-
return {
|
|
684
|
-
...shared,
|
|
685
|
-
tileSize: parts.tileSize,
|
|
686
|
-
rootUVCompute: parts.rootUV,
|
|
687
|
-
tileVertexWorldPositionCompute: parts.tileVertexWorldPosition
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
function tileLocalToFieldUV(localCoord, innerSegments) {
|
|
691
|
-
const edge = float(innerSegments).add(float(FIELD_EDGE_EXTRA_TEXELS));
|
|
692
|
-
return float(localCoord).mul(float(innerSegments)).add(float(FIELD_INNER_TEXEL_OFFSET)).div(edge);
|
|
693
|
-
}
|
|
694
|
-
function tileLocalToFieldUVNumber(localCoord, innerSegments) {
|
|
695
|
-
const edge = innerSegments + FIELD_EDGE_EXTRA_TEXELS;
|
|
696
|
-
return (localCoord * innerSegments + FIELD_INNER_TEXEL_OFFSET) / edge;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
433
|
function createLeafStorage(maxNodes) {
|
|
700
434
|
const data = new Int32Array(maxNodes * 4);
|
|
701
435
|
const attribute = new StorageBufferAttribute(data, 4);
|
|
@@ -1176,97 +910,373 @@ function buildSeams2to1(topology, leaves, outSeams, outIndex) {
|
|
|
1176
910
|
if (j !== U32_EMPTY) neighbors[outOffset + 1] = j;
|
|
1177
911
|
}
|
|
1178
912
|
}
|
|
1179
|
-
return outSeams;
|
|
913
|
+
return outSeams;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function createFlatNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
917
|
+
return Fn(
|
|
918
|
+
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
919
|
+
const iEdge = int(edgeVertexCount);
|
|
920
|
+
const verticesPerNode = iEdge.mul(iEdge);
|
|
921
|
+
const baseOffset = int(nodeIndex).mul(verticesPerNode);
|
|
922
|
+
const xLeft = int(ix).sub(int(1));
|
|
923
|
+
const xRight = int(ix).add(int(1));
|
|
924
|
+
const yUp = int(iy).sub(int(1));
|
|
925
|
+
const yDown = int(iy).add(int(1));
|
|
926
|
+
const hLeft = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
927
|
+
const hRight = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
928
|
+
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
929
|
+
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
930
|
+
const innerSegments = float(iEdge).sub(float(3));
|
|
931
|
+
const stepWorld = tileSize.div(innerSegments);
|
|
932
|
+
const inv2Step = float(0.5).div(stepWorld);
|
|
933
|
+
const dhdx = float(hRight).sub(float(hLeft)).mul(inv2Step);
|
|
934
|
+
const dhdz = float(hDown).sub(float(hUp)).mul(inv2Step);
|
|
935
|
+
return vec3(dhdx.negate(), float(1), dhdz.negate()).normalize();
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function createDisplacedSurfaceNormalFromElevationField(elevationFieldNode, edgeVertexCount, makeSurfaceFns) {
|
|
940
|
+
return Fn(([nodeIndex, ix, iy, elevationScale]) => {
|
|
941
|
+
const iEdge = int(edgeVertexCount);
|
|
942
|
+
const verticesPerNode = iEdge.mul(iEdge);
|
|
943
|
+
const baseOffset = int(nodeIndex).mul(verticesPerNode);
|
|
944
|
+
const xLeft = int(ix).sub(int(1));
|
|
945
|
+
const xRight = int(ix).add(int(1));
|
|
946
|
+
const yUp = int(iy).sub(int(1));
|
|
947
|
+
const yDown = int(iy).add(int(1));
|
|
948
|
+
const heightAt = (gx, gy) => elevationFieldNode.element(baseOffset.add(gy.mul(iEdge).add(gx))).mul(elevationScale);
|
|
949
|
+
const { positionAt, dirAt } = makeSurfaceFns(nodeIndex);
|
|
950
|
+
const pLeft = positionAt(xLeft, int(iy), heightAt(xLeft, int(iy)));
|
|
951
|
+
const pRight = positionAt(xRight, int(iy), heightAt(xRight, int(iy)));
|
|
952
|
+
const pUp = positionAt(int(ix), yUp, heightAt(int(ix), yUp));
|
|
953
|
+
const pDown = positionAt(int(ix), yDown, heightAt(int(ix), yDown));
|
|
954
|
+
const tangentU = pRight.sub(pLeft);
|
|
955
|
+
const tangentV = pDown.sub(pUp);
|
|
956
|
+
const normal = cross(tangentU, tangentV).normalize();
|
|
957
|
+
const dir = dirAt(int(ix), int(iy));
|
|
958
|
+
return normal.mul(normal.dot(dir).sign());
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const isSkirtVertex = Fn(([segments]) => {
|
|
963
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
964
|
+
const vIndex = int(vertexIndex);
|
|
965
|
+
const segmentEdges = int(segmentsNode.add(3));
|
|
966
|
+
const vx = vIndex.mod(segmentEdges);
|
|
967
|
+
const vy = vIndex.div(segmentEdges);
|
|
968
|
+
const last = segmentEdges.sub(int(1));
|
|
969
|
+
return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
|
|
970
|
+
});
|
|
971
|
+
const isSkirtUV = Fn(([segments]) => {
|
|
972
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
973
|
+
const ux = uv().x;
|
|
974
|
+
const uy = uv().y;
|
|
975
|
+
const segmentCount = segmentsNode.add(2);
|
|
976
|
+
const segmentStep = float(1).div(segmentCount);
|
|
977
|
+
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
978
|
+
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
979
|
+
return innerX.and(innerY).not();
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const TILE_BOUNDS_FLOATS_PER_TILE = 4;
|
|
983
|
+
const TILE_BOUNDS_LOD_MIN_OFFSET = 0;
|
|
984
|
+
const TILE_BOUNDS_LOD_MAX_OFFSET = 1;
|
|
985
|
+
const TILE_BOUNDS_PACK_MIN_OFFSET = 2;
|
|
986
|
+
const TILE_BOUNDS_PACK_MAX_OFFSET = 3;
|
|
987
|
+
const TERRAIN_FIELD_PACK_EPSILON = 1e-4;
|
|
988
|
+
function resolveType(format) {
|
|
989
|
+
return format === "rgba16float" ? HalfFloatType : FloatType;
|
|
990
|
+
}
|
|
991
|
+
function resolveFilter(mode) {
|
|
992
|
+
return mode === "linear" ? LinearFilter : NearestFilter;
|
|
993
|
+
}
|
|
994
|
+
function configureStorageTexture(texture2, format, filter) {
|
|
995
|
+
texture2.format = RGBAFormat;
|
|
996
|
+
texture2.type = resolveType(format);
|
|
997
|
+
texture2.magFilter = resolveFilter(filter);
|
|
998
|
+
texture2.minFilter = resolveFilter(filter);
|
|
999
|
+
texture2.wrapS = ClampToEdgeWrapping;
|
|
1000
|
+
texture2.wrapT = ClampToEdgeWrapping;
|
|
1001
|
+
texture2.generateMipmaps = false;
|
|
1002
|
+
texture2.needsUpdate = true;
|
|
1003
|
+
}
|
|
1004
|
+
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
1005
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
1006
|
+
let currentTileCount = tileCount;
|
|
1007
|
+
const tex = new StorageArrayTexture(
|
|
1008
|
+
edgeVertexCount,
|
|
1009
|
+
edgeVertexCount,
|
|
1010
|
+
tileCount
|
|
1011
|
+
);
|
|
1012
|
+
tex.name = "terrainField";
|
|
1013
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
1014
|
+
return {
|
|
1015
|
+
backendType: "array-texture",
|
|
1016
|
+
get edgeVertexCount() {
|
|
1017
|
+
return currentEdgeVertexCount;
|
|
1018
|
+
},
|
|
1019
|
+
get tileCount() {
|
|
1020
|
+
return currentTileCount;
|
|
1021
|
+
},
|
|
1022
|
+
texture: tex,
|
|
1023
|
+
uv(ix, iy, _tileIndex) {
|
|
1024
|
+
return vec2(ix.toFloat(), iy.toFloat());
|
|
1025
|
+
},
|
|
1026
|
+
texel(ix, iy, tileIndex) {
|
|
1027
|
+
return ivec3(ix, iy, tileIndex);
|
|
1028
|
+
},
|
|
1029
|
+
sample(u, v, tileIndex) {
|
|
1030
|
+
return texture(tex, vec2(u, v)).depth(int(tileIndex));
|
|
1031
|
+
},
|
|
1032
|
+
resize(width, height, nextTileCount) {
|
|
1033
|
+
currentEdgeVertexCount = width;
|
|
1034
|
+
currentTileCount = nextTileCount;
|
|
1035
|
+
tex.setSize(width, height, nextTileCount);
|
|
1036
|
+
tex.needsUpdate = true;
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
|
|
1041
|
+
const tilesPerRowNode = int(tilesPerRow);
|
|
1042
|
+
const edge = int(edgeVertexCount);
|
|
1043
|
+
const tile = int(tileIndex);
|
|
1044
|
+
const col = tile.mod(tilesPerRowNode);
|
|
1045
|
+
const row = tile.div(tilesPerRowNode);
|
|
1046
|
+
const atlasX = col.mul(edge).add(int(ix));
|
|
1047
|
+
const atlasY = row.mul(edge).add(int(iy));
|
|
1048
|
+
return { atlasX, atlasY };
|
|
1049
|
+
}
|
|
1050
|
+
function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
1051
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
1052
|
+
let currentTileCount = tileCount;
|
|
1053
|
+
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
1054
|
+
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
1055
|
+
const tex = new StorageTexture(atlasSize, atlasSize);
|
|
1056
|
+
tex.name = "terrainFieldAtlas";
|
|
1057
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
1058
|
+
return {
|
|
1059
|
+
backendType: "atlas",
|
|
1060
|
+
get edgeVertexCount() {
|
|
1061
|
+
return currentEdgeVertexCount;
|
|
1062
|
+
},
|
|
1063
|
+
get tileCount() {
|
|
1064
|
+
return currentTileCount;
|
|
1065
|
+
},
|
|
1066
|
+
texture: tex,
|
|
1067
|
+
uv(ix, iy, tileIndex) {
|
|
1068
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
1069
|
+
tilesPerRow,
|
|
1070
|
+
currentEdgeVertexCount,
|
|
1071
|
+
ix,
|
|
1072
|
+
iy,
|
|
1073
|
+
tileIndex
|
|
1074
|
+
);
|
|
1075
|
+
const currentAtlasSize = float(tilesPerRow * currentEdgeVertexCount);
|
|
1076
|
+
return vec2(
|
|
1077
|
+
atlasX.toFloat().add(0.5).div(currentAtlasSize),
|
|
1078
|
+
atlasY.toFloat().add(0.5).div(currentAtlasSize)
|
|
1079
|
+
);
|
|
1080
|
+
},
|
|
1081
|
+
texel(ix, iy, tileIndex) {
|
|
1082
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
1083
|
+
tilesPerRow,
|
|
1084
|
+
currentEdgeVertexCount,
|
|
1085
|
+
ix,
|
|
1086
|
+
iy,
|
|
1087
|
+
tileIndex
|
|
1088
|
+
);
|
|
1089
|
+
return ivec2(atlasX, atlasY);
|
|
1090
|
+
},
|
|
1091
|
+
sample(u, v, tileIndex) {
|
|
1092
|
+
const tile = int(tileIndex);
|
|
1093
|
+
const tilesPerRowNode = int(tilesPerRow);
|
|
1094
|
+
const col = tile.mod(tilesPerRowNode);
|
|
1095
|
+
const row = tile.div(tilesPerRowNode);
|
|
1096
|
+
const invTilesPerRow = float(1 / tilesPerRow);
|
|
1097
|
+
const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
|
|
1098
|
+
const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
|
|
1099
|
+
return texture(tex, vec2(atlasU, atlasV));
|
|
1100
|
+
},
|
|
1101
|
+
resize(width, height, nextTileCount) {
|
|
1102
|
+
currentEdgeVertexCount = width;
|
|
1103
|
+
currentTileCount = nextTileCount;
|
|
1104
|
+
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
1105
|
+
const nextAtlasSize = tilesPerRow * width;
|
|
1106
|
+
const image = tex.image;
|
|
1107
|
+
image.width = nextAtlasSize;
|
|
1108
|
+
image.height = nextAtlasSize;
|
|
1109
|
+
tex.needsUpdate = true;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
1114
|
+
const storage = ArrayTextureBackend(edgeVertexCount, tileCount, options);
|
|
1115
|
+
return { ...storage, backendType: "texture-3d" };
|
|
1116
|
+
}
|
|
1117
|
+
function tryGetDeviceLimits(renderer) {
|
|
1118
|
+
const backend = renderer;
|
|
1119
|
+
return backend.backend?.device?.limits ?? {};
|
|
1120
|
+
}
|
|
1121
|
+
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
1122
|
+
const filter = options.filter ?? "linear";
|
|
1123
|
+
const format = options.format ?? "rgba16float";
|
|
1124
|
+
const forcedBackend = options.backend;
|
|
1125
|
+
if (forcedBackend === "atlas") {
|
|
1126
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1127
|
+
}
|
|
1128
|
+
if (forcedBackend === "texture-3d") {
|
|
1129
|
+
return texture3DBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1130
|
+
}
|
|
1131
|
+
if (forcedBackend === "array-texture") {
|
|
1132
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1133
|
+
}
|
|
1134
|
+
const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
|
|
1135
|
+
const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
|
|
1136
|
+
if (tileCount > maxLayers) {
|
|
1137
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1138
|
+
}
|
|
1139
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1140
|
+
}
|
|
1141
|
+
function storeTerrainField(storage, ix, iy, tileIndex, value) {
|
|
1142
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
1143
|
+
return textureStore(
|
|
1144
|
+
storage.texture,
|
|
1145
|
+
uvec3(int(ix), int(iy), int(tileIndex)),
|
|
1146
|
+
value
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
return textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
|
|
1150
|
+
}
|
|
1151
|
+
function loadTerrainField(storage, ix, iy, tileIndex) {
|
|
1152
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
1153
|
+
return textureLoad(storage.texture, ivec2(int(ix), int(iy)), int(0)).depth(
|
|
1154
|
+
int(tileIndex)
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
return textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), int(0));
|
|
1158
|
+
}
|
|
1159
|
+
function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
1160
|
+
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
1161
|
+
}
|
|
1162
|
+
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
1163
|
+
const raw = loadTerrainField(storage, ix, iy, tileIndex);
|
|
1164
|
+
return vec3(raw.g, raw.b, raw.a);
|
|
1165
|
+
}
|
|
1166
|
+
function sampleTerrainField(storage, u, v, tileIndex) {
|
|
1167
|
+
return storage.sample(u, v, tileIndex);
|
|
1168
|
+
}
|
|
1169
|
+
function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
|
|
1170
|
+
return sampleTerrainField(storage, u, v, tileIndex).r;
|
|
1171
|
+
}
|
|
1172
|
+
function packTerrainFieldSample(height, normal) {
|
|
1173
|
+
return vec4(height, normal.x, normal.y, normal.z);
|
|
1174
|
+
}
|
|
1175
|
+
function loadTilePackBounds(boundsNode, tileIndex) {
|
|
1176
|
+
const base = int(tileIndex).mul(int(TILE_BOUNDS_FLOATS_PER_TILE));
|
|
1177
|
+
return {
|
|
1178
|
+
packMin: boundsNode.element(base.add(int(TILE_BOUNDS_PACK_MIN_OFFSET))),
|
|
1179
|
+
packMax: boundsNode.element(base.add(int(TILE_BOUNDS_PACK_MAX_OFFSET)))
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function packNormalizedTerrainFieldSample(height, normal, packMin, packMax) {
|
|
1183
|
+
const span = max(packMax.sub(packMin), float(TERRAIN_FIELD_PACK_EPSILON));
|
|
1184
|
+
const normalized = height.sub(packMin).div(span);
|
|
1185
|
+
return vec4(normalized, normal.x, normal.y, normal.z);
|
|
1186
|
+
}
|
|
1187
|
+
function denormalizeTerrainFieldElevation(normalized, packMin, packMax) {
|
|
1188
|
+
const span = max(packMax.sub(packMin), float(TERRAIN_FIELD_PACK_EPSILON));
|
|
1189
|
+
return packMin.add(normalized.mul(span));
|
|
1180
1190
|
}
|
|
1181
1191
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
const baseOffset = int(nodeIndex).mul(verticesPerNode);
|
|
1188
|
-
const xLeft = int(ix).sub(int(1));
|
|
1189
|
-
const xRight = int(ix).add(int(1));
|
|
1190
|
-
const yUp = int(iy).sub(int(1));
|
|
1191
|
-
const yDown = int(iy).add(int(1));
|
|
1192
|
-
const hLeft = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
1193
|
-
const hRight = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
1194
|
-
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
1195
|
-
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
1196
|
-
const innerSegments = float(iEdge).sub(float(3));
|
|
1197
|
-
const stepWorld = tileSize.div(innerSegments);
|
|
1198
|
-
const inv2Step = float(0.5).div(stepWorld);
|
|
1199
|
-
const dhdx = float(hRight).sub(float(hLeft)).mul(inv2Step);
|
|
1200
|
-
const dhdz = float(hDown).sub(float(hUp)).mul(inv2Step);
|
|
1201
|
-
return vec3(dhdx.negate(), float(1), dhdz.negate()).normalize();
|
|
1202
|
-
}
|
|
1203
|
-
);
|
|
1192
|
+
const HALF_PI = Math.PI * 0.5;
|
|
1193
|
+
const FIELD_INNER_TEXEL_OFFSET = 1.5;
|
|
1194
|
+
const FIELD_EDGE_EXTRA_TEXELS = 3;
|
|
1195
|
+
function sphereTileArcLength(radius, levelDivisor) {
|
|
1196
|
+
return radius * HALF_PI / levelDivisor;
|
|
1204
1197
|
}
|
|
1205
|
-
function
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1198
|
+
function decodeLeafTile(leafStorage, nodeIndex) {
|
|
1199
|
+
const nodeOffset = int(nodeIndex).mul(int(4));
|
|
1200
|
+
return {
|
|
1201
|
+
level: leafStorage.node.element(nodeOffset).toInt(),
|
|
1202
|
+
x: leafStorage.node.element(nodeOffset.add(int(1))).toFloat(),
|
|
1203
|
+
y: leafStorage.node.element(nodeOffset.add(int(2))).toFloat(),
|
|
1204
|
+
face: leafStorage.node.element(nodeOffset.add(int(3))).toInt()
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function faceUVFromTileLocal(tile, localU, localV, baseU = float(1), baseV = float(1)) {
|
|
1208
|
+
const levelScale = pow(float(2), tile.level.toFloat());
|
|
1209
|
+
const nU = baseU.mul(levelScale);
|
|
1210
|
+
const nV = baseV.mul(levelScale);
|
|
1211
|
+
return vec2(tile.x.add(localU).div(nU), tile.y.add(localV).div(nV));
|
|
1212
|
+
}
|
|
1213
|
+
function createTileCompute(leafStorage, uniforms, projection) {
|
|
1214
|
+
const baseU = float(projection.baseResolution?.u ?? 1);
|
|
1215
|
+
const baseV = float(projection.baseResolution?.v ?? 1);
|
|
1216
|
+
const tileLevel = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).level);
|
|
1217
|
+
const tileFace = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).face);
|
|
1218
|
+
const tileOriginVec2 = Fn(([nodeIndex]) => {
|
|
1219
|
+
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
1220
|
+
return vec2(tile.x, tile.y);
|
|
1221
|
+
});
|
|
1222
|
+
const tileFaceUV = Fn(([nodeIndex, ix, iy]) => {
|
|
1223
|
+
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
1224
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
1225
|
+
const localU = int(ix).toFloat().sub(float(1)).div(fInnerSegments);
|
|
1226
|
+
const localV = int(iy).toFloat().sub(float(1)).div(fInnerSegments);
|
|
1227
|
+
return faceUVFromTileLocal(tile, localU, localV, baseU, baseV);
|
|
1225
1228
|
});
|
|
1229
|
+
const shared = {
|
|
1230
|
+
tileLevel: (nodeIndex) => tileLevel(nodeIndex),
|
|
1231
|
+
tileFace: (nodeIndex) => tileFace(nodeIndex),
|
|
1232
|
+
tileOriginVec2: (nodeIndex) => tileOriginVec2(nodeIndex),
|
|
1233
|
+
tileFaceUV: (nodeIndex, ix, iy) => tileFaceUV(nodeIndex, ix, iy)
|
|
1234
|
+
};
|
|
1235
|
+
const parts = projection.gpu.createTileComputeParts({ leafStorage, uniforms, shared });
|
|
1236
|
+
return {
|
|
1237
|
+
...shared,
|
|
1238
|
+
tileSize: parts.tileSize,
|
|
1239
|
+
rootUVCompute: parts.rootUV,
|
|
1240
|
+
tileVertexWorldPositionCompute: parts.tileVertexWorldPosition
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function tileLocalToFieldUV(localCoord, innerSegments) {
|
|
1244
|
+
const edge = float(innerSegments).add(float(FIELD_EDGE_EXTRA_TEXELS));
|
|
1245
|
+
return float(localCoord).mul(float(innerSegments)).add(float(FIELD_INNER_TEXEL_OFFSET)).div(edge);
|
|
1246
|
+
}
|
|
1247
|
+
function tileLocalToFieldUVNumber(localCoord, innerSegments) {
|
|
1248
|
+
const edge = innerSegments + FIELD_EDGE_EXTRA_TEXELS;
|
|
1249
|
+
return (localCoord * innerSegments + FIELD_INNER_TEXEL_OFFSET) / edge;
|
|
1226
1250
|
}
|
|
1227
1251
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
const vIndex = int(vertexIndex);
|
|
1231
|
-
const segmentEdges = int(segmentsNode.add(3));
|
|
1232
|
-
const vx = vIndex.mod(segmentEdges);
|
|
1233
|
-
const vy = vIndex.div(segmentEdges);
|
|
1234
|
-
const last = segmentEdges.sub(int(1));
|
|
1235
|
-
return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
|
|
1236
|
-
});
|
|
1237
|
-
const isSkirtUV = Fn(([segments]) => {
|
|
1238
|
-
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
1239
|
-
const ux = uv().x;
|
|
1240
|
-
const uy = uv().y;
|
|
1241
|
-
const segmentCount = segmentsNode.add(2);
|
|
1242
|
-
const segmentStep = float(1).div(segmentCount);
|
|
1243
|
-
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
1244
|
-
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
1245
|
-
return innerX.and(innerY).not();
|
|
1246
|
-
});
|
|
1247
|
-
|
|
1248
|
-
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1249
|
-
if (!terrainFieldStorage) return float(0);
|
|
1252
|
+
function createTileElevation(terrainUniforms, terrainFieldStorage, tileBoundsNode) {
|
|
1253
|
+
if (!terrainFieldStorage || !tileBoundsNode) return float(0);
|
|
1250
1254
|
const innerSegs = terrainUniforms.uInnerTileSegments;
|
|
1251
1255
|
const u = tileLocalToFieldUV(positionLocal.x.add(float(0.5)), innerSegs);
|
|
1252
1256
|
const v = tileLocalToFieldUV(positionLocal.z.add(float(0.5)), innerSegs);
|
|
1253
|
-
|
|
1257
|
+
const normalized = sampleTerrainFieldElevation(
|
|
1258
|
+
terrainFieldStorage,
|
|
1259
|
+
u,
|
|
1260
|
+
v,
|
|
1261
|
+
int(instanceIndex)
|
|
1262
|
+
);
|
|
1263
|
+
const { packMin, packMax } = loadTilePackBounds(tileBoundsNode, int(instanceIndex));
|
|
1264
|
+
return denormalizeTerrainFieldElevation(normalized, packMin, packMax).mul(
|
|
1254
1265
|
terrainUniforms.uElevationScale
|
|
1255
1266
|
);
|
|
1256
1267
|
}
|
|
1257
1268
|
function loadWorldNormal(terrainUniforms, terrainFieldStorage) {
|
|
1258
|
-
const
|
|
1259
|
-
const
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
return loadTerrainFieldNormal(terrainFieldStorage, ix, iy, nodeIndex);
|
|
1269
|
+
const innerSegs = terrainUniforms.uInnerTileSegments;
|
|
1270
|
+
const u = tileLocalToFieldUV(positionLocal.x.add(float(0.5)), innerSegs);
|
|
1271
|
+
const v = tileLocalToFieldUV(positionLocal.z.add(float(0.5)), innerSegs);
|
|
1272
|
+
const raw = sampleTerrainField(terrainFieldStorage, u, v, int(instanceIndex));
|
|
1273
|
+
return vec3(raw.g, raw.b, raw.a).normalize();
|
|
1264
1274
|
}
|
|
1265
1275
|
function assignWorldNormal(terrainUniforms, terrainFieldStorage) {
|
|
1266
1276
|
if (!terrainFieldStorage) return;
|
|
1267
1277
|
normalLocal.assign(Fn(() => loadWorldNormal(terrainUniforms, terrainFieldStorage))());
|
|
1268
1278
|
}
|
|
1269
|
-
function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1279
|
+
function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, tileBoundsNode) {
|
|
1270
1280
|
return Fn(() => {
|
|
1271
1281
|
const tile = decodeLeafTile(leafStorage, int(instanceIndex));
|
|
1272
1282
|
const rootSize = terrainUniforms.uRootSize.toVar();
|
|
@@ -1280,7 +1290,11 @@ function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFie
|
|
|
1280
1290
|
const clampedZ = positionLocal.z.max(half.negate()).min(half);
|
|
1281
1291
|
const worldX = centerX.add(clampedX.mul(size));
|
|
1282
1292
|
const worldZ = centerZ.add(clampedZ.mul(size));
|
|
1283
|
-
const yElevation = createTileElevation(
|
|
1293
|
+
const yElevation = createTileElevation(
|
|
1294
|
+
terrainUniforms,
|
|
1295
|
+
terrainFieldStorage,
|
|
1296
|
+
tileBoundsNode
|
|
1297
|
+
);
|
|
1284
1298
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1285
1299
|
const baseY = rootOrigin.y.add(yElevation);
|
|
1286
1300
|
const skirtY = baseY.sub(terrainUniforms.uSkirtScale.toVar());
|
|
@@ -1289,7 +1303,7 @@ function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFie
|
|
|
1289
1303
|
return vec3(worldX, worldY, worldZ);
|
|
1290
1304
|
})();
|
|
1291
1305
|
}
|
|
1292
|
-
function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, surfacePoint, baseU = 1, baseV = 1) {
|
|
1306
|
+
function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, surfacePoint, tileBoundsNode, baseU = 1, baseV = 1) {
|
|
1293
1307
|
const fBaseU = float(baseU);
|
|
1294
1308
|
const fBaseV = float(baseV);
|
|
1295
1309
|
return Fn(() => {
|
|
@@ -1298,7 +1312,11 @@ function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainF
|
|
|
1298
1312
|
const localU = positionLocal.x.max(half.negate()).min(half).add(half);
|
|
1299
1313
|
const localV = positionLocal.z.max(half.negate()).min(half).add(half);
|
|
1300
1314
|
const faceUV = faceUVFromTileLocal(tile, localU, localV, fBaseU, fBaseV);
|
|
1301
|
-
const yElevation = createTileElevation(
|
|
1315
|
+
const yElevation = createTileElevation(
|
|
1316
|
+
terrainUniforms,
|
|
1317
|
+
terrainFieldStorage,
|
|
1318
|
+
tileBoundsNode
|
|
1319
|
+
);
|
|
1302
1320
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1303
1321
|
const displacement = select(
|
|
1304
1322
|
skirtVertex,
|
|
@@ -1715,7 +1733,12 @@ function createFlatProjection() {
|
|
|
1715
1733
|
faceOutward: false,
|
|
1716
1734
|
gpu: {
|
|
1717
1735
|
renderVertexPosition(ctx) {
|
|
1718
|
-
return createFlatRenderVertexPosition(
|
|
1736
|
+
return createFlatRenderVertexPosition(
|
|
1737
|
+
ctx.leafStorage,
|
|
1738
|
+
ctx.uniforms,
|
|
1739
|
+
ctx.terrainFieldStorage,
|
|
1740
|
+
ctx.tileBoundsNode
|
|
1741
|
+
);
|
|
1719
1742
|
},
|
|
1720
1743
|
createTileComputeParts: createFlatTileComputeParts,
|
|
1721
1744
|
createFieldNormal(ctx) {
|
|
@@ -2278,9 +2301,11 @@ function packedSampleFromTileResult(params, tileResult) {
|
|
|
2278
2301
|
fieldV,
|
|
2279
2302
|
safeTileIndex
|
|
2280
2303
|
).toVar();
|
|
2304
|
+
const { packMin, packMax } = loadTilePackBounds(params.tileBoundsNode, safeTileIndex);
|
|
2305
|
+
const elevation = denormalizeTerrainFieldElevation(sampled.r, packMin, packMax);
|
|
2281
2306
|
const normal = vec3(sampled.g, sampled.b, sampled.a);
|
|
2282
2307
|
const valid = found.select(float(1), float(0)).toVar();
|
|
2283
|
-
return vec4(
|
|
2308
|
+
return vec4(elevation, normal.x, normal.y, normal.z).mul(valid);
|
|
2284
2309
|
}
|
|
2285
2310
|
function createTerrainSampleNode(params) {
|
|
2286
2311
|
const tileLookup = createTileIndexFromWorldPosition(
|
|
@@ -2508,7 +2533,8 @@ function createCubeSphereProjection(config) {
|
|
|
2508
2533
|
const dir = cubeFaceDirection(basis, faceUV.x, faceUV.y);
|
|
2509
2534
|
const r = invert ? ctx.uniforms.uRadius.toVar().sub(displacement) : ctx.uniforms.uRadius.toVar().add(displacement);
|
|
2510
2535
|
return ctx.uniforms.uRootOrigin.toVar().add(dir.mul(r));
|
|
2511
|
-
}
|
|
2536
|
+
},
|
|
2537
|
+
ctx.tileBoundsNode
|
|
2512
2538
|
);
|
|
2513
2539
|
},
|
|
2514
2540
|
createTileComputeParts: createSphereTileComputeParts,
|
|
@@ -2856,6 +2882,7 @@ function createTorusProjection(config) {
|
|
|
2856
2882
|
ctx.uniforms,
|
|
2857
2883
|
ctx.terrainFieldStorage,
|
|
2858
2884
|
(_tile, faceUV, displacement) => torusPosition(geometry, faceUV.x, faceUV.y, displacement),
|
|
2885
|
+
ctx.tileBoundsNode,
|
|
2859
2886
|
baseU,
|
|
2860
2887
|
baseV
|
|
2861
2888
|
);
|
|
@@ -3082,8 +3109,8 @@ function buildTileElevationPyramid(pyramid, index, tileBounds, leafCount) {
|
|
|
3082
3109
|
const level = index.keysLevel[slot];
|
|
3083
3110
|
const x = index.keysX[slot];
|
|
3084
3111
|
const y = index.keysY[slot];
|
|
3085
|
-
const rawMin = tileBounds[leafIndex *
|
|
3086
|
-
const rawMax = tileBounds[leafIndex *
|
|
3112
|
+
const rawMin = tileBounds[leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MIN_OFFSET];
|
|
3113
|
+
const rawMax = tileBounds[leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET];
|
|
3087
3114
|
for (let ancestorLevel = level; ancestorLevel >= 0; ancestorLevel--) {
|
|
3088
3115
|
const shift = level - ancestorLevel;
|
|
3089
3116
|
mergeRange(
|
|
@@ -3237,8 +3264,8 @@ function createTerrainSnapshotState(maxNodes, maxLevel, totalElements) {
|
|
|
3237
3264
|
backElevation: new Float32Array(totalElements),
|
|
3238
3265
|
frontIndex: createSpatialIndex(maxNodes),
|
|
3239
3266
|
backIndex: createSpatialIndex(maxNodes),
|
|
3240
|
-
frontTileBounds: new Float32Array(maxNodes *
|
|
3241
|
-
backTileBounds: new Float32Array(maxNodes *
|
|
3267
|
+
frontTileBounds: new Float32Array(maxNodes * TILE_BOUNDS_FLOATS_PER_TILE),
|
|
3268
|
+
backTileBounds: new Float32Array(maxNodes * TILE_BOUNDS_FLOATS_PER_TILE),
|
|
3242
3269
|
frontLeafCount: 0,
|
|
3243
3270
|
globalRange: null,
|
|
3244
3271
|
hasSnapshot: false,
|
|
@@ -3279,7 +3306,7 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3279
3306
|
let boundsValid = activeLeafCount === 0;
|
|
3280
3307
|
if (boundsFilled) {
|
|
3281
3308
|
for (let i = 0; i < activeLeafCount; i += 1) {
|
|
3282
|
-
if ((state.backTileBounds[i *
|
|
3309
|
+
if ((state.backTileBounds[i * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET] ?? 0) !== 0) {
|
|
3283
3310
|
boundsValid = true;
|
|
3284
3311
|
break;
|
|
3285
3312
|
}
|
|
@@ -3301,8 +3328,8 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3301
3328
|
let gMin = Infinity;
|
|
3302
3329
|
let gMax = -Infinity;
|
|
3303
3330
|
for (let i = 0; i < activeLeafCount; i++) {
|
|
3304
|
-
const rawMin = state.frontTileBounds[i *
|
|
3305
|
-
const rawMax = state.frontTileBounds[i *
|
|
3331
|
+
const rawMin = state.frontTileBounds[i * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MIN_OFFSET];
|
|
3332
|
+
const rawMax = state.frontTileBounds[i * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET];
|
|
3306
3333
|
const a = originY + rawMin * elevationScale;
|
|
3307
3334
|
const b = originY + rawMax * elevationScale;
|
|
3308
3335
|
gMin = Math.min(gMin, a, b);
|
|
@@ -3338,7 +3365,7 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3338
3365
|
boundsAttribute,
|
|
3339
3366
|
state.boundsReadback,
|
|
3340
3367
|
state.backTileBounds,
|
|
3341
|
-
activeLeafCount *
|
|
3368
|
+
activeLeafCount * TILE_BOUNDS_FLOATS_PER_TILE,
|
|
3342
3369
|
"terrainBoundsReadback"
|
|
3343
3370
|
);
|
|
3344
3371
|
}
|
|
@@ -3357,7 +3384,9 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3357
3384
|
if (boundsResult) {
|
|
3358
3385
|
const rawBounds = new Float32Array(boundsResult);
|
|
3359
3386
|
state.backTileBounds.fill(0);
|
|
3360
|
-
state.backTileBounds.set(
|
|
3387
|
+
state.backTileBounds.set(
|
|
3388
|
+
rawBounds.subarray(0, activeLeafCount * TILE_BOUNDS_FLOATS_PER_TILE)
|
|
3389
|
+
);
|
|
3361
3390
|
boundsFilled = true;
|
|
3362
3391
|
}
|
|
3363
3392
|
applySnapshot(boundsFilled);
|
|
@@ -3471,8 +3500,8 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3471
3500
|
};
|
|
3472
3501
|
const tileBoundsFromLookup = (lookup, elevationBase) => {
|
|
3473
3502
|
if (!lookup.found || lookup.leafIndex >= state.frontLeafCount) return null;
|
|
3474
|
-
const rawMin = state.frontTileBounds[lookup.leafIndex *
|
|
3475
|
-
const rawMax = state.frontTileBounds[lookup.leafIndex *
|
|
3503
|
+
const rawMin = state.frontTileBounds[lookup.leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MIN_OFFSET];
|
|
3504
|
+
const rawMax = state.frontTileBounds[lookup.leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET];
|
|
3476
3505
|
const a = elevationBase + rawMin * config.elevationScale;
|
|
3477
3506
|
const b = elevationBase + rawMax * config.elevationScale;
|
|
3478
3507
|
return {
|
|
@@ -3700,84 +3729,132 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3700
3729
|
return lookupTileElevationRange(state.elevationPyramid, space, level, x, y, out);
|
|
3701
3730
|
}
|
|
3702
3731
|
};
|
|
3703
|
-
return api;
|
|
3704
|
-
}
|
|
3732
|
+
return api;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
const createElevation = (tile, uniforms, elevationFn) => {
|
|
3736
|
+
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
3737
|
+
const ix = int(localCoordinates.x);
|
|
3738
|
+
const iy = int(localCoordinates.y);
|
|
3739
|
+
const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(int(3));
|
|
3740
|
+
const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
|
|
3741
|
+
const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
|
|
3742
|
+
const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
|
|
3743
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
3744
|
+
return elevationFn({
|
|
3745
|
+
worldPosition,
|
|
3746
|
+
rootSize,
|
|
3747
|
+
rootUV,
|
|
3748
|
+
tileOriginVec2: tile.tileOriginVec2(nodeIndex),
|
|
3749
|
+
tileSize: tile.tileSize(nodeIndex),
|
|
3750
|
+
tileLevel: tile.tileLevel(nodeIndex),
|
|
3751
|
+
nodeIndex: int(nodeIndex),
|
|
3752
|
+
tileUV
|
|
3753
|
+
});
|
|
3754
|
+
};
|
|
3755
|
+
};
|
|
3756
|
+
|
|
3757
|
+
function createTerrainUniforms(params) {
|
|
3758
|
+
const sanitizedId = params.instanceId?.replace(/-/g, "_");
|
|
3759
|
+
const suffix = sanitizedId ? `_${sanitizedId}` : "";
|
|
3760
|
+
const uRootOrigin = uniform(
|
|
3761
|
+
new Vector3$1(params.rootOrigin.x, params.rootOrigin.y, params.rootOrigin.z)
|
|
3762
|
+
).setName(`uRootOrigin${suffix}`);
|
|
3763
|
+
const uRootSize = uniform(float(params.rootSize)).setName(`uRootSize${suffix}`);
|
|
3764
|
+
const uInnerTileSegments = uniform(int(params.innerTileSegments)).setName(
|
|
3765
|
+
`uInnerTileSegments${suffix}`
|
|
3766
|
+
);
|
|
3767
|
+
const uSkirtScale = uniform(float(params.skirtScale)).setName(`uSkirtScale${suffix}`);
|
|
3768
|
+
const uElevationScale = uniform(float(params.elevationScale)).setName(`uElevationScale${suffix}`);
|
|
3769
|
+
const uRadius = uniform(float(params.radius)).setName(`uRadius${suffix}`);
|
|
3770
|
+
return {
|
|
3771
|
+
uRootOrigin,
|
|
3772
|
+
uRootSize,
|
|
3773
|
+
uInnerTileSegments,
|
|
3774
|
+
uSkirtScale,
|
|
3775
|
+
uElevationScale,
|
|
3776
|
+
uRadius
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
const instanceIdTask = task(() => crypto.randomUUID()).displayName("instanceIdTask").cache("once");
|
|
3781
|
+
|
|
3782
|
+
const scratchVector3 = new Vector3();
|
|
3783
|
+
const createUniformsTask = task((get, work) => {
|
|
3784
|
+
const uniformParams = {
|
|
3785
|
+
rootOrigin: get(origin),
|
|
3786
|
+
rootSize: get(rootSize),
|
|
3787
|
+
innerTileSegments: get(innerTileSegments),
|
|
3788
|
+
skirtScale: get(skirtScale),
|
|
3789
|
+
elevationScale: get(elevationScale),
|
|
3790
|
+
radius: get(radius),
|
|
3791
|
+
instanceId: get(instanceIdTask)
|
|
3792
|
+
};
|
|
3793
|
+
return work(() => createTerrainUniforms(uniformParams));
|
|
3794
|
+
}).displayName("createUniformsTask").cache("once");
|
|
3795
|
+
const updateUniformsTask = task((get, work) => {
|
|
3796
|
+
const terrainUniformsContext = get(createUniformsTask);
|
|
3797
|
+
const rootSizeVal = get(rootSize);
|
|
3798
|
+
const rootOrigin = get(origin);
|
|
3799
|
+
const innerTileSegmentsVal = get(innerTileSegments);
|
|
3800
|
+
const skirtScaleVal = get(skirtScale);
|
|
3801
|
+
const elevationScaleVal = get(elevationScale);
|
|
3802
|
+
const radiusVal = get(radius);
|
|
3803
|
+
return work(() => {
|
|
3804
|
+
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
3805
|
+
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
3806
|
+
rootOrigin.x,
|
|
3807
|
+
rootOrigin.y,
|
|
3808
|
+
rootOrigin.z
|
|
3809
|
+
);
|
|
3810
|
+
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
3811
|
+
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
3812
|
+
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
3813
|
+
terrainUniformsContext.uRadius.value = radiusVal;
|
|
3814
|
+
return terrainUniformsContext;
|
|
3815
|
+
});
|
|
3816
|
+
}).displayName("updateUniformsTask");
|
|
3705
3817
|
|
|
3706
|
-
const
|
|
3707
|
-
function buildReductionKernel(elevationFieldNode, boundsNode, verticesPerNode, edgeVertexCount) {
|
|
3708
|
-
const elemsPerThread = Math.ceil(verticesPerNode / WGSIZE);
|
|
3709
|
-
return Fn(() => {
|
|
3710
|
-
const sharedMin = workgroupArray("float", WGSIZE);
|
|
3711
|
-
const sharedMax = workgroupArray("float", WGSIZE);
|
|
3712
|
-
const tid = int(localId.x);
|
|
3713
|
-
const tileIdx = int(workgroupId.z);
|
|
3714
|
-
const baseOffset = tileIdx.mul(int(verticesPerNode));
|
|
3715
|
-
const start = tid.mul(int(elemsPerThread));
|
|
3716
|
-
const end = min(start.add(int(elemsPerThread)), int(verticesPerNode));
|
|
3717
|
-
const localMin = float(1e10).toVar("localMin");
|
|
3718
|
-
const localMax = float(-1e10).toVar("localMax");
|
|
3719
|
-
const edge = int(edgeVertexCount);
|
|
3720
|
-
const lastEdge = int(edgeVertexCount - 1);
|
|
3721
|
-
Loop({ start, end, type: "int", condition: "<" }, ({ i }) => {
|
|
3722
|
-
const ix = int(i).mod(edge);
|
|
3723
|
-
const iy = int(i).div(edge);
|
|
3724
|
-
const isSkirt = ix.equal(int(0)).or(ix.equal(lastEdge)).or(iy.equal(int(0))).or(iy.equal(lastEdge));
|
|
3725
|
-
If(isSkirt.not(), () => {
|
|
3726
|
-
const h = elevationFieldNode.element(baseOffset.add(i));
|
|
3727
|
-
localMin.assign(min(localMin, h));
|
|
3728
|
-
localMax.assign(max(localMax, h));
|
|
3729
|
-
});
|
|
3730
|
-
});
|
|
3731
|
-
sharedMin.element(tid).assign(localMin);
|
|
3732
|
-
sharedMax.element(tid).assign(localMax);
|
|
3733
|
-
workgroupBarrier();
|
|
3734
|
-
If(tid.equal(int(0)), () => {
|
|
3735
|
-
const finalMin = float(1e10).toVar("finalMin");
|
|
3736
|
-
const finalMax = float(-1e10).toVar("finalMax");
|
|
3737
|
-
Loop(WGSIZE, ({ i }) => {
|
|
3738
|
-
finalMin.assign(min(finalMin, sharedMin.element(i)));
|
|
3739
|
-
finalMax.assign(max(finalMax, sharedMax.element(i)));
|
|
3740
|
-
});
|
|
3741
|
-
const outIdx = tileIdx.mul(int(2));
|
|
3742
|
-
boundsNode.element(outIdx).assign(finalMin);
|
|
3743
|
-
boundsNode.element(outIdx.add(int(1))).assign(finalMax);
|
|
3744
|
-
});
|
|
3745
|
-
})().computeKernel([WGSIZE, 1, 1]);
|
|
3746
|
-
}
|
|
3747
|
-
const tileBoundsContextTask = task((get, work) => {
|
|
3748
|
-
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
3749
|
-
const maxNodesValue = get(maxNodes);
|
|
3818
|
+
const createElevationFieldContextTask = task((get, work) => {
|
|
3750
3819
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
3820
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
3821
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
3751
3822
|
return work(() => {
|
|
3752
|
-
const data = new Float32Array(
|
|
3823
|
+
const data = new Float32Array(totalElements);
|
|
3753
3824
|
const attribute = new StorageBufferAttribute(data, 1);
|
|
3754
|
-
attribute.name = "
|
|
3755
|
-
const node = storage(attribute, "float",
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
node,
|
|
3762
|
-
verticesPerNode,
|
|
3763
|
-
edgeVertexCount
|
|
3764
|
-
);
|
|
3765
|
-
return { data, attribute, node, kernel };
|
|
3825
|
+
attribute.name = "elevationField";
|
|
3826
|
+
const node = storage(attribute, "float", totalElements).setName("elevationField");
|
|
3827
|
+
return {
|
|
3828
|
+
data,
|
|
3829
|
+
attribute,
|
|
3830
|
+
node
|
|
3831
|
+
};
|
|
3766
3832
|
});
|
|
3767
|
-
}).displayName("
|
|
3768
|
-
const
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
return
|
|
3774
|
-
|
|
3775
|
-
|
|
3833
|
+
}).displayName("createElevationFieldContextTask");
|
|
3834
|
+
const tileNodesTask = task((get, work) => {
|
|
3835
|
+
const leafStorage = get(leafStorageTask);
|
|
3836
|
+
const uniforms = get(updateUniformsTask);
|
|
3837
|
+
const topology = get(topologyTask);
|
|
3838
|
+
return work(() => {
|
|
3839
|
+
return createTileCompute(leafStorage, uniforms, topology.projection);
|
|
3840
|
+
});
|
|
3841
|
+
}).displayName("tileNodesTask");
|
|
3842
|
+
const elevationFieldStageTask = task((get, work) => {
|
|
3843
|
+
const tile = get(tileNodesTask);
|
|
3844
|
+
const uniforms = get(updateUniformsTask);
|
|
3845
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
3846
|
+
const userElevationFn = get(elevationFn);
|
|
3847
|
+
return work(() => {
|
|
3848
|
+
const heightFn = createElevationFunction(userElevationFn);
|
|
3849
|
+
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
3850
|
+
return [
|
|
3851
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
3852
|
+
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
3853
|
+
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
3776
3854
|
}
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
).displayName("tileBoundsReductionTask").lane("gpu");
|
|
3855
|
+
];
|
|
3856
|
+
});
|
|
3857
|
+
}).displayName("elevationFieldStageTask");
|
|
3781
3858
|
|
|
3782
3859
|
const terrainQueryTask = task((get, work) => {
|
|
3783
3860
|
const maxNodesValue = get(maxNodes);
|
|
@@ -3920,107 +3997,88 @@ const leafGpuBufferTask = task((get, work) => {
|
|
|
3920
3997
|
});
|
|
3921
3998
|
}).displayName("leafGpuBufferTask");
|
|
3922
3999
|
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
const
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
4000
|
+
const WGSIZE = 64;
|
|
4001
|
+
function buildReductionKernel(elevationFieldNode, boundsNode, verticesPerNode, edgeVertexCount) {
|
|
4002
|
+
const elemsPerThread = Math.ceil(verticesPerNode / WGSIZE);
|
|
4003
|
+
return Fn(() => {
|
|
4004
|
+
const sharedLodMin = workgroupArray("float", WGSIZE);
|
|
4005
|
+
const sharedLodMax = workgroupArray("float", WGSIZE);
|
|
4006
|
+
const sharedPackMin = workgroupArray("float", WGSIZE);
|
|
4007
|
+
const sharedPackMax = workgroupArray("float", WGSIZE);
|
|
4008
|
+
const tid = int(localId.x);
|
|
4009
|
+
const tileIdx = int(workgroupId.z);
|
|
4010
|
+
const baseOffset = tileIdx.mul(int(verticesPerNode));
|
|
4011
|
+
const start = tid.mul(int(elemsPerThread));
|
|
4012
|
+
const end = min(start.add(int(elemsPerThread)), int(verticesPerNode));
|
|
4013
|
+
const localLodMin = float(1e10).toVar("localLodMin");
|
|
4014
|
+
const localLodMax = float(-1e10).toVar("localLodMax");
|
|
4015
|
+
const localPackMin = float(1e10).toVar("localPackMin");
|
|
4016
|
+
const localPackMax = float(-1e10).toVar("localPackMax");
|
|
4017
|
+
const edge = int(edgeVertexCount);
|
|
4018
|
+
const lastEdge = int(edgeVertexCount - 1);
|
|
4019
|
+
Loop({ start, end, type: "int", condition: "<" }, ({ i }) => {
|
|
4020
|
+
const ix = int(i).mod(edge);
|
|
4021
|
+
const iy = int(i).div(edge);
|
|
4022
|
+
const isSkirt = ix.equal(int(0)).or(ix.equal(lastEdge)).or(iy.equal(int(0))).or(iy.equal(lastEdge));
|
|
4023
|
+
const h = elevationFieldNode.element(baseOffset.add(i));
|
|
4024
|
+
localPackMin.assign(min(localPackMin, h));
|
|
4025
|
+
localPackMax.assign(max(localPackMax, h));
|
|
4026
|
+
If(isSkirt.not(), () => {
|
|
4027
|
+
localLodMin.assign(min(localLodMin, h));
|
|
4028
|
+
localLodMax.assign(max(localLodMax, h));
|
|
4029
|
+
});
|
|
4030
|
+
});
|
|
4031
|
+
sharedLodMin.element(tid).assign(localLodMin);
|
|
4032
|
+
sharedLodMax.element(tid).assign(localLodMax);
|
|
4033
|
+
sharedPackMin.element(tid).assign(localPackMin);
|
|
4034
|
+
sharedPackMax.element(tid).assign(localPackMax);
|
|
4035
|
+
workgroupBarrier();
|
|
4036
|
+
If(tid.equal(int(0)), () => {
|
|
4037
|
+
const finalLodMin = float(1e10).toVar("finalLodMin");
|
|
4038
|
+
const finalLodMax = float(-1e10).toVar("finalLodMax");
|
|
4039
|
+
const finalPackMin = float(1e10).toVar("finalPackMin");
|
|
4040
|
+
const finalPackMax = float(-1e10).toVar("finalPackMax");
|
|
4041
|
+
Loop(WGSIZE, ({ i }) => {
|
|
4042
|
+
finalLodMin.assign(min(finalLodMin, sharedLodMin.element(i)));
|
|
4043
|
+
finalLodMax.assign(max(finalLodMax, sharedLodMax.element(i)));
|
|
4044
|
+
finalPackMin.assign(min(finalPackMin, sharedPackMin.element(i)));
|
|
4045
|
+
finalPackMax.assign(max(finalPackMax, sharedPackMax.element(i)));
|
|
4046
|
+
});
|
|
4047
|
+
const outIdx = tileIdx.mul(int(TILE_BOUNDS_FLOATS_PER_TILE));
|
|
4048
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_LOD_MIN_OFFSET))).assign(finalLodMin);
|
|
4049
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_LOD_MAX_OFFSET))).assign(finalLodMax);
|
|
4050
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_PACK_MIN_OFFSET))).assign(finalPackMin);
|
|
4051
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_PACK_MAX_OFFSET))).assign(finalPackMax);
|
|
4052
|
+
});
|
|
4053
|
+
})().computeKernel([WGSIZE, 1, 1]);
|
|
3944
4054
|
}
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
innerTileSegments: get(innerTileSegments),
|
|
3954
|
-
skirtScale: get(skirtScale),
|
|
3955
|
-
elevationScale: get(elevationScale),
|
|
3956
|
-
radius: get(radius),
|
|
3957
|
-
instanceId: get(instanceIdTask)
|
|
3958
|
-
};
|
|
3959
|
-
return work(() => createTerrainUniforms(uniformParams));
|
|
3960
|
-
}).displayName("createUniformsTask").cache("once");
|
|
3961
|
-
const updateUniformsTask = task((get, work) => {
|
|
3962
|
-
const terrainUniformsContext = get(createUniformsTask);
|
|
3963
|
-
const rootSizeVal = get(rootSize);
|
|
3964
|
-
const rootOrigin = get(origin);
|
|
3965
|
-
const innerTileSegmentsVal = get(innerTileSegments);
|
|
3966
|
-
const skirtScaleVal = get(skirtScale);
|
|
3967
|
-
const elevationScaleVal = get(elevationScale);
|
|
3968
|
-
const radiusVal = get(radius);
|
|
3969
|
-
return work(() => {
|
|
3970
|
-
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
3971
|
-
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
3972
|
-
rootOrigin.x,
|
|
3973
|
-
rootOrigin.y,
|
|
3974
|
-
rootOrigin.z
|
|
3975
|
-
);
|
|
3976
|
-
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
3977
|
-
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
3978
|
-
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
3979
|
-
terrainUniformsContext.uRadius.value = radiusVal;
|
|
3980
|
-
return terrainUniformsContext;
|
|
3981
|
-
});
|
|
3982
|
-
}).displayName("updateUniformsTask");
|
|
3983
|
-
|
|
3984
|
-
const createElevationFieldContextTask = task((get, work) => {
|
|
4055
|
+
function runTileBoundsReduction(renderer, boundsContext, leafCount) {
|
|
4056
|
+
if (leafCount > 0) {
|
|
4057
|
+
renderer.compute(boundsContext.kernel, [1, 1, leafCount]);
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
const tileBoundsContextTask = task((get, work) => {
|
|
4061
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
4062
|
+
const maxNodesValue = get(maxNodes);
|
|
3985
4063
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
3986
|
-
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
3987
|
-
const totalElements = get(maxNodes) * verticesPerNode;
|
|
3988
4064
|
return work(() => {
|
|
3989
|
-
const
|
|
4065
|
+
const floatCount = maxNodesValue * TILE_BOUNDS_FLOATS_PER_TILE;
|
|
4066
|
+
const data = new Float32Array(floatCount);
|
|
3990
4067
|
const attribute = new StorageBufferAttribute(data, 1);
|
|
3991
|
-
attribute.name = "
|
|
3992
|
-
const node = storage(attribute, "float",
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
const topology = get(topologyTask);
|
|
4004
|
-
return work(() => {
|
|
4005
|
-
return createTileCompute(leafStorage, uniforms, topology.projection);
|
|
4006
|
-
});
|
|
4007
|
-
}).displayName("tileNodesTask");
|
|
4008
|
-
const elevationFieldStageTask = task((get, work) => {
|
|
4009
|
-
const tile = get(tileNodesTask);
|
|
4010
|
-
const uniforms = get(updateUniformsTask);
|
|
4011
|
-
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
4012
|
-
const userElevationFn = get(elevationFn);
|
|
4013
|
-
return work(() => {
|
|
4014
|
-
const heightFn = createElevationFunction(userElevationFn);
|
|
4015
|
-
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
4016
|
-
return [
|
|
4017
|
-
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
4018
|
-
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
4019
|
-
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
4020
|
-
}
|
|
4021
|
-
];
|
|
4068
|
+
attribute.name = "tileBounds";
|
|
4069
|
+
const node = storage(attribute, "float", floatCount).setName(
|
|
4070
|
+
"tileBounds"
|
|
4071
|
+
);
|
|
4072
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
4073
|
+
const kernel = buildReductionKernel(
|
|
4074
|
+
elevationFieldContext.node,
|
|
4075
|
+
node,
|
|
4076
|
+
verticesPerNode,
|
|
4077
|
+
edgeVertexCount
|
|
4078
|
+
);
|
|
4079
|
+
return { data, attribute, node, kernel };
|
|
4022
4080
|
});
|
|
4023
|
-
}).displayName("
|
|
4081
|
+
}).displayName("tileBoundsContextTask");
|
|
4024
4082
|
|
|
4025
4083
|
const createTerrainFieldTextureTask = task(
|
|
4026
4084
|
(get, work, { resources }) => {
|
|
@@ -4045,6 +4103,7 @@ const terrainFieldStageTask = task((get, work) => {
|
|
|
4045
4103
|
const tile = get(tileNodesTask);
|
|
4046
4104
|
const uniforms = get(updateUniformsTask);
|
|
4047
4105
|
const topology = get(topologyTask);
|
|
4106
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4048
4107
|
return work(() => {
|
|
4049
4108
|
const computeNormal = topology.projection.gpu.createFieldNormal({
|
|
4050
4109
|
elevationFieldNode: elevationFieldContext.node,
|
|
@@ -4059,12 +4118,13 @@ const terrainFieldStageTask = task((get, work) => {
|
|
|
4059
4118
|
const iy = int(localCoordinates.y);
|
|
4060
4119
|
const height = elevationFieldContext.node.element(globalVertexIndex);
|
|
4061
4120
|
const normal = computeNormal(nodeIndex, ix, iy);
|
|
4121
|
+
const { packMin, packMax } = loadTilePackBounds(boundsContext.node, nodeIndex);
|
|
4062
4122
|
storeTerrainField(
|
|
4063
4123
|
terrainFieldStorage,
|
|
4064
4124
|
ix,
|
|
4065
4125
|
iy,
|
|
4066
4126
|
nodeIndex,
|
|
4067
|
-
|
|
4127
|
+
packNormalizedTerrainFieldSample(height, normal, packMin, packMax)
|
|
4068
4128
|
);
|
|
4069
4129
|
}
|
|
4070
4130
|
];
|
|
@@ -4076,23 +4136,28 @@ function createComputePipelineTasks(leafStageTask) {
|
|
|
4076
4136
|
const compile = task((get, work) => {
|
|
4077
4137
|
const pipeline = get(leafStageTask);
|
|
4078
4138
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
4139
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4079
4140
|
return work(
|
|
4080
4141
|
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
4081
|
-
|
|
4142
|
+
midPipelineExecute: (renderer, instanceCount) => {
|
|
4143
|
+
runTileBoundsReduction(renderer, boundsContext, instanceCount);
|
|
4144
|
+
}
|
|
4145
|
+
})
|
|
4082
4146
|
);
|
|
4083
4147
|
}).displayName("compileComputeTask");
|
|
4084
|
-
const execute = task(
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
}
|
|
4091
|
-
);
|
|
4092
|
-
}
|
|
4093
|
-
).displayName("executeComputeTask").lane("gpu");
|
|
4148
|
+
const execute = task((get, work, { resources }) => {
|
|
4149
|
+
const { execute: run } = get(compile);
|
|
4150
|
+
const leafState = get(leafGpuBufferTask);
|
|
4151
|
+
return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
4152
|
+
});
|
|
4153
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
4094
4154
|
return { compile, execute };
|
|
4095
4155
|
}
|
|
4156
|
+
const tileBoundsReductionTask = task((get, work) => {
|
|
4157
|
+
get(executeComputeTask);
|
|
4158
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4159
|
+
return work(() => boundsContext);
|
|
4160
|
+
}).displayName("tileBoundsReductionTask").lane("gpu");
|
|
4096
4161
|
|
|
4097
4162
|
const gpuSpatialIndexStorageTask = task((get, work) => {
|
|
4098
4163
|
const maxNodesValue = get(maxNodes);
|
|
@@ -4110,6 +4175,7 @@ const gpuSpatialIndexUploadTask = task((get, work) => {
|
|
|
4110
4175
|
|
|
4111
4176
|
const createTerrainSamplerTask = task((get, work) => {
|
|
4112
4177
|
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
4178
|
+
const tileBoundsContext = get(tileBoundsContextTask);
|
|
4113
4179
|
const spatialIndex = get(gpuSpatialIndexStorageTask);
|
|
4114
4180
|
const uniforms = get(updateUniformsTask);
|
|
4115
4181
|
const elevationCallback = get(elevationFn);
|
|
@@ -4118,6 +4184,7 @@ const createTerrainSamplerTask = task((get, work) => {
|
|
|
4118
4184
|
return work(
|
|
4119
4185
|
() => createTerrainSampler({
|
|
4120
4186
|
terrainFieldStorage,
|
|
4187
|
+
tileBoundsNode: tileBoundsContext.node,
|
|
4121
4188
|
spatialIndex,
|
|
4122
4189
|
uniforms,
|
|
4123
4190
|
elevationCallback,
|
|
@@ -4131,12 +4198,14 @@ const positionNodeTask = task((get, work) => {
|
|
|
4131
4198
|
const leafStorage = get(leafStorageTask);
|
|
4132
4199
|
const terrainUniforms = get(updateUniformsTask);
|
|
4133
4200
|
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
4201
|
+
const tileBoundsContext = get(tileBoundsContextTask);
|
|
4134
4202
|
const topology = get(topologyTask);
|
|
4135
4203
|
return work(
|
|
4136
4204
|
() => topology.projection.gpu.renderVertexPosition({
|
|
4137
4205
|
leafStorage,
|
|
4138
4206
|
uniforms: terrainUniforms,
|
|
4139
|
-
terrainFieldStorage
|
|
4207
|
+
terrainFieldStorage,
|
|
4208
|
+
tileBoundsNode: tileBoundsContext.node
|
|
4140
4209
|
})
|
|
4141
4210
|
);
|
|
4142
4211
|
}).displayName("positionNodeTask");
|
|
@@ -4257,6 +4326,17 @@ function terrainGraph() {
|
|
|
4257
4326
|
return g;
|
|
4258
4327
|
}
|
|
4259
4328
|
|
|
4329
|
+
const decodeUint16RG = Fn(
|
|
4330
|
+
([sample]) => sample.r.mul(float(256)).add(sample.g).div(float(257))
|
|
4331
|
+
);
|
|
4332
|
+
const sampleHeightmapMeters = Fn(
|
|
4333
|
+
([heightmapTexture, uv, minM, _maxM, rangeM]) => {
|
|
4334
|
+
const sample = texture(heightmapTexture, uv);
|
|
4335
|
+
const normalized = decodeUint16RG(sample);
|
|
4336
|
+
return minM.add(normalized.mul(rangeM));
|
|
4337
|
+
}
|
|
4338
|
+
);
|
|
4339
|
+
|
|
4260
4340
|
const textureSpaceToVectorSpace = Fn(([value]) => {
|
|
4261
4341
|
return remap(value, float(0), float(1), float(-1), float(1));
|
|
4262
4342
|
});
|
|
@@ -4309,4 +4389,4 @@ const voronoiCells = Fn((params) => {
|
|
|
4309
4389
|
return k;
|
|
4310
4390
|
});
|
|
4311
4391
|
|
|
4312
|
-
export { ArrayTextureBackend, AtlasBackend, CUBE_FACES, CUBE_FACE_COUNT, Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, augmentCubeSphereSampler, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereProjection, createCubeSphereTopology, createElevationFieldContextTask, createFlatProjection, createFlatTopology, createInfiniteFlatTopology, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainQuery, createTerrainRaycast, createTerrainSampler, createTerrainSamplerTask, createTerrainSurfaceQuery, createTerrainUniforms, createTorusProjection, createTorusTopology, createUniformsTask, cubeFaceBasis, cubeFaceDirection, cubeFaceFromDirection, cubeFacePoint, cubeFaceUVFromDirection, deriveNormalZ, directionToFace, directionToFaceUV, directionToLatLong, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, faceUVToCube, getDeviceComputeLimits, gpuSpatialIndexStorageTask, gpuSpatialIndexUploadTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, latLongToDirection, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, positionToTorusParams, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, radius, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, skirtScale, sphereTangentFrameNormal, storeTerrainField, tangentFromAxis, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, topology, topologyTask, torusOutwardNormal$1 as torusOutwardNormal, torusUVToPoint, unpackTangentNormal, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells, wrap01 };
|
|
4392
|
+
export { ArrayTextureBackend, AtlasBackend, CUBE_FACES, CUBE_FACE_COUNT, Dir, TERRAIN_FIELD_PACK_EPSILON, TILE_BOUNDS_FLOATS_PER_TILE, TILE_BOUNDS_LOD_MAX_OFFSET, TILE_BOUNDS_LOD_MIN_OFFSET, TILE_BOUNDS_PACK_MAX_OFFSET, TILE_BOUNDS_PACK_MIN_OFFSET, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, augmentCubeSphereSampler, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereProjection, createCubeSphereTopology, createElevationFieldContextTask, createFlatProjection, createFlatTopology, createInfiniteFlatTopology, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainQuery, createTerrainRaycast, createTerrainSampler, createTerrainSamplerTask, createTerrainSurfaceQuery, createTerrainUniforms, createTorusProjection, createTorusTopology, createUniformsTask, cubeFaceBasis, cubeFaceDirection, cubeFaceFromDirection, cubeFacePoint, cubeFaceUVFromDirection, decodeUint16RG, denormalizeTerrainFieldElevation, deriveNormalZ, directionToFace, directionToFaceUV, directionToLatLong, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, faceUVToCube, getDeviceComputeLimits, gpuSpatialIndexStorageTask, gpuSpatialIndexUploadTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, latLongToDirection, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, loadTilePackBounds, maxLevel, maxNodes, origin, packNormalizedTerrainFieldSample, packTerrainFieldSample, positionNodeTask, positionToTorusParams, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, radius, resetLeafSet, resetSeamTable, rootSize, runTileBoundsReduction, sampleHeightmapMeters, sampleTerrainField, sampleTerrainFieldElevation, skirtScale, sphereTangentFrameNormal, storeTerrainField, tangentFromAxis, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileBoundsContextTask, tileBoundsReductionTask, tileNodesTask, topology, topologyTask, torusOutwardNormal$1 as torusOutwardNormal, torusUVToPoint, unpackTangentNormal, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells, wrap01 };
|