@hello-terrain/three 0.0.0-alpha.3 → 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 +1305 -488
- package/dist/index.d.cts +586 -246
- package/dist/index.d.mts +586 -246
- package/dist/index.d.ts +586 -246
- package/dist/index.mjs +1252 -471
- package/package.json +6 -3
package/dist/index.cjs
CHANGED
|
@@ -1,49 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const three = require('three');
|
|
4
|
+
const webgpu = require('three/webgpu');
|
|
5
|
+
const work = require('@hello-terrain/work');
|
|
4
6
|
const tsl = require('three/tsl');
|
|
7
|
+
const TSL_js = require('three/src/nodes/TSL.js');
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
8
|
-
const n = Object.create(null);
|
|
9
|
-
if (e) {
|
|
10
|
-
for (const k in e) {
|
|
11
|
-
n[k] = e[k];
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
n.default = e;
|
|
15
|
-
return n;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const THREE__namespace = /*#__PURE__*/_interopNamespaceCompat(THREE);
|
|
19
|
-
|
|
20
|
-
class TerrainGeometry extends THREE.BufferGeometry {
|
|
9
|
+
class TerrainGeometry extends three.BufferGeometry {
|
|
21
10
|
constructor(innerSegments = 14, extendUV = false) {
|
|
22
11
|
super();
|
|
23
12
|
if (innerSegments < 1 || !Number.isFinite(innerSegments) || !Number.isInteger(innerSegments)) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
`Invalid innerSegments: ${innerSegments}. Must be a positive integer.`
|
|
26
|
-
);
|
|
13
|
+
throw new Error(`Invalid innerSegments: ${innerSegments}. Must be a positive integer.`);
|
|
27
14
|
}
|
|
28
15
|
try {
|
|
29
16
|
this.setIndex(this.generateIndices(innerSegments));
|
|
30
17
|
this.setAttribute(
|
|
31
18
|
"position",
|
|
32
|
-
new
|
|
33
|
-
new Float32Array(this.generatePositions(innerSegments)),
|
|
34
|
-
3
|
|
35
|
-
)
|
|
19
|
+
new three.BufferAttribute(new Float32Array(this.generatePositions(innerSegments)), 3)
|
|
36
20
|
);
|
|
37
21
|
this.setAttribute(
|
|
38
22
|
"normal",
|
|
39
|
-
new
|
|
40
|
-
new Float32Array(this.generateNormals(innerSegments)),
|
|
41
|
-
3
|
|
42
|
-
)
|
|
23
|
+
new three.BufferAttribute(new Float32Array(this.generateNormals(innerSegments)), 3)
|
|
43
24
|
);
|
|
44
25
|
this.setAttribute(
|
|
45
26
|
"uv",
|
|
46
|
-
new
|
|
27
|
+
new three.BufferAttribute(
|
|
47
28
|
new Float32Array(
|
|
48
29
|
extendUV ? this.generateUvsExtended(innerSegments) : this.generateUvsOnlyInner(innerSegments)
|
|
49
30
|
),
|
|
@@ -229,507 +210,1343 @@ class TerrainGeometry extends THREE.BufferGeometry {
|
|
|
229
210
|
}
|
|
230
211
|
}
|
|
231
212
|
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
249
|
-
return innerX.and(innerY).not();
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
const CHILDREN_STRIDE = 4;
|
|
253
|
-
const NEIGHBORS_STRIDE = 4;
|
|
254
|
-
const NODE_STRIDE = 4;
|
|
255
|
-
const U_INT_16_MAX_VALUE = 65535;
|
|
256
|
-
const EMPTY_SENTINEL_VALUE = U_INT_16_MAX_VALUE;
|
|
257
|
-
class QuadtreeNodeView {
|
|
258
|
-
maxNodeCount;
|
|
259
|
-
childrenIndicesBuffer;
|
|
260
|
-
neighborsIndicesBuffer;
|
|
261
|
-
nodeBuffer;
|
|
262
|
-
leafNodeMask;
|
|
263
|
-
leafNodeCountBuffer;
|
|
264
|
-
activeLeafIndices;
|
|
265
|
-
activeLeafCount = 0;
|
|
266
|
-
constructor(maxNodeCount, childrenIndicesBuffer, neighborsIndicesBuffer, nodeBuffer, leafNodeMask, leafNodeCountBuffer) {
|
|
267
|
-
this.maxNodeCount = maxNodeCount;
|
|
268
|
-
this.childrenIndicesBuffer = childrenIndicesBuffer ?? new Uint16Array(CHILDREN_STRIDE * maxNodeCount);
|
|
269
|
-
this.neighborsIndicesBuffer = neighborsIndicesBuffer ?? new Uint16Array(NEIGHBORS_STRIDE * maxNodeCount);
|
|
270
|
-
this.nodeBuffer = nodeBuffer ?? new Int32Array(NODE_STRIDE * maxNodeCount);
|
|
271
|
-
this.leafNodeMask = leafNodeMask ?? new Uint8Array(maxNodeCount);
|
|
272
|
-
this.leafNodeCountBuffer = leafNodeCountBuffer ?? new Uint16Array(1);
|
|
273
|
-
this.activeLeafIndices = new Uint16Array(maxNodeCount);
|
|
274
|
-
this.clear();
|
|
213
|
+
const defaultTerrainMeshParams = {
|
|
214
|
+
innerTileSegments: 14,
|
|
215
|
+
maxNodes: 1024,
|
|
216
|
+
material: new webgpu.MeshStandardNodeMaterial()
|
|
217
|
+
};
|
|
218
|
+
class TerrainMesh extends webgpu.InstancedMesh {
|
|
219
|
+
_innerTileSegments;
|
|
220
|
+
_maxNodes;
|
|
221
|
+
constructor(params = defaultTerrainMeshParams) {
|
|
222
|
+
const mergedParams = { ...defaultTerrainMeshParams, ...params };
|
|
223
|
+
const { innerTileSegments, maxNodes, material } = mergedParams;
|
|
224
|
+
const geometry = new TerrainGeometry(innerTileSegments, true);
|
|
225
|
+
super(geometry, material, maxNodes);
|
|
226
|
+
this.frustumCulled = false;
|
|
227
|
+
this._innerTileSegments = innerTileSegments;
|
|
228
|
+
this._maxNodes = maxNodes;
|
|
275
229
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
*/
|
|
279
|
-
clear() {
|
|
280
|
-
this.nodeBuffer.fill(0);
|
|
281
|
-
this.childrenIndicesBuffer.fill(EMPTY_SENTINEL_VALUE);
|
|
282
|
-
this.neighborsIndicesBuffer.fill(EMPTY_SENTINEL_VALUE);
|
|
283
|
-
this.leafNodeMask.fill(0);
|
|
284
|
-
this.leafNodeCountBuffer[0] = 0;
|
|
285
|
-
this.activeLeafCount = 0;
|
|
230
|
+
get innerTileSegments() {
|
|
231
|
+
return this._innerTileSegments;
|
|
286
232
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
childrenIndicesBuffer: this.childrenIndicesBuffer,
|
|
293
|
-
neighborsIndicesBuffer: this.neighborsIndicesBuffer,
|
|
294
|
-
nodeBuffer: this.nodeBuffer,
|
|
295
|
-
leafNodeMask: this.leafNodeMask
|
|
296
|
-
};
|
|
233
|
+
set innerTileSegments(tileSegments) {
|
|
234
|
+
const oldGeometry = this.geometry;
|
|
235
|
+
this.geometry = new TerrainGeometry(tileSegments, true);
|
|
236
|
+
this._innerTileSegments = tileSegments;
|
|
237
|
+
setTimeout(oldGeometry.dispose);
|
|
297
238
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
*/
|
|
301
|
-
getMaxNodeCount() {
|
|
302
|
-
return this.maxNodeCount;
|
|
303
|
-
}
|
|
304
|
-
// Getters for individual buffer values
|
|
305
|
-
getLevel(index) {
|
|
306
|
-
return this.nodeBuffer[index * NODE_STRIDE];
|
|
307
|
-
}
|
|
308
|
-
getX(index) {
|
|
309
|
-
return this.nodeBuffer[index * NODE_STRIDE + 1];
|
|
310
|
-
}
|
|
311
|
-
getY(index) {
|
|
312
|
-
return this.nodeBuffer[index * NODE_STRIDE + 2];
|
|
313
|
-
}
|
|
314
|
-
getLeafNodeCount() {
|
|
315
|
-
return this.leafNodeCountBuffer[0];
|
|
316
|
-
}
|
|
317
|
-
getLeaf(index) {
|
|
318
|
-
return this.leafNodeMask[index] === 1;
|
|
319
|
-
}
|
|
320
|
-
getChildren(index) {
|
|
321
|
-
const offset = index * CHILDREN_STRIDE;
|
|
322
|
-
return [
|
|
323
|
-
this.childrenIndicesBuffer[offset],
|
|
324
|
-
this.childrenIndicesBuffer[offset + 1],
|
|
325
|
-
this.childrenIndicesBuffer[offset + 2],
|
|
326
|
-
this.childrenIndicesBuffer[offset + 3]
|
|
327
|
-
];
|
|
239
|
+
get maxNodes() {
|
|
240
|
+
return this._maxNodes;
|
|
328
241
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
this.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
this.leafNodeCountBuffer[0]++;
|
|
353
|
-
this.leafNodeMask[index] = 1;
|
|
354
|
-
this.activeLeafIndices[this.activeLeafCount] = index;
|
|
355
|
-
this.activeLeafCount++;
|
|
356
|
-
this.setChildren(index, [
|
|
357
|
-
EMPTY_SENTINEL_VALUE,
|
|
358
|
-
EMPTY_SENTINEL_VALUE,
|
|
359
|
-
EMPTY_SENTINEL_VALUE,
|
|
360
|
-
EMPTY_SENTINEL_VALUE
|
|
361
|
-
]);
|
|
362
|
-
} else if (!leaf && wasLeaf) {
|
|
363
|
-
this.leafNodeCountBuffer[0]--;
|
|
364
|
-
this.leafNodeMask[index] = 0;
|
|
242
|
+
set maxNodes(maxNodes) {
|
|
243
|
+
if (!Number.isInteger(maxNodes) || maxNodes < 1) {
|
|
244
|
+
throw new Error(`Invalid maxNodes: ${maxNodes}. Must be a positive integer.`);
|
|
245
|
+
}
|
|
246
|
+
if (maxNodes === this._maxNodes) return;
|
|
247
|
+
const oldMax = this._maxNodes;
|
|
248
|
+
const nextMatrix = new Float32Array(maxNodes * 16);
|
|
249
|
+
const oldMatrixArray = this.instanceMatrix.array;
|
|
250
|
+
nextMatrix.set(oldMatrixArray.subarray(0, Math.min(oldMatrixArray.length, nextMatrix.length)));
|
|
251
|
+
this.instanceMatrix = new webgpu.InstancedBufferAttribute(nextMatrix, 16);
|
|
252
|
+
if (this.instanceColor) {
|
|
253
|
+
const itemSize = this.instanceColor.itemSize;
|
|
254
|
+
const nextColor = new Float32Array(maxNodes * itemSize);
|
|
255
|
+
const oldColorArray = this.instanceColor.array;
|
|
256
|
+
nextColor.set(oldColorArray.subarray(0, Math.min(oldColorArray.length, nextColor.length)));
|
|
257
|
+
this.instanceColor = new webgpu.InstancedBufferAttribute(nextColor, itemSize);
|
|
258
|
+
}
|
|
259
|
+
this._maxNodes = maxNodes;
|
|
260
|
+
this.count = Math.min(this.count, maxNodes);
|
|
261
|
+
this.instanceMatrix.needsUpdate = true;
|
|
262
|
+
if (this.instanceColor) this.instanceColor.needsUpdate = true;
|
|
263
|
+
if (maxNodes < oldMax && this.count >= maxNodes) {
|
|
264
|
+
this.count = maxNodes;
|
|
365
265
|
}
|
|
366
|
-
this.nodeBuffer[index * NODE_STRIDE + 3] = newValue;
|
|
367
|
-
}
|
|
368
|
-
setChildren(index, children) {
|
|
369
|
-
const offset = index * CHILDREN_STRIDE;
|
|
370
|
-
this.childrenIndicesBuffer[offset] = children[0];
|
|
371
|
-
this.childrenIndicesBuffer[offset + 1] = children[1];
|
|
372
|
-
this.childrenIndicesBuffer[offset + 2] = children[2];
|
|
373
|
-
this.childrenIndicesBuffer[offset + 3] = children[3];
|
|
374
|
-
}
|
|
375
|
-
setNeighbors(index, neighbors) {
|
|
376
|
-
const offset = index * NEIGHBORS_STRIDE;
|
|
377
|
-
this.neighborsIndicesBuffer[offset] = neighbors[0];
|
|
378
|
-
this.neighborsIndicesBuffer[offset + 1] = neighbors[1];
|
|
379
|
-
this.neighborsIndicesBuffer[offset + 2] = neighbors[2];
|
|
380
|
-
this.neighborsIndicesBuffer[offset + 3] = neighbors[3];
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Get array of active leaf node indices with count (zero-copy, no allocation)
|
|
384
|
-
*/
|
|
385
|
-
getActiveLeafNodeIndices() {
|
|
386
|
-
return {
|
|
387
|
-
indices: this.activeLeafIndices,
|
|
388
|
-
count: this.activeLeafCount
|
|
389
|
-
};
|
|
390
266
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const WORKGROUP_X = 16;
|
|
270
|
+
const WORKGROUP_Y = 16;
|
|
271
|
+
function compileComputePipeline(stages, width, bindings) {
|
|
272
|
+
const workgroupSize = [WORKGROUP_X, WORKGROUP_Y, 1];
|
|
273
|
+
const dispatchX = Math.ceil(width / WORKGROUP_X);
|
|
274
|
+
const dispatchY = Math.ceil(width / WORKGROUP_Y);
|
|
275
|
+
const uInstanceCount = tsl.uniform(0, "uint");
|
|
276
|
+
const computeShader = tsl.Fn(() => {
|
|
277
|
+
const fWidth = tsl.float(width);
|
|
278
|
+
const activeIndex = tsl.globalId.z;
|
|
279
|
+
const nodeIndex = tsl.int(activeIndex).toVar();
|
|
280
|
+
const iWidth = tsl.int(width);
|
|
281
|
+
const ix = tsl.int(tsl.globalId.x);
|
|
282
|
+
const iy = tsl.int(tsl.globalId.y);
|
|
283
|
+
const texelSize = tsl.vec2(1, 1).div(fWidth);
|
|
284
|
+
const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
|
|
285
|
+
const localUVCoords = localCoordinates.div(fWidth);
|
|
286
|
+
const verticesPerNode = iWidth.mul(iWidth);
|
|
287
|
+
const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
|
|
288
|
+
const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
|
|
289
|
+
for (let i = 0; i < stages.length; i++) {
|
|
290
|
+
if (i > 0) {
|
|
291
|
+
tsl.workgroupBarrier();
|
|
292
|
+
}
|
|
293
|
+
tsl.If(inBounds, () => {
|
|
294
|
+
stages[i](nodeIndex, globalIndex, localUVCoords, localCoordinates, texelSize);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
})().computeKernel(workgroupSize);
|
|
298
|
+
function execute(renderer, instanceCount) {
|
|
299
|
+
uInstanceCount.value = instanceCount;
|
|
300
|
+
renderer.compute(computeShader, [dispatchX, dispatchY, instanceCount]);
|
|
401
301
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
302
|
+
return { execute };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const createElevation = (tile, uniforms, elevationFn) => {
|
|
306
|
+
return function perVertexElevation(nodeIndex, localCoordinates) {
|
|
307
|
+
const ix = tsl.int(localCoordinates.x);
|
|
308
|
+
const iy = tsl.int(localCoordinates.y);
|
|
309
|
+
const edgeVertexCount = uniforms.uInnerTileSegments.toVar().add(tsl.int(3));
|
|
310
|
+
const tileUV = localCoordinates.toFloat().div(edgeVertexCount.toFloat());
|
|
311
|
+
const rootUV = tile.rootUVCompute(nodeIndex, ix, iy);
|
|
312
|
+
const worldPosition = tile.tileVertexWorldPositionCompute(nodeIndex, ix, iy).setName("worldPositionWithSkirt");
|
|
313
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
314
|
+
return elevationFn({
|
|
315
|
+
worldPosition,
|
|
316
|
+
rootSize,
|
|
317
|
+
rootUV,
|
|
318
|
+
tileOriginVec2: tile.tileOriginVec2(nodeIndex),
|
|
319
|
+
tileSize: tile.tileSize(nodeIndex),
|
|
320
|
+
tileLevel: tile.tileLevel(nodeIndex),
|
|
321
|
+
nodeIndex: tsl.int(nodeIndex),
|
|
322
|
+
tileUV
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount, positionLocal) => tsl.Fn(() => {
|
|
327
|
+
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
328
|
+
const intEdge = tsl.int(edgeVertexCount);
|
|
329
|
+
const innerSegments = tsl.int(edgeVertexCount).sub(3);
|
|
330
|
+
const fInnerSegments = tsl.float(innerSegments);
|
|
331
|
+
const last = intEdge.sub(tsl.int(1));
|
|
332
|
+
const u = positionLocal.x.add(tsl.float(0.5));
|
|
333
|
+
const v = positionLocal.z.add(tsl.float(0.5));
|
|
334
|
+
const x = u.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
335
|
+
const y = v.mul(fInnerSegments).round().toInt().add(tsl.int(1));
|
|
336
|
+
const xClamped = tsl.min(tsl.max(x, tsl.int(0)), last);
|
|
337
|
+
const yClamped = tsl.min(tsl.max(y, tsl.int(0)), last);
|
|
338
|
+
const verticesPerNode = intEdge.mul(intEdge);
|
|
339
|
+
const perNodeVertexIndex = yClamped.mul(intEdge).add(xClamped);
|
|
340
|
+
const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(perNodeVertexIndex);
|
|
341
|
+
return elevationFieldBuffer.element(globalVertexIndex);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
function createTileCompute(leafStorage, uniforms) {
|
|
345
|
+
const tileLevel = tsl.Fn(([nodeIndex]) => {
|
|
346
|
+
const nodeOffset = nodeIndex.mul(tsl.int(4));
|
|
347
|
+
return leafStorage.node.element(nodeOffset).toInt();
|
|
348
|
+
});
|
|
349
|
+
const tileOriginVec2 = tsl.Fn(([nodeIndex]) => {
|
|
350
|
+
const nodeOffset = nodeIndex.mul(tsl.int(4));
|
|
351
|
+
const nodeX = leafStorage.node.element(nodeOffset.add(tsl.int(1))).toFloat();
|
|
352
|
+
const nodeY = leafStorage.node.element(nodeOffset.add(tsl.int(2))).toFloat();
|
|
353
|
+
return tsl.vec2(nodeX, nodeY);
|
|
354
|
+
});
|
|
355
|
+
const tileSize = tsl.Fn(([nodeIndex]) => {
|
|
356
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
357
|
+
const level = tileLevel(nodeIndex);
|
|
358
|
+
return tsl.float(rootSize).div(tsl.pow(tsl.float(2), level.toFloat()));
|
|
359
|
+
});
|
|
360
|
+
const rootUVCompute = tsl.Fn(([nodeIndex, ix, iy]) => {
|
|
361
|
+
const nodeVec2 = tileOriginVec2(nodeIndex);
|
|
362
|
+
const nodeX = nodeVec2.x;
|
|
363
|
+
const nodeY = nodeVec2.y;
|
|
364
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
365
|
+
const rootOrigin = uniforms.uRootOrigin.toVar();
|
|
366
|
+
const size = tileSize(nodeIndex);
|
|
367
|
+
const half = tsl.float(0.5);
|
|
368
|
+
const halfRoot = tsl.float(rootSize).mul(half);
|
|
369
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
370
|
+
const texelSpacing = size.div(fInnerSegments);
|
|
371
|
+
const absX = nodeX.mul(fInnerSegments).add(tsl.int(ix).toFloat().sub(tsl.float(1)));
|
|
372
|
+
const absY = nodeY.mul(fInnerSegments).add(tsl.int(iy).toFloat().sub(tsl.float(1)));
|
|
373
|
+
const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
|
|
374
|
+
const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
|
|
375
|
+
const centeredX = worldX.sub(rootOrigin.x);
|
|
376
|
+
const centeredZ = worldZ.sub(rootOrigin.z);
|
|
377
|
+
return tsl.vec2(
|
|
378
|
+
centeredX.div(rootSize).add(half),
|
|
379
|
+
centeredZ.div(rootSize).mul(tsl.float(-1)).add(half)
|
|
410
380
|
);
|
|
381
|
+
});
|
|
382
|
+
const tileVertexWorldPositionCompute = tsl.Fn(
|
|
383
|
+
([nodeIndex, ix, iy]) => {
|
|
384
|
+
const rootOrigin = uniforms.uRootOrigin.toVar();
|
|
385
|
+
const nodeVec2 = tileOriginVec2(nodeIndex);
|
|
386
|
+
const nodeX = nodeVec2.x;
|
|
387
|
+
const nodeY = nodeVec2.y;
|
|
388
|
+
const rootSize = uniforms.uRootSize.toVar();
|
|
389
|
+
const size = tileSize(nodeIndex);
|
|
390
|
+
const half = tsl.float(0.5);
|
|
391
|
+
const halfRoot = tsl.float(rootSize).mul(half);
|
|
392
|
+
const fInnerSegments = uniforms.uInnerTileSegments.toVar().toFloat();
|
|
393
|
+
const texelSpacing = size.div(fInnerSegments);
|
|
394
|
+
const absX = nodeX.mul(fInnerSegments).add(tsl.int(ix).toFloat().sub(tsl.float(1)));
|
|
395
|
+
const absY = nodeY.mul(fInnerSegments).add(tsl.int(iy).toFloat().sub(tsl.float(1)));
|
|
396
|
+
const worldX = rootOrigin.x.add(absX.mul(texelSpacing)).sub(halfRoot);
|
|
397
|
+
const worldZ = rootOrigin.z.add(absY.mul(texelSpacing)).sub(halfRoot);
|
|
398
|
+
return tsl.vec3(worldX, rootOrigin.y, worldZ);
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
return {
|
|
402
|
+
tileLevel,
|
|
403
|
+
tileOriginVec2,
|
|
404
|
+
tileSize,
|
|
405
|
+
rootUVCompute,
|
|
406
|
+
tileVertexWorldPositionCompute
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const rootSize = work.param(256).displayName("rootSize");
|
|
411
|
+
const origin = work.param({
|
|
412
|
+
x: 0,
|
|
413
|
+
y: 0,
|
|
414
|
+
z: 0
|
|
415
|
+
}).displayName("origin");
|
|
416
|
+
const innerTileSegments = work.param(13).displayName("innerTileSegments");
|
|
417
|
+
const skirtScale = work.param(100).displayName("skirtScale");
|
|
418
|
+
const elevationScale = work.param(1).displayName("elevationScale");
|
|
419
|
+
const maxNodes = work.param(1024).displayName("maxNodes");
|
|
420
|
+
const maxLevel = work.param(16).displayName("maxLevel");
|
|
421
|
+
const quadtreeUpdate = work.param({
|
|
422
|
+
cameraOrigin: { x: 0, y: 0, z: 0 },
|
|
423
|
+
mode: "distance",
|
|
424
|
+
distanceFactor: 1.5
|
|
425
|
+
}).displayName("quadtreeUpdate");
|
|
426
|
+
const surface = work.param(null).displayName("surface");
|
|
427
|
+
const elevationFn = work.param(() => tsl.float(0));
|
|
428
|
+
|
|
429
|
+
function createLeafStorage(maxNodes) {
|
|
430
|
+
const data = new Int32Array(maxNodes * 4);
|
|
431
|
+
const attribute = new webgpu.StorageBufferAttribute(data, 4);
|
|
432
|
+
const node = tsl.storage(attribute, "i32", 1).toReadOnly().setName("leafStorage");
|
|
433
|
+
return { data, attribute, node };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const Dir = {
|
|
437
|
+
LEFT: 0,
|
|
438
|
+
RIGHT: 1,
|
|
439
|
+
TOP: 2,
|
|
440
|
+
BOTTOM: 3
|
|
441
|
+
};
|
|
442
|
+
const U32_EMPTY = 4294967295;
|
|
443
|
+
function allocLeafSet(capacity) {
|
|
444
|
+
return {
|
|
445
|
+
capacity,
|
|
446
|
+
count: 0,
|
|
447
|
+
space: new Uint8Array(capacity),
|
|
448
|
+
level: new Uint8Array(capacity),
|
|
449
|
+
x: new Int32Array(capacity),
|
|
450
|
+
y: new Int32Array(capacity)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function resetLeafSet(leaves) {
|
|
454
|
+
leaves.count = 0;
|
|
455
|
+
}
|
|
456
|
+
function allocSeamTable(capacity) {
|
|
457
|
+
return {
|
|
458
|
+
capacity,
|
|
459
|
+
count: 0,
|
|
460
|
+
stride: 8,
|
|
461
|
+
neighbors: new Uint32Array(capacity * 8)
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function resetSeamTable(seams) {
|
|
465
|
+
seams.count = 0;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function createNodeStore(maxNodes, spaceCount) {
|
|
469
|
+
return {
|
|
470
|
+
maxNodes,
|
|
471
|
+
nodesUsed: 0,
|
|
472
|
+
currentGen: 1,
|
|
473
|
+
gen: new Uint16Array(maxNodes),
|
|
474
|
+
space: new Uint8Array(maxNodes),
|
|
475
|
+
level: new Uint8Array(maxNodes),
|
|
476
|
+
x: new Int32Array(maxNodes),
|
|
477
|
+
y: new Int32Array(maxNodes),
|
|
478
|
+
firstChild: new Uint32Array(maxNodes),
|
|
479
|
+
flags: new Uint8Array(maxNodes),
|
|
480
|
+
roots: new Uint32Array(spaceCount)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function beginFrame(store) {
|
|
484
|
+
store.nodesUsed = 0;
|
|
485
|
+
store.currentGen = store.currentGen + 1 & 65535;
|
|
486
|
+
if (store.currentGen === 0) {
|
|
487
|
+
store.gen.fill(0);
|
|
488
|
+
store.currentGen = 1;
|
|
411
489
|
}
|
|
412
490
|
}
|
|
491
|
+
function allocNode(store, tile) {
|
|
492
|
+
const id = store.nodesUsed;
|
|
493
|
+
if (id >= store.maxNodes) return U32_EMPTY;
|
|
494
|
+
store.nodesUsed = id + 1;
|
|
495
|
+
store.gen[id] = store.currentGen;
|
|
496
|
+
store.space[id] = tile.space;
|
|
497
|
+
store.level[id] = tile.level;
|
|
498
|
+
store.x[id] = tile.x;
|
|
499
|
+
store.y[id] = tile.y;
|
|
500
|
+
store.firstChild[id] = U32_EMPTY;
|
|
501
|
+
store.flags[id] = 0;
|
|
502
|
+
return id;
|
|
503
|
+
}
|
|
504
|
+
function hasChildren(store, nodeId) {
|
|
505
|
+
return store.firstChild[nodeId] !== U32_EMPTY;
|
|
506
|
+
}
|
|
507
|
+
function ensureChildren(store, parentId) {
|
|
508
|
+
const existing = store.firstChild[parentId];
|
|
509
|
+
if (existing !== U32_EMPTY) return existing;
|
|
510
|
+
const childBase = store.nodesUsed;
|
|
511
|
+
if (childBase + 4 > store.maxNodes) return U32_EMPTY;
|
|
512
|
+
const space = store.space[parentId];
|
|
513
|
+
const level = store.level[parentId] + 1;
|
|
514
|
+
const px = store.x[parentId] << 1;
|
|
515
|
+
const py = store.y[parentId] << 1;
|
|
516
|
+
allocNode(store, { space, level, x: px, y: py });
|
|
517
|
+
allocNode(store, { space, level, x: px + 1, y: py });
|
|
518
|
+
allocNode(store, { space, level, x: px, y: py + 1 });
|
|
519
|
+
allocNode(store, { space, level, x: px + 1, y: py + 1 });
|
|
520
|
+
store.firstChild[parentId] = childBase;
|
|
521
|
+
return childBase;
|
|
522
|
+
}
|
|
413
523
|
|
|
414
|
-
function
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
524
|
+
function nextPow2(n) {
|
|
525
|
+
let x = 1;
|
|
526
|
+
while (x < n) x <<= 1;
|
|
527
|
+
return x;
|
|
528
|
+
}
|
|
529
|
+
function mix32(x) {
|
|
530
|
+
x >>>= 0;
|
|
531
|
+
x ^= x >>> 16;
|
|
532
|
+
x = Math.imul(x, 2146121005) >>> 0;
|
|
533
|
+
x ^= x >>> 15;
|
|
534
|
+
x = Math.imul(x, 2221713035) >>> 0;
|
|
535
|
+
x ^= x >>> 16;
|
|
536
|
+
return x >>> 0;
|
|
537
|
+
}
|
|
538
|
+
function hashKey(space, level, x, y) {
|
|
539
|
+
const h = space & 255 ^ (level & 255) << 8 ^ mix32(x) >>> 0 ^ mix32(y) >>> 0;
|
|
540
|
+
return mix32(h);
|
|
541
|
+
}
|
|
542
|
+
function createSpatialIndex(maxEntries) {
|
|
543
|
+
const size = nextPow2(Math.max(2, maxEntries * 2));
|
|
544
|
+
return {
|
|
545
|
+
size,
|
|
546
|
+
mask: size - 1,
|
|
547
|
+
stampGen: 1,
|
|
548
|
+
stamp: new Uint16Array(size),
|
|
549
|
+
keysSpace: new Uint8Array(size),
|
|
550
|
+
keysLevel: new Uint8Array(size),
|
|
551
|
+
keysX: new Uint32Array(size),
|
|
552
|
+
keysY: new Uint32Array(size),
|
|
553
|
+
values: new Uint32Array(size)
|
|
420
554
|
};
|
|
421
555
|
}
|
|
422
|
-
function
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
556
|
+
function resetSpatialIndex(index) {
|
|
557
|
+
index.stampGen = index.stampGen + 1 & 65535;
|
|
558
|
+
if (index.stampGen === 0) {
|
|
559
|
+
index.stamp.fill(0);
|
|
560
|
+
index.stampGen = 1;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function insertSpatialIndexRaw(index, space, level, x, y, value) {
|
|
564
|
+
const s = space & 255;
|
|
565
|
+
const l = level & 255;
|
|
566
|
+
const xx = x >>> 0;
|
|
567
|
+
const yy = y >>> 0;
|
|
568
|
+
let slot = hashKey(s, l, xx, yy) & index.mask;
|
|
569
|
+
for (let probes = 0; probes < index.size; probes++) {
|
|
570
|
+
if (index.stamp[slot] !== index.stampGen) {
|
|
571
|
+
index.stamp[slot] = index.stampGen;
|
|
572
|
+
index.keysSpace[slot] = s;
|
|
573
|
+
index.keysLevel[slot] = l;
|
|
574
|
+
index.keysX[slot] = xx;
|
|
575
|
+
index.keysY[slot] = yy;
|
|
576
|
+
index.values[slot] = value >>> 0;
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (index.keysSpace[slot] === s && index.keysLevel[slot] === l && index.keysX[slot] === xx && index.keysY[slot] === yy) {
|
|
580
|
+
index.values[slot] = value >>> 0;
|
|
581
|
+
return;
|
|
428
582
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
583
|
+
slot = slot + 1 & index.mask;
|
|
584
|
+
}
|
|
585
|
+
throw new Error("SpatialIndex is full (no empty slot found).");
|
|
586
|
+
}
|
|
587
|
+
function lookupSpatialIndexRaw(index, space, level, x, y) {
|
|
588
|
+
const s = space & 255;
|
|
589
|
+
const l = level & 255;
|
|
590
|
+
const xx = x >>> 0;
|
|
591
|
+
const yy = y >>> 0;
|
|
592
|
+
let slot = hashKey(s, l, xx, yy) & index.mask;
|
|
593
|
+
for (let probes = 0; probes < index.size; probes++) {
|
|
594
|
+
if (index.stamp[slot] !== index.stampGen) return U32_EMPTY;
|
|
595
|
+
if (index.keysSpace[slot] === s && index.keysLevel[slot] === l && index.keysX[slot] === xx && index.keysY[slot] === yy) {
|
|
596
|
+
return index.values[slot];
|
|
432
597
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
598
|
+
slot = slot + 1 & index.mask;
|
|
599
|
+
}
|
|
600
|
+
return U32_EMPTY;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function buildLeafIndex(leaves, out) {
|
|
604
|
+
const index = out ?? createSpatialIndex(leaves.count);
|
|
605
|
+
resetSpatialIndex(index);
|
|
606
|
+
for (let i = 0; i < leaves.count; i++) {
|
|
607
|
+
insertSpatialIndexRaw(index, leaves.space[i], leaves.level[i], leaves.x[i], leaves.y[i], i);
|
|
608
|
+
}
|
|
609
|
+
return index;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function createState(cfg, surface) {
|
|
613
|
+
const store = createNodeStore(cfg.maxNodes, surface.spaceCount);
|
|
614
|
+
const scratchRootTiles = [];
|
|
615
|
+
for (let i = 0; i < surface.maxRootCount; i++) {
|
|
616
|
+
scratchRootTiles.push({ space: 0, level: 0, x: 0, y: 0 });
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
cfg,
|
|
620
|
+
store,
|
|
621
|
+
leaves: allocLeafSet(cfg.maxNodes),
|
|
622
|
+
leafNodeIds: new Uint32Array(cfg.maxNodes),
|
|
623
|
+
leafIndex: createSpatialIndex(cfg.maxNodes),
|
|
624
|
+
stack: new Uint32Array(cfg.maxNodes),
|
|
625
|
+
rootNodeIds: new Uint32Array(surface.maxRootCount),
|
|
626
|
+
rootCount: 0,
|
|
627
|
+
splitQueue: new Uint32Array(cfg.maxNodes),
|
|
628
|
+
splitStamp: new Uint16Array(cfg.maxNodes),
|
|
629
|
+
splitGen: 1,
|
|
630
|
+
scratchTile: { space: 0, level: 0, x: 0, y: 0 },
|
|
631
|
+
scratchNeighbor: { space: 0, level: 0, x: 0, y: 0 },
|
|
632
|
+
scratchBounds: { cx: 0, cy: 0, cz: 0, r: 0 },
|
|
633
|
+
scratchRootTiles,
|
|
634
|
+
spaceCount: surface.spaceCount
|
|
437
635
|
};
|
|
438
636
|
}
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const tempVector3 = new THREE__namespace.Vector3();
|
|
445
|
-
const tempBox3 = new THREE__namespace.Box3();
|
|
446
|
-
const tempMin = new THREE__namespace.Vector3();
|
|
447
|
-
const tempMax = new THREE__namespace.Vector3();
|
|
448
|
-
class Quadtree {
|
|
449
|
-
nodeCount = 0;
|
|
450
|
-
deepestLevel = 0;
|
|
451
|
-
config;
|
|
452
|
-
nodeView;
|
|
453
|
-
subdivisionStrategy;
|
|
454
|
-
// Pre-allocated buffers to avoid object creation
|
|
455
|
-
tempChildIndices = [-1, -1, -1, -1];
|
|
456
|
-
tempNeighborIndices = [-1, -1, -1, -1];
|
|
457
|
-
/**
|
|
458
|
-
* Create a new Quadtree.
|
|
459
|
-
*
|
|
460
|
-
* @param config Quadtree configuration parameters
|
|
461
|
-
* @param subdivisionStrategy Strategy function for subdivision decisions.
|
|
462
|
-
* Defaults to distanceBasedSubdivision(2).
|
|
463
|
-
* @param nodeView Optional pre-allocated NodeView for buffer reuse
|
|
464
|
-
*/
|
|
465
|
-
constructor(config, subdivisionStrategy, nodeView) {
|
|
466
|
-
this.config = config;
|
|
467
|
-
this.subdivisionStrategy = subdivisionStrategy ?? distanceBasedSubdivision(2);
|
|
468
|
-
this.nodeView = nodeView ?? new QuadtreeNodeView(config.maxNodes);
|
|
469
|
-
this.initialize();
|
|
637
|
+
function beginUpdate(state, surface, params) {
|
|
638
|
+
if (surface.spaceCount !== state.spaceCount) {
|
|
639
|
+
throw new Error(
|
|
640
|
+
`Surface spaceCount changed (${state.spaceCount} -> ${surface.spaceCount}). Create a new quadtree state.`
|
|
641
|
+
);
|
|
470
642
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
* @param strategy The subdivision strategy function
|
|
476
|
-
*/
|
|
477
|
-
setSubdivisionStrategy(strategy) {
|
|
478
|
-
this.subdivisionStrategy = strategy;
|
|
643
|
+
if (surface.maxRootCount !== state.rootNodeIds.length) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Surface maxRootCount changed (${state.rootNodeIds.length} -> ${surface.maxRootCount}). Create a new quadtree state.`
|
|
646
|
+
);
|
|
479
647
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
648
|
+
beginFrame(state.store);
|
|
649
|
+
state.rootCount = 0;
|
|
650
|
+
const rootCount = surface.rootTiles(params.cameraOrigin, state.scratchRootTiles);
|
|
651
|
+
if (rootCount < 0 || rootCount > surface.maxRootCount) {
|
|
652
|
+
throw new Error(`Surface returned invalid root count (${rootCount}).`);
|
|
485
653
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
654
|
+
for (let i = 0; i < rootCount; i++) {
|
|
655
|
+
const rootId = allocNode(state.store, state.scratchRootTiles[i]);
|
|
656
|
+
if (rootId === U32_EMPTY) {
|
|
657
|
+
throw new Error("Failed to allocate root node (maxNodes too small).");
|
|
658
|
+
}
|
|
659
|
+
state.rootNodeIds[i] = rootId;
|
|
660
|
+
state.rootCount = i + 1;
|
|
491
661
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function shouldSplit(bounds, level, maxLevel, params) {
|
|
665
|
+
if (level >= maxLevel) return false;
|
|
666
|
+
const mode = params.mode ?? "distance";
|
|
667
|
+
const cx = bounds.cx;
|
|
668
|
+
const cy = bounds.cy;
|
|
669
|
+
const cz = bounds.cz;
|
|
670
|
+
const distSq = cx * cx + cy * cy + cz * cz;
|
|
671
|
+
const safeDistSq = distSq > 1e-12 ? distSq : 1e-12;
|
|
672
|
+
if (mode === "screen") {
|
|
673
|
+
const proj = params.projectionFactor ?? 0;
|
|
674
|
+
const target = params.targetPixels ?? 0;
|
|
675
|
+
if (proj <= 0 || target <= 0) {
|
|
676
|
+
const f2 = params.distanceFactor ?? 2;
|
|
677
|
+
const threshold2 = bounds.r * f2;
|
|
678
|
+
return safeDistSq < threshold2 * threshold2;
|
|
679
|
+
}
|
|
680
|
+
const left = bounds.r * bounds.r * proj * proj;
|
|
681
|
+
const right = safeDistSq * target * target;
|
|
682
|
+
return left > right;
|
|
500
683
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
684
|
+
const f = params.distanceFactor ?? 2;
|
|
685
|
+
const threshold = bounds.r * f;
|
|
686
|
+
return safeDistSq < threshold * threshold;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function refineLeaves(state, surface, params, outLeaves) {
|
|
690
|
+
const leaves = outLeaves ?? state.leaves;
|
|
691
|
+
resetLeafSet(leaves);
|
|
692
|
+
const store = state.store;
|
|
693
|
+
const stack = state.stack;
|
|
694
|
+
let sp = 0;
|
|
695
|
+
for (let i = 0; i < state.rootCount; i++) {
|
|
696
|
+
stack[sp++] = state.rootNodeIds[i];
|
|
697
|
+
}
|
|
698
|
+
while (sp > 0) {
|
|
699
|
+
const nodeId = stack[--sp];
|
|
700
|
+
const level = store.level[nodeId];
|
|
701
|
+
const space = store.space[nodeId];
|
|
702
|
+
const x = store.x[nodeId];
|
|
703
|
+
const y = store.y[nodeId];
|
|
704
|
+
const tile = state.scratchTile;
|
|
705
|
+
tile.space = space;
|
|
706
|
+
tile.level = level;
|
|
707
|
+
tile.x = x;
|
|
708
|
+
tile.y = y;
|
|
709
|
+
const bounds = state.scratchBounds;
|
|
710
|
+
surface.tileBounds(tile, params.cameraOrigin, bounds);
|
|
711
|
+
if (hasChildren(store, nodeId)) {
|
|
712
|
+
const base = store.firstChild[nodeId];
|
|
713
|
+
stack[sp++] = base + 3;
|
|
714
|
+
stack[sp++] = base + 2;
|
|
715
|
+
stack[sp++] = base + 1;
|
|
716
|
+
stack[sp++] = base + 0;
|
|
717
|
+
continue;
|
|
526
718
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
719
|
+
const split = shouldSplit(bounds, level, state.cfg.maxLevel, params);
|
|
720
|
+
if (split) {
|
|
721
|
+
const base = ensureChildren(store, nodeId);
|
|
722
|
+
if (base !== U32_EMPTY) {
|
|
723
|
+
stack[sp++] = base + 3;
|
|
724
|
+
stack[sp++] = base + 2;
|
|
725
|
+
stack[sp++] = base + 1;
|
|
726
|
+
stack[sp++] = base + 0;
|
|
727
|
+
continue;
|
|
534
728
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
729
|
+
}
|
|
730
|
+
const i = leaves.count;
|
|
731
|
+
if (i >= leaves.capacity) {
|
|
732
|
+
throw new Error("LeafSet capacity exceeded.");
|
|
733
|
+
}
|
|
734
|
+
leaves.space[i] = space;
|
|
735
|
+
leaves.level[i] = level;
|
|
736
|
+
leaves.x[i] = x;
|
|
737
|
+
leaves.y[i] = y;
|
|
738
|
+
state.leafNodeIds[i] = nodeId;
|
|
739
|
+
leaves.count = i + 1;
|
|
740
|
+
}
|
|
741
|
+
return leaves;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function resetSplitMarks(state) {
|
|
745
|
+
state.splitGen = state.splitGen + 1 & 65535;
|
|
746
|
+
if (state.splitGen === 0) {
|
|
747
|
+
state.splitStamp.fill(0);
|
|
748
|
+
state.splitGen = 1;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function scheduleSplit(state, nodeId, count) {
|
|
752
|
+
if (nodeId === U32_EMPTY) return count;
|
|
753
|
+
if (state.splitStamp[nodeId] === state.splitGen) return count;
|
|
754
|
+
state.splitStamp[nodeId] = state.splitGen;
|
|
755
|
+
state.splitQueue[count] = nodeId;
|
|
756
|
+
return count + 1;
|
|
757
|
+
}
|
|
758
|
+
function balance2to1(state, surface, params, leaves) {
|
|
759
|
+
const maxIters = state.cfg.maxLevel + 1;
|
|
760
|
+
for (let iter = 0; iter < maxIters; iter++) {
|
|
761
|
+
const index = buildLeafIndex(leaves, state.leafIndex);
|
|
762
|
+
resetSplitMarks(state);
|
|
763
|
+
let splitCount = 0;
|
|
764
|
+
for (let i = 0; i < leaves.count; i++) {
|
|
765
|
+
const leafLevel = leaves.level[i];
|
|
766
|
+
if (leafLevel < 2) continue;
|
|
767
|
+
const leafSpace = leaves.space[i];
|
|
768
|
+
const leafX = leaves.x[i];
|
|
769
|
+
const leafY = leaves.y[i];
|
|
770
|
+
for (let dir = 0; dir < 4; dir++) {
|
|
771
|
+
for (let candidateLevel = leafLevel - 2; candidateLevel >= 0; candidateLevel--) {
|
|
772
|
+
const shift = leafLevel - candidateLevel;
|
|
773
|
+
const tile = state.scratchTile;
|
|
774
|
+
tile.space = leafSpace;
|
|
775
|
+
tile.level = candidateLevel;
|
|
776
|
+
tile.x = leafX >>> shift;
|
|
777
|
+
tile.y = leafY >>> shift;
|
|
778
|
+
const neighbor = state.scratchNeighbor;
|
|
779
|
+
if (!surface.neighborSameLevel(tile, dir, neighbor)) break;
|
|
780
|
+
const j = lookupSpatialIndexRaw(
|
|
781
|
+
index,
|
|
782
|
+
neighbor.space,
|
|
783
|
+
neighbor.level,
|
|
784
|
+
neighbor.x,
|
|
785
|
+
neighbor.y
|
|
786
|
+
);
|
|
787
|
+
if (j !== U32_EMPTY) {
|
|
788
|
+
splitCount = scheduleSplit(state, state.leafNodeIds[j], splitCount);
|
|
789
|
+
break;
|
|
556
790
|
}
|
|
557
791
|
}
|
|
558
792
|
}
|
|
559
|
-
this.nodeView.setLeaf(nodeIndex, false);
|
|
560
|
-
return bestLeafIndex;
|
|
561
793
|
}
|
|
562
|
-
|
|
563
|
-
|
|
794
|
+
if (splitCount === 0) return leaves;
|
|
795
|
+
let anySplit = false;
|
|
796
|
+
for (let k = 0; k < splitCount; k++) {
|
|
797
|
+
const nodeId = state.splitQueue[k];
|
|
798
|
+
if (state.store.level[nodeId] >= state.cfg.maxLevel) continue;
|
|
799
|
+
const base = ensureChildren(state.store, nodeId);
|
|
800
|
+
if (base !== U32_EMPTY) anySplit = true;
|
|
801
|
+
}
|
|
802
|
+
if (!anySplit) return leaves;
|
|
803
|
+
refineLeaves(state, surface, params, leaves);
|
|
564
804
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
805
|
+
return leaves;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function update(state, surface, params, outLeaves) {
|
|
809
|
+
beginUpdate(state, surface, params);
|
|
810
|
+
const leaves = refineLeaves(state, surface, params, outLeaves);
|
|
811
|
+
return balance2to1(state, surface, params, leaves);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const scratchTile = { space: 0, level: 0, x: 0, y: 0 };
|
|
815
|
+
const scratchNbr = { space: 0, level: 0, x: 0, y: 0 };
|
|
816
|
+
const scratchParentTile = { space: 0, level: 0, x: 0, y: 0 };
|
|
817
|
+
const scratchParentNbr = { space: 0, level: 0, x: 0, y: 0 };
|
|
818
|
+
function buildSeams2to1(surface, leaves, outSeams, outIndex) {
|
|
819
|
+
if (outSeams.capacity < leaves.count) {
|
|
820
|
+
throw new Error("SeamTable capacity is smaller than LeafSet.count.");
|
|
577
821
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
822
|
+
const index = buildLeafIndex(leaves, outIndex);
|
|
823
|
+
outSeams.count = leaves.count;
|
|
824
|
+
const neighbors = outSeams.neighbors;
|
|
825
|
+
for (let i = 0; i < leaves.count; i++) {
|
|
826
|
+
const base = i * 8;
|
|
827
|
+
const space = leaves.space[i];
|
|
828
|
+
const level = leaves.level[i];
|
|
829
|
+
const x = leaves.x[i];
|
|
830
|
+
const y = leaves.y[i];
|
|
831
|
+
for (let dir = 0; dir < 4; dir++) {
|
|
832
|
+
const outOffset = base + dir * 2;
|
|
833
|
+
neighbors[outOffset + 0] = U32_EMPTY;
|
|
834
|
+
neighbors[outOffset + 1] = U32_EMPTY;
|
|
835
|
+
scratchTile.space = space;
|
|
836
|
+
scratchTile.level = level;
|
|
837
|
+
scratchTile.x = x;
|
|
838
|
+
scratchTile.y = y;
|
|
839
|
+
if (!surface.neighborSameLevel(scratchTile, dir, scratchNbr)) continue;
|
|
840
|
+
let j = lookupSpatialIndexRaw(index, scratchNbr.space, scratchNbr.level, scratchNbr.x, scratchNbr.y);
|
|
841
|
+
if (j !== U32_EMPTY) {
|
|
842
|
+
neighbors[outOffset + 0] = j;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (level > 0) {
|
|
846
|
+
const px = x >>> 1;
|
|
847
|
+
const py = y >>> 1;
|
|
848
|
+
scratchParentTile.space = space;
|
|
849
|
+
scratchParentTile.level = level - 1;
|
|
850
|
+
scratchParentTile.x = px;
|
|
851
|
+
scratchParentTile.y = py;
|
|
852
|
+
if (surface.neighborSameLevel(scratchParentTile, dir, scratchParentNbr)) {
|
|
853
|
+
j = lookupSpatialIndexRaw(
|
|
854
|
+
index,
|
|
855
|
+
scratchParentNbr.space,
|
|
856
|
+
scratchParentNbr.level,
|
|
857
|
+
scratchParentNbr.x,
|
|
858
|
+
scratchParentNbr.y
|
|
859
|
+
);
|
|
860
|
+
if (j !== U32_EMPTY) {
|
|
861
|
+
neighbors[outOffset + 0] = j;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const childLevel = scratchNbr.level + 1;
|
|
867
|
+
const x2 = scratchNbr.x << 1 >>> 0;
|
|
868
|
+
const y2 = scratchNbr.y << 1 >>> 0;
|
|
869
|
+
let ax = 0;
|
|
870
|
+
let ay = 0;
|
|
871
|
+
let bx = 0;
|
|
872
|
+
let by = 0;
|
|
873
|
+
switch (dir) {
|
|
874
|
+
case Dir.LEFT:
|
|
875
|
+
ax = x2 + 1;
|
|
876
|
+
ay = y2;
|
|
877
|
+
bx = x2 + 1;
|
|
878
|
+
by = y2 + 1;
|
|
879
|
+
break;
|
|
880
|
+
case Dir.RIGHT:
|
|
881
|
+
ax = x2;
|
|
882
|
+
ay = y2;
|
|
883
|
+
bx = x2;
|
|
884
|
+
by = y2 + 1;
|
|
885
|
+
break;
|
|
886
|
+
case Dir.TOP:
|
|
887
|
+
ax = x2;
|
|
888
|
+
ay = y2 + 1;
|
|
889
|
+
bx = x2 + 1;
|
|
890
|
+
by = y2 + 1;
|
|
891
|
+
break;
|
|
892
|
+
case Dir.BOTTOM:
|
|
893
|
+
ax = x2;
|
|
894
|
+
ay = y2;
|
|
895
|
+
bx = x2 + 1;
|
|
896
|
+
by = y2;
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
j = lookupSpatialIndexRaw(index, scratchNbr.space, childLevel, ax, ay);
|
|
900
|
+
if (j !== U32_EMPTY) neighbors[outOffset + 0] = j;
|
|
901
|
+
j = lookupSpatialIndexRaw(index, scratchNbr.space, childLevel, bx, by);
|
|
902
|
+
if (j !== U32_EMPTY) neighbors[outOffset + 1] = j;
|
|
588
903
|
}
|
|
589
|
-
this.tempChildIndices[0] = EMPTY_SENTINEL_VALUE;
|
|
590
|
-
this.tempChildIndices[1] = EMPTY_SENTINEL_VALUE;
|
|
591
|
-
this.tempChildIndices[2] = EMPTY_SENTINEL_VALUE;
|
|
592
|
-
this.tempChildIndices[3] = EMPTY_SENTINEL_VALUE;
|
|
593
|
-
this.tempNeighborIndices[0] = EMPTY_SENTINEL_VALUE;
|
|
594
|
-
this.tempNeighborIndices[1] = EMPTY_SENTINEL_VALUE;
|
|
595
|
-
this.tempNeighborIndices[2] = EMPTY_SENTINEL_VALUE;
|
|
596
|
-
this.tempNeighborIndices[3] = EMPTY_SENTINEL_VALUE;
|
|
597
|
-
const nodeIndex = this.nodeCount++;
|
|
598
|
-
this.nodeView.setLevel(nodeIndex, level);
|
|
599
|
-
this.nodeView.setX(nodeIndex, x);
|
|
600
|
-
this.nodeView.setY(nodeIndex, y);
|
|
601
|
-
this.nodeView.setChildren(nodeIndex, this.tempChildIndices);
|
|
602
|
-
this.nodeView.setNeighbors(nodeIndex, this.tempNeighborIndices);
|
|
603
|
-
this.nodeView.setLeaf(nodeIndex, false);
|
|
604
|
-
return nodeIndex;
|
|
605
904
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
905
|
+
return outSeams;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function createFlatSurface(cfg) {
|
|
909
|
+
const halfRoot = 0.5 * cfg.rootSize;
|
|
910
|
+
const maxHeight = cfg.maxHeight ?? 0;
|
|
911
|
+
const surface = {
|
|
912
|
+
spaceCount: 1,
|
|
913
|
+
maxRootCount: 1,
|
|
914
|
+
neighborSameLevel(tile, dir, out) {
|
|
915
|
+
const level = tile.level;
|
|
916
|
+
const x = tile.x;
|
|
917
|
+
const y = tile.y;
|
|
918
|
+
let nx = x;
|
|
919
|
+
let ny = y;
|
|
920
|
+
switch (dir) {
|
|
921
|
+
case Dir.LEFT:
|
|
922
|
+
nx = x - 1;
|
|
923
|
+
break;
|
|
924
|
+
case Dir.RIGHT:
|
|
925
|
+
nx = x + 1;
|
|
926
|
+
break;
|
|
927
|
+
case Dir.TOP:
|
|
928
|
+
ny = y - 1;
|
|
929
|
+
break;
|
|
930
|
+
case Dir.BOTTOM:
|
|
931
|
+
ny = y + 1;
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
if (nx < 0 || ny < 0) return false;
|
|
935
|
+
const maxCoord = (1 << level) - 1;
|
|
936
|
+
if (nx > maxCoord || ny > maxCoord) return false;
|
|
937
|
+
out.space = 0;
|
|
938
|
+
out.level = level;
|
|
939
|
+
out.x = nx;
|
|
940
|
+
out.y = ny;
|
|
941
|
+
return true;
|
|
942
|
+
},
|
|
943
|
+
tileBounds(tile, cameraOrigin, out) {
|
|
944
|
+
const level = tile.level;
|
|
945
|
+
const scale = 1 / (1 << level);
|
|
946
|
+
const size = cfg.rootSize * scale;
|
|
947
|
+
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
948
|
+
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
949
|
+
const centerX = minX + 0.5 * size;
|
|
950
|
+
const centerY = cfg.origin.y;
|
|
951
|
+
const centerZ = minZ + 0.5 * size;
|
|
952
|
+
out.cx = centerX - cameraOrigin.x;
|
|
953
|
+
out.cy = centerY - cameraOrigin.y;
|
|
954
|
+
out.cz = centerZ - cameraOrigin.z;
|
|
955
|
+
out.r = 0.7071067811865476 * size + maxHeight;
|
|
956
|
+
},
|
|
957
|
+
rootTiles(_cameraOrigin, out) {
|
|
958
|
+
const root = out[0];
|
|
959
|
+
root.space = 0;
|
|
960
|
+
root.level = 0;
|
|
961
|
+
root.x = 0;
|
|
962
|
+
root.y = 0;
|
|
963
|
+
return 1;
|
|
626
964
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
965
|
+
};
|
|
966
|
+
return surface;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function createInfiniteFlatSurface(cfg) {
|
|
970
|
+
const halfRoot = 0.5 * cfg.rootSize;
|
|
971
|
+
const maxHeight = cfg.maxHeight ?? 0;
|
|
972
|
+
const rootGridRadius = Math.max(0, Math.floor(cfg.rootGridRadius ?? 1));
|
|
973
|
+
const rootWidth = rootGridRadius * 2 + 1;
|
|
974
|
+
return {
|
|
975
|
+
spaceCount: 1,
|
|
976
|
+
maxRootCount: rootWidth * rootWidth,
|
|
977
|
+
neighborSameLevel(tile, dir, out) {
|
|
978
|
+
let nx = tile.x;
|
|
979
|
+
let ny = tile.y;
|
|
980
|
+
switch (dir) {
|
|
981
|
+
case Dir.LEFT:
|
|
982
|
+
nx = tile.x - 1;
|
|
983
|
+
break;
|
|
984
|
+
case Dir.RIGHT:
|
|
985
|
+
nx = tile.x + 1;
|
|
986
|
+
break;
|
|
987
|
+
case Dir.TOP:
|
|
988
|
+
ny = tile.y - 1;
|
|
989
|
+
break;
|
|
990
|
+
case Dir.BOTTOM:
|
|
991
|
+
ny = tile.y + 1;
|
|
992
|
+
break;
|
|
646
993
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
994
|
+
out.space = tile.space;
|
|
995
|
+
out.level = tile.level;
|
|
996
|
+
out.x = nx;
|
|
997
|
+
out.y = ny;
|
|
998
|
+
return true;
|
|
999
|
+
},
|
|
1000
|
+
tileBounds(tile, cameraOrigin, out) {
|
|
1001
|
+
const level = tile.level;
|
|
1002
|
+
const scale = 1 / (1 << level);
|
|
1003
|
+
const size = cfg.rootSize * scale;
|
|
1004
|
+
const minX = cfg.origin.x + (tile.x * size - halfRoot);
|
|
1005
|
+
const minZ = cfg.origin.z + (tile.y * size - halfRoot);
|
|
1006
|
+
const centerX = minX + 0.5 * size;
|
|
1007
|
+
const centerY = cfg.origin.y;
|
|
1008
|
+
const centerZ = minZ + 0.5 * size;
|
|
1009
|
+
out.cx = centerX - cameraOrigin.x;
|
|
1010
|
+
out.cy = centerY - cameraOrigin.y;
|
|
1011
|
+
out.cz = centerZ - cameraOrigin.z;
|
|
1012
|
+
out.r = 0.7071067811865476 * size + maxHeight;
|
|
1013
|
+
},
|
|
1014
|
+
rootTiles(cameraOrigin, out) {
|
|
1015
|
+
const camRootX = Math.floor((cameraOrigin.x - cfg.origin.x + halfRoot) / cfg.rootSize);
|
|
1016
|
+
const camRootY = Math.floor((cameraOrigin.z - cfg.origin.z + halfRoot) / cfg.rootSize);
|
|
1017
|
+
let index = 0;
|
|
1018
|
+
for (let dy = -rootGridRadius; dy <= rootGridRadius; dy++) {
|
|
1019
|
+
for (let dx = -rootGridRadius; dx <= rootGridRadius; dx++) {
|
|
1020
|
+
const root = out[index];
|
|
1021
|
+
root.space = 0;
|
|
1022
|
+
root.level = 0;
|
|
1023
|
+
root.x = camRootX + dx;
|
|
1024
|
+
root.y = camRootY + dy;
|
|
1025
|
+
index++;
|
|
1026
|
+
}
|
|
651
1027
|
}
|
|
652
|
-
|
|
1028
|
+
return index;
|
|
653
1029
|
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
* Get the configuration
|
|
678
|
-
*/
|
|
679
|
-
getConfig() {
|
|
680
|
-
return this.config;
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Get all leaf nodes as an array of node objects
|
|
684
|
-
*/
|
|
685
|
-
getLeafNodes() {
|
|
686
|
-
const leafNodes = [];
|
|
687
|
-
for (let i = 0; i < this.nodeCount; i++) {
|
|
688
|
-
if (this.nodeView.getLeaf(i)) {
|
|
689
|
-
leafNodes.push({
|
|
690
|
-
level: this.nodeView.getLevel(i),
|
|
691
|
-
x: this.nodeView.getX(i),
|
|
692
|
-
y: this.nodeView.getY(i)
|
|
693
|
-
});
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function createCubeSphereSurface(_cfg) {
|
|
1034
|
+
return {
|
|
1035
|
+
spaceCount: 6,
|
|
1036
|
+
maxRootCount: 6,
|
|
1037
|
+
neighborSameLevel(_tile, _dir, _out) {
|
|
1038
|
+
return false;
|
|
1039
|
+
},
|
|
1040
|
+
tileBounds(_tile, _cameraOrigin, out) {
|
|
1041
|
+
out.cx = 0;
|
|
1042
|
+
out.cy = 0;
|
|
1043
|
+
out.cz = 0;
|
|
1044
|
+
out.r = Number.MAX_VALUE;
|
|
1045
|
+
},
|
|
1046
|
+
rootTiles(_cameraOrigin, out) {
|
|
1047
|
+
for (let s = 0; s < 6; s++) {
|
|
1048
|
+
const root = out[s];
|
|
1049
|
+
root.space = s;
|
|
1050
|
+
root.level = 0;
|
|
1051
|
+
root.x = 0;
|
|
1052
|
+
root.y = 0;
|
|
694
1053
|
}
|
|
1054
|
+
return 6;
|
|
695
1055
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const surfaceTask = work.task((get, work) => {
|
|
1060
|
+
const customSurface = get(surface);
|
|
1061
|
+
const rootSizeVal = get(rootSize);
|
|
1062
|
+
const originVal = get(origin);
|
|
1063
|
+
return work(() => {
|
|
1064
|
+
if (customSurface) return customSurface;
|
|
1065
|
+
return createFlatSurface({ rootSize: rootSizeVal, origin: originVal });
|
|
1066
|
+
});
|
|
1067
|
+
}).displayName("surfaceTask");
|
|
1068
|
+
const quadtreeConfigTask = work.task((get, work) => {
|
|
1069
|
+
const surfaceVal = get(surfaceTask);
|
|
1070
|
+
const maxNodesVal = get(maxNodes);
|
|
1071
|
+
const maxLevelVal = get(maxLevel);
|
|
1072
|
+
return work(() => {
|
|
1073
|
+
const state = createState({ maxNodes: maxNodesVal, maxLevel: maxLevelVal }, surfaceVal);
|
|
1074
|
+
return {
|
|
1075
|
+
state,
|
|
1076
|
+
surface: surfaceVal
|
|
1077
|
+
};
|
|
1078
|
+
});
|
|
1079
|
+
}).displayName("quadtreeConfigTask");
|
|
1080
|
+
const quadtreeUpdateTask = work.task((get, work) => {
|
|
1081
|
+
const quadtreeConfig = get(quadtreeConfigTask);
|
|
1082
|
+
const quadtreeUpdateConfig = get(quadtreeUpdate);
|
|
1083
|
+
let outLeaves = void 0;
|
|
1084
|
+
return work(() => {
|
|
1085
|
+
outLeaves = update(
|
|
1086
|
+
quadtreeConfig.state,
|
|
1087
|
+
quadtreeConfig.surface,
|
|
1088
|
+
quadtreeUpdateConfig,
|
|
1089
|
+
outLeaves
|
|
1090
|
+
);
|
|
1091
|
+
return outLeaves;
|
|
1092
|
+
});
|
|
1093
|
+
}).displayName("quadtreeUpdateTask");
|
|
1094
|
+
const leafStorageTask = work.task((get, work) => {
|
|
1095
|
+
const maxNodesVal = get(maxNodes);
|
|
1096
|
+
return work(() => createLeafStorage(maxNodesVal));
|
|
1097
|
+
}).displayName("leafStorageTask");
|
|
1098
|
+
const leafGpuBufferTask = work.task((get, work) => {
|
|
1099
|
+
const leafSet = get(quadtreeUpdateTask);
|
|
1100
|
+
const leafStorage = get(leafStorageTask);
|
|
1101
|
+
return work(() => {
|
|
1102
|
+
const bufferCapacity = leafStorage.data.length / 4;
|
|
1103
|
+
const leafCount = Math.min(leafSet.count, bufferCapacity);
|
|
1104
|
+
for (let i = 0; i < leafCount; i += 1) {
|
|
1105
|
+
const offset = i * 4;
|
|
1106
|
+
leafStorage.data[offset] = leafSet.level[i] ?? 0;
|
|
1107
|
+
leafStorage.data[offset + 1] = leafSet.x[i] ?? 0;
|
|
1108
|
+
leafStorage.data[offset + 2] = leafSet.y[i] ?? 0;
|
|
1109
|
+
leafStorage.data[offset + 3] = 1;
|
|
725
1110
|
}
|
|
726
|
-
|
|
1111
|
+
leafStorage.attribute.needsUpdate = true;
|
|
1112
|
+
leafStorage.node.needsUpdate = true;
|
|
1113
|
+
return {
|
|
1114
|
+
count: leafCount,
|
|
1115
|
+
data: leafStorage.data,
|
|
1116
|
+
attribute: leafStorage.attribute,
|
|
1117
|
+
node: leafStorage.node
|
|
1118
|
+
};
|
|
1119
|
+
});
|
|
1120
|
+
}).displayName("leafGpuBufferTask");
|
|
1121
|
+
|
|
1122
|
+
function createElevationFunction(callback) {
|
|
1123
|
+
const tslFunction = (args) => {
|
|
1124
|
+
const params = {
|
|
1125
|
+
worldPosition: args.worldPosition,
|
|
1126
|
+
rootSize: args.rootSize,
|
|
1127
|
+
rootUV: args.rootUV,
|
|
1128
|
+
tileUV: args.tileUV,
|
|
1129
|
+
tileLevel: args.tileLevel,
|
|
1130
|
+
tileSize: args.tileSize,
|
|
1131
|
+
tileOriginVec2: args.tileOriginVec2,
|
|
1132
|
+
nodeIndex: args.nodeIndex
|
|
1133
|
+
};
|
|
1134
|
+
return callback(params);
|
|
1135
|
+
};
|
|
1136
|
+
return TSL_js.Fn(tslFunction);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function createTerrainUniforms(params) {
|
|
1140
|
+
const sanitizedId = params.instanceId?.replace(/-/g, "_");
|
|
1141
|
+
const suffix = sanitizedId ? `_${sanitizedId}` : "";
|
|
1142
|
+
const uRootOrigin = tsl.uniform(
|
|
1143
|
+
new webgpu.Vector3(params.rootOrigin.x, params.rootOrigin.y, params.rootOrigin.z)
|
|
1144
|
+
).setName(`uRootOrigin${suffix}`);
|
|
1145
|
+
const uRootSize = tsl.uniform(tsl.float(params.rootSize)).setName(`uRootSize${suffix}`);
|
|
1146
|
+
const uInnerTileSegments = tsl.uniform(tsl.int(params.innerTileSegments)).setName(
|
|
1147
|
+
`uInnerTileSegments${suffix}`
|
|
1148
|
+
);
|
|
1149
|
+
const uSkirtScale = tsl.uniform(tsl.float(params.skirtScale)).setName(`uSkirtScale${suffix}`);
|
|
1150
|
+
const uElevationScale = tsl.uniform(tsl.float(params.elevationScale)).setName(`uElevationScale${suffix}`);
|
|
1151
|
+
return {
|
|
1152
|
+
uRootOrigin,
|
|
1153
|
+
uRootSize,
|
|
1154
|
+
uInnerTileSegments,
|
|
1155
|
+
uSkirtScale,
|
|
1156
|
+
uElevationScale
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const instanceIdTask = work.task(() => crypto.randomUUID()).displayName("instanceIdTask").cache("once");
|
|
1161
|
+
|
|
1162
|
+
const scratchVector3 = new three.Vector3();
|
|
1163
|
+
const createUniformsTask = work.task((get, work) => {
|
|
1164
|
+
const uniformParams = {
|
|
1165
|
+
rootOrigin: get(origin),
|
|
1166
|
+
rootSize: get(rootSize),
|
|
1167
|
+
innerTileSegments: get(innerTileSegments),
|
|
1168
|
+
skirtScale: get(skirtScale),
|
|
1169
|
+
elevationScale: get(elevationScale),
|
|
1170
|
+
instanceId: get(instanceIdTask)
|
|
1171
|
+
};
|
|
1172
|
+
return work(() => createTerrainUniforms(uniformParams));
|
|
1173
|
+
}).displayName("createUniformsTask").cache("once");
|
|
1174
|
+
const updateUniformsTask = work.task((get, work) => {
|
|
1175
|
+
const terrainUniformsContext = get(createUniformsTask);
|
|
1176
|
+
const rootSizeVal = get(rootSize);
|
|
1177
|
+
const rootOrigin = get(origin);
|
|
1178
|
+
const innerTileSegmentsVal = get(innerTileSegments);
|
|
1179
|
+
const skirtScaleVal = get(skirtScale);
|
|
1180
|
+
const elevationScaleVal = get(elevationScale);
|
|
1181
|
+
return work(() => {
|
|
1182
|
+
terrainUniformsContext.uRootSize.value = rootSizeVal;
|
|
1183
|
+
terrainUniformsContext.uRootOrigin.value = scratchVector3.set(
|
|
1184
|
+
rootOrigin.x,
|
|
1185
|
+
rootOrigin.y,
|
|
1186
|
+
rootOrigin.z
|
|
1187
|
+
);
|
|
1188
|
+
terrainUniformsContext.uInnerTileSegments.value = innerTileSegmentsVal;
|
|
1189
|
+
terrainUniformsContext.uSkirtScale.value = skirtScaleVal;
|
|
1190
|
+
terrainUniformsContext.uElevationScale.value = elevationScaleVal;
|
|
1191
|
+
return terrainUniformsContext;
|
|
1192
|
+
});
|
|
1193
|
+
}).displayName("updateUniformsTask");
|
|
1194
|
+
|
|
1195
|
+
const createElevationFieldContextTask = work.task((get, work) => {
|
|
1196
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1197
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
1198
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
1199
|
+
return work(() => {
|
|
1200
|
+
const data = new Float32Array(totalElements);
|
|
1201
|
+
const attribute = new webgpu.StorageBufferAttribute(data, 1);
|
|
1202
|
+
const node = tsl.storage(attribute, "float", totalElements);
|
|
1203
|
+
return {
|
|
1204
|
+
data,
|
|
1205
|
+
attribute,
|
|
1206
|
+
node
|
|
1207
|
+
};
|
|
1208
|
+
});
|
|
1209
|
+
}).displayName("createElevationFieldContextTask");
|
|
1210
|
+
const tileNodesTask = work.task((get, work) => {
|
|
1211
|
+
const leafStorage = get(leafStorageTask);
|
|
1212
|
+
const uniforms = get(createUniformsTask);
|
|
1213
|
+
return work(() => {
|
|
1214
|
+
return createTileCompute(leafStorage, uniforms);
|
|
1215
|
+
});
|
|
1216
|
+
}).displayName("tileNodesTask");
|
|
1217
|
+
const elevationFieldStageTask = work.task((get, work) => {
|
|
1218
|
+
const tile = get(tileNodesTask);
|
|
1219
|
+
const uniforms = get(createUniformsTask);
|
|
1220
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1221
|
+
const userElevationFn = get(elevationFn);
|
|
1222
|
+
return work(() => {
|
|
1223
|
+
const heightFn = createElevationFunction(userElevationFn);
|
|
1224
|
+
const heightWriteFn = createElevation(tile, uniforms, heightFn);
|
|
1225
|
+
return [
|
|
1226
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
1227
|
+
const height = heightWriteFn(nodeIndex, localCoordinates);
|
|
1228
|
+
elevationFieldContext.node.element(globalVertexIndex).assign(height);
|
|
1229
|
+
}
|
|
1230
|
+
];
|
|
1231
|
+
});
|
|
1232
|
+
}).displayName("elevationFieldStageTask");
|
|
1233
|
+
|
|
1234
|
+
const createNormalFieldContextTask = work.task((get, work) => {
|
|
1235
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1236
|
+
const verticesPerNode = edgeVertexCount * edgeVertexCount;
|
|
1237
|
+
const totalElements = get(maxNodes) * verticesPerNode;
|
|
1238
|
+
return work(() => {
|
|
1239
|
+
const data = new Uint32Array(totalElements);
|
|
1240
|
+
const attribute = new webgpu.StorageBufferAttribute(data, 1);
|
|
1241
|
+
const node = tsl.storage(attribute, "uint", totalElements);
|
|
1242
|
+
return {
|
|
1243
|
+
data,
|
|
1244
|
+
attribute,
|
|
1245
|
+
node
|
|
1246
|
+
};
|
|
1247
|
+
});
|
|
1248
|
+
}).displayName("createNormalFieldContextTask");
|
|
1249
|
+
function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
|
|
1250
|
+
return tsl.Fn(
|
|
1251
|
+
([nodeIndex, tileSize, ix, iy, elevationScale]) => {
|
|
1252
|
+
const iEdge = tsl.int(edgeVertexCount);
|
|
1253
|
+
const verticesPerNode = iEdge.mul(iEdge);
|
|
1254
|
+
const baseOffset = tsl.int(nodeIndex).mul(verticesPerNode);
|
|
1255
|
+
const xLeft = tsl.int(ix).sub(tsl.int(1));
|
|
1256
|
+
const xRight = tsl.int(ix).add(tsl.int(1));
|
|
1257
|
+
const yUp = tsl.int(iy).sub(tsl.int(1));
|
|
1258
|
+
const yDown = tsl.int(iy).add(tsl.int(1));
|
|
1259
|
+
const hLeft = elevationFieldNode.element(baseOffset.add(tsl.int(iy).mul(iEdge).add(xLeft))).mul(elevationScale);
|
|
1260
|
+
const hRight = elevationFieldNode.element(baseOffset.add(tsl.int(iy).mul(iEdge).add(xRight))).mul(elevationScale);
|
|
1261
|
+
const hUp = elevationFieldNode.element(baseOffset.add(yUp.mul(iEdge).add(tsl.int(ix)))).mul(elevationScale);
|
|
1262
|
+
const hDown = elevationFieldNode.element(baseOffset.add(yDown.mul(iEdge).add(tsl.int(ix)))).mul(elevationScale);
|
|
1263
|
+
const innerSegments = tsl.float(iEdge).sub(tsl.float(3));
|
|
1264
|
+
const stepWorld = tileSize.div(innerSegments);
|
|
1265
|
+
const inv2Step = tsl.float(0.5).div(stepWorld);
|
|
1266
|
+
const dhdx = tsl.float(hRight).sub(tsl.float(hLeft)).mul(inv2Step);
|
|
1267
|
+
const dhdz = tsl.float(hDown).sub(tsl.float(hUp)).mul(inv2Step);
|
|
1268
|
+
const normal = tsl.vec3(dhdx.negate(), tsl.float(1), dhdz.negate()).normalize();
|
|
1269
|
+
return tsl.vec2(normal.x, normal.z);
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
const normalFieldStageTask = work.task((get, work) => {
|
|
1274
|
+
const upstream = get(elevationFieldStageTask);
|
|
1275
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1276
|
+
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1277
|
+
const tileEdgeVertexCount = get(innerTileSegments) + 3;
|
|
1278
|
+
const tile = get(tileNodesTask);
|
|
1279
|
+
const uniforms = get(createUniformsTask);
|
|
1280
|
+
return work(() => {
|
|
1281
|
+
const computeNormal = createNormalFromElevationField(
|
|
1282
|
+
elevationFieldContext.node,
|
|
1283
|
+
tileEdgeVertexCount
|
|
1284
|
+
);
|
|
1285
|
+
return [
|
|
1286
|
+
...upstream,
|
|
1287
|
+
(nodeIndex, globalVertexIndex, _uv, localCoordinates) => {
|
|
1288
|
+
const ix = tsl.int(localCoordinates.x);
|
|
1289
|
+
const iy = tsl.int(localCoordinates.y);
|
|
1290
|
+
const tileSize = tile.tileSize(nodeIndex);
|
|
1291
|
+
const normalXZ = computeNormal(
|
|
1292
|
+
nodeIndex,
|
|
1293
|
+
tileSize,
|
|
1294
|
+
ix,
|
|
1295
|
+
iy,
|
|
1296
|
+
uniforms.uElevationScale
|
|
1297
|
+
);
|
|
1298
|
+
normalFieldContext.node.element(globalVertexIndex).assign(tsl.packHalf2x16(normalXZ));
|
|
1299
|
+
}
|
|
1300
|
+
];
|
|
1301
|
+
});
|
|
1302
|
+
}).displayName("normalFieldStageTask");
|
|
1303
|
+
|
|
1304
|
+
const compileComputeTask = work.task((get, work) => {
|
|
1305
|
+
const pipeline = get(normalFieldStageTask);
|
|
1306
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1307
|
+
return work(() => compileComputePipeline(pipeline, edgeVertexCount));
|
|
1308
|
+
}).displayName("compileComputeTask");
|
|
1309
|
+
const executeComputeTask = work.task((get, work, { resources }) => {
|
|
1310
|
+
const { execute } = get(compileComputeTask);
|
|
1311
|
+
const leafState = get(leafGpuBufferTask);
|
|
1312
|
+
return work(
|
|
1313
|
+
() => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
1316
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
1317
|
+
function createComputePipelineTasks(leafStageTask) {
|
|
1318
|
+
const compile = work.task((get, work) => {
|
|
1319
|
+
const pipeline = get(leafStageTask);
|
|
1320
|
+
const edgeVertexCount = get(innerTileSegments) + 3;
|
|
1321
|
+
return work(() => compileComputePipeline(pipeline, edgeVertexCount));
|
|
1322
|
+
}).displayName("compileComputeTask");
|
|
1323
|
+
const execute = work.task((get, work, { resources }) => {
|
|
1324
|
+
const { execute: run } = get(compile);
|
|
1325
|
+
const leafState = get(leafGpuBufferTask);
|
|
1326
|
+
return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
|
|
1327
|
+
});
|
|
1328
|
+
}).displayName("executeComputeTask").lane("gpu");
|
|
1329
|
+
return { compile, execute };
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
|
|
1333
|
+
return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
|
|
1334
|
+
});
|
|
1335
|
+
const vectorSpaceToTextureSpace = tsl.Fn(([value]) => {
|
|
1336
|
+
return tsl.remap(value, tsl.float(-1), tsl.float(1), tsl.float(0), tsl.float(1));
|
|
1337
|
+
});
|
|
1338
|
+
const blendAngleCorrectedNormals = tsl.Fn(([n1, n2]) => {
|
|
1339
|
+
const t = tsl.vec3(n1.x, n1.y, n1.z.add(1));
|
|
1340
|
+
const u = tsl.vec3(n2.x.negate(), n2.y.negate(), n2.z);
|
|
1341
|
+
const r = t.mul(tsl.dot(t, u)).sub(u.mul(t.z)).normalize();
|
|
1342
|
+
return r;
|
|
1343
|
+
});
|
|
1344
|
+
const deriveNormalZ = tsl.Fn(([normalXY]) => {
|
|
1345
|
+
const xy = normalXY.toVar();
|
|
1346
|
+
const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
|
|
1347
|
+
return tsl.vec3(xy.x, xy.y, z);
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
const isSkirtVertex = tsl.Fn(([segments]) => {
|
|
1351
|
+
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1352
|
+
const vIndex = tsl.int(tsl.vertexIndex);
|
|
1353
|
+
const segmentEdges = tsl.int(segmentsNode.add(3));
|
|
1354
|
+
const vx = vIndex.mod(segmentEdges);
|
|
1355
|
+
const vy = vIndex.div(segmentEdges);
|
|
1356
|
+
const last = segmentEdges.sub(tsl.int(1));
|
|
1357
|
+
return vx.equal(tsl.int(0)).or(vx.equal(last)).or(vy.equal(tsl.int(0))).or(vy.equal(last));
|
|
1358
|
+
});
|
|
1359
|
+
const isSkirtUV = tsl.Fn(([segments]) => {
|
|
1360
|
+
const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
|
|
1361
|
+
const ux = tsl.uv().x;
|
|
1362
|
+
const uy = tsl.uv().y;
|
|
1363
|
+
const segmentCount = segmentsNode.add(2);
|
|
1364
|
+
const segmentStep = tsl.float(1).div(segmentCount);
|
|
1365
|
+
const innerX = ux.greaterThan(segmentStep).and(ux.lessThan(segmentStep.oneMinus()));
|
|
1366
|
+
const innerY = uy.greaterThan(segmentStep).and(uy.lessThan(segmentStep.oneMinus()));
|
|
1367
|
+
return innerX.and(innerY).not();
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
|
|
1371
|
+
return tsl.Fn(() => {
|
|
1372
|
+
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
1373
|
+
const nodeOffset = nodeIndex.mul(tsl.int(4));
|
|
1374
|
+
const nodeLevel = leafStorage.node.element(nodeOffset).toInt();
|
|
1375
|
+
const nodeX = leafStorage.node.element(nodeOffset.add(tsl.int(1))).toFloat();
|
|
1376
|
+
const nodeY = leafStorage.node.element(nodeOffset.add(tsl.int(2))).toFloat();
|
|
1377
|
+
const rootSize = terrainUniforms.uRootSize.toVar();
|
|
1378
|
+
const rootOrigin = terrainUniforms.uRootOrigin.toVar();
|
|
1379
|
+
const half = tsl.float(0.5);
|
|
1380
|
+
const size = rootSize.div(tsl.pow(tsl.float(2), nodeLevel.toFloat()));
|
|
1381
|
+
const halfRoot = rootSize.mul(half);
|
|
1382
|
+
const centerX = rootOrigin.x.add(nodeX.add(half).mul(size)).sub(halfRoot);
|
|
1383
|
+
const centerZ = rootOrigin.z.add(nodeY.add(half).mul(size)).sub(halfRoot);
|
|
1384
|
+
const clampedX = tsl.positionLocal.x.max(half.negate()).min(half);
|
|
1385
|
+
const clampedZ = tsl.positionLocal.z.max(half.negate()).min(half);
|
|
1386
|
+
const worldX = centerX.add(clampedX.mul(size));
|
|
1387
|
+
const worldZ = centerZ.add(clampedZ.mul(size));
|
|
1388
|
+
return tsl.vec3(worldX, rootOrigin.y, worldZ);
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
function createTileElevation(terrainUniforms, elevationFieldBufferNode) {
|
|
1392
|
+
if (!elevationFieldBufferNode) return tsl.float(0);
|
|
1393
|
+
const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
|
|
1394
|
+
return readElevationFieldAtPositionLocal(
|
|
1395
|
+
elevationFieldBufferNode,
|
|
1396
|
+
edgeVertexCount,
|
|
1397
|
+
tsl.positionLocal
|
|
1398
|
+
)().mul(
|
|
1399
|
+
terrainUniforms.uElevationScale
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
function createNormalAssignment(terrainUniforms, normalFieldBufferNode) {
|
|
1403
|
+
if (!normalFieldBufferNode) return;
|
|
1404
|
+
const nodeIndex = tsl.int(tsl.instanceIndex);
|
|
1405
|
+
const intEdge = tsl.int(terrainUniforms.uInnerTileSegments.add(3));
|
|
1406
|
+
const verticesPerNode = intEdge.mul(intEdge);
|
|
1407
|
+
const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(tsl.int(tsl.vertexIndex));
|
|
1408
|
+
const packed = normalFieldBufferNode.element(globalVertexIndex);
|
|
1409
|
+
const normalXZ = tsl.unpackHalf2x16(packed);
|
|
1410
|
+
const reconstructed = deriveNormalZ(normalXZ);
|
|
1411
|
+
tsl.normalLocal.assign(tsl.vec3(reconstructed.x, reconstructed.z, reconstructed.y));
|
|
727
1412
|
}
|
|
1413
|
+
function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBufferNode, normalFieldBufferNode) {
|
|
1414
|
+
const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
|
|
1415
|
+
return tsl.Fn(() => {
|
|
1416
|
+
const base = baseWorldPosition();
|
|
1417
|
+
const yElevation = createTileElevation(terrainUniforms, elevationFieldBufferNode);
|
|
1418
|
+
const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
|
|
1419
|
+
const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
|
|
1420
|
+
const worldY = tsl.select(skirtVertex, skirtY, base.y.add(yElevation));
|
|
1421
|
+
createNormalAssignment(terrainUniforms, normalFieldBufferNode);
|
|
1422
|
+
return tsl.vec3(base.x, worldY, base.z);
|
|
1423
|
+
})();
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const positionNodeTask = work.task((get, work) => {
|
|
1427
|
+
const leafStorage = get(leafStorageTask);
|
|
1428
|
+
const terrainUniforms = get(createUniformsTask);
|
|
1429
|
+
const elevationFieldContext = get(createElevationFieldContextTask);
|
|
1430
|
+
const normalFieldContext = get(createNormalFieldContextTask);
|
|
1431
|
+
return work(
|
|
1432
|
+
() => createTileWorldPosition(
|
|
1433
|
+
leafStorage,
|
|
1434
|
+
terrainUniforms,
|
|
1435
|
+
elevationFieldContext.node,
|
|
1436
|
+
normalFieldContext.node
|
|
1437
|
+
)
|
|
1438
|
+
);
|
|
1439
|
+
}).displayName("positionNodeTask");
|
|
1440
|
+
|
|
1441
|
+
function terrainGraph() {
|
|
1442
|
+
return work.graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(createNormalFieldContextTask).add(elevationFieldStageTask).add(normalFieldStageTask).add(compileComputeTask).add(executeComputeTask);
|
|
1443
|
+
}
|
|
1444
|
+
const terrainTasks = {
|
|
1445
|
+
instanceId: instanceIdTask,
|
|
1446
|
+
quadtreeConfig: quadtreeConfigTask,
|
|
1447
|
+
quadtreeUpdate: quadtreeUpdateTask,
|
|
1448
|
+
leafStorage: leafStorageTask,
|
|
1449
|
+
surface: surfaceTask,
|
|
1450
|
+
leafGpuBuffer: leafGpuBufferTask,
|
|
1451
|
+
createUniforms: createUniformsTask,
|
|
1452
|
+
updateUniforms: updateUniformsTask,
|
|
1453
|
+
positionNode: positionNodeTask,
|
|
1454
|
+
createElevationFieldContext: createElevationFieldContextTask,
|
|
1455
|
+
createTileNodes: tileNodesTask,
|
|
1456
|
+
createNormalFieldContext: createNormalFieldContextTask,
|
|
1457
|
+
elevationFieldStage: elevationFieldStageTask,
|
|
1458
|
+
normalFieldStage: normalFieldStageTask,
|
|
1459
|
+
compileCompute: compileComputeTask,
|
|
1460
|
+
executeCompute: executeComputeTask
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
const vGlobalVertexIndex = /* @__PURE__ */ tsl.varyingProperty("int", "vGlobalVertexIndex");
|
|
1464
|
+
const vElevation = /* @__PURE__ */ tsl.varyingProperty("f32", "vElevation");
|
|
1465
|
+
|
|
1466
|
+
const cellCenter = tsl.Fn(({ cell }) => {
|
|
1467
|
+
return cell.add(tsl.mx_noise_float(cell.mul(Math.PI)));
|
|
1468
|
+
});
|
|
1469
|
+
const voronoiCells = tsl.Fn((params) => {
|
|
1470
|
+
const scale = tsl.float(params.scale);
|
|
1471
|
+
const facet = tsl.float(params.facet);
|
|
1472
|
+
const seed = tsl.float(params.seed);
|
|
1473
|
+
const pos = params.uv.mul(scale).add(seed);
|
|
1474
|
+
const midCell = pos.round().toVar();
|
|
1475
|
+
const minCell = midCell.toVar();
|
|
1476
|
+
const minDist = tsl.float(1).toVar();
|
|
1477
|
+
const cell = tsl.vec3(0, 0, 0).toVar();
|
|
1478
|
+
const dist = tsl.float().toVar();
|
|
1479
|
+
const i = tsl.float(0).toVar();
|
|
1480
|
+
tsl.Loop(27, () => {
|
|
1481
|
+
const ix = i.mod(3).sub(1);
|
|
1482
|
+
const iy = i.div(3).floor().mod(3).sub(1);
|
|
1483
|
+
const iz = i.div(9).floor().sub(1);
|
|
1484
|
+
cell.assign(midCell.add(tsl.vec3(ix, iy, iz)));
|
|
1485
|
+
dist.assign(pos.distance(cellCenter({ cell })).add(tsl.mx_noise_float(pos).div(5)));
|
|
1486
|
+
tsl.If(dist.lessThan(minDist), () => {
|
|
1487
|
+
minDist.assign(dist);
|
|
1488
|
+
minCell.assign(cell);
|
|
1489
|
+
});
|
|
1490
|
+
i.addAssign(1);
|
|
1491
|
+
});
|
|
1492
|
+
const n = tsl.mx_noise_float(minCell.mul(Math.PI)).toVar();
|
|
1493
|
+
const k = tsl.mix(minDist, n.add(1).div(2), facet);
|
|
1494
|
+
return k;
|
|
1495
|
+
});
|
|
728
1496
|
|
|
729
|
-
exports.
|
|
1497
|
+
exports.Dir = Dir;
|
|
730
1498
|
exports.TerrainGeometry = TerrainGeometry;
|
|
731
|
-
exports.
|
|
732
|
-
exports.
|
|
1499
|
+
exports.TerrainMesh = TerrainMesh;
|
|
1500
|
+
exports.U32_EMPTY = U32_EMPTY;
|
|
1501
|
+
exports.allocLeafSet = allocLeafSet;
|
|
1502
|
+
exports.allocSeamTable = allocSeamTable;
|
|
1503
|
+
exports.beginUpdate = beginUpdate;
|
|
1504
|
+
exports.blendAngleCorrectedNormals = blendAngleCorrectedNormals;
|
|
1505
|
+
exports.buildLeafIndex = buildLeafIndex;
|
|
1506
|
+
exports.buildSeams2to1 = buildSeams2to1;
|
|
1507
|
+
exports.compileComputeTask = compileComputeTask;
|
|
1508
|
+
exports.createComputePipelineTasks = createComputePipelineTasks;
|
|
1509
|
+
exports.createCubeSphereSurface = createCubeSphereSurface;
|
|
1510
|
+
exports.createElevationFieldContextTask = createElevationFieldContextTask;
|
|
1511
|
+
exports.createFlatSurface = createFlatSurface;
|
|
1512
|
+
exports.createInfiniteFlatSurface = createInfiniteFlatSurface;
|
|
1513
|
+
exports.createNormalFieldContextTask = createNormalFieldContextTask;
|
|
1514
|
+
exports.createSpatialIndex = createSpatialIndex;
|
|
1515
|
+
exports.createState = createState;
|
|
1516
|
+
exports.createTerrainUniforms = createTerrainUniforms;
|
|
1517
|
+
exports.createUniformsTask = createUniformsTask;
|
|
1518
|
+
exports.deriveNormalZ = deriveNormalZ;
|
|
1519
|
+
exports.elevationFieldStageTask = elevationFieldStageTask;
|
|
1520
|
+
exports.elevationFn = elevationFn;
|
|
1521
|
+
exports.elevationScale = elevationScale;
|
|
1522
|
+
exports.executeComputeTask = executeComputeTask;
|
|
1523
|
+
exports.innerTileSegments = innerTileSegments;
|
|
1524
|
+
exports.instanceIdTask = instanceIdTask;
|
|
733
1525
|
exports.isSkirtUV = isSkirtUV;
|
|
734
1526
|
exports.isSkirtVertex = isSkirtVertex;
|
|
735
|
-
exports.
|
|
1527
|
+
exports.leafGpuBufferTask = leafGpuBufferTask;
|
|
1528
|
+
exports.leafStorageTask = leafStorageTask;
|
|
1529
|
+
exports.maxLevel = maxLevel;
|
|
1530
|
+
exports.maxNodes = maxNodes;
|
|
1531
|
+
exports.normalFieldStageTask = normalFieldStageTask;
|
|
1532
|
+
exports.origin = origin;
|
|
1533
|
+
exports.positionNodeTask = positionNodeTask;
|
|
1534
|
+
exports.quadtreeConfigTask = quadtreeConfigTask;
|
|
1535
|
+
exports.quadtreeUpdate = quadtreeUpdate;
|
|
1536
|
+
exports.quadtreeUpdateTask = quadtreeUpdateTask;
|
|
1537
|
+
exports.resetLeafSet = resetLeafSet;
|
|
1538
|
+
exports.resetSeamTable = resetSeamTable;
|
|
1539
|
+
exports.rootSize = rootSize;
|
|
1540
|
+
exports.skirtScale = skirtScale;
|
|
1541
|
+
exports.surface = surface;
|
|
1542
|
+
exports.surfaceTask = surfaceTask;
|
|
1543
|
+
exports.terrainGraph = terrainGraph;
|
|
1544
|
+
exports.terrainTasks = terrainTasks;
|
|
1545
|
+
exports.textureSpaceToVectorSpace = textureSpaceToVectorSpace;
|
|
1546
|
+
exports.tileNodesTask = tileNodesTask;
|
|
1547
|
+
exports.update = update;
|
|
1548
|
+
exports.updateUniformsTask = updateUniformsTask;
|
|
1549
|
+
exports.vElevation = vElevation;
|
|
1550
|
+
exports.vGlobalVertexIndex = vGlobalVertexIndex;
|
|
1551
|
+
exports.vectorSpaceToTextureSpace = vectorSpaceToTextureSpace;
|
|
1552
|
+
exports.voronoiCells = voronoiCells;
|