3dtiles-inspector 0.2.7 → 0.2.8
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/CHANGELOG.md +7 -0
- package/dist/inspector-assets/viewer/app.js +76 -12
- package/package.json +1 -1
- package/src/server/splatCrop/gltfResource.js +4 -4
- package/src/server/splatCrop/traversal.js +108 -41
- package/src/server/splatCrop/workerPool.js +35 -2
- package/src/viewer/scene/tiles.js +2 -2
- package/src/viewer/transform/geometricError.js +98 -13
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.8] - 2026-05-13
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed save-time crop memory spikes on large tilesets by limiting concurrent Gaussian Splat resource processing based on CPU parallelism, capped at 8 workers.
|
|
14
|
+
- Fixed viewer stalls when many tiles are loaded by avoiding repeated tileset-wide leaf geometric-error scans during tile preprocessing.
|
|
15
|
+
|
|
9
16
|
## [0.2.7] - 2026-05-10
|
|
10
17
|
|
|
11
18
|
### Changed
|
|
@@ -45288,6 +45288,9 @@ function createGeometricErrorController({
|
|
|
45288
45288
|
getTiles
|
|
45289
45289
|
}) {
|
|
45290
45290
|
const originalTileGeometricErrors = /* @__PURE__ */ new WeakMap();
|
|
45291
|
+
let knownTileChildCounts = /* @__PURE__ */ new WeakMap();
|
|
45292
|
+
let cachedGlobalLeafGeometricError = null;
|
|
45293
|
+
let cachedGlobalLeafGeometricErrorRoot = null;
|
|
45291
45294
|
let geometricErrorScaleExponent = 0;
|
|
45292
45295
|
let geometricErrorScale = 1;
|
|
45293
45296
|
let lastSavedGeometricErrorScale = 1;
|
|
@@ -45300,6 +45303,39 @@ function createGeometricErrorController({
|
|
|
45300
45303
|
function getEffectiveGeometricErrorLayerScale() {
|
|
45301
45304
|
return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
|
|
45302
45305
|
}
|
|
45306
|
+
function getTilesRoot() {
|
|
45307
|
+
return getTiles()?.root || null;
|
|
45308
|
+
}
|
|
45309
|
+
function getCachedGlobalLeafGeometricError() {
|
|
45310
|
+
const root = getTilesRoot();
|
|
45311
|
+
if (cachedGlobalLeafGeometricErrorRoot !== root) {
|
|
45312
|
+
cachedGlobalLeafGeometricErrorRoot = root;
|
|
45313
|
+
cachedGlobalLeafGeometricError = null;
|
|
45314
|
+
knownTileChildCounts = /* @__PURE__ */ new WeakMap();
|
|
45315
|
+
}
|
|
45316
|
+
return cachedGlobalLeafGeometricError;
|
|
45317
|
+
}
|
|
45318
|
+
function setCachedGlobalLeafGeometricError(leafGeometricError) {
|
|
45319
|
+
cachedGlobalLeafGeometricErrorRoot = getTilesRoot();
|
|
45320
|
+
cachedGlobalLeafGeometricError = leafGeometricError;
|
|
45321
|
+
}
|
|
45322
|
+
function clearCachedGlobalLeafGeometricError() {
|
|
45323
|
+
cachedGlobalLeafGeometricError = null;
|
|
45324
|
+
}
|
|
45325
|
+
function trackTileChildCount(tile, children) {
|
|
45326
|
+
knownTileChildCounts.set(tile, children.length);
|
|
45327
|
+
}
|
|
45328
|
+
function invalidateLeafCacheIfParentChildrenChanged(parentTile) {
|
|
45329
|
+
if (!parentTile || typeof parentTile !== "object") {
|
|
45330
|
+
return;
|
|
45331
|
+
}
|
|
45332
|
+
const children = Array.isArray(parentTile.children) ? parentTile.children : [];
|
|
45333
|
+
const knownChildCount = knownTileChildCounts.get(parentTile);
|
|
45334
|
+
if (cachedGlobalLeafGeometricError !== null && knownChildCount !== children.length) {
|
|
45335
|
+
clearCachedGlobalLeafGeometricError();
|
|
45336
|
+
}
|
|
45337
|
+
trackTileChildCount(parentTile, children);
|
|
45338
|
+
}
|
|
45303
45339
|
function updateTilesetErrorTarget() {
|
|
45304
45340
|
const tiles = getTiles();
|
|
45305
45341
|
if (!tiles) {
|
|
@@ -45338,6 +45374,7 @@ function createGeometricErrorController({
|
|
|
45338
45374
|
visited.add(tile);
|
|
45339
45375
|
let leafGeometricError = null;
|
|
45340
45376
|
const children = Array.isArray(tile.children) ? tile.children : [];
|
|
45377
|
+
trackTileChildCount(tile, children);
|
|
45341
45378
|
for (const child of children) {
|
|
45342
45379
|
const childLeafGeometricError = getKnownTileLeafGeometricError(
|
|
45343
45380
|
child,
|
|
@@ -45350,31 +45387,58 @@ function createGeometricErrorController({
|
|
|
45350
45387
|
visited.delete(tile);
|
|
45351
45388
|
return leafGeometricError === null ? originalGeometricError : leafGeometricError;
|
|
45352
45389
|
}
|
|
45353
|
-
function getGlobalTileLeafGeometricError(tile) {
|
|
45390
|
+
function getGlobalTileLeafGeometricError(tile, { forceRefresh = false } = {}) {
|
|
45391
|
+
if (!forceRefresh) {
|
|
45392
|
+
const cachedLeafGeometricError = getCachedGlobalLeafGeometricError();
|
|
45393
|
+
if (cachedLeafGeometricError !== null) {
|
|
45394
|
+
return cachedLeafGeometricError;
|
|
45395
|
+
}
|
|
45396
|
+
}
|
|
45354
45397
|
const tiles = getTiles();
|
|
45355
45398
|
const rootLeafGeometricError = tiles?.root ? getKnownTileLeafGeometricError(tiles.root) : null;
|
|
45399
|
+
if (tiles?.root && tile === tiles.root) {
|
|
45400
|
+
setCachedGlobalLeafGeometricError(rootLeafGeometricError);
|
|
45401
|
+
return rootLeafGeometricError;
|
|
45402
|
+
}
|
|
45356
45403
|
const tileLeafGeometricError = getKnownTileLeafGeometricError(tile);
|
|
45404
|
+
let leafGeometricError = null;
|
|
45357
45405
|
if (rootLeafGeometricError === null) {
|
|
45358
|
-
|
|
45359
|
-
}
|
|
45360
|
-
|
|
45361
|
-
|
|
45406
|
+
leafGeometricError = tileLeafGeometricError;
|
|
45407
|
+
} else if (tileLeafGeometricError === null) {
|
|
45408
|
+
leafGeometricError = rootLeafGeometricError;
|
|
45409
|
+
} else {
|
|
45410
|
+
leafGeometricError = Math.min(
|
|
45411
|
+
rootLeafGeometricError,
|
|
45412
|
+
tileLeafGeometricError
|
|
45413
|
+
);
|
|
45362
45414
|
}
|
|
45363
|
-
|
|
45415
|
+
setCachedGlobalLeafGeometricError(leafGeometricError);
|
|
45416
|
+
return leafGeometricError;
|
|
45364
45417
|
}
|
|
45365
|
-
function applyLayerScaleToTile(tile, leafGeometricError =
|
|
45418
|
+
function applyLayerScaleToTile(tile, leafGeometricError = null, parentTile = null) {
|
|
45419
|
+
invalidateLeafCacheIfParentChildrenChanged(parentTile);
|
|
45366
45420
|
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
45367
|
-
if (originalGeometricError === null
|
|
45421
|
+
if (originalGeometricError === null) {
|
|
45422
|
+
return;
|
|
45423
|
+
}
|
|
45424
|
+
const layerScale = getEffectiveGeometricErrorLayerScale();
|
|
45425
|
+
if (layerScale === 1) {
|
|
45426
|
+
tile.geometricError = originalGeometricError;
|
|
45427
|
+
return;
|
|
45428
|
+
}
|
|
45429
|
+
const effectiveLeafGeometricError = leafGeometricError ?? getGlobalTileLeafGeometricError(tile);
|
|
45430
|
+
if (effectiveLeafGeometricError === null) {
|
|
45368
45431
|
return;
|
|
45369
45432
|
}
|
|
45370
|
-
tile.geometricError =
|
|
45433
|
+
tile.geometricError = effectiveLeafGeometricError + (originalGeometricError - effectiveLeafGeometricError) * layerScale;
|
|
45371
45434
|
}
|
|
45372
45435
|
function applyLayerScaleToTileset() {
|
|
45373
45436
|
const tiles = getTiles();
|
|
45374
45437
|
if (!tiles) {
|
|
45375
45438
|
return;
|
|
45376
45439
|
}
|
|
45377
|
-
const
|
|
45440
|
+
const layerScale = getEffectiveGeometricErrorLayerScale();
|
|
45441
|
+
const leafGeometricError = layerScale === 1 ? null : getGlobalTileLeafGeometricError(tiles.root, { forceRefresh: true });
|
|
45378
45442
|
tiles.traverse(
|
|
45379
45443
|
(tile) => {
|
|
45380
45444
|
applyLayerScaleToTile(tile, leafGeometricError);
|
|
@@ -63249,8 +63313,8 @@ function createTerrainGlobeTiles(options) {
|
|
|
63249
63313
|
function createGeometricErrorLayerScalePlugin(preprocessNode) {
|
|
63250
63314
|
return {
|
|
63251
63315
|
name: "GeometricErrorLayerScalePlugin",
|
|
63252
|
-
preprocessNode(tile) {
|
|
63253
|
-
preprocessNode(tile);
|
|
63316
|
+
preprocessNode(tile, tilesetDir, parentTile) {
|
|
63317
|
+
preprocessNode(tile, null, parentTile);
|
|
63254
63318
|
}
|
|
63255
63319
|
};
|
|
63256
63320
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "3dtiles-inspector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Inspect, align, and save local 3D Tiles root transforms in an interactive browser session.",
|
|
5
5
|
"author": "William Liu <lyz15972107087@gmail.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -70,7 +70,7 @@ function parseGlb(filePath) {
|
|
|
70
70
|
if (chunkType === GLB_JSON_CHUNK_TYPE) {
|
|
71
71
|
json = JSON.parse(chunk.toString('utf8').replace(/\0+$/g, '').trimEnd());
|
|
72
72
|
} else if (chunkType === GLB_BIN_CHUNK_TYPE && !bin) {
|
|
73
|
-
bin =
|
|
73
|
+
bin = chunk;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
offset = chunkEnd;
|
|
@@ -190,7 +190,7 @@ function loadResourceBuffer(resource, bufferIndex) {
|
|
|
190
190
|
`${resource.filePath}.buffers[${bufferIndex}].uri`,
|
|
191
191
|
);
|
|
192
192
|
resource.dataUriMetadata.set(bufferIndex, metadata);
|
|
193
|
-
record = { bytes
|
|
193
|
+
record = { bytes, dataUri: true };
|
|
194
194
|
} else {
|
|
195
195
|
const bufferPath = resolveLocalUri(
|
|
196
196
|
path.dirname(resource.filePath),
|
|
@@ -205,7 +205,7 @@ function loadResourceBuffer(resource, bufferIndex) {
|
|
|
205
205
|
}
|
|
206
206
|
} else if (resource.type === 'glb' && bufferIndex === 0) {
|
|
207
207
|
record = {
|
|
208
|
-
bytes:
|
|
208
|
+
bytes: resource.embeddedBin || Buffer.alloc(0),
|
|
209
209
|
embedded: true,
|
|
210
210
|
};
|
|
211
211
|
} else {
|
|
@@ -322,7 +322,7 @@ function applyBufferReplacements(resource, bufferIndex, replacements) {
|
|
|
322
322
|
|
|
323
323
|
replacements.forEach((replacement) => {
|
|
324
324
|
parts.push(record.bytes.subarray(cursor, replacement.start));
|
|
325
|
-
parts.push(
|
|
325
|
+
parts.push(replacement.bytes);
|
|
326
326
|
cursor = replacement.end;
|
|
327
327
|
});
|
|
328
328
|
|
|
@@ -22,6 +22,71 @@ const {
|
|
|
22
22
|
removeMeshPrimitives,
|
|
23
23
|
updateGaussianPrimitiveAccessorCounts,
|
|
24
24
|
} = require('./gaussianPrimitives');
|
|
25
|
+
const { SPLAT_CROP_WORKER_COUNT } = require('./workerPool');
|
|
26
|
+
|
|
27
|
+
class AsyncLimiter {
|
|
28
|
+
constructor(limit) {
|
|
29
|
+
this.active = 0;
|
|
30
|
+
this.limit = normalizeConcurrency(limit);
|
|
31
|
+
this.queue = [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
run(task) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
this.queue.push({ reject, resolve, task });
|
|
37
|
+
this.dispatch();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dispatch() {
|
|
42
|
+
while (this.active < this.limit && this.queue.length > 0) {
|
|
43
|
+
const job = this.queue.shift();
|
|
44
|
+
this.active += 1;
|
|
45
|
+
Promise.resolve()
|
|
46
|
+
.then(job.task)
|
|
47
|
+
.then(job.resolve, job.reject)
|
|
48
|
+
.finally(() => {
|
|
49
|
+
this.active -= 1;
|
|
50
|
+
this.dispatch();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeConcurrency(value) {
|
|
57
|
+
const number = Number(value);
|
|
58
|
+
return Number.isFinite(number) && number > 0
|
|
59
|
+
? Math.max(1, Math.floor(number))
|
|
60
|
+
: 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createAsyncLimiter(limit = SPLAT_CROP_WORKER_COUNT) {
|
|
64
|
+
return new AsyncLimiter(limit);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
68
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const limit = normalizeConcurrency(concurrency);
|
|
73
|
+
const results = new Array(items.length);
|
|
74
|
+
let nextIndex = 0;
|
|
75
|
+
|
|
76
|
+
async function worker() {
|
|
77
|
+
while (nextIndex < items.length) {
|
|
78
|
+
const index = nextIndex;
|
|
79
|
+
nextIndex += 1;
|
|
80
|
+
results[index] = await mapper(items[index], index);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const workerCount = Math.min(limit, items.length);
|
|
85
|
+
await Promise.all(
|
|
86
|
+
Array.from({ length: workerCount }, () => worker()),
|
|
87
|
+
);
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
25
90
|
|
|
26
91
|
function getContentSlots(tile) {
|
|
27
92
|
const slots = [];
|
|
@@ -594,36 +659,16 @@ async function processGltfResource({
|
|
|
594
659
|
let hasResourceBounds = false;
|
|
595
660
|
let deletedSplats = 0;
|
|
596
661
|
let modified = false;
|
|
597
|
-
const rewriteTasks = [];
|
|
598
662
|
|
|
599
663
|
for (const [bufferViewIndex, viewDescriptors] of byBufferView) {
|
|
600
664
|
const slice = getBufferViewSlice(resource, bufferViewIndex);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
bytes: slice.bytes,
|
|
607
|
-
descriptors: viewDescriptors,
|
|
608
|
-
screenSelections,
|
|
609
|
-
workerPool,
|
|
610
|
-
}),
|
|
665
|
+
const rewrite = await rewriteSpzBytesInWorker({
|
|
666
|
+
bytes: slice.bytes,
|
|
667
|
+
descriptors: viewDescriptors,
|
|
668
|
+
screenSelections,
|
|
669
|
+
workerPool,
|
|
611
670
|
});
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const rewriteResults = await Promise.all(
|
|
615
|
-
rewriteTasks.map(async (task) => ({
|
|
616
|
-
...task,
|
|
617
|
-
rewrite: await task.promise,
|
|
618
|
-
})),
|
|
619
|
-
);
|
|
620
671
|
|
|
621
|
-
for (const {
|
|
622
|
-
bufferViewIndex,
|
|
623
|
-
rewrite,
|
|
624
|
-
slice,
|
|
625
|
-
viewDescriptors,
|
|
626
|
-
} of rewriteResults) {
|
|
627
672
|
deletedSplats += rewrite.deleted;
|
|
628
673
|
if (rewrite.bounds) {
|
|
629
674
|
hasResourceBounds =
|
|
@@ -766,11 +811,13 @@ async function processGltfContentSlot(
|
|
|
766
811
|
context.resourceLocks,
|
|
767
812
|
resourcePath,
|
|
768
813
|
() =>
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
814
|
+
context.resourceLimiter.run(() =>
|
|
815
|
+
processLockedSplatResource(
|
|
816
|
+
context,
|
|
817
|
+
resourcePath,
|
|
818
|
+
tileSceneMatrix,
|
|
819
|
+
tileProjectionMatrix,
|
|
820
|
+
),
|
|
774
821
|
),
|
|
775
822
|
);
|
|
776
823
|
|
|
@@ -809,6 +856,8 @@ async function processNestedTilesetContentSlot(
|
|
|
809
856
|
emptySplatResources: context.emptySplatResources,
|
|
810
857
|
progress: context.progress,
|
|
811
858
|
resourceLocks: context.resourceLocks,
|
|
859
|
+
resourceLimiter: context.resourceLimiter,
|
|
860
|
+
traversalConcurrency: context.traversalConcurrency,
|
|
812
861
|
workerPool: context.workerPool,
|
|
813
862
|
});
|
|
814
863
|
|
|
@@ -862,6 +911,14 @@ async function processContentSlot(context, slot, tileTransform, tileProjection)
|
|
|
862
911
|
return createContentSlotResult(slot, { boundsKnown: false });
|
|
863
912
|
}
|
|
864
913
|
|
|
914
|
+
async function processContentSlots(context, tile, tileTransform, tileProjection) {
|
|
915
|
+
return mapWithConcurrency(
|
|
916
|
+
getContentSlots(tile),
|
|
917
|
+
context.traversalConcurrency,
|
|
918
|
+
(slot) => processContentSlot(context, slot, tileTransform, tileProjection),
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
|
|
865
922
|
function applyContentResultsToTile(context, tile, contentResults) {
|
|
866
923
|
const contentBounds = [];
|
|
867
924
|
let boundsKnown = true;
|
|
@@ -893,16 +950,18 @@ async function pruneEmptyChildren(context, tile, tileTransform, tileProjection)
|
|
|
893
950
|
};
|
|
894
951
|
}
|
|
895
952
|
|
|
896
|
-
const children = tile.children.slice();
|
|
897
|
-
const childResults = await Promise.all(
|
|
898
|
-
children.map(async (child) => ({
|
|
899
|
-
child,
|
|
900
|
-
result: await visitTilesetTile(context, child, tileTransform, false),
|
|
901
|
-
})),
|
|
902
|
-
);
|
|
903
953
|
const childBounds = [];
|
|
904
954
|
let boundsKnown = true;
|
|
905
955
|
const keptChildren = [];
|
|
956
|
+
const childResults = await mapWithConcurrency(
|
|
957
|
+
tile.children,
|
|
958
|
+
context.traversalConcurrency,
|
|
959
|
+
async (child) => ({
|
|
960
|
+
child,
|
|
961
|
+
result: await visitTilesetTile(context, child, tileTransform, false),
|
|
962
|
+
}),
|
|
963
|
+
);
|
|
964
|
+
|
|
906
965
|
childResults.forEach(({ child, result }) => {
|
|
907
966
|
if (result.empty) {
|
|
908
967
|
context.tilesetModified = true;
|
|
@@ -960,10 +1019,11 @@ async function visitTilesetTile(context, tile, inheritedTransform, isRootTile) {
|
|
|
960
1019
|
isRootTile,
|
|
961
1020
|
);
|
|
962
1021
|
const tileProjection = getTileProjection(context.THREE, tile);
|
|
963
|
-
const contentResults = await
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1022
|
+
const contentResults = await processContentSlots(
|
|
1023
|
+
context,
|
|
1024
|
+
tile,
|
|
1025
|
+
worldTransform,
|
|
1026
|
+
tileProjection,
|
|
967
1027
|
);
|
|
968
1028
|
|
|
969
1029
|
const contentSummary = applyContentResultsToTile(context, tile, contentResults);
|
|
@@ -1016,6 +1076,8 @@ async function traverseTileset({
|
|
|
1016
1076
|
emptySplatResources,
|
|
1017
1077
|
progress,
|
|
1018
1078
|
resourceLocks,
|
|
1079
|
+
resourceLimiter = null,
|
|
1080
|
+
traversalConcurrency = SPLAT_CROP_WORKER_COUNT,
|
|
1019
1081
|
workerPool,
|
|
1020
1082
|
}) {
|
|
1021
1083
|
const resolvedTilesetPath = assertPathInsideRoot(
|
|
@@ -1039,6 +1101,8 @@ async function traverseTileset({
|
|
|
1039
1101
|
throw new InspectorError(`${resolvedTilesetPath} must contain a root object.`);
|
|
1040
1102
|
}
|
|
1041
1103
|
|
|
1104
|
+
const normalizedTraversalConcurrency =
|
|
1105
|
+
normalizeConcurrency(traversalConcurrency);
|
|
1042
1106
|
const context = {
|
|
1043
1107
|
THREE,
|
|
1044
1108
|
deletedSplats: 0,
|
|
@@ -1047,11 +1111,14 @@ async function traverseTileset({
|
|
|
1047
1111
|
processedResources,
|
|
1048
1112
|
processedSplatResources: 0,
|
|
1049
1113
|
resourceLocks,
|
|
1114
|
+
resourceLimiter:
|
|
1115
|
+
resourceLimiter || createAsyncLimiter(normalizedTraversalConcurrency),
|
|
1050
1116
|
rootDir,
|
|
1051
1117
|
rootTransform,
|
|
1052
1118
|
screenSelections,
|
|
1053
1119
|
tilesetDir: path.dirname(resolvedTilesetPath),
|
|
1054
1120
|
tilesetModified: false,
|
|
1121
|
+
traversalConcurrency: normalizedTraversalConcurrency,
|
|
1055
1122
|
upRotationMatrix,
|
|
1056
1123
|
visitedTilesets,
|
|
1057
1124
|
workerPool,
|
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
2
3
|
const { Worker } = require('worker_threads');
|
|
3
4
|
|
|
4
5
|
const { InspectorError } = require('../../errors');
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const MAX_SPLAT_CROP_WORKER_COUNT = 8;
|
|
7
8
|
const SPLAT_CROP_WORKER_PATH = path.join(__dirname, 'worker.js');
|
|
8
9
|
|
|
10
|
+
function getAvailableParallelism() {
|
|
11
|
+
if (typeof os.availableParallelism === 'function') {
|
|
12
|
+
try {
|
|
13
|
+
const parallelism = os.availableParallelism();
|
|
14
|
+
if (Number.isFinite(parallelism) && parallelism > 0) {
|
|
15
|
+
return parallelism;
|
|
16
|
+
}
|
|
17
|
+
} catch (err) {
|
|
18
|
+
// Fall back to os.cpus() below.
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const cpus = os.cpus();
|
|
23
|
+
return Array.isArray(cpus) && cpus.length > 0 ? cpus.length : 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getDefaultSplatCropWorkerCount() {
|
|
27
|
+
return Math.min(
|
|
28
|
+
MAX_SPLAT_CROP_WORKER_COUNT,
|
|
29
|
+
Math.max(1, getAvailableParallelism() - 1),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SPLAT_CROP_WORKER_COUNT = getDefaultSplatCropWorkerCount();
|
|
34
|
+
|
|
9
35
|
function deserializeWorkerError(error) {
|
|
10
36
|
const message =
|
|
11
37
|
error && typeof error.message === 'string'
|
|
@@ -74,7 +100,13 @@ class SplatCropWorkerPool {
|
|
|
74
100
|
const result = message.result || {};
|
|
75
101
|
job.resolve({
|
|
76
102
|
bounds: result.bounds || null,
|
|
77
|
-
bytes: result.bytes
|
|
103
|
+
bytes: result.bytes
|
|
104
|
+
? Buffer.from(
|
|
105
|
+
result.bytes.buffer,
|
|
106
|
+
result.bytes.byteOffset,
|
|
107
|
+
result.bytes.byteLength,
|
|
108
|
+
)
|
|
109
|
+
: null,
|
|
78
110
|
deleted: Number(result.deleted || 0),
|
|
79
111
|
empty: !!result.empty,
|
|
80
112
|
splatCount: Number(result.splatCount || 0),
|
|
@@ -179,6 +211,7 @@ class SplatCropWorkerPool {
|
|
|
179
211
|
}
|
|
180
212
|
|
|
181
213
|
module.exports = {
|
|
214
|
+
getDefaultSplatCropWorkerCount,
|
|
182
215
|
SPLAT_CROP_WORKER_COUNT,
|
|
183
216
|
SplatCropWorkerPool,
|
|
184
217
|
};
|
|
@@ -100,8 +100,8 @@ export function createTerrainGlobeTiles(options) {
|
|
|
100
100
|
function createGeometricErrorLayerScalePlugin(preprocessNode) {
|
|
101
101
|
return {
|
|
102
102
|
name: 'GeometricErrorLayerScalePlugin',
|
|
103
|
-
preprocessNode(tile) {
|
|
104
|
-
preprocessNode(tile);
|
|
103
|
+
preprocessNode(tile, tilesetDir, parentTile) {
|
|
104
|
+
preprocessNode(tile, null, parentTile);
|
|
105
105
|
},
|
|
106
106
|
};
|
|
107
107
|
}
|
|
@@ -21,6 +21,9 @@ export function createGeometricErrorController({
|
|
|
21
21
|
getTiles,
|
|
22
22
|
}) {
|
|
23
23
|
const originalTileGeometricErrors = new WeakMap();
|
|
24
|
+
let knownTileChildCounts = new WeakMap();
|
|
25
|
+
let cachedGlobalLeafGeometricError = null;
|
|
26
|
+
let cachedGlobalLeafGeometricErrorRoot = null;
|
|
24
27
|
let geometricErrorScaleExponent = 0;
|
|
25
28
|
let geometricErrorScale = 1;
|
|
26
29
|
let lastSavedGeometricErrorScale = 1;
|
|
@@ -36,6 +39,51 @@ export function createGeometricErrorController({
|
|
|
36
39
|
return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
function getTilesRoot() {
|
|
43
|
+
return getTiles()?.root || null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getCachedGlobalLeafGeometricError() {
|
|
47
|
+
const root = getTilesRoot();
|
|
48
|
+
if (cachedGlobalLeafGeometricErrorRoot !== root) {
|
|
49
|
+
cachedGlobalLeafGeometricErrorRoot = root;
|
|
50
|
+
cachedGlobalLeafGeometricError = null;
|
|
51
|
+
knownTileChildCounts = new WeakMap();
|
|
52
|
+
}
|
|
53
|
+
return cachedGlobalLeafGeometricError;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setCachedGlobalLeafGeometricError(leafGeometricError) {
|
|
57
|
+
cachedGlobalLeafGeometricErrorRoot = getTilesRoot();
|
|
58
|
+
cachedGlobalLeafGeometricError = leafGeometricError;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function clearCachedGlobalLeafGeometricError() {
|
|
62
|
+
cachedGlobalLeafGeometricError = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function trackTileChildCount(tile, children) {
|
|
66
|
+
knownTileChildCounts.set(tile, children.length);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function invalidateLeafCacheIfParentChildrenChanged(parentTile) {
|
|
70
|
+
if (!parentTile || typeof parentTile !== 'object') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const children = Array.isArray(parentTile.children)
|
|
75
|
+
? parentTile.children
|
|
76
|
+
: [];
|
|
77
|
+
const knownChildCount = knownTileChildCounts.get(parentTile);
|
|
78
|
+
if (
|
|
79
|
+
cachedGlobalLeafGeometricError !== null &&
|
|
80
|
+
knownChildCount !== children.length
|
|
81
|
+
) {
|
|
82
|
+
clearCachedGlobalLeafGeometricError();
|
|
83
|
+
}
|
|
84
|
+
trackTileChildCount(parentTile, children);
|
|
85
|
+
}
|
|
86
|
+
|
|
39
87
|
function updateTilesetErrorTarget() {
|
|
40
88
|
const tiles = getTiles();
|
|
41
89
|
if (!tiles) {
|
|
@@ -87,6 +135,7 @@ export function createGeometricErrorController({
|
|
|
87
135
|
visited.add(tile);
|
|
88
136
|
let leafGeometricError = null;
|
|
89
137
|
const children = Array.isArray(tile.children) ? tile.children : [];
|
|
138
|
+
trackTileChildCount(tile, children);
|
|
90
139
|
for (const child of children) {
|
|
91
140
|
const childLeafGeometricError = getKnownTileLeafGeometricError(
|
|
92
141
|
child,
|
|
@@ -105,37 +154,69 @@ export function createGeometricErrorController({
|
|
|
105
154
|
: leafGeometricError;
|
|
106
155
|
}
|
|
107
156
|
|
|
108
|
-
function getGlobalTileLeafGeometricError(tile) {
|
|
157
|
+
function getGlobalTileLeafGeometricError(tile, { forceRefresh = false } = {}) {
|
|
158
|
+
if (!forceRefresh) {
|
|
159
|
+
const cachedLeafGeometricError = getCachedGlobalLeafGeometricError();
|
|
160
|
+
if (cachedLeafGeometricError !== null) {
|
|
161
|
+
return cachedLeafGeometricError;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
109
165
|
const tiles = getTiles();
|
|
110
166
|
const rootLeafGeometricError = tiles?.root
|
|
111
167
|
? getKnownTileLeafGeometricError(tiles.root)
|
|
112
168
|
: null;
|
|
113
|
-
const tileLeafGeometricError = getKnownTileLeafGeometricError(tile);
|
|
114
169
|
|
|
115
|
-
if (
|
|
116
|
-
|
|
170
|
+
if (tiles?.root && tile === tiles.root) {
|
|
171
|
+
setCachedGlobalLeafGeometricError(rootLeafGeometricError);
|
|
172
|
+
return rootLeafGeometricError;
|
|
117
173
|
}
|
|
118
174
|
|
|
119
|
-
|
|
120
|
-
|
|
175
|
+
const tileLeafGeometricError = getKnownTileLeafGeometricError(tile);
|
|
176
|
+
let leafGeometricError = null;
|
|
177
|
+
|
|
178
|
+
if (rootLeafGeometricError === null) {
|
|
179
|
+
leafGeometricError = tileLeafGeometricError;
|
|
180
|
+
} else if (tileLeafGeometricError === null) {
|
|
181
|
+
leafGeometricError = rootLeafGeometricError;
|
|
182
|
+
} else {
|
|
183
|
+
leafGeometricError = Math.min(
|
|
184
|
+
rootLeafGeometricError,
|
|
185
|
+
tileLeafGeometricError,
|
|
186
|
+
);
|
|
121
187
|
}
|
|
122
188
|
|
|
123
|
-
|
|
189
|
+
setCachedGlobalLeafGeometricError(leafGeometricError);
|
|
190
|
+
return leafGeometricError;
|
|
124
191
|
}
|
|
125
192
|
|
|
126
193
|
function applyLayerScaleToTile(
|
|
127
194
|
tile,
|
|
128
|
-
leafGeometricError =
|
|
195
|
+
leafGeometricError = null,
|
|
196
|
+
parentTile = null,
|
|
129
197
|
) {
|
|
198
|
+
invalidateLeafCacheIfParentChildrenChanged(parentTile);
|
|
199
|
+
|
|
130
200
|
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
131
|
-
if (originalGeometricError === null
|
|
201
|
+
if (originalGeometricError === null) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const layerScale = getEffectiveGeometricErrorLayerScale();
|
|
206
|
+
if (layerScale === 1) {
|
|
207
|
+
tile.geometricError = originalGeometricError;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const effectiveLeafGeometricError =
|
|
212
|
+
leafGeometricError ?? getGlobalTileLeafGeometricError(tile);
|
|
213
|
+
if (effectiveLeafGeometricError === null) {
|
|
132
214
|
return;
|
|
133
215
|
}
|
|
134
216
|
|
|
135
217
|
tile.geometricError =
|
|
136
|
-
|
|
137
|
-
(originalGeometricError -
|
|
138
|
-
getEffectiveGeometricErrorLayerScale();
|
|
218
|
+
effectiveLeafGeometricError +
|
|
219
|
+
(originalGeometricError - effectiveLeafGeometricError) * layerScale;
|
|
139
220
|
}
|
|
140
221
|
|
|
141
222
|
function applyLayerScaleToTileset() {
|
|
@@ -144,7 +225,11 @@ export function createGeometricErrorController({
|
|
|
144
225
|
return;
|
|
145
226
|
}
|
|
146
227
|
|
|
147
|
-
const
|
|
228
|
+
const layerScale = getEffectiveGeometricErrorLayerScale();
|
|
229
|
+
const leafGeometricError =
|
|
230
|
+
layerScale === 1
|
|
231
|
+
? null
|
|
232
|
+
: getGlobalTileLeafGeometricError(tiles.root, { forceRefresh: true });
|
|
148
233
|
tiles.traverse(
|
|
149
234
|
(tile) => {
|
|
150
235
|
applyLayerScaleToTile(tile, leafGeometricError);
|