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