3dtiles-inspector 0.2.6 → 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 +18 -0
- package/dist/inspector-assets/viewer/app.js +175 -195
- package/package.json +2 -2
- package/src/server/saveTransform.js +2 -0
- package/src/server/saveTransformHandler.js +2 -0
- package/src/server/splatCrop/gaussianPrimitives.js +48 -0
- package/src/server/splatCrop/gltfResource.js +4 -4
- package/src/server/splatCrop/index.js +21 -3
- package/src/server/splatCrop/traversal.js +239 -42
- package/src/server/splatCrop/worker.js +20 -2
- package/src/server/splatCrop/workerPool.js +37 -2
- package/src/viewer/app.js +2 -1
- package/src/viewer/scene/tiles.js +2 -2
- package/src/viewer/transform/geometricError.js +98 -13
|
@@ -6,6 +6,7 @@ const { assertPathInsideRoot } = require('./gltfResource');
|
|
|
6
6
|
const { getRootUpRotationMatrix } = require('./gaussianPrimitives');
|
|
7
7
|
const {
|
|
8
8
|
collectCandidateSplatResources,
|
|
9
|
+
deleteOrphanedSplatResources,
|
|
9
10
|
readTilesetJson,
|
|
10
11
|
traverseTileset,
|
|
11
12
|
} = require('./traversal');
|
|
@@ -35,6 +36,8 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
35
36
|
if (normalizedScreenSelections.length === 0) {
|
|
36
37
|
return {
|
|
37
38
|
deletedSplats: 0,
|
|
39
|
+
deletedSplatFiles: 0,
|
|
40
|
+
failedSplatFileDeletes: 0,
|
|
38
41
|
processedSplatResources: 0,
|
|
39
42
|
};
|
|
40
43
|
}
|
|
@@ -45,11 +48,12 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
45
48
|
|
|
46
49
|
const { THREE } = await getSplatCropModules();
|
|
47
50
|
const rootTileset = readTilesetJson(tilesetPath);
|
|
48
|
-
const
|
|
51
|
+
const initialResourcePaths = collectCandidateSplatResources({
|
|
49
52
|
rootDir,
|
|
50
53
|
tileset: rootTileset,
|
|
51
54
|
tilesetPath,
|
|
52
|
-
})
|
|
55
|
+
});
|
|
56
|
+
const totalResources = initialResourcePaths.size;
|
|
53
57
|
if (typeof onProgress === 'function') {
|
|
54
58
|
const readStreamHint = tileReadStreamsClosed
|
|
55
59
|
? ' Tile read streams closed.'
|
|
@@ -77,7 +81,7 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
77
81
|
const workerPool = new SplatCropWorkerPool(SPLAT_CROP_WORKER_COUNT);
|
|
78
82
|
|
|
79
83
|
try {
|
|
80
|
-
|
|
84
|
+
const traversalResult = await traverseTileset({
|
|
81
85
|
THREE,
|
|
82
86
|
tilesetPath,
|
|
83
87
|
tileset: rootTileset,
|
|
@@ -102,6 +106,20 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
102
106
|
resourceLocks: new Map(),
|
|
103
107
|
workerPool,
|
|
104
108
|
});
|
|
109
|
+
const remainingResourcePaths = collectCandidateSplatResources({
|
|
110
|
+
rootDir,
|
|
111
|
+
tilesetPath,
|
|
112
|
+
});
|
|
113
|
+
const cleanupResult = await deleteOrphanedSplatResources({
|
|
114
|
+
initialResourcePaths,
|
|
115
|
+
remainingResourcePaths,
|
|
116
|
+
rootDir,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
...traversalResult,
|
|
120
|
+
deletedSplatFiles: cleanupResult.deletedFiles.length,
|
|
121
|
+
failedSplatFileDeletes: cleanupResult.failedFiles.length,
|
|
122
|
+
};
|
|
105
123
|
} finally {
|
|
106
124
|
await workerPool.close();
|
|
107
125
|
}
|
|
@@ -20,7 +20,73 @@ const {
|
|
|
20
20
|
hasNonGaussianScenePrimitives,
|
|
21
21
|
hasScenePrimitives,
|
|
22
22
|
removeMeshPrimitives,
|
|
23
|
+
updateGaussianPrimitiveAccessorCounts,
|
|
23
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
|
+
}
|
|
24
90
|
|
|
25
91
|
function getContentSlots(tile) {
|
|
26
92
|
const slots = [];
|
|
@@ -399,6 +465,126 @@ function collectCandidateSplatResources({
|
|
|
399
465
|
return resourcePaths;
|
|
400
466
|
}
|
|
401
467
|
|
|
468
|
+
function collectLocalGltfBufferPaths(filePath, rootDir) {
|
|
469
|
+
const bufferPaths = new Set();
|
|
470
|
+
if (path.extname(filePath).toLowerCase() !== '.gltf') {
|
|
471
|
+
return bufferPaths;
|
|
472
|
+
}
|
|
473
|
+
if (!fs.existsSync(filePath)) {
|
|
474
|
+
return bufferPaths;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const json = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
478
|
+
const buffers = Array.isArray(json.buffers) ? json.buffers : [];
|
|
479
|
+
const resourceDir = path.dirname(filePath);
|
|
480
|
+
buffers.forEach((buffer, index) => {
|
|
481
|
+
const uri = buffer?.uri;
|
|
482
|
+
if (
|
|
483
|
+
typeof uri !== 'string' ||
|
|
484
|
+
uri.length === 0 ||
|
|
485
|
+
/^data:/i.test(uri) ||
|
|
486
|
+
isRemoteOrProtocolUri(uri)
|
|
487
|
+
) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
bufferPaths.add(
|
|
492
|
+
resolveLocalUri(
|
|
493
|
+
resourceDir,
|
|
494
|
+
rootDir,
|
|
495
|
+
uri,
|
|
496
|
+
`${filePath}.buffers[${index}].uri`,
|
|
497
|
+
),
|
|
498
|
+
);
|
|
499
|
+
});
|
|
500
|
+
return bufferPaths;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function collectReferencedGltfBufferPaths(resourcePaths, rootDir) {
|
|
504
|
+
const bufferPaths = new Set();
|
|
505
|
+
resourcePaths.forEach((resourcePath) => {
|
|
506
|
+
collectLocalGltfBufferPaths(resourcePath, rootDir).forEach((bufferPath) => {
|
|
507
|
+
bufferPaths.add(bufferPath);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
return bufferPaths;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function deleteFileIfSafe(filePath, rootDir) {
|
|
514
|
+
const resolvedPath = assertPathInsideRoot(
|
|
515
|
+
filePath,
|
|
516
|
+
rootDir,
|
|
517
|
+
'Orphaned splat resource path',
|
|
518
|
+
);
|
|
519
|
+
let stats;
|
|
520
|
+
try {
|
|
521
|
+
stats = await fs.promises.stat(resolvedPath);
|
|
522
|
+
} catch (err) {
|
|
523
|
+
if (err && err.code === 'ENOENT') {
|
|
524
|
+
return { deleted: false };
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
deleted: false,
|
|
528
|
+
error: err && err.message ? err.message : String(err),
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (!stats.isFile()) {
|
|
533
|
+
return { deleted: false };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
await fs.promises.unlink(resolvedPath);
|
|
538
|
+
return { deleted: true };
|
|
539
|
+
} catch (err) {
|
|
540
|
+
return {
|
|
541
|
+
deleted: false,
|
|
542
|
+
error: err && err.message ? err.message : String(err),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function deleteOrphanedSplatResources({
|
|
548
|
+
initialResourcePaths,
|
|
549
|
+
remainingResourcePaths,
|
|
550
|
+
rootDir,
|
|
551
|
+
}) {
|
|
552
|
+
const remainingBuffers = collectReferencedGltfBufferPaths(
|
|
553
|
+
remainingResourcePaths,
|
|
554
|
+
rootDir,
|
|
555
|
+
);
|
|
556
|
+
const deletePaths = new Set();
|
|
557
|
+
|
|
558
|
+
initialResourcePaths.forEach((resourcePath) => {
|
|
559
|
+
if (remainingResourcePaths.has(resourcePath)) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
deletePaths.add(resourcePath);
|
|
564
|
+
collectLocalGltfBufferPaths(resourcePath, rootDir).forEach((bufferPath) => {
|
|
565
|
+
if (
|
|
566
|
+
!remainingResourcePaths.has(bufferPath) &&
|
|
567
|
+
!remainingBuffers.has(bufferPath)
|
|
568
|
+
) {
|
|
569
|
+
deletePaths.add(bufferPath);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const deletedFiles = [];
|
|
575
|
+
const failedFiles = [];
|
|
576
|
+
for (const filePath of deletePaths) {
|
|
577
|
+
const result = await deleteFileIfSafe(filePath, rootDir);
|
|
578
|
+
if (result.deleted) {
|
|
579
|
+
deletedFiles.push(filePath);
|
|
580
|
+
} else if (result.error) {
|
|
581
|
+
failedFiles.push({ error: result.error, filePath });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return { deletedFiles, failedFiles };
|
|
586
|
+
}
|
|
587
|
+
|
|
402
588
|
function markSplatResourceProgress(context, resourcePath) {
|
|
403
589
|
const progress = context.progress;
|
|
404
590
|
if (
|
|
@@ -472,36 +658,17 @@ async function processGltfResource({
|
|
|
472
658
|
const resourceBounds = createBounds();
|
|
473
659
|
let hasResourceBounds = false;
|
|
474
660
|
let deletedSplats = 0;
|
|
475
|
-
|
|
661
|
+
let modified = false;
|
|
476
662
|
|
|
477
663
|
for (const [bufferViewIndex, viewDescriptors] of byBufferView) {
|
|
478
664
|
const slice = getBufferViewSlice(resource, bufferViewIndex);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
bytes: slice.bytes,
|
|
485
|
-
descriptors: viewDescriptors,
|
|
486
|
-
screenSelections,
|
|
487
|
-
workerPool,
|
|
488
|
-
}),
|
|
665
|
+
const rewrite = await rewriteSpzBytesInWorker({
|
|
666
|
+
bytes: slice.bytes,
|
|
667
|
+
descriptors: viewDescriptors,
|
|
668
|
+
screenSelections,
|
|
669
|
+
workerPool,
|
|
489
670
|
});
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const rewriteResults = await Promise.all(
|
|
493
|
-
rewriteTasks.map(async (task) => ({
|
|
494
|
-
...task,
|
|
495
|
-
rewrite: await task.promise,
|
|
496
|
-
})),
|
|
497
|
-
);
|
|
498
671
|
|
|
499
|
-
for (const {
|
|
500
|
-
bufferViewIndex,
|
|
501
|
-
rewrite,
|
|
502
|
-
slice,
|
|
503
|
-
viewDescriptors,
|
|
504
|
-
} of rewriteResults) {
|
|
505
672
|
deletedSplats += rewrite.deleted;
|
|
506
673
|
if (rewrite.bounds) {
|
|
507
674
|
hasResourceBounds =
|
|
@@ -511,6 +678,14 @@ async function processGltfResource({
|
|
|
511
678
|
emptyDescriptors.push(...viewDescriptors);
|
|
512
679
|
continue;
|
|
513
680
|
}
|
|
681
|
+
if (Number.isInteger(rewrite.survivorCount)) {
|
|
682
|
+
modified =
|
|
683
|
+
updateGaussianPrimitiveAccessorCounts(
|
|
684
|
+
resource,
|
|
685
|
+
viewDescriptors,
|
|
686
|
+
rewrite.survivorCount,
|
|
687
|
+
) > 0 || modified;
|
|
688
|
+
}
|
|
514
689
|
if (!rewrite.bytes) {
|
|
515
690
|
continue;
|
|
516
691
|
}
|
|
@@ -526,7 +701,6 @@ async function processGltfResource({
|
|
|
526
701
|
});
|
|
527
702
|
}
|
|
528
703
|
|
|
529
|
-
let modified = false;
|
|
530
704
|
if (emptyDescriptors.length > 0) {
|
|
531
705
|
modified = removeMeshPrimitives(resource, emptyDescriptors) > 0 || modified;
|
|
532
706
|
}
|
|
@@ -637,11 +811,13 @@ async function processGltfContentSlot(
|
|
|
637
811
|
context.resourceLocks,
|
|
638
812
|
resourcePath,
|
|
639
813
|
() =>
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
814
|
+
context.resourceLimiter.run(() =>
|
|
815
|
+
processLockedSplatResource(
|
|
816
|
+
context,
|
|
817
|
+
resourcePath,
|
|
818
|
+
tileSceneMatrix,
|
|
819
|
+
tileProjectionMatrix,
|
|
820
|
+
),
|
|
645
821
|
),
|
|
646
822
|
);
|
|
647
823
|
|
|
@@ -680,6 +856,8 @@ async function processNestedTilesetContentSlot(
|
|
|
680
856
|
emptySplatResources: context.emptySplatResources,
|
|
681
857
|
progress: context.progress,
|
|
682
858
|
resourceLocks: context.resourceLocks,
|
|
859
|
+
resourceLimiter: context.resourceLimiter,
|
|
860
|
+
traversalConcurrency: context.traversalConcurrency,
|
|
683
861
|
workerPool: context.workerPool,
|
|
684
862
|
});
|
|
685
863
|
|
|
@@ -733,6 +911,14 @@ async function processContentSlot(context, slot, tileTransform, tileProjection)
|
|
|
733
911
|
return createContentSlotResult(slot, { boundsKnown: false });
|
|
734
912
|
}
|
|
735
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
|
+
|
|
736
922
|
function applyContentResultsToTile(context, tile, contentResults) {
|
|
737
923
|
const contentBounds = [];
|
|
738
924
|
let boundsKnown = true;
|
|
@@ -764,16 +950,18 @@ async function pruneEmptyChildren(context, tile, tileTransform, tileProjection)
|
|
|
764
950
|
};
|
|
765
951
|
}
|
|
766
952
|
|
|
767
|
-
const children = tile.children.slice();
|
|
768
|
-
const childResults = await Promise.all(
|
|
769
|
-
children.map(async (child) => ({
|
|
770
|
-
child,
|
|
771
|
-
result: await visitTilesetTile(context, child, tileTransform, false),
|
|
772
|
-
})),
|
|
773
|
-
);
|
|
774
953
|
const childBounds = [];
|
|
775
954
|
let boundsKnown = true;
|
|
776
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
|
+
|
|
777
965
|
childResults.forEach(({ child, result }) => {
|
|
778
966
|
if (result.empty) {
|
|
779
967
|
context.tilesetModified = true;
|
|
@@ -831,10 +1019,11 @@ async function visitTilesetTile(context, tile, inheritedTransform, isRootTile) {
|
|
|
831
1019
|
isRootTile,
|
|
832
1020
|
);
|
|
833
1021
|
const tileProjection = getTileProjection(context.THREE, tile);
|
|
834
|
-
const contentResults = await
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1022
|
+
const contentResults = await processContentSlots(
|
|
1023
|
+
context,
|
|
1024
|
+
tile,
|
|
1025
|
+
worldTransform,
|
|
1026
|
+
tileProjection,
|
|
838
1027
|
);
|
|
839
1028
|
|
|
840
1029
|
const contentSummary = applyContentResultsToTile(context, tile, contentResults);
|
|
@@ -887,6 +1076,8 @@ async function traverseTileset({
|
|
|
887
1076
|
emptySplatResources,
|
|
888
1077
|
progress,
|
|
889
1078
|
resourceLocks,
|
|
1079
|
+
resourceLimiter = null,
|
|
1080
|
+
traversalConcurrency = SPLAT_CROP_WORKER_COUNT,
|
|
890
1081
|
workerPool,
|
|
891
1082
|
}) {
|
|
892
1083
|
const resolvedTilesetPath = assertPathInsideRoot(
|
|
@@ -910,6 +1101,8 @@ async function traverseTileset({
|
|
|
910
1101
|
throw new InspectorError(`${resolvedTilesetPath} must contain a root object.`);
|
|
911
1102
|
}
|
|
912
1103
|
|
|
1104
|
+
const normalizedTraversalConcurrency =
|
|
1105
|
+
normalizeConcurrency(traversalConcurrency);
|
|
913
1106
|
const context = {
|
|
914
1107
|
THREE,
|
|
915
1108
|
deletedSplats: 0,
|
|
@@ -918,11 +1111,14 @@ async function traverseTileset({
|
|
|
918
1111
|
processedResources,
|
|
919
1112
|
processedSplatResources: 0,
|
|
920
1113
|
resourceLocks,
|
|
1114
|
+
resourceLimiter:
|
|
1115
|
+
resourceLimiter || createAsyncLimiter(normalizedTraversalConcurrency),
|
|
921
1116
|
rootDir,
|
|
922
1117
|
rootTransform,
|
|
923
1118
|
screenSelections,
|
|
924
1119
|
tilesetDir: path.dirname(resolvedTilesetPath),
|
|
925
1120
|
tilesetModified: false,
|
|
1121
|
+
traversalConcurrency: normalizedTraversalConcurrency,
|
|
926
1122
|
upRotationMatrix,
|
|
927
1123
|
visitedTilesets,
|
|
928
1124
|
workerPool,
|
|
@@ -948,6 +1144,7 @@ async function traverseTileset({
|
|
|
948
1144
|
|
|
949
1145
|
module.exports = {
|
|
950
1146
|
collectCandidateSplatResources,
|
|
1147
|
+
deleteOrphanedSplatResources,
|
|
951
1148
|
readTilesetJson,
|
|
952
1149
|
traverseTileset,
|
|
953
1150
|
};
|
|
@@ -344,10 +344,24 @@ async function rewriteSpzBytes({
|
|
|
344
344
|
descriptors,
|
|
345
345
|
);
|
|
346
346
|
if (survivors.length === 0) {
|
|
347
|
-
return {
|
|
347
|
+
return {
|
|
348
|
+
bounds,
|
|
349
|
+
bytes: null,
|
|
350
|
+
deleted,
|
|
351
|
+
empty: true,
|
|
352
|
+
splatCount: spz.numSplats,
|
|
353
|
+
survivorCount: 0,
|
|
354
|
+
};
|
|
348
355
|
}
|
|
349
356
|
if (deleted === 0) {
|
|
350
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
bounds,
|
|
359
|
+
bytes: null,
|
|
360
|
+
deleted: 0,
|
|
361
|
+
empty: false,
|
|
362
|
+
splatCount: spz.numSplats,
|
|
363
|
+
survivorCount: survivors.length,
|
|
364
|
+
};
|
|
351
365
|
}
|
|
352
366
|
|
|
353
367
|
return {
|
|
@@ -355,6 +369,8 @@ async function rewriteSpzBytes({
|
|
|
355
369
|
bytes: await writeSurvivingSpzBytes(SpzWriter, spz, splatData, survivors),
|
|
356
370
|
deleted,
|
|
357
371
|
empty: false,
|
|
372
|
+
splatCount: spz.numSplats,
|
|
373
|
+
survivorCount: survivors.length,
|
|
358
374
|
};
|
|
359
375
|
}
|
|
360
376
|
|
|
@@ -380,6 +396,8 @@ parentPort.on('message', async (message) => {
|
|
|
380
396
|
bytes,
|
|
381
397
|
deleted: result.deleted,
|
|
382
398
|
empty: result.empty,
|
|
399
|
+
splatCount: result.splatCount,
|
|
400
|
+
survivorCount: result.survivorCount,
|
|
383
401
|
},
|
|
384
402
|
},
|
|
385
403
|
transferList,
|
|
@@ -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,9 +100,17 @@ 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,
|
|
112
|
+
splatCount: Number(result.splatCount || 0),
|
|
113
|
+
survivorCount: Number(result.survivorCount || 0),
|
|
80
114
|
});
|
|
81
115
|
}
|
|
82
116
|
|
|
@@ -177,6 +211,7 @@ class SplatCropWorkerPool {
|
|
|
177
211
|
}
|
|
178
212
|
|
|
179
213
|
module.exports = {
|
|
214
|
+
getDefaultSplatCropWorkerCount,
|
|
180
215
|
SPLAT_CROP_WORKER_COUNT,
|
|
181
216
|
SplatCropWorkerPool,
|
|
182
217
|
};
|
package/src/viewer/app.js
CHANGED
|
@@ -550,10 +550,11 @@ async function saveTransform() {
|
|
|
550
550
|
const processedSplatResources = Number(
|
|
551
551
|
payload.processedSplatResources || 0,
|
|
552
552
|
);
|
|
553
|
+
const deletedSplatFiles = Number(payload.deletedSplatFiles || 0);
|
|
553
554
|
cropController.clearAll();
|
|
554
555
|
loadTileset(TILESET_URL, { frameOnLoad: false });
|
|
555
556
|
setStatus(
|
|
556
|
-
`Saved transform and deleted ${deletedSplats} cropped splats from ${processedSplatResources} splat resource${processedSplatResources === 1 ? '' : 's'}. Reloading tileset.`,
|
|
557
|
+
`Saved transform and deleted ${deletedSplats} cropped splats from ${processedSplatResources} splat resource${processedSplatResources === 1 ? '' : 's'}${deletedSplatFiles > 0 ? `, removing ${deletedSplatFiles} orphaned file${deletedSplatFiles === 1 ? '' : 's'}` : ''}. Reloading tileset.`,
|
|
557
558
|
);
|
|
558
559
|
} else {
|
|
559
560
|
setStatus(
|
|
@@ -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
|
}
|