@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.cjs
CHANGED
|
@@ -276,42 +276,356 @@ 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(texture2, format, filter) {
|
|
415
|
+
texture2.format = three.RGBAFormat;
|
|
416
|
+
texture2.type = resolveType(format);
|
|
417
|
+
texture2.magFilter = resolveFilter(filter);
|
|
418
|
+
texture2.minFilter = resolveFilter(filter);
|
|
419
|
+
texture2.wrapS = three.ClampToEdgeWrapping;
|
|
420
|
+
texture2.wrapT = three.ClampToEdgeWrapping;
|
|
421
|
+
texture2.generateMipmaps = false;
|
|
422
|
+
texture2.needsUpdate = true;
|
|
423
|
+
}
|
|
424
|
+
function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
|
|
425
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
426
|
+
let currentTileCount = tileCount;
|
|
427
|
+
const tex = new webgpu.StorageArrayTexture(
|
|
428
|
+
edgeVertexCount,
|
|
429
|
+
edgeVertexCount,
|
|
430
|
+
tileCount
|
|
431
|
+
);
|
|
432
|
+
configureStorageTexture(tex, 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: tex,
|
|
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
|
+
sample(u, v, tileIndex) {
|
|
449
|
+
return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
|
|
450
|
+
},
|
|
451
|
+
resize(width, height, nextTileCount) {
|
|
452
|
+
currentEdgeVertexCount = width;
|
|
453
|
+
currentTileCount = nextTileCount;
|
|
454
|
+
tex.setSize(width, height, nextTileCount);
|
|
455
|
+
tex.needsUpdate = true;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
|
|
460
|
+
const tilesPerRowNode = tsl.int(tilesPerRow);
|
|
461
|
+
const edge = tsl.int(edgeVertexCount);
|
|
462
|
+
const tile = tsl.int(tileIndex);
|
|
463
|
+
const col = tile.mod(tilesPerRowNode);
|
|
464
|
+
const row = tile.div(tilesPerRowNode);
|
|
465
|
+
const atlasX = col.mul(edge).add(tsl.int(ix));
|
|
466
|
+
const atlasY = row.mul(edge).add(tsl.int(iy));
|
|
467
|
+
return { atlasX, atlasY };
|
|
468
|
+
}
|
|
469
|
+
function AtlasBackend(edgeVertexCount, tileCount, options) {
|
|
470
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
471
|
+
let currentTileCount = tileCount;
|
|
472
|
+
let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
|
|
473
|
+
const atlasSize = tilesPerRow * edgeVertexCount;
|
|
474
|
+
const tex = new webgpu.StorageTexture(atlasSize, atlasSize);
|
|
475
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
476
|
+
return {
|
|
477
|
+
backendType: "atlas",
|
|
478
|
+
get edgeVertexCount() {
|
|
479
|
+
return currentEdgeVertexCount;
|
|
480
|
+
},
|
|
481
|
+
get tileCount() {
|
|
482
|
+
return currentTileCount;
|
|
483
|
+
},
|
|
484
|
+
texture: tex,
|
|
485
|
+
uv(ix, iy, tileIndex) {
|
|
486
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
487
|
+
tilesPerRow,
|
|
488
|
+
currentEdgeVertexCount,
|
|
489
|
+
ix,
|
|
490
|
+
iy,
|
|
491
|
+
tileIndex
|
|
492
|
+
);
|
|
493
|
+
const currentAtlasSize = tsl.float(tilesPerRow * currentEdgeVertexCount);
|
|
494
|
+
return tsl.vec2(
|
|
495
|
+
atlasX.toFloat().add(0.5).div(currentAtlasSize),
|
|
496
|
+
atlasY.toFloat().add(0.5).div(currentAtlasSize)
|
|
497
|
+
);
|
|
498
|
+
},
|
|
499
|
+
texel(ix, iy, tileIndex) {
|
|
500
|
+
const { atlasX, atlasY } = atlasCoord(
|
|
501
|
+
tilesPerRow,
|
|
502
|
+
currentEdgeVertexCount,
|
|
503
|
+
ix,
|
|
504
|
+
iy,
|
|
505
|
+
tileIndex
|
|
506
|
+
);
|
|
507
|
+
return tsl.ivec2(atlasX, atlasY);
|
|
508
|
+
},
|
|
509
|
+
sample(u, v, tileIndex) {
|
|
510
|
+
const tile = tsl.int(tileIndex);
|
|
511
|
+
const tilesPerRowNode = tsl.int(tilesPerRow);
|
|
512
|
+
const col = tile.mod(tilesPerRowNode);
|
|
513
|
+
const row = tile.div(tilesPerRowNode);
|
|
514
|
+
const invTilesPerRow = tsl.float(1 / tilesPerRow);
|
|
515
|
+
const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
|
|
516
|
+
const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
|
|
517
|
+
return tsl.texture(tex, tsl.vec2(atlasU, atlasV));
|
|
518
|
+
},
|
|
519
|
+
resize(width, height, nextTileCount) {
|
|
520
|
+
currentEdgeVertexCount = width;
|
|
521
|
+
currentTileCount = nextTileCount;
|
|
522
|
+
tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
|
|
523
|
+
const nextAtlasSize = tilesPerRow * width;
|
|
524
|
+
const image = tex.image;
|
|
525
|
+
image.width = nextAtlasSize;
|
|
526
|
+
image.height = nextAtlasSize;
|
|
527
|
+
tex.needsUpdate = true;
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function Texture3DBackend(edgeVertexCount, tileCount, options) {
|
|
532
|
+
let currentEdgeVertexCount = edgeVertexCount;
|
|
533
|
+
let currentTileCount = tileCount;
|
|
534
|
+
const tex = new webgpu.StorageArrayTexture(
|
|
535
|
+
edgeVertexCount,
|
|
536
|
+
edgeVertexCount,
|
|
537
|
+
tileCount
|
|
538
|
+
);
|
|
539
|
+
configureStorageTexture(tex, options.format, options.filter);
|
|
540
|
+
return {
|
|
541
|
+
backendType: "texture-3d",
|
|
542
|
+
get edgeVertexCount() {
|
|
543
|
+
return currentEdgeVertexCount;
|
|
544
|
+
},
|
|
545
|
+
get tileCount() {
|
|
546
|
+
return currentTileCount;
|
|
547
|
+
},
|
|
548
|
+
texture: tex,
|
|
549
|
+
uv(ix, iy, _tileIndex) {
|
|
550
|
+
return tsl.vec2(ix.toFloat(), iy.toFloat());
|
|
551
|
+
},
|
|
552
|
+
texel(ix, iy, tileIndex) {
|
|
553
|
+
return tsl.ivec3(ix, iy, tileIndex);
|
|
554
|
+
},
|
|
555
|
+
sample(u, v, tileIndex) {
|
|
556
|
+
return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
|
|
557
|
+
},
|
|
558
|
+
resize(width, height, nextTileCount) {
|
|
559
|
+
currentEdgeVertexCount = width;
|
|
560
|
+
currentTileCount = nextTileCount;
|
|
561
|
+
tex.setSize(width, height, nextTileCount);
|
|
562
|
+
tex.needsUpdate = true;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function tryGetDeviceLimits(renderer) {
|
|
567
|
+
const backend = renderer;
|
|
568
|
+
return backend.backend?.device?.limits ?? {};
|
|
569
|
+
}
|
|
570
|
+
function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
|
|
571
|
+
const filter = options.filter ?? "linear";
|
|
572
|
+
const format = options.format ?? "rgba16float";
|
|
573
|
+
const forcedBackend = options.backend;
|
|
574
|
+
if (forcedBackend === "atlas") {
|
|
575
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
576
|
+
}
|
|
577
|
+
if (forcedBackend === "texture-3d") {
|
|
578
|
+
return Texture3DBackend(edgeVertexCount, tileCount, { filter, format });
|
|
579
|
+
}
|
|
580
|
+
if (forcedBackend === "array-texture") {
|
|
581
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
582
|
+
}
|
|
583
|
+
const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
|
|
584
|
+
const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
|
|
585
|
+
if (tileCount > maxLayers) {
|
|
586
|
+
return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
|
|
587
|
+
}
|
|
588
|
+
return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
|
|
589
|
+
}
|
|
590
|
+
function storeTerrainField(storage, ix, iy, tileIndex, value) {
|
|
591
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
592
|
+
return tsl.textureStore(
|
|
593
|
+
storage.texture,
|
|
594
|
+
tsl.uvec3(tsl.int(ix), tsl.int(iy), tsl.int(tileIndex)),
|
|
595
|
+
value
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
return tsl.textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
|
|
599
|
+
}
|
|
600
|
+
function loadTerrainField(storage, ix, iy, tileIndex) {
|
|
601
|
+
if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
|
|
602
|
+
return tsl.textureLoad(storage.texture, tsl.ivec2(tsl.int(ix), tsl.int(iy)), tsl.int(0)).depth(
|
|
603
|
+
tsl.int(tileIndex)
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
return tsl.textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), tsl.int(0));
|
|
607
|
+
}
|
|
608
|
+
function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
|
|
609
|
+
return loadTerrainField(storage, ix, iy, tileIndex).r;
|
|
610
|
+
}
|
|
611
|
+
function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
|
|
612
|
+
const raw = loadTerrainField(storage, ix, iy, tileIndex);
|
|
613
|
+
return tsl.vec2(raw.g, raw.b);
|
|
614
|
+
}
|
|
615
|
+
function sampleTerrainField(storage, u, v, tileIndex) {
|
|
616
|
+
return storage.sample(u, v, tileIndex);
|
|
617
|
+
}
|
|
618
|
+
function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
|
|
619
|
+
return sampleTerrainField(storage, u, v, tileIndex).r;
|
|
620
|
+
}
|
|
621
|
+
function sampleTerrainFieldNormal(storage, u, v, tileIndex) {
|
|
622
|
+
const raw = sampleTerrainField(storage, u, v, tileIndex);
|
|
623
|
+
return tsl.vec2(raw.g, raw.b);
|
|
624
|
+
}
|
|
625
|
+
function packTerrainFieldSample(height, normalXZ, extra = tsl.float(0)) {
|
|
626
|
+
return tsl.vec4(height, normalXZ.x, normalXZ.y, extra);
|
|
627
|
+
}
|
|
628
|
+
|
|
315
629
|
const createElevation = (tile, uniforms, elevationFn) => {
|
|
316
630
|
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
317
631
|
const ix = tsl.int(localCoordinates.x);
|
|
@@ -333,7 +647,7 @@ const createElevation = (tile, uniforms, elevationFn) => {
|
|
|
333
647
|
});
|
|
334
648
|
};
|
|
335
649
|
};
|
|
336
|
-
const readElevationFieldAtPositionLocal = (
|
|
650
|
+
const readElevationFieldAtPositionLocal = (terrainFieldStorage, edgeVertexCount, positionLocal) => tsl.Fn(() => {
|
|
337
651
|
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
338
652
|
const intEdge = tsl.int(edgeVertexCount);
|
|
339
653
|
const innerSegments = tsl.int(edgeVertexCount).sub(3);
|
|
@@ -345,10 +659,12 @@ const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount
|
|
|
345
659
|
const y = v.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
346
660
|
const xClamped = tsl.min(tsl.max(x, tsl.int(0)), last);
|
|
347
661
|
const yClamped = tsl.min(tsl.max(y, tsl.int(0)), last);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
662
|
+
return loadTerrainFieldElevation(
|
|
663
|
+
terrainFieldStorage,
|
|
664
|
+
xClamped,
|
|
665
|
+
yClamped,
|
|
666
|
+
nodeIndex
|
|
667
|
+
);
|
|
352
668
|
});
|
|
353
669
|
|
|
354
670
|
function createTileCompute(leafStorage, uniforms) {
|
|
@@ -434,6 +750,7 @@ const quadtreeUpdate = work.param({
|
|
|
434
750
|
distanceFactor: 1.5
|
|
435
751
|
}).displayName("quadtreeUpdate");
|
|
436
752
|
const surface = work.param(null).displayName("surface");
|
|
753
|
+
const terrainFieldFilter = work.param("linear").displayName("terrainFieldFilter");
|
|
437
754
|
const elevationFn = work.param(() => tsl.float(0));
|
|
438
755
|
|
|
439
756
|
function createLeafStorage(maxNodes) {
|
|
@@ -1241,21 +1558,21 @@ const elevationFieldStageTask = work.task((get, work) => {
|
|
|
1241
1558
|
});
|
|
1242
1559
|
}).displayName("elevationFieldStageTask");
|
|
1243
1560
|
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1561
|
+
const createTerrainFieldTextureTask = work.task(
|
|
1562
|
+
(get, work, { resources }) => {
|
|
1563
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1564
|
+
const maxNodesValue = get(maxNodes);
|
|
1565
|
+
const filter = get(terrainFieldFilter);
|
|
1566
|
+
return work(
|
|
1567
|
+
() => createTerrainFieldStorage(
|
|
1568
|
+
edgeVertexCount,
|
|
1569
|
+
maxNodesValue,
|
|
1570
|
+
resources?.renderer,
|
|
1571
|
+
{ filter }
|
|
1572
|
+
)
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
).displayName("createTerrainFieldTextureTask");
|
|
1259
1576
|
function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1260
1577
|
return tsl.Fn(
|
|
1261
1578
|
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
@@ -1280,10 +1597,10 @@ function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
|
1280
1597
|
}
|
|
1281
1598
|
);
|
|
1282
1599
|
}
|
|
1283
|
-
const
|
|
1600
|
+
const terrainFieldStageTask = work.task((get, work) => {
|
|
1284
1601
|
const upstream = get(elevationFieldStageTask);
|
|
1285
1602
|
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1286
|
-
const
|
|
1603
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1287
1604
|
const tileEdgeVertexCount = get(innerTileSegments) + 3;
|
|
1288
1605
|
const tile = get(tileNodesTask);
|
|
1289
1606
|
const uniforms = get(createUniformsTask);
|
|
@@ -1298,6 +1615,7 @@ const normalFieldStageTask = work.task((get, work) => {
|
|
|
1298
1615
|
const ix = tsl.int(localCoordinates.x);
|
|
1299
1616
|
const iy = tsl.int(localCoordinates.y);
|
|
1300
1617
|
const tileSize = tile.tileSize(nodeIndex);
|
|
1618
|
+
const height = elevationFieldContext.node.element(globalVertexIndex);
|
|
1301
1619
|
const normalXZ = computeNormal(
|
|
1302
1620
|
nodeIndex,
|
|
1303
1621
|
tileSize,
|
|
@@ -1305,58 +1623,60 @@ const normalFieldStageTask = work.task((get, work) => {
|
|
|
1305
1623
|
iy,
|
|
1306
1624
|
uniforms.uElevationScale
|
|
1307
1625
|
);
|
|
1308
|
-
|
|
1626
|
+
storeTerrainField(
|
|
1627
|
+
terrainFieldStorage,
|
|
1628
|
+
ix,
|
|
1629
|
+
iy,
|
|
1630
|
+
nodeIndex,
|
|
1631
|
+
packTerrainFieldSample(height, normalXZ)
|
|
1632
|
+
);
|
|
1309
1633
|
}
|
|
1310
1634
|
];
|
|
1311
1635
|
});
|
|
1312
|
-
}).displayName("
|
|
1636
|
+
}).displayName("terrainFieldStageTask");
|
|
1313
1637
|
|
|
1314
1638
|
const compileComputeTask = work.task((get, work) => {
|
|
1315
|
-
const pipeline = get(
|
|
1639
|
+
const pipeline = get(terrainFieldStageTask);
|
|
1316
1640
|
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
1641
|
return work(
|
|
1323
|
-
() =>
|
|
1324
|
-
|
|
1642
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1643
|
+
preferSingleKernelWhenPossible: false
|
|
1644
|
+
})
|
|
1325
1645
|
);
|
|
1326
|
-
}).displayName("
|
|
1646
|
+
}).displayName("compileComputeTask");
|
|
1647
|
+
const executeComputeTask = work.task(
|
|
1648
|
+
(get, work, { resources }) => {
|
|
1649
|
+
const { execute } = get(compileComputeTask);
|
|
1650
|
+
const leafState = get(leafGpuBufferTask);
|
|
1651
|
+
return work(
|
|
1652
|
+
() => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
|
|
1653
|
+
}
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
).displayName("executeComputeTask").lane("gpu");
|
|
1327
1657
|
function createComputePipelineTasks(leafStageTask) {
|
|
1328
1658
|
const compile = work.task((get, work) => {
|
|
1329
1659
|
const pipeline = get(leafStageTask);
|
|
1330
1660
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1331
|
-
return work(
|
|
1661
|
+
return work(
|
|
1662
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1663
|
+
preferSingleKernelWhenPossible: false
|
|
1664
|
+
})
|
|
1665
|
+
);
|
|
1332
1666
|
}).displayName("compileComputeTask");
|
|
1333
|
-
const execute = work.task(
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1667
|
+
const execute = work.task(
|
|
1668
|
+
(get, work, { resources }) => {
|
|
1669
|
+
const { execute: run } = get(compile);
|
|
1670
|
+
const leafState = get(leafGpuBufferTask);
|
|
1671
|
+
return work(
|
|
1672
|
+
() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
1673
|
+
}
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
).displayName("executeComputeTask").lane("gpu");
|
|
1339
1677
|
return { compile, execute };
|
|
1340
1678
|
}
|
|
1341
1679
|
|
|
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
1680
|
const isSkirtVertex = tsl.Fn(([segments]) => {
|
|
1361
1681
|
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1362
1682
|
const vIndex = tsl.int(tsl.vertexIndex);
|
|
@@ -1398,37 +1718,40 @@ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
|
1398
1718
|
return tsl.vec3(worldX, rootOrigin.y, worldZ);
|
|
1399
1719
|
});
|
|
1400
1720
|
}
|
|
1401
|
-
function createTileElevation(terrainUniforms,
|
|
1402
|
-
if (!
|
|
1721
|
+
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1722
|
+
if (!terrainFieldStorage) return tsl.float(0);
|
|
1403
1723
|
const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
|
|
1404
1724
|
return readElevationFieldAtPositionLocal(
|
|
1405
|
-
|
|
1725
|
+
terrainFieldStorage,
|
|
1406
1726
|
edgeVertexCount,
|
|
1407
1727
|
tsl.positionLocal
|
|
1408
1728
|
)().mul(
|
|
1409
1729
|
terrainUniforms.uElevationScale
|
|
1410
1730
|
);
|
|
1411
1731
|
}
|
|
1412
|
-
function createNormalAssignment(terrainUniforms,
|
|
1413
|
-
if (!
|
|
1732
|
+
function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
|
|
1733
|
+
if (!terrainFieldStorage) return;
|
|
1414
1734
|
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
1415
|
-
const
|
|
1416
|
-
const
|
|
1417
|
-
const
|
|
1418
|
-
const
|
|
1419
|
-
const normalXZ =
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1735
|
+
const edgeVertexCount = tsl.int(terrainUniforms.uInnerTileSegments.add(3));
|
|
1736
|
+
const localVertexIndex = tsl.int(tsl.vertexIndex);
|
|
1737
|
+
const ix = localVertexIndex.mod(edgeVertexCount);
|
|
1738
|
+
const iy = localVertexIndex.div(edgeVertexCount);
|
|
1739
|
+
const normalXZ = loadTerrainFieldNormal(terrainFieldStorage, ix, iy, nodeIndex);
|
|
1740
|
+
const nx = normalXZ.x;
|
|
1741
|
+
const nz = normalXZ.y;
|
|
1742
|
+
const nySq = tsl.float(1).sub(nx.mul(nx)).sub(nz.mul(nz)).max(tsl.float(0));
|
|
1743
|
+
const ny = nySq.sqrt();
|
|
1744
|
+
tsl.normalLocal.assign(tsl.vec3(nx, ny, nz));
|
|
1422
1745
|
}
|
|
1423
|
-
function createTileWorldPosition(leafStorage, terrainUniforms,
|
|
1746
|
+
function createTileWorldPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1424
1747
|
const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
|
|
1425
1748
|
return tsl.Fn(() => {
|
|
1426
1749
|
const base = baseWorldPosition();
|
|
1427
|
-
const yElevation = createTileElevation(terrainUniforms,
|
|
1750
|
+
const yElevation = createTileElevation(terrainUniforms, terrainFieldStorage);
|
|
1428
1751
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1429
1752
|
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1430
1753
|
const worldY = tsl.select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
1431
|
-
createNormalAssignment(terrainUniforms,
|
|
1754
|
+
createNormalAssignment(terrainUniforms, terrainFieldStorage);
|
|
1432
1755
|
return tsl.vec3(base.x, worldY, base.z);
|
|
1433
1756
|
})();
|
|
1434
1757
|
}
|
|
@@ -1436,20 +1759,18 @@ function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBuf
|
|
|
1436
1759
|
const positionNodeTask = work.task((get, work) => {
|
|
1437
1760
|
const leafStorage = get(leafStorageTask);
|
|
1438
1761
|
const terrainUniforms = get(createUniformsTask);
|
|
1439
|
-
const
|
|
1440
|
-
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1762
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1441
1763
|
return work(
|
|
1442
1764
|
() => createTileWorldPosition(
|
|
1443
1765
|
leafStorage,
|
|
1444
1766
|
terrainUniforms,
|
|
1445
|
-
|
|
1446
|
-
normalFieldContext.node
|
|
1767
|
+
terrainFieldStorage
|
|
1447
1768
|
)
|
|
1448
1769
|
);
|
|
1449
1770
|
}).displayName("positionNodeTask");
|
|
1450
1771
|
|
|
1451
1772
|
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(
|
|
1773
|
+
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
1774
|
}
|
|
1454
1775
|
const terrainTasks = {
|
|
1455
1776
|
instanceId: instanceIdTask,
|
|
@@ -1463,13 +1784,31 @@ const terrainTasks = {
|
|
|
1463
1784
|
positionNode: positionNodeTask,
|
|
1464
1785
|
createElevationFieldContext: createElevationFieldContextTask,
|
|
1465
1786
|
createTileNodes: tileNodesTask,
|
|
1466
|
-
|
|
1787
|
+
createTerrainFieldTexture: createTerrainFieldTextureTask,
|
|
1467
1788
|
elevationFieldStage: elevationFieldStageTask,
|
|
1468
|
-
|
|
1789
|
+
terrainFieldStage: terrainFieldStageTask,
|
|
1469
1790
|
compileCompute: compileComputeTask,
|
|
1470
1791
|
executeCompute: executeComputeTask
|
|
1471
1792
|
};
|
|
1472
1793
|
|
|
1794
|
+
const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
|
|
1795
|
+
return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
|
|
1796
|
+
});
|
|
1797
|
+
const vectorSpaceToTextureSpace = tsl.Fn(([value]) => {
|
|
1798
|
+
return tsl.remap(value, tsl.float(-1), tsl.float(1), tsl.float(0), tsl.float(1));
|
|
1799
|
+
});
|
|
1800
|
+
const blendAngleCorrectedNormals = tsl.Fn(([n1, n2]) => {
|
|
1801
|
+
const t = tsl.vec3(n1.x, n1.y, n1.z.add(1));
|
|
1802
|
+
const u = tsl.vec3(n2.x.negate(), n2.y.negate(), n2.z);
|
|
1803
|
+
const r = t.mul(tsl.dot(t, u)).sub(u.mul(t.z)).normalize();
|
|
1804
|
+
return r;
|
|
1805
|
+
});
|
|
1806
|
+
const deriveNormalZ = tsl.Fn(([normalXY]) => {
|
|
1807
|
+
const xy = normalXY.toVar();
|
|
1808
|
+
const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
|
|
1809
|
+
return tsl.vec3(xy.x, xy.y, z);
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1473
1812
|
const vGlobalVertexIndex = /* @__PURE__ */ tsl.varyingProperty("int", "vGlobalVertexIndex");
|
|
1474
1813
|
const vElevation = /* @__PURE__ */ tsl.varyingProperty("f32", "vElevation");
|
|
1475
1814
|
|
|
@@ -1504,9 +1843,12 @@ const voronoiCells = tsl.Fn((params) => {
|
|
|
1504
1843
|
return k;
|
|
1505
1844
|
});
|
|
1506
1845
|
|
|
1846
|
+
exports.ArrayTextureBackend = ArrayTextureBackend;
|
|
1847
|
+
exports.AtlasBackend = AtlasBackend;
|
|
1507
1848
|
exports.Dir = Dir;
|
|
1508
1849
|
exports.TerrainGeometry = TerrainGeometry;
|
|
1509
1850
|
exports.TerrainMesh = TerrainMesh;
|
|
1851
|
+
exports.Texture3DBackend = Texture3DBackend;
|
|
1510
1852
|
exports.U32_EMPTY = U32_EMPTY;
|
|
1511
1853
|
exports.allocLeafSet = allocLeafSet;
|
|
1512
1854
|
exports.allocSeamTable = allocSeamTable;
|
|
@@ -1520,9 +1862,10 @@ exports.createCubeSphereSurface = createCubeSphereSurface;
|
|
|
1520
1862
|
exports.createElevationFieldContextTask = createElevationFieldContextTask;
|
|
1521
1863
|
exports.createFlatSurface = createFlatSurface;
|
|
1522
1864
|
exports.createInfiniteFlatSurface = createInfiniteFlatSurface;
|
|
1523
|
-
exports.createNormalFieldContextTask = createNormalFieldContextTask;
|
|
1524
1865
|
exports.createSpatialIndex = createSpatialIndex;
|
|
1525
1866
|
exports.createState = createState;
|
|
1867
|
+
exports.createTerrainFieldStorage = createTerrainFieldStorage;
|
|
1868
|
+
exports.createTerrainFieldTextureTask = createTerrainFieldTextureTask;
|
|
1526
1869
|
exports.createTerrainUniforms = createTerrainUniforms;
|
|
1527
1870
|
exports.createUniformsTask = createUniformsTask;
|
|
1528
1871
|
exports.deriveNormalZ = deriveNormalZ;
|
|
@@ -1530,16 +1873,20 @@ exports.elevationFieldStageTask = elevationFieldStageTask;
|
|
|
1530
1873
|
exports.elevationFn = elevationFn;
|
|
1531
1874
|
exports.elevationScale = elevationScale;
|
|
1532
1875
|
exports.executeComputeTask = executeComputeTask;
|
|
1876
|
+
exports.getDeviceComputeLimits = getDeviceComputeLimits;
|
|
1533
1877
|
exports.innerTileSegments = innerTileSegments;
|
|
1534
1878
|
exports.instanceIdTask = instanceIdTask;
|
|
1535
1879
|
exports.isSkirtUV = isSkirtUV;
|
|
1536
1880
|
exports.isSkirtVertex = isSkirtVertex;
|
|
1537
1881
|
exports.leafGpuBufferTask = leafGpuBufferTask;
|
|
1538
1882
|
exports.leafStorageTask = leafStorageTask;
|
|
1883
|
+
exports.loadTerrainField = loadTerrainField;
|
|
1884
|
+
exports.loadTerrainFieldElevation = loadTerrainFieldElevation;
|
|
1885
|
+
exports.loadTerrainFieldNormal = loadTerrainFieldNormal;
|
|
1539
1886
|
exports.maxLevel = maxLevel;
|
|
1540
1887
|
exports.maxNodes = maxNodes;
|
|
1541
|
-
exports.normalFieldStageTask = normalFieldStageTask;
|
|
1542
1888
|
exports.origin = origin;
|
|
1889
|
+
exports.packTerrainFieldSample = packTerrainFieldSample;
|
|
1543
1890
|
exports.positionNodeTask = positionNodeTask;
|
|
1544
1891
|
exports.quadtreeConfigTask = quadtreeConfigTask;
|
|
1545
1892
|
exports.quadtreeUpdate = quadtreeUpdate;
|
|
@@ -1547,9 +1894,15 @@ exports.quadtreeUpdateTask = quadtreeUpdateTask;
|
|
|
1547
1894
|
exports.resetLeafSet = resetLeafSet;
|
|
1548
1895
|
exports.resetSeamTable = resetSeamTable;
|
|
1549
1896
|
exports.rootSize = rootSize;
|
|
1897
|
+
exports.sampleTerrainField = sampleTerrainField;
|
|
1898
|
+
exports.sampleTerrainFieldElevation = sampleTerrainFieldElevation;
|
|
1899
|
+
exports.sampleTerrainFieldNormal = sampleTerrainFieldNormal;
|
|
1550
1900
|
exports.skirtScale = skirtScale;
|
|
1901
|
+
exports.storeTerrainField = storeTerrainField;
|
|
1551
1902
|
exports.surface = surface;
|
|
1552
1903
|
exports.surfaceTask = surfaceTask;
|
|
1904
|
+
exports.terrainFieldFilter = terrainFieldFilter;
|
|
1905
|
+
exports.terrainFieldStageTask = terrainFieldStageTask;
|
|
1553
1906
|
exports.terrainGraph = terrainGraph;
|
|
1554
1907
|
exports.terrainTasks = terrainTasks;
|
|
1555
1908
|
exports.textureSpaceToVectorSpace = textureSpaceToVectorSpace;
|