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