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