@hello-terrain/three 0.0.0-alpha.12 → 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 +772 -760
- package/dist/index.d.cts +331 -288
- package/dist/index.d.mts +331 -288
- package/dist/index.d.ts +331 -288
- package/dist/index.mjs +761 -763
- package/package.json +3 -3
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,281 +422,14 @@ 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(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
|
|
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;
|
|
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
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { execute };
|
|
699
433
|
}
|
|
700
434
|
|
|
701
435
|
function createLeafStorage(maxNodes) {
|
|
@@ -937,20 +671,15 @@ function beginUpdate(state, topology, params) {
|
|
|
937
671
|
|
|
938
672
|
function shouldSplit(bounds, level, maxLevel, params) {
|
|
939
673
|
if (level >= maxLevel) return false;
|
|
940
|
-
const mode = params.mode ?? "distance";
|
|
941
674
|
const cx = bounds.cx;
|
|
942
675
|
const cy = bounds.cy;
|
|
943
676
|
const cz = bounds.cz;
|
|
944
677
|
const distSq = cx * cx + cy * cy + cz * cz;
|
|
945
678
|
const safeDistSq = distSq > 1e-12 ? distSq : 1e-12;
|
|
946
|
-
if (mode === "screen") {
|
|
947
|
-
const proj = params.projectionFactor
|
|
948
|
-
const target = params.targetPixels
|
|
949
|
-
if (proj <= 0 || target <= 0)
|
|
950
|
-
const f2 = params.distanceFactor ?? 2;
|
|
951
|
-
const threshold2 = bounds.r * f2;
|
|
952
|
-
return safeDistSq < threshold2 * threshold2;
|
|
953
|
-
}
|
|
679
|
+
if (params.mode === "screen") {
|
|
680
|
+
const proj = params.projectionFactor;
|
|
681
|
+
const target = params.targetPixels;
|
|
682
|
+
if (proj <= 0 || target <= 0) return false;
|
|
954
683
|
const left = bounds.r * bounds.r * proj * proj;
|
|
955
684
|
const right = safeDistSq * target * target;
|
|
956
685
|
return left > right;
|
|
@@ -984,7 +713,7 @@ function refineLeaves(state, topology, params, outLeaves) {
|
|
|
984
713
|
let elevationRange;
|
|
985
714
|
if (params.tileElevationRange) {
|
|
986
715
|
const range = state.scratchElevationRange;
|
|
987
|
-
if (params.tileElevationRange(
|
|
716
|
+
if (params.tileElevationRange(tile, range)) {
|
|
988
717
|
elevationRange = range;
|
|
989
718
|
}
|
|
990
719
|
}
|
|
@@ -1087,19 +816,9 @@ function balance2to1(state, topology, params, leaves) {
|
|
|
1087
816
|
}
|
|
1088
817
|
|
|
1089
818
|
function update(state, topology, params, outLeaves) {
|
|
1090
|
-
const cam = params.cameraOrigin;
|
|
1091
|
-
const elevation = params.elevationAtCameraXZ ?? 0;
|
|
1092
|
-
const origX = cam.x;
|
|
1093
|
-
const origY = cam.y;
|
|
1094
|
-
const origZ = cam.z;
|
|
1095
|
-
topology.projection.cpu.cameraSurfaceOffset(cam, elevation);
|
|
1096
819
|
beginUpdate(state, topology, params);
|
|
1097
820
|
const leaves = refineLeaves(state, topology, params, outLeaves);
|
|
1098
|
-
|
|
1099
|
-
cam.x = origX;
|
|
1100
|
-
cam.y = origY;
|
|
1101
|
-
cam.z = origZ;
|
|
1102
|
-
return result;
|
|
821
|
+
return balance2to1(state, topology, params, leaves);
|
|
1103
822
|
}
|
|
1104
823
|
|
|
1105
824
|
const scratchTile = { space: 0, level: 0, x: 0, y: 0 };
|
|
@@ -1196,94 +915,370 @@ function buildSeams2to1(topology, leaves, outSeams, outIndex) {
|
|
|
1196
915
|
return outSeams;
|
|
1197
916
|
}
|
|
1198
917
|
|
|
1199
|
-
function createFlatNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1200
|
-
return tsl.Fn(
|
|
1201
|
-
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
1202
|
-
const iEdge = tsl.int(edgeVertexCount);
|
|
1203
|
-
const verticesPerNode = iEdge.mul(iEdge);
|
|
1204
|
-
const baseOffset = tsl.int(nodeIndex).mul(verticesPerNode);
|
|
1205
|
-
const xLeft = tsl.int(ix).sub(tsl.int(1));
|
|
1206
|
-
const xRight = tsl.int(ix).add(tsl.int(1));
|
|
1207
|
-
const yUp = tsl.int(iy).sub(tsl.int(1));
|
|
1208
|
-
const yDown = tsl.int(iy).add(tsl.int(1));
|
|
1209
|
-
const hLeft = elevationFieldNode.element(baseOffset.add(tsl.int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
1210
|
-
const hRight = elevationFieldNode.element(baseOffset.add(tsl.int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
1211
|
-
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(tsl.int(ix)))).mul(elevationScale);
|
|
1212
|
-
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(tsl.int(ix)))).mul(elevationScale);
|
|
1213
|
-
const innerSegments = tsl.float(iEdge).sub(tsl.float(3));
|
|
1214
|
-
const stepWorld = tileSize.div(innerSegments);
|
|
1215
|
-
const inv2Step = tsl.float(0.5).div(stepWorld);
|
|
1216
|
-
const dhdx = tsl.float(hRight).sub(tsl.float(hLeft)).mul(inv2Step);
|
|
1217
|
-
const dhdz = tsl.float(hDown).sub(tsl.float(hUp)).mul(inv2Step);
|
|
1218
|
-
return tsl.vec3(dhdx.negate(), tsl.float(1), dhdz.negate()).normalize();
|
|
1219
|
-
}
|
|
1220
|
-
);
|
|
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));
|
|
1192
|
+
}
|
|
1193
|
+
|
|
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;
|
|
1221
1199
|
}
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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);
|
|
1242
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;
|
|
1243
1252
|
}
|
|
1244
1253
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const vIndex = tsl.int(tsl.vertexIndex);
|
|
1248
|
-
const segmentEdges = tsl.int(segmentsNode.add(3));
|
|
1249
|
-
const vx = vIndex.mod(segmentEdges);
|
|
1250
|
-
const vy = vIndex.div(segmentEdges);
|
|
1251
|
-
const last = segmentEdges.sub(tsl.int(1));
|
|
1252
|
-
return vx.equal(tsl.int(0)).or(vx.equal(last)).or(vy.equal(tsl.int(0))).or(vy.equal(last));
|
|
1253
|
-
});
|
|
1254
|
-
const isSkirtUV = tsl.Fn(([segments]) => {
|
|
1255
|
-
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1256
|
-
const ux = tsl.uv().x;
|
|
1257
|
-
const uy = tsl.uv().y;
|
|
1258
|
-
const segmentCount = segmentsNode.add(2);
|
|
1259
|
-
const segmentStep = tsl.float(1).div(segmentCount);
|
|
1260
|
-
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
1261
|
-
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
1262
|
-
return innerX.and(innerY).not();
|
|
1263
|
-
});
|
|
1264
|
-
|
|
1265
|
-
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1266
|
-
if (!terrainFieldStorage) return tsl.float(0);
|
|
1254
|
+
function createTileElevation(terrainUniforms, terrainFieldStorage, tileBoundsNode) {
|
|
1255
|
+
if (!terrainFieldStorage || !tileBoundsNode) return tsl.float(0);
|
|
1267
1256
|
const innerSegs = terrainUniforms.uInnerTileSegments;
|
|
1268
1257
|
const u = tileLocalToFieldUV(tsl.positionLocal.x.add(tsl.float(0.5)), innerSegs);
|
|
1269
1258
|
const v = tileLocalToFieldUV(tsl.positionLocal.z.add(tsl.float(0.5)), innerSegs);
|
|
1270
|
-
|
|
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(
|
|
1271
1267
|
terrainUniforms.uElevationScale
|
|
1272
1268
|
);
|
|
1273
1269
|
}
|
|
1274
1270
|
function loadWorldNormal(terrainUniforms, terrainFieldStorage) {
|
|
1275
|
-
const
|
|
1276
|
-
const
|
|
1277
|
-
const
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1280
|
-
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();
|
|
1281
1276
|
}
|
|
1282
1277
|
function assignWorldNormal(terrainUniforms, terrainFieldStorage) {
|
|
1283
1278
|
if (!terrainFieldStorage) return;
|
|
1284
1279
|
tsl.normalLocal.assign(tsl.Fn(() => loadWorldNormal(terrainUniforms, terrainFieldStorage))());
|
|
1285
1280
|
}
|
|
1286
|
-
function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1281
|
+
function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, tileBoundsNode) {
|
|
1287
1282
|
return tsl.Fn(() => {
|
|
1288
1283
|
const tile = decodeLeafTile(leafStorage, tsl.int(tsl.instanceIndex));
|
|
1289
1284
|
const rootSize = terrainUniforms.uRootSize.toVar();
|
|
@@ -1297,7 +1292,11 @@ function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFie
|
|
|
1297
1292
|
const clampedZ = tsl.positionLocal.z.max(half.negate()).min(half);
|
|
1298
1293
|
const worldX = centerX.add(clampedX.mul(size));
|
|
1299
1294
|
const worldZ = centerZ.add(clampedZ.mul(size));
|
|
1300
|
-
const yElevation = createTileElevation(
|
|
1295
|
+
const yElevation = createTileElevation(
|
|
1296
|
+
terrainUniforms,
|
|
1297
|
+
terrainFieldStorage,
|
|
1298
|
+
tileBoundsNode
|
|
1299
|
+
);
|
|
1301
1300
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1302
1301
|
const baseY = rootOrigin.y.add(yElevation);
|
|
1303
1302
|
const skirtY = baseY.sub(terrainUniforms.uSkirtScale.toVar());
|
|
@@ -1306,7 +1305,7 @@ function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFie
|
|
|
1306
1305
|
return tsl.vec3(worldX, worldY, worldZ);
|
|
1307
1306
|
})();
|
|
1308
1307
|
}
|
|
1309
|
-
function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, surfacePoint, baseU = 1, baseV = 1) {
|
|
1308
|
+
function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, surfacePoint, tileBoundsNode, baseU = 1, baseV = 1) {
|
|
1310
1309
|
const fBaseU = tsl.float(baseU);
|
|
1311
1310
|
const fBaseV = tsl.float(baseV);
|
|
1312
1311
|
return tsl.Fn(() => {
|
|
@@ -1315,7 +1314,11 @@ function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainF
|
|
|
1315
1314
|
const localU = tsl.positionLocal.x.max(half.negate()).min(half).add(half);
|
|
1316
1315
|
const localV = tsl.positionLocal.z.max(half.negate()).min(half).add(half);
|
|
1317
1316
|
const faceUV = faceUVFromTileLocal(tile, localU, localV, fBaseU, fBaseV);
|
|
1318
|
-
const yElevation = createTileElevation(
|
|
1317
|
+
const yElevation = createTileElevation(
|
|
1318
|
+
terrainUniforms,
|
|
1319
|
+
terrainFieldStorage,
|
|
1320
|
+
tileBoundsNode
|
|
1321
|
+
);
|
|
1319
1322
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1320
1323
|
const displacement = tsl.select(
|
|
1321
1324
|
skirtVertex,
|
|
@@ -1500,26 +1503,6 @@ function cpuRaycast(query, ray, config, options) {
|
|
|
1500
1503
|
distance: ray.origin.distanceTo(point)
|
|
1501
1504
|
};
|
|
1502
1505
|
}
|
|
1503
|
-
function cpuRaycastBoundsOnly(ray, config, options) {
|
|
1504
|
-
const bounds = getTerrainBounds(config);
|
|
1505
|
-
const planeY = (config.minY + config.maxY) * 0.5;
|
|
1506
|
-
const dirY = ray.direction.y;
|
|
1507
|
-
if (Math.abs(dirY) < 1e-8) return null;
|
|
1508
|
-
const t = (planeY - ray.origin.y) / dirY;
|
|
1509
|
-
if (t < 0) return null;
|
|
1510
|
-
const maxDistance = options?.maxDistance ?? Number.POSITIVE_INFINITY;
|
|
1511
|
-
if (t > maxDistance) return null;
|
|
1512
|
-
const point = new three.Vector3();
|
|
1513
|
-
ray.at(t, point);
|
|
1514
|
-
if (point.x < bounds.minX || point.x > bounds.maxX || point.z < bounds.minZ || point.z > bounds.maxZ) {
|
|
1515
|
-
return null;
|
|
1516
|
-
}
|
|
1517
|
-
return {
|
|
1518
|
-
position: point,
|
|
1519
|
-
normal: new three.Vector3(0, 1, 0),
|
|
1520
|
-
distance: ray.origin.distanceTo(point)
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
1506
|
function intersectRaySphere(ray, cx, cy, cz, radius) {
|
|
1524
1507
|
const ox = ray.origin.x - cx;
|
|
1525
1508
|
const oy = ray.origin.y - cy;
|
|
@@ -1585,22 +1568,6 @@ function cubeSphereRaycast(query, ray, params, options) {
|
|
|
1585
1568
|
distance: ray.origin.distanceTo(sample.position)
|
|
1586
1569
|
};
|
|
1587
1570
|
}
|
|
1588
|
-
function cubeSphereRaycastBoundsOnly(ray, params, options) {
|
|
1589
|
-
const shell = intersectRaySphere(ray, params.centerX, params.centerY, params.centerZ, params.radius);
|
|
1590
|
-
if (!shell) return null;
|
|
1591
|
-
const maxDistance = options?.maxDistance ?? Number.POSITIVE_INFINITY;
|
|
1592
|
-
const t = shell.t0 >= 0 ? shell.t0 : shell.t1;
|
|
1593
|
-
if (t < 0 || t > maxDistance) return null;
|
|
1594
|
-
const point = new three.Vector3();
|
|
1595
|
-
ray.at(t, point);
|
|
1596
|
-
const normal = new three.Vector3(
|
|
1597
|
-
point.x - params.centerX,
|
|
1598
|
-
point.y - params.centerY,
|
|
1599
|
-
point.z - params.centerZ
|
|
1600
|
-
).normalize();
|
|
1601
|
-
if (params.invert) normal.negate();
|
|
1602
|
-
return { position: point, normal, distance: ray.origin.distanceTo(point) };
|
|
1603
|
-
}
|
|
1604
1571
|
function torusSignedDistance(query, params, px, py, pz, scratchPoint, scratchParams) {
|
|
1605
1572
|
positionToTorusParams(
|
|
1606
1573
|
px,
|
|
@@ -1655,28 +1622,6 @@ function torusRaycast(query, ray, params, options) {
|
|
|
1655
1622
|
distance: ray.origin.distanceTo(sample.position)
|
|
1656
1623
|
};
|
|
1657
1624
|
}
|
|
1658
|
-
function torusRaycastBoundsOnly(ray, params, options) {
|
|
1659
|
-
const shell = intersectRaySphere(
|
|
1660
|
-
ray,
|
|
1661
|
-
params.centerX,
|
|
1662
|
-
params.centerY,
|
|
1663
|
-
params.centerZ,
|
|
1664
|
-
params.outerRadius
|
|
1665
|
-
);
|
|
1666
|
-
if (!shell) return null;
|
|
1667
|
-
const maxDistance = options?.maxDistance ?? Number.POSITIVE_INFINITY;
|
|
1668
|
-
const t = shell.t0 >= 0 ? shell.t0 : shell.t1;
|
|
1669
|
-
if (t < 0 || t > maxDistance) return null;
|
|
1670
|
-
const point = new three.Vector3();
|
|
1671
|
-
ray.at(t, point);
|
|
1672
|
-
const normal = new three.Vector3(
|
|
1673
|
-
point.x - params.centerX,
|
|
1674
|
-
point.y - params.centerY,
|
|
1675
|
-
point.z - params.centerZ
|
|
1676
|
-
).normalize();
|
|
1677
|
-
if (params.invert) normal.negate();
|
|
1678
|
-
return { position: point, normal, distance: ray.origin.distanceTo(point) };
|
|
1679
|
-
}
|
|
1680
1625
|
|
|
1681
1626
|
function createTerrainQuery(cache) {
|
|
1682
1627
|
return {
|
|
@@ -1790,7 +1735,12 @@ function createFlatProjection() {
|
|
|
1790
1735
|
faceOutward: false,
|
|
1791
1736
|
gpu: {
|
|
1792
1737
|
renderVertexPosition(ctx) {
|
|
1793
|
-
return createFlatRenderVertexPosition(
|
|
1738
|
+
return createFlatRenderVertexPosition(
|
|
1739
|
+
ctx.leafStorage,
|
|
1740
|
+
ctx.uniforms,
|
|
1741
|
+
ctx.terrainFieldStorage,
|
|
1742
|
+
ctx.tileBoundsNode
|
|
1743
|
+
);
|
|
1794
1744
|
},
|
|
1795
1745
|
createTileComputeParts: createFlatTileComputeParts,
|
|
1796
1746
|
createFieldNormal(ctx) {
|
|
@@ -1802,9 +1752,6 @@ function createFlatProjection() {
|
|
|
1802
1752
|
}
|
|
1803
1753
|
},
|
|
1804
1754
|
cpu: {
|
|
1805
|
-
cameraSurfaceOffset(cam, elevation) {
|
|
1806
|
-
cam.y -= elevation;
|
|
1807
|
-
},
|
|
1808
1755
|
createSurfaceOps() {
|
|
1809
1756
|
return null;
|
|
1810
1757
|
},
|
|
@@ -1813,26 +1760,44 @@ function createFlatProjection() {
|
|
|
1813
1760
|
},
|
|
1814
1761
|
raycast(ctx) {
|
|
1815
1762
|
const { ray, options, terrainQuery, config } = ctx;
|
|
1816
|
-
if (terrainQuery)
|
|
1817
|
-
|
|
1818
|
-
if (precise) return precise;
|
|
1819
|
-
}
|
|
1820
|
-
const coarse = cpuRaycastBoundsOnly(ray, config, options);
|
|
1821
|
-
if (coarse && terrainQuery) {
|
|
1822
|
-
const sample = terrainQuery.sampleTerrain(coarse.position.x, coarse.position.z);
|
|
1823
|
-
if (sample.valid) {
|
|
1824
|
-
coarse.position.y = sample.elevation;
|
|
1825
|
-
coarse.normal.copy(sample.normal);
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
return coarse;
|
|
1763
|
+
if (!terrainQuery) return null;
|
|
1764
|
+
return cpuRaycast(terrainQuery, ray, config, options);
|
|
1829
1765
|
}
|
|
1830
1766
|
}
|
|
1831
1767
|
};
|
|
1832
1768
|
}
|
|
1833
1769
|
|
|
1770
|
+
function boundingSphereFromPoints(px, py, pz, count, cameraOrigin, out) {
|
|
1771
|
+
let sumX = 0;
|
|
1772
|
+
let sumY = 0;
|
|
1773
|
+
let sumZ = 0;
|
|
1774
|
+
for (let i = 0; i < count; i++) {
|
|
1775
|
+
sumX += px[i];
|
|
1776
|
+
sumY += py[i];
|
|
1777
|
+
sumZ += pz[i];
|
|
1778
|
+
}
|
|
1779
|
+
const cX = sumX / count;
|
|
1780
|
+
const cY = sumY / count;
|
|
1781
|
+
const cZ = sumZ / count;
|
|
1782
|
+
let maxDistSq = 0;
|
|
1783
|
+
for (let i = 0; i < count; i++) {
|
|
1784
|
+
const dx = px[i] - cX;
|
|
1785
|
+
const dy = py[i] - cY;
|
|
1786
|
+
const dz = pz[i] - cZ;
|
|
1787
|
+
const dSq = dx * dx + dy * dy + dz * dz;
|
|
1788
|
+
if (dSq > maxDistSq) maxDistSq = dSq;
|
|
1789
|
+
}
|
|
1790
|
+
out.cx = cX - cameraOrigin.x;
|
|
1791
|
+
out.cy = cY - cameraOrigin.y;
|
|
1792
|
+
out.cz = cZ - cameraOrigin.z;
|
|
1793
|
+
out.r = Math.sqrt(maxDistSq);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1834
1796
|
function createFlatTopology(cfg) {
|
|
1835
1797
|
const halfRoot = 0.5 * cfg.rootSize;
|
|
1798
|
+
const px = new Float64Array(8);
|
|
1799
|
+
const py = new Float64Array(8);
|
|
1800
|
+
const pz = new Float64Array(8);
|
|
1836
1801
|
const topology = {
|
|
1837
1802
|
spaceCount: 1,
|
|
1838
1803
|
maxRootCount: 1,
|
|
@@ -1872,15 +1837,26 @@ function createFlatTopology(cfg) {
|
|
|
1872
1837
|
const size = cfg.rootSize * scale;
|
|
1873
1838
|
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
1874
1839
|
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
1875
|
-
const
|
|
1876
|
-
const
|
|
1877
|
-
const
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1840
|
+
const maxX = minX + size;
|
|
1841
|
+
const maxZ = minZ + size;
|
|
1842
|
+
const yLo = cfg.origin.y + (elevationRange ? elevationRange.min : 0);
|
|
1843
|
+
const yHi = elevationRange ? cfg.origin.y + elevationRange.max : 0;
|
|
1844
|
+
let pointCount = 0;
|
|
1845
|
+
for (let i = 0; i < 4; i++) {
|
|
1846
|
+
const cornerX = (i & 1) === 0 ? minX : maxX;
|
|
1847
|
+
const cornerZ = i < 2 ? minZ : maxZ;
|
|
1848
|
+
px[pointCount] = cornerX;
|
|
1849
|
+
py[pointCount] = yLo;
|
|
1850
|
+
pz[pointCount] = cornerZ;
|
|
1851
|
+
pointCount += 1;
|
|
1852
|
+
if (elevationRange) {
|
|
1853
|
+
px[pointCount] = cornerX;
|
|
1854
|
+
py[pointCount] = yHi;
|
|
1855
|
+
pz[pointCount] = cornerZ;
|
|
1856
|
+
pointCount += 1;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
1884
1860
|
},
|
|
1885
1861
|
rootTiles(_cameraOrigin, out) {
|
|
1886
1862
|
const root = out[0];
|
|
@@ -1898,6 +1874,9 @@ function createInfiniteFlatTopology(cfg) {
|
|
|
1898
1874
|
const halfRoot = 0.5 * cfg.rootSize;
|
|
1899
1875
|
const rootGridRadius = Math.max(0, Math.floor(cfg.rootGridRadius ?? 1));
|
|
1900
1876
|
const rootWidth = rootGridRadius * 2 + 1;
|
|
1877
|
+
const px = new Float64Array(8);
|
|
1878
|
+
const py = new Float64Array(8);
|
|
1879
|
+
const pz = new Float64Array(8);
|
|
1901
1880
|
return {
|
|
1902
1881
|
spaceCount: 1,
|
|
1903
1882
|
maxRootCount: rootWidth * rootWidth,
|
|
@@ -1931,15 +1910,26 @@ function createInfiniteFlatTopology(cfg) {
|
|
|
1931
1910
|
const size = cfg.rootSize * scale;
|
|
1932
1911
|
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
1933
1912
|
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
1934
|
-
const
|
|
1935
|
-
const
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1913
|
+
const maxX = minX + size;
|
|
1914
|
+
const maxZ = minZ + size;
|
|
1915
|
+
const yLo = cfg.origin.y + (elevationRange ? elevationRange.min : 0);
|
|
1916
|
+
const yHi = elevationRange ? cfg.origin.y + elevationRange.max : 0;
|
|
1917
|
+
let pointCount = 0;
|
|
1918
|
+
for (let i = 0; i < 4; i++) {
|
|
1919
|
+
const cornerX = (i & 1) === 0 ? minX : maxX;
|
|
1920
|
+
const cornerZ = i < 2 ? minZ : maxZ;
|
|
1921
|
+
px[pointCount] = cornerX;
|
|
1922
|
+
py[pointCount] = yLo;
|
|
1923
|
+
pz[pointCount] = cornerZ;
|
|
1924
|
+
pointCount += 1;
|
|
1925
|
+
if (elevationRange) {
|
|
1926
|
+
px[pointCount] = cornerX;
|
|
1927
|
+
py[pointCount] = yHi;
|
|
1928
|
+
pz[pointCount] = cornerZ;
|
|
1929
|
+
pointCount += 1;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
1943
1933
|
},
|
|
1944
1934
|
rootTiles(cameraOrigin, out) {
|
|
1945
1935
|
const camRootX = Math.floor((cameraOrigin.x - cfg.origin.x + halfRoot) / cfg.rootSize);
|
|
@@ -2313,9 +2303,11 @@ function packedSampleFromTileResult(params, tileResult) {
|
|
|
2313
2303
|
fieldV,
|
|
2314
2304
|
safeTileIndex
|
|
2315
2305
|
).toVar();
|
|
2306
|
+
const { packMin, packMax } = loadTilePackBounds(params.tileBoundsNode, safeTileIndex);
|
|
2307
|
+
const elevation = denormalizeTerrainFieldElevation(sampled.r, packMin, packMax);
|
|
2316
2308
|
const normal = tsl.vec3(sampled.g, sampled.b, sampled.a);
|
|
2317
2309
|
const valid = found.select(tsl.float(1), tsl.float(0)).toVar();
|
|
2318
|
-
return tsl.vec4(
|
|
2310
|
+
return tsl.vec4(elevation, normal.x, normal.y, normal.z).mul(valid);
|
|
2319
2311
|
}
|
|
2320
2312
|
function createTerrainSampleNode(params) {
|
|
2321
2313
|
const tileLookup = createTileIndexFromWorldPosition(
|
|
@@ -2475,7 +2467,6 @@ function createCubeSphereProjection(config) {
|
|
|
2475
2467
|
const nx = dx / len;
|
|
2476
2468
|
const ny = dy / len;
|
|
2477
2469
|
const nz = dz / len;
|
|
2478
|
-
const dirSign = invert ? -1 : 1;
|
|
2479
2470
|
dirScratch[0] = nx;
|
|
2480
2471
|
dirScratch[1] = ny;
|
|
2481
2472
|
dirScratch[2] = nz;
|
|
@@ -2484,9 +2475,9 @@ function createCubeSphereProjection(config) {
|
|
|
2484
2475
|
out.space = face;
|
|
2485
2476
|
out.u = uvScratch[0];
|
|
2486
2477
|
out.v = uvScratch[1];
|
|
2487
|
-
out.dirX = nx
|
|
2488
|
-
out.dirY = ny
|
|
2489
|
-
out.dirZ = nz
|
|
2478
|
+
out.dirX = nx;
|
|
2479
|
+
out.dirY = ny;
|
|
2480
|
+
out.dirZ = nz;
|
|
2490
2481
|
return true;
|
|
2491
2482
|
},
|
|
2492
2483
|
surfacePosition(key, elevation, outVec) {
|
|
@@ -2519,7 +2510,8 @@ function createCubeSphereProjection(config) {
|
|
|
2519
2510
|
let nx = tuy * tvz - tuz * tvy;
|
|
2520
2511
|
let ny = tuz * tvx - tux * tvz;
|
|
2521
2512
|
let nz = tux * tvy - tuy * tvx;
|
|
2522
|
-
|
|
2513
|
+
const outwardSign = invert ? -1 : 1;
|
|
2514
|
+
if ((nx * key.dirX + ny * key.dirY + nz * key.dirZ) * outwardSign < 0) {
|
|
2523
2515
|
nx = -nx;
|
|
2524
2516
|
ny = -ny;
|
|
2525
2517
|
nz = -nz;
|
|
@@ -2543,7 +2535,8 @@ function createCubeSphereProjection(config) {
|
|
|
2543
2535
|
const dir = cubeFaceDirection(basis, faceUV.x, faceUV.y);
|
|
2544
2536
|
const r = invert ? ctx.uniforms.uRadius.toVar().sub(displacement) : ctx.uniforms.uRadius.toVar().add(displacement);
|
|
2545
2537
|
return ctx.uniforms.uRootOrigin.toVar().add(dir.mul(r));
|
|
2546
|
-
}
|
|
2538
|
+
},
|
|
2539
|
+
ctx.tileBoundsNode
|
|
2547
2540
|
);
|
|
2548
2541
|
},
|
|
2549
2542
|
createTileComputeParts: createSphereTileComputeParts,
|
|
@@ -2573,19 +2566,6 @@ function createCubeSphereProjection(config) {
|
|
|
2573
2566
|
augmentSampler: augmentCubeSphereSampler
|
|
2574
2567
|
},
|
|
2575
2568
|
cpu: {
|
|
2576
|
-
cameraSurfaceOffset(cam, elevation) {
|
|
2577
|
-
const dx = cam.x - center.x;
|
|
2578
|
-
const dy = cam.y - center.y;
|
|
2579
|
-
const dz = cam.z - center.z;
|
|
2580
|
-
const len = Math.hypot(dx, dy, dz);
|
|
2581
|
-
if (len > 1e-12) {
|
|
2582
|
-
const sign = invert ? 1 : -1;
|
|
2583
|
-
const inv = sign * elevation / len;
|
|
2584
|
-
cam.x += dx * inv;
|
|
2585
|
-
cam.y += dy * inv;
|
|
2586
|
-
cam.z += dz * inv;
|
|
2587
|
-
}
|
|
2588
|
-
},
|
|
2589
2569
|
createSurfaceOps() {
|
|
2590
2570
|
return surfaceOps;
|
|
2591
2571
|
},
|
|
@@ -2596,6 +2576,7 @@ function createCubeSphereProjection(config) {
|
|
|
2596
2576
|
return { query, surfaceQuery, sphereQuery };
|
|
2597
2577
|
},
|
|
2598
2578
|
raycast(ctx) {
|
|
2579
|
+
if (!ctx.sphereQuery) return null;
|
|
2599
2580
|
const range = ctx.terrainQuery?.getGlobalElevationRange();
|
|
2600
2581
|
const dispMax = range ? Math.max(0, range.max - center.y) : radius * 0.1;
|
|
2601
2582
|
const outerPadding = invert ? 0 : dispMax + RAYCAST_PADDING$1;
|
|
@@ -2607,11 +2588,7 @@ function createCubeSphereProjection(config) {
|
|
|
2607
2588
|
maxRadius: radius + outerPadding,
|
|
2608
2589
|
invert
|
|
2609
2590
|
};
|
|
2610
|
-
|
|
2611
|
-
const precise = cubeSphereRaycast(ctx.sphereQuery, ctx.ray, params, ctx.options);
|
|
2612
|
-
if (precise) return precise;
|
|
2613
|
-
}
|
|
2614
|
-
return cubeSphereRaycastBoundsOnly(ctx.ray, params, ctx.options);
|
|
2591
|
+
return cubeSphereRaycast(ctx.sphereQuery, ctx.ray, params, ctx.options);
|
|
2615
2592
|
}
|
|
2616
2593
|
}
|
|
2617
2594
|
};
|
|
@@ -2702,8 +2679,6 @@ function createCubeSphereTopology(cfg) {
|
|
|
2702
2679
|
spaceCount: 6,
|
|
2703
2680
|
maxRootCount: 6,
|
|
2704
2681
|
projection: createCubeSphereProjection({ radius, center, invert: cfg.invert }),
|
|
2705
|
-
radius,
|
|
2706
|
-
center,
|
|
2707
2682
|
neighborSameLevel(tile, dir, out) {
|
|
2708
2683
|
const level = tile.level;
|
|
2709
2684
|
const n = 1 << level;
|
|
@@ -2740,48 +2715,29 @@ function createCubeSphereTopology(cfg) {
|
|
|
2740
2715
|
const u1 = (tile.x + 1) / n;
|
|
2741
2716
|
const v0 = tile.y / n;
|
|
2742
2717
|
const v1 = (tile.y + 1) / n;
|
|
2743
|
-
const
|
|
2744
|
-
const
|
|
2745
|
-
const disps = elevationRange ? [elevationRange.min, elevationRange.max] : [0];
|
|
2718
|
+
const shellLo = radius + (elevationRange ? elevationRange.min : 0);
|
|
2719
|
+
const shellHi = elevationRange ? radius + elevationRange.max : 0;
|
|
2746
2720
|
let pointCount = 0;
|
|
2747
|
-
let sumX = 0;
|
|
2748
|
-
let sumY = 0;
|
|
2749
|
-
let sumZ = 0;
|
|
2750
2721
|
for (let i = 0; i < 4; i++) {
|
|
2751
|
-
|
|
2722
|
+
const u = (i & 1) === 0 ? u0 : u1;
|
|
2723
|
+
const v = i < 2 ? v0 : v1;
|
|
2724
|
+
faceUVToCube(tile.space, u, v, cube);
|
|
2752
2725
|
const len = Math.hypot(cube[0], cube[1], cube[2]);
|
|
2753
2726
|
const dirX = cube[0] / len;
|
|
2754
2727
|
const dirY = cube[1] / len;
|
|
2755
2728
|
const dirZ = cube[2] / len;
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
px[pointCount] =
|
|
2762
|
-
py[pointCount] =
|
|
2763
|
-
pz[pointCount] =
|
|
2764
|
-
sumX += sx;
|
|
2765
|
-
sumY += sy;
|
|
2766
|
-
sumZ += sz;
|
|
2729
|
+
px[pointCount] = center.x + dirX * shellLo;
|
|
2730
|
+
py[pointCount] = center.y + dirY * shellLo;
|
|
2731
|
+
pz[pointCount] = center.z + dirZ * shellLo;
|
|
2732
|
+
pointCount += 1;
|
|
2733
|
+
if (elevationRange) {
|
|
2734
|
+
px[pointCount] = center.x + dirX * shellHi;
|
|
2735
|
+
py[pointCount] = center.y + dirY * shellHi;
|
|
2736
|
+
pz[pointCount] = center.z + dirZ * shellHi;
|
|
2767
2737
|
pointCount += 1;
|
|
2768
2738
|
}
|
|
2769
2739
|
}
|
|
2770
|
-
|
|
2771
|
-
const cY = sumY / pointCount;
|
|
2772
|
-
const cZ = sumZ / pointCount;
|
|
2773
|
-
let maxDistSq = 0;
|
|
2774
|
-
for (let i = 0; i < pointCount; i++) {
|
|
2775
|
-
const dx = px[i] - cX;
|
|
2776
|
-
const dy = py[i] - cY;
|
|
2777
|
-
const dz = pz[i] - cZ;
|
|
2778
|
-
const dSq = dx * dx + dy * dy + dz * dz;
|
|
2779
|
-
if (dSq > maxDistSq) maxDistSq = dSq;
|
|
2780
|
-
}
|
|
2781
|
-
out.cx = cX - cameraOrigin.x;
|
|
2782
|
-
out.cy = cY - cameraOrigin.y;
|
|
2783
|
-
out.cz = cZ - cameraOrigin.z;
|
|
2784
|
-
out.r = Math.sqrt(maxDistSq);
|
|
2740
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
2785
2741
|
},
|
|
2786
2742
|
rootTiles(_cameraOrigin, out) {
|
|
2787
2743
|
for (let s = 0; s < 6; s++) {
|
|
@@ -2928,6 +2884,7 @@ function createTorusProjection(config) {
|
|
|
2928
2884
|
ctx.uniforms,
|
|
2929
2885
|
ctx.terrainFieldStorage,
|
|
2930
2886
|
(_tile, faceUV, displacement) => torusPosition(geometry, faceUV.x, faceUV.y, displacement),
|
|
2887
|
+
ctx.tileBoundsNode,
|
|
2931
2888
|
baseU,
|
|
2932
2889
|
baseV
|
|
2933
2890
|
);
|
|
@@ -2952,13 +2909,6 @@ function createTorusProjection(config) {
|
|
|
2952
2909
|
}
|
|
2953
2910
|
},
|
|
2954
2911
|
cpu: {
|
|
2955
|
-
cameraSurfaceOffset(cam, elevation) {
|
|
2956
|
-
positionToTorusParams(cam.x, cam.y, cam.z, majorRadius, center, params);
|
|
2957
|
-
torusOutwardNormal$1(params.u, params.v, normalScratch, invert);
|
|
2958
|
-
cam.x -= normalScratch[0] * elevation;
|
|
2959
|
-
cam.y -= normalScratch[1] * elevation;
|
|
2960
|
-
cam.z -= normalScratch[2] * elevation;
|
|
2961
|
-
},
|
|
2962
2912
|
createSurfaceOps() {
|
|
2963
2913
|
return surfaceOps;
|
|
2964
2914
|
},
|
|
@@ -2968,6 +2918,7 @@ function createTorusProjection(config) {
|
|
|
2968
2918
|
return { query, surfaceQuery, sphereQuery: null };
|
|
2969
2919
|
},
|
|
2970
2920
|
raycast(ctx) {
|
|
2921
|
+
if (!ctx.surfaceQuery) return null;
|
|
2971
2922
|
const range = ctx.terrainQuery?.getGlobalElevationRange();
|
|
2972
2923
|
const dispMax = range ? Math.max(0, range.max - ctx.config.originY) : minorRadius * 0.5;
|
|
2973
2924
|
const outerPadding = invert ? 0 : dispMax + RAYCAST_PADDING;
|
|
@@ -2980,11 +2931,7 @@ function createTorusProjection(config) {
|
|
|
2980
2931
|
outerRadius: majorRadius + minorRadius + outerPadding,
|
|
2981
2932
|
invert
|
|
2982
2933
|
};
|
|
2983
|
-
|
|
2984
|
-
const precise = torusRaycast(ctx.surfaceQuery, ctx.ray, raycastParams, ctx.options);
|
|
2985
|
-
if (precise) return precise;
|
|
2986
|
-
}
|
|
2987
|
-
return torusRaycastBoundsOnly(ctx.ray, raycastParams, ctx.options);
|
|
2934
|
+
return torusRaycast(ctx.surfaceQuery, ctx.ray, raycastParams, ctx.options);
|
|
2988
2935
|
}
|
|
2989
2936
|
}
|
|
2990
2937
|
};
|
|
@@ -3017,8 +2964,6 @@ function createTorusTopology(cfg) {
|
|
|
3017
2964
|
baseU,
|
|
3018
2965
|
baseV
|
|
3019
2966
|
}),
|
|
3020
|
-
radius: majorRadius + minorRadius,
|
|
3021
|
-
center,
|
|
3022
2967
|
neighborSameLevel(tile, dir, out) {
|
|
3023
2968
|
const { nU, nV } = levelResolution(tile.level);
|
|
3024
2969
|
let nx = tile.x;
|
|
@@ -3049,42 +2994,28 @@ function createTorusTopology(cfg) {
|
|
|
3049
2994
|
const v0 = tile.y / nV;
|
|
3050
2995
|
const stepU = 1 / nU;
|
|
3051
2996
|
const stepV = 1 / nV;
|
|
3052
|
-
const
|
|
2997
|
+
const dispLo = elevationRange ? elevationRange.min : 0;
|
|
2998
|
+
const dispHi = elevationRange ? elevationRange.max : 0;
|
|
3053
2999
|
let pointCount = 0;
|
|
3054
|
-
let sumX = 0;
|
|
3055
|
-
let sumY = 0;
|
|
3056
|
-
let sumZ = 0;
|
|
3057
3000
|
for (let sj = 0; sj <= 2; sj++) {
|
|
3058
3001
|
for (let si = 0; si <= 2; si++) {
|
|
3059
3002
|
const u = u0 + si * stepU / 2;
|
|
3060
3003
|
const v = v0 + sj * stepV / 2;
|
|
3061
|
-
|
|
3062
|
-
|
|
3004
|
+
torusUVToPoint(u, v, majorRadius, minorRadius, dispLo, center, corner, invert);
|
|
3005
|
+
px[pointCount] = corner[0];
|
|
3006
|
+
py[pointCount] = corner[1];
|
|
3007
|
+
pz[pointCount] = corner[2];
|
|
3008
|
+
pointCount += 1;
|
|
3009
|
+
if (elevationRange) {
|
|
3010
|
+
torusUVToPoint(u, v, majorRadius, minorRadius, dispHi, center, corner, invert);
|
|
3063
3011
|
px[pointCount] = corner[0];
|
|
3064
3012
|
py[pointCount] = corner[1];
|
|
3065
3013
|
pz[pointCount] = corner[2];
|
|
3066
|
-
sumX += corner[0];
|
|
3067
|
-
sumY += corner[1];
|
|
3068
|
-
sumZ += corner[2];
|
|
3069
3014
|
pointCount += 1;
|
|
3070
3015
|
}
|
|
3071
3016
|
}
|
|
3072
3017
|
}
|
|
3073
|
-
|
|
3074
|
-
const cY = sumY / pointCount;
|
|
3075
|
-
const cZ = sumZ / pointCount;
|
|
3076
|
-
let maxDistSq = 0;
|
|
3077
|
-
for (let i = 0; i < pointCount; i++) {
|
|
3078
|
-
const dx = px[i] - cX;
|
|
3079
|
-
const dy = py[i] - cY;
|
|
3080
|
-
const dz = pz[i] - cZ;
|
|
3081
|
-
const dSq = dx * dx + dy * dy + dz * dz;
|
|
3082
|
-
if (dSq > maxDistSq) maxDistSq = dSq;
|
|
3083
|
-
}
|
|
3084
|
-
out.cx = cX - cameraOrigin.x;
|
|
3085
|
-
out.cy = cY - cameraOrigin.y;
|
|
3086
|
-
out.cz = cZ - cameraOrigin.z;
|
|
3087
|
-
out.r = Math.sqrt(maxDistSq);
|
|
3018
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
3088
3019
|
},
|
|
3089
3020
|
rootTiles(_cameraOrigin, out) {
|
|
3090
3021
|
let count = 0;
|
|
@@ -3180,8 +3111,8 @@ function buildTileElevationPyramid(pyramid, index, tileBounds, leafCount) {
|
|
|
3180
3111
|
const level = index.keysLevel[slot];
|
|
3181
3112
|
const x = index.keysX[slot];
|
|
3182
3113
|
const y = index.keysY[slot];
|
|
3183
|
-
const rawMin = tileBounds[leafIndex *
|
|
3184
|
-
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];
|
|
3185
3116
|
for (let ancestorLevel = level; ancestorLevel >= 0; ancestorLevel--) {
|
|
3186
3117
|
const shift = level - ancestorLevel;
|
|
3187
3118
|
mergeRange(
|
|
@@ -3335,8 +3266,8 @@ function createTerrainSnapshotState(maxNodes, maxLevel, totalElements) {
|
|
|
3335
3266
|
backElevation: new Float32Array(totalElements),
|
|
3336
3267
|
frontIndex: createSpatialIndex(maxNodes),
|
|
3337
3268
|
backIndex: createSpatialIndex(maxNodes),
|
|
3338
|
-
frontTileBounds: new Float32Array(maxNodes *
|
|
3339
|
-
backTileBounds: new Float32Array(maxNodes *
|
|
3269
|
+
frontTileBounds: new Float32Array(maxNodes * TILE_BOUNDS_FLOATS_PER_TILE),
|
|
3270
|
+
backTileBounds: new Float32Array(maxNodes * TILE_BOUNDS_FLOATS_PER_TILE),
|
|
3340
3271
|
frontLeafCount: 0,
|
|
3341
3272
|
globalRange: null,
|
|
3342
3273
|
hasSnapshot: false,
|
|
@@ -3377,7 +3308,7 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3377
3308
|
let boundsValid = activeLeafCount === 0;
|
|
3378
3309
|
if (boundsFilled) {
|
|
3379
3310
|
for (let i = 0; i < activeLeafCount; i += 1) {
|
|
3380
|
-
if ((state.backTileBounds[i *
|
|
3311
|
+
if ((state.backTileBounds[i * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET] ?? 0) !== 0) {
|
|
3381
3312
|
boundsValid = true;
|
|
3382
3313
|
break;
|
|
3383
3314
|
}
|
|
@@ -3399,8 +3330,8 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3399
3330
|
let gMin = Infinity;
|
|
3400
3331
|
let gMax = -Infinity;
|
|
3401
3332
|
for (let i = 0; i < activeLeafCount; i++) {
|
|
3402
|
-
const rawMin = state.frontTileBounds[i *
|
|
3403
|
-
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];
|
|
3404
3335
|
const a = originY + rawMin * elevationScale;
|
|
3405
3336
|
const b = originY + rawMax * elevationScale;
|
|
3406
3337
|
gMin = Math.min(gMin, a, b);
|
|
@@ -3436,7 +3367,7 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3436
3367
|
boundsAttribute,
|
|
3437
3368
|
state.boundsReadback,
|
|
3438
3369
|
state.backTileBounds,
|
|
3439
|
-
activeLeafCount *
|
|
3370
|
+
activeLeafCount * TILE_BOUNDS_FLOATS_PER_TILE,
|
|
3440
3371
|
"terrainBoundsReadback"
|
|
3441
3372
|
);
|
|
3442
3373
|
}
|
|
@@ -3455,7 +3386,9 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3455
3386
|
if (boundsResult) {
|
|
3456
3387
|
const rawBounds = new Float32Array(boundsResult);
|
|
3457
3388
|
state.backTileBounds.fill(0);
|
|
3458
|
-
state.backTileBounds.set(
|
|
3389
|
+
state.backTileBounds.set(
|
|
3390
|
+
rawBounds.subarray(0, activeLeafCount * TILE_BOUNDS_FLOATS_PER_TILE)
|
|
3391
|
+
);
|
|
3459
3392
|
boundsFilled = true;
|
|
3460
3393
|
}
|
|
3461
3394
|
applySnapshot(boundsFilled);
|
|
@@ -3569,8 +3502,8 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3569
3502
|
};
|
|
3570
3503
|
const tileBoundsFromLookup = (lookup, elevationBase) => {
|
|
3571
3504
|
if (!lookup.found || lookup.leafIndex >= state.frontLeafCount) return null;
|
|
3572
|
-
const rawMin = state.frontTileBounds[lookup.leafIndex *
|
|
3573
|
-
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];
|
|
3574
3507
|
const a = elevationBase + rawMin * config.elevationScale;
|
|
3575
3508
|
const b = elevationBase + rawMax * config.elevationScale;
|
|
3576
3509
|
return {
|
|
@@ -3648,6 +3581,9 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3648
3581
|
get hasSurface() {
|
|
3649
3582
|
return surfaceOps !== null;
|
|
3650
3583
|
},
|
|
3584
|
+
setSurfaceOps(nextSurfaceOps) {
|
|
3585
|
+
surfaceOps = nextSurfaceOps;
|
|
3586
|
+
},
|
|
3651
3587
|
updateConfig(nextConfig) {
|
|
3652
3588
|
config = nextConfig;
|
|
3653
3589
|
shape.edgeVertexCount = config.innerTileSegments + 3;
|
|
@@ -3795,72 +3731,132 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3795
3731
|
return lookupTileElevationRange(state.elevationPyramid, space, level, x, y, out);
|
|
3796
3732
|
}
|
|
3797
3733
|
};
|
|
3798
|
-
return api;
|
|
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
|
+
};
|
|
3799
3780
|
}
|
|
3800
3781
|
|
|
3801
|
-
const
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
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");
|
|
3819
|
+
|
|
3820
|
+
const createElevationFieldContextTask = work.task((get, work) => {
|
|
3838
3821
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
3822
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
3823
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
3839
3824
|
return work(() => {
|
|
3840
|
-
const data = new Float32Array(
|
|
3825
|
+
const data = new Float32Array(totalElements);
|
|
3841
3826
|
const attribute = new webgpu.StorageBufferAttribute(data, 1);
|
|
3842
|
-
attribute.name = "
|
|
3843
|
-
const node = tsl.storage(attribute, "float",
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3827
|
+
attribute.name = "elevationField";
|
|
3828
|
+
const node = tsl.storage(attribute, "float", totalElements).setName("elevationField");
|
|
3829
|
+
return {
|
|
3830
|
+
data,
|
|
3831
|
+
attribute,
|
|
3832
|
+
node
|
|
3833
|
+
};
|
|
3849
3834
|
});
|
|
3850
|
-
}).displayName("
|
|
3851
|
-
const
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
return
|
|
3857
|
-
|
|
3858
|
-
|
|
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);
|
|
3859
3856
|
}
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
).displayName("tileBoundsReductionTask").lane("gpu");
|
|
3857
|
+
];
|
|
3858
|
+
});
|
|
3859
|
+
}).displayName("elevationFieldStageTask");
|
|
3864
3860
|
|
|
3865
3861
|
const terrainQueryTask = work.task((get, work) => {
|
|
3866
3862
|
const maxNodesValue = get(maxNodes);
|
|
@@ -3874,6 +3870,7 @@ const terrainQueryTask = work.task((get, work) => {
|
|
|
3874
3870
|
const projection = topologyValue.projection;
|
|
3875
3871
|
return work((prev) => {
|
|
3876
3872
|
const shapeKey = `${maxNodesValue}:${innerTileSegmentsValue}:${projection.kind}`;
|
|
3873
|
+
const resolvedRadius = projection.radius ?? radiusValue;
|
|
3877
3874
|
const configValues = {
|
|
3878
3875
|
rootSize: rootSizeValue,
|
|
3879
3876
|
originX: originValue.x,
|
|
@@ -3882,7 +3879,7 @@ const terrainQueryTask = work.task((get, work) => {
|
|
|
3882
3879
|
innerTileSegments: innerTileSegmentsValue,
|
|
3883
3880
|
elevationScale: elevationScaleValue,
|
|
3884
3881
|
maxLevel: maxLevelValue,
|
|
3885
|
-
radius:
|
|
3882
|
+
radius: resolvedRadius,
|
|
3886
3883
|
baseU: projection.baseResolution?.u ?? 1,
|
|
3887
3884
|
baseV: projection.baseResolution?.v ?? 1
|
|
3888
3885
|
};
|
|
@@ -3897,9 +3894,15 @@ const terrainQueryTask = work.task((get, work) => {
|
|
|
3897
3894
|
query = runtime.query;
|
|
3898
3895
|
surfaceQuery = runtime.surfaceQuery;
|
|
3899
3896
|
sphereQuery = runtime.sphereQuery;
|
|
3897
|
+
} else if (prev?.projection !== projection) {
|
|
3898
|
+
cache.setSurfaceOps(projection.cpu.createSurfaceOps());
|
|
3899
|
+
const runtime = projection.cpu.createRuntimeQueries(cache);
|
|
3900
|
+
query = runtime.query;
|
|
3901
|
+
surfaceQuery = runtime.surfaceQuery;
|
|
3902
|
+
sphereQuery = runtime.sphereQuery;
|
|
3900
3903
|
}
|
|
3901
3904
|
cache.updateConfig(configValues);
|
|
3902
|
-
return { cache, query, surfaceQuery, sphereQuery, shapeKey };
|
|
3905
|
+
return { cache, query, surfaceQuery, sphereQuery, shapeKey, projection };
|
|
3903
3906
|
});
|
|
3904
3907
|
}).displayName("terrainQueryTask");
|
|
3905
3908
|
const terrainReadbackTask = work.task(
|
|
@@ -3946,27 +3949,19 @@ const quadtreeConfigTask = work.task((get, work) => {
|
|
|
3946
3949
|
const quadtreeUpdateTask = work.task((get, work) => {
|
|
3947
3950
|
const quadtreeConfig = get(quadtreeConfigTask);
|
|
3948
3951
|
const quadtreeUpdateConfig = get(quadtreeUpdate);
|
|
3949
|
-
const {
|
|
3952
|
+
const { cache } = get(terrainQueryTask);
|
|
3950
3953
|
const elevationScaleValue = get(elevationScale);
|
|
3951
3954
|
let outLeaves = void 0;
|
|
3952
|
-
const cameraPosition = new three.Vector3();
|
|
3953
3955
|
const elevationRangeScratch = { min: 0, max: 0 };
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
cameraPosition.set(cam.x, cam.y, cam.z);
|
|
3958
|
-
quadtreeUpdateConfig.elevationAtCameraXZ = surfaceQuery.getElevationByPosition(cameraPosition) ?? 0;
|
|
3959
|
-
} else {
|
|
3960
|
-
quadtreeUpdateConfig.elevationAtCameraXZ = terrainQuery.getElevation(cam.x, cam.z) ?? 0;
|
|
3956
|
+
quadtreeUpdateConfig.tileElevationRange = (tile, out) => {
|
|
3957
|
+
if (!cache.getTileElevationRange(tile.space, tile.level, tile.x, tile.y, elevationRangeScratch)) {
|
|
3958
|
+
return false;
|
|
3961
3959
|
}
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
out.max = elevationRangeScratch.max * elevationScaleValue;
|
|
3968
|
-
return true;
|
|
3969
|
-
};
|
|
3960
|
+
out.min = elevationRangeScratch.min * elevationScaleValue;
|
|
3961
|
+
out.max = elevationRangeScratch.max * elevationScaleValue;
|
|
3962
|
+
return true;
|
|
3963
|
+
};
|
|
3964
|
+
return work(() => {
|
|
3970
3965
|
outLeaves = update(
|
|
3971
3966
|
quadtreeConfig.state,
|
|
3972
3967
|
quadtreeConfig.topology,
|
|
@@ -4004,107 +3999,88 @@ const leafGpuBufferTask = work.task((get, work) => {
|
|
|
4004
3999
|
});
|
|
4005
4000
|
}).displayName("leafGpuBufferTask");
|
|
4006
4001
|
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
const
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
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]);
|
|
4028
4056
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
innerTileSegments: get(innerTileSegments),
|
|
4038
|
-
skirtScale: get(skirtScale),
|
|
4039
|
-
elevationScale: get(elevationScale),
|
|
4040
|
-
radius: get(radius),
|
|
4041
|
-
instanceId: get(instanceIdTask)
|
|
4042
|
-
};
|
|
4043
|
-
return work(() => createTerrainUniforms(uniformParams));
|
|
4044
|
-
}).displayName("createUniformsTask").cache("once");
|
|
4045
|
-
const updateUniformsTask = work.task((get, work) => {
|
|
4046
|
-
const terrainUniformsContext = get(createUniformsTask);
|
|
4047
|
-
const rootSizeVal = get(rootSize);
|
|
4048
|
-
const rootOrigin = get(origin);
|
|
4049
|
-
const innerTileSegmentsVal = get(innerTileSegments);
|
|
4050
|
-
const skirtScaleVal = get(skirtScale);
|
|
4051
|
-
const elevationScaleVal = get(elevationScale);
|
|
4052
|
-
const radiusVal = get(radius);
|
|
4053
|
-
return work(() => {
|
|
4054
|
-
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
4055
|
-
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
4056
|
-
rootOrigin.x,
|
|
4057
|
-
rootOrigin.y,
|
|
4058
|
-
rootOrigin.z
|
|
4059
|
-
);
|
|
4060
|
-
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
4061
|
-
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
4062
|
-
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
4063
|
-
terrainUniformsContext.uRadius.value = radiusVal;
|
|
4064
|
-
return terrainUniformsContext;
|
|
4065
|
-
});
|
|
4066
|
-
}).displayName("updateUniformsTask");
|
|
4067
|
-
|
|
4068
|
-
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);
|
|
4069
4065
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
4070
|
-
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
4071
|
-
const totalElements = get(maxNodes) * verticesPerNode;
|
|
4072
4066
|
return work(() => {
|
|
4073
|
-
const
|
|
4067
|
+
const floatCount = maxNodesValue * TILE_BOUNDS_FLOATS_PER_TILE;
|
|
4068
|
+
const data = new Float32Array(floatCount);
|
|
4074
4069
|
const attribute = new webgpu.StorageBufferAttribute(data, 1);
|
|
4075
|
-
attribute.name = "
|
|
4076
|
-
const node = tsl.storage(attribute, "float",
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
const topology = get(topologyTask);
|
|
4088
|
-
return work(() => {
|
|
4089
|
-
return createTileCompute(leafStorage, uniforms, topology.projection);
|
|
4090
|
-
});
|
|
4091
|
-
}).displayName("tileNodesTask");
|
|
4092
|
-
const elevationFieldStageTask = work.task((get, work) => {
|
|
4093
|
-
const tile = get(tileNodesTask);
|
|
4094
|
-
const uniforms = get(updateUniformsTask);
|
|
4095
|
-
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
4096
|
-
const userElevationFn = get(elevationFn);
|
|
4097
|
-
return work(() => {
|
|
4098
|
-
const heightFn = createElevationFunction(userElevationFn);
|
|
4099
|
-
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
4100
|
-
return [
|
|
4101
|
-
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
4102
|
-
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
4103
|
-
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
4104
|
-
}
|
|
4105
|
-
];
|
|
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 };
|
|
4106
4082
|
});
|
|
4107
|
-
}).displayName("
|
|
4083
|
+
}).displayName("tileBoundsContextTask");
|
|
4108
4084
|
|
|
4109
4085
|
const createTerrainFieldTextureTask = work.task(
|
|
4110
4086
|
(get, work, { resources }) => {
|
|
@@ -4129,6 +4105,7 @@ const terrainFieldStageTask = work.task((get, work) => {
|
|
|
4129
4105
|
const tile = get(tileNodesTask);
|
|
4130
4106
|
const uniforms = get(updateUniformsTask);
|
|
4131
4107
|
const topology = get(topologyTask);
|
|
4108
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4132
4109
|
return work(() => {
|
|
4133
4110
|
const computeNormal = topology.projection.gpu.createFieldNormal({
|
|
4134
4111
|
elevationFieldNode: elevationFieldContext.node,
|
|
@@ -4143,12 +4120,13 @@ const terrainFieldStageTask = work.task((get, work) => {
|
|
|
4143
4120
|
const iy = tsl.int(localCoordinates.y);
|
|
4144
4121
|
const height = elevationFieldContext.node.element(globalVertexIndex);
|
|
4145
4122
|
const normal = computeNormal(nodeIndex, ix, iy);
|
|
4123
|
+
const { packMin, packMax } = loadTilePackBounds(boundsContext.node, nodeIndex);
|
|
4146
4124
|
storeTerrainField(
|
|
4147
4125
|
terrainFieldStorage,
|
|
4148
4126
|
ix,
|
|
4149
4127
|
iy,
|
|
4150
4128
|
nodeIndex,
|
|
4151
|
-
|
|
4129
|
+
packNormalizedTerrainFieldSample(height, normal, packMin, packMax)
|
|
4152
4130
|
);
|
|
4153
4131
|
}
|
|
4154
4132
|
];
|
|
@@ -4160,23 +4138,28 @@ function createComputePipelineTasks(leafStageTask) {
|
|
|
4160
4138
|
const compile = work.task((get, work) => {
|
|
4161
4139
|
const pipeline = get(leafStageTask);
|
|
4162
4140
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
4141
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4163
4142
|
return work(
|
|
4164
4143
|
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
4165
|
-
|
|
4144
|
+
midPipelineExecute: (renderer, instanceCount) => {
|
|
4145
|
+
runTileBoundsReduction(renderer, boundsContext, instanceCount);
|
|
4146
|
+
}
|
|
4147
|
+
})
|
|
4166
4148
|
);
|
|
4167
4149
|
}).displayName("compileComputeTask");
|
|
4168
|
-
const execute = work.task(
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
}
|
|
4175
|
-
);
|
|
4176
|
-
}
|
|
4177
|
-
).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");
|
|
4178
4156
|
return { compile, execute };
|
|
4179
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");
|
|
4180
4163
|
|
|
4181
4164
|
const gpuSpatialIndexStorageTask = work.task((get, work) => {
|
|
4182
4165
|
const maxNodesValue = get(maxNodes);
|
|
@@ -4194,6 +4177,7 @@ const gpuSpatialIndexUploadTask = work.task((get, work) => {
|
|
|
4194
4177
|
|
|
4195
4178
|
const createTerrainSamplerTask = work.task((get, work) => {
|
|
4196
4179
|
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
4180
|
+
const tileBoundsContext = get(tileBoundsContextTask);
|
|
4197
4181
|
const spatialIndex = get(gpuSpatialIndexStorageTask);
|
|
4198
4182
|
const uniforms = get(updateUniformsTask);
|
|
4199
4183
|
const elevationCallback = get(elevationFn);
|
|
@@ -4202,6 +4186,7 @@ const createTerrainSamplerTask = work.task((get, work) => {
|
|
|
4202
4186
|
return work(
|
|
4203
4187
|
() => createTerrainSampler({
|
|
4204
4188
|
terrainFieldStorage,
|
|
4189
|
+
tileBoundsNode: tileBoundsContext.node,
|
|
4205
4190
|
spatialIndex,
|
|
4206
4191
|
uniforms,
|
|
4207
4192
|
elevationCallback,
|
|
@@ -4215,12 +4200,14 @@ const positionNodeTask = work.task((get, work) => {
|
|
|
4215
4200
|
const leafStorage = get(leafStorageTask);
|
|
4216
4201
|
const terrainUniforms = get(updateUniformsTask);
|
|
4217
4202
|
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
4203
|
+
const tileBoundsContext = get(tileBoundsContextTask);
|
|
4218
4204
|
const topology = get(topologyTask);
|
|
4219
4205
|
return work(
|
|
4220
4206
|
() => topology.projection.gpu.renderVertexPosition({
|
|
4221
4207
|
leafStorage,
|
|
4222
4208
|
uniforms: terrainUniforms,
|
|
4223
|
-
terrainFieldStorage
|
|
4209
|
+
terrainFieldStorage,
|
|
4210
|
+
tileBoundsNode: tileBoundsContext.node
|
|
4224
4211
|
})
|
|
4225
4212
|
);
|
|
4226
4213
|
}).displayName("positionNodeTask");
|
|
@@ -4341,6 +4328,17 @@ function terrainGraph() {
|
|
|
4341
4328
|
return g;
|
|
4342
4329
|
}
|
|
4343
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
|
+
|
|
4344
4342
|
const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
|
|
4345
4343
|
return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
|
|
4346
4344
|
});
|
|
@@ -4398,6 +4396,12 @@ exports.AtlasBackend = AtlasBackend;
|
|
|
4398
4396
|
exports.CUBE_FACES = CUBE_FACES;
|
|
4399
4397
|
exports.CUBE_FACE_COUNT = CUBE_FACE_COUNT;
|
|
4400
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;
|
|
4401
4405
|
exports.TerrainGeometry = TerrainGeometry;
|
|
4402
4406
|
exports.TerrainMesh = TerrainMesh;
|
|
4403
4407
|
exports.U32_EMPTY = U32_EMPTY;
|
|
@@ -4434,6 +4438,8 @@ exports.cubeFaceDirection = cubeFaceDirection;
|
|
|
4434
4438
|
exports.cubeFaceFromDirection = cubeFaceFromDirection;
|
|
4435
4439
|
exports.cubeFacePoint = cubeFacePoint;
|
|
4436
4440
|
exports.cubeFaceUVFromDirection = cubeFaceUVFromDirection;
|
|
4441
|
+
exports.decodeUint16RG = decodeUint16RG;
|
|
4442
|
+
exports.denormalizeTerrainFieldElevation = denormalizeTerrainFieldElevation;
|
|
4437
4443
|
exports.deriveNormalZ = deriveNormalZ;
|
|
4438
4444
|
exports.directionToFace = directionToFace;
|
|
4439
4445
|
exports.directionToFaceUV = directionToFaceUV;
|
|
@@ -4456,9 +4462,11 @@ exports.leafStorageTask = leafStorageTask;
|
|
|
4456
4462
|
exports.loadTerrainField = loadTerrainField;
|
|
4457
4463
|
exports.loadTerrainFieldElevation = loadTerrainFieldElevation;
|
|
4458
4464
|
exports.loadTerrainFieldNormal = loadTerrainFieldNormal;
|
|
4465
|
+
exports.loadTilePackBounds = loadTilePackBounds;
|
|
4459
4466
|
exports.maxLevel = maxLevel;
|
|
4460
4467
|
exports.maxNodes = maxNodes;
|
|
4461
4468
|
exports.origin = origin;
|
|
4469
|
+
exports.packNormalizedTerrainFieldSample = packNormalizedTerrainFieldSample;
|
|
4462
4470
|
exports.packTerrainFieldSample = packTerrainFieldSample;
|
|
4463
4471
|
exports.positionNodeTask = positionNodeTask;
|
|
4464
4472
|
exports.positionToTorusParams = positionToTorusParams;
|
|
@@ -4469,6 +4477,8 @@ exports.radius = radius;
|
|
|
4469
4477
|
exports.resetLeafSet = resetLeafSet;
|
|
4470
4478
|
exports.resetSeamTable = resetSeamTable;
|
|
4471
4479
|
exports.rootSize = rootSize;
|
|
4480
|
+
exports.runTileBoundsReduction = runTileBoundsReduction;
|
|
4481
|
+
exports.sampleHeightmapMeters = sampleHeightmapMeters;
|
|
4472
4482
|
exports.sampleTerrainField = sampleTerrainField;
|
|
4473
4483
|
exports.sampleTerrainFieldElevation = sampleTerrainFieldElevation;
|
|
4474
4484
|
exports.skirtScale = skirtScale;
|
|
@@ -4483,6 +4493,8 @@ exports.terrainRaycastTask = terrainRaycastTask;
|
|
|
4483
4493
|
exports.terrainReadbackTask = terrainReadbackTask;
|
|
4484
4494
|
exports.terrainTasks = terrainTasks;
|
|
4485
4495
|
exports.textureSpaceToVectorSpace = textureSpaceToVectorSpace;
|
|
4496
|
+
exports.tileBoundsContextTask = tileBoundsContextTask;
|
|
4497
|
+
exports.tileBoundsReductionTask = tileBoundsReductionTask;
|
|
4486
4498
|
exports.tileNodesTask = tileNodesTask;
|
|
4487
4499
|
exports.topology = topology;
|
|
4488
4500
|
exports.topologyTask = topologyTask;
|