@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.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BufferGeometry, BufferAttribute, RGBAFormat, ClampToEdgeWrapping, HalfFloatType, FloatType, LinearFilter, NearestFilter, Vector3 } from 'three';
|
|
2
|
-
import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageTexture, StorageArrayTexture,
|
|
2
|
+
import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageBufferAttribute, StorageTexture, StorageArrayTexture, Vector3 as Vector3$1 } from 'three/webgpu';
|
|
3
3
|
import { param, task, graph } from '@hello-terrain/work';
|
|
4
|
-
import { float, uniform, Fn, globalId, int, vec2, uint, If, vec3,
|
|
4
|
+
import { float, uniform, Fn, globalId, int, vec2, uint, If, storage, vec3, cross, vertexIndex, uv, max, textureStore, uvec3, vec4, texture, ivec2, ivec3, textureLoad, pow, instanceIndex, positionLocal, select, normalLocal, Loop, Break, sin, cos, bool, workgroupArray, localId, workgroupId, min, workgroupBarrier, remap, dot as dot$1, varyingProperty, mx_noise_float, mix } from 'three/tsl';
|
|
5
5
|
import { Fn as Fn$1 } from 'three/src/nodes/TSL.js';
|
|
6
6
|
|
|
7
7
|
class TerrainGeometry extends BufferGeometry {
|
|
@@ -359,6 +359,7 @@ function compileComputePipeline(stages, width, options) {
|
|
|
359
359
|
WORKGROUP_X,
|
|
360
360
|
WORKGROUP_Y
|
|
361
361
|
];
|
|
362
|
+
const midPipelineExecute = options?.midPipelineExecute;
|
|
362
363
|
const uInstanceCount = uniform(0, "uint").setName("uInstanceCount");
|
|
363
364
|
const stagedKernelCache = /* @__PURE__ */ new Map();
|
|
364
365
|
function clampWorkgroupToLimits(requested, limits) {
|
|
@@ -419,281 +420,14 @@ function compileComputePipeline(stages, width, options) {
|
|
|
419
420
|
}
|
|
420
421
|
const dispatchX = Math.ceil(width / workgroupX);
|
|
421
422
|
const dispatchY = Math.ceil(width / workgroupY);
|
|
422
|
-
for (
|
|
423
|
-
renderer.compute(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return format === "rgba16float" ? HalfFloatType : FloatType;
|
|
431
|
-
}
|
|
432
|
-
function resolveFilter(mode) {
|
|
433
|
-
return mode === "linear" ? LinearFilter : NearestFilter;
|
|
434
|
-
}
|
|
435
|
-
function configureStorageTexture(texture2, format, filter) {
|
|
436
|
-
texture2.format = RGBAFormat;
|
|
437
|
-
texture2.type = resolveType(format);
|
|
438
|
-
texture2.magFilter = resolveFilter(filter);
|
|
439
|
-
texture2.minFilter = resolveFilter(filter);
|
|
440
|
-
texture2.wrapS = ClampToEdgeWrapping;
|
|
441
|
-
texture2.wrapT = ClampToEdgeWrapping;
|
|
442
|
-
texture2.generateMipmaps = false;
|
|
443
|
-
texture2.needsUpdate = true;
|
|
444
|
-
}
|
|
445
|
-
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
446
|
-
let currentEdgeVertexCount = edgeVertexCount;
|
|
447
|
-
let currentTileCount = tileCount;
|
|
448
|
-
const tex = new StorageArrayTexture(
|
|
449
|
-
edgeVertexCount,
|
|
450
|
-
edgeVertexCount,
|
|
451
|
-
tileCount
|
|
452
|
-
);
|
|
453
|
-
tex.name = "terrainField";
|
|
454
|
-
configureStorageTexture(tex, options.format, options.filter);
|
|
455
|
-
return {
|
|
456
|
-
backendType: "array-texture",
|
|
457
|
-
get edgeVertexCount() {
|
|
458
|
-
return currentEdgeVertexCount;
|
|
459
|
-
},
|
|
460
|
-
get tileCount() {
|
|
461
|
-
return currentTileCount;
|
|
462
|
-
},
|
|
463
|
-
texture: tex,
|
|
464
|
-
uv(ix, iy, _tileIndex) {
|
|
465
|
-
return vec2(ix.toFloat(), iy.toFloat());
|
|
466
|
-
},
|
|
467
|
-
texel(ix, iy, tileIndex) {
|
|
468
|
-
return ivec3(ix, iy, tileIndex);
|
|
469
|
-
},
|
|
470
|
-
sample(u, v, tileIndex) {
|
|
471
|
-
return texture(tex, vec2(u, v)).depth(int(tileIndex));
|
|
472
|
-
},
|
|
473
|
-
resize(width, height, nextTileCount) {
|
|
474
|
-
currentEdgeVertexCount = width;
|
|
475
|
-
currentTileCount = nextTileCount;
|
|
476
|
-
tex.setSize(width, height, nextTileCount);
|
|
477
|
-
tex.needsUpdate = true;
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
|
|
482
|
-
const tilesPerRowNode = int(tilesPerRow);
|
|
483
|
-
const edge = int(edgeVertexCount);
|
|
484
|
-
const tile = int(tileIndex);
|
|
485
|
-
const col = tile.mod(tilesPerRowNode);
|
|
486
|
-
const row = tile.div(tilesPerRowNode);
|
|
487
|
-
const atlasX = col.mul(edge).add(int(ix));
|
|
488
|
-
const atlasY = row.mul(edge).add(int(iy));
|
|
489
|
-
return { atlasX, atlasY };
|
|
490
|
-
}
|
|
491
|
-
function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
492
|
-
let currentEdgeVertexCount = edgeVertexCount;
|
|
493
|
-
let currentTileCount = tileCount;
|
|
494
|
-
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
495
|
-
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
496
|
-
const tex = new StorageTexture(atlasSize, atlasSize);
|
|
497
|
-
tex.name = "terrainFieldAtlas";
|
|
498
|
-
configureStorageTexture(tex, options.format, options.filter);
|
|
499
|
-
return {
|
|
500
|
-
backendType: "atlas",
|
|
501
|
-
get edgeVertexCount() {
|
|
502
|
-
return currentEdgeVertexCount;
|
|
503
|
-
},
|
|
504
|
-
get tileCount() {
|
|
505
|
-
return currentTileCount;
|
|
506
|
-
},
|
|
507
|
-
texture: tex,
|
|
508
|
-
uv(ix, iy, tileIndex) {
|
|
509
|
-
const { atlasX, atlasY } = atlasCoord(
|
|
510
|
-
tilesPerRow,
|
|
511
|
-
currentEdgeVertexCount,
|
|
512
|
-
ix,
|
|
513
|
-
iy,
|
|
514
|
-
tileIndex
|
|
515
|
-
);
|
|
516
|
-
const currentAtlasSize = float(tilesPerRow * currentEdgeVertexCount);
|
|
517
|
-
return vec2(
|
|
518
|
-
atlasX.toFloat().add(0.5).div(currentAtlasSize),
|
|
519
|
-
atlasY.toFloat().add(0.5).div(currentAtlasSize)
|
|
520
|
-
);
|
|
521
|
-
},
|
|
522
|
-
texel(ix, iy, tileIndex) {
|
|
523
|
-
const { atlasX, atlasY } = atlasCoord(
|
|
524
|
-
tilesPerRow,
|
|
525
|
-
currentEdgeVertexCount,
|
|
526
|
-
ix,
|
|
527
|
-
iy,
|
|
528
|
-
tileIndex
|
|
529
|
-
);
|
|
530
|
-
return ivec2(atlasX, atlasY);
|
|
531
|
-
},
|
|
532
|
-
sample(u, v, tileIndex) {
|
|
533
|
-
const tile = int(tileIndex);
|
|
534
|
-
const tilesPerRowNode = int(tilesPerRow);
|
|
535
|
-
const col = tile.mod(tilesPerRowNode);
|
|
536
|
-
const row = tile.div(tilesPerRowNode);
|
|
537
|
-
const invTilesPerRow = float(1 / tilesPerRow);
|
|
538
|
-
const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
|
|
539
|
-
const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
|
|
540
|
-
return texture(tex, vec2(atlasU, atlasV));
|
|
541
|
-
},
|
|
542
|
-
resize(width, height, nextTileCount) {
|
|
543
|
-
currentEdgeVertexCount = width;
|
|
544
|
-
currentTileCount = nextTileCount;
|
|
545
|
-
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
546
|
-
const nextAtlasSize = tilesPerRow * width;
|
|
547
|
-
const image = tex.image;
|
|
548
|
-
image.width = nextAtlasSize;
|
|
549
|
-
image.height = nextAtlasSize;
|
|
550
|
-
tex.needsUpdate = true;
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
function texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
555
|
-
const storage = ArrayTextureBackend(edgeVertexCount, tileCount, options);
|
|
556
|
-
return { ...storage, backendType: "texture-3d" };
|
|
557
|
-
}
|
|
558
|
-
function tryGetDeviceLimits(renderer) {
|
|
559
|
-
const backend = renderer;
|
|
560
|
-
return backend.backend?.device?.limits ?? {};
|
|
561
|
-
}
|
|
562
|
-
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
563
|
-
const filter = options.filter ?? "linear";
|
|
564
|
-
const format = options.format ?? "rgba16float";
|
|
565
|
-
const forcedBackend = options.backend;
|
|
566
|
-
if (forcedBackend === "atlas") {
|
|
567
|
-
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
568
|
-
}
|
|
569
|
-
if (forcedBackend === "texture-3d") {
|
|
570
|
-
return texture3DBackend(edgeVertexCount, tileCount, { filter, format });
|
|
571
|
-
}
|
|
572
|
-
if (forcedBackend === "array-texture") {
|
|
573
|
-
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
574
|
-
}
|
|
575
|
-
const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
|
|
576
|
-
const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
|
|
577
|
-
if (tileCount > maxLayers) {
|
|
578
|
-
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
579
|
-
}
|
|
580
|
-
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
581
|
-
}
|
|
582
|
-
function storeTerrainField(storage, ix, iy, tileIndex, value) {
|
|
583
|
-
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
584
|
-
return textureStore(
|
|
585
|
-
storage.texture,
|
|
586
|
-
uvec3(int(ix), int(iy), int(tileIndex)),
|
|
587
|
-
value
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
return textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
|
|
591
|
-
}
|
|
592
|
-
function loadTerrainField(storage, ix, iy, tileIndex) {
|
|
593
|
-
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
594
|
-
return textureLoad(storage.texture, ivec2(int(ix), int(iy)), int(0)).depth(
|
|
595
|
-
int(tileIndex)
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
return textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), int(0));
|
|
599
|
-
}
|
|
600
|
-
function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
601
|
-
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
602
|
-
}
|
|
603
|
-
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
604
|
-
const raw = loadTerrainField(storage, ix, iy, tileIndex);
|
|
605
|
-
return vec3(raw.g, raw.b, raw.a);
|
|
606
|
-
}
|
|
607
|
-
function sampleTerrainField(storage, u, v, tileIndex) {
|
|
608
|
-
return storage.sample(u, v, tileIndex);
|
|
609
|
-
}
|
|
610
|
-
function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
|
|
611
|
-
return sampleTerrainField(storage, u, v, tileIndex).r;
|
|
612
|
-
}
|
|
613
|
-
function packTerrainFieldSample(height, normal) {
|
|
614
|
-
return vec4(height, normal.x, normal.y, normal.z);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const createElevation = (tile, uniforms, elevationFn) => {
|
|
618
|
-
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
619
|
-
const ix = int(localCoordinates.x);
|
|
620
|
-
const iy = int(localCoordinates.y);
|
|
621
|
-
const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(int(3));
|
|
622
|
-
const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
|
|
623
|
-
const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
|
|
624
|
-
const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
|
|
625
|
-
const rootSize = uniforms.uRootSize.toVar();
|
|
626
|
-
return elevationFn({
|
|
627
|
-
worldPosition,
|
|
628
|
-
rootSize,
|
|
629
|
-
rootUV,
|
|
630
|
-
tileOriginVec2: tile.tileOriginVec2(nodeIndex),
|
|
631
|
-
tileSize: tile.tileSize(nodeIndex),
|
|
632
|
-
tileLevel: tile.tileLevel(nodeIndex),
|
|
633
|
-
nodeIndex: int(nodeIndex),
|
|
634
|
-
tileUV
|
|
635
|
-
});
|
|
636
|
-
};
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const HALF_PI = Math.PI * 0.5;
|
|
640
|
-
const FIELD_INNER_TEXEL_OFFSET = 1.5;
|
|
641
|
-
const FIELD_EDGE_EXTRA_TEXELS = 3;
|
|
642
|
-
function sphereTileArcLength(radius, levelDivisor) {
|
|
643
|
-
return radius * HALF_PI / levelDivisor;
|
|
644
|
-
}
|
|
645
|
-
function decodeLeafTile(leafStorage, nodeIndex) {
|
|
646
|
-
const nodeOffset = int(nodeIndex).mul(int(4));
|
|
647
|
-
return {
|
|
648
|
-
level: leafStorage.node.element(nodeOffset).toInt(),
|
|
649
|
-
x: leafStorage.node.element(nodeOffset.add(int(1))).toFloat(),
|
|
650
|
-
y: leafStorage.node.element(nodeOffset.add(int(2))).toFloat(),
|
|
651
|
-
face: leafStorage.node.element(nodeOffset.add(int(3))).toInt()
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
function faceUVFromTileLocal(tile, localU, localV, baseU = float(1), baseV = float(1)) {
|
|
655
|
-
const levelScale = pow(float(2), tile.level.toFloat());
|
|
656
|
-
const nU = baseU.mul(levelScale);
|
|
657
|
-
const nV = baseV.mul(levelScale);
|
|
658
|
-
return vec2(tile.x.add(localU).div(nU), tile.y.add(localV).div(nV));
|
|
659
|
-
}
|
|
660
|
-
function createTileCompute(leafStorage, uniforms, projection) {
|
|
661
|
-
const baseU = float(projection.baseResolution?.u ?? 1);
|
|
662
|
-
const baseV = float(projection.baseResolution?.v ?? 1);
|
|
663
|
-
const tileLevel = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).level);
|
|
664
|
-
const tileFace = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).face);
|
|
665
|
-
const tileOriginVec2 = Fn(([nodeIndex]) => {
|
|
666
|
-
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
667
|
-
return vec2(tile.x, tile.y);
|
|
668
|
-
});
|
|
669
|
-
const tileFaceUV = Fn(([nodeIndex, ix, iy]) => {
|
|
670
|
-
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
671
|
-
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
672
|
-
const localU = int(ix).toFloat().sub(float(1)).div(fInnerSegments);
|
|
673
|
-
const localV = int(iy).toFloat().sub(float(1)).div(fInnerSegments);
|
|
674
|
-
return faceUVFromTileLocal(tile, localU, localV, baseU, baseV);
|
|
675
|
-
});
|
|
676
|
-
const shared = {
|
|
677
|
-
tileLevel: (nodeIndex) => tileLevel(nodeIndex),
|
|
678
|
-
tileFace: (nodeIndex) => tileFace(nodeIndex),
|
|
679
|
-
tileOriginVec2: (nodeIndex) => tileOriginVec2(nodeIndex),
|
|
680
|
-
tileFaceUV: (nodeIndex, ix, iy) => tileFaceUV(nodeIndex, ix, iy)
|
|
681
|
-
};
|
|
682
|
-
const parts = projection.gpu.createTileComputeParts({ leafStorage, uniforms, shared });
|
|
683
|
-
return {
|
|
684
|
-
...shared,
|
|
685
|
-
tileSize: parts.tileSize,
|
|
686
|
-
rootUVCompute: parts.rootUV,
|
|
687
|
-
tileVertexWorldPositionCompute: parts.tileVertexWorldPosition
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
function tileLocalToFieldUV(localCoord, innerSegments) {
|
|
691
|
-
const edge = float(innerSegments).add(float(FIELD_EDGE_EXTRA_TEXELS));
|
|
692
|
-
return float(localCoord).mul(float(innerSegments)).add(float(FIELD_INNER_TEXEL_OFFSET)).div(edge);
|
|
693
|
-
}
|
|
694
|
-
function tileLocalToFieldUVNumber(localCoord, innerSegments) {
|
|
695
|
-
const edge = innerSegments + FIELD_EDGE_EXTRA_TEXELS;
|
|
696
|
-
return (localCoord * innerSegments + FIELD_INNER_TEXEL_OFFSET) / edge;
|
|
423
|
+
for (let stageIndex = 0; stageIndex < stagedKernels.length; stageIndex += 1) {
|
|
424
|
+
renderer.compute(stagedKernels[stageIndex], [dispatchX, dispatchY, instanceCount]);
|
|
425
|
+
if (midPipelineExecute && stagedKernels.length > 1 && stageIndex === stagedKernels.length - 2) {
|
|
426
|
+
midPipelineExecute(renderer, instanceCount);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return { execute };
|
|
697
431
|
}
|
|
698
432
|
|
|
699
433
|
function createLeafStorage(maxNodes) {
|
|
@@ -935,20 +669,15 @@ function beginUpdate(state, topology, params) {
|
|
|
935
669
|
|
|
936
670
|
function shouldSplit(bounds, level, maxLevel, params) {
|
|
937
671
|
if (level >= maxLevel) return false;
|
|
938
|
-
const mode = params.mode ?? "distance";
|
|
939
672
|
const cx = bounds.cx;
|
|
940
673
|
const cy = bounds.cy;
|
|
941
674
|
const cz = bounds.cz;
|
|
942
675
|
const distSq = cx * cx + cy * cy + cz * cz;
|
|
943
676
|
const safeDistSq = distSq > 1e-12 ? distSq : 1e-12;
|
|
944
|
-
if (mode === "screen") {
|
|
945
|
-
const proj = params.projectionFactor
|
|
946
|
-
const target = params.targetPixels
|
|
947
|
-
if (proj <= 0 || target <= 0)
|
|
948
|
-
const f2 = params.distanceFactor ?? 2;
|
|
949
|
-
const threshold2 = bounds.r * f2;
|
|
950
|
-
return safeDistSq < threshold2 * threshold2;
|
|
951
|
-
}
|
|
677
|
+
if (params.mode === "screen") {
|
|
678
|
+
const proj = params.projectionFactor;
|
|
679
|
+
const target = params.targetPixels;
|
|
680
|
+
if (proj <= 0 || target <= 0) return false;
|
|
952
681
|
const left = bounds.r * bounds.r * proj * proj;
|
|
953
682
|
const right = safeDistSq * target * target;
|
|
954
683
|
return left > right;
|
|
@@ -982,7 +711,7 @@ function refineLeaves(state, topology, params, outLeaves) {
|
|
|
982
711
|
let elevationRange;
|
|
983
712
|
if (params.tileElevationRange) {
|
|
984
713
|
const range = state.scratchElevationRange;
|
|
985
|
-
if (params.tileElevationRange(
|
|
714
|
+
if (params.tileElevationRange(tile, range)) {
|
|
986
715
|
elevationRange = range;
|
|
987
716
|
}
|
|
988
717
|
}
|
|
@@ -1085,19 +814,9 @@ function balance2to1(state, topology, params, leaves) {
|
|
|
1085
814
|
}
|
|
1086
815
|
|
|
1087
816
|
function update(state, topology, params, outLeaves) {
|
|
1088
|
-
const cam = params.cameraOrigin;
|
|
1089
|
-
const elevation = params.elevationAtCameraXZ ?? 0;
|
|
1090
|
-
const origX = cam.x;
|
|
1091
|
-
const origY = cam.y;
|
|
1092
|
-
const origZ = cam.z;
|
|
1093
|
-
topology.projection.cpu.cameraSurfaceOffset(cam, elevation);
|
|
1094
817
|
beginUpdate(state, topology, params);
|
|
1095
818
|
const leaves = refineLeaves(state, topology, params, outLeaves);
|
|
1096
|
-
|
|
1097
|
-
cam.x = origX;
|
|
1098
|
-
cam.y = origY;
|
|
1099
|
-
cam.z = origZ;
|
|
1100
|
-
return result;
|
|
819
|
+
return balance2to1(state, topology, params, leaves);
|
|
1101
820
|
}
|
|
1102
821
|
|
|
1103
822
|
const scratchTile = { space: 0, level: 0, x: 0, y: 0 };
|
|
@@ -1194,94 +913,370 @@ function buildSeams2to1(topology, leaves, outSeams, outIndex) {
|
|
|
1194
913
|
return outSeams;
|
|
1195
914
|
}
|
|
1196
915
|
|
|
1197
|
-
function createFlatNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1198
|
-
return Fn(
|
|
1199
|
-
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
1200
|
-
const iEdge = int(edgeVertexCount);
|
|
1201
|
-
const verticesPerNode = iEdge.mul(iEdge);
|
|
1202
|
-
const baseOffset = int(nodeIndex).mul(verticesPerNode);
|
|
1203
|
-
const xLeft = int(ix).sub(int(1));
|
|
1204
|
-
const xRight = int(ix).add(int(1));
|
|
1205
|
-
const yUp = int(iy).sub(int(1));
|
|
1206
|
-
const yDown = int(iy).add(int(1));
|
|
1207
|
-
const hLeft = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
1208
|
-
const hRight = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
1209
|
-
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
1210
|
-
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
1211
|
-
const innerSegments = float(iEdge).sub(float(3));
|
|
1212
|
-
const stepWorld = tileSize.div(innerSegments);
|
|
1213
|
-
const inv2Step = float(0.5).div(stepWorld);
|
|
1214
|
-
const dhdx = float(hRight).sub(float(hLeft)).mul(inv2Step);
|
|
1215
|
-
const dhdz = float(hDown).sub(float(hUp)).mul(inv2Step);
|
|
1216
|
-
return vec3(dhdx.negate(), float(1), dhdz.negate()).normalize();
|
|
1217
|
-
}
|
|
1218
|
-
);
|
|
916
|
+
function createFlatNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
917
|
+
return Fn(
|
|
918
|
+
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
919
|
+
const iEdge = int(edgeVertexCount);
|
|
920
|
+
const verticesPerNode = iEdge.mul(iEdge);
|
|
921
|
+
const baseOffset = int(nodeIndex).mul(verticesPerNode);
|
|
922
|
+
const xLeft = int(ix).sub(int(1));
|
|
923
|
+
const xRight = int(ix).add(int(1));
|
|
924
|
+
const yUp = int(iy).sub(int(1));
|
|
925
|
+
const yDown = int(iy).add(int(1));
|
|
926
|
+
const hLeft = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
927
|
+
const hRight = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
928
|
+
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
929
|
+
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
930
|
+
const innerSegments = float(iEdge).sub(float(3));
|
|
931
|
+
const stepWorld = tileSize.div(innerSegments);
|
|
932
|
+
const inv2Step = float(0.5).div(stepWorld);
|
|
933
|
+
const dhdx = float(hRight).sub(float(hLeft)).mul(inv2Step);
|
|
934
|
+
const dhdz = float(hDown).sub(float(hUp)).mul(inv2Step);
|
|
935
|
+
return vec3(dhdx.negate(), float(1), dhdz.negate()).normalize();
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function createDisplacedSurfaceNormalFromElevationField(elevationFieldNode, edgeVertexCount, makeSurfaceFns) {
|
|
940
|
+
return Fn(([nodeIndex, ix, iy, elevationScale]) => {
|
|
941
|
+
const iEdge = int(edgeVertexCount);
|
|
942
|
+
const verticesPerNode = iEdge.mul(iEdge);
|
|
943
|
+
const baseOffset = int(nodeIndex).mul(verticesPerNode);
|
|
944
|
+
const xLeft = int(ix).sub(int(1));
|
|
945
|
+
const xRight = int(ix).add(int(1));
|
|
946
|
+
const yUp = int(iy).sub(int(1));
|
|
947
|
+
const yDown = int(iy).add(int(1));
|
|
948
|
+
const heightAt = (gx, gy) => elevationFieldNode.element(baseOffset.add(gy.mul(iEdge).add(gx))).mul(elevationScale);
|
|
949
|
+
const { positionAt, dirAt } = makeSurfaceFns(nodeIndex);
|
|
950
|
+
const pLeft = positionAt(xLeft, int(iy), heightAt(xLeft, int(iy)));
|
|
951
|
+
const pRight = positionAt(xRight, int(iy), heightAt(xRight, int(iy)));
|
|
952
|
+
const pUp = positionAt(int(ix), yUp, heightAt(int(ix), yUp));
|
|
953
|
+
const pDown = positionAt(int(ix), yDown, heightAt(int(ix), yDown));
|
|
954
|
+
const tangentU = pRight.sub(pLeft);
|
|
955
|
+
const tangentV = pDown.sub(pUp);
|
|
956
|
+
const normal = cross(tangentU, tangentV).normalize();
|
|
957
|
+
const dir = dirAt(int(ix), int(iy));
|
|
958
|
+
return normal.mul(normal.dot(dir).sign());
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const isSkirtVertex = Fn(([segments]) => {
|
|
963
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
964
|
+
const vIndex = int(vertexIndex);
|
|
965
|
+
const segmentEdges = int(segmentsNode.add(3));
|
|
966
|
+
const vx = vIndex.mod(segmentEdges);
|
|
967
|
+
const vy = vIndex.div(segmentEdges);
|
|
968
|
+
const last = segmentEdges.sub(int(1));
|
|
969
|
+
return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
|
|
970
|
+
});
|
|
971
|
+
const isSkirtUV = Fn(([segments]) => {
|
|
972
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
973
|
+
const ux = uv().x;
|
|
974
|
+
const uy = uv().y;
|
|
975
|
+
const segmentCount = segmentsNode.add(2);
|
|
976
|
+
const segmentStep = float(1).div(segmentCount);
|
|
977
|
+
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
978
|
+
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
979
|
+
return innerX.and(innerY).not();
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const TILE_BOUNDS_FLOATS_PER_TILE = 4;
|
|
983
|
+
const TILE_BOUNDS_LOD_MIN_OFFSET = 0;
|
|
984
|
+
const TILE_BOUNDS_LOD_MAX_OFFSET = 1;
|
|
985
|
+
const TILE_BOUNDS_PACK_MIN_OFFSET = 2;
|
|
986
|
+
const TILE_BOUNDS_PACK_MAX_OFFSET = 3;
|
|
987
|
+
const TERRAIN_FIELD_PACK_EPSILON = 1e-4;
|
|
988
|
+
function resolveType(format) {
|
|
989
|
+
return format === "rgba16float" ? HalfFloatType : FloatType;
|
|
990
|
+
}
|
|
991
|
+
function resolveFilter(mode) {
|
|
992
|
+
return mode === "linear" ? LinearFilter : NearestFilter;
|
|
993
|
+
}
|
|
994
|
+
function configureStorageTexture(texture2, format, filter) {
|
|
995
|
+
texture2.format = RGBAFormat;
|
|
996
|
+
texture2.type = resolveType(format);
|
|
997
|
+
texture2.magFilter = resolveFilter(filter);
|
|
998
|
+
texture2.minFilter = resolveFilter(filter);
|
|
999
|
+
texture2.wrapS = ClampToEdgeWrapping;
|
|
1000
|
+
texture2.wrapT = ClampToEdgeWrapping;
|
|
1001
|
+
texture2.generateMipmaps = false;
|
|
1002
|
+
texture2.needsUpdate = true;
|
|
1003
|
+
}
|
|
1004
|
+
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
1005
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
1006
|
+
let currentTileCount = tileCount;
|
|
1007
|
+
const tex = new StorageArrayTexture(
|
|
1008
|
+
edgeVertexCount,
|
|
1009
|
+
edgeVertexCount,
|
|
1010
|
+
tileCount
|
|
1011
|
+
);
|
|
1012
|
+
tex.name = "terrainField";
|
|
1013
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
1014
|
+
return {
|
|
1015
|
+
backendType: "array-texture",
|
|
1016
|
+
get edgeVertexCount() {
|
|
1017
|
+
return currentEdgeVertexCount;
|
|
1018
|
+
},
|
|
1019
|
+
get tileCount() {
|
|
1020
|
+
return currentTileCount;
|
|
1021
|
+
},
|
|
1022
|
+
texture: tex,
|
|
1023
|
+
uv(ix, iy, _tileIndex) {
|
|
1024
|
+
return vec2(ix.toFloat(), iy.toFloat());
|
|
1025
|
+
},
|
|
1026
|
+
texel(ix, iy, tileIndex) {
|
|
1027
|
+
return ivec3(ix, iy, tileIndex);
|
|
1028
|
+
},
|
|
1029
|
+
sample(u, v, tileIndex) {
|
|
1030
|
+
return texture(tex, vec2(u, v)).depth(int(tileIndex));
|
|
1031
|
+
},
|
|
1032
|
+
resize(width, height, nextTileCount) {
|
|
1033
|
+
currentEdgeVertexCount = width;
|
|
1034
|
+
currentTileCount = nextTileCount;
|
|
1035
|
+
tex.setSize(width, height, nextTileCount);
|
|
1036
|
+
tex.needsUpdate = true;
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
|
|
1041
|
+
const tilesPerRowNode = int(tilesPerRow);
|
|
1042
|
+
const edge = int(edgeVertexCount);
|
|
1043
|
+
const tile = int(tileIndex);
|
|
1044
|
+
const col = tile.mod(tilesPerRowNode);
|
|
1045
|
+
const row = tile.div(tilesPerRowNode);
|
|
1046
|
+
const atlasX = col.mul(edge).add(int(ix));
|
|
1047
|
+
const atlasY = row.mul(edge).add(int(iy));
|
|
1048
|
+
return { atlasX, atlasY };
|
|
1049
|
+
}
|
|
1050
|
+
function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
1051
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
1052
|
+
let currentTileCount = tileCount;
|
|
1053
|
+
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
1054
|
+
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
1055
|
+
const tex = new StorageTexture(atlasSize, atlasSize);
|
|
1056
|
+
tex.name = "terrainFieldAtlas";
|
|
1057
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
1058
|
+
return {
|
|
1059
|
+
backendType: "atlas",
|
|
1060
|
+
get edgeVertexCount() {
|
|
1061
|
+
return currentEdgeVertexCount;
|
|
1062
|
+
},
|
|
1063
|
+
get tileCount() {
|
|
1064
|
+
return currentTileCount;
|
|
1065
|
+
},
|
|
1066
|
+
texture: tex,
|
|
1067
|
+
uv(ix, iy, tileIndex) {
|
|
1068
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
1069
|
+
tilesPerRow,
|
|
1070
|
+
currentEdgeVertexCount,
|
|
1071
|
+
ix,
|
|
1072
|
+
iy,
|
|
1073
|
+
tileIndex
|
|
1074
|
+
);
|
|
1075
|
+
const currentAtlasSize = float(tilesPerRow * currentEdgeVertexCount);
|
|
1076
|
+
return vec2(
|
|
1077
|
+
atlasX.toFloat().add(0.5).div(currentAtlasSize),
|
|
1078
|
+
atlasY.toFloat().add(0.5).div(currentAtlasSize)
|
|
1079
|
+
);
|
|
1080
|
+
},
|
|
1081
|
+
texel(ix, iy, tileIndex) {
|
|
1082
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
1083
|
+
tilesPerRow,
|
|
1084
|
+
currentEdgeVertexCount,
|
|
1085
|
+
ix,
|
|
1086
|
+
iy,
|
|
1087
|
+
tileIndex
|
|
1088
|
+
);
|
|
1089
|
+
return ivec2(atlasX, atlasY);
|
|
1090
|
+
},
|
|
1091
|
+
sample(u, v, tileIndex) {
|
|
1092
|
+
const tile = int(tileIndex);
|
|
1093
|
+
const tilesPerRowNode = int(tilesPerRow);
|
|
1094
|
+
const col = tile.mod(tilesPerRowNode);
|
|
1095
|
+
const row = tile.div(tilesPerRowNode);
|
|
1096
|
+
const invTilesPerRow = float(1 / tilesPerRow);
|
|
1097
|
+
const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
|
|
1098
|
+
const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
|
|
1099
|
+
return texture(tex, vec2(atlasU, atlasV));
|
|
1100
|
+
},
|
|
1101
|
+
resize(width, height, nextTileCount) {
|
|
1102
|
+
currentEdgeVertexCount = width;
|
|
1103
|
+
currentTileCount = nextTileCount;
|
|
1104
|
+
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
1105
|
+
const nextAtlasSize = tilesPerRow * width;
|
|
1106
|
+
const image = tex.image;
|
|
1107
|
+
image.width = nextAtlasSize;
|
|
1108
|
+
image.height = nextAtlasSize;
|
|
1109
|
+
tex.needsUpdate = true;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
1114
|
+
const storage = ArrayTextureBackend(edgeVertexCount, tileCount, options);
|
|
1115
|
+
return { ...storage, backendType: "texture-3d" };
|
|
1116
|
+
}
|
|
1117
|
+
function tryGetDeviceLimits(renderer) {
|
|
1118
|
+
const backend = renderer;
|
|
1119
|
+
return backend.backend?.device?.limits ?? {};
|
|
1120
|
+
}
|
|
1121
|
+
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
1122
|
+
const filter = options.filter ?? "linear";
|
|
1123
|
+
const format = options.format ?? "rgba16float";
|
|
1124
|
+
const forcedBackend = options.backend;
|
|
1125
|
+
if (forcedBackend === "atlas") {
|
|
1126
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1127
|
+
}
|
|
1128
|
+
if (forcedBackend === "texture-3d") {
|
|
1129
|
+
return texture3DBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1130
|
+
}
|
|
1131
|
+
if (forcedBackend === "array-texture") {
|
|
1132
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1133
|
+
}
|
|
1134
|
+
const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
|
|
1135
|
+
const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
|
|
1136
|
+
if (tileCount > maxLayers) {
|
|
1137
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1138
|
+
}
|
|
1139
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
1140
|
+
}
|
|
1141
|
+
function storeTerrainField(storage, ix, iy, tileIndex, value) {
|
|
1142
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
1143
|
+
return textureStore(
|
|
1144
|
+
storage.texture,
|
|
1145
|
+
uvec3(int(ix), int(iy), int(tileIndex)),
|
|
1146
|
+
value
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
return textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
|
|
1150
|
+
}
|
|
1151
|
+
function loadTerrainField(storage, ix, iy, tileIndex) {
|
|
1152
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
1153
|
+
return textureLoad(storage.texture, ivec2(int(ix), int(iy)), int(0)).depth(
|
|
1154
|
+
int(tileIndex)
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
return textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), int(0));
|
|
1158
|
+
}
|
|
1159
|
+
function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
1160
|
+
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
1161
|
+
}
|
|
1162
|
+
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
1163
|
+
const raw = loadTerrainField(storage, ix, iy, tileIndex);
|
|
1164
|
+
return vec3(raw.g, raw.b, raw.a);
|
|
1165
|
+
}
|
|
1166
|
+
function sampleTerrainField(storage, u, v, tileIndex) {
|
|
1167
|
+
return storage.sample(u, v, tileIndex);
|
|
1168
|
+
}
|
|
1169
|
+
function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
|
|
1170
|
+
return sampleTerrainField(storage, u, v, tileIndex).r;
|
|
1171
|
+
}
|
|
1172
|
+
function packTerrainFieldSample(height, normal) {
|
|
1173
|
+
return vec4(height, normal.x, normal.y, normal.z);
|
|
1174
|
+
}
|
|
1175
|
+
function loadTilePackBounds(boundsNode, tileIndex) {
|
|
1176
|
+
const base = int(tileIndex).mul(int(TILE_BOUNDS_FLOATS_PER_TILE));
|
|
1177
|
+
return {
|
|
1178
|
+
packMin: boundsNode.element(base.add(int(TILE_BOUNDS_PACK_MIN_OFFSET))),
|
|
1179
|
+
packMax: boundsNode.element(base.add(int(TILE_BOUNDS_PACK_MAX_OFFSET)))
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function packNormalizedTerrainFieldSample(height, normal, packMin, packMax) {
|
|
1183
|
+
const span = max(packMax.sub(packMin), float(TERRAIN_FIELD_PACK_EPSILON));
|
|
1184
|
+
const normalized = height.sub(packMin).div(span);
|
|
1185
|
+
return vec4(normalized, normal.x, normal.y, normal.z);
|
|
1186
|
+
}
|
|
1187
|
+
function denormalizeTerrainFieldElevation(normalized, packMin, packMax) {
|
|
1188
|
+
const span = max(packMax.sub(packMin), float(TERRAIN_FIELD_PACK_EPSILON));
|
|
1189
|
+
return packMin.add(normalized.mul(span));
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const HALF_PI = Math.PI * 0.5;
|
|
1193
|
+
const FIELD_INNER_TEXEL_OFFSET = 1.5;
|
|
1194
|
+
const FIELD_EDGE_EXTRA_TEXELS = 3;
|
|
1195
|
+
function sphereTileArcLength(radius, levelDivisor) {
|
|
1196
|
+
return radius * HALF_PI / levelDivisor;
|
|
1219
1197
|
}
|
|
1220
|
-
function
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1198
|
+
function decodeLeafTile(leafStorage, nodeIndex) {
|
|
1199
|
+
const nodeOffset = int(nodeIndex).mul(int(4));
|
|
1200
|
+
return {
|
|
1201
|
+
level: leafStorage.node.element(nodeOffset).toInt(),
|
|
1202
|
+
x: leafStorage.node.element(nodeOffset.add(int(1))).toFloat(),
|
|
1203
|
+
y: leafStorage.node.element(nodeOffset.add(int(2))).toFloat(),
|
|
1204
|
+
face: leafStorage.node.element(nodeOffset.add(int(3))).toInt()
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function faceUVFromTileLocal(tile, localU, localV, baseU = float(1), baseV = float(1)) {
|
|
1208
|
+
const levelScale = pow(float(2), tile.level.toFloat());
|
|
1209
|
+
const nU = baseU.mul(levelScale);
|
|
1210
|
+
const nV = baseV.mul(levelScale);
|
|
1211
|
+
return vec2(tile.x.add(localU).div(nU), tile.y.add(localV).div(nV));
|
|
1212
|
+
}
|
|
1213
|
+
function createTileCompute(leafStorage, uniforms, projection) {
|
|
1214
|
+
const baseU = float(projection.baseResolution?.u ?? 1);
|
|
1215
|
+
const baseV = float(projection.baseResolution?.v ?? 1);
|
|
1216
|
+
const tileLevel = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).level);
|
|
1217
|
+
const tileFace = Fn(([nodeIndex]) => decodeLeafTile(leafStorage, nodeIndex).face);
|
|
1218
|
+
const tileOriginVec2 = Fn(([nodeIndex]) => {
|
|
1219
|
+
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
1220
|
+
return vec2(tile.x, tile.y);
|
|
1221
|
+
});
|
|
1222
|
+
const tileFaceUV = Fn(([nodeIndex, ix, iy]) => {
|
|
1223
|
+
const tile = decodeLeafTile(leafStorage, nodeIndex);
|
|
1224
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
1225
|
+
const localU = int(ix).toFloat().sub(float(1)).div(fInnerSegments);
|
|
1226
|
+
const localV = int(iy).toFloat().sub(float(1)).div(fInnerSegments);
|
|
1227
|
+
return faceUVFromTileLocal(tile, localU, localV, baseU, baseV);
|
|
1240
1228
|
});
|
|
1229
|
+
const shared = {
|
|
1230
|
+
tileLevel: (nodeIndex) => tileLevel(nodeIndex),
|
|
1231
|
+
tileFace: (nodeIndex) => tileFace(nodeIndex),
|
|
1232
|
+
tileOriginVec2: (nodeIndex) => tileOriginVec2(nodeIndex),
|
|
1233
|
+
tileFaceUV: (nodeIndex, ix, iy) => tileFaceUV(nodeIndex, ix, iy)
|
|
1234
|
+
};
|
|
1235
|
+
const parts = projection.gpu.createTileComputeParts({ leafStorage, uniforms, shared });
|
|
1236
|
+
return {
|
|
1237
|
+
...shared,
|
|
1238
|
+
tileSize: parts.tileSize,
|
|
1239
|
+
rootUVCompute: parts.rootUV,
|
|
1240
|
+
tileVertexWorldPositionCompute: parts.tileVertexWorldPosition
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function tileLocalToFieldUV(localCoord, innerSegments) {
|
|
1244
|
+
const edge = float(innerSegments).add(float(FIELD_EDGE_EXTRA_TEXELS));
|
|
1245
|
+
return float(localCoord).mul(float(innerSegments)).add(float(FIELD_INNER_TEXEL_OFFSET)).div(edge);
|
|
1246
|
+
}
|
|
1247
|
+
function tileLocalToFieldUVNumber(localCoord, innerSegments) {
|
|
1248
|
+
const edge = innerSegments + FIELD_EDGE_EXTRA_TEXELS;
|
|
1249
|
+
return (localCoord * innerSegments + FIELD_INNER_TEXEL_OFFSET) / edge;
|
|
1241
1250
|
}
|
|
1242
1251
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
const vIndex = int(vertexIndex);
|
|
1246
|
-
const segmentEdges = int(segmentsNode.add(3));
|
|
1247
|
-
const vx = vIndex.mod(segmentEdges);
|
|
1248
|
-
const vy = vIndex.div(segmentEdges);
|
|
1249
|
-
const last = segmentEdges.sub(int(1));
|
|
1250
|
-
return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
|
|
1251
|
-
});
|
|
1252
|
-
const isSkirtUV = Fn(([segments]) => {
|
|
1253
|
-
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
1254
|
-
const ux = uv().x;
|
|
1255
|
-
const uy = uv().y;
|
|
1256
|
-
const segmentCount = segmentsNode.add(2);
|
|
1257
|
-
const segmentStep = float(1).div(segmentCount);
|
|
1258
|
-
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
1259
|
-
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
1260
|
-
return innerX.and(innerY).not();
|
|
1261
|
-
});
|
|
1262
|
-
|
|
1263
|
-
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1264
|
-
if (!terrainFieldStorage) return float(0);
|
|
1252
|
+
function createTileElevation(terrainUniforms, terrainFieldStorage, tileBoundsNode) {
|
|
1253
|
+
if (!terrainFieldStorage || !tileBoundsNode) return float(0);
|
|
1265
1254
|
const innerSegs = terrainUniforms.uInnerTileSegments;
|
|
1266
1255
|
const u = tileLocalToFieldUV(positionLocal.x.add(float(0.5)), innerSegs);
|
|
1267
1256
|
const v = tileLocalToFieldUV(positionLocal.z.add(float(0.5)), innerSegs);
|
|
1268
|
-
|
|
1257
|
+
const normalized = sampleTerrainFieldElevation(
|
|
1258
|
+
terrainFieldStorage,
|
|
1259
|
+
u,
|
|
1260
|
+
v,
|
|
1261
|
+
int(instanceIndex)
|
|
1262
|
+
);
|
|
1263
|
+
const { packMin, packMax } = loadTilePackBounds(tileBoundsNode, int(instanceIndex));
|
|
1264
|
+
return denormalizeTerrainFieldElevation(normalized, packMin, packMax).mul(
|
|
1269
1265
|
terrainUniforms.uElevationScale
|
|
1270
1266
|
);
|
|
1271
1267
|
}
|
|
1272
1268
|
function loadWorldNormal(terrainUniforms, terrainFieldStorage) {
|
|
1273
|
-
const
|
|
1274
|
-
const
|
|
1275
|
-
const
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
return loadTerrainFieldNormal(terrainFieldStorage, ix, iy, nodeIndex);
|
|
1269
|
+
const innerSegs = terrainUniforms.uInnerTileSegments;
|
|
1270
|
+
const u = tileLocalToFieldUV(positionLocal.x.add(float(0.5)), innerSegs);
|
|
1271
|
+
const v = tileLocalToFieldUV(positionLocal.z.add(float(0.5)), innerSegs);
|
|
1272
|
+
const raw = sampleTerrainField(terrainFieldStorage, u, v, int(instanceIndex));
|
|
1273
|
+
return vec3(raw.g, raw.b, raw.a).normalize();
|
|
1279
1274
|
}
|
|
1280
1275
|
function assignWorldNormal(terrainUniforms, terrainFieldStorage) {
|
|
1281
1276
|
if (!terrainFieldStorage) return;
|
|
1282
1277
|
normalLocal.assign(Fn(() => loadWorldNormal(terrainUniforms, terrainFieldStorage))());
|
|
1283
1278
|
}
|
|
1284
|
-
function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1279
|
+
function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, tileBoundsNode) {
|
|
1285
1280
|
return Fn(() => {
|
|
1286
1281
|
const tile = decodeLeafTile(leafStorage, int(instanceIndex));
|
|
1287
1282
|
const rootSize = terrainUniforms.uRootSize.toVar();
|
|
@@ -1295,7 +1290,11 @@ function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFie
|
|
|
1295
1290
|
const clampedZ = positionLocal.z.max(half.negate()).min(half);
|
|
1296
1291
|
const worldX = centerX.add(clampedX.mul(size));
|
|
1297
1292
|
const worldZ = centerZ.add(clampedZ.mul(size));
|
|
1298
|
-
const yElevation = createTileElevation(
|
|
1293
|
+
const yElevation = createTileElevation(
|
|
1294
|
+
terrainUniforms,
|
|
1295
|
+
terrainFieldStorage,
|
|
1296
|
+
tileBoundsNode
|
|
1297
|
+
);
|
|
1299
1298
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1300
1299
|
const baseY = rootOrigin.y.add(yElevation);
|
|
1301
1300
|
const skirtY = baseY.sub(terrainUniforms.uSkirtScale.toVar());
|
|
@@ -1304,7 +1303,7 @@ function createFlatRenderVertexPosition(leafStorage, terrainUniforms, terrainFie
|
|
|
1304
1303
|
return vec3(worldX, worldY, worldZ);
|
|
1305
1304
|
})();
|
|
1306
1305
|
}
|
|
1307
|
-
function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, surfacePoint, baseU = 1, baseV = 1) {
|
|
1306
|
+
function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainFieldStorage, surfacePoint, tileBoundsNode, baseU = 1, baseV = 1) {
|
|
1308
1307
|
const fBaseU = float(baseU);
|
|
1309
1308
|
const fBaseV = float(baseV);
|
|
1310
1309
|
return Fn(() => {
|
|
@@ -1313,7 +1312,11 @@ function createCurvedRenderVertexPosition(leafStorage, terrainUniforms, terrainF
|
|
|
1313
1312
|
const localU = positionLocal.x.max(half.negate()).min(half).add(half);
|
|
1314
1313
|
const localV = positionLocal.z.max(half.negate()).min(half).add(half);
|
|
1315
1314
|
const faceUV = faceUVFromTileLocal(tile, localU, localV, fBaseU, fBaseV);
|
|
1316
|
-
const yElevation = createTileElevation(
|
|
1315
|
+
const yElevation = createTileElevation(
|
|
1316
|
+
terrainUniforms,
|
|
1317
|
+
terrainFieldStorage,
|
|
1318
|
+
tileBoundsNode
|
|
1319
|
+
);
|
|
1317
1320
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1318
1321
|
const displacement = select(
|
|
1319
1322
|
skirtVertex,
|
|
@@ -1498,26 +1501,6 @@ function cpuRaycast(query, ray, config, options) {
|
|
|
1498
1501
|
distance: ray.origin.distanceTo(point)
|
|
1499
1502
|
};
|
|
1500
1503
|
}
|
|
1501
|
-
function cpuRaycastBoundsOnly(ray, config, options) {
|
|
1502
|
-
const bounds = getTerrainBounds(config);
|
|
1503
|
-
const planeY = (config.minY + config.maxY) * 0.5;
|
|
1504
|
-
const dirY = ray.direction.y;
|
|
1505
|
-
if (Math.abs(dirY) < 1e-8) return null;
|
|
1506
|
-
const t = (planeY - ray.origin.y) / dirY;
|
|
1507
|
-
if (t < 0) return null;
|
|
1508
|
-
const maxDistance = options?.maxDistance ?? Number.POSITIVE_INFINITY;
|
|
1509
|
-
if (t > maxDistance) return null;
|
|
1510
|
-
const point = new Vector3();
|
|
1511
|
-
ray.at(t, point);
|
|
1512
|
-
if (point.x < bounds.minX || point.x > bounds.maxX || point.z < bounds.minZ || point.z > bounds.maxZ) {
|
|
1513
|
-
return null;
|
|
1514
|
-
}
|
|
1515
|
-
return {
|
|
1516
|
-
position: point,
|
|
1517
|
-
normal: new Vector3(0, 1, 0),
|
|
1518
|
-
distance: ray.origin.distanceTo(point)
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
1504
|
function intersectRaySphere(ray, cx, cy, cz, radius) {
|
|
1522
1505
|
const ox = ray.origin.x - cx;
|
|
1523
1506
|
const oy = ray.origin.y - cy;
|
|
@@ -1583,22 +1566,6 @@ function cubeSphereRaycast(query, ray, params, options) {
|
|
|
1583
1566
|
distance: ray.origin.distanceTo(sample.position)
|
|
1584
1567
|
};
|
|
1585
1568
|
}
|
|
1586
|
-
function cubeSphereRaycastBoundsOnly(ray, params, options) {
|
|
1587
|
-
const shell = intersectRaySphere(ray, params.centerX, params.centerY, params.centerZ, params.radius);
|
|
1588
|
-
if (!shell) return null;
|
|
1589
|
-
const maxDistance = options?.maxDistance ?? Number.POSITIVE_INFINITY;
|
|
1590
|
-
const t = shell.t0 >= 0 ? shell.t0 : shell.t1;
|
|
1591
|
-
if (t < 0 || t > maxDistance) return null;
|
|
1592
|
-
const point = new Vector3();
|
|
1593
|
-
ray.at(t, point);
|
|
1594
|
-
const normal = new Vector3(
|
|
1595
|
-
point.x - params.centerX,
|
|
1596
|
-
point.y - params.centerY,
|
|
1597
|
-
point.z - params.centerZ
|
|
1598
|
-
).normalize();
|
|
1599
|
-
if (params.invert) normal.negate();
|
|
1600
|
-
return { position: point, normal, distance: ray.origin.distanceTo(point) };
|
|
1601
|
-
}
|
|
1602
1569
|
function torusSignedDistance(query, params, px, py, pz, scratchPoint, scratchParams) {
|
|
1603
1570
|
positionToTorusParams(
|
|
1604
1571
|
px,
|
|
@@ -1653,28 +1620,6 @@ function torusRaycast(query, ray, params, options) {
|
|
|
1653
1620
|
distance: ray.origin.distanceTo(sample.position)
|
|
1654
1621
|
};
|
|
1655
1622
|
}
|
|
1656
|
-
function torusRaycastBoundsOnly(ray, params, options) {
|
|
1657
|
-
const shell = intersectRaySphere(
|
|
1658
|
-
ray,
|
|
1659
|
-
params.centerX,
|
|
1660
|
-
params.centerY,
|
|
1661
|
-
params.centerZ,
|
|
1662
|
-
params.outerRadius
|
|
1663
|
-
);
|
|
1664
|
-
if (!shell) return null;
|
|
1665
|
-
const maxDistance = options?.maxDistance ?? Number.POSITIVE_INFINITY;
|
|
1666
|
-
const t = shell.t0 >= 0 ? shell.t0 : shell.t1;
|
|
1667
|
-
if (t < 0 || t > maxDistance) return null;
|
|
1668
|
-
const point = new Vector3();
|
|
1669
|
-
ray.at(t, point);
|
|
1670
|
-
const normal = new Vector3(
|
|
1671
|
-
point.x - params.centerX,
|
|
1672
|
-
point.y - params.centerY,
|
|
1673
|
-
point.z - params.centerZ
|
|
1674
|
-
).normalize();
|
|
1675
|
-
if (params.invert) normal.negate();
|
|
1676
|
-
return { position: point, normal, distance: ray.origin.distanceTo(point) };
|
|
1677
|
-
}
|
|
1678
1623
|
|
|
1679
1624
|
function createTerrainQuery(cache) {
|
|
1680
1625
|
return {
|
|
@@ -1788,7 +1733,12 @@ function createFlatProjection() {
|
|
|
1788
1733
|
faceOutward: false,
|
|
1789
1734
|
gpu: {
|
|
1790
1735
|
renderVertexPosition(ctx) {
|
|
1791
|
-
return createFlatRenderVertexPosition(
|
|
1736
|
+
return createFlatRenderVertexPosition(
|
|
1737
|
+
ctx.leafStorage,
|
|
1738
|
+
ctx.uniforms,
|
|
1739
|
+
ctx.terrainFieldStorage,
|
|
1740
|
+
ctx.tileBoundsNode
|
|
1741
|
+
);
|
|
1792
1742
|
},
|
|
1793
1743
|
createTileComputeParts: createFlatTileComputeParts,
|
|
1794
1744
|
createFieldNormal(ctx) {
|
|
@@ -1800,9 +1750,6 @@ function createFlatProjection() {
|
|
|
1800
1750
|
}
|
|
1801
1751
|
},
|
|
1802
1752
|
cpu: {
|
|
1803
|
-
cameraSurfaceOffset(cam, elevation) {
|
|
1804
|
-
cam.y -= elevation;
|
|
1805
|
-
},
|
|
1806
1753
|
createSurfaceOps() {
|
|
1807
1754
|
return null;
|
|
1808
1755
|
},
|
|
@@ -1811,26 +1758,44 @@ function createFlatProjection() {
|
|
|
1811
1758
|
},
|
|
1812
1759
|
raycast(ctx) {
|
|
1813
1760
|
const { ray, options, terrainQuery, config } = ctx;
|
|
1814
|
-
if (terrainQuery)
|
|
1815
|
-
|
|
1816
|
-
if (precise) return precise;
|
|
1817
|
-
}
|
|
1818
|
-
const coarse = cpuRaycastBoundsOnly(ray, config, options);
|
|
1819
|
-
if (coarse && terrainQuery) {
|
|
1820
|
-
const sample = terrainQuery.sampleTerrain(coarse.position.x, coarse.position.z);
|
|
1821
|
-
if (sample.valid) {
|
|
1822
|
-
coarse.position.y = sample.elevation;
|
|
1823
|
-
coarse.normal.copy(sample.normal);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
return coarse;
|
|
1761
|
+
if (!terrainQuery) return null;
|
|
1762
|
+
return cpuRaycast(terrainQuery, ray, config, options);
|
|
1827
1763
|
}
|
|
1828
1764
|
}
|
|
1829
1765
|
};
|
|
1830
1766
|
}
|
|
1831
1767
|
|
|
1768
|
+
function boundingSphereFromPoints(px, py, pz, count, cameraOrigin, out) {
|
|
1769
|
+
let sumX = 0;
|
|
1770
|
+
let sumY = 0;
|
|
1771
|
+
let sumZ = 0;
|
|
1772
|
+
for (let i = 0; i < count; i++) {
|
|
1773
|
+
sumX += px[i];
|
|
1774
|
+
sumY += py[i];
|
|
1775
|
+
sumZ += pz[i];
|
|
1776
|
+
}
|
|
1777
|
+
const cX = sumX / count;
|
|
1778
|
+
const cY = sumY / count;
|
|
1779
|
+
const cZ = sumZ / count;
|
|
1780
|
+
let maxDistSq = 0;
|
|
1781
|
+
for (let i = 0; i < count; i++) {
|
|
1782
|
+
const dx = px[i] - cX;
|
|
1783
|
+
const dy = py[i] - cY;
|
|
1784
|
+
const dz = pz[i] - cZ;
|
|
1785
|
+
const dSq = dx * dx + dy * dy + dz * dz;
|
|
1786
|
+
if (dSq > maxDistSq) maxDistSq = dSq;
|
|
1787
|
+
}
|
|
1788
|
+
out.cx = cX - cameraOrigin.x;
|
|
1789
|
+
out.cy = cY - cameraOrigin.y;
|
|
1790
|
+
out.cz = cZ - cameraOrigin.z;
|
|
1791
|
+
out.r = Math.sqrt(maxDistSq);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1832
1794
|
function createFlatTopology(cfg) {
|
|
1833
1795
|
const halfRoot = 0.5 * cfg.rootSize;
|
|
1796
|
+
const px = new Float64Array(8);
|
|
1797
|
+
const py = new Float64Array(8);
|
|
1798
|
+
const pz = new Float64Array(8);
|
|
1834
1799
|
const topology = {
|
|
1835
1800
|
spaceCount: 1,
|
|
1836
1801
|
maxRootCount: 1,
|
|
@@ -1870,15 +1835,26 @@ function createFlatTopology(cfg) {
|
|
|
1870
1835
|
const size = cfg.rootSize * scale;
|
|
1871
1836
|
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
1872
1837
|
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
1873
|
-
const
|
|
1874
|
-
const
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1838
|
+
const maxX = minX + size;
|
|
1839
|
+
const maxZ = minZ + size;
|
|
1840
|
+
const yLo = cfg.origin.y + (elevationRange ? elevationRange.min : 0);
|
|
1841
|
+
const yHi = elevationRange ? cfg.origin.y + elevationRange.max : 0;
|
|
1842
|
+
let pointCount = 0;
|
|
1843
|
+
for (let i = 0; i < 4; i++) {
|
|
1844
|
+
const cornerX = (i & 1) === 0 ? minX : maxX;
|
|
1845
|
+
const cornerZ = i < 2 ? minZ : maxZ;
|
|
1846
|
+
px[pointCount] = cornerX;
|
|
1847
|
+
py[pointCount] = yLo;
|
|
1848
|
+
pz[pointCount] = cornerZ;
|
|
1849
|
+
pointCount += 1;
|
|
1850
|
+
if (elevationRange) {
|
|
1851
|
+
px[pointCount] = cornerX;
|
|
1852
|
+
py[pointCount] = yHi;
|
|
1853
|
+
pz[pointCount] = cornerZ;
|
|
1854
|
+
pointCount += 1;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
1882
1858
|
},
|
|
1883
1859
|
rootTiles(_cameraOrigin, out) {
|
|
1884
1860
|
const root = out[0];
|
|
@@ -1896,6 +1872,9 @@ function createInfiniteFlatTopology(cfg) {
|
|
|
1896
1872
|
const halfRoot = 0.5 * cfg.rootSize;
|
|
1897
1873
|
const rootGridRadius = Math.max(0, Math.floor(cfg.rootGridRadius ?? 1));
|
|
1898
1874
|
const rootWidth = rootGridRadius * 2 + 1;
|
|
1875
|
+
const px = new Float64Array(8);
|
|
1876
|
+
const py = new Float64Array(8);
|
|
1877
|
+
const pz = new Float64Array(8);
|
|
1899
1878
|
return {
|
|
1900
1879
|
spaceCount: 1,
|
|
1901
1880
|
maxRootCount: rootWidth * rootWidth,
|
|
@@ -1929,15 +1908,26 @@ function createInfiniteFlatTopology(cfg) {
|
|
|
1929
1908
|
const size = cfg.rootSize * scale;
|
|
1930
1909
|
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
1931
1910
|
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
1932
|
-
const
|
|
1933
|
-
const
|
|
1934
|
-
const
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1911
|
+
const maxX = minX + size;
|
|
1912
|
+
const maxZ = minZ + size;
|
|
1913
|
+
const yLo = cfg.origin.y + (elevationRange ? elevationRange.min : 0);
|
|
1914
|
+
const yHi = elevationRange ? cfg.origin.y + elevationRange.max : 0;
|
|
1915
|
+
let pointCount = 0;
|
|
1916
|
+
for (let i = 0; i < 4; i++) {
|
|
1917
|
+
const cornerX = (i & 1) === 0 ? minX : maxX;
|
|
1918
|
+
const cornerZ = i < 2 ? minZ : maxZ;
|
|
1919
|
+
px[pointCount] = cornerX;
|
|
1920
|
+
py[pointCount] = yLo;
|
|
1921
|
+
pz[pointCount] = cornerZ;
|
|
1922
|
+
pointCount += 1;
|
|
1923
|
+
if (elevationRange) {
|
|
1924
|
+
px[pointCount] = cornerX;
|
|
1925
|
+
py[pointCount] = yHi;
|
|
1926
|
+
pz[pointCount] = cornerZ;
|
|
1927
|
+
pointCount += 1;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
1941
1931
|
},
|
|
1942
1932
|
rootTiles(cameraOrigin, out) {
|
|
1943
1933
|
const camRootX = Math.floor((cameraOrigin.x - cfg.origin.x + halfRoot) / cfg.rootSize);
|
|
@@ -2311,9 +2301,11 @@ function packedSampleFromTileResult(params, tileResult) {
|
|
|
2311
2301
|
fieldV,
|
|
2312
2302
|
safeTileIndex
|
|
2313
2303
|
).toVar();
|
|
2304
|
+
const { packMin, packMax } = loadTilePackBounds(params.tileBoundsNode, safeTileIndex);
|
|
2305
|
+
const elevation = denormalizeTerrainFieldElevation(sampled.r, packMin, packMax);
|
|
2314
2306
|
const normal = vec3(sampled.g, sampled.b, sampled.a);
|
|
2315
2307
|
const valid = found.select(float(1), float(0)).toVar();
|
|
2316
|
-
return vec4(
|
|
2308
|
+
return vec4(elevation, normal.x, normal.y, normal.z).mul(valid);
|
|
2317
2309
|
}
|
|
2318
2310
|
function createTerrainSampleNode(params) {
|
|
2319
2311
|
const tileLookup = createTileIndexFromWorldPosition(
|
|
@@ -2473,7 +2465,6 @@ function createCubeSphereProjection(config) {
|
|
|
2473
2465
|
const nx = dx / len;
|
|
2474
2466
|
const ny = dy / len;
|
|
2475
2467
|
const nz = dz / len;
|
|
2476
|
-
const dirSign = invert ? -1 : 1;
|
|
2477
2468
|
dirScratch[0] = nx;
|
|
2478
2469
|
dirScratch[1] = ny;
|
|
2479
2470
|
dirScratch[2] = nz;
|
|
@@ -2482,9 +2473,9 @@ function createCubeSphereProjection(config) {
|
|
|
2482
2473
|
out.space = face;
|
|
2483
2474
|
out.u = uvScratch[0];
|
|
2484
2475
|
out.v = uvScratch[1];
|
|
2485
|
-
out.dirX = nx
|
|
2486
|
-
out.dirY = ny
|
|
2487
|
-
out.dirZ = nz
|
|
2476
|
+
out.dirX = nx;
|
|
2477
|
+
out.dirY = ny;
|
|
2478
|
+
out.dirZ = nz;
|
|
2488
2479
|
return true;
|
|
2489
2480
|
},
|
|
2490
2481
|
surfacePosition(key, elevation, outVec) {
|
|
@@ -2517,7 +2508,8 @@ function createCubeSphereProjection(config) {
|
|
|
2517
2508
|
let nx = tuy * tvz - tuz * tvy;
|
|
2518
2509
|
let ny = tuz * tvx - tux * tvz;
|
|
2519
2510
|
let nz = tux * tvy - tuy * tvx;
|
|
2520
|
-
|
|
2511
|
+
const outwardSign = invert ? -1 : 1;
|
|
2512
|
+
if ((nx * key.dirX + ny * key.dirY + nz * key.dirZ) * outwardSign < 0) {
|
|
2521
2513
|
nx = -nx;
|
|
2522
2514
|
ny = -ny;
|
|
2523
2515
|
nz = -nz;
|
|
@@ -2541,7 +2533,8 @@ function createCubeSphereProjection(config) {
|
|
|
2541
2533
|
const dir = cubeFaceDirection(basis, faceUV.x, faceUV.y);
|
|
2542
2534
|
const r = invert ? ctx.uniforms.uRadius.toVar().sub(displacement) : ctx.uniforms.uRadius.toVar().add(displacement);
|
|
2543
2535
|
return ctx.uniforms.uRootOrigin.toVar().add(dir.mul(r));
|
|
2544
|
-
}
|
|
2536
|
+
},
|
|
2537
|
+
ctx.tileBoundsNode
|
|
2545
2538
|
);
|
|
2546
2539
|
},
|
|
2547
2540
|
createTileComputeParts: createSphereTileComputeParts,
|
|
@@ -2571,19 +2564,6 @@ function createCubeSphereProjection(config) {
|
|
|
2571
2564
|
augmentSampler: augmentCubeSphereSampler
|
|
2572
2565
|
},
|
|
2573
2566
|
cpu: {
|
|
2574
|
-
cameraSurfaceOffset(cam, elevation) {
|
|
2575
|
-
const dx = cam.x - center.x;
|
|
2576
|
-
const dy = cam.y - center.y;
|
|
2577
|
-
const dz = cam.z - center.z;
|
|
2578
|
-
const len = Math.hypot(dx, dy, dz);
|
|
2579
|
-
if (len > 1e-12) {
|
|
2580
|
-
const sign = invert ? 1 : -1;
|
|
2581
|
-
const inv = sign * elevation / len;
|
|
2582
|
-
cam.x += dx * inv;
|
|
2583
|
-
cam.y += dy * inv;
|
|
2584
|
-
cam.z += dz * inv;
|
|
2585
|
-
}
|
|
2586
|
-
},
|
|
2587
2567
|
createSurfaceOps() {
|
|
2588
2568
|
return surfaceOps;
|
|
2589
2569
|
},
|
|
@@ -2594,6 +2574,7 @@ function createCubeSphereProjection(config) {
|
|
|
2594
2574
|
return { query, surfaceQuery, sphereQuery };
|
|
2595
2575
|
},
|
|
2596
2576
|
raycast(ctx) {
|
|
2577
|
+
if (!ctx.sphereQuery) return null;
|
|
2597
2578
|
const range = ctx.terrainQuery?.getGlobalElevationRange();
|
|
2598
2579
|
const dispMax = range ? Math.max(0, range.max - center.y) : radius * 0.1;
|
|
2599
2580
|
const outerPadding = invert ? 0 : dispMax + RAYCAST_PADDING$1;
|
|
@@ -2605,11 +2586,7 @@ function createCubeSphereProjection(config) {
|
|
|
2605
2586
|
maxRadius: radius + outerPadding,
|
|
2606
2587
|
invert
|
|
2607
2588
|
};
|
|
2608
|
-
|
|
2609
|
-
const precise = cubeSphereRaycast(ctx.sphereQuery, ctx.ray, params, ctx.options);
|
|
2610
|
-
if (precise) return precise;
|
|
2611
|
-
}
|
|
2612
|
-
return cubeSphereRaycastBoundsOnly(ctx.ray, params, ctx.options);
|
|
2589
|
+
return cubeSphereRaycast(ctx.sphereQuery, ctx.ray, params, ctx.options);
|
|
2613
2590
|
}
|
|
2614
2591
|
}
|
|
2615
2592
|
};
|
|
@@ -2700,8 +2677,6 @@ function createCubeSphereTopology(cfg) {
|
|
|
2700
2677
|
spaceCount: 6,
|
|
2701
2678
|
maxRootCount: 6,
|
|
2702
2679
|
projection: createCubeSphereProjection({ radius, center, invert: cfg.invert }),
|
|
2703
|
-
radius,
|
|
2704
|
-
center,
|
|
2705
2680
|
neighborSameLevel(tile, dir, out) {
|
|
2706
2681
|
const level = tile.level;
|
|
2707
2682
|
const n = 1 << level;
|
|
@@ -2738,48 +2713,29 @@ function createCubeSphereTopology(cfg) {
|
|
|
2738
2713
|
const u1 = (tile.x + 1) / n;
|
|
2739
2714
|
const v0 = tile.y / n;
|
|
2740
2715
|
const v1 = (tile.y + 1) / n;
|
|
2741
|
-
const
|
|
2742
|
-
const
|
|
2743
|
-
const disps = elevationRange ? [elevationRange.min, elevationRange.max] : [0];
|
|
2716
|
+
const shellLo = radius + (elevationRange ? elevationRange.min : 0);
|
|
2717
|
+
const shellHi = elevationRange ? radius + elevationRange.max : 0;
|
|
2744
2718
|
let pointCount = 0;
|
|
2745
|
-
let sumX = 0;
|
|
2746
|
-
let sumY = 0;
|
|
2747
|
-
let sumZ = 0;
|
|
2748
2719
|
for (let i = 0; i < 4; i++) {
|
|
2749
|
-
|
|
2720
|
+
const u = (i & 1) === 0 ? u0 : u1;
|
|
2721
|
+
const v = i < 2 ? v0 : v1;
|
|
2722
|
+
faceUVToCube(tile.space, u, v, cube);
|
|
2750
2723
|
const len = Math.hypot(cube[0], cube[1], cube[2]);
|
|
2751
2724
|
const dirX = cube[0] / len;
|
|
2752
2725
|
const dirY = cube[1] / len;
|
|
2753
2726
|
const dirZ = cube[2] / len;
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
px[pointCount] =
|
|
2760
|
-
py[pointCount] =
|
|
2761
|
-
pz[pointCount] =
|
|
2762
|
-
sumX += sx;
|
|
2763
|
-
sumY += sy;
|
|
2764
|
-
sumZ += sz;
|
|
2727
|
+
px[pointCount] = center.x + dirX * shellLo;
|
|
2728
|
+
py[pointCount] = center.y + dirY * shellLo;
|
|
2729
|
+
pz[pointCount] = center.z + dirZ * shellLo;
|
|
2730
|
+
pointCount += 1;
|
|
2731
|
+
if (elevationRange) {
|
|
2732
|
+
px[pointCount] = center.x + dirX * shellHi;
|
|
2733
|
+
py[pointCount] = center.y + dirY * shellHi;
|
|
2734
|
+
pz[pointCount] = center.z + dirZ * shellHi;
|
|
2765
2735
|
pointCount += 1;
|
|
2766
2736
|
}
|
|
2767
2737
|
}
|
|
2768
|
-
|
|
2769
|
-
const cY = sumY / pointCount;
|
|
2770
|
-
const cZ = sumZ / pointCount;
|
|
2771
|
-
let maxDistSq = 0;
|
|
2772
|
-
for (let i = 0; i < pointCount; i++) {
|
|
2773
|
-
const dx = px[i] - cX;
|
|
2774
|
-
const dy = py[i] - cY;
|
|
2775
|
-
const dz = pz[i] - cZ;
|
|
2776
|
-
const dSq = dx * dx + dy * dy + dz * dz;
|
|
2777
|
-
if (dSq > maxDistSq) maxDistSq = dSq;
|
|
2778
|
-
}
|
|
2779
|
-
out.cx = cX - cameraOrigin.x;
|
|
2780
|
-
out.cy = cY - cameraOrigin.y;
|
|
2781
|
-
out.cz = cZ - cameraOrigin.z;
|
|
2782
|
-
out.r = Math.sqrt(maxDistSq);
|
|
2738
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
2783
2739
|
},
|
|
2784
2740
|
rootTiles(_cameraOrigin, out) {
|
|
2785
2741
|
for (let s = 0; s < 6; s++) {
|
|
@@ -2926,6 +2882,7 @@ function createTorusProjection(config) {
|
|
|
2926
2882
|
ctx.uniforms,
|
|
2927
2883
|
ctx.terrainFieldStorage,
|
|
2928
2884
|
(_tile, faceUV, displacement) => torusPosition(geometry, faceUV.x, faceUV.y, displacement),
|
|
2885
|
+
ctx.tileBoundsNode,
|
|
2929
2886
|
baseU,
|
|
2930
2887
|
baseV
|
|
2931
2888
|
);
|
|
@@ -2950,13 +2907,6 @@ function createTorusProjection(config) {
|
|
|
2950
2907
|
}
|
|
2951
2908
|
},
|
|
2952
2909
|
cpu: {
|
|
2953
|
-
cameraSurfaceOffset(cam, elevation) {
|
|
2954
|
-
positionToTorusParams(cam.x, cam.y, cam.z, majorRadius, center, params);
|
|
2955
|
-
torusOutwardNormal$1(params.u, params.v, normalScratch, invert);
|
|
2956
|
-
cam.x -= normalScratch[0] * elevation;
|
|
2957
|
-
cam.y -= normalScratch[1] * elevation;
|
|
2958
|
-
cam.z -= normalScratch[2] * elevation;
|
|
2959
|
-
},
|
|
2960
2910
|
createSurfaceOps() {
|
|
2961
2911
|
return surfaceOps;
|
|
2962
2912
|
},
|
|
@@ -2966,6 +2916,7 @@ function createTorusProjection(config) {
|
|
|
2966
2916
|
return { query, surfaceQuery, sphereQuery: null };
|
|
2967
2917
|
},
|
|
2968
2918
|
raycast(ctx) {
|
|
2919
|
+
if (!ctx.surfaceQuery) return null;
|
|
2969
2920
|
const range = ctx.terrainQuery?.getGlobalElevationRange();
|
|
2970
2921
|
const dispMax = range ? Math.max(0, range.max - ctx.config.originY) : minorRadius * 0.5;
|
|
2971
2922
|
const outerPadding = invert ? 0 : dispMax + RAYCAST_PADDING;
|
|
@@ -2978,11 +2929,7 @@ function createTorusProjection(config) {
|
|
|
2978
2929
|
outerRadius: majorRadius + minorRadius + outerPadding,
|
|
2979
2930
|
invert
|
|
2980
2931
|
};
|
|
2981
|
-
|
|
2982
|
-
const precise = torusRaycast(ctx.surfaceQuery, ctx.ray, raycastParams, ctx.options);
|
|
2983
|
-
if (precise) return precise;
|
|
2984
|
-
}
|
|
2985
|
-
return torusRaycastBoundsOnly(ctx.ray, raycastParams, ctx.options);
|
|
2932
|
+
return torusRaycast(ctx.surfaceQuery, ctx.ray, raycastParams, ctx.options);
|
|
2986
2933
|
}
|
|
2987
2934
|
}
|
|
2988
2935
|
};
|
|
@@ -3015,8 +2962,6 @@ function createTorusTopology(cfg) {
|
|
|
3015
2962
|
baseU,
|
|
3016
2963
|
baseV
|
|
3017
2964
|
}),
|
|
3018
|
-
radius: majorRadius + minorRadius,
|
|
3019
|
-
center,
|
|
3020
2965
|
neighborSameLevel(tile, dir, out) {
|
|
3021
2966
|
const { nU, nV } = levelResolution(tile.level);
|
|
3022
2967
|
let nx = tile.x;
|
|
@@ -3047,42 +2992,28 @@ function createTorusTopology(cfg) {
|
|
|
3047
2992
|
const v0 = tile.y / nV;
|
|
3048
2993
|
const stepU = 1 / nU;
|
|
3049
2994
|
const stepV = 1 / nV;
|
|
3050
|
-
const
|
|
2995
|
+
const dispLo = elevationRange ? elevationRange.min : 0;
|
|
2996
|
+
const dispHi = elevationRange ? elevationRange.max : 0;
|
|
3051
2997
|
let pointCount = 0;
|
|
3052
|
-
let sumX = 0;
|
|
3053
|
-
let sumY = 0;
|
|
3054
|
-
let sumZ = 0;
|
|
3055
2998
|
for (let sj = 0; sj <= 2; sj++) {
|
|
3056
2999
|
for (let si = 0; si <= 2; si++) {
|
|
3057
3000
|
const u = u0 + si * stepU / 2;
|
|
3058
3001
|
const v = v0 + sj * stepV / 2;
|
|
3059
|
-
|
|
3060
|
-
|
|
3002
|
+
torusUVToPoint(u, v, majorRadius, minorRadius, dispLo, center, corner, invert);
|
|
3003
|
+
px[pointCount] = corner[0];
|
|
3004
|
+
py[pointCount] = corner[1];
|
|
3005
|
+
pz[pointCount] = corner[2];
|
|
3006
|
+
pointCount += 1;
|
|
3007
|
+
if (elevationRange) {
|
|
3008
|
+
torusUVToPoint(u, v, majorRadius, minorRadius, dispHi, center, corner, invert);
|
|
3061
3009
|
px[pointCount] = corner[0];
|
|
3062
3010
|
py[pointCount] = corner[1];
|
|
3063
3011
|
pz[pointCount] = corner[2];
|
|
3064
|
-
sumX += corner[0];
|
|
3065
|
-
sumY += corner[1];
|
|
3066
|
-
sumZ += corner[2];
|
|
3067
3012
|
pointCount += 1;
|
|
3068
3013
|
}
|
|
3069
3014
|
}
|
|
3070
3015
|
}
|
|
3071
|
-
|
|
3072
|
-
const cY = sumY / pointCount;
|
|
3073
|
-
const cZ = sumZ / pointCount;
|
|
3074
|
-
let maxDistSq = 0;
|
|
3075
|
-
for (let i = 0; i < pointCount; i++) {
|
|
3076
|
-
const dx = px[i] - cX;
|
|
3077
|
-
const dy = py[i] - cY;
|
|
3078
|
-
const dz = pz[i] - cZ;
|
|
3079
|
-
const dSq = dx * dx + dy * dy + dz * dz;
|
|
3080
|
-
if (dSq > maxDistSq) maxDistSq = dSq;
|
|
3081
|
-
}
|
|
3082
|
-
out.cx = cX - cameraOrigin.x;
|
|
3083
|
-
out.cy = cY - cameraOrigin.y;
|
|
3084
|
-
out.cz = cZ - cameraOrigin.z;
|
|
3085
|
-
out.r = Math.sqrt(maxDistSq);
|
|
3016
|
+
boundingSphereFromPoints(px, py, pz, pointCount, cameraOrigin, out);
|
|
3086
3017
|
},
|
|
3087
3018
|
rootTiles(_cameraOrigin, out) {
|
|
3088
3019
|
let count = 0;
|
|
@@ -3178,8 +3109,8 @@ function buildTileElevationPyramid(pyramid, index, tileBounds, leafCount) {
|
|
|
3178
3109
|
const level = index.keysLevel[slot];
|
|
3179
3110
|
const x = index.keysX[slot];
|
|
3180
3111
|
const y = index.keysY[slot];
|
|
3181
|
-
const rawMin = tileBounds[leafIndex *
|
|
3182
|
-
const rawMax = tileBounds[leafIndex *
|
|
3112
|
+
const rawMin = tileBounds[leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MIN_OFFSET];
|
|
3113
|
+
const rawMax = tileBounds[leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET];
|
|
3183
3114
|
for (let ancestorLevel = level; ancestorLevel >= 0; ancestorLevel--) {
|
|
3184
3115
|
const shift = level - ancestorLevel;
|
|
3185
3116
|
mergeRange(
|
|
@@ -3333,8 +3264,8 @@ function createTerrainSnapshotState(maxNodes, maxLevel, totalElements) {
|
|
|
3333
3264
|
backElevation: new Float32Array(totalElements),
|
|
3334
3265
|
frontIndex: createSpatialIndex(maxNodes),
|
|
3335
3266
|
backIndex: createSpatialIndex(maxNodes),
|
|
3336
|
-
frontTileBounds: new Float32Array(maxNodes *
|
|
3337
|
-
backTileBounds: new Float32Array(maxNodes *
|
|
3267
|
+
frontTileBounds: new Float32Array(maxNodes * TILE_BOUNDS_FLOATS_PER_TILE),
|
|
3268
|
+
backTileBounds: new Float32Array(maxNodes * TILE_BOUNDS_FLOATS_PER_TILE),
|
|
3338
3269
|
frontLeafCount: 0,
|
|
3339
3270
|
globalRange: null,
|
|
3340
3271
|
hasSnapshot: false,
|
|
@@ -3375,7 +3306,7 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3375
3306
|
let boundsValid = activeLeafCount === 0;
|
|
3376
3307
|
if (boundsFilled) {
|
|
3377
3308
|
for (let i = 0; i < activeLeafCount; i += 1) {
|
|
3378
|
-
if ((state.backTileBounds[i *
|
|
3309
|
+
if ((state.backTileBounds[i * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET] ?? 0) !== 0) {
|
|
3379
3310
|
boundsValid = true;
|
|
3380
3311
|
break;
|
|
3381
3312
|
}
|
|
@@ -3397,8 +3328,8 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3397
3328
|
let gMin = Infinity;
|
|
3398
3329
|
let gMax = -Infinity;
|
|
3399
3330
|
for (let i = 0; i < activeLeafCount; i++) {
|
|
3400
|
-
const rawMin = state.frontTileBounds[i *
|
|
3401
|
-
const rawMax = state.frontTileBounds[i *
|
|
3331
|
+
const rawMin = state.frontTileBounds[i * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MIN_OFFSET];
|
|
3332
|
+
const rawMax = state.frontTileBounds[i * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET];
|
|
3402
3333
|
const a = originY + rawMin * elevationScale;
|
|
3403
3334
|
const b = originY + rawMax * elevationScale;
|
|
3404
3335
|
gMin = Math.min(gMin, a, b);
|
|
@@ -3434,7 +3365,7 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3434
3365
|
boundsAttribute,
|
|
3435
3366
|
state.boundsReadback,
|
|
3436
3367
|
state.backTileBounds,
|
|
3437
|
-
activeLeafCount *
|
|
3368
|
+
activeLeafCount * TILE_BOUNDS_FLOATS_PER_TILE,
|
|
3438
3369
|
"terrainBoundsReadback"
|
|
3439
3370
|
);
|
|
3440
3371
|
}
|
|
@@ -3453,7 +3384,9 @@ function triggerSnapshotReadback(state, renderer, attribute, spatialIndex, bound
|
|
|
3453
3384
|
if (boundsResult) {
|
|
3454
3385
|
const rawBounds = new Float32Array(boundsResult);
|
|
3455
3386
|
state.backTileBounds.fill(0);
|
|
3456
|
-
state.backTileBounds.set(
|
|
3387
|
+
state.backTileBounds.set(
|
|
3388
|
+
rawBounds.subarray(0, activeLeafCount * TILE_BOUNDS_FLOATS_PER_TILE)
|
|
3389
|
+
);
|
|
3457
3390
|
boundsFilled = true;
|
|
3458
3391
|
}
|
|
3459
3392
|
applySnapshot(boundsFilled);
|
|
@@ -3567,8 +3500,8 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3567
3500
|
};
|
|
3568
3501
|
const tileBoundsFromLookup = (lookup, elevationBase) => {
|
|
3569
3502
|
if (!lookup.found || lookup.leafIndex >= state.frontLeafCount) return null;
|
|
3570
|
-
const rawMin = state.frontTileBounds[lookup.leafIndex *
|
|
3571
|
-
const rawMax = state.frontTileBounds[lookup.leafIndex *
|
|
3503
|
+
const rawMin = state.frontTileBounds[lookup.leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MIN_OFFSET];
|
|
3504
|
+
const rawMax = state.frontTileBounds[lookup.leafIndex * TILE_BOUNDS_FLOATS_PER_TILE + TILE_BOUNDS_LOD_MAX_OFFSET];
|
|
3572
3505
|
const a = elevationBase + rawMin * config.elevationScale;
|
|
3573
3506
|
const b = elevationBase + rawMax * config.elevationScale;
|
|
3574
3507
|
return {
|
|
@@ -3646,6 +3579,9 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3646
3579
|
get hasSurface() {
|
|
3647
3580
|
return surfaceOps !== null;
|
|
3648
3581
|
},
|
|
3582
|
+
setSurfaceOps(nextSurfaceOps) {
|
|
3583
|
+
surfaceOps = nextSurfaceOps;
|
|
3584
|
+
},
|
|
3649
3585
|
updateConfig(nextConfig) {
|
|
3650
3586
|
config = nextConfig;
|
|
3651
3587
|
shape.edgeVertexCount = config.innerTileSegments + 3;
|
|
@@ -3793,72 +3729,132 @@ function createCpuTerrainCache(maxNodes, initialConfig, surfaceOps) {
|
|
|
3793
3729
|
return lookupTileElevationRange(state.elevationPyramid, space, level, x, y, out);
|
|
3794
3730
|
}
|
|
3795
3731
|
};
|
|
3796
|
-
return api;
|
|
3732
|
+
return api;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
const createElevation = (tile, uniforms, elevationFn) => {
|
|
3736
|
+
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
3737
|
+
const ix = int(localCoordinates.x);
|
|
3738
|
+
const iy = int(localCoordinates.y);
|
|
3739
|
+
const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(int(3));
|
|
3740
|
+
const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
|
|
3741
|
+
const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
|
|
3742
|
+
const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
|
|
3743
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
3744
|
+
return elevationFn({
|
|
3745
|
+
worldPosition,
|
|
3746
|
+
rootSize,
|
|
3747
|
+
rootUV,
|
|
3748
|
+
tileOriginVec2: tile.tileOriginVec2(nodeIndex),
|
|
3749
|
+
tileSize: tile.tileSize(nodeIndex),
|
|
3750
|
+
tileLevel: tile.tileLevel(nodeIndex),
|
|
3751
|
+
nodeIndex: int(nodeIndex),
|
|
3752
|
+
tileUV
|
|
3753
|
+
});
|
|
3754
|
+
};
|
|
3755
|
+
};
|
|
3756
|
+
|
|
3757
|
+
function createTerrainUniforms(params) {
|
|
3758
|
+
const sanitizedId = params.instanceId?.replace(/-/g, "_");
|
|
3759
|
+
const suffix = sanitizedId ? `_${sanitizedId}` : "";
|
|
3760
|
+
const uRootOrigin = uniform(
|
|
3761
|
+
new Vector3$1(params.rootOrigin.x, params.rootOrigin.y, params.rootOrigin.z)
|
|
3762
|
+
).setName(`uRootOrigin${suffix}`);
|
|
3763
|
+
const uRootSize = uniform(float(params.rootSize)).setName(`uRootSize${suffix}`);
|
|
3764
|
+
const uInnerTileSegments = uniform(int(params.innerTileSegments)).setName(
|
|
3765
|
+
`uInnerTileSegments${suffix}`
|
|
3766
|
+
);
|
|
3767
|
+
const uSkirtScale = uniform(float(params.skirtScale)).setName(`uSkirtScale${suffix}`);
|
|
3768
|
+
const uElevationScale = uniform(float(params.elevationScale)).setName(`uElevationScale${suffix}`);
|
|
3769
|
+
const uRadius = uniform(float(params.radius)).setName(`uRadius${suffix}`);
|
|
3770
|
+
return {
|
|
3771
|
+
uRootOrigin,
|
|
3772
|
+
uRootSize,
|
|
3773
|
+
uInnerTileSegments,
|
|
3774
|
+
uSkirtScale,
|
|
3775
|
+
uElevationScale,
|
|
3776
|
+
uRadius
|
|
3777
|
+
};
|
|
3797
3778
|
}
|
|
3798
3779
|
|
|
3799
|
-
const
|
|
3800
|
-
|
|
3801
|
-
|
|
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
|
-
|
|
3780
|
+
const instanceIdTask = task(() => crypto.randomUUID()).displayName("instanceIdTask").cache("once");
|
|
3781
|
+
|
|
3782
|
+
const scratchVector3 = new Vector3();
|
|
3783
|
+
const createUniformsTask = task((get, work) => {
|
|
3784
|
+
const uniformParams = {
|
|
3785
|
+
rootOrigin: get(origin),
|
|
3786
|
+
rootSize: get(rootSize),
|
|
3787
|
+
innerTileSegments: get(innerTileSegments),
|
|
3788
|
+
skirtScale: get(skirtScale),
|
|
3789
|
+
elevationScale: get(elevationScale),
|
|
3790
|
+
radius: get(radius),
|
|
3791
|
+
instanceId: get(instanceIdTask)
|
|
3792
|
+
};
|
|
3793
|
+
return work(() => createTerrainUniforms(uniformParams));
|
|
3794
|
+
}).displayName("createUniformsTask").cache("once");
|
|
3795
|
+
const updateUniformsTask = task((get, work) => {
|
|
3796
|
+
const terrainUniformsContext = get(createUniformsTask);
|
|
3797
|
+
const rootSizeVal = get(rootSize);
|
|
3798
|
+
const rootOrigin = get(origin);
|
|
3799
|
+
const innerTileSegmentsVal = get(innerTileSegments);
|
|
3800
|
+
const skirtScaleVal = get(skirtScale);
|
|
3801
|
+
const elevationScaleVal = get(elevationScale);
|
|
3802
|
+
const radiusVal = get(radius);
|
|
3803
|
+
return work(() => {
|
|
3804
|
+
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
3805
|
+
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
3806
|
+
rootOrigin.x,
|
|
3807
|
+
rootOrigin.y,
|
|
3808
|
+
rootOrigin.z
|
|
3809
|
+
);
|
|
3810
|
+
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
3811
|
+
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
3812
|
+
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
3813
|
+
terrainUniformsContext.uRadius.value = radiusVal;
|
|
3814
|
+
return terrainUniformsContext;
|
|
3815
|
+
});
|
|
3816
|
+
}).displayName("updateUniformsTask");
|
|
3817
|
+
|
|
3818
|
+
const createElevationFieldContextTask = task((get, work) => {
|
|
3836
3819
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
3820
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
3821
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
3837
3822
|
return work(() => {
|
|
3838
|
-
const data = new Float32Array(
|
|
3823
|
+
const data = new Float32Array(totalElements);
|
|
3839
3824
|
const attribute = new StorageBufferAttribute(data, 1);
|
|
3840
|
-
attribute.name = "
|
|
3841
|
-
const node = storage(attribute, "float",
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3825
|
+
attribute.name = "elevationField";
|
|
3826
|
+
const node = storage(attribute, "float", totalElements).setName("elevationField");
|
|
3827
|
+
return {
|
|
3828
|
+
data,
|
|
3829
|
+
attribute,
|
|
3830
|
+
node
|
|
3831
|
+
};
|
|
3847
3832
|
});
|
|
3848
|
-
}).displayName("
|
|
3849
|
-
const
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
return
|
|
3855
|
-
|
|
3856
|
-
|
|
3833
|
+
}).displayName("createElevationFieldContextTask");
|
|
3834
|
+
const tileNodesTask = task((get, work) => {
|
|
3835
|
+
const leafStorage = get(leafStorageTask);
|
|
3836
|
+
const uniforms = get(updateUniformsTask);
|
|
3837
|
+
const topology = get(topologyTask);
|
|
3838
|
+
return work(() => {
|
|
3839
|
+
return createTileCompute(leafStorage, uniforms, topology.projection);
|
|
3840
|
+
});
|
|
3841
|
+
}).displayName("tileNodesTask");
|
|
3842
|
+
const elevationFieldStageTask = task((get, work) => {
|
|
3843
|
+
const tile = get(tileNodesTask);
|
|
3844
|
+
const uniforms = get(updateUniformsTask);
|
|
3845
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
3846
|
+
const userElevationFn = get(elevationFn);
|
|
3847
|
+
return work(() => {
|
|
3848
|
+
const heightFn = createElevationFunction(userElevationFn);
|
|
3849
|
+
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
3850
|
+
return [
|
|
3851
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
3852
|
+
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
3853
|
+
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
3857
3854
|
}
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
).displayName("tileBoundsReductionTask").lane("gpu");
|
|
3855
|
+
];
|
|
3856
|
+
});
|
|
3857
|
+
}).displayName("elevationFieldStageTask");
|
|
3862
3858
|
|
|
3863
3859
|
const terrainQueryTask = task((get, work) => {
|
|
3864
3860
|
const maxNodesValue = get(maxNodes);
|
|
@@ -3872,6 +3868,7 @@ const terrainQueryTask = task((get, work) => {
|
|
|
3872
3868
|
const projection = topologyValue.projection;
|
|
3873
3869
|
return work((prev) => {
|
|
3874
3870
|
const shapeKey = `${maxNodesValue}:${innerTileSegmentsValue}:${projection.kind}`;
|
|
3871
|
+
const resolvedRadius = projection.radius ?? radiusValue;
|
|
3875
3872
|
const configValues = {
|
|
3876
3873
|
rootSize: rootSizeValue,
|
|
3877
3874
|
originX: originValue.x,
|
|
@@ -3880,7 +3877,7 @@ const terrainQueryTask = task((get, work) => {
|
|
|
3880
3877
|
innerTileSegments: innerTileSegmentsValue,
|
|
3881
3878
|
elevationScale: elevationScaleValue,
|
|
3882
3879
|
maxLevel: maxLevelValue,
|
|
3883
|
-
radius:
|
|
3880
|
+
radius: resolvedRadius,
|
|
3884
3881
|
baseU: projection.baseResolution?.u ?? 1,
|
|
3885
3882
|
baseV: projection.baseResolution?.v ?? 1
|
|
3886
3883
|
};
|
|
@@ -3895,9 +3892,15 @@ const terrainQueryTask = task((get, work) => {
|
|
|
3895
3892
|
query = runtime.query;
|
|
3896
3893
|
surfaceQuery = runtime.surfaceQuery;
|
|
3897
3894
|
sphereQuery = runtime.sphereQuery;
|
|
3895
|
+
} else if (prev?.projection !== projection) {
|
|
3896
|
+
cache.setSurfaceOps(projection.cpu.createSurfaceOps());
|
|
3897
|
+
const runtime = projection.cpu.createRuntimeQueries(cache);
|
|
3898
|
+
query = runtime.query;
|
|
3899
|
+
surfaceQuery = runtime.surfaceQuery;
|
|
3900
|
+
sphereQuery = runtime.sphereQuery;
|
|
3898
3901
|
}
|
|
3899
3902
|
cache.updateConfig(configValues);
|
|
3900
|
-
return { cache, query, surfaceQuery, sphereQuery, shapeKey };
|
|
3903
|
+
return { cache, query, surfaceQuery, sphereQuery, shapeKey, projection };
|
|
3901
3904
|
});
|
|
3902
3905
|
}).displayName("terrainQueryTask");
|
|
3903
3906
|
const terrainReadbackTask = task(
|
|
@@ -3944,27 +3947,19 @@ const quadtreeConfigTask = task((get, work) => {
|
|
|
3944
3947
|
const quadtreeUpdateTask = task((get, work) => {
|
|
3945
3948
|
const quadtreeConfig = get(quadtreeConfigTask);
|
|
3946
3949
|
const quadtreeUpdateConfig = get(quadtreeUpdate);
|
|
3947
|
-
const {
|
|
3950
|
+
const { cache } = get(terrainQueryTask);
|
|
3948
3951
|
const elevationScaleValue = get(elevationScale);
|
|
3949
3952
|
let outLeaves = void 0;
|
|
3950
|
-
const cameraPosition = new Vector3();
|
|
3951
3953
|
const elevationRangeScratch = { min: 0, max: 0 };
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
cameraPosition.set(cam.x, cam.y, cam.z);
|
|
3956
|
-
quadtreeUpdateConfig.elevationAtCameraXZ = surfaceQuery.getElevationByPosition(cameraPosition) ?? 0;
|
|
3957
|
-
} else {
|
|
3958
|
-
quadtreeUpdateConfig.elevationAtCameraXZ = terrainQuery.getElevation(cam.x, cam.z) ?? 0;
|
|
3954
|
+
quadtreeUpdateConfig.tileElevationRange = (tile, out) => {
|
|
3955
|
+
if (!cache.getTileElevationRange(tile.space, tile.level, tile.x, tile.y, elevationRangeScratch)) {
|
|
3956
|
+
return false;
|
|
3959
3957
|
}
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
out.max = elevationRangeScratch.max * elevationScaleValue;
|
|
3966
|
-
return true;
|
|
3967
|
-
};
|
|
3958
|
+
out.min = elevationRangeScratch.min * elevationScaleValue;
|
|
3959
|
+
out.max = elevationRangeScratch.max * elevationScaleValue;
|
|
3960
|
+
return true;
|
|
3961
|
+
};
|
|
3962
|
+
return work(() => {
|
|
3968
3963
|
outLeaves = update(
|
|
3969
3964
|
quadtreeConfig.state,
|
|
3970
3965
|
quadtreeConfig.topology,
|
|
@@ -4002,107 +3997,88 @@ const leafGpuBufferTask = task((get, work) => {
|
|
|
4002
3997
|
});
|
|
4003
3998
|
}).displayName("leafGpuBufferTask");
|
|
4004
3999
|
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
const
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4000
|
+
const WGSIZE = 64;
|
|
4001
|
+
function buildReductionKernel(elevationFieldNode, boundsNode, verticesPerNode, edgeVertexCount) {
|
|
4002
|
+
const elemsPerThread = Math.ceil(verticesPerNode / WGSIZE);
|
|
4003
|
+
return Fn(() => {
|
|
4004
|
+
const sharedLodMin = workgroupArray("float", WGSIZE);
|
|
4005
|
+
const sharedLodMax = workgroupArray("float", WGSIZE);
|
|
4006
|
+
const sharedPackMin = workgroupArray("float", WGSIZE);
|
|
4007
|
+
const sharedPackMax = workgroupArray("float", WGSIZE);
|
|
4008
|
+
const tid = int(localId.x);
|
|
4009
|
+
const tileIdx = int(workgroupId.z);
|
|
4010
|
+
const baseOffset = tileIdx.mul(int(verticesPerNode));
|
|
4011
|
+
const start = tid.mul(int(elemsPerThread));
|
|
4012
|
+
const end = min(start.add(int(elemsPerThread)), int(verticesPerNode));
|
|
4013
|
+
const localLodMin = float(1e10).toVar("localLodMin");
|
|
4014
|
+
const localLodMax = float(-1e10).toVar("localLodMax");
|
|
4015
|
+
const localPackMin = float(1e10).toVar("localPackMin");
|
|
4016
|
+
const localPackMax = float(-1e10).toVar("localPackMax");
|
|
4017
|
+
const edge = int(edgeVertexCount);
|
|
4018
|
+
const lastEdge = int(edgeVertexCount - 1);
|
|
4019
|
+
Loop({ start, end, type: "int", condition: "<" }, ({ i }) => {
|
|
4020
|
+
const ix = int(i).mod(edge);
|
|
4021
|
+
const iy = int(i).div(edge);
|
|
4022
|
+
const isSkirt = ix.equal(int(0)).or(ix.equal(lastEdge)).or(iy.equal(int(0))).or(iy.equal(lastEdge));
|
|
4023
|
+
const h = elevationFieldNode.element(baseOffset.add(i));
|
|
4024
|
+
localPackMin.assign(min(localPackMin, h));
|
|
4025
|
+
localPackMax.assign(max(localPackMax, h));
|
|
4026
|
+
If(isSkirt.not(), () => {
|
|
4027
|
+
localLodMin.assign(min(localLodMin, h));
|
|
4028
|
+
localLodMax.assign(max(localLodMax, h));
|
|
4029
|
+
});
|
|
4030
|
+
});
|
|
4031
|
+
sharedLodMin.element(tid).assign(localLodMin);
|
|
4032
|
+
sharedLodMax.element(tid).assign(localLodMax);
|
|
4033
|
+
sharedPackMin.element(tid).assign(localPackMin);
|
|
4034
|
+
sharedPackMax.element(tid).assign(localPackMax);
|
|
4035
|
+
workgroupBarrier();
|
|
4036
|
+
If(tid.equal(int(0)), () => {
|
|
4037
|
+
const finalLodMin = float(1e10).toVar("finalLodMin");
|
|
4038
|
+
const finalLodMax = float(-1e10).toVar("finalLodMax");
|
|
4039
|
+
const finalPackMin = float(1e10).toVar("finalPackMin");
|
|
4040
|
+
const finalPackMax = float(-1e10).toVar("finalPackMax");
|
|
4041
|
+
Loop(WGSIZE, ({ i }) => {
|
|
4042
|
+
finalLodMin.assign(min(finalLodMin, sharedLodMin.element(i)));
|
|
4043
|
+
finalLodMax.assign(max(finalLodMax, sharedLodMax.element(i)));
|
|
4044
|
+
finalPackMin.assign(min(finalPackMin, sharedPackMin.element(i)));
|
|
4045
|
+
finalPackMax.assign(max(finalPackMax, sharedPackMax.element(i)));
|
|
4046
|
+
});
|
|
4047
|
+
const outIdx = tileIdx.mul(int(TILE_BOUNDS_FLOATS_PER_TILE));
|
|
4048
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_LOD_MIN_OFFSET))).assign(finalLodMin);
|
|
4049
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_LOD_MAX_OFFSET))).assign(finalLodMax);
|
|
4050
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_PACK_MIN_OFFSET))).assign(finalPackMin);
|
|
4051
|
+
boundsNode.element(outIdx.add(int(TILE_BOUNDS_PACK_MAX_OFFSET))).assign(finalPackMax);
|
|
4052
|
+
});
|
|
4053
|
+
})().computeKernel([WGSIZE, 1, 1]);
|
|
4026
4054
|
}
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
innerTileSegments: get(innerTileSegments),
|
|
4036
|
-
skirtScale: get(skirtScale),
|
|
4037
|
-
elevationScale: get(elevationScale),
|
|
4038
|
-
radius: get(radius),
|
|
4039
|
-
instanceId: get(instanceIdTask)
|
|
4040
|
-
};
|
|
4041
|
-
return work(() => createTerrainUniforms(uniformParams));
|
|
4042
|
-
}).displayName("createUniformsTask").cache("once");
|
|
4043
|
-
const updateUniformsTask = task((get, work) => {
|
|
4044
|
-
const terrainUniformsContext = get(createUniformsTask);
|
|
4045
|
-
const rootSizeVal = get(rootSize);
|
|
4046
|
-
const rootOrigin = get(origin);
|
|
4047
|
-
const innerTileSegmentsVal = get(innerTileSegments);
|
|
4048
|
-
const skirtScaleVal = get(skirtScale);
|
|
4049
|
-
const elevationScaleVal = get(elevationScale);
|
|
4050
|
-
const radiusVal = get(radius);
|
|
4051
|
-
return work(() => {
|
|
4052
|
-
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
4053
|
-
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
4054
|
-
rootOrigin.x,
|
|
4055
|
-
rootOrigin.y,
|
|
4056
|
-
rootOrigin.z
|
|
4057
|
-
);
|
|
4058
|
-
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
4059
|
-
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
4060
|
-
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
4061
|
-
terrainUniformsContext.uRadius.value = radiusVal;
|
|
4062
|
-
return terrainUniformsContext;
|
|
4063
|
-
});
|
|
4064
|
-
}).displayName("updateUniformsTask");
|
|
4065
|
-
|
|
4066
|
-
const createElevationFieldContextTask = task((get, work) => {
|
|
4055
|
+
function runTileBoundsReduction(renderer, boundsContext, leafCount) {
|
|
4056
|
+
if (leafCount > 0) {
|
|
4057
|
+
renderer.compute(boundsContext.kernel, [1, 1, leafCount]);
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
const tileBoundsContextTask = task((get, work) => {
|
|
4061
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
4062
|
+
const maxNodesValue = get(maxNodes);
|
|
4067
4063
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
4068
|
-
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
4069
|
-
const totalElements = get(maxNodes) * verticesPerNode;
|
|
4070
4064
|
return work(() => {
|
|
4071
|
-
const
|
|
4065
|
+
const floatCount = maxNodesValue * TILE_BOUNDS_FLOATS_PER_TILE;
|
|
4066
|
+
const data = new Float32Array(floatCount);
|
|
4072
4067
|
const attribute = new StorageBufferAttribute(data, 1);
|
|
4073
|
-
attribute.name = "
|
|
4074
|
-
const node = storage(attribute, "float",
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
const topology = get(topologyTask);
|
|
4086
|
-
return work(() => {
|
|
4087
|
-
return createTileCompute(leafStorage, uniforms, topology.projection);
|
|
4088
|
-
});
|
|
4089
|
-
}).displayName("tileNodesTask");
|
|
4090
|
-
const elevationFieldStageTask = task((get, work) => {
|
|
4091
|
-
const tile = get(tileNodesTask);
|
|
4092
|
-
const uniforms = get(updateUniformsTask);
|
|
4093
|
-
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
4094
|
-
const userElevationFn = get(elevationFn);
|
|
4095
|
-
return work(() => {
|
|
4096
|
-
const heightFn = createElevationFunction(userElevationFn);
|
|
4097
|
-
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
4098
|
-
return [
|
|
4099
|
-
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
4100
|
-
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
4101
|
-
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
4102
|
-
}
|
|
4103
|
-
];
|
|
4068
|
+
attribute.name = "tileBounds";
|
|
4069
|
+
const node = storage(attribute, "float", floatCount).setName(
|
|
4070
|
+
"tileBounds"
|
|
4071
|
+
);
|
|
4072
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
4073
|
+
const kernel = buildReductionKernel(
|
|
4074
|
+
elevationFieldContext.node,
|
|
4075
|
+
node,
|
|
4076
|
+
verticesPerNode,
|
|
4077
|
+
edgeVertexCount
|
|
4078
|
+
);
|
|
4079
|
+
return { data, attribute, node, kernel };
|
|
4104
4080
|
});
|
|
4105
|
-
}).displayName("
|
|
4081
|
+
}).displayName("tileBoundsContextTask");
|
|
4106
4082
|
|
|
4107
4083
|
const createTerrainFieldTextureTask = task(
|
|
4108
4084
|
(get, work, { resources }) => {
|
|
@@ -4127,6 +4103,7 @@ const terrainFieldStageTask = task((get, work) => {
|
|
|
4127
4103
|
const tile = get(tileNodesTask);
|
|
4128
4104
|
const uniforms = get(updateUniformsTask);
|
|
4129
4105
|
const topology = get(topologyTask);
|
|
4106
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4130
4107
|
return work(() => {
|
|
4131
4108
|
const computeNormal = topology.projection.gpu.createFieldNormal({
|
|
4132
4109
|
elevationFieldNode: elevationFieldContext.node,
|
|
@@ -4141,12 +4118,13 @@ const terrainFieldStageTask = task((get, work) => {
|
|
|
4141
4118
|
const iy = int(localCoordinates.y);
|
|
4142
4119
|
const height = elevationFieldContext.node.element(globalVertexIndex);
|
|
4143
4120
|
const normal = computeNormal(nodeIndex, ix, iy);
|
|
4121
|
+
const { packMin, packMax } = loadTilePackBounds(boundsContext.node, nodeIndex);
|
|
4144
4122
|
storeTerrainField(
|
|
4145
4123
|
terrainFieldStorage,
|
|
4146
4124
|
ix,
|
|
4147
4125
|
iy,
|
|
4148
4126
|
nodeIndex,
|
|
4149
|
-
|
|
4127
|
+
packNormalizedTerrainFieldSample(height, normal, packMin, packMax)
|
|
4150
4128
|
);
|
|
4151
4129
|
}
|
|
4152
4130
|
];
|
|
@@ -4158,23 +4136,28 @@ function createComputePipelineTasks(leafStageTask) {
|
|
|
4158
4136
|
const compile = task((get, work) => {
|
|
4159
4137
|
const pipeline = get(leafStageTask);
|
|
4160
4138
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
4139
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4161
4140
|
return work(
|
|
4162
4141
|
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
4163
|
-
|
|
4142
|
+
midPipelineExecute: (renderer, instanceCount) => {
|
|
4143
|
+
runTileBoundsReduction(renderer, boundsContext, instanceCount);
|
|
4144
|
+
}
|
|
4145
|
+
})
|
|
4164
4146
|
);
|
|
4165
4147
|
}).displayName("compileComputeTask");
|
|
4166
|
-
const execute = task(
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
}
|
|
4173
|
-
);
|
|
4174
|
-
}
|
|
4175
|
-
).displayName("executeComputeTask").lane("gpu");
|
|
4148
|
+
const execute = task((get, work, { resources }) => {
|
|
4149
|
+
const { execute: run } = get(compile);
|
|
4150
|
+
const leafState = get(leafGpuBufferTask);
|
|
4151
|
+
return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
4152
|
+
});
|
|
4153
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
4176
4154
|
return { compile, execute };
|
|
4177
4155
|
}
|
|
4156
|
+
const tileBoundsReductionTask = task((get, work) => {
|
|
4157
|
+
get(executeComputeTask);
|
|
4158
|
+
const boundsContext = get(tileBoundsContextTask);
|
|
4159
|
+
return work(() => boundsContext);
|
|
4160
|
+
}).displayName("tileBoundsReductionTask").lane("gpu");
|
|
4178
4161
|
|
|
4179
4162
|
const gpuSpatialIndexStorageTask = task((get, work) => {
|
|
4180
4163
|
const maxNodesValue = get(maxNodes);
|
|
@@ -4192,6 +4175,7 @@ const gpuSpatialIndexUploadTask = task((get, work) => {
|
|
|
4192
4175
|
|
|
4193
4176
|
const createTerrainSamplerTask = task((get, work) => {
|
|
4194
4177
|
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
4178
|
+
const tileBoundsContext = get(tileBoundsContextTask);
|
|
4195
4179
|
const spatialIndex = get(gpuSpatialIndexStorageTask);
|
|
4196
4180
|
const uniforms = get(updateUniformsTask);
|
|
4197
4181
|
const elevationCallback = get(elevationFn);
|
|
@@ -4200,6 +4184,7 @@ const createTerrainSamplerTask = task((get, work) => {
|
|
|
4200
4184
|
return work(
|
|
4201
4185
|
() => createTerrainSampler({
|
|
4202
4186
|
terrainFieldStorage,
|
|
4187
|
+
tileBoundsNode: tileBoundsContext.node,
|
|
4203
4188
|
spatialIndex,
|
|
4204
4189
|
uniforms,
|
|
4205
4190
|
elevationCallback,
|
|
@@ -4213,12 +4198,14 @@ const positionNodeTask = task((get, work) => {
|
|
|
4213
4198
|
const leafStorage = get(leafStorageTask);
|
|
4214
4199
|
const terrainUniforms = get(updateUniformsTask);
|
|
4215
4200
|
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
4201
|
+
const tileBoundsContext = get(tileBoundsContextTask);
|
|
4216
4202
|
const topology = get(topologyTask);
|
|
4217
4203
|
return work(
|
|
4218
4204
|
() => topology.projection.gpu.renderVertexPosition({
|
|
4219
4205
|
leafStorage,
|
|
4220
4206
|
uniforms: terrainUniforms,
|
|
4221
|
-
terrainFieldStorage
|
|
4207
|
+
terrainFieldStorage,
|
|
4208
|
+
tileBoundsNode: tileBoundsContext.node
|
|
4222
4209
|
})
|
|
4223
4210
|
);
|
|
4224
4211
|
}).displayName("positionNodeTask");
|
|
@@ -4339,6 +4326,17 @@ function terrainGraph() {
|
|
|
4339
4326
|
return g;
|
|
4340
4327
|
}
|
|
4341
4328
|
|
|
4329
|
+
const decodeUint16RG = Fn(
|
|
4330
|
+
([sample]) => sample.r.mul(float(256)).add(sample.g).div(float(257))
|
|
4331
|
+
);
|
|
4332
|
+
const sampleHeightmapMeters = Fn(
|
|
4333
|
+
([heightmapTexture, uv, minM, _maxM, rangeM]) => {
|
|
4334
|
+
const sample = texture(heightmapTexture, uv);
|
|
4335
|
+
const normalized = decodeUint16RG(sample);
|
|
4336
|
+
return minM.add(normalized.mul(rangeM));
|
|
4337
|
+
}
|
|
4338
|
+
);
|
|
4339
|
+
|
|
4342
4340
|
const textureSpaceToVectorSpace = Fn(([value]) => {
|
|
4343
4341
|
return remap(value, float(0), float(1), float(-1), float(1));
|
|
4344
4342
|
});
|
|
@@ -4391,4 +4389,4 @@ const voronoiCells = Fn((params) => {
|
|
|
4391
4389
|
return k;
|
|
4392
4390
|
});
|
|
4393
4391
|
|
|
4394
|
-
export { ArrayTextureBackend, AtlasBackend, CUBE_FACES, CUBE_FACE_COUNT, Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, augmentCubeSphereSampler, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereProjection, createCubeSphereTopology, createElevationFieldContextTask, createFlatProjection, createFlatTopology, createInfiniteFlatTopology, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainQuery, createTerrainRaycast, createTerrainSampler, createTerrainSamplerTask, createTerrainSurfaceQuery, createTerrainUniforms, createTorusProjection, createTorusTopology, createUniformsTask, cubeFaceBasis, cubeFaceDirection, cubeFaceFromDirection, cubeFacePoint, cubeFaceUVFromDirection, deriveNormalZ, directionToFace, directionToFaceUV, directionToLatLong, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, faceUVToCube, getDeviceComputeLimits, gpuSpatialIndexStorageTask, gpuSpatialIndexUploadTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, latLongToDirection, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, positionToTorusParams, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, radius, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, skirtScale, sphereTangentFrameNormal, storeTerrainField, tangentFromAxis, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, topology, topologyTask, torusOutwardNormal$1 as torusOutwardNormal, torusUVToPoint, unpackTangentNormal, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells, wrap01 };
|
|
4392
|
+
export { ArrayTextureBackend, AtlasBackend, CUBE_FACES, CUBE_FACE_COUNT, Dir, TERRAIN_FIELD_PACK_EPSILON, TILE_BOUNDS_FLOATS_PER_TILE, TILE_BOUNDS_LOD_MAX_OFFSET, TILE_BOUNDS_LOD_MIN_OFFSET, TILE_BOUNDS_PACK_MAX_OFFSET, TILE_BOUNDS_PACK_MIN_OFFSET, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, augmentCubeSphereSampler, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereProjection, createCubeSphereTopology, createElevationFieldContextTask, createFlatProjection, createFlatTopology, createInfiniteFlatTopology, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainQuery, createTerrainRaycast, createTerrainSampler, createTerrainSamplerTask, createTerrainSurfaceQuery, createTerrainUniforms, createTorusProjection, createTorusTopology, createUniformsTask, cubeFaceBasis, cubeFaceDirection, cubeFaceFromDirection, cubeFacePoint, cubeFaceUVFromDirection, decodeUint16RG, denormalizeTerrainFieldElevation, deriveNormalZ, directionToFace, directionToFaceUV, directionToLatLong, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, faceUVToCube, getDeviceComputeLimits, gpuSpatialIndexStorageTask, gpuSpatialIndexUploadTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, latLongToDirection, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, loadTilePackBounds, maxLevel, maxNodes, origin, packNormalizedTerrainFieldSample, packTerrainFieldSample, positionNodeTask, positionToTorusParams, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, radius, resetLeafSet, resetSeamTable, rootSize, runTileBoundsReduction, sampleHeightmapMeters, sampleTerrainField, sampleTerrainFieldElevation, skirtScale, sphereTangentFrameNormal, storeTerrainField, tangentFromAxis, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainQueryTask, terrainRaycastTask, terrainReadbackTask, terrainTasks, textureSpaceToVectorSpace, tileBoundsContextTask, tileBoundsReductionTask, tileNodesTask, topology, topologyTask, torusOutwardNormal$1 as torusOutwardNormal, torusUVToPoint, unpackTangentNormal, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells, wrap01 };
|