@hello-terrain/three 0.0.0-alpha.4 → 0.0.0-alpha.5
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/README.md +179 -23
- package/dist/index.cjs +644 -126
- package/dist/index.d.cts +335 -136
- package/dist/index.d.mts +335 -136
- package/dist/index.d.ts +335 -136
- package/dist/index.mjs +631 -127
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -2,32 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
const three = require('three');
|
|
4
4
|
const webgpu = require('three/webgpu');
|
|
5
|
-
const tsl = require('three/tsl');
|
|
6
5
|
const work = require('@hello-terrain/work');
|
|
6
|
+
const tsl = require('three/tsl');
|
|
7
|
+
const TSL_js = require('three/src/nodes/TSL.js');
|
|
7
8
|
|
|
8
9
|
class TerrainGeometry extends three.BufferGeometry {
|
|
9
10
|
constructor(innerSegments = 14, extendUV = false) {
|
|
10
11
|
super();
|
|
11
12
|
if (innerSegments < 1 || !Number.isFinite(innerSegments) || !Number.isInteger(innerSegments)) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
`Invalid innerSegments: ${innerSegments}. Must be a positive integer.`
|
|
14
|
-
);
|
|
13
|
+
throw new Error(`Invalid innerSegments: ${innerSegments}. Must be a positive integer.`);
|
|
15
14
|
}
|
|
16
15
|
try {
|
|
17
16
|
this.setIndex(this.generateIndices(innerSegments));
|
|
18
17
|
this.setAttribute(
|
|
19
18
|
"position",
|
|
20
|
-
new three.BufferAttribute(
|
|
21
|
-
new Float32Array(this.generatePositions(innerSegments)),
|
|
22
|
-
3
|
|
23
|
-
)
|
|
19
|
+
new three.BufferAttribute(new Float32Array(this.generatePositions(innerSegments)), 3)
|
|
24
20
|
);
|
|
25
21
|
this.setAttribute(
|
|
26
22
|
"normal",
|
|
27
|
-
new three.BufferAttribute(
|
|
28
|
-
new Float32Array(this.generateNormals(innerSegments)),
|
|
29
|
-
3
|
|
30
|
-
)
|
|
23
|
+
new three.BufferAttribute(new Float32Array(this.generateNormals(innerSegments)), 3)
|
|
31
24
|
);
|
|
32
25
|
this.setAttribute(
|
|
33
26
|
"uv",
|
|
@@ -219,7 +212,7 @@ class TerrainGeometry extends three.BufferGeometry {
|
|
|
219
212
|
|
|
220
213
|
const defaultTerrainMeshParams = {
|
|
221
214
|
innerTileSegments: 14,
|
|
222
|
-
maxNodes:
|
|
215
|
+
maxNodes: 1024,
|
|
223
216
|
material: new webgpu.MeshStandardNodeMaterial()
|
|
224
217
|
};
|
|
225
218
|
class TerrainMesh extends webgpu.InstancedMesh {
|
|
@@ -230,6 +223,7 @@ class TerrainMesh extends webgpu.InstancedMesh {
|
|
|
230
223
|
const { innerTileSegments, maxNodes, material } = mergedParams;
|
|
231
224
|
const geometry = new TerrainGeometry(innerTileSegments, true);
|
|
232
225
|
super(geometry, material, maxNodes);
|
|
226
|
+
this.frustumCulled = false;
|
|
233
227
|
this._innerTileSegments = innerTileSegments;
|
|
234
228
|
this._maxNodes = maxNodes;
|
|
235
229
|
}
|
|
@@ -246,72 +240,197 @@ class TerrainMesh extends webgpu.InstancedMesh {
|
|
|
246
240
|
return this._maxNodes;
|
|
247
241
|
}
|
|
248
242
|
set maxNodes(maxNodes) {
|
|
243
|
+
if (!Number.isInteger(maxNodes) || maxNodes < 1) {
|
|
244
|
+
throw new Error(`Invalid maxNodes: ${maxNodes}. Must be a positive integer.`);
|
|
245
|
+
}
|
|
246
|
+
if (maxNodes === this._maxNodes) return;
|
|
247
|
+
const oldMax = this._maxNodes;
|
|
248
|
+
const nextMatrix = new Float32Array(maxNodes * 16);
|
|
249
|
+
const oldMatrixArray = this.instanceMatrix.array;
|
|
250
|
+
nextMatrix.set(oldMatrixArray.subarray(0, Math.min(oldMatrixArray.length, nextMatrix.length)));
|
|
251
|
+
this.instanceMatrix = new webgpu.InstancedBufferAttribute(nextMatrix, 16);
|
|
252
|
+
if (this.instanceColor) {
|
|
253
|
+
const itemSize = this.instanceColor.itemSize;
|
|
254
|
+
const nextColor = new Float32Array(maxNodes * itemSize);
|
|
255
|
+
const oldColorArray = this.instanceColor.array;
|
|
256
|
+
nextColor.set(oldColorArray.subarray(0, Math.min(oldColorArray.length, nextColor.length)));
|
|
257
|
+
this.instanceColor = new webgpu.InstancedBufferAttribute(nextColor, itemSize);
|
|
258
|
+
}
|
|
249
259
|
this._maxNodes = maxNodes;
|
|
260
|
+
this.count = Math.min(this.count, maxNodes);
|
|
261
|
+
this.instanceMatrix.needsUpdate = true;
|
|
262
|
+
if (this.instanceColor) this.instanceColor.needsUpdate = true;
|
|
263
|
+
if (maxNodes < oldMax && this.count >= maxNodes) {
|
|
264
|
+
this.count = maxNodes;
|
|
265
|
+
}
|
|
250
266
|
}
|
|
251
267
|
}
|
|
252
268
|
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
const WORKGROUP_X = 16;
|
|
270
|
+
const WORKGROUP_Y = 16;
|
|
271
|
+
function compileComputePipeline(stages, width, bindings) {
|
|
272
|
+
const workgroupSize = [WORKGROUP_X, WORKGROUP_Y, 1];
|
|
273
|
+
const dispatchX = Math.ceil(width / WORKGROUP_X);
|
|
274
|
+
const dispatchY = Math.ceil(width / WORKGROUP_Y);
|
|
275
|
+
const uInstanceCount = tsl.uniform(0, "uint");
|
|
276
|
+
const computeShader = tsl.Fn(() => {
|
|
277
|
+
const fWidth = tsl.float(width);
|
|
278
|
+
const activeIndex = tsl.globalId.z;
|
|
279
|
+
const nodeIndex = tsl.int(activeIndex).toVar();
|
|
280
|
+
const iWidth = tsl.int(width);
|
|
281
|
+
const ix = tsl.int(tsl.globalId.x);
|
|
282
|
+
const iy = tsl.int(tsl.globalId.y);
|
|
283
|
+
const texelSize = tsl.vec2(1, 1).div(fWidth);
|
|
284
|
+
const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
|
|
285
|
+
const localUVCoords = localCoordinates.div(fWidth);
|
|
286
|
+
const verticesPerNode = iWidth.mul(iWidth);
|
|
287
|
+
const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
|
|
288
|
+
const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
|
|
289
|
+
for (let i = 0; i < stages.length; i++) {
|
|
290
|
+
if (i > 0) {
|
|
291
|
+
tsl.workgroupBarrier();
|
|
292
|
+
}
|
|
293
|
+
tsl.If(inBounds, () => {
|
|
294
|
+
stages[i](nodeIndex, globalIndex, localUVCoords, localCoordinates, texelSize);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
})().computeKernel(workgroupSize);
|
|
298
|
+
function execute(renderer, instanceCount) {
|
|
299
|
+
uInstanceCount.value = instanceCount;
|
|
300
|
+
renderer.compute(computeShader, [dispatchX, dispatchY, instanceCount]);
|
|
301
|
+
}
|
|
302
|
+
return { execute };
|
|
303
|
+
}
|
|
270
304
|
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
305
|
+
const createElevation = (tile, uniforms, elevationFn) => {
|
|
306
|
+
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
307
|
+
const ix = tsl.int(localCoordinates.x);
|
|
308
|
+
const iy = tsl.int(localCoordinates.y);
|
|
309
|
+
const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(tsl.int(3));
|
|
310
|
+
const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
|
|
311
|
+
const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
|
|
312
|
+
const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
|
|
313
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
314
|
+
return elevationFn({
|
|
315
|
+
worldPosition,
|
|
316
|
+
rootSize,
|
|
317
|
+
rootUV,
|
|
318
|
+
tileOriginVec2: tile.tileOriginVec2(nodeIndex),
|
|
319
|
+
tileSize: tile.tileSize(nodeIndex),
|
|
320
|
+
tileLevel: tile.tileLevel(nodeIndex),
|
|
321
|
+
nodeIndex: tsl.int(nodeIndex),
|
|
322
|
+
tileUV
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount, positionLocal) => tsl.Fn(() => {
|
|
327
|
+
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
328
|
+
const intEdge = tsl.int(edgeVertexCount);
|
|
329
|
+
const innerSegments = tsl.int(edgeVertexCount).sub(3);
|
|
330
|
+
const fInnerSegments = tsl.float(innerSegments);
|
|
331
|
+
const last = intEdge.sub(tsl.int(1));
|
|
332
|
+
const u = positionLocal.x.add(tsl.float(0.5));
|
|
333
|
+
const v = positionLocal.z.add(tsl.float(0.5));
|
|
334
|
+
const x = u.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
335
|
+
const y = v.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
336
|
+
const xClamped = tsl.min(tsl.max(x, tsl.int(0)), last);
|
|
337
|
+
const yClamped = tsl.min(tsl.max(y, tsl.int(0)), last);
|
|
338
|
+
const verticesPerNode = intEdge.mul(intEdge);
|
|
339
|
+
const perNodeVertexIndex = yClamped.mul(intEdge).add(xClamped);
|
|
340
|
+
const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(perNodeVertexIndex);
|
|
341
|
+
return elevationFieldBuffer.element(globalVertexIndex);
|
|
289
342
|
});
|
|
290
343
|
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
344
|
+
function createTileCompute(leafStorage, uniforms) {
|
|
345
|
+
const tileLevel = tsl.Fn(([nodeIndex]) => {
|
|
346
|
+
const nodeOffset = nodeIndex.mul(tsl.int(4));
|
|
347
|
+
return leafStorage.node.element(nodeOffset).toInt();
|
|
348
|
+
});
|
|
349
|
+
const tileOriginVec2 = tsl.Fn(([nodeIndex]) => {
|
|
295
350
|
const nodeOffset = nodeIndex.mul(tsl.int(4));
|
|
296
|
-
const nodeLevel = leafStorage.node.element(nodeOffset).toInt();
|
|
297
351
|
const nodeX = leafStorage.node.element(nodeOffset.add(tsl.int(1))).toFloat();
|
|
298
352
|
const nodeY = leafStorage.node.element(nodeOffset.add(tsl.int(2))).toFloat();
|
|
299
|
-
|
|
300
|
-
|
|
353
|
+
return tsl.vec2(nodeX, nodeY);
|
|
354
|
+
});
|
|
355
|
+
const tileSize = tsl.Fn(([nodeIndex]) => {
|
|
356
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
357
|
+
const level = tileLevel(nodeIndex);
|
|
358
|
+
return tsl.float(rootSize).div(tsl.pow(tsl.float(2), level.toFloat()));
|
|
359
|
+
});
|
|
360
|
+
const rootUVCompute = tsl.Fn(([nodeIndex, ix, iy]) => {
|
|
361
|
+
const nodeVec2 = tileOriginVec2(nodeIndex);
|
|
362
|
+
const nodeX = nodeVec2.x;
|
|
363
|
+
const nodeY = nodeVec2.y;
|
|
364
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
365
|
+
const rootOrigin = uniforms.uRootOrigin.toVar();
|
|
366
|
+
const size = tileSize(nodeIndex);
|
|
301
367
|
const half = tsl.float(0.5);
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
368
|
+
const halfRoot = tsl.float(rootSize).mul(half);
|
|
369
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
370
|
+
const texelSpacing = size.div(fInnerSegments);
|
|
371
|
+
const absX = nodeX.mul(fInnerSegments).add(tsl.int(ix).toFloat().sub(tsl.float(1)));
|
|
372
|
+
const absY = nodeY.mul(fInnerSegments).add(tsl.int(iy).toFloat().sub(tsl.float(1)));
|
|
373
|
+
const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
|
|
374
|
+
const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
|
|
375
|
+
const centeredX = worldX.sub(rootOrigin.x);
|
|
376
|
+
const centeredZ = worldZ.sub(rootOrigin.z);
|
|
377
|
+
return tsl.vec2(
|
|
378
|
+
centeredX.div(rootSize).add(half),
|
|
379
|
+
centeredZ.div(rootSize).mul(tsl.float(-1)).add(half)
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
const tileVertexWorldPositionCompute = tsl.Fn(
|
|
383
|
+
([nodeIndex, ix, iy]) => {
|
|
384
|
+
const rootOrigin = uniforms.uRootOrigin.toVar();
|
|
385
|
+
const nodeVec2 = tileOriginVec2(nodeIndex);
|
|
386
|
+
const nodeX = nodeVec2.x;
|
|
387
|
+
const nodeY = nodeVec2.y;
|
|
388
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
389
|
+
const size = tileSize(nodeIndex);
|
|
390
|
+
const half = tsl.float(0.5);
|
|
391
|
+
const halfRoot = tsl.float(rootSize).mul(half);
|
|
392
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
393
|
+
const texelSpacing = size.div(fInnerSegments);
|
|
394
|
+
const absX = nodeX.mul(fInnerSegments).add(tsl.int(ix).toFloat().sub(tsl.float(1)));
|
|
395
|
+
const absY = nodeY.mul(fInnerSegments).add(tsl.int(iy).toFloat().sub(tsl.float(1)));
|
|
396
|
+
const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
|
|
397
|
+
const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
|
|
398
|
+
return tsl.vec3(worldX, rootOrigin.y, worldZ);
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
return {
|
|
402
|
+
tileLevel,
|
|
403
|
+
tileOriginVec2,
|
|
404
|
+
tileSize,
|
|
405
|
+
rootUVCompute,
|
|
406
|
+
tileVertexWorldPositionCompute
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const rootSize = work.param(256).displayName("rootSize");
|
|
411
|
+
const origin = work.param({
|
|
412
|
+
x: 0,
|
|
413
|
+
y: 0,
|
|
414
|
+
z: 0
|
|
415
|
+
}).displayName("origin");
|
|
416
|
+
const innerTileSegments = work.param(13).displayName("innerTileSegments");
|
|
417
|
+
const skirtScale = work.param(100).displayName("skirtScale");
|
|
418
|
+
const elevationScale = work.param(1).displayName("elevationScale");
|
|
419
|
+
const maxNodes = work.param(1024).displayName("maxNodes");
|
|
420
|
+
const maxLevel = work.param(16).displayName("maxLevel");
|
|
421
|
+
const quadtreeUpdate = work.param({
|
|
422
|
+
cameraOrigin: { x: 0, y: 0, z: 0 },
|
|
423
|
+
mode: "distance",
|
|
424
|
+
distanceFactor: 1.5
|
|
425
|
+
}).displayName("quadtreeUpdate");
|
|
426
|
+
const surface = work.param(null).displayName("surface");
|
|
427
|
+
const elevationFn = work.param(() => tsl.float(0));
|
|
428
|
+
|
|
429
|
+
function createLeafStorage(maxNodes) {
|
|
430
|
+
const data = new Int32Array(maxNodes * 4);
|
|
431
|
+
const attribute = new webgpu.StorageBufferAttribute(data, 4);
|
|
432
|
+
const node = tsl.storage(attribute, "i32", 1).toReadOnly().setName("leafStorage");
|
|
433
|
+
return { data, attribute, node };
|
|
315
434
|
}
|
|
316
435
|
|
|
317
436
|
const Dir = {
|
|
@@ -327,8 +446,8 @@ function allocLeafSet(capacity) {
|
|
|
327
446
|
count: 0,
|
|
328
447
|
space: new Uint8Array(capacity),
|
|
329
448
|
level: new Uint8Array(capacity),
|
|
330
|
-
x: new
|
|
331
|
-
y: new
|
|
449
|
+
x: new Int32Array(capacity),
|
|
450
|
+
y: new Int32Array(capacity)
|
|
332
451
|
};
|
|
333
452
|
}
|
|
334
453
|
function resetLeafSet(leaves) {
|
|
@@ -354,8 +473,8 @@ function createNodeStore(maxNodes, spaceCount) {
|
|
|
354
473
|
gen: new Uint16Array(maxNodes),
|
|
355
474
|
space: new Uint8Array(maxNodes),
|
|
356
475
|
level: new Uint8Array(maxNodes),
|
|
357
|
-
x: new
|
|
358
|
-
y: new
|
|
476
|
+
x: new Int32Array(maxNodes),
|
|
477
|
+
y: new Int32Array(maxNodes),
|
|
359
478
|
firstChild: new Uint32Array(maxNodes),
|
|
360
479
|
flags: new Uint8Array(maxNodes),
|
|
361
480
|
roots: new Uint32Array(spaceCount)
|
|
@@ -376,8 +495,8 @@ function allocNode(store, tile) {
|
|
|
376
495
|
store.gen[id] = store.currentGen;
|
|
377
496
|
store.space[id] = tile.space;
|
|
378
497
|
store.level[id] = tile.level;
|
|
379
|
-
store.x[id] = tile.x
|
|
380
|
-
store.y[id] = tile.y
|
|
498
|
+
store.x[id] = tile.x;
|
|
499
|
+
store.y[id] = tile.y;
|
|
381
500
|
store.firstChild[id] = U32_EMPTY;
|
|
382
501
|
store.flags[id] = 0;
|
|
383
502
|
return id;
|
|
@@ -492,6 +611,10 @@ function buildLeafIndex(leaves, out) {
|
|
|
492
611
|
|
|
493
612
|
function createState(cfg, surface) {
|
|
494
613
|
const store = createNodeStore(cfg.maxNodes, surface.spaceCount);
|
|
614
|
+
const scratchRootTiles = [];
|
|
615
|
+
for (let i = 0; i < surface.maxRootCount; i++) {
|
|
616
|
+
scratchRootTiles.push({ space: 0, level: 0, x: 0, y: 0 });
|
|
617
|
+
}
|
|
495
618
|
return {
|
|
496
619
|
cfg,
|
|
497
620
|
store,
|
|
@@ -499,28 +622,42 @@ function createState(cfg, surface) {
|
|
|
499
622
|
leafNodeIds: new Uint32Array(cfg.maxNodes),
|
|
500
623
|
leafIndex: createSpatialIndex(cfg.maxNodes),
|
|
501
624
|
stack: new Uint32Array(cfg.maxNodes),
|
|
625
|
+
rootNodeIds: new Uint32Array(surface.maxRootCount),
|
|
626
|
+
rootCount: 0,
|
|
502
627
|
splitQueue: new Uint32Array(cfg.maxNodes),
|
|
503
628
|
splitStamp: new Uint16Array(cfg.maxNodes),
|
|
504
629
|
splitGen: 1,
|
|
505
630
|
scratchTile: { space: 0, level: 0, x: 0, y: 0 },
|
|
506
631
|
scratchNeighbor: { space: 0, level: 0, x: 0, y: 0 },
|
|
507
632
|
scratchBounds: { cx: 0, cy: 0, cz: 0, r: 0 },
|
|
633
|
+
scratchRootTiles,
|
|
508
634
|
spaceCount: surface.spaceCount
|
|
509
635
|
};
|
|
510
636
|
}
|
|
511
|
-
function beginUpdate(state, surface) {
|
|
637
|
+
function beginUpdate(state, surface, params) {
|
|
512
638
|
if (surface.spaceCount !== state.spaceCount) {
|
|
513
639
|
throw new Error(
|
|
514
640
|
`Surface spaceCount changed (${state.spaceCount} -> ${surface.spaceCount}). Create a new quadtree state.`
|
|
515
641
|
);
|
|
516
642
|
}
|
|
643
|
+
if (surface.maxRootCount !== state.rootNodeIds.length) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Surface maxRootCount changed (${state.rootNodeIds.length} -> ${surface.maxRootCount}). Create a new quadtree state.`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
517
648
|
beginFrame(state.store);
|
|
518
|
-
|
|
519
|
-
|
|
649
|
+
state.rootCount = 0;
|
|
650
|
+
const rootCount = surface.rootTiles(params.cameraOrigin, state.scratchRootTiles);
|
|
651
|
+
if (rootCount < 0 || rootCount > surface.maxRootCount) {
|
|
652
|
+
throw new Error(`Surface returned invalid root count (${rootCount}).`);
|
|
653
|
+
}
|
|
654
|
+
for (let i = 0; i < rootCount; i++) {
|
|
655
|
+
const rootId = allocNode(state.store, state.scratchRootTiles[i]);
|
|
520
656
|
if (rootId === U32_EMPTY) {
|
|
521
657
|
throw new Error("Failed to allocate root node (maxNodes too small).");
|
|
522
658
|
}
|
|
523
|
-
state.
|
|
659
|
+
state.rootNodeIds[i] = rootId;
|
|
660
|
+
state.rootCount = i + 1;
|
|
524
661
|
}
|
|
525
662
|
}
|
|
526
663
|
|
|
@@ -555,8 +692,8 @@ function refineLeaves(state, surface, params, outLeaves) {
|
|
|
555
692
|
const store = state.store;
|
|
556
693
|
const stack = state.stack;
|
|
557
694
|
let sp = 0;
|
|
558
|
-
for (let
|
|
559
|
-
stack[sp++] =
|
|
695
|
+
for (let i = 0; i < state.rootCount; i++) {
|
|
696
|
+
stack[sp++] = state.rootNodeIds[i];
|
|
560
697
|
}
|
|
561
698
|
while (sp > 0) {
|
|
562
699
|
const nodeId = stack[--sp];
|
|
@@ -669,7 +806,7 @@ function balance2to1(state, surface, params, leaves) {
|
|
|
669
806
|
}
|
|
670
807
|
|
|
671
808
|
function update(state, surface, params, outLeaves) {
|
|
672
|
-
beginUpdate(state, surface);
|
|
809
|
+
beginUpdate(state, surface, params);
|
|
673
810
|
const leaves = refineLeaves(state, surface, params, outLeaves);
|
|
674
811
|
return balance2to1(state, surface, params, leaves);
|
|
675
812
|
}
|
|
@@ -773,6 +910,7 @@ function createFlatSurface(cfg) {
|
|
|
773
910
|
const maxHeight = cfg.maxHeight ?? 0;
|
|
774
911
|
const surface = {
|
|
775
912
|
spaceCount: 1,
|
|
913
|
+
maxRootCount: 1,
|
|
776
914
|
neighborSameLevel(tile, dir, out) {
|
|
777
915
|
const level = tile.level;
|
|
778
916
|
const x = tile.x;
|
|
@@ -798,8 +936,8 @@ function createFlatSurface(cfg) {
|
|
|
798
936
|
if (nx > maxCoord || ny > maxCoord) return false;
|
|
799
937
|
out.space = 0;
|
|
800
938
|
out.level = level;
|
|
801
|
-
out.x = nx
|
|
802
|
-
out.y = ny
|
|
939
|
+
out.x = nx;
|
|
940
|
+
out.y = ny;
|
|
803
941
|
return true;
|
|
804
942
|
},
|
|
805
943
|
tileBounds(tile, cameraOrigin, out) {
|
|
@@ -815,14 +953,87 @@ function createFlatSurface(cfg) {
|
|
|
815
953
|
out.cy = centerY - cameraOrigin.y;
|
|
816
954
|
out.cz = centerZ - cameraOrigin.z;
|
|
817
955
|
out.r = 0.7071067811865476 * size + maxHeight;
|
|
956
|
+
},
|
|
957
|
+
rootTiles(_cameraOrigin, out) {
|
|
958
|
+
const root = out[0];
|
|
959
|
+
root.space = 0;
|
|
960
|
+
root.level = 0;
|
|
961
|
+
root.x = 0;
|
|
962
|
+
root.y = 0;
|
|
963
|
+
return 1;
|
|
818
964
|
}
|
|
819
965
|
};
|
|
820
966
|
return surface;
|
|
821
967
|
}
|
|
822
968
|
|
|
969
|
+
function createInfiniteFlatSurface(cfg) {
|
|
970
|
+
const halfRoot = 0.5 * cfg.rootSize;
|
|
971
|
+
const maxHeight = cfg.maxHeight ?? 0;
|
|
972
|
+
const rootGridRadius = Math.max(0, Math.floor(cfg.rootGridRadius ?? 1));
|
|
973
|
+
const rootWidth = rootGridRadius * 2 + 1;
|
|
974
|
+
return {
|
|
975
|
+
spaceCount: 1,
|
|
976
|
+
maxRootCount: rootWidth * rootWidth,
|
|
977
|
+
neighborSameLevel(tile, dir, out) {
|
|
978
|
+
let nx = tile.x;
|
|
979
|
+
let ny = tile.y;
|
|
980
|
+
switch (dir) {
|
|
981
|
+
case Dir.LEFT:
|
|
982
|
+
nx = tile.x - 1;
|
|
983
|
+
break;
|
|
984
|
+
case Dir.RIGHT:
|
|
985
|
+
nx = tile.x + 1;
|
|
986
|
+
break;
|
|
987
|
+
case Dir.TOP:
|
|
988
|
+
ny = tile.y - 1;
|
|
989
|
+
break;
|
|
990
|
+
case Dir.BOTTOM:
|
|
991
|
+
ny = tile.y + 1;
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
out.space = tile.space;
|
|
995
|
+
out.level = tile.level;
|
|
996
|
+
out.x = nx;
|
|
997
|
+
out.y = ny;
|
|
998
|
+
return true;
|
|
999
|
+
},
|
|
1000
|
+
tileBounds(tile, cameraOrigin, out) {
|
|
1001
|
+
const level = tile.level;
|
|
1002
|
+
const scale = 1 / (1 << level);
|
|
1003
|
+
const size = cfg.rootSize * scale;
|
|
1004
|
+
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
1005
|
+
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
1006
|
+
const centerX = minX + 0.5 * size;
|
|
1007
|
+
const centerY = cfg.origin.y;
|
|
1008
|
+
const centerZ = minZ + 0.5 * size;
|
|
1009
|
+
out.cx = centerX - cameraOrigin.x;
|
|
1010
|
+
out.cy = centerY - cameraOrigin.y;
|
|
1011
|
+
out.cz = centerZ - cameraOrigin.z;
|
|
1012
|
+
out.r = 0.7071067811865476 * size + maxHeight;
|
|
1013
|
+
},
|
|
1014
|
+
rootTiles(cameraOrigin, out) {
|
|
1015
|
+
const camRootX = Math.floor((cameraOrigin.x - cfg.origin.x + halfRoot) / cfg.rootSize);
|
|
1016
|
+
const camRootY = Math.floor((cameraOrigin.z - cfg.origin.z + halfRoot) / cfg.rootSize);
|
|
1017
|
+
let index = 0;
|
|
1018
|
+
for (let dy = -rootGridRadius; dy <= rootGridRadius; dy++) {
|
|
1019
|
+
for (let dx = -rootGridRadius; dx <= rootGridRadius; dx++) {
|
|
1020
|
+
const root = out[index];
|
|
1021
|
+
root.space = 0;
|
|
1022
|
+
root.level = 0;
|
|
1023
|
+
root.x = camRootX + dx;
|
|
1024
|
+
root.y = camRootY + dy;
|
|
1025
|
+
index++;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return index;
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
823
1033
|
function createCubeSphereSurface(_cfg) {
|
|
824
1034
|
return {
|
|
825
1035
|
spaceCount: 6,
|
|
1036
|
+
maxRootCount: 6,
|
|
826
1037
|
neighborSameLevel(_tile, _dir, _out) {
|
|
827
1038
|
return false;
|
|
828
1039
|
},
|
|
@@ -831,41 +1042,38 @@ function createCubeSphereSurface(_cfg) {
|
|
|
831
1042
|
out.cy = 0;
|
|
832
1043
|
out.cz = 0;
|
|
833
1044
|
out.r = Number.MAX_VALUE;
|
|
1045
|
+
},
|
|
1046
|
+
rootTiles(_cameraOrigin, out) {
|
|
1047
|
+
for (let s = 0; s < 6; s++) {
|
|
1048
|
+
const root = out[s];
|
|
1049
|
+
root.space = s;
|
|
1050
|
+
root.level = 0;
|
|
1051
|
+
root.x = 0;
|
|
1052
|
+
root.y = 0;
|
|
1053
|
+
}
|
|
1054
|
+
return 6;
|
|
834
1055
|
}
|
|
835
1056
|
};
|
|
836
1057
|
}
|
|
837
1058
|
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
const rootSize = work.param(256).displayName("rootSize");
|
|
841
|
-
const origin = work.param({ x: 0, y: 0, z: 0 }).displayName(
|
|
842
|
-
"origin"
|
|
843
|
-
);
|
|
844
|
-
const innerTileSegments = work.param(14).displayName("innerTileSegments");
|
|
845
|
-
const skirtScale = work.param(100).displayName("skirtScale");
|
|
846
|
-
const heightmapScale = work.param(1).displayName("heightmapScale");
|
|
847
|
-
const maxNodes = work.param(1028).displayName("maxNodes");
|
|
848
|
-
const maxLevel = work.param(16).displayName("maxLevel");
|
|
849
|
-
const quadtreeUpdate = work.param({
|
|
850
|
-
cameraOrigin: { x: 0, y: 0, z: 0 },
|
|
851
|
-
mode: "distance",
|
|
852
|
-
distanceFactor: 1.5
|
|
853
|
-
}).displayName("quadtreeUpdate");
|
|
854
|
-
|
|
855
|
-
const quadtreeConfigTask = work.task((get, work) => {
|
|
1059
|
+
const surfaceTask = work.task((get, work) => {
|
|
1060
|
+
const customSurface = get(surface);
|
|
856
1061
|
const rootSizeVal = get(rootSize);
|
|
857
1062
|
const originVal = get(origin);
|
|
1063
|
+
return work(() => {
|
|
1064
|
+
if (customSurface) return customSurface;
|
|
1065
|
+
return createFlatSurface({ rootSize: rootSizeVal, origin: originVal });
|
|
1066
|
+
});
|
|
1067
|
+
}).displayName("surfaceTask");
|
|
1068
|
+
const quadtreeConfigTask = work.task((get, work) => {
|
|
1069
|
+
const surfaceVal = get(surfaceTask);
|
|
858
1070
|
const maxNodesVal = get(maxNodes);
|
|
859
1071
|
const maxLevelVal = get(maxLevel);
|
|
860
1072
|
return work(() => {
|
|
861
|
-
const
|
|
862
|
-
rootSize: rootSizeVal,
|
|
863
|
-
origin: originVal
|
|
864
|
-
});
|
|
865
|
-
const state = createState({ maxNodes: maxNodesVal, maxLevel: maxLevelVal }, surface);
|
|
1073
|
+
const state = createState({ maxNodes: maxNodesVal, maxLevel: maxLevelVal }, surfaceVal);
|
|
866
1074
|
return {
|
|
867
1075
|
state,
|
|
868
|
-
surface
|
|
1076
|
+
surface: surfaceVal
|
|
869
1077
|
};
|
|
870
1078
|
});
|
|
871
1079
|
}).displayName("quadtreeConfigTask");
|
|
@@ -885,12 +1093,7 @@ const quadtreeUpdateTask = work.task((get, work) => {
|
|
|
885
1093
|
}).displayName("quadtreeUpdateTask");
|
|
886
1094
|
const leafStorageTask = work.task((get, work) => {
|
|
887
1095
|
const maxNodesVal = get(maxNodes);
|
|
888
|
-
return work(() =>
|
|
889
|
-
const data = new Int32Array(maxNodesVal * 4);
|
|
890
|
-
const attribute = new webgpu.StorageBufferAttribute(data, 4);
|
|
891
|
-
const node = tsl.storage(attribute, "i32", 1).toReadOnly();
|
|
892
|
-
return { data, attribute, node };
|
|
893
|
-
});
|
|
1096
|
+
return work(() => createLeafStorage(maxNodesVal));
|
|
894
1097
|
}).displayName("leafStorageTask");
|
|
895
1098
|
const leafGpuBufferTask = work.task((get, work) => {
|
|
896
1099
|
const leafSet = get(quadtreeUpdateTask);
|
|
@@ -916,6 +1119,23 @@ const leafGpuBufferTask = work.task((get, work) => {
|
|
|
916
1119
|
});
|
|
917
1120
|
}).displayName("leafGpuBufferTask");
|
|
918
1121
|
|
|
1122
|
+
function createElevationFunction(callback) {
|
|
1123
|
+
const tslFunction = (args) => {
|
|
1124
|
+
const params = {
|
|
1125
|
+
worldPosition: args.worldPosition,
|
|
1126
|
+
rootSize: args.rootSize,
|
|
1127
|
+
rootUV: args.rootUV,
|
|
1128
|
+
tileUV: args.tileUV,
|
|
1129
|
+
tileLevel: args.tileLevel,
|
|
1130
|
+
tileSize: args.tileSize,
|
|
1131
|
+
tileOriginVec2: args.tileOriginVec2,
|
|
1132
|
+
nodeIndex: args.nodeIndex
|
|
1133
|
+
};
|
|
1134
|
+
return callback(params);
|
|
1135
|
+
};
|
|
1136
|
+
return TSL_js.Fn(tslFunction);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
919
1139
|
function createTerrainUniforms(params) {
|
|
920
1140
|
const sanitizedId = params.instanceId?.replace(/-/g, "_");
|
|
921
1141
|
const suffix = sanitizedId ? `_${sanitizedId}` : "";
|
|
@@ -927,16 +1147,18 @@ function createTerrainUniforms(params) {
|
|
|
927
1147
|
`uInnerTileSegments${suffix}`
|
|
928
1148
|
);
|
|
929
1149
|
const uSkirtScale = tsl.uniform(tsl.float(params.skirtScale)).setName(`uSkirtScale${suffix}`);
|
|
930
|
-
const
|
|
1150
|
+
const uElevationScale = tsl.uniform(tsl.float(params.elevationScale)).setName(`uElevationScale${suffix}`);
|
|
931
1151
|
return {
|
|
932
1152
|
uRootOrigin,
|
|
933
1153
|
uRootSize,
|
|
934
1154
|
uInnerTileSegments,
|
|
935
1155
|
uSkirtScale,
|
|
936
|
-
|
|
1156
|
+
uElevationScale
|
|
937
1157
|
};
|
|
938
1158
|
}
|
|
939
1159
|
|
|
1160
|
+
const instanceIdTask = work.task(() => crypto.randomUUID()).displayName("instanceIdTask").cache("once");
|
|
1161
|
+
|
|
940
1162
|
const scratchVector3 = new three.Vector3();
|
|
941
1163
|
const createUniformsTask = work.task((get, work) => {
|
|
942
1164
|
const uniformParams = {
|
|
@@ -944,18 +1166,18 @@ const createUniformsTask = work.task((get, work) => {
|
|
|
944
1166
|
rootSize: get(rootSize),
|
|
945
1167
|
innerTileSegments: get(innerTileSegments),
|
|
946
1168
|
skirtScale: get(skirtScale),
|
|
947
|
-
|
|
1169
|
+
elevationScale: get(elevationScale),
|
|
948
1170
|
instanceId: get(instanceIdTask)
|
|
949
1171
|
};
|
|
950
1172
|
return work(() => createTerrainUniforms(uniformParams));
|
|
951
|
-
}).displayName("
|
|
1173
|
+
}).displayName("createUniformsTask").cache("once");
|
|
952
1174
|
const updateUniformsTask = work.task((get, work) => {
|
|
953
1175
|
const terrainUniformsContext = get(createUniformsTask);
|
|
954
1176
|
const rootSizeVal = get(rootSize);
|
|
955
1177
|
const rootOrigin = get(origin);
|
|
956
1178
|
const innerTileSegmentsVal = get(innerTileSegments);
|
|
957
1179
|
const skirtScaleVal = get(skirtScale);
|
|
958
|
-
const
|
|
1180
|
+
const elevationScaleVal = get(elevationScale);
|
|
959
1181
|
return work(() => {
|
|
960
1182
|
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
961
1183
|
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
@@ -965,31 +1187,313 @@ const updateUniformsTask = work.task((get, work) => {
|
|
|
965
1187
|
);
|
|
966
1188
|
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
967
1189
|
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
968
|
-
terrainUniformsContext.
|
|
1190
|
+
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
969
1191
|
return terrainUniformsContext;
|
|
970
1192
|
});
|
|
971
|
-
}).displayName("
|
|
1193
|
+
}).displayName("updateUniformsTask");
|
|
1194
|
+
|
|
1195
|
+
const createElevationFieldContextTask = work.task((get, work) => {
|
|
1196
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1197
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
1198
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
1199
|
+
return work(() => {
|
|
1200
|
+
const data = new Float32Array(totalElements);
|
|
1201
|
+
const attribute = new webgpu.StorageBufferAttribute(data, 1);
|
|
1202
|
+
const node = tsl.storage(attribute, "float", totalElements);
|
|
1203
|
+
return {
|
|
1204
|
+
data,
|
|
1205
|
+
attribute,
|
|
1206
|
+
node
|
|
1207
|
+
};
|
|
1208
|
+
});
|
|
1209
|
+
}).displayName("createElevationFieldContextTask");
|
|
1210
|
+
const tileNodesTask = work.task((get, work) => {
|
|
1211
|
+
const leafStorage = get(leafStorageTask);
|
|
1212
|
+
const uniforms = get(createUniformsTask);
|
|
1213
|
+
return work(() => {
|
|
1214
|
+
return createTileCompute(leafStorage, uniforms);
|
|
1215
|
+
});
|
|
1216
|
+
}).displayName("tileNodesTask");
|
|
1217
|
+
const elevationFieldStageTask = work.task((get, work) => {
|
|
1218
|
+
const tile = get(tileNodesTask);
|
|
1219
|
+
const uniforms = get(createUniformsTask);
|
|
1220
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1221
|
+
const userElevationFn = get(elevationFn);
|
|
1222
|
+
return work(() => {
|
|
1223
|
+
const heightFn = createElevationFunction(userElevationFn);
|
|
1224
|
+
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
1225
|
+
return [
|
|
1226
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
1227
|
+
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
1228
|
+
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
1229
|
+
}
|
|
1230
|
+
];
|
|
1231
|
+
});
|
|
1232
|
+
}).displayName("elevationFieldStageTask");
|
|
1233
|
+
|
|
1234
|
+
const createNormalFieldContextTask = work.task((get, work) => {
|
|
1235
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1236
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
1237
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
1238
|
+
return work(() => {
|
|
1239
|
+
const data = new Uint32Array(totalElements);
|
|
1240
|
+
const attribute = new webgpu.StorageBufferAttribute(data, 1);
|
|
1241
|
+
const node = tsl.storage(attribute, "uint", totalElements);
|
|
1242
|
+
return {
|
|
1243
|
+
data,
|
|
1244
|
+
attribute,
|
|
1245
|
+
node
|
|
1246
|
+
};
|
|
1247
|
+
});
|
|
1248
|
+
}).displayName("createNormalFieldContextTask");
|
|
1249
|
+
function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1250
|
+
return tsl.Fn(
|
|
1251
|
+
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
1252
|
+
const iEdge = tsl.int(edgeVertexCount);
|
|
1253
|
+
const verticesPerNode = iEdge.mul(iEdge);
|
|
1254
|
+
const baseOffset = tsl.int(nodeIndex).mul(verticesPerNode);
|
|
1255
|
+
const xLeft = tsl.int(ix).sub(tsl.int(1));
|
|
1256
|
+
const xRight = tsl.int(ix).add(tsl.int(1));
|
|
1257
|
+
const yUp = tsl.int(iy).sub(tsl.int(1));
|
|
1258
|
+
const yDown = tsl.int(iy).add(tsl.int(1));
|
|
1259
|
+
const hLeft = elevationFieldNode.element(baseOffset.add(tsl.int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
1260
|
+
const hRight = elevationFieldNode.element(baseOffset.add(tsl.int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
1261
|
+
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(tsl.int(ix)))).mul(elevationScale);
|
|
1262
|
+
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(tsl.int(ix)))).mul(elevationScale);
|
|
1263
|
+
const innerSegments = tsl.float(iEdge).sub(tsl.float(3));
|
|
1264
|
+
const stepWorld = tileSize.div(innerSegments);
|
|
1265
|
+
const inv2Step = tsl.float(0.5).div(stepWorld);
|
|
1266
|
+
const dhdx = tsl.float(hRight).sub(tsl.float(hLeft)).mul(inv2Step);
|
|
1267
|
+
const dhdz = tsl.float(hDown).sub(tsl.float(hUp)).mul(inv2Step);
|
|
1268
|
+
const normal = tsl.vec3(dhdx.negate(), tsl.float(1), dhdz.negate()).normalize();
|
|
1269
|
+
return tsl.vec2(normal.x, normal.z);
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
const normalFieldStageTask = work.task((get, work) => {
|
|
1274
|
+
const upstream = get(elevationFieldStageTask);
|
|
1275
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1276
|
+
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1277
|
+
const tileEdgeVertexCount = get(innerTileSegments) + 3;
|
|
1278
|
+
const tile = get(tileNodesTask);
|
|
1279
|
+
const uniforms = get(createUniformsTask);
|
|
1280
|
+
return work(() => {
|
|
1281
|
+
const computeNormal = createNormalFromElevationField(
|
|
1282
|
+
elevationFieldContext.node,
|
|
1283
|
+
tileEdgeVertexCount
|
|
1284
|
+
);
|
|
1285
|
+
return [
|
|
1286
|
+
...upstream,
|
|
1287
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
1288
|
+
const ix = tsl.int(localCoordinates.x);
|
|
1289
|
+
const iy = tsl.int(localCoordinates.y);
|
|
1290
|
+
const tileSize = tile.tileSize(nodeIndex);
|
|
1291
|
+
const normalXZ = computeNormal(
|
|
1292
|
+
nodeIndex,
|
|
1293
|
+
tileSize,
|
|
1294
|
+
ix,
|
|
1295
|
+
iy,
|
|
1296
|
+
uniforms.uElevationScale
|
|
1297
|
+
);
|
|
1298
|
+
normalFieldContext.node.element(globalVertexIndex).assign(tsl.packHalf2x16(normalXZ));
|
|
1299
|
+
}
|
|
1300
|
+
];
|
|
1301
|
+
});
|
|
1302
|
+
}).displayName("normalFieldStageTask");
|
|
1303
|
+
|
|
1304
|
+
const compileComputeTask = work.task((get, work) => {
|
|
1305
|
+
const pipeline = get(normalFieldStageTask);
|
|
1306
|
+
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
|
+
return work(
|
|
1313
|
+
() => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
1316
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
1317
|
+
function createComputePipelineTasks(leafStageTask) {
|
|
1318
|
+
const compile = work.task((get, work) => {
|
|
1319
|
+
const pipeline = get(leafStageTask);
|
|
1320
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1321
|
+
return work(() => compileComputePipeline(pipeline, edgeVertexCount));
|
|
1322
|
+
}).displayName("compileComputeTask");
|
|
1323
|
+
const execute = work.task((get, work, { resources }) => {
|
|
1324
|
+
const { execute: run } = get(compile);
|
|
1325
|
+
const leafState = get(leafGpuBufferTask);
|
|
1326
|
+
return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
1327
|
+
});
|
|
1328
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
1329
|
+
return { compile, execute };
|
|
1330
|
+
}
|
|
1331
|
+
|
|
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
|
+
const isSkirtVertex = tsl.Fn(([segments]) => {
|
|
1351
|
+
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1352
|
+
const vIndex = tsl.int(tsl.vertexIndex);
|
|
1353
|
+
const segmentEdges = tsl.int(segmentsNode.add(3));
|
|
1354
|
+
const vx = vIndex.mod(segmentEdges);
|
|
1355
|
+
const vy = vIndex.div(segmentEdges);
|
|
1356
|
+
const last = segmentEdges.sub(tsl.int(1));
|
|
1357
|
+
return vx.equal(tsl.int(0)).or(vx.equal(last)).or(vy.equal(tsl.int(0))).or(vy.equal(last));
|
|
1358
|
+
});
|
|
1359
|
+
const isSkirtUV = tsl.Fn(([segments]) => {
|
|
1360
|
+
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1361
|
+
const ux = tsl.uv().x;
|
|
1362
|
+
const uy = tsl.uv().y;
|
|
1363
|
+
const segmentCount = segmentsNode.add(2);
|
|
1364
|
+
const segmentStep = tsl.float(1).div(segmentCount);
|
|
1365
|
+
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
1366
|
+
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
1367
|
+
return innerX.and(innerY).not();
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
1371
|
+
return tsl.Fn(() => {
|
|
1372
|
+
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
1373
|
+
const nodeOffset = nodeIndex.mul(tsl.int(4));
|
|
1374
|
+
const nodeLevel = leafStorage.node.element(nodeOffset).toInt();
|
|
1375
|
+
const nodeX = leafStorage.node.element(nodeOffset.add(tsl.int(1))).toFloat();
|
|
1376
|
+
const nodeY = leafStorage.node.element(nodeOffset.add(tsl.int(2))).toFloat();
|
|
1377
|
+
const rootSize = terrainUniforms.uRootSize.toVar();
|
|
1378
|
+
const rootOrigin = terrainUniforms.uRootOrigin.toVar();
|
|
1379
|
+
const half = tsl.float(0.5);
|
|
1380
|
+
const size = rootSize.div(tsl.pow(tsl.float(2), nodeLevel.toFloat()));
|
|
1381
|
+
const halfRoot = rootSize.mul(half);
|
|
1382
|
+
const centerX = rootOrigin.x.add(nodeX.add(half).mul(size)).sub(halfRoot);
|
|
1383
|
+
const centerZ = rootOrigin.z.add(nodeY.add(half).mul(size)).sub(halfRoot);
|
|
1384
|
+
const clampedX = tsl.positionLocal.x.max(half.negate()).min(half);
|
|
1385
|
+
const clampedZ = tsl.positionLocal.z.max(half.negate()).min(half);
|
|
1386
|
+
const worldX = centerX.add(clampedX.mul(size));
|
|
1387
|
+
const worldZ = centerZ.add(clampedZ.mul(size));
|
|
1388
|
+
return tsl.vec3(worldX, rootOrigin.y, worldZ);
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
function createTileElevation(terrainUniforms, elevationFieldBufferNode) {
|
|
1392
|
+
if (!elevationFieldBufferNode) return tsl.float(0);
|
|
1393
|
+
const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
|
|
1394
|
+
return readElevationFieldAtPositionLocal(
|
|
1395
|
+
elevationFieldBufferNode,
|
|
1396
|
+
edgeVertexCount,
|
|
1397
|
+
tsl.positionLocal
|
|
1398
|
+
)().mul(
|
|
1399
|
+
terrainUniforms.uElevationScale
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
function createNormalAssignment(terrainUniforms, normalFieldBufferNode) {
|
|
1403
|
+
if (!normalFieldBufferNode) return;
|
|
1404
|
+
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
1405
|
+
const intEdge = tsl.int(terrainUniforms.uInnerTileSegments.add(3));
|
|
1406
|
+
const verticesPerNode = intEdge.mul(intEdge);
|
|
1407
|
+
const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(tsl.int(tsl.vertexIndex));
|
|
1408
|
+
const packed = normalFieldBufferNode.element(globalVertexIndex);
|
|
1409
|
+
const normalXZ = tsl.unpackHalf2x16(packed);
|
|
1410
|
+
const reconstructed = deriveNormalZ(normalXZ);
|
|
1411
|
+
tsl.normalLocal.assign(tsl.vec3(reconstructed.x, reconstructed.z, reconstructed.y));
|
|
1412
|
+
}
|
|
1413
|
+
function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBufferNode, normalFieldBufferNode) {
|
|
1414
|
+
const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
|
|
1415
|
+
return tsl.Fn(() => {
|
|
1416
|
+
const base = baseWorldPosition();
|
|
1417
|
+
const yElevation = createTileElevation(terrainUniforms, elevationFieldBufferNode);
|
|
1418
|
+
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1419
|
+
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1420
|
+
const worldY = tsl.select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
1421
|
+
createNormalAssignment(terrainUniforms, normalFieldBufferNode);
|
|
1422
|
+
return tsl.vec3(base.x, worldY, base.z);
|
|
1423
|
+
})();
|
|
1424
|
+
}
|
|
972
1425
|
|
|
973
1426
|
const positionNodeTask = work.task((get, work) => {
|
|
974
1427
|
const leafStorage = get(leafStorageTask);
|
|
975
1428
|
const terrainUniforms = get(createUniformsTask);
|
|
976
|
-
|
|
977
|
-
|
|
1429
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1430
|
+
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1431
|
+
return work(
|
|
1432
|
+
() => createTileWorldPosition(
|
|
1433
|
+
leafStorage,
|
|
1434
|
+
terrainUniforms,
|
|
1435
|
+
elevationFieldContext.node,
|
|
1436
|
+
normalFieldContext.node
|
|
1437
|
+
)
|
|
1438
|
+
);
|
|
1439
|
+
}).displayName("positionNodeTask");
|
|
978
1440
|
|
|
979
1441
|
function terrainGraph() {
|
|
980
|
-
return work.graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask);
|
|
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(createNormalFieldContextTask).add(elevationFieldStageTask).add(normalFieldStageTask).add(compileComputeTask).add(executeComputeTask);
|
|
981
1443
|
}
|
|
982
1444
|
const terrainTasks = {
|
|
983
1445
|
instanceId: instanceIdTask,
|
|
984
1446
|
quadtreeConfig: quadtreeConfigTask,
|
|
985
1447
|
quadtreeUpdate: quadtreeUpdateTask,
|
|
986
1448
|
leafStorage: leafStorageTask,
|
|
1449
|
+
surface: surfaceTask,
|
|
987
1450
|
leafGpuBuffer: leafGpuBufferTask,
|
|
988
1451
|
createUniforms: createUniformsTask,
|
|
989
1452
|
updateUniforms: updateUniformsTask,
|
|
990
|
-
positionNode: positionNodeTask
|
|
1453
|
+
positionNode: positionNodeTask,
|
|
1454
|
+
createElevationFieldContext: createElevationFieldContextTask,
|
|
1455
|
+
createTileNodes: tileNodesTask,
|
|
1456
|
+
createNormalFieldContext: createNormalFieldContextTask,
|
|
1457
|
+
elevationFieldStage: elevationFieldStageTask,
|
|
1458
|
+
normalFieldStage: normalFieldStageTask,
|
|
1459
|
+
compileCompute: compileComputeTask,
|
|
1460
|
+
executeCompute: executeComputeTask
|
|
991
1461
|
};
|
|
992
1462
|
|
|
1463
|
+
const vGlobalVertexIndex = /* @__PURE__ */ tsl.varyingProperty("int", "vGlobalVertexIndex");
|
|
1464
|
+
const vElevation = /* @__PURE__ */ tsl.varyingProperty("f32", "vElevation");
|
|
1465
|
+
|
|
1466
|
+
const cellCenter = tsl.Fn(({ cell }) => {
|
|
1467
|
+
return cell.add(tsl.mx_noise_float(cell.mul(Math.PI)));
|
|
1468
|
+
});
|
|
1469
|
+
const voronoiCells = tsl.Fn((params) => {
|
|
1470
|
+
const scale = tsl.float(params.scale);
|
|
1471
|
+
const facet = tsl.float(params.facet);
|
|
1472
|
+
const seed = tsl.float(params.seed);
|
|
1473
|
+
const pos = params.uv.mul(scale).add(seed);
|
|
1474
|
+
const midCell = pos.round().toVar();
|
|
1475
|
+
const minCell = midCell.toVar();
|
|
1476
|
+
const minDist = tsl.float(1).toVar();
|
|
1477
|
+
const cell = tsl.vec3(0, 0, 0).toVar();
|
|
1478
|
+
const dist = tsl.float().toVar();
|
|
1479
|
+
const i = tsl.float(0).toVar();
|
|
1480
|
+
tsl.Loop(27, () => {
|
|
1481
|
+
const ix = i.mod(3).sub(1);
|
|
1482
|
+
const iy = i.div(3).floor().mod(3).sub(1);
|
|
1483
|
+
const iz = i.div(9).floor().sub(1);
|
|
1484
|
+
cell.assign(midCell.add(tsl.vec3(ix, iy, iz)));
|
|
1485
|
+
dist.assign(pos.distance(cellCenter({ cell })).add(tsl.mx_noise_float(pos).div(5)));
|
|
1486
|
+
tsl.If(dist.lessThan(minDist), () => {
|
|
1487
|
+
minDist.assign(dist);
|
|
1488
|
+
minCell.assign(cell);
|
|
1489
|
+
});
|
|
1490
|
+
i.addAssign(1);
|
|
1491
|
+
});
|
|
1492
|
+
const n = tsl.mx_noise_float(minCell.mul(Math.PI)).toVar();
|
|
1493
|
+
const k = tsl.mix(minDist, n.add(1).div(2), facet);
|
|
1494
|
+
return k;
|
|
1495
|
+
});
|
|
1496
|
+
|
|
993
1497
|
exports.Dir = Dir;
|
|
994
1498
|
exports.TerrainGeometry = TerrainGeometry;
|
|
995
1499
|
exports.TerrainMesh = TerrainMesh;
|
|
@@ -1000,15 +1504,22 @@ exports.beginUpdate = beginUpdate;
|
|
|
1000
1504
|
exports.blendAngleCorrectedNormals = blendAngleCorrectedNormals;
|
|
1001
1505
|
exports.buildLeafIndex = buildLeafIndex;
|
|
1002
1506
|
exports.buildSeams2to1 = buildSeams2to1;
|
|
1507
|
+
exports.compileComputeTask = compileComputeTask;
|
|
1508
|
+
exports.createComputePipelineTasks = createComputePipelineTasks;
|
|
1003
1509
|
exports.createCubeSphereSurface = createCubeSphereSurface;
|
|
1510
|
+
exports.createElevationFieldContextTask = createElevationFieldContextTask;
|
|
1004
1511
|
exports.createFlatSurface = createFlatSurface;
|
|
1512
|
+
exports.createInfiniteFlatSurface = createInfiniteFlatSurface;
|
|
1513
|
+
exports.createNormalFieldContextTask = createNormalFieldContextTask;
|
|
1005
1514
|
exports.createSpatialIndex = createSpatialIndex;
|
|
1006
1515
|
exports.createState = createState;
|
|
1007
1516
|
exports.createTerrainUniforms = createTerrainUniforms;
|
|
1008
|
-
exports.createTileWorldPosition = createTileWorldPosition;
|
|
1009
1517
|
exports.createUniformsTask = createUniformsTask;
|
|
1010
1518
|
exports.deriveNormalZ = deriveNormalZ;
|
|
1011
|
-
exports.
|
|
1519
|
+
exports.elevationFieldStageTask = elevationFieldStageTask;
|
|
1520
|
+
exports.elevationFn = elevationFn;
|
|
1521
|
+
exports.elevationScale = elevationScale;
|
|
1522
|
+
exports.executeComputeTask = executeComputeTask;
|
|
1012
1523
|
exports.innerTileSegments = innerTileSegments;
|
|
1013
1524
|
exports.instanceIdTask = instanceIdTask;
|
|
1014
1525
|
exports.isSkirtUV = isSkirtUV;
|
|
@@ -1017,6 +1528,7 @@ exports.leafGpuBufferTask = leafGpuBufferTask;
|
|
|
1017
1528
|
exports.leafStorageTask = leafStorageTask;
|
|
1018
1529
|
exports.maxLevel = maxLevel;
|
|
1019
1530
|
exports.maxNodes = maxNodes;
|
|
1531
|
+
exports.normalFieldStageTask = normalFieldStageTask;
|
|
1020
1532
|
exports.origin = origin;
|
|
1021
1533
|
exports.positionNodeTask = positionNodeTask;
|
|
1022
1534
|
exports.quadtreeConfigTask = quadtreeConfigTask;
|
|
@@ -1026,9 +1538,15 @@ exports.resetLeafSet = resetLeafSet;
|
|
|
1026
1538
|
exports.resetSeamTable = resetSeamTable;
|
|
1027
1539
|
exports.rootSize = rootSize;
|
|
1028
1540
|
exports.skirtScale = skirtScale;
|
|
1541
|
+
exports.surface = surface;
|
|
1542
|
+
exports.surfaceTask = surfaceTask;
|
|
1029
1543
|
exports.terrainGraph = terrainGraph;
|
|
1030
1544
|
exports.terrainTasks = terrainTasks;
|
|
1031
1545
|
exports.textureSpaceToVectorSpace = textureSpaceToVectorSpace;
|
|
1546
|
+
exports.tileNodesTask = tileNodesTask;
|
|
1032
1547
|
exports.update = update;
|
|
1033
1548
|
exports.updateUniformsTask = updateUniformsTask;
|
|
1549
|
+
exports.vElevation = vElevation;
|
|
1550
|
+
exports.vGlobalVertexIndex = vGlobalVertexIndex;
|
|
1034
1551
|
exports.vectorSpaceToTextureSpace = vectorSpaceToTextureSpace;
|
|
1552
|
+
exports.voronoiCells = voronoiCells;
|