@hello-terrain/three 0.0.0-alpha.5 → 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 +449 -119
- package/dist/index.d.cts +69 -39
- package/dist/index.d.mts +69 -39
- package/dist/index.d.ts +69 -39
- package/dist/index.mjs +441 -121
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -10,17 +10,25 @@ class TerrainGeometry extends three.BufferGeometry {
|
|
|
10
10
|
constructor(innerSegments = 14, extendUV = false) {
|
|
11
11
|
super();
|
|
12
12
|
if (innerSegments < 1 || !Number.isFinite(innerSegments) || !Number.isInteger(innerSegments)) {
|
|
13
|
-
throw new Error(
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Invalid innerSegments: ${innerSegments}. Must be a positive integer.`
|
|
15
|
+
);
|
|
14
16
|
}
|
|
15
17
|
try {
|
|
16
18
|
this.setIndex(this.generateIndices(innerSegments));
|
|
17
19
|
this.setAttribute(
|
|
18
20
|
"position",
|
|
19
|
-
new three.BufferAttribute(
|
|
21
|
+
new three.BufferAttribute(
|
|
22
|
+
new Float32Array(this.generatePositions(innerSegments)),
|
|
23
|
+
3
|
|
24
|
+
)
|
|
20
25
|
);
|
|
21
26
|
this.setAttribute(
|
|
22
27
|
"normal",
|
|
23
|
-
new three.BufferAttribute(
|
|
28
|
+
new three.BufferAttribute(
|
|
29
|
+
new Float32Array(this.generateNormals(innerSegments)),
|
|
30
|
+
3
|
|
31
|
+
)
|
|
24
32
|
);
|
|
25
33
|
this.setAttribute(
|
|
26
34
|
"uv",
|
|
@@ -56,17 +64,19 @@ class TerrainGeometry extends three.BufferGeometry {
|
|
|
56
64
|
* | / | \ | / | \ |
|
|
57
65
|
* o---o---o---o---o
|
|
58
66
|
*
|
|
59
|
-
* INNER GRID (
|
|
60
|
-
* o---o---o
|
|
61
|
-
* | \ | \ |
|
|
62
|
-
* o---o---o
|
|
63
|
-
* | \ | \ |
|
|
64
|
-
* o---o---o
|
|
67
|
+
* INNER GRID (alternating diagonals — checkerboard pattern):
|
|
68
|
+
* o---o---o---o---o
|
|
69
|
+
* | \ | / | \ | / |
|
|
70
|
+
* o---o---o---o---o
|
|
71
|
+
* | / | \ | / | \ |
|
|
72
|
+
* o---o---o---o---o
|
|
73
|
+
* | \ | / | \ | / |
|
|
74
|
+
* o---o---o---o---o
|
|
65
75
|
*
|
|
66
76
|
* Where o = vertex
|
|
67
77
|
* Each square cell is split into 2 triangles.
|
|
68
78
|
* - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
|
|
69
|
-
* - Inner cells:
|
|
79
|
+
* - Inner cells: alternating diagonal via (x+y)%2 to reduce interpolation artifacts
|
|
70
80
|
*
|
|
71
81
|
* Vertex layout (for innerSegments = 2):
|
|
72
82
|
*
|
|
@@ -112,7 +122,7 @@ class TerrainGeometry extends three.BufferGeometry {
|
|
|
112
122
|
const topHalf = y < mid;
|
|
113
123
|
useDefaultDiagonal = leftHalf && topHalf || !leftHalf && !topHalf;
|
|
114
124
|
} else {
|
|
115
|
-
useDefaultDiagonal =
|
|
125
|
+
useDefaultDiagonal = (x + y) % 2 === 0;
|
|
116
126
|
}
|
|
117
127
|
if (useDefaultDiagonal) {
|
|
118
128
|
indices.push(a, d, b);
|
|
@@ -266,42 +276,330 @@ class TerrainMesh extends webgpu.InstancedMesh {
|
|
|
266
276
|
}
|
|
267
277
|
}
|
|
268
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
|
+
|
|
269
289
|
const WORKGROUP_X = 16;
|
|
270
290
|
const WORKGROUP_Y = 16;
|
|
271
|
-
function compileComputePipeline(stages, width,
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
|
|
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;
|
|
275
298
|
const uInstanceCount = tsl.uniform(0, "uint");
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
});
|
|
292
347
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
}
|
|
298
378
|
function execute(renderer, instanceCount) {
|
|
379
|
+
const limits = getDeviceComputeLimits(renderer);
|
|
380
|
+
const canUseSingleKernel = preferSingleKernelWhenPossible && canRunSingleKernel(width, limits);
|
|
299
381
|
uInstanceCount.value = instanceCount;
|
|
300
|
-
|
|
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
|
+
}
|
|
301
404
|
}
|
|
302
405
|
return { execute };
|
|
303
406
|
}
|
|
304
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
|
+
|
|
305
603
|
const createElevation = (tile, uniforms, elevationFn) => {
|
|
306
604
|
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
307
605
|
const ix = tsl.int(localCoordinates.x);
|
|
@@ -323,7 +621,7 @@ const createElevation = (tile, uniforms, elevationFn) => {
|
|
|
323
621
|
});
|
|
324
622
|
};
|
|
325
623
|
};
|
|
326
|
-
const readElevationFieldAtPositionLocal = (
|
|
624
|
+
const readElevationFieldAtPositionLocal = (terrainFieldStorage, edgeVertexCount, positionLocal) => tsl.Fn(() => {
|
|
327
625
|
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
328
626
|
const intEdge = tsl.int(edgeVertexCount);
|
|
329
627
|
const innerSegments = tsl.int(edgeVertexCount).sub(3);
|
|
@@ -335,10 +633,12 @@ const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount
|
|
|
335
633
|
const y = v.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
336
634
|
const xClamped = tsl.min(tsl.max(x, tsl.int(0)), last);
|
|
337
635
|
const yClamped = tsl.min(tsl.max(y, tsl.int(0)), last);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
636
|
+
return loadTerrainFieldElevation(
|
|
637
|
+
terrainFieldStorage,
|
|
638
|
+
xClamped,
|
|
639
|
+
yClamped,
|
|
640
|
+
nodeIndex
|
|
641
|
+
);
|
|
342
642
|
});
|
|
343
643
|
|
|
344
644
|
function createTileCompute(leafStorage, uniforms) {
|
|
@@ -1231,21 +1531,19 @@ const elevationFieldStageTask = work.task((get, work) => {
|
|
|
1231
1531
|
});
|
|
1232
1532
|
}).displayName("elevationFieldStageTask");
|
|
1233
1533
|
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
});
|
|
1248
|
-
}).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");
|
|
1249
1547
|
function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1250
1548
|
return tsl.Fn(
|
|
1251
1549
|
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
@@ -1270,10 +1568,10 @@ function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
|
1270
1568
|
}
|
|
1271
1569
|
);
|
|
1272
1570
|
}
|
|
1273
|
-
const
|
|
1571
|
+
const terrainFieldStageTask = work.task((get, work) => {
|
|
1274
1572
|
const upstream = get(elevationFieldStageTask);
|
|
1275
1573
|
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1276
|
-
const
|
|
1574
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1277
1575
|
const tileEdgeVertexCount = get(innerTileSegments) + 3;
|
|
1278
1576
|
const tile = get(tileNodesTask);
|
|
1279
1577
|
const uniforms = get(createUniformsTask);
|
|
@@ -1288,6 +1586,7 @@ const normalFieldStageTask = work.task((get, work) => {
|
|
|
1288
1586
|
const ix = tsl.int(localCoordinates.x);
|
|
1289
1587
|
const iy = tsl.int(localCoordinates.y);
|
|
1290
1588
|
const tileSize = tile.tileSize(nodeIndex);
|
|
1589
|
+
const height = elevationFieldContext.node.element(globalVertexIndex);
|
|
1291
1590
|
const normalXZ = computeNormal(
|
|
1292
1591
|
nodeIndex,
|
|
1293
1592
|
tileSize,
|
|
@@ -1295,58 +1594,60 @@ const normalFieldStageTask = work.task((get, work) => {
|
|
|
1295
1594
|
iy,
|
|
1296
1595
|
uniforms.uElevationScale
|
|
1297
1596
|
);
|
|
1298
|
-
|
|
1597
|
+
storeTerrainField(
|
|
1598
|
+
terrainFieldStorage,
|
|
1599
|
+
ix,
|
|
1600
|
+
iy,
|
|
1601
|
+
nodeIndex,
|
|
1602
|
+
packTerrainFieldSample(height, normalXZ)
|
|
1603
|
+
);
|
|
1299
1604
|
}
|
|
1300
1605
|
];
|
|
1301
1606
|
});
|
|
1302
|
-
}).displayName("
|
|
1607
|
+
}).displayName("terrainFieldStageTask");
|
|
1303
1608
|
|
|
1304
1609
|
const compileComputeTask = work.task((get, work) => {
|
|
1305
|
-
const pipeline = get(
|
|
1610
|
+
const pipeline = get(terrainFieldStageTask);
|
|
1306
1611
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1307
|
-
return work(() => compileComputePipeline(pipeline, edgeVertexCount));
|
|
1308
|
-
}).displayName("compileComputeTask");
|
|
1309
|
-
const executeComputeTask = work.task((get, work, { resources }) => {
|
|
1310
|
-
const { execute } = get(compileComputeTask);
|
|
1311
|
-
const leafState = get(leafGpuBufferTask);
|
|
1312
1612
|
return work(
|
|
1313
|
-
() =>
|
|
1314
|
-
|
|
1613
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1614
|
+
preferSingleKernelWhenPossible: false
|
|
1615
|
+
})
|
|
1315
1616
|
);
|
|
1316
|
-
}).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");
|
|
1317
1628
|
function createComputePipelineTasks(leafStageTask) {
|
|
1318
1629
|
const compile = work.task((get, work) => {
|
|
1319
1630
|
const pipeline = get(leafStageTask);
|
|
1320
1631
|
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1321
|
-
return work(
|
|
1632
|
+
return work(
|
|
1633
|
+
() => compileComputePipeline(pipeline, edgeVertexCount, {
|
|
1634
|
+
preferSingleKernelWhenPossible: false
|
|
1635
|
+
})
|
|
1636
|
+
);
|
|
1322
1637
|
}).displayName("compileComputeTask");
|
|
1323
|
-
const execute = work.task(
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
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");
|
|
1329
1648
|
return { compile, execute };
|
|
1330
1649
|
}
|
|
1331
1650
|
|
|
1332
|
-
const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
|
|
1333
|
-
return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
|
|
1334
|
-
});
|
|
1335
|
-
const vectorSpaceToTextureSpace = tsl.Fn(([value]) => {
|
|
1336
|
-
return tsl.remap(value, tsl.float(-1), tsl.float(1), tsl.float(0), tsl.float(1));
|
|
1337
|
-
});
|
|
1338
|
-
const blendAngleCorrectedNormals = tsl.Fn(([n1, n2]) => {
|
|
1339
|
-
const t = tsl.vec3(n1.x, n1.y, n1.z.add(1));
|
|
1340
|
-
const u = tsl.vec3(n2.x.negate(), n2.y.negate(), n2.z);
|
|
1341
|
-
const r = t.mul(tsl.dot(t, u)).sub(u.mul(t.z)).normalize();
|
|
1342
|
-
return r;
|
|
1343
|
-
});
|
|
1344
|
-
const deriveNormalZ = tsl.Fn(([normalXY]) => {
|
|
1345
|
-
const xy = normalXY.toVar();
|
|
1346
|
-
const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
|
|
1347
|
-
return tsl.vec3(xy.x, xy.y, z);
|
|
1348
|
-
});
|
|
1349
|
-
|
|
1350
1651
|
const isSkirtVertex = tsl.Fn(([segments]) => {
|
|
1351
1652
|
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1352
1653
|
const vIndex = tsl.int(tsl.vertexIndex);
|
|
@@ -1388,37 +1689,40 @@ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
|
1388
1689
|
return tsl.vec3(worldX, rootOrigin.y, worldZ);
|
|
1389
1690
|
});
|
|
1390
1691
|
}
|
|
1391
|
-
function createTileElevation(terrainUniforms,
|
|
1392
|
-
if (!
|
|
1692
|
+
function createTileElevation(terrainUniforms, terrainFieldStorage) {
|
|
1693
|
+
if (!terrainFieldStorage) return tsl.float(0);
|
|
1393
1694
|
const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
|
|
1394
1695
|
return readElevationFieldAtPositionLocal(
|
|
1395
|
-
|
|
1696
|
+
terrainFieldStorage,
|
|
1396
1697
|
edgeVertexCount,
|
|
1397
1698
|
tsl.positionLocal
|
|
1398
1699
|
)().mul(
|
|
1399
1700
|
terrainUniforms.uElevationScale
|
|
1400
1701
|
);
|
|
1401
1702
|
}
|
|
1402
|
-
function createNormalAssignment(terrainUniforms,
|
|
1403
|
-
if (!
|
|
1703
|
+
function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
|
|
1704
|
+
if (!terrainFieldStorage) return;
|
|
1404
1705
|
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
1405
|
-
const
|
|
1406
|
-
const
|
|
1407
|
-
const
|
|
1408
|
-
const
|
|
1409
|
-
const normalXZ =
|
|
1410
|
-
const
|
|
1411
|
-
|
|
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));
|
|
1412
1716
|
}
|
|
1413
|
-
function createTileWorldPosition(leafStorage, terrainUniforms,
|
|
1717
|
+
function createTileWorldPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
|
|
1414
1718
|
const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
|
|
1415
1719
|
return tsl.Fn(() => {
|
|
1416
1720
|
const base = baseWorldPosition();
|
|
1417
|
-
const yElevation = createTileElevation(terrainUniforms,
|
|
1721
|
+
const yElevation = createTileElevation(terrainUniforms, terrainFieldStorage);
|
|
1418
1722
|
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1419
1723
|
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1420
1724
|
const worldY = tsl.select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
1421
|
-
createNormalAssignment(terrainUniforms,
|
|
1725
|
+
createNormalAssignment(terrainUniforms, terrainFieldStorage);
|
|
1422
1726
|
return tsl.vec3(base.x, worldY, base.z);
|
|
1423
1727
|
})();
|
|
1424
1728
|
}
|
|
@@ -1426,20 +1730,18 @@ function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBuf
|
|
|
1426
1730
|
const positionNodeTask = work.task((get, work) => {
|
|
1427
1731
|
const leafStorage = get(leafStorageTask);
|
|
1428
1732
|
const terrainUniforms = get(createUniformsTask);
|
|
1429
|
-
const
|
|
1430
|
-
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1733
|
+
const terrainFieldStorage = get(createTerrainFieldTextureTask);
|
|
1431
1734
|
return work(
|
|
1432
1735
|
() => createTileWorldPosition(
|
|
1433
1736
|
leafStorage,
|
|
1434
1737
|
terrainUniforms,
|
|
1435
|
-
|
|
1436
|
-
normalFieldContext.node
|
|
1738
|
+
terrainFieldStorage
|
|
1437
1739
|
)
|
|
1438
1740
|
);
|
|
1439
1741
|
}).displayName("positionNodeTask");
|
|
1440
1742
|
|
|
1441
1743
|
function terrainGraph() {
|
|
1442
|
-
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);
|
|
1443
1745
|
}
|
|
1444
1746
|
const terrainTasks = {
|
|
1445
1747
|
instanceId: instanceIdTask,
|
|
@@ -1453,13 +1755,31 @@ const terrainTasks = {
|
|
|
1453
1755
|
positionNode: positionNodeTask,
|
|
1454
1756
|
createElevationFieldContext: createElevationFieldContextTask,
|
|
1455
1757
|
createTileNodes: tileNodesTask,
|
|
1456
|
-
|
|
1758
|
+
createTerrainFieldTexture: createTerrainFieldTextureTask,
|
|
1457
1759
|
elevationFieldStage: elevationFieldStageTask,
|
|
1458
|
-
|
|
1760
|
+
terrainFieldStage: terrainFieldStageTask,
|
|
1459
1761
|
compileCompute: compileComputeTask,
|
|
1460
1762
|
executeCompute: executeComputeTask
|
|
1461
1763
|
};
|
|
1462
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
|
+
|
|
1463
1783
|
const vGlobalVertexIndex = /* @__PURE__ */ tsl.varyingProperty("int", "vGlobalVertexIndex");
|
|
1464
1784
|
const vElevation = /* @__PURE__ */ tsl.varyingProperty("f32", "vElevation");
|
|
1465
1785
|
|
|
@@ -1494,9 +1814,12 @@ const voronoiCells = tsl.Fn((params) => {
|
|
|
1494
1814
|
return k;
|
|
1495
1815
|
});
|
|
1496
1816
|
|
|
1817
|
+
exports.ArrayTextureBackend = ArrayTextureBackend;
|
|
1818
|
+
exports.AtlasBackend = AtlasBackend;
|
|
1497
1819
|
exports.Dir = Dir;
|
|
1498
1820
|
exports.TerrainGeometry = TerrainGeometry;
|
|
1499
1821
|
exports.TerrainMesh = TerrainMesh;
|
|
1822
|
+
exports.Texture3DBackend = Texture3DBackend;
|
|
1500
1823
|
exports.U32_EMPTY = U32_EMPTY;
|
|
1501
1824
|
exports.allocLeafSet = allocLeafSet;
|
|
1502
1825
|
exports.allocSeamTable = allocSeamTable;
|
|
@@ -1510,9 +1833,10 @@ exports.createCubeSphereSurface = createCubeSphereSurface;
|
|
|
1510
1833
|
exports.createElevationFieldContextTask = createElevationFieldContextTask;
|
|
1511
1834
|
exports.createFlatSurface = createFlatSurface;
|
|
1512
1835
|
exports.createInfiniteFlatSurface = createInfiniteFlatSurface;
|
|
1513
|
-
exports.createNormalFieldContextTask = createNormalFieldContextTask;
|
|
1514
1836
|
exports.createSpatialIndex = createSpatialIndex;
|
|
1515
1837
|
exports.createState = createState;
|
|
1838
|
+
exports.createTerrainFieldStorage = createTerrainFieldStorage;
|
|
1839
|
+
exports.createTerrainFieldTextureTask = createTerrainFieldTextureTask;
|
|
1516
1840
|
exports.createTerrainUniforms = createTerrainUniforms;
|
|
1517
1841
|
exports.createUniformsTask = createUniformsTask;
|
|
1518
1842
|
exports.deriveNormalZ = deriveNormalZ;
|
|
@@ -1520,16 +1844,20 @@ exports.elevationFieldStageTask = elevationFieldStageTask;
|
|
|
1520
1844
|
exports.elevationFn = elevationFn;
|
|
1521
1845
|
exports.elevationScale = elevationScale;
|
|
1522
1846
|
exports.executeComputeTask = executeComputeTask;
|
|
1847
|
+
exports.getDeviceComputeLimits = getDeviceComputeLimits;
|
|
1523
1848
|
exports.innerTileSegments = innerTileSegments;
|
|
1524
1849
|
exports.instanceIdTask = instanceIdTask;
|
|
1525
1850
|
exports.isSkirtUV = isSkirtUV;
|
|
1526
1851
|
exports.isSkirtVertex = isSkirtVertex;
|
|
1527
1852
|
exports.leafGpuBufferTask = leafGpuBufferTask;
|
|
1528
1853
|
exports.leafStorageTask = leafStorageTask;
|
|
1854
|
+
exports.loadTerrainField = loadTerrainField;
|
|
1855
|
+
exports.loadTerrainFieldElevation = loadTerrainFieldElevation;
|
|
1856
|
+
exports.loadTerrainFieldNormal = loadTerrainFieldNormal;
|
|
1529
1857
|
exports.maxLevel = maxLevel;
|
|
1530
1858
|
exports.maxNodes = maxNodes;
|
|
1531
|
-
exports.normalFieldStageTask = normalFieldStageTask;
|
|
1532
1859
|
exports.origin = origin;
|
|
1860
|
+
exports.packTerrainFieldSample = packTerrainFieldSample;
|
|
1533
1861
|
exports.positionNodeTask = positionNodeTask;
|
|
1534
1862
|
exports.quadtreeConfigTask = quadtreeConfigTask;
|
|
1535
1863
|
exports.quadtreeUpdate = quadtreeUpdate;
|
|
@@ -1538,8 +1866,10 @@ exports.resetLeafSet = resetLeafSet;
|
|
|
1538
1866
|
exports.resetSeamTable = resetSeamTable;
|
|
1539
1867
|
exports.rootSize = rootSize;
|
|
1540
1868
|
exports.skirtScale = skirtScale;
|
|
1869
|
+
exports.storeTerrainField = storeTerrainField;
|
|
1541
1870
|
exports.surface = surface;
|
|
1542
1871
|
exports.surfaceTask = surfaceTask;
|
|
1872
|
+
exports.terrainFieldStageTask = terrainFieldStageTask;
|
|
1543
1873
|
exports.terrainGraph = terrainGraph;
|
|
1544
1874
|
exports.terrainTasks = terrainTasks;
|
|
1545
1875
|
exports.textureSpaceToVectorSpace = textureSpaceToVectorSpace;
|