@hello-terrain/three 0.0.0-alpha.6 → 0.0.0-alpha.8
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 +461 -108
- package/dist/index.d.cts +71 -32
- package/dist/index.d.mts +71 -32
- package/dist/index.d.ts +71 -32
- package/dist/index.mjs +449 -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, texture, 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,356 @@ 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(texture2, format, filter) {
|
|
413
|
+
texture2.format = RGBAFormat;
|
|
414
|
+
texture2.type = resolveType(format);
|
|
415
|
+
texture2.magFilter = resolveFilter(filter);
|
|
416
|
+
texture2.minFilter = resolveFilter(filter);
|
|
417
|
+
texture2.wrapS = ClampToEdgeWrapping;
|
|
418
|
+
texture2.wrapT = ClampToEdgeWrapping;
|
|
419
|
+
texture2.generateMipmaps = false;
|
|
420
|
+
texture2.needsUpdate = true;
|
|
421
|
+
}
|
|
422
|
+
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
423
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
424
|
+
let currentTileCount = tileCount;
|
|
425
|
+
const tex = new StorageArrayTexture(
|
|
426
|
+
edgeVertexCount,
|
|
427
|
+
edgeVertexCount,
|
|
428
|
+
tileCount
|
|
429
|
+
);
|
|
430
|
+
configureStorageTexture(tex, 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: tex,
|
|
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
|
+
sample(u, v, tileIndex) {
|
|
447
|
+
return texture(tex, vec2(u, v)).depth(int(tileIndex));
|
|
448
|
+
},
|
|
449
|
+
resize(width, height, nextTileCount) {
|
|
450
|
+
currentEdgeVertexCount = width;
|
|
451
|
+
currentTileCount = nextTileCount;
|
|
452
|
+
tex.setSize(width, height, nextTileCount);
|
|
453
|
+
tex.needsUpdate = true;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
|
|
458
|
+
const tilesPerRowNode = int(tilesPerRow);
|
|
459
|
+
const edge = int(edgeVertexCount);
|
|
460
|
+
const tile = int(tileIndex);
|
|
461
|
+
const col = tile.mod(tilesPerRowNode);
|
|
462
|
+
const row = tile.div(tilesPerRowNode);
|
|
463
|
+
const atlasX = col.mul(edge).add(int(ix));
|
|
464
|
+
const atlasY = row.mul(edge).add(int(iy));
|
|
465
|
+
return { atlasX, atlasY };
|
|
466
|
+
}
|
|
467
|
+
function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
468
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
469
|
+
let currentTileCount = tileCount;
|
|
470
|
+
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
471
|
+
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
472
|
+
const tex = new StorageTexture(atlasSize, atlasSize);
|
|
473
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
474
|
+
return {
|
|
475
|
+
backendType: "atlas",
|
|
476
|
+
get edgeVertexCount() {
|
|
477
|
+
return currentEdgeVertexCount;
|
|
478
|
+
},
|
|
479
|
+
get tileCount() {
|
|
480
|
+
return currentTileCount;
|
|
481
|
+
},
|
|
482
|
+
texture: tex,
|
|
483
|
+
uv(ix, iy, tileIndex) {
|
|
484
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
485
|
+
tilesPerRow,
|
|
486
|
+
currentEdgeVertexCount,
|
|
487
|
+
ix,
|
|
488
|
+
iy,
|
|
489
|
+
tileIndex
|
|
490
|
+
);
|
|
491
|
+
const currentAtlasSize = float(tilesPerRow * currentEdgeVertexCount);
|
|
492
|
+
return vec2(
|
|
493
|
+
atlasX.toFloat().add(0.5).div(currentAtlasSize),
|
|
494
|
+
atlasY.toFloat().add(0.5).div(currentAtlasSize)
|
|
495
|
+
);
|
|
496
|
+
},
|
|
497
|
+
texel(ix, iy, tileIndex) {
|
|
498
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
499
|
+
tilesPerRow,
|
|
500
|
+
currentEdgeVertexCount,
|
|
501
|
+
ix,
|
|
502
|
+
iy,
|
|
503
|
+
tileIndex
|
|
504
|
+
);
|
|
505
|
+
return ivec2(atlasX, atlasY);
|
|
506
|
+
},
|
|
507
|
+
sample(u, v, tileIndex) {
|
|
508
|
+
const tile = int(tileIndex);
|
|
509
|
+
const tilesPerRowNode = int(tilesPerRow);
|
|
510
|
+
const col = tile.mod(tilesPerRowNode);
|
|
511
|
+
const row = tile.div(tilesPerRowNode);
|
|
512
|
+
const invTilesPerRow = float(1 / tilesPerRow);
|
|
513
|
+
const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
|
|
514
|
+
const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
|
|
515
|
+
return texture(tex, vec2(atlasU, atlasV));
|
|
516
|
+
},
|
|
517
|
+
resize(width, height, nextTileCount) {
|
|
518
|
+
currentEdgeVertexCount = width;
|
|
519
|
+
currentTileCount = nextTileCount;
|
|
520
|
+
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
521
|
+
const nextAtlasSize = tilesPerRow * width;
|
|
522
|
+
const image = tex.image;
|
|
523
|
+
image.width = nextAtlasSize;
|
|
524
|
+
image.height = nextAtlasSize;
|
|
525
|
+
tex.needsUpdate = true;
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function Texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
530
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
531
|
+
let currentTileCount = tileCount;
|
|
532
|
+
const tex = new StorageArrayTexture(
|
|
533
|
+
edgeVertexCount,
|
|
534
|
+
edgeVertexCount,
|
|
535
|
+
tileCount
|
|
536
|
+
);
|
|
537
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
538
|
+
return {
|
|
539
|
+
backendType: "texture-3d",
|
|
540
|
+
get edgeVertexCount() {
|
|
541
|
+
return currentEdgeVertexCount;
|
|
542
|
+
},
|
|
543
|
+
get tileCount() {
|
|
544
|
+
return currentTileCount;
|
|
545
|
+
},
|
|
546
|
+
texture: tex,
|
|
547
|
+
uv(ix, iy, _tileIndex) {
|
|
548
|
+
return vec2(ix.toFloat(), iy.toFloat());
|
|
549
|
+
},
|
|
550
|
+
texel(ix, iy, tileIndex) {
|
|
551
|
+
return ivec3(ix, iy, tileIndex);
|
|
552
|
+
},
|
|
553
|
+
sample(u, v, tileIndex) {
|
|
554
|
+
return texture(tex, vec2(u, v)).depth(int(tileIndex));
|
|
555
|
+
},
|
|
556
|
+
resize(width, height, nextTileCount) {
|
|
557
|
+
currentEdgeVertexCount = width;
|
|
558
|
+
currentTileCount = nextTileCount;
|
|
559
|
+
tex.setSize(width, height, nextTileCount);
|
|
560
|
+
tex.needsUpdate = true;
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function tryGetDeviceLimits(renderer) {
|
|
565
|
+
const backend = renderer;
|
|
566
|
+
return backend.backend?.device?.limits ?? {};
|
|
567
|
+
}
|
|
568
|
+
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
569
|
+
const filter = options.filter ?? "linear";
|
|
570
|
+
const format = options.format ?? "rgba16float";
|
|
571
|
+
const forcedBackend = options.backend;
|
|
572
|
+
if (forcedBackend === "atlas") {
|
|
573
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
574
|
+
}
|
|
575
|
+
if (forcedBackend === "texture-3d") {
|
|
576
|
+
return Texture3DBackend(edgeVertexCount, tileCount, { filter, format });
|
|
577
|
+
}
|
|
578
|
+
if (forcedBackend === "array-texture") {
|
|
579
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
580
|
+
}
|
|
581
|
+
const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
|
|
582
|
+
const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
|
|
583
|
+
if (tileCount > maxLayers) {
|
|
584
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
585
|
+
}
|
|
586
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
587
|
+
}
|
|
588
|
+
function storeTerrainField(storage, ix, iy, tileIndex, value) {
|
|
589
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
590
|
+
return textureStore(
|
|
591
|
+
storage.texture,
|
|
592
|
+
uvec3(int(ix), int(iy), int(tileIndex)),
|
|
593
|
+
value
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
return textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
|
|
597
|
+
}
|
|
598
|
+
function loadTerrainField(storage, ix, iy, tileIndex) {
|
|
599
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
600
|
+
return textureLoad(storage.texture, ivec2(int(ix), int(iy)), int(0)).depth(
|
|
601
|
+
int(tileIndex)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
return textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), int(0));
|
|
605
|
+
}
|
|
606
|
+
function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
607
|
+
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
608
|
+
}
|
|
609
|
+
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
610
|
+
const raw = loadTerrainField(storage, ix, iy, tileIndex);
|
|
611
|
+
return vec2(raw.g, raw.b);
|
|
612
|
+
}
|
|
613
|
+
function sampleTerrainField(storage, u, v, tileIndex) {
|
|
614
|
+
return storage.sample(u, v, tileIndex);
|
|
615
|
+
}
|
|
616
|
+
function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
|
|
617
|
+
return sampleTerrainField(storage, u, v, tileIndex).r;
|
|
618
|
+
}
|
|
619
|
+
function sampleTerrainFieldNormal(storage, u, v, tileIndex) {
|
|
620
|
+
const raw = sampleTerrainField(storage, u, v, tileIndex);
|
|
621
|
+
return vec2(raw.g, raw.b);
|
|
622
|
+
}
|
|
623
|
+
function packTerrainFieldSample(height, normalXZ, extra = float(0)) {
|
|
624
|
+
return vec4(height, normalXZ.x, normalXZ.y, extra);
|
|
625
|
+
}
|
|
626
|
+
|
|
313
627
|
const createElevation = (tile, uniforms, elevationFn) => {
|
|
314
628
|
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
315
629
|
const ix = int(localCoordinates.x);
|
|
@@ -331,7 +645,7 @@ const createElevation = (tile, uniforms, elevationFn) => {
|
|
|
331
645
|
});
|
|
332
646
|
};
|
|
333
647
|
};
|
|
334
|
-
const readElevationFieldAtPositionLocal = (
|
|
648
|
+
const readElevationFieldAtPositionLocal = (terrainFieldStorage, edgeVertexCount, positionLocal) => Fn(() => {
|
|
335
649
|
const nodeIndex = int(instanceIndex);
|
|
336
650
|
const intEdge = int(edgeVertexCount);
|
|
337
651
|
const innerSegments = int(edgeVertexCount).sub(3);
|
|
@@ -343,10 +657,12 @@ const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount
|
|
|
343
657
|
const y = v.mul(fInnerSegments).round().toInt().add(int(1));
|
|
344
658
|
const xClamped = min(max(x, int(0)), last);
|
|
345
659
|
const yClamped = min(max(y, int(0)), last);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
660
|
+
return loadTerrainFieldElevation(
|
|
661
|
+
terrainFieldStorage,
|
|
662
|
+
xClamped,
|
|
663
|
+
yClamped,
|
|
664
|
+
nodeIndex
|
|
665
|
+
);
|
|
350
666
|
});
|
|
351
667
|
|
|
352
668
|
function createTileCompute(leafStorage, uniforms) {
|
|
@@ -432,6 +748,7 @@ const quadtreeUpdate = param({
|
|
|
432
748
|
distanceFactor: 1.5
|
|
433
749
|
}).displayName("quadtreeUpdate");
|
|
434
750
|
const surface = param(null).displayName("surface");
|
|
751
|
+
const terrainFieldFilter = param("linear").displayName("terrainFieldFilter");
|
|
435
752
|
const elevationFn = param(() => float(0));
|
|
436
753
|
|
|
437
754
|
function createLeafStorage(maxNodes) {
|
|
@@ -1239,21 +1556,21 @@ const elevationFieldStageTask = task((get, work) => {
|
|
|
1239
1556
|
});
|
|
1240
1557
|
}).displayName("elevationFieldStageTask");
|
|
1241
1558
|
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1559
|
+
const createTerrainFieldTextureTask = task(
|
|
1560
|
+
(get, work, { resources }) => {
|
|
1561
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1562
|
+
const maxNodesValue = get(maxNodes);
|
|
1563
|
+
const filter = get(terrainFieldFilter);
|
|
1564
|
+
return work(
|
|
1565
|
+
() => createTerrainFieldStorage(
|
|
1566
|
+
edgeVertexCount,
|
|
1567
|
+
maxNodesValue,
|
|
1568
|
+
resources?.renderer,
|
|
1569
|
+
{ filter }
|
|
1570
|
+
)
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
).displayName("createTerrainFieldTextureTask");
|
|
1257
1574
|
function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1258
1575
|
return Fn(
|
|
1259
1576
|
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
@@ -1278,10 +1595,10 @@ function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
|
1278
1595
|
}
|
|
1279
1596
|
);
|
|
1280
1597
|
}
|
|
1281
|
-
const
|
|
1598
|
+
const terrainFieldStageTask = task((get, work) => {
|
|
1282
1599
|
const upstream = get(elevationFieldStageTask);
|
|
1283
1600
|
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1284
|
-
const
|
|
1601
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1285
1602
|
const tileEdgeVertexCount = get(innerTileSegments) + 3;
|
|
1286
1603
|
const tile = get(tileNodesTask);
|
|
1287
1604
|
const uniforms = get(createUniformsTask);
|
|
@@ -1296,6 +1613,7 @@ const normalFieldStageTask = task((get, work) => {
|
|
|
1296
1613
|
const ix = int(localCoordinates.x);
|
|
1297
1614
|
const iy = int(localCoordinates.y);
|
|
1298
1615
|
const tileSize = tile.tileSize(nodeIndex);
|
|
1616
|
+
const height = elevationFieldContext.node.element(globalVertexIndex);
|
|
1299
1617
|
const normalXZ = computeNormal(
|
|
1300
1618
|
nodeIndex,
|
|
1301
1619
|
tileSize,
|
|
@@ -1303,58 +1621,60 @@ const normalFieldStageTask = task((get, work) => {
|
|
|
1303
1621
|
iy,
|
|
1304
1622
|
uniforms.uElevationScale
|
|
1305
1623
|
);
|
|
1306
|
-
|
|
1624
|
+
storeTerrainField(
|
|
1625
|
+
terrainFieldStorage,
|
|
1626
|
+
ix,
|
|
1627
|
+
iy,
|
|
1628
|
+
nodeIndex,
|
|
1629
|
+
packTerrainFieldSample(height, normalXZ)
|
|
1630
|
+
);
|
|
1307
1631
|
}
|
|
1308
1632
|
];
|
|
1309
1633
|
});
|
|
1310
|
-
}).displayName("
|
|
1634
|
+
}).displayName("terrainFieldStageTask");
|
|
1311
1635
|
|
|
1312
1636
|
const compileComputeTask = task((get, work) => {
|
|
1313
|
-
const pipeline = get(
|
|
1637
|
+
const pipeline = get(terrainFieldStageTask);
|
|
1314
1638
|
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
1639
|
return work(
|
|
1321
|
-
() =>
|
|
1322
|
-
|
|
1640
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1641
|
+
preferSingleKernelWhenPossible: false
|
|
1642
|
+
})
|
|
1323
1643
|
);
|
|
1324
|
-
}).displayName("
|
|
1644
|
+
}).displayName("compileComputeTask");
|
|
1645
|
+
const executeComputeTask = task(
|
|
1646
|
+
(get, work, { resources }) => {
|
|
1647
|
+
const { execute } = get(compileComputeTask);
|
|
1648
|
+
const leafState = get(leafGpuBufferTask);
|
|
1649
|
+
return work(
|
|
1650
|
+
() => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
|
|
1651
|
+
}
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
).displayName("executeComputeTask").lane("gpu");
|
|
1325
1655
|
function createComputePipelineTasks(leafStageTask) {
|
|
1326
1656
|
const compile = task((get, work) => {
|
|
1327
1657
|
const pipeline = get(leafStageTask);
|
|
1328
1658
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1329
|
-
return work(
|
|
1659
|
+
return work(
|
|
1660
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1661
|
+
preferSingleKernelWhenPossible: false
|
|
1662
|
+
})
|
|
1663
|
+
);
|
|
1330
1664
|
}).displayName("compileComputeTask");
|
|
1331
|
-
const execute = task(
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1665
|
+
const execute = task(
|
|
1666
|
+
(get, work, { resources }) => {
|
|
1667
|
+
const { execute: run } = get(compile);
|
|
1668
|
+
const leafState = get(leafGpuBufferTask);
|
|
1669
|
+
return work(
|
|
1670
|
+
() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
1671
|
+
}
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
).displayName("executeComputeTask").lane("gpu");
|
|
1337
1675
|
return { compile, execute };
|
|
1338
1676
|
}
|
|
1339
1677
|
|
|
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
1678
|
const isSkirtVertex = Fn(([segments]) => {
|
|
1359
1679
|
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
1360
1680
|
const vIndex = int(vertexIndex);
|
|
@@ -1396,37 +1716,40 @@ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
|
1396
1716
|
return vec3(worldX, rootOrigin.y, worldZ);
|
|
1397
1717
|
});
|
|
1398
1718
|
}
|
|
1399
|
-
function createTileElevation(terrainUniforms,
|
|
1400
|
-
if (!
|
|
1719
|
+
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1720
|
+
if (!terrainFieldStorage) return float(0);
|
|
1401
1721
|
const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
|
|
1402
1722
|
return readElevationFieldAtPositionLocal(
|
|
1403
|
-
|
|
1723
|
+
terrainFieldStorage,
|
|
1404
1724
|
edgeVertexCount,
|
|
1405
1725
|
positionLocal
|
|
1406
1726
|
)().mul(
|
|
1407
1727
|
terrainUniforms.uElevationScale
|
|
1408
1728
|
);
|
|
1409
1729
|
}
|
|
1410
|
-
function createNormalAssignment(terrainUniforms,
|
|
1411
|
-
if (!
|
|
1730
|
+
function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
|
|
1731
|
+
if (!terrainFieldStorage) return;
|
|
1412
1732
|
const nodeIndex = int(instanceIndex);
|
|
1413
|
-
const
|
|
1414
|
-
const
|
|
1415
|
-
const
|
|
1416
|
-
const
|
|
1417
|
-
const normalXZ =
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1733
|
+
const edgeVertexCount = int(terrainUniforms.uInnerTileSegments.add(3));
|
|
1734
|
+
const localVertexIndex = int(vertexIndex);
|
|
1735
|
+
const ix = localVertexIndex.mod(edgeVertexCount);
|
|
1736
|
+
const iy = localVertexIndex.div(edgeVertexCount);
|
|
1737
|
+
const normalXZ = loadTerrainFieldNormal(terrainFieldStorage, ix, iy, nodeIndex);
|
|
1738
|
+
const nx = normalXZ.x;
|
|
1739
|
+
const nz = normalXZ.y;
|
|
1740
|
+
const nySq = float(1).sub(nx.mul(nx)).sub(nz.mul(nz)).max(float(0));
|
|
1741
|
+
const ny = nySq.sqrt();
|
|
1742
|
+
normalLocal.assign(vec3(nx, ny, nz));
|
|
1420
1743
|
}
|
|
1421
|
-
function createTileWorldPosition(leafStorage, terrainUniforms,
|
|
1744
|
+
function createTileWorldPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1422
1745
|
const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
|
|
1423
1746
|
return Fn(() => {
|
|
1424
1747
|
const base = baseWorldPosition();
|
|
1425
|
-
const yElevation = createTileElevation(terrainUniforms,
|
|
1748
|
+
const yElevation = createTileElevation(terrainUniforms, terrainFieldStorage);
|
|
1426
1749
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1427
1750
|
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1428
1751
|
const worldY = select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
1429
|
-
createNormalAssignment(terrainUniforms,
|
|
1752
|
+
createNormalAssignment(terrainUniforms, terrainFieldStorage);
|
|
1430
1753
|
return vec3(base.x, worldY, base.z);
|
|
1431
1754
|
})();
|
|
1432
1755
|
}
|
|
@@ -1434,20 +1757,18 @@ function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBuf
|
|
|
1434
1757
|
const positionNodeTask = task((get, work) => {
|
|
1435
1758
|
const leafStorage = get(leafStorageTask);
|
|
1436
1759
|
const terrainUniforms = get(createUniformsTask);
|
|
1437
|
-
const
|
|
1438
|
-
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1760
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1439
1761
|
return work(
|
|
1440
1762
|
() => createTileWorldPosition(
|
|
1441
1763
|
leafStorage,
|
|
1442
1764
|
terrainUniforms,
|
|
1443
|
-
|
|
1444
|
-
normalFieldContext.node
|
|
1765
|
+
terrainFieldStorage
|
|
1445
1766
|
)
|
|
1446
1767
|
);
|
|
1447
1768
|
}).displayName("positionNodeTask");
|
|
1448
1769
|
|
|
1449
1770
|
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(
|
|
1771
|
+
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
1772
|
}
|
|
1452
1773
|
const terrainTasks = {
|
|
1453
1774
|
instanceId: instanceIdTask,
|
|
@@ -1461,13 +1782,31 @@ const terrainTasks = {
|
|
|
1461
1782
|
positionNode: positionNodeTask,
|
|
1462
1783
|
createElevationFieldContext: createElevationFieldContextTask,
|
|
1463
1784
|
createTileNodes: tileNodesTask,
|
|
1464
|
-
|
|
1785
|
+
createTerrainFieldTexture: createTerrainFieldTextureTask,
|
|
1465
1786
|
elevationFieldStage: elevationFieldStageTask,
|
|
1466
|
-
|
|
1787
|
+
terrainFieldStage: terrainFieldStageTask,
|
|
1467
1788
|
compileCompute: compileComputeTask,
|
|
1468
1789
|
executeCompute: executeComputeTask
|
|
1469
1790
|
};
|
|
1470
1791
|
|
|
1792
|
+
const textureSpaceToVectorSpace = Fn(([value]) => {
|
|
1793
|
+
return remap(value, float(0), float(1), float(-1), float(1));
|
|
1794
|
+
});
|
|
1795
|
+
const vectorSpaceToTextureSpace = Fn(([value]) => {
|
|
1796
|
+
return remap(value, float(-1), float(1), float(0), float(1));
|
|
1797
|
+
});
|
|
1798
|
+
const blendAngleCorrectedNormals = Fn(([n1, n2]) => {
|
|
1799
|
+
const t = vec3(n1.x, n1.y, n1.z.add(1));
|
|
1800
|
+
const u = vec3(n2.x.negate(), n2.y.negate(), n2.z);
|
|
1801
|
+
const r = t.mul(dot(t, u)).sub(u.mul(t.z)).normalize();
|
|
1802
|
+
return r;
|
|
1803
|
+
});
|
|
1804
|
+
const deriveNormalZ = Fn(([normalXY]) => {
|
|
1805
|
+
const xy = normalXY.toVar();
|
|
1806
|
+
const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
|
|
1807
|
+
return vec3(xy.x, xy.y, z);
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1471
1810
|
const vGlobalVertexIndex = /* @__PURE__ */ varyingProperty("int", "vGlobalVertexIndex");
|
|
1472
1811
|
const vElevation = /* @__PURE__ */ varyingProperty("f32", "vElevation");
|
|
1473
1812
|
|
|
@@ -1502,4 +1841,4 @@ const voronoiCells = Fn((params) => {
|
|
|
1502
1841
|
return k;
|
|
1503
1842
|
});
|
|
1504
1843
|
|
|
1505
|
-
export { Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface,
|
|
1844
|
+
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, sampleTerrainField, sampleTerrainFieldElevation, sampleTerrainFieldNormal, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
|