@hello-terrain/three 0.0.0-alpha.6 → 0.0.0-alpha.7
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 +428 -108
- package/dist/index.d.cts +60 -32
- package/dist/index.d.mts +60 -32
- package/dist/index.d.ts +60 -32
- package/dist/index.mjs +420 -110
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BufferGeometry, BufferAttribute, Vector3 as Vector3$1 } from 'three';
|
|
2
|
-
import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageBufferAttribute, Vector3 } from 'three/webgpu';
|
|
1
|
+
import { BufferGeometry, BufferAttribute, RGBAFormat, ClampToEdgeWrapping, HalfFloatType, FloatType, LinearFilter, NearestFilter, Vector3 as Vector3$1 } from 'three';
|
|
2
|
+
import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageTexture, StorageArrayTexture, StorageBufferAttribute, Vector3 } from 'three/webgpu';
|
|
3
3
|
import { param, task, graph } from '@hello-terrain/work';
|
|
4
|
-
import { uniform, Fn, float, globalId, int, vec2, uint, workgroupBarrier,
|
|
4
|
+
import { uniform, Fn, float, globalId, int, vec2, uint, If, workgroupBarrier, textureStore, uvec3, vec4, ivec2, ivec3, textureLoad, instanceIndex, min, max, pow, vec3, storage, vertexIndex, uv, select, positionLocal, normalLocal, remap, dot, varyingProperty, mx_noise_float, Loop, 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 {
|
|
@@ -274,42 +274,330 @@ class TerrainMesh extends InstancedMesh {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
function getDeviceComputeLimits(renderer) {
|
|
278
|
+
const backend = renderer.backend;
|
|
279
|
+
const limits = backend?.device?.limits;
|
|
280
|
+
return {
|
|
281
|
+
maxWorkgroupSizeX: limits?.maxComputeWorkgroupSizeX ?? 256,
|
|
282
|
+
maxWorkgroupSizeY: limits?.maxComputeWorkgroupSizeY ?? 256,
|
|
283
|
+
maxWorkgroupInvocations: limits?.maxComputeWorkgroupInvocations ?? 256
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
277
287
|
const WORKGROUP_X = 16;
|
|
278
288
|
const WORKGROUP_Y = 16;
|
|
279
|
-
function compileComputePipeline(stages, width,
|
|
280
|
-
const
|
|
281
|
-
const
|
|
282
|
-
|
|
289
|
+
function compileComputePipeline(stages, width, options) {
|
|
290
|
+
const bindings = options?.bindings;
|
|
291
|
+
const preferredWorkgroup = options?.workgroupSize ?? [
|
|
292
|
+
WORKGROUP_X,
|
|
293
|
+
WORKGROUP_Y
|
|
294
|
+
];
|
|
295
|
+
const preferSingleKernelWhenPossible = options?.preferSingleKernelWhenPossible ?? true;
|
|
283
296
|
const uInstanceCount = uniform(0, "uint");
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
let singleKernel;
|
|
298
|
+
const stagedKernelCache = /* @__PURE__ */ new Map();
|
|
299
|
+
function canRunSingleKernel(widthValue, limits) {
|
|
300
|
+
return widthValue <= limits.maxWorkgroupSizeX && widthValue <= limits.maxWorkgroupSizeY && widthValue * widthValue <= limits.maxWorkgroupInvocations;
|
|
301
|
+
}
|
|
302
|
+
function clampWorkgroupToLimits(requested, limits) {
|
|
303
|
+
let x = Math.max(1, Math.floor(requested[0]));
|
|
304
|
+
let y = Math.max(1, Math.floor(requested[1]));
|
|
305
|
+
x = Math.min(x, limits.maxWorkgroupSizeX);
|
|
306
|
+
y = Math.min(y, limits.maxWorkgroupSizeY);
|
|
307
|
+
y = Math.min(
|
|
308
|
+
y,
|
|
309
|
+
Math.max(1, Math.floor(limits.maxWorkgroupInvocations / x))
|
|
310
|
+
);
|
|
311
|
+
x = Math.min(
|
|
312
|
+
x,
|
|
313
|
+
Math.max(1, Math.floor(limits.maxWorkgroupInvocations / y))
|
|
314
|
+
);
|
|
315
|
+
return [x, y];
|
|
316
|
+
}
|
|
317
|
+
function buildSingleKernel(workgroupSize) {
|
|
318
|
+
return Fn(() => {
|
|
319
|
+
bindings?.forEach((b) => b.toVar());
|
|
320
|
+
const fWidth = float(width);
|
|
321
|
+
const activeIndex = globalId.z;
|
|
322
|
+
const nodeIndex = int(activeIndex).toVar();
|
|
323
|
+
const iWidth = int(width);
|
|
324
|
+
const ix = int(globalId.x);
|
|
325
|
+
const iy = int(globalId.y);
|
|
326
|
+
const texelSize = vec2(1, 1).div(fWidth);
|
|
327
|
+
const localCoordinates = vec2(globalId.x, globalId.y);
|
|
328
|
+
const localUVCoords = localCoordinates.div(fWidth);
|
|
329
|
+
const verticesPerNode = iWidth.mul(iWidth);
|
|
330
|
+
const globalIndex = int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
|
|
331
|
+
const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(uint(activeIndex).lessThan(uInstanceCount)).toVar();
|
|
332
|
+
for (let i = 0; i < stages.length; i++) {
|
|
333
|
+
if (i > 0) {
|
|
334
|
+
workgroupBarrier();
|
|
335
|
+
}
|
|
336
|
+
If(inBounds, () => {
|
|
337
|
+
stages[i](
|
|
338
|
+
nodeIndex,
|
|
339
|
+
globalIndex,
|
|
340
|
+
localUVCoords,
|
|
341
|
+
localCoordinates,
|
|
342
|
+
texelSize
|
|
343
|
+
);
|
|
344
|
+
});
|
|
300
345
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
346
|
+
})().computeKernel(workgroupSize);
|
|
347
|
+
}
|
|
348
|
+
function buildStagedKernels(workgroupSize) {
|
|
349
|
+
return stages.map(
|
|
350
|
+
(stage) => Fn(() => {
|
|
351
|
+
bindings?.forEach((b) => b.toVar());
|
|
352
|
+
const fWidth = float(width);
|
|
353
|
+
const activeIndex = globalId.z;
|
|
354
|
+
const nodeIndex = int(activeIndex).toVar();
|
|
355
|
+
const iWidth = int(width);
|
|
356
|
+
const ix = int(globalId.x);
|
|
357
|
+
const iy = int(globalId.y);
|
|
358
|
+
const texelSize = vec2(1, 1).div(fWidth);
|
|
359
|
+
const localCoordinates = vec2(globalId.x, globalId.y);
|
|
360
|
+
const localUVCoords = localCoordinates.div(fWidth);
|
|
361
|
+
const verticesPerNode = iWidth.mul(iWidth);
|
|
362
|
+
const globalIndex = int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
|
|
363
|
+
const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(uint(activeIndex).lessThan(uInstanceCount)).toVar();
|
|
364
|
+
If(inBounds, () => {
|
|
365
|
+
stage(
|
|
366
|
+
nodeIndex,
|
|
367
|
+
globalIndex,
|
|
368
|
+
localUVCoords,
|
|
369
|
+
localCoordinates,
|
|
370
|
+
texelSize
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
})().computeKernel(workgroupSize)
|
|
374
|
+
);
|
|
375
|
+
}
|
|
306
376
|
function execute(renderer, instanceCount) {
|
|
377
|
+
const limits = getDeviceComputeLimits(renderer);
|
|
378
|
+
const canUseSingleKernel = preferSingleKernelWhenPossible && canRunSingleKernel(width, limits);
|
|
307
379
|
uInstanceCount.value = instanceCount;
|
|
308
|
-
|
|
380
|
+
if (canUseSingleKernel) {
|
|
381
|
+
if (!singleKernel) {
|
|
382
|
+
singleKernel = buildSingleKernel([width, width, 1]);
|
|
383
|
+
}
|
|
384
|
+
renderer.compute(singleKernel, [1, 1, instanceCount]);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const [workgroupX, workgroupY] = clampWorkgroupToLimits(
|
|
388
|
+
preferredWorkgroup,
|
|
389
|
+
limits
|
|
390
|
+
);
|
|
391
|
+
const cacheKey = `${workgroupX}x${workgroupY}`;
|
|
392
|
+
let stagedKernels = stagedKernelCache.get(cacheKey);
|
|
393
|
+
if (!stagedKernels) {
|
|
394
|
+
stagedKernels = buildStagedKernels([workgroupX, workgroupY, 1]);
|
|
395
|
+
stagedKernelCache.set(cacheKey, stagedKernels);
|
|
396
|
+
}
|
|
397
|
+
const dispatchX = Math.ceil(width / workgroupX);
|
|
398
|
+
const dispatchY = Math.ceil(width / workgroupY);
|
|
399
|
+
for (const kernel of stagedKernels) {
|
|
400
|
+
renderer.compute(kernel, [dispatchX, dispatchY, instanceCount]);
|
|
401
|
+
}
|
|
309
402
|
}
|
|
310
403
|
return { execute };
|
|
311
404
|
}
|
|
312
405
|
|
|
406
|
+
function resolveType(format) {
|
|
407
|
+
return format === "rgba16float" ? HalfFloatType : FloatType;
|
|
408
|
+
}
|
|
409
|
+
function resolveFilter(mode) {
|
|
410
|
+
return mode === "linear" ? LinearFilter : NearestFilter;
|
|
411
|
+
}
|
|
412
|
+
function configureStorageTexture(texture, format, filter) {
|
|
413
|
+
texture.format = RGBAFormat;
|
|
414
|
+
texture.type = resolveType(format);
|
|
415
|
+
texture.magFilter = resolveFilter(filter);
|
|
416
|
+
texture.minFilter = resolveFilter(filter);
|
|
417
|
+
texture.wrapS = ClampToEdgeWrapping;
|
|
418
|
+
texture.wrapT = ClampToEdgeWrapping;
|
|
419
|
+
texture.generateMipmaps = false;
|
|
420
|
+
texture.needsUpdate = true;
|
|
421
|
+
}
|
|
422
|
+
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
423
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
424
|
+
let currentTileCount = tileCount;
|
|
425
|
+
const texture = new StorageArrayTexture(
|
|
426
|
+
edgeVertexCount,
|
|
427
|
+
edgeVertexCount,
|
|
428
|
+
tileCount
|
|
429
|
+
);
|
|
430
|
+
configureStorageTexture(texture, options.format, options.filter);
|
|
431
|
+
return {
|
|
432
|
+
backendType: "array-texture",
|
|
433
|
+
get edgeVertexCount() {
|
|
434
|
+
return currentEdgeVertexCount;
|
|
435
|
+
},
|
|
436
|
+
get tileCount() {
|
|
437
|
+
return currentTileCount;
|
|
438
|
+
},
|
|
439
|
+
texture,
|
|
440
|
+
uv(ix, iy, _tileIndex) {
|
|
441
|
+
return vec2(ix.toFloat(), iy.toFloat());
|
|
442
|
+
},
|
|
443
|
+
texel(ix, iy, tileIndex) {
|
|
444
|
+
return ivec3(ix, iy, tileIndex);
|
|
445
|
+
},
|
|
446
|
+
resize(width, height, nextTileCount) {
|
|
447
|
+
currentEdgeVertexCount = width;
|
|
448
|
+
currentTileCount = nextTileCount;
|
|
449
|
+
texture.setSize(width, height, nextTileCount);
|
|
450
|
+
texture.needsUpdate = true;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
|
|
455
|
+
const tilesPerRowNode = int(tilesPerRow);
|
|
456
|
+
const edge = int(edgeVertexCount);
|
|
457
|
+
const tile = int(tileIndex);
|
|
458
|
+
const col = tile.mod(tilesPerRowNode);
|
|
459
|
+
const row = tile.div(tilesPerRowNode);
|
|
460
|
+
const atlasX = col.mul(edge).add(int(ix));
|
|
461
|
+
const atlasY = row.mul(edge).add(int(iy));
|
|
462
|
+
return { atlasX, atlasY };
|
|
463
|
+
}
|
|
464
|
+
function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
465
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
466
|
+
let currentTileCount = tileCount;
|
|
467
|
+
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
468
|
+
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
469
|
+
const texture = new StorageTexture(atlasSize, atlasSize);
|
|
470
|
+
configureStorageTexture(texture, options.format, options.filter);
|
|
471
|
+
return {
|
|
472
|
+
backendType: "atlas",
|
|
473
|
+
get edgeVertexCount() {
|
|
474
|
+
return currentEdgeVertexCount;
|
|
475
|
+
},
|
|
476
|
+
get tileCount() {
|
|
477
|
+
return currentTileCount;
|
|
478
|
+
},
|
|
479
|
+
texture,
|
|
480
|
+
uv(ix, iy, tileIndex) {
|
|
481
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
482
|
+
tilesPerRow,
|
|
483
|
+
currentEdgeVertexCount,
|
|
484
|
+
ix,
|
|
485
|
+
iy,
|
|
486
|
+
tileIndex
|
|
487
|
+
);
|
|
488
|
+
const currentAtlasSize = float(tilesPerRow * currentEdgeVertexCount);
|
|
489
|
+
return vec2(
|
|
490
|
+
atlasX.toFloat().add(0.5).div(currentAtlasSize),
|
|
491
|
+
atlasY.toFloat().add(0.5).div(currentAtlasSize)
|
|
492
|
+
);
|
|
493
|
+
},
|
|
494
|
+
texel(ix, iy, tileIndex) {
|
|
495
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
496
|
+
tilesPerRow,
|
|
497
|
+
currentEdgeVertexCount,
|
|
498
|
+
ix,
|
|
499
|
+
iy,
|
|
500
|
+
tileIndex
|
|
501
|
+
);
|
|
502
|
+
return ivec2(atlasX, atlasY);
|
|
503
|
+
},
|
|
504
|
+
resize(width, height, nextTileCount) {
|
|
505
|
+
currentEdgeVertexCount = width;
|
|
506
|
+
currentTileCount = nextTileCount;
|
|
507
|
+
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
508
|
+
const nextAtlasSize = tilesPerRow * width;
|
|
509
|
+
const image = texture.image;
|
|
510
|
+
image.width = nextAtlasSize;
|
|
511
|
+
image.height = nextAtlasSize;
|
|
512
|
+
texture.needsUpdate = true;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function Texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
517
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
518
|
+
let currentTileCount = tileCount;
|
|
519
|
+
const texture = new StorageArrayTexture(
|
|
520
|
+
edgeVertexCount,
|
|
521
|
+
edgeVertexCount,
|
|
522
|
+
tileCount
|
|
523
|
+
);
|
|
524
|
+
configureStorageTexture(texture, options.format, options.filter);
|
|
525
|
+
return {
|
|
526
|
+
backendType: "texture-3d",
|
|
527
|
+
get edgeVertexCount() {
|
|
528
|
+
return currentEdgeVertexCount;
|
|
529
|
+
},
|
|
530
|
+
get tileCount() {
|
|
531
|
+
return currentTileCount;
|
|
532
|
+
},
|
|
533
|
+
texture,
|
|
534
|
+
uv(ix, iy, _tileIndex) {
|
|
535
|
+
return vec2(ix.toFloat(), iy.toFloat());
|
|
536
|
+
},
|
|
537
|
+
texel(ix, iy, tileIndex) {
|
|
538
|
+
return ivec3(ix, iy, tileIndex);
|
|
539
|
+
},
|
|
540
|
+
resize(width, height, nextTileCount) {
|
|
541
|
+
currentEdgeVertexCount = width;
|
|
542
|
+
currentTileCount = nextTileCount;
|
|
543
|
+
texture.setSize(width, height, nextTileCount);
|
|
544
|
+
texture.needsUpdate = true;
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function tryGetDeviceLimits(renderer) {
|
|
549
|
+
const backend = renderer;
|
|
550
|
+
return backend.backend?.device?.limits ?? {};
|
|
551
|
+
}
|
|
552
|
+
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
553
|
+
const filter = options.filter ?? "nearest";
|
|
554
|
+
const format = options.format ?? "rgba16float";
|
|
555
|
+
const forcedBackend = options.backend;
|
|
556
|
+
if (forcedBackend === "atlas") {
|
|
557
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
558
|
+
}
|
|
559
|
+
if (forcedBackend === "texture-3d") {
|
|
560
|
+
return Texture3DBackend(edgeVertexCount, tileCount, { filter, format });
|
|
561
|
+
}
|
|
562
|
+
if (forcedBackend === "array-texture") {
|
|
563
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
564
|
+
}
|
|
565
|
+
const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
|
|
566
|
+
const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
|
|
567
|
+
if (tileCount > maxLayers) {
|
|
568
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
569
|
+
}
|
|
570
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
571
|
+
}
|
|
572
|
+
function storeTerrainField(storage, ix, iy, tileIndex, value) {
|
|
573
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
574
|
+
return textureStore(
|
|
575
|
+
storage.texture,
|
|
576
|
+
uvec3(int(ix), int(iy), int(tileIndex)),
|
|
577
|
+
value
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
return textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
|
|
581
|
+
}
|
|
582
|
+
function loadTerrainField(storage, ix, iy, tileIndex) {
|
|
583
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
584
|
+
return textureLoad(storage.texture, ivec2(int(ix), int(iy)), int(0)).depth(
|
|
585
|
+
int(tileIndex)
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
return textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), int(0));
|
|
589
|
+
}
|
|
590
|
+
function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
591
|
+
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
592
|
+
}
|
|
593
|
+
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
594
|
+
const sample = loadTerrainField(storage, ix, iy, tileIndex);
|
|
595
|
+
return vec2(sample.g, sample.b);
|
|
596
|
+
}
|
|
597
|
+
function packTerrainFieldSample(height, normalXZ, extra = float(0)) {
|
|
598
|
+
return vec4(height, normalXZ.x, normalXZ.y, extra);
|
|
599
|
+
}
|
|
600
|
+
|
|
313
601
|
const createElevation = (tile, uniforms, elevationFn) => {
|
|
314
602
|
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
315
603
|
const ix = int(localCoordinates.x);
|
|
@@ -331,7 +619,7 @@ const createElevation = (tile, uniforms, elevationFn) => {
|
|
|
331
619
|
});
|
|
332
620
|
};
|
|
333
621
|
};
|
|
334
|
-
const readElevationFieldAtPositionLocal = (
|
|
622
|
+
const readElevationFieldAtPositionLocal = (terrainFieldStorage, edgeVertexCount, positionLocal) => Fn(() => {
|
|
335
623
|
const nodeIndex = int(instanceIndex);
|
|
336
624
|
const intEdge = int(edgeVertexCount);
|
|
337
625
|
const innerSegments = int(edgeVertexCount).sub(3);
|
|
@@ -343,10 +631,12 @@ const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount
|
|
|
343
631
|
const y = v.mul(fInnerSegments).round().toInt().add(int(1));
|
|
344
632
|
const xClamped = min(max(x, int(0)), last);
|
|
345
633
|
const yClamped = min(max(y, int(0)), last);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
634
|
+
return loadTerrainFieldElevation(
|
|
635
|
+
terrainFieldStorage,
|
|
636
|
+
xClamped,
|
|
637
|
+
yClamped,
|
|
638
|
+
nodeIndex
|
|
639
|
+
);
|
|
350
640
|
});
|
|
351
641
|
|
|
352
642
|
function createTileCompute(leafStorage, uniforms) {
|
|
@@ -1239,21 +1529,19 @@ const elevationFieldStageTask = task((get, work) => {
|
|
|
1239
1529
|
});
|
|
1240
1530
|
}).displayName("elevationFieldStageTask");
|
|
1241
1531
|
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
});
|
|
1256
|
-
}).displayName("createNormalFieldContextTask");
|
|
1532
|
+
const createTerrainFieldTextureTask = task(
|
|
1533
|
+
(get, work, { resources }) => {
|
|
1534
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1535
|
+
const maxNodesValue = get(maxNodes);
|
|
1536
|
+
return work(
|
|
1537
|
+
() => createTerrainFieldStorage(
|
|
1538
|
+
edgeVertexCount,
|
|
1539
|
+
maxNodesValue,
|
|
1540
|
+
resources?.renderer
|
|
1541
|
+
)
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
).displayName("createTerrainFieldTextureTask");
|
|
1257
1545
|
function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1258
1546
|
return Fn(
|
|
1259
1547
|
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
@@ -1278,10 +1566,10 @@ function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
|
1278
1566
|
}
|
|
1279
1567
|
);
|
|
1280
1568
|
}
|
|
1281
|
-
const
|
|
1569
|
+
const terrainFieldStageTask = task((get, work) => {
|
|
1282
1570
|
const upstream = get(elevationFieldStageTask);
|
|
1283
1571
|
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1284
|
-
const
|
|
1572
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1285
1573
|
const tileEdgeVertexCount = get(innerTileSegments) + 3;
|
|
1286
1574
|
const tile = get(tileNodesTask);
|
|
1287
1575
|
const uniforms = get(createUniformsTask);
|
|
@@ -1296,6 +1584,7 @@ const normalFieldStageTask = task((get, work) => {
|
|
|
1296
1584
|
const ix = int(localCoordinates.x);
|
|
1297
1585
|
const iy = int(localCoordinates.y);
|
|
1298
1586
|
const tileSize = tile.tileSize(nodeIndex);
|
|
1587
|
+
const height = elevationFieldContext.node.element(globalVertexIndex);
|
|
1299
1588
|
const normalXZ = computeNormal(
|
|
1300
1589
|
nodeIndex,
|
|
1301
1590
|
tileSize,
|
|
@@ -1303,58 +1592,60 @@ const normalFieldStageTask = task((get, work) => {
|
|
|
1303
1592
|
iy,
|
|
1304
1593
|
uniforms.uElevationScale
|
|
1305
1594
|
);
|
|
1306
|
-
|
|
1595
|
+
storeTerrainField(
|
|
1596
|
+
terrainFieldStorage,
|
|
1597
|
+
ix,
|
|
1598
|
+
iy,
|
|
1599
|
+
nodeIndex,
|
|
1600
|
+
packTerrainFieldSample(height, normalXZ)
|
|
1601
|
+
);
|
|
1307
1602
|
}
|
|
1308
1603
|
];
|
|
1309
1604
|
});
|
|
1310
|
-
}).displayName("
|
|
1605
|
+
}).displayName("terrainFieldStageTask");
|
|
1311
1606
|
|
|
1312
1607
|
const compileComputeTask = task((get, work) => {
|
|
1313
|
-
const pipeline = get(
|
|
1608
|
+
const pipeline = get(terrainFieldStageTask);
|
|
1314
1609
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1315
|
-
return work(() => compileComputePipeline(pipeline, edgeVertexCount));
|
|
1316
|
-
}).displayName("compileComputeTask");
|
|
1317
|
-
const executeComputeTask = task((get, work, { resources }) => {
|
|
1318
|
-
const { execute } = get(compileComputeTask);
|
|
1319
|
-
const leafState = get(leafGpuBufferTask);
|
|
1320
1610
|
return work(
|
|
1321
|
-
() =>
|
|
1322
|
-
|
|
1611
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1612
|
+
preferSingleKernelWhenPossible: false
|
|
1613
|
+
})
|
|
1323
1614
|
);
|
|
1324
|
-
}).displayName("
|
|
1615
|
+
}).displayName("compileComputeTask");
|
|
1616
|
+
const executeComputeTask = task(
|
|
1617
|
+
(get, work, { resources }) => {
|
|
1618
|
+
const { execute } = get(compileComputeTask);
|
|
1619
|
+
const leafState = get(leafGpuBufferTask);
|
|
1620
|
+
return work(
|
|
1621
|
+
() => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
|
|
1622
|
+
}
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
).displayName("executeComputeTask").lane("gpu");
|
|
1325
1626
|
function createComputePipelineTasks(leafStageTask) {
|
|
1326
1627
|
const compile = task((get, work) => {
|
|
1327
1628
|
const pipeline = get(leafStageTask);
|
|
1328
1629
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1329
|
-
return work(
|
|
1630
|
+
return work(
|
|
1631
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1632
|
+
preferSingleKernelWhenPossible: false
|
|
1633
|
+
})
|
|
1634
|
+
);
|
|
1330
1635
|
}).displayName("compileComputeTask");
|
|
1331
|
-
const execute = task(
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1636
|
+
const execute = task(
|
|
1637
|
+
(get, work, { resources }) => {
|
|
1638
|
+
const { execute: run } = get(compile);
|
|
1639
|
+
const leafState = get(leafGpuBufferTask);
|
|
1640
|
+
return work(
|
|
1641
|
+
() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
1642
|
+
}
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
).displayName("executeComputeTask").lane("gpu");
|
|
1337
1646
|
return { compile, execute };
|
|
1338
1647
|
}
|
|
1339
1648
|
|
|
1340
|
-
const textureSpaceToVectorSpace = Fn(([value]) => {
|
|
1341
|
-
return remap(value, float(0), float(1), float(-1), float(1));
|
|
1342
|
-
});
|
|
1343
|
-
const vectorSpaceToTextureSpace = Fn(([value]) => {
|
|
1344
|
-
return remap(value, float(-1), float(1), float(0), float(1));
|
|
1345
|
-
});
|
|
1346
|
-
const blendAngleCorrectedNormals = Fn(([n1, n2]) => {
|
|
1347
|
-
const t = vec3(n1.x, n1.y, n1.z.add(1));
|
|
1348
|
-
const u = vec3(n2.x.negate(), n2.y.negate(), n2.z);
|
|
1349
|
-
const r = t.mul(dot(t, u)).sub(u.mul(t.z)).normalize();
|
|
1350
|
-
return r;
|
|
1351
|
-
});
|
|
1352
|
-
const deriveNormalZ = Fn(([normalXY]) => {
|
|
1353
|
-
const xy = normalXY.toVar();
|
|
1354
|
-
const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
|
|
1355
|
-
return vec3(xy.x, xy.y, z);
|
|
1356
|
-
});
|
|
1357
|
-
|
|
1358
1649
|
const isSkirtVertex = Fn(([segments]) => {
|
|
1359
1650
|
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
1360
1651
|
const vIndex = int(vertexIndex);
|
|
@@ -1396,37 +1687,40 @@ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
|
1396
1687
|
return vec3(worldX, rootOrigin.y, worldZ);
|
|
1397
1688
|
});
|
|
1398
1689
|
}
|
|
1399
|
-
function createTileElevation(terrainUniforms,
|
|
1400
|
-
if (!
|
|
1690
|
+
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1691
|
+
if (!terrainFieldStorage) return float(0);
|
|
1401
1692
|
const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
|
|
1402
1693
|
return readElevationFieldAtPositionLocal(
|
|
1403
|
-
|
|
1694
|
+
terrainFieldStorage,
|
|
1404
1695
|
edgeVertexCount,
|
|
1405
1696
|
positionLocal
|
|
1406
1697
|
)().mul(
|
|
1407
1698
|
terrainUniforms.uElevationScale
|
|
1408
1699
|
);
|
|
1409
1700
|
}
|
|
1410
|
-
function createNormalAssignment(terrainUniforms,
|
|
1411
|
-
if (!
|
|
1701
|
+
function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
|
|
1702
|
+
if (!terrainFieldStorage) return;
|
|
1412
1703
|
const nodeIndex = int(instanceIndex);
|
|
1413
|
-
const
|
|
1414
|
-
const
|
|
1415
|
-
const
|
|
1416
|
-
const
|
|
1417
|
-
const normalXZ =
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1704
|
+
const edgeVertexCount = int(terrainUniforms.uInnerTileSegments.add(3));
|
|
1705
|
+
const localVertexIndex = int(vertexIndex);
|
|
1706
|
+
const ix = localVertexIndex.mod(edgeVertexCount);
|
|
1707
|
+
const iy = localVertexIndex.div(edgeVertexCount);
|
|
1708
|
+
const normalXZ = loadTerrainFieldNormal(terrainFieldStorage, ix, iy, nodeIndex);
|
|
1709
|
+
const nx = normalXZ.x;
|
|
1710
|
+
const nz = normalXZ.y;
|
|
1711
|
+
const nySq = float(1).sub(nx.mul(nx)).sub(nz.mul(nz)).max(float(0));
|
|
1712
|
+
const ny = nySq.sqrt();
|
|
1713
|
+
normalLocal.assign(vec3(nx, ny, nz));
|
|
1420
1714
|
}
|
|
1421
|
-
function createTileWorldPosition(leafStorage, terrainUniforms,
|
|
1715
|
+
function createTileWorldPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1422
1716
|
const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
|
|
1423
1717
|
return Fn(() => {
|
|
1424
1718
|
const base = baseWorldPosition();
|
|
1425
|
-
const yElevation = createTileElevation(terrainUniforms,
|
|
1719
|
+
const yElevation = createTileElevation(terrainUniforms, terrainFieldStorage);
|
|
1426
1720
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1427
1721
|
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1428
1722
|
const worldY = select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
1429
|
-
createNormalAssignment(terrainUniforms,
|
|
1723
|
+
createNormalAssignment(terrainUniforms, terrainFieldStorage);
|
|
1430
1724
|
return vec3(base.x, worldY, base.z);
|
|
1431
1725
|
})();
|
|
1432
1726
|
}
|
|
@@ -1434,20 +1728,18 @@ function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBuf
|
|
|
1434
1728
|
const positionNodeTask = task((get, work) => {
|
|
1435
1729
|
const leafStorage = get(leafStorageTask);
|
|
1436
1730
|
const terrainUniforms = get(createUniformsTask);
|
|
1437
|
-
const
|
|
1438
|
-
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1731
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1439
1732
|
return work(
|
|
1440
1733
|
() => createTileWorldPosition(
|
|
1441
1734
|
leafStorage,
|
|
1442
1735
|
terrainUniforms,
|
|
1443
|
-
|
|
1444
|
-
normalFieldContext.node
|
|
1736
|
+
terrainFieldStorage
|
|
1445
1737
|
)
|
|
1446
1738
|
);
|
|
1447
1739
|
}).displayName("positionNodeTask");
|
|
1448
1740
|
|
|
1449
1741
|
function terrainGraph() {
|
|
1450
|
-
return graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(
|
|
1742
|
+
return graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(createTerrainFieldTextureTask).add(elevationFieldStageTask).add(terrainFieldStageTask).add(compileComputeTask).add(executeComputeTask);
|
|
1451
1743
|
}
|
|
1452
1744
|
const terrainTasks = {
|
|
1453
1745
|
instanceId: instanceIdTask,
|
|
@@ -1461,13 +1753,31 @@ const terrainTasks = {
|
|
|
1461
1753
|
positionNode: positionNodeTask,
|
|
1462
1754
|
createElevationFieldContext: createElevationFieldContextTask,
|
|
1463
1755
|
createTileNodes: tileNodesTask,
|
|
1464
|
-
|
|
1756
|
+
createTerrainFieldTexture: createTerrainFieldTextureTask,
|
|
1465
1757
|
elevationFieldStage: elevationFieldStageTask,
|
|
1466
|
-
|
|
1758
|
+
terrainFieldStage: terrainFieldStageTask,
|
|
1467
1759
|
compileCompute: compileComputeTask,
|
|
1468
1760
|
executeCompute: executeComputeTask
|
|
1469
1761
|
};
|
|
1470
1762
|
|
|
1763
|
+
const textureSpaceToVectorSpace = Fn(([value]) => {
|
|
1764
|
+
return remap(value, float(0), float(1), float(-1), float(1));
|
|
1765
|
+
});
|
|
1766
|
+
const vectorSpaceToTextureSpace = Fn(([value]) => {
|
|
1767
|
+
return remap(value, float(-1), float(1), float(0), float(1));
|
|
1768
|
+
});
|
|
1769
|
+
const blendAngleCorrectedNormals = Fn(([n1, n2]) => {
|
|
1770
|
+
const t = vec3(n1.x, n1.y, n1.z.add(1));
|
|
1771
|
+
const u = vec3(n2.x.negate(), n2.y.negate(), n2.z);
|
|
1772
|
+
const r = t.mul(dot(t, u)).sub(u.mul(t.z)).normalize();
|
|
1773
|
+
return r;
|
|
1774
|
+
});
|
|
1775
|
+
const deriveNormalZ = Fn(([normalXY]) => {
|
|
1776
|
+
const xy = normalXY.toVar();
|
|
1777
|
+
const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
|
|
1778
|
+
return vec3(xy.x, xy.y, z);
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1471
1781
|
const vGlobalVertexIndex = /* @__PURE__ */ varyingProperty("int", "vGlobalVertexIndex");
|
|
1472
1782
|
const vElevation = /* @__PURE__ */ varyingProperty("f32", "vElevation");
|
|
1473
1783
|
|
|
@@ -1502,4 +1812,4 @@ const voronoiCells = Fn((params) => {
|
|
|
1502
1812
|
return k;
|
|
1503
1813
|
});
|
|
1504
1814
|
|
|
1505
|
-
export { Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface,
|
|
1815
|
+
export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
|