@hello-terrain/three 0.0.0-alpha.4 → 0.0.0-alpha.6
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 +651 -123
- package/dist/index.d.cts +344 -143
- package/dist/index.d.mts +344 -143
- package/dist/index.d.ts +344 -143
- package/dist/index.mjs +638 -124
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BufferGeometry, BufferAttribute, Vector3 as Vector3$1 } from 'three';
|
|
2
|
-
import { MeshStandardNodeMaterial, InstancedMesh, StorageBufferAttribute, Vector3 } from 'three/webgpu';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageBufferAttribute, Vector3 } from 'three/webgpu';
|
|
3
|
+
import { param, task, graph } from '@hello-terrain/work';
|
|
4
|
+
import { uniform, Fn, float, globalId, int, vec2, uint, workgroupBarrier, If, instanceIndex, min, max, pow, vec3, storage, packHalf2x16, remap, dot, vertexIndex, uv, select, positionLocal, unpackHalf2x16, normalLocal, varyingProperty, mx_noise_float, Loop, mix } from 'three/tsl';
|
|
5
|
+
import { Fn as Fn$1 } from 'three/src/nodes/TSL.js';
|
|
5
6
|
|
|
6
7
|
class TerrainGeometry extends BufferGeometry {
|
|
7
8
|
constructor(innerSegments = 14, extendUV = false) {
|
|
@@ -61,17 +62,19 @@ class TerrainGeometry extends BufferGeometry {
|
|
|
61
62
|
* | / | \ | / | \ |
|
|
62
63
|
* o---o---o---o---o
|
|
63
64
|
*
|
|
64
|
-
* INNER GRID (
|
|
65
|
-
* o---o---o
|
|
66
|
-
* | \ | \ |
|
|
67
|
-
* o---o---o
|
|
68
|
-
* | \ | \ |
|
|
69
|
-
* 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
|
|
70
73
|
*
|
|
71
74
|
* Where o = vertex
|
|
72
75
|
* Each square cell is split into 2 triangles.
|
|
73
76
|
* - Skirt cells (outer ring): diagonal flip based on quadrant for corner correctness
|
|
74
|
-
* - Inner cells:
|
|
77
|
+
* - Inner cells: alternating diagonal via (x+y)%2 to reduce interpolation artifacts
|
|
75
78
|
*
|
|
76
79
|
* Vertex layout (for innerSegments = 2):
|
|
77
80
|
*
|
|
@@ -117,7 +120,7 @@ class TerrainGeometry extends BufferGeometry {
|
|
|
117
120
|
const topHalf = y < mid;
|
|
118
121
|
useDefaultDiagonal = leftHalf && topHalf || !leftHalf && !topHalf;
|
|
119
122
|
} else {
|
|
120
|
-
useDefaultDiagonal =
|
|
123
|
+
useDefaultDiagonal = (x + y) % 2 === 0;
|
|
121
124
|
}
|
|
122
125
|
if (useDefaultDiagonal) {
|
|
123
126
|
indices.push(a, d, b);
|
|
@@ -217,7 +220,7 @@ class TerrainGeometry extends BufferGeometry {
|
|
|
217
220
|
|
|
218
221
|
const defaultTerrainMeshParams = {
|
|
219
222
|
innerTileSegments: 14,
|
|
220
|
-
maxNodes:
|
|
223
|
+
maxNodes: 1024,
|
|
221
224
|
material: new MeshStandardNodeMaterial()
|
|
222
225
|
};
|
|
223
226
|
class TerrainMesh extends InstancedMesh {
|
|
@@ -228,6 +231,7 @@ class TerrainMesh extends InstancedMesh {
|
|
|
228
231
|
const { innerTileSegments, maxNodes, material } = mergedParams;
|
|
229
232
|
const geometry = new TerrainGeometry(innerTileSegments, true);
|
|
230
233
|
super(geometry, material, maxNodes);
|
|
234
|
+
this.frustumCulled = false;
|
|
231
235
|
this._innerTileSegments = innerTileSegments;
|
|
232
236
|
this._maxNodes = maxNodes;
|
|
233
237
|
}
|
|
@@ -244,72 +248,197 @@ class TerrainMesh extends InstancedMesh {
|
|
|
244
248
|
return this._maxNodes;
|
|
245
249
|
}
|
|
246
250
|
set maxNodes(maxNodes) {
|
|
251
|
+
if (!Number.isInteger(maxNodes) || maxNodes < 1) {
|
|
252
|
+
throw new Error(`Invalid maxNodes: ${maxNodes}. Must be a positive integer.`);
|
|
253
|
+
}
|
|
254
|
+
if (maxNodes === this._maxNodes) return;
|
|
255
|
+
const oldMax = this._maxNodes;
|
|
256
|
+
const nextMatrix = new Float32Array(maxNodes * 16);
|
|
257
|
+
const oldMatrixArray = this.instanceMatrix.array;
|
|
258
|
+
nextMatrix.set(oldMatrixArray.subarray(0, Math.min(oldMatrixArray.length, nextMatrix.length)));
|
|
259
|
+
this.instanceMatrix = new InstancedBufferAttribute(nextMatrix, 16);
|
|
260
|
+
if (this.instanceColor) {
|
|
261
|
+
const itemSize = this.instanceColor.itemSize;
|
|
262
|
+
const nextColor = new Float32Array(maxNodes * itemSize);
|
|
263
|
+
const oldColorArray = this.instanceColor.array;
|
|
264
|
+
nextColor.set(oldColorArray.subarray(0, Math.min(oldColorArray.length, nextColor.length)));
|
|
265
|
+
this.instanceColor = new InstancedBufferAttribute(nextColor, itemSize);
|
|
266
|
+
}
|
|
247
267
|
this._maxNodes = maxNodes;
|
|
268
|
+
this.count = Math.min(this.count, maxNodes);
|
|
269
|
+
this.instanceMatrix.needsUpdate = true;
|
|
270
|
+
if (this.instanceColor) this.instanceColor.needsUpdate = true;
|
|
271
|
+
if (maxNodes < oldMax && this.count >= maxNodes) {
|
|
272
|
+
this.count = maxNodes;
|
|
273
|
+
}
|
|
248
274
|
}
|
|
249
275
|
}
|
|
250
276
|
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
277
|
+
const WORKGROUP_X = 16;
|
|
278
|
+
const WORKGROUP_Y = 16;
|
|
279
|
+
function compileComputePipeline(stages, width, bindings) {
|
|
280
|
+
const workgroupSize = [WORKGROUP_X, WORKGROUP_Y, 1];
|
|
281
|
+
const dispatchX = Math.ceil(width / WORKGROUP_X);
|
|
282
|
+
const dispatchY = Math.ceil(width / WORKGROUP_Y);
|
|
283
|
+
const uInstanceCount = uniform(0, "uint");
|
|
284
|
+
const computeShader = Fn(() => {
|
|
285
|
+
const fWidth = float(width);
|
|
286
|
+
const activeIndex = globalId.z;
|
|
287
|
+
const nodeIndex = int(activeIndex).toVar();
|
|
288
|
+
const iWidth = int(width);
|
|
289
|
+
const ix = int(globalId.x);
|
|
290
|
+
const iy = int(globalId.y);
|
|
291
|
+
const texelSize = vec2(1, 1).div(fWidth);
|
|
292
|
+
const localCoordinates = vec2(globalId.x, globalId.y);
|
|
293
|
+
const localUVCoords = localCoordinates.div(fWidth);
|
|
294
|
+
const verticesPerNode = iWidth.mul(iWidth);
|
|
295
|
+
const globalIndex = int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
|
|
296
|
+
const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(uint(activeIndex).lessThan(uInstanceCount)).toVar();
|
|
297
|
+
for (let i = 0; i < stages.length; i++) {
|
|
298
|
+
if (i > 0) {
|
|
299
|
+
workgroupBarrier();
|
|
300
|
+
}
|
|
301
|
+
If(inBounds, () => {
|
|
302
|
+
stages[i](nodeIndex, globalIndex, localUVCoords, localCoordinates, texelSize);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
})().computeKernel(workgroupSize);
|
|
306
|
+
function execute(renderer, instanceCount) {
|
|
307
|
+
uInstanceCount.value = instanceCount;
|
|
308
|
+
renderer.compute(computeShader, [dispatchX, dispatchY, instanceCount]);
|
|
309
|
+
}
|
|
310
|
+
return { execute };
|
|
311
|
+
}
|
|
268
312
|
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
313
|
+
const createElevation = (tile, uniforms, elevationFn) => {
|
|
314
|
+
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
315
|
+
const ix = int(localCoordinates.x);
|
|
316
|
+
const iy = int(localCoordinates.y);
|
|
317
|
+
const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(int(3));
|
|
318
|
+
const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
|
|
319
|
+
const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
|
|
320
|
+
const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
|
|
321
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
322
|
+
return elevationFn({
|
|
323
|
+
worldPosition,
|
|
324
|
+
rootSize,
|
|
325
|
+
rootUV,
|
|
326
|
+
tileOriginVec2: tile.tileOriginVec2(nodeIndex),
|
|
327
|
+
tileSize: tile.tileSize(nodeIndex),
|
|
328
|
+
tileLevel: tile.tileLevel(nodeIndex),
|
|
329
|
+
nodeIndex: int(nodeIndex),
|
|
330
|
+
tileUV
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount, positionLocal) => Fn(() => {
|
|
335
|
+
const nodeIndex = int(instanceIndex);
|
|
336
|
+
const intEdge = int(edgeVertexCount);
|
|
337
|
+
const innerSegments = int(edgeVertexCount).sub(3);
|
|
338
|
+
const fInnerSegments = float(innerSegments);
|
|
339
|
+
const last = intEdge.sub(int(1));
|
|
340
|
+
const u = positionLocal.x.add(float(0.5));
|
|
341
|
+
const v = positionLocal.z.add(float(0.5));
|
|
342
|
+
const x = u.mul(fInnerSegments).round().toInt().add(int(1));
|
|
343
|
+
const y = v.mul(fInnerSegments).round().toInt().add(int(1));
|
|
344
|
+
const xClamped = min(max(x, int(0)), last);
|
|
345
|
+
const yClamped = min(max(y, int(0)), last);
|
|
346
|
+
const verticesPerNode = intEdge.mul(intEdge);
|
|
347
|
+
const perNodeVertexIndex = yClamped.mul(intEdge).add(xClamped);
|
|
348
|
+
const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(perNodeVertexIndex);
|
|
349
|
+
return elevationFieldBuffer.element(globalVertexIndex);
|
|
287
350
|
});
|
|
288
351
|
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
352
|
+
function createTileCompute(leafStorage, uniforms) {
|
|
353
|
+
const tileLevel = Fn(([nodeIndex]) => {
|
|
354
|
+
const nodeOffset = nodeIndex.mul(int(4));
|
|
355
|
+
return leafStorage.node.element(nodeOffset).toInt();
|
|
356
|
+
});
|
|
357
|
+
const tileOriginVec2 = Fn(([nodeIndex]) => {
|
|
293
358
|
const nodeOffset = nodeIndex.mul(int(4));
|
|
294
|
-
const nodeLevel = leafStorage.node.element(nodeOffset).toInt();
|
|
295
359
|
const nodeX = leafStorage.node.element(nodeOffset.add(int(1))).toFloat();
|
|
296
360
|
const nodeY = leafStorage.node.element(nodeOffset.add(int(2))).toFloat();
|
|
297
|
-
|
|
298
|
-
|
|
361
|
+
return vec2(nodeX, nodeY);
|
|
362
|
+
});
|
|
363
|
+
const tileSize = Fn(([nodeIndex]) => {
|
|
364
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
365
|
+
const level = tileLevel(nodeIndex);
|
|
366
|
+
return float(rootSize).div(pow(float(2), level.toFloat()));
|
|
367
|
+
});
|
|
368
|
+
const rootUVCompute = Fn(([nodeIndex, ix, iy]) => {
|
|
369
|
+
const nodeVec2 = tileOriginVec2(nodeIndex);
|
|
370
|
+
const nodeX = nodeVec2.x;
|
|
371
|
+
const nodeY = nodeVec2.y;
|
|
372
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
373
|
+
const rootOrigin = uniforms.uRootOrigin.toVar();
|
|
374
|
+
const size = tileSize(nodeIndex);
|
|
299
375
|
const half = float(0.5);
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
376
|
+
const halfRoot = float(rootSize).mul(half);
|
|
377
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
378
|
+
const texelSpacing = size.div(fInnerSegments);
|
|
379
|
+
const absX = nodeX.mul(fInnerSegments).add(int(ix).toFloat().sub(float(1)));
|
|
380
|
+
const absY = nodeY.mul(fInnerSegments).add(int(iy).toFloat().sub(float(1)));
|
|
381
|
+
const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
|
|
382
|
+
const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
|
|
383
|
+
const centeredX = worldX.sub(rootOrigin.x);
|
|
384
|
+
const centeredZ = worldZ.sub(rootOrigin.z);
|
|
385
|
+
return vec2(
|
|
386
|
+
centeredX.div(rootSize).add(half),
|
|
387
|
+
centeredZ.div(rootSize).mul(float(-1)).add(half)
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
const tileVertexWorldPositionCompute = Fn(
|
|
391
|
+
([nodeIndex, ix, iy]) => {
|
|
392
|
+
const rootOrigin = uniforms.uRootOrigin.toVar();
|
|
393
|
+
const nodeVec2 = tileOriginVec2(nodeIndex);
|
|
394
|
+
const nodeX = nodeVec2.x;
|
|
395
|
+
const nodeY = nodeVec2.y;
|
|
396
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
397
|
+
const size = tileSize(nodeIndex);
|
|
398
|
+
const half = float(0.5);
|
|
399
|
+
const halfRoot = float(rootSize).mul(half);
|
|
400
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
401
|
+
const texelSpacing = size.div(fInnerSegments);
|
|
402
|
+
const absX = nodeX.mul(fInnerSegments).add(int(ix).toFloat().sub(float(1)));
|
|
403
|
+
const absY = nodeY.mul(fInnerSegments).add(int(iy).toFloat().sub(float(1)));
|
|
404
|
+
const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
|
|
405
|
+
const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
|
|
406
|
+
return vec3(worldX, rootOrigin.y, worldZ);
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
return {
|
|
410
|
+
tileLevel,
|
|
411
|
+
tileOriginVec2,
|
|
412
|
+
tileSize,
|
|
413
|
+
rootUVCompute,
|
|
414
|
+
tileVertexWorldPositionCompute
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const rootSize = param(256).displayName("rootSize");
|
|
419
|
+
const origin = param({
|
|
420
|
+
x: 0,
|
|
421
|
+
y: 0,
|
|
422
|
+
z: 0
|
|
423
|
+
}).displayName("origin");
|
|
424
|
+
const innerTileSegments = param(13).displayName("innerTileSegments");
|
|
425
|
+
const skirtScale = param(100).displayName("skirtScale");
|
|
426
|
+
const elevationScale = param(1).displayName("elevationScale");
|
|
427
|
+
const maxNodes = param(1024).displayName("maxNodes");
|
|
428
|
+
const maxLevel = param(16).displayName("maxLevel");
|
|
429
|
+
const quadtreeUpdate = param({
|
|
430
|
+
cameraOrigin: { x: 0, y: 0, z: 0 },
|
|
431
|
+
mode: "distance",
|
|
432
|
+
distanceFactor: 1.5
|
|
433
|
+
}).displayName("quadtreeUpdate");
|
|
434
|
+
const surface = param(null).displayName("surface");
|
|
435
|
+
const elevationFn = param(() => float(0));
|
|
436
|
+
|
|
437
|
+
function createLeafStorage(maxNodes) {
|
|
438
|
+
const data = new Int32Array(maxNodes * 4);
|
|
439
|
+
const attribute = new StorageBufferAttribute(data, 4);
|
|
440
|
+
const node = storage(attribute, "i32", 1).toReadOnly().setName("leafStorage");
|
|
441
|
+
return { data, attribute, node };
|
|
313
442
|
}
|
|
314
443
|
|
|
315
444
|
const Dir = {
|
|
@@ -325,8 +454,8 @@ function allocLeafSet(capacity) {
|
|
|
325
454
|
count: 0,
|
|
326
455
|
space: new Uint8Array(capacity),
|
|
327
456
|
level: new Uint8Array(capacity),
|
|
328
|
-
x: new
|
|
329
|
-
y: new
|
|
457
|
+
x: new Int32Array(capacity),
|
|
458
|
+
y: new Int32Array(capacity)
|
|
330
459
|
};
|
|
331
460
|
}
|
|
332
461
|
function resetLeafSet(leaves) {
|
|
@@ -352,8 +481,8 @@ function createNodeStore(maxNodes, spaceCount) {
|
|
|
352
481
|
gen: new Uint16Array(maxNodes),
|
|
353
482
|
space: new Uint8Array(maxNodes),
|
|
354
483
|
level: new Uint8Array(maxNodes),
|
|
355
|
-
x: new
|
|
356
|
-
y: new
|
|
484
|
+
x: new Int32Array(maxNodes),
|
|
485
|
+
y: new Int32Array(maxNodes),
|
|
357
486
|
firstChild: new Uint32Array(maxNodes),
|
|
358
487
|
flags: new Uint8Array(maxNodes),
|
|
359
488
|
roots: new Uint32Array(spaceCount)
|
|
@@ -374,8 +503,8 @@ function allocNode(store, tile) {
|
|
|
374
503
|
store.gen[id] = store.currentGen;
|
|
375
504
|
store.space[id] = tile.space;
|
|
376
505
|
store.level[id] = tile.level;
|
|
377
|
-
store.x[id] = tile.x
|
|
378
|
-
store.y[id] = tile.y
|
|
506
|
+
store.x[id] = tile.x;
|
|
507
|
+
store.y[id] = tile.y;
|
|
379
508
|
store.firstChild[id] = U32_EMPTY;
|
|
380
509
|
store.flags[id] = 0;
|
|
381
510
|
return id;
|
|
@@ -490,6 +619,10 @@ function buildLeafIndex(leaves, out) {
|
|
|
490
619
|
|
|
491
620
|
function createState(cfg, surface) {
|
|
492
621
|
const store = createNodeStore(cfg.maxNodes, surface.spaceCount);
|
|
622
|
+
const scratchRootTiles = [];
|
|
623
|
+
for (let i = 0; i < surface.maxRootCount; i++) {
|
|
624
|
+
scratchRootTiles.push({ space: 0, level: 0, x: 0, y: 0 });
|
|
625
|
+
}
|
|
493
626
|
return {
|
|
494
627
|
cfg,
|
|
495
628
|
store,
|
|
@@ -497,28 +630,42 @@ function createState(cfg, surface) {
|
|
|
497
630
|
leafNodeIds: new Uint32Array(cfg.maxNodes),
|
|
498
631
|
leafIndex: createSpatialIndex(cfg.maxNodes),
|
|
499
632
|
stack: new Uint32Array(cfg.maxNodes),
|
|
633
|
+
rootNodeIds: new Uint32Array(surface.maxRootCount),
|
|
634
|
+
rootCount: 0,
|
|
500
635
|
splitQueue: new Uint32Array(cfg.maxNodes),
|
|
501
636
|
splitStamp: new Uint16Array(cfg.maxNodes),
|
|
502
637
|
splitGen: 1,
|
|
503
638
|
scratchTile: { space: 0, level: 0, x: 0, y: 0 },
|
|
504
639
|
scratchNeighbor: { space: 0, level: 0, x: 0, y: 0 },
|
|
505
640
|
scratchBounds: { cx: 0, cy: 0, cz: 0, r: 0 },
|
|
641
|
+
scratchRootTiles,
|
|
506
642
|
spaceCount: surface.spaceCount
|
|
507
643
|
};
|
|
508
644
|
}
|
|
509
|
-
function beginUpdate(state, surface) {
|
|
645
|
+
function beginUpdate(state, surface, params) {
|
|
510
646
|
if (surface.spaceCount !== state.spaceCount) {
|
|
511
647
|
throw new Error(
|
|
512
648
|
`Surface spaceCount changed (${state.spaceCount} -> ${surface.spaceCount}). Create a new quadtree state.`
|
|
513
649
|
);
|
|
514
650
|
}
|
|
651
|
+
if (surface.maxRootCount !== state.rootNodeIds.length) {
|
|
652
|
+
throw new Error(
|
|
653
|
+
`Surface maxRootCount changed (${state.rootNodeIds.length} -> ${surface.maxRootCount}). Create a new quadtree state.`
|
|
654
|
+
);
|
|
655
|
+
}
|
|
515
656
|
beginFrame(state.store);
|
|
516
|
-
|
|
517
|
-
|
|
657
|
+
state.rootCount = 0;
|
|
658
|
+
const rootCount = surface.rootTiles(params.cameraOrigin, state.scratchRootTiles);
|
|
659
|
+
if (rootCount < 0 || rootCount > surface.maxRootCount) {
|
|
660
|
+
throw new Error(`Surface returned invalid root count (${rootCount}).`);
|
|
661
|
+
}
|
|
662
|
+
for (let i = 0; i < rootCount; i++) {
|
|
663
|
+
const rootId = allocNode(state.store, state.scratchRootTiles[i]);
|
|
518
664
|
if (rootId === U32_EMPTY) {
|
|
519
665
|
throw new Error("Failed to allocate root node (maxNodes too small).");
|
|
520
666
|
}
|
|
521
|
-
state.
|
|
667
|
+
state.rootNodeIds[i] = rootId;
|
|
668
|
+
state.rootCount = i + 1;
|
|
522
669
|
}
|
|
523
670
|
}
|
|
524
671
|
|
|
@@ -553,8 +700,8 @@ function refineLeaves(state, surface, params, outLeaves) {
|
|
|
553
700
|
const store = state.store;
|
|
554
701
|
const stack = state.stack;
|
|
555
702
|
let sp = 0;
|
|
556
|
-
for (let
|
|
557
|
-
stack[sp++] =
|
|
703
|
+
for (let i = 0; i < state.rootCount; i++) {
|
|
704
|
+
stack[sp++] = state.rootNodeIds[i];
|
|
558
705
|
}
|
|
559
706
|
while (sp > 0) {
|
|
560
707
|
const nodeId = stack[--sp];
|
|
@@ -667,7 +814,7 @@ function balance2to1(state, surface, params, leaves) {
|
|
|
667
814
|
}
|
|
668
815
|
|
|
669
816
|
function update(state, surface, params, outLeaves) {
|
|
670
|
-
beginUpdate(state, surface);
|
|
817
|
+
beginUpdate(state, surface, params);
|
|
671
818
|
const leaves = refineLeaves(state, surface, params, outLeaves);
|
|
672
819
|
return balance2to1(state, surface, params, leaves);
|
|
673
820
|
}
|
|
@@ -771,6 +918,7 @@ function createFlatSurface(cfg) {
|
|
|
771
918
|
const maxHeight = cfg.maxHeight ?? 0;
|
|
772
919
|
const surface = {
|
|
773
920
|
spaceCount: 1,
|
|
921
|
+
maxRootCount: 1,
|
|
774
922
|
neighborSameLevel(tile, dir, out) {
|
|
775
923
|
const level = tile.level;
|
|
776
924
|
const x = tile.x;
|
|
@@ -796,8 +944,8 @@ function createFlatSurface(cfg) {
|
|
|
796
944
|
if (nx > maxCoord || ny > maxCoord) return false;
|
|
797
945
|
out.space = 0;
|
|
798
946
|
out.level = level;
|
|
799
|
-
out.x = nx
|
|
800
|
-
out.y = ny
|
|
947
|
+
out.x = nx;
|
|
948
|
+
out.y = ny;
|
|
801
949
|
return true;
|
|
802
950
|
},
|
|
803
951
|
tileBounds(tile, cameraOrigin, out) {
|
|
@@ -813,14 +961,87 @@ function createFlatSurface(cfg) {
|
|
|
813
961
|
out.cy = centerY - cameraOrigin.y;
|
|
814
962
|
out.cz = centerZ - cameraOrigin.z;
|
|
815
963
|
out.r = 0.7071067811865476 * size + maxHeight;
|
|
964
|
+
},
|
|
965
|
+
rootTiles(_cameraOrigin, out) {
|
|
966
|
+
const root = out[0];
|
|
967
|
+
root.space = 0;
|
|
968
|
+
root.level = 0;
|
|
969
|
+
root.x = 0;
|
|
970
|
+
root.y = 0;
|
|
971
|
+
return 1;
|
|
816
972
|
}
|
|
817
973
|
};
|
|
818
974
|
return surface;
|
|
819
975
|
}
|
|
820
976
|
|
|
977
|
+
function createInfiniteFlatSurface(cfg) {
|
|
978
|
+
const halfRoot = 0.5 * cfg.rootSize;
|
|
979
|
+
const maxHeight = cfg.maxHeight ?? 0;
|
|
980
|
+
const rootGridRadius = Math.max(0, Math.floor(cfg.rootGridRadius ?? 1));
|
|
981
|
+
const rootWidth = rootGridRadius * 2 + 1;
|
|
982
|
+
return {
|
|
983
|
+
spaceCount: 1,
|
|
984
|
+
maxRootCount: rootWidth * rootWidth,
|
|
985
|
+
neighborSameLevel(tile, dir, out) {
|
|
986
|
+
let nx = tile.x;
|
|
987
|
+
let ny = tile.y;
|
|
988
|
+
switch (dir) {
|
|
989
|
+
case Dir.LEFT:
|
|
990
|
+
nx = tile.x - 1;
|
|
991
|
+
break;
|
|
992
|
+
case Dir.RIGHT:
|
|
993
|
+
nx = tile.x + 1;
|
|
994
|
+
break;
|
|
995
|
+
case Dir.TOP:
|
|
996
|
+
ny = tile.y - 1;
|
|
997
|
+
break;
|
|
998
|
+
case Dir.BOTTOM:
|
|
999
|
+
ny = tile.y + 1;
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
out.space = tile.space;
|
|
1003
|
+
out.level = tile.level;
|
|
1004
|
+
out.x = nx;
|
|
1005
|
+
out.y = ny;
|
|
1006
|
+
return true;
|
|
1007
|
+
},
|
|
1008
|
+
tileBounds(tile, cameraOrigin, out) {
|
|
1009
|
+
const level = tile.level;
|
|
1010
|
+
const scale = 1 / (1 << level);
|
|
1011
|
+
const size = cfg.rootSize * scale;
|
|
1012
|
+
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
1013
|
+
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
1014
|
+
const centerX = minX + 0.5 * size;
|
|
1015
|
+
const centerY = cfg.origin.y;
|
|
1016
|
+
const centerZ = minZ + 0.5 * size;
|
|
1017
|
+
out.cx = centerX - cameraOrigin.x;
|
|
1018
|
+
out.cy = centerY - cameraOrigin.y;
|
|
1019
|
+
out.cz = centerZ - cameraOrigin.z;
|
|
1020
|
+
out.r = 0.7071067811865476 * size + maxHeight;
|
|
1021
|
+
},
|
|
1022
|
+
rootTiles(cameraOrigin, out) {
|
|
1023
|
+
const camRootX = Math.floor((cameraOrigin.x - cfg.origin.x + halfRoot) / cfg.rootSize);
|
|
1024
|
+
const camRootY = Math.floor((cameraOrigin.z - cfg.origin.z + halfRoot) / cfg.rootSize);
|
|
1025
|
+
let index = 0;
|
|
1026
|
+
for (let dy = -rootGridRadius; dy <= rootGridRadius; dy++) {
|
|
1027
|
+
for (let dx = -rootGridRadius; dx <= rootGridRadius; dx++) {
|
|
1028
|
+
const root = out[index];
|
|
1029
|
+
root.space = 0;
|
|
1030
|
+
root.level = 0;
|
|
1031
|
+
root.x = camRootX + dx;
|
|
1032
|
+
root.y = camRootY + dy;
|
|
1033
|
+
index++;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return index;
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
821
1041
|
function createCubeSphereSurface(_cfg) {
|
|
822
1042
|
return {
|
|
823
1043
|
spaceCount: 6,
|
|
1044
|
+
maxRootCount: 6,
|
|
824
1045
|
neighborSameLevel(_tile, _dir, _out) {
|
|
825
1046
|
return false;
|
|
826
1047
|
},
|
|
@@ -829,41 +1050,38 @@ function createCubeSphereSurface(_cfg) {
|
|
|
829
1050
|
out.cy = 0;
|
|
830
1051
|
out.cz = 0;
|
|
831
1052
|
out.r = Number.MAX_VALUE;
|
|
1053
|
+
},
|
|
1054
|
+
rootTiles(_cameraOrigin, out) {
|
|
1055
|
+
for (let s = 0; s < 6; s++) {
|
|
1056
|
+
const root = out[s];
|
|
1057
|
+
root.space = s;
|
|
1058
|
+
root.level = 0;
|
|
1059
|
+
root.x = 0;
|
|
1060
|
+
root.y = 0;
|
|
1061
|
+
}
|
|
1062
|
+
return 6;
|
|
832
1063
|
}
|
|
833
1064
|
};
|
|
834
1065
|
}
|
|
835
1066
|
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
const rootSize = param(256).displayName("rootSize");
|
|
839
|
-
const origin = param({ x: 0, y: 0, z: 0 }).displayName(
|
|
840
|
-
"origin"
|
|
841
|
-
);
|
|
842
|
-
const innerTileSegments = param(14).displayName("innerTileSegments");
|
|
843
|
-
const skirtScale = param(100).displayName("skirtScale");
|
|
844
|
-
const heightmapScale = param(1).displayName("heightmapScale");
|
|
845
|
-
const maxNodes = param(1028).displayName("maxNodes");
|
|
846
|
-
const maxLevel = param(16).displayName("maxLevel");
|
|
847
|
-
const quadtreeUpdate = param({
|
|
848
|
-
cameraOrigin: { x: 0, y: 0, z: 0 },
|
|
849
|
-
mode: "distance",
|
|
850
|
-
distanceFactor: 1.5
|
|
851
|
-
}).displayName("quadtreeUpdate");
|
|
852
|
-
|
|
853
|
-
const quadtreeConfigTask = task((get, work) => {
|
|
1067
|
+
const surfaceTask = task((get, work) => {
|
|
1068
|
+
const customSurface = get(surface);
|
|
854
1069
|
const rootSizeVal = get(rootSize);
|
|
855
1070
|
const originVal = get(origin);
|
|
1071
|
+
return work(() => {
|
|
1072
|
+
if (customSurface) return customSurface;
|
|
1073
|
+
return createFlatSurface({ rootSize: rootSizeVal, origin: originVal });
|
|
1074
|
+
});
|
|
1075
|
+
}).displayName("surfaceTask");
|
|
1076
|
+
const quadtreeConfigTask = task((get, work) => {
|
|
1077
|
+
const surfaceVal = get(surfaceTask);
|
|
856
1078
|
const maxNodesVal = get(maxNodes);
|
|
857
1079
|
const maxLevelVal = get(maxLevel);
|
|
858
1080
|
return work(() => {
|
|
859
|
-
const
|
|
860
|
-
rootSize: rootSizeVal,
|
|
861
|
-
origin: originVal
|
|
862
|
-
});
|
|
863
|
-
const state = createState({ maxNodes: maxNodesVal, maxLevel: maxLevelVal }, surface);
|
|
1081
|
+
const state = createState({ maxNodes: maxNodesVal, maxLevel: maxLevelVal }, surfaceVal);
|
|
864
1082
|
return {
|
|
865
1083
|
state,
|
|
866
|
-
surface
|
|
1084
|
+
surface: surfaceVal
|
|
867
1085
|
};
|
|
868
1086
|
});
|
|
869
1087
|
}).displayName("quadtreeConfigTask");
|
|
@@ -883,12 +1101,7 @@ const quadtreeUpdateTask = task((get, work) => {
|
|
|
883
1101
|
}).displayName("quadtreeUpdateTask");
|
|
884
1102
|
const leafStorageTask = task((get, work) => {
|
|
885
1103
|
const maxNodesVal = get(maxNodes);
|
|
886
|
-
return work(() =>
|
|
887
|
-
const data = new Int32Array(maxNodesVal * 4);
|
|
888
|
-
const attribute = new StorageBufferAttribute(data, 4);
|
|
889
|
-
const node = storage(attribute, "i32", 1).toReadOnly();
|
|
890
|
-
return { data, attribute, node };
|
|
891
|
-
});
|
|
1104
|
+
return work(() => createLeafStorage(maxNodesVal));
|
|
892
1105
|
}).displayName("leafStorageTask");
|
|
893
1106
|
const leafGpuBufferTask = task((get, work) => {
|
|
894
1107
|
const leafSet = get(quadtreeUpdateTask);
|
|
@@ -914,6 +1127,23 @@ const leafGpuBufferTask = task((get, work) => {
|
|
|
914
1127
|
});
|
|
915
1128
|
}).displayName("leafGpuBufferTask");
|
|
916
1129
|
|
|
1130
|
+
function createElevationFunction(callback) {
|
|
1131
|
+
const tslFunction = (args) => {
|
|
1132
|
+
const params = {
|
|
1133
|
+
worldPosition: args.worldPosition,
|
|
1134
|
+
rootSize: args.rootSize,
|
|
1135
|
+
rootUV: args.rootUV,
|
|
1136
|
+
tileUV: args.tileUV,
|
|
1137
|
+
tileLevel: args.tileLevel,
|
|
1138
|
+
tileSize: args.tileSize,
|
|
1139
|
+
tileOriginVec2: args.tileOriginVec2,
|
|
1140
|
+
nodeIndex: args.nodeIndex
|
|
1141
|
+
};
|
|
1142
|
+
return callback(params);
|
|
1143
|
+
};
|
|
1144
|
+
return Fn$1(tslFunction);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
917
1147
|
function createTerrainUniforms(params) {
|
|
918
1148
|
const sanitizedId = params.instanceId?.replace(/-/g, "_");
|
|
919
1149
|
const suffix = sanitizedId ? `_${sanitizedId}` : "";
|
|
@@ -925,16 +1155,18 @@ function createTerrainUniforms(params) {
|
|
|
925
1155
|
`uInnerTileSegments${suffix}`
|
|
926
1156
|
);
|
|
927
1157
|
const uSkirtScale = uniform(float(params.skirtScale)).setName(`uSkirtScale${suffix}`);
|
|
928
|
-
const
|
|
1158
|
+
const uElevationScale = uniform(float(params.elevationScale)).setName(`uElevationScale${suffix}`);
|
|
929
1159
|
return {
|
|
930
1160
|
uRootOrigin,
|
|
931
1161
|
uRootSize,
|
|
932
1162
|
uInnerTileSegments,
|
|
933
1163
|
uSkirtScale,
|
|
934
|
-
|
|
1164
|
+
uElevationScale
|
|
935
1165
|
};
|
|
936
1166
|
}
|
|
937
1167
|
|
|
1168
|
+
const instanceIdTask = task(() => crypto.randomUUID()).displayName("instanceIdTask").cache("once");
|
|
1169
|
+
|
|
938
1170
|
const scratchVector3 = new Vector3$1();
|
|
939
1171
|
const createUniformsTask = task((get, work) => {
|
|
940
1172
|
const uniformParams = {
|
|
@@ -942,18 +1174,18 @@ const createUniformsTask = task((get, work) => {
|
|
|
942
1174
|
rootSize: get(rootSize),
|
|
943
1175
|
innerTileSegments: get(innerTileSegments),
|
|
944
1176
|
skirtScale: get(skirtScale),
|
|
945
|
-
|
|
1177
|
+
elevationScale: get(elevationScale),
|
|
946
1178
|
instanceId: get(instanceIdTask)
|
|
947
1179
|
};
|
|
948
1180
|
return work(() => createTerrainUniforms(uniformParams));
|
|
949
|
-
}).displayName("
|
|
1181
|
+
}).displayName("createUniformsTask").cache("once");
|
|
950
1182
|
const updateUniformsTask = task((get, work) => {
|
|
951
1183
|
const terrainUniformsContext = get(createUniformsTask);
|
|
952
1184
|
const rootSizeVal = get(rootSize);
|
|
953
1185
|
const rootOrigin = get(origin);
|
|
954
1186
|
const innerTileSegmentsVal = get(innerTileSegments);
|
|
955
1187
|
const skirtScaleVal = get(skirtScale);
|
|
956
|
-
const
|
|
1188
|
+
const elevationScaleVal = get(elevationScale);
|
|
957
1189
|
return work(() => {
|
|
958
1190
|
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
959
1191
|
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
@@ -963,29 +1195,311 @@ const updateUniformsTask = task((get, work) => {
|
|
|
963
1195
|
);
|
|
964
1196
|
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
965
1197
|
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
966
|
-
terrainUniformsContext.
|
|
1198
|
+
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
967
1199
|
return terrainUniformsContext;
|
|
968
1200
|
});
|
|
969
|
-
}).displayName("
|
|
1201
|
+
}).displayName("updateUniformsTask");
|
|
1202
|
+
|
|
1203
|
+
const createElevationFieldContextTask = task((get, work) => {
|
|
1204
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1205
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
1206
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
1207
|
+
return work(() => {
|
|
1208
|
+
const data = new Float32Array(totalElements);
|
|
1209
|
+
const attribute = new StorageBufferAttribute(data, 1);
|
|
1210
|
+
const node = storage(attribute, "float", totalElements);
|
|
1211
|
+
return {
|
|
1212
|
+
data,
|
|
1213
|
+
attribute,
|
|
1214
|
+
node
|
|
1215
|
+
};
|
|
1216
|
+
});
|
|
1217
|
+
}).displayName("createElevationFieldContextTask");
|
|
1218
|
+
const tileNodesTask = task((get, work) => {
|
|
1219
|
+
const leafStorage = get(leafStorageTask);
|
|
1220
|
+
const uniforms = get(createUniformsTask);
|
|
1221
|
+
return work(() => {
|
|
1222
|
+
return createTileCompute(leafStorage, uniforms);
|
|
1223
|
+
});
|
|
1224
|
+
}).displayName("tileNodesTask");
|
|
1225
|
+
const elevationFieldStageTask = task((get, work) => {
|
|
1226
|
+
const tile = get(tileNodesTask);
|
|
1227
|
+
const uniforms = get(createUniformsTask);
|
|
1228
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1229
|
+
const userElevationFn = get(elevationFn);
|
|
1230
|
+
return work(() => {
|
|
1231
|
+
const heightFn = createElevationFunction(userElevationFn);
|
|
1232
|
+
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
1233
|
+
return [
|
|
1234
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
1235
|
+
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
1236
|
+
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
1237
|
+
}
|
|
1238
|
+
];
|
|
1239
|
+
});
|
|
1240
|
+
}).displayName("elevationFieldStageTask");
|
|
1241
|
+
|
|
1242
|
+
const createNormalFieldContextTask = task((get, work) => {
|
|
1243
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1244
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
1245
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
1246
|
+
return work(() => {
|
|
1247
|
+
const data = new Uint32Array(totalElements);
|
|
1248
|
+
const attribute = new StorageBufferAttribute(data, 1);
|
|
1249
|
+
const node = storage(attribute, "uint", totalElements);
|
|
1250
|
+
return {
|
|
1251
|
+
data,
|
|
1252
|
+
attribute,
|
|
1253
|
+
node
|
|
1254
|
+
};
|
|
1255
|
+
});
|
|
1256
|
+
}).displayName("createNormalFieldContextTask");
|
|
1257
|
+
function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1258
|
+
return Fn(
|
|
1259
|
+
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
1260
|
+
const iEdge = int(edgeVertexCount);
|
|
1261
|
+
const verticesPerNode = iEdge.mul(iEdge);
|
|
1262
|
+
const baseOffset = int(nodeIndex).mul(verticesPerNode);
|
|
1263
|
+
const xLeft = int(ix).sub(int(1));
|
|
1264
|
+
const xRight = int(ix).add(int(1));
|
|
1265
|
+
const yUp = int(iy).sub(int(1));
|
|
1266
|
+
const yDown = int(iy).add(int(1));
|
|
1267
|
+
const hLeft = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
1268
|
+
const hRight = elevationFieldNode.element(baseOffset.add(int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
1269
|
+
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
1270
|
+
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(int(ix)))).mul(elevationScale);
|
|
1271
|
+
const innerSegments = float(iEdge).sub(float(3));
|
|
1272
|
+
const stepWorld = tileSize.div(innerSegments);
|
|
1273
|
+
const inv2Step = float(0.5).div(stepWorld);
|
|
1274
|
+
const dhdx = float(hRight).sub(float(hLeft)).mul(inv2Step);
|
|
1275
|
+
const dhdz = float(hDown).sub(float(hUp)).mul(inv2Step);
|
|
1276
|
+
const normal = vec3(dhdx.negate(), float(1), dhdz.negate()).normalize();
|
|
1277
|
+
return vec2(normal.x, normal.z);
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
const normalFieldStageTask = task((get, work) => {
|
|
1282
|
+
const upstream = get(elevationFieldStageTask);
|
|
1283
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1284
|
+
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1285
|
+
const tileEdgeVertexCount = get(innerTileSegments) + 3;
|
|
1286
|
+
const tile = get(tileNodesTask);
|
|
1287
|
+
const uniforms = get(createUniformsTask);
|
|
1288
|
+
return work(() => {
|
|
1289
|
+
const computeNormal = createNormalFromElevationField(
|
|
1290
|
+
elevationFieldContext.node,
|
|
1291
|
+
tileEdgeVertexCount
|
|
1292
|
+
);
|
|
1293
|
+
return [
|
|
1294
|
+
...upstream,
|
|
1295
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
1296
|
+
const ix = int(localCoordinates.x);
|
|
1297
|
+
const iy = int(localCoordinates.y);
|
|
1298
|
+
const tileSize = tile.tileSize(nodeIndex);
|
|
1299
|
+
const normalXZ = computeNormal(
|
|
1300
|
+
nodeIndex,
|
|
1301
|
+
tileSize,
|
|
1302
|
+
ix,
|
|
1303
|
+
iy,
|
|
1304
|
+
uniforms.uElevationScale
|
|
1305
|
+
);
|
|
1306
|
+
normalFieldContext.node.element(globalVertexIndex).assign(packHalf2x16(normalXZ));
|
|
1307
|
+
}
|
|
1308
|
+
];
|
|
1309
|
+
});
|
|
1310
|
+
}).displayName("normalFieldStageTask");
|
|
1311
|
+
|
|
1312
|
+
const compileComputeTask = task((get, work) => {
|
|
1313
|
+
const pipeline = get(normalFieldStageTask);
|
|
1314
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1315
|
+
return work(() => compileComputePipeline(pipeline, edgeVertexCount));
|
|
1316
|
+
}).displayName("compileComputeTask");
|
|
1317
|
+
const executeComputeTask = task((get, work, { resources }) => {
|
|
1318
|
+
const { execute } = get(compileComputeTask);
|
|
1319
|
+
const leafState = get(leafGpuBufferTask);
|
|
1320
|
+
return work(
|
|
1321
|
+
() => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
|
|
1322
|
+
}
|
|
1323
|
+
);
|
|
1324
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
1325
|
+
function createComputePipelineTasks(leafStageTask) {
|
|
1326
|
+
const compile = task((get, work) => {
|
|
1327
|
+
const pipeline = get(leafStageTask);
|
|
1328
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1329
|
+
return work(() => compileComputePipeline(pipeline, edgeVertexCount));
|
|
1330
|
+
}).displayName("compileComputeTask");
|
|
1331
|
+
const execute = task((get, work, { resources }) => {
|
|
1332
|
+
const { execute: run } = get(compile);
|
|
1333
|
+
const leafState = get(leafGpuBufferTask);
|
|
1334
|
+
return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
1335
|
+
});
|
|
1336
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
1337
|
+
return { compile, execute };
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
const textureSpaceToVectorSpace = Fn(([value]) => {
|
|
1341
|
+
return remap(value, float(0), float(1), float(-1), float(1));
|
|
1342
|
+
});
|
|
1343
|
+
const vectorSpaceToTextureSpace = Fn(([value]) => {
|
|
1344
|
+
return remap(value, float(-1), float(1), float(0), float(1));
|
|
1345
|
+
});
|
|
1346
|
+
const blendAngleCorrectedNormals = Fn(([n1, n2]) => {
|
|
1347
|
+
const t = vec3(n1.x, n1.y, n1.z.add(1));
|
|
1348
|
+
const u = vec3(n2.x.negate(), n2.y.negate(), n2.z);
|
|
1349
|
+
const r = t.mul(dot(t, u)).sub(u.mul(t.z)).normalize();
|
|
1350
|
+
return r;
|
|
1351
|
+
});
|
|
1352
|
+
const deriveNormalZ = Fn(([normalXY]) => {
|
|
1353
|
+
const xy = normalXY.toVar();
|
|
1354
|
+
const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
|
|
1355
|
+
return vec3(xy.x, xy.y, z);
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
const isSkirtVertex = Fn(([segments]) => {
|
|
1359
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
1360
|
+
const vIndex = int(vertexIndex);
|
|
1361
|
+
const segmentEdges = int(segmentsNode.add(3));
|
|
1362
|
+
const vx = vIndex.mod(segmentEdges);
|
|
1363
|
+
const vy = vIndex.div(segmentEdges);
|
|
1364
|
+
const last = segmentEdges.sub(int(1));
|
|
1365
|
+
return vx.equal(int(0)).or(vx.equal(last)).or(vy.equal(int(0))).or(vy.equal(last));
|
|
1366
|
+
});
|
|
1367
|
+
const isSkirtUV = Fn(([segments]) => {
|
|
1368
|
+
const segmentsNode = typeof segments === "number" ? int(segments) : segments;
|
|
1369
|
+
const ux = uv().x;
|
|
1370
|
+
const uy = uv().y;
|
|
1371
|
+
const segmentCount = segmentsNode.add(2);
|
|
1372
|
+
const segmentStep = float(1).div(segmentCount);
|
|
1373
|
+
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
1374
|
+
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
1375
|
+
return innerX.and(innerY).not();
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
1379
|
+
return Fn(() => {
|
|
1380
|
+
const nodeIndex = int(instanceIndex);
|
|
1381
|
+
const nodeOffset = nodeIndex.mul(int(4));
|
|
1382
|
+
const nodeLevel = leafStorage.node.element(nodeOffset).toInt();
|
|
1383
|
+
const nodeX = leafStorage.node.element(nodeOffset.add(int(1))).toFloat();
|
|
1384
|
+
const nodeY = leafStorage.node.element(nodeOffset.add(int(2))).toFloat();
|
|
1385
|
+
const rootSize = terrainUniforms.uRootSize.toVar();
|
|
1386
|
+
const rootOrigin = terrainUniforms.uRootOrigin.toVar();
|
|
1387
|
+
const half = float(0.5);
|
|
1388
|
+
const size = rootSize.div(pow(float(2), nodeLevel.toFloat()));
|
|
1389
|
+
const halfRoot = rootSize.mul(half);
|
|
1390
|
+
const centerX = rootOrigin.x.add(nodeX.add(half).mul(size)).sub(halfRoot);
|
|
1391
|
+
const centerZ = rootOrigin.z.add(nodeY.add(half).mul(size)).sub(halfRoot);
|
|
1392
|
+
const clampedX = positionLocal.x.max(half.negate()).min(half);
|
|
1393
|
+
const clampedZ = positionLocal.z.max(half.negate()).min(half);
|
|
1394
|
+
const worldX = centerX.add(clampedX.mul(size));
|
|
1395
|
+
const worldZ = centerZ.add(clampedZ.mul(size));
|
|
1396
|
+
return vec3(worldX, rootOrigin.y, worldZ);
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
function createTileElevation(terrainUniforms, elevationFieldBufferNode) {
|
|
1400
|
+
if (!elevationFieldBufferNode) return float(0);
|
|
1401
|
+
const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
|
|
1402
|
+
return readElevationFieldAtPositionLocal(
|
|
1403
|
+
elevationFieldBufferNode,
|
|
1404
|
+
edgeVertexCount,
|
|
1405
|
+
positionLocal
|
|
1406
|
+
)().mul(
|
|
1407
|
+
terrainUniforms.uElevationScale
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
function createNormalAssignment(terrainUniforms, normalFieldBufferNode) {
|
|
1411
|
+
if (!normalFieldBufferNode) return;
|
|
1412
|
+
const nodeIndex = int(instanceIndex);
|
|
1413
|
+
const intEdge = int(terrainUniforms.uInnerTileSegments.add(3));
|
|
1414
|
+
const verticesPerNode = intEdge.mul(intEdge);
|
|
1415
|
+
const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(int(vertexIndex));
|
|
1416
|
+
const packed = normalFieldBufferNode.element(globalVertexIndex);
|
|
1417
|
+
const normalXZ = unpackHalf2x16(packed);
|
|
1418
|
+
const reconstructed = deriveNormalZ(normalXZ);
|
|
1419
|
+
normalLocal.assign(vec3(reconstructed.x, reconstructed.z, reconstructed.y));
|
|
1420
|
+
}
|
|
1421
|
+
function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBufferNode, normalFieldBufferNode) {
|
|
1422
|
+
const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
|
|
1423
|
+
return Fn(() => {
|
|
1424
|
+
const base = baseWorldPosition();
|
|
1425
|
+
const yElevation = createTileElevation(terrainUniforms, elevationFieldBufferNode);
|
|
1426
|
+
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1427
|
+
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1428
|
+
const worldY = select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
1429
|
+
createNormalAssignment(terrainUniforms, normalFieldBufferNode);
|
|
1430
|
+
return vec3(base.x, worldY, base.z);
|
|
1431
|
+
})();
|
|
1432
|
+
}
|
|
970
1433
|
|
|
971
1434
|
const positionNodeTask = task((get, work) => {
|
|
972
1435
|
const leafStorage = get(leafStorageTask);
|
|
973
1436
|
const terrainUniforms = get(createUniformsTask);
|
|
974
|
-
|
|
975
|
-
|
|
1437
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1438
|
+
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1439
|
+
return work(
|
|
1440
|
+
() => createTileWorldPosition(
|
|
1441
|
+
leafStorage,
|
|
1442
|
+
terrainUniforms,
|
|
1443
|
+
elevationFieldContext.node,
|
|
1444
|
+
normalFieldContext.node
|
|
1445
|
+
)
|
|
1446
|
+
);
|
|
1447
|
+
}).displayName("positionNodeTask");
|
|
976
1448
|
|
|
977
1449
|
function terrainGraph() {
|
|
978
|
-
return graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask);
|
|
1450
|
+
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(createNormalFieldContextTask).add(elevationFieldStageTask).add(normalFieldStageTask).add(compileComputeTask).add(executeComputeTask);
|
|
979
1451
|
}
|
|
980
1452
|
const terrainTasks = {
|
|
981
1453
|
instanceId: instanceIdTask,
|
|
982
1454
|
quadtreeConfig: quadtreeConfigTask,
|
|
983
1455
|
quadtreeUpdate: quadtreeUpdateTask,
|
|
984
1456
|
leafStorage: leafStorageTask,
|
|
1457
|
+
surface: surfaceTask,
|
|
985
1458
|
leafGpuBuffer: leafGpuBufferTask,
|
|
986
1459
|
createUniforms: createUniformsTask,
|
|
987
1460
|
updateUniforms: updateUniformsTask,
|
|
988
|
-
positionNode: positionNodeTask
|
|
1461
|
+
positionNode: positionNodeTask,
|
|
1462
|
+
createElevationFieldContext: createElevationFieldContextTask,
|
|
1463
|
+
createTileNodes: tileNodesTask,
|
|
1464
|
+
createNormalFieldContext: createNormalFieldContextTask,
|
|
1465
|
+
elevationFieldStage: elevationFieldStageTask,
|
|
1466
|
+
normalFieldStage: normalFieldStageTask,
|
|
1467
|
+
compileCompute: compileComputeTask,
|
|
1468
|
+
executeCompute: executeComputeTask
|
|
989
1469
|
};
|
|
990
1470
|
|
|
991
|
-
|
|
1471
|
+
const vGlobalVertexIndex = /* @__PURE__ */ varyingProperty("int", "vGlobalVertexIndex");
|
|
1472
|
+
const vElevation = /* @__PURE__ */ varyingProperty("f32", "vElevation");
|
|
1473
|
+
|
|
1474
|
+
const cellCenter = Fn(({ cell }) => {
|
|
1475
|
+
return cell.add(mx_noise_float(cell.mul(Math.PI)));
|
|
1476
|
+
});
|
|
1477
|
+
const voronoiCells = Fn((params) => {
|
|
1478
|
+
const scale = float(params.scale);
|
|
1479
|
+
const facet = float(params.facet);
|
|
1480
|
+
const seed = float(params.seed);
|
|
1481
|
+
const pos = params.uv.mul(scale).add(seed);
|
|
1482
|
+
const midCell = pos.round().toVar();
|
|
1483
|
+
const minCell = midCell.toVar();
|
|
1484
|
+
const minDist = float(1).toVar();
|
|
1485
|
+
const cell = vec3(0, 0, 0).toVar();
|
|
1486
|
+
const dist = float().toVar();
|
|
1487
|
+
const i = float(0).toVar();
|
|
1488
|
+
Loop(27, () => {
|
|
1489
|
+
const ix = i.mod(3).sub(1);
|
|
1490
|
+
const iy = i.div(3).floor().mod(3).sub(1);
|
|
1491
|
+
const iz = i.div(9).floor().sub(1);
|
|
1492
|
+
cell.assign(midCell.add(vec3(ix, iy, iz)));
|
|
1493
|
+
dist.assign(pos.distance(cellCenter({ cell })).add(mx_noise_float(pos).div(5)));
|
|
1494
|
+
If(dist.lessThan(minDist), () => {
|
|
1495
|
+
minDist.assign(dist);
|
|
1496
|
+
minCell.assign(cell);
|
|
1497
|
+
});
|
|
1498
|
+
i.addAssign(1);
|
|
1499
|
+
});
|
|
1500
|
+
const n = mx_noise_float(minCell.mul(Math.PI)).toVar();
|
|
1501
|
+
const k = mix(minDist, n.add(1).div(2), facet);
|
|
1502
|
+
return k;
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
export { Dir, TerrainGeometry, TerrainMesh, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createNormalFieldContextTask, createSpatialIndex, createState, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, maxLevel, maxNodes, normalFieldStageTask, origin, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, surface, surfaceTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
|