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