3dtiles-inspector 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +2 -0
- package/dist/inspector-assets/viewer/app.js +133 -2
- package/package.json +1 -1
- package/src/viewer/app.js +160 -2
- package/src/viewer/session.js +273 -51
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.6] - 2026-04-27
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Changed `Layer Multiplier` to scale each tile's geometric-error difference from its leaf baseline instead of applying a depth-based multiplier.
|
|
14
|
+
- Expanded the `Layer Multiplier` range to `1/8x` through `8x`.
|
|
15
|
+
|
|
16
|
+
## [0.1.5] - 2026-04-27
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added an LOD `Layer Multiplier` slider to scale geometric errors progressively by distance from leaf tiles.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Tightened inspector toolbar spacing and LOD slider value labels.
|
|
25
|
+
|
|
9
26
|
## [0.1.4] - 2026-04-25
|
|
10
27
|
|
|
11
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ const {
|
|
|
82
82
|
- `Set Position` to click the globe, terrain, or loaded tiles and place the tileset there
|
|
83
83
|
- `Terrain` to toggle Cesium World Terrain while keeping satellite imagery
|
|
84
84
|
- `Geometric Error` scaling from `1/16x` to `16x`
|
|
85
|
+
- `Layer Multiplier` scaling from `1/8x` to `8x` for each tile's geometric-error difference from its leaf baseline
|
|
85
86
|
- `Save` to persist the updated root transform and geometric-error scale back to disk
|
|
86
87
|
|
|
87
88
|
If `build_summary.json` exists next to the root tileset, `Save` also updates:
|
|
@@ -90,6 +91,7 @@ If `build_summary.json` exists next to the root tileset, `Save` also updates:
|
|
|
90
91
|
- `root_transform_source`
|
|
91
92
|
- `root_coordinate`
|
|
92
93
|
- `viewer_geometric_error_scale`
|
|
94
|
+
- `viewer_geometric_error_layer_scale`
|
|
93
95
|
|
|
94
96
|
## Package Surface
|
|
95
97
|
|
|
@@ -65484,12 +65484,21 @@ var geometricErrorScaleInput = document.getElementById(
|
|
|
65484
65484
|
"geometric-error-scale"
|
|
65485
65485
|
);
|
|
65486
65486
|
var geometricErrorValueEl = document.getElementById("geometric-error-value");
|
|
65487
|
+
var geometricErrorLayerScaleInput = document.getElementById(
|
|
65488
|
+
"geometric-error-layer-scale"
|
|
65489
|
+
);
|
|
65490
|
+
var geometricErrorLayerValueEl = document.getElementById(
|
|
65491
|
+
"geometric-error-layer-value"
|
|
65492
|
+
);
|
|
65487
65493
|
var setPositionButton = document.getElementById("set-position");
|
|
65488
65494
|
var resetButton = document.getElementById("reset");
|
|
65489
65495
|
var saveButton = document.getElementById("save");
|
|
65490
65496
|
var GEOMETRIC_ERROR_SCALE_MIN_EXPONENT = -4;
|
|
65491
65497
|
var GEOMETRIC_ERROR_SCALE_MAX_EXPONENT = 4;
|
|
65492
65498
|
var GEOMETRIC_ERROR_SCALE_STEP = 0.1;
|
|
65499
|
+
var GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT = -3;
|
|
65500
|
+
var GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT = 3;
|
|
65501
|
+
var GEOMETRIC_ERROR_LAYER_SCALE_STEP = 0.1;
|
|
65493
65502
|
var DEFAULT_ERROR_TARGET = 16;
|
|
65494
65503
|
var DEFAULT_TERRAIN_ERROR_TARGET = 16;
|
|
65495
65504
|
var RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
|
|
@@ -65801,12 +65810,16 @@ var savedRootInverseMatrix = new Matrix4();
|
|
|
65801
65810
|
var pointerCoords = new Vector2();
|
|
65802
65811
|
var pickRaycaster = new Raycaster();
|
|
65803
65812
|
var pickTargets = [];
|
|
65813
|
+
var originalTileGeometricErrors = /* @__PURE__ */ new WeakMap();
|
|
65804
65814
|
var tiles = null;
|
|
65805
65815
|
var toolbarVisible = true;
|
|
65806
65816
|
var activeTransformMode = null;
|
|
65807
65817
|
var geometricErrorScaleExponent = 0;
|
|
65808
65818
|
var geometricErrorScale = 1;
|
|
65809
65819
|
var lastSavedGeometricErrorScale = 1;
|
|
65820
|
+
var geometricErrorLayerScaleExponent = 0;
|
|
65821
|
+
var geometricErrorLayerScale = 1;
|
|
65822
|
+
var lastSavedGeometricErrorLayerScale = 1;
|
|
65810
65823
|
var lastSavedMatrix = new Matrix4();
|
|
65811
65824
|
var savedRootMatrix = new Matrix4();
|
|
65812
65825
|
var savedRootMatrixPromise = Promise.resolve();
|
|
@@ -65831,9 +65844,77 @@ function updateGeometricErrorScaleDisplay() {
|
|
|
65831
65844
|
geometricErrorScale
|
|
65832
65845
|
)}`;
|
|
65833
65846
|
}
|
|
65847
|
+
function updateGeometricErrorLayerScaleDisplay() {
|
|
65848
|
+
geometricErrorLayerValueEl.textContent = `x${formatGeometricErrorScale(
|
|
65849
|
+
geometricErrorLayerScale
|
|
65850
|
+
)}`;
|
|
65851
|
+
}
|
|
65834
65852
|
function getEffectiveGeometricErrorScale() {
|
|
65835
65853
|
return lastSavedGeometricErrorScale * geometricErrorScale;
|
|
65836
65854
|
}
|
|
65855
|
+
function getEffectiveGeometricErrorLayerScale() {
|
|
65856
|
+
return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
|
|
65857
|
+
}
|
|
65858
|
+
function getOriginalTileGeometricError(tile) {
|
|
65859
|
+
if (!tile || typeof tile !== "object") {
|
|
65860
|
+
return null;
|
|
65861
|
+
}
|
|
65862
|
+
if (!originalTileGeometricErrors.has(tile)) {
|
|
65863
|
+
const number = Number(tile.geometricError);
|
|
65864
|
+
if (!Number.isFinite(number)) {
|
|
65865
|
+
return null;
|
|
65866
|
+
}
|
|
65867
|
+
originalTileGeometricErrors.set(tile, number);
|
|
65868
|
+
}
|
|
65869
|
+
return originalTileGeometricErrors.get(tile);
|
|
65870
|
+
}
|
|
65871
|
+
function getKnownTileLeafGeometricError(tile, visited = /* @__PURE__ */ new Set()) {
|
|
65872
|
+
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
65873
|
+
if (originalGeometricError === null || !tile || typeof tile !== "object" || visited.has(tile)) {
|
|
65874
|
+
return originalGeometricError;
|
|
65875
|
+
}
|
|
65876
|
+
visited.add(tile);
|
|
65877
|
+
let leafGeometricError = null;
|
|
65878
|
+
const children = Array.isArray(tile.children) ? tile.children : [];
|
|
65879
|
+
for (const child of children) {
|
|
65880
|
+
const childLeafGeometricError = getKnownTileLeafGeometricError(
|
|
65881
|
+
child,
|
|
65882
|
+
visited
|
|
65883
|
+
);
|
|
65884
|
+
if (childLeafGeometricError !== null) {
|
|
65885
|
+
leafGeometricError = leafGeometricError === null ? childLeafGeometricError : Math.min(leafGeometricError, childLeafGeometricError);
|
|
65886
|
+
}
|
|
65887
|
+
}
|
|
65888
|
+
visited.delete(tile);
|
|
65889
|
+
return leafGeometricError === null ? originalGeometricError : leafGeometricError;
|
|
65890
|
+
}
|
|
65891
|
+
function applyGeometricErrorLayerScaleToTile(tile) {
|
|
65892
|
+
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
65893
|
+
const leafGeometricError = getKnownTileLeafGeometricError(tile);
|
|
65894
|
+
if (originalGeometricError === null || leafGeometricError === null) {
|
|
65895
|
+
return;
|
|
65896
|
+
}
|
|
65897
|
+
tile.geometricError = leafGeometricError + (originalGeometricError - leafGeometricError) * getEffectiveGeometricErrorLayerScale();
|
|
65898
|
+
}
|
|
65899
|
+
function applyGeometricErrorLayerScaleToTileset() {
|
|
65900
|
+
if (!tiles) {
|
|
65901
|
+
return;
|
|
65902
|
+
}
|
|
65903
|
+
tiles.traverse(
|
|
65904
|
+
(tile) => {
|
|
65905
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
65906
|
+
return false;
|
|
65907
|
+
},
|
|
65908
|
+
null,
|
|
65909
|
+
false
|
|
65910
|
+
);
|
|
65911
|
+
}
|
|
65912
|
+
var geometricErrorLayerScalePlugin = {
|
|
65913
|
+
name: "GeometricErrorLayerScalePlugin",
|
|
65914
|
+
preprocessNode(tile) {
|
|
65915
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
65916
|
+
}
|
|
65917
|
+
};
|
|
65837
65918
|
function getGaussianMeshSplatCount(mesh) {
|
|
65838
65919
|
if (!mesh || typeof mesh !== "object") {
|
|
65839
65920
|
return 0;
|
|
@@ -65898,6 +65979,19 @@ function setGeometricErrorScaleExponent(exponent) {
|
|
|
65898
65979
|
updateGeometricErrorScaleDisplay();
|
|
65899
65980
|
updateTilesetErrorTarget();
|
|
65900
65981
|
}
|
|
65982
|
+
function setGeometricErrorLayerScaleExponent(exponent) {
|
|
65983
|
+
geometricErrorLayerScaleExponent = clamp2(
|
|
65984
|
+
Number(exponent),
|
|
65985
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT,
|
|
65986
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT
|
|
65987
|
+
);
|
|
65988
|
+
geometricErrorLayerScale = exponentToGeometricErrorScale(
|
|
65989
|
+
geometricErrorLayerScaleExponent
|
|
65990
|
+
);
|
|
65991
|
+
geometricErrorLayerScaleInput.value = geometricErrorLayerScaleExponent.toFixed(1);
|
|
65992
|
+
updateGeometricErrorLayerScaleDisplay();
|
|
65993
|
+
applyGeometricErrorLayerScaleToTileset();
|
|
65994
|
+
}
|
|
65901
65995
|
function syncTerrainButton() {
|
|
65902
65996
|
terrainButton.classList.toggle("active", terrainEnabled);
|
|
65903
65997
|
terrainLight.visible = terrainEnabled;
|
|
@@ -65979,7 +66073,15 @@ function toggleTransformMode(mode) {
|
|
|
65979
66073
|
geometricErrorScaleInput.min = String(GEOMETRIC_ERROR_SCALE_MIN_EXPONENT);
|
|
65980
66074
|
geometricErrorScaleInput.max = String(GEOMETRIC_ERROR_SCALE_MAX_EXPONENT);
|
|
65981
66075
|
geometricErrorScaleInput.step = String(GEOMETRIC_ERROR_SCALE_STEP);
|
|
66076
|
+
geometricErrorLayerScaleInput.min = String(
|
|
66077
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT
|
|
66078
|
+
);
|
|
66079
|
+
geometricErrorLayerScaleInput.max = String(
|
|
66080
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT
|
|
66081
|
+
);
|
|
66082
|
+
geometricErrorLayerScaleInput.step = String(GEOMETRIC_ERROR_LAYER_SCALE_STEP);
|
|
65982
66083
|
setGeometricErrorScaleExponent(geometricErrorScaleExponent);
|
|
66084
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleExponent);
|
|
65983
66085
|
setTerrainEnabled(terrainEnabled);
|
|
65984
66086
|
setTransformMode(activeTransformMode);
|
|
65985
66087
|
syncToolbarVisibility();
|
|
@@ -66473,7 +66575,9 @@ function loadTileset(url) {
|
|
|
66473
66575
|
updateRuntimeStats(true);
|
|
66474
66576
|
resetEditableGroup();
|
|
66475
66577
|
lastSavedGeometricErrorScale = 1;
|
|
66578
|
+
lastSavedGeometricErrorLayerScale = 1;
|
|
66476
66579
|
setGeometricErrorScaleExponent(0);
|
|
66580
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
66477
66581
|
savedRootMatrix.identity();
|
|
66478
66582
|
savedRootMatrixLoadError = null;
|
|
66479
66583
|
savedRootMatrixPromise = refreshSavedRootMatrix(url).then(
|
|
@@ -66496,7 +66600,16 @@ function loadTileset(url) {
|
|
|
66496
66600
|
next.registerPlugin(new go());
|
|
66497
66601
|
next.registerPlugin(new To());
|
|
66498
66602
|
next.registerPlugin(new w2());
|
|
66499
|
-
next.registerPlugin(
|
|
66603
|
+
next.registerPlugin(geometricErrorLayerScalePlugin);
|
|
66604
|
+
next.registerPlugin(
|
|
66605
|
+
new GaussianSplatPlugin({
|
|
66606
|
+
renderer,
|
|
66607
|
+
scene,
|
|
66608
|
+
sparkRendererOptions: {
|
|
66609
|
+
accumExtSplats: true
|
|
66610
|
+
}
|
|
66611
|
+
})
|
|
66612
|
+
);
|
|
66500
66613
|
debugTilesPlugin = new Oo({
|
|
66501
66614
|
displayBoxBounds: showBoundingVolume,
|
|
66502
66615
|
displaySphereBounds: showBoundingVolume,
|
|
@@ -66543,6 +66656,7 @@ function loadTileset(url) {
|
|
|
66543
66656
|
setStatus("Tileset ready.");
|
|
66544
66657
|
}
|
|
66545
66658
|
};
|
|
66659
|
+
next.addEventListener("load-tileset", applyGeometricErrorLayerScaleToTileset);
|
|
66546
66660
|
next.addEventListener("load-tile-set", tryFrame);
|
|
66547
66661
|
next.addEventListener("load-tileset", tryFrame);
|
|
66548
66662
|
}
|
|
@@ -66554,6 +66668,8 @@ async function saveTransform() {
|
|
|
66554
66668
|
const incrementalMatrix = currentMatrix.clone().multiply(lastSavedMatrix.clone().invert());
|
|
66555
66669
|
const incrementalGeometricErrorScale = geometricErrorScale;
|
|
66556
66670
|
const savedGeometricErrorScale = getEffectiveGeometricErrorScale();
|
|
66671
|
+
const incrementalGeometricErrorLayerScale = geometricErrorLayerScale;
|
|
66672
|
+
const savedGeometricErrorLayerScale = getEffectiveGeometricErrorLayerScale();
|
|
66557
66673
|
try {
|
|
66558
66674
|
const response = await fetch(SAVE_URL, {
|
|
66559
66675
|
method: "POST",
|
|
@@ -66561,6 +66677,7 @@ async function saveTransform() {
|
|
|
66561
66677
|
"Content-Type": "application/json"
|
|
66562
66678
|
},
|
|
66563
66679
|
body: JSON.stringify({
|
|
66680
|
+
geometricErrorLayerScale: incrementalGeometricErrorLayerScale,
|
|
66564
66681
|
geometricErrorScale: incrementalGeometricErrorScale,
|
|
66565
66682
|
transform: incrementalMatrix.toArray()
|
|
66566
66683
|
})
|
|
@@ -66591,13 +66708,17 @@ async function saveTransform() {
|
|
|
66591
66708
|
}
|
|
66592
66709
|
}
|
|
66593
66710
|
lastSavedGeometricErrorScale = savedGeometricErrorScale;
|
|
66711
|
+
lastSavedGeometricErrorLayerScale = savedGeometricErrorLayerScale;
|
|
66594
66712
|
lastSavedMatrix.copy(currentMatrix);
|
|
66595
66713
|
setGeometricErrorScaleExponent(0);
|
|
66714
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
66596
66715
|
syncTransformHandleFromTilesTransform();
|
|
66597
66716
|
syncCoordinateInputsFromTilesTransform();
|
|
66598
66717
|
setStatus(
|
|
66599
|
-
`Saved transform
|
|
66718
|
+
`Saved transform, geometric-error scale x${formatGeometricErrorScale(
|
|
66600
66719
|
savedGeometricErrorScale
|
|
66720
|
+
)}, and layer multiplier x${formatGeometricErrorScale(
|
|
66721
|
+
savedGeometricErrorLayerScale
|
|
66601
66722
|
)} to ${ROOT_TILESET_LABEL} and build_summary.json.`
|
|
66602
66723
|
);
|
|
66603
66724
|
} catch (err2) {
|
|
@@ -66638,6 +66759,16 @@ geometricErrorScaleInput.addEventListener("change", () => {
|
|
|
66638
66759
|
)}.`
|
|
66639
66760
|
);
|
|
66640
66761
|
});
|
|
66762
|
+
geometricErrorLayerScaleInput.addEventListener("input", () => {
|
|
66763
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleInput.value);
|
|
66764
|
+
});
|
|
66765
|
+
geometricErrorLayerScaleInput.addEventListener("change", () => {
|
|
66766
|
+
setStatus(
|
|
66767
|
+
`Geometric-error layer multiplier set to x${formatGeometricErrorScale(
|
|
66768
|
+
geometricErrorLayerScale
|
|
66769
|
+
)}.`
|
|
66770
|
+
);
|
|
66771
|
+
});
|
|
66641
66772
|
moveToTilesButton.addEventListener("click", moveCameraToTiles);
|
|
66642
66773
|
moveCameraToCoordinateButton.addEventListener("click", moveCameraToCoordinate);
|
|
66643
66774
|
moveTilesToCoordinateButton.addEventListener("click", moveTilesToCoordinate);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "3dtiles-inspector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|
package/src/viewer/app.js
CHANGED
|
@@ -100,12 +100,21 @@ const geometricErrorScaleInput = document.getElementById(
|
|
|
100
100
|
'geometric-error-scale',
|
|
101
101
|
);
|
|
102
102
|
const geometricErrorValueEl = document.getElementById('geometric-error-value');
|
|
103
|
+
const geometricErrorLayerScaleInput = document.getElementById(
|
|
104
|
+
'geometric-error-layer-scale',
|
|
105
|
+
);
|
|
106
|
+
const geometricErrorLayerValueEl = document.getElementById(
|
|
107
|
+
'geometric-error-layer-value',
|
|
108
|
+
);
|
|
103
109
|
const setPositionButton = document.getElementById('set-position');
|
|
104
110
|
const resetButton = document.getElementById('reset');
|
|
105
111
|
const saveButton = document.getElementById('save');
|
|
106
112
|
const GEOMETRIC_ERROR_SCALE_MIN_EXPONENT = -4;
|
|
107
113
|
const GEOMETRIC_ERROR_SCALE_MAX_EXPONENT = 4;
|
|
108
114
|
const GEOMETRIC_ERROR_SCALE_STEP = 0.1;
|
|
115
|
+
const GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT = -3;
|
|
116
|
+
const GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT = 3;
|
|
117
|
+
const GEOMETRIC_ERROR_LAYER_SCALE_STEP = 0.1;
|
|
109
118
|
const DEFAULT_ERROR_TARGET = 16;
|
|
110
119
|
const DEFAULT_TERRAIN_ERROR_TARGET = 16;
|
|
111
120
|
const RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
|
|
@@ -470,12 +479,16 @@ const savedRootInverseMatrix = new Matrix4();
|
|
|
470
479
|
const pointerCoords = new Vector2();
|
|
471
480
|
const pickRaycaster = new Raycaster();
|
|
472
481
|
const pickTargets = [];
|
|
482
|
+
const originalTileGeometricErrors = new WeakMap();
|
|
473
483
|
let tiles = null;
|
|
474
484
|
let toolbarVisible = true;
|
|
475
485
|
let activeTransformMode = null;
|
|
476
486
|
let geometricErrorScaleExponent = 0;
|
|
477
487
|
let geometricErrorScale = 1;
|
|
478
488
|
let lastSavedGeometricErrorScale = 1;
|
|
489
|
+
let geometricErrorLayerScaleExponent = 0;
|
|
490
|
+
let geometricErrorLayerScale = 1;
|
|
491
|
+
let lastSavedGeometricErrorLayerScale = 1;
|
|
479
492
|
let lastSavedMatrix = new Matrix4();
|
|
480
493
|
const savedRootMatrix = new Matrix4();
|
|
481
494
|
let savedRootMatrixPromise = Promise.resolve();
|
|
@@ -505,10 +518,103 @@ function updateGeometricErrorScaleDisplay() {
|
|
|
505
518
|
)}`;
|
|
506
519
|
}
|
|
507
520
|
|
|
521
|
+
function updateGeometricErrorLayerScaleDisplay() {
|
|
522
|
+
geometricErrorLayerValueEl.textContent = `x${formatGeometricErrorScale(
|
|
523
|
+
geometricErrorLayerScale,
|
|
524
|
+
)}`;
|
|
525
|
+
}
|
|
526
|
+
|
|
508
527
|
function getEffectiveGeometricErrorScale() {
|
|
509
528
|
return lastSavedGeometricErrorScale * geometricErrorScale;
|
|
510
529
|
}
|
|
511
530
|
|
|
531
|
+
function getEffectiveGeometricErrorLayerScale() {
|
|
532
|
+
return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function getOriginalTileGeometricError(tile) {
|
|
536
|
+
if (!tile || typeof tile !== 'object') {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (!originalTileGeometricErrors.has(tile)) {
|
|
541
|
+
const number = Number(tile.geometricError);
|
|
542
|
+
if (!Number.isFinite(number)) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
originalTileGeometricErrors.set(tile, number);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return originalTileGeometricErrors.get(tile);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function getKnownTileLeafGeometricError(tile, visited = new Set()) {
|
|
552
|
+
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
553
|
+
if (
|
|
554
|
+
originalGeometricError === null ||
|
|
555
|
+
!tile ||
|
|
556
|
+
typeof tile !== 'object' ||
|
|
557
|
+
visited.has(tile)
|
|
558
|
+
) {
|
|
559
|
+
return originalGeometricError;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
visited.add(tile);
|
|
563
|
+
let leafGeometricError = null;
|
|
564
|
+
const children = Array.isArray(tile.children) ? tile.children : [];
|
|
565
|
+
for (const child of children) {
|
|
566
|
+
const childLeafGeometricError = getKnownTileLeafGeometricError(
|
|
567
|
+
child,
|
|
568
|
+
visited,
|
|
569
|
+
);
|
|
570
|
+
if (childLeafGeometricError !== null) {
|
|
571
|
+
leafGeometricError =
|
|
572
|
+
leafGeometricError === null
|
|
573
|
+
? childLeafGeometricError
|
|
574
|
+
: Math.min(leafGeometricError, childLeafGeometricError);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
visited.delete(tile);
|
|
578
|
+
return leafGeometricError === null
|
|
579
|
+
? originalGeometricError
|
|
580
|
+
: leafGeometricError;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function applyGeometricErrorLayerScaleToTile(tile) {
|
|
584
|
+
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
585
|
+
const leafGeometricError = getKnownTileLeafGeometricError(tile);
|
|
586
|
+
if (originalGeometricError === null || leafGeometricError === null) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
tile.geometricError =
|
|
591
|
+
leafGeometricError +
|
|
592
|
+
(originalGeometricError - leafGeometricError) *
|
|
593
|
+
getEffectiveGeometricErrorLayerScale();
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function applyGeometricErrorLayerScaleToTileset() {
|
|
597
|
+
if (!tiles) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
tiles.traverse(
|
|
602
|
+
(tile) => {
|
|
603
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
604
|
+
return false;
|
|
605
|
+
},
|
|
606
|
+
null,
|
|
607
|
+
false,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const geometricErrorLayerScalePlugin = {
|
|
612
|
+
name: 'GeometricErrorLayerScalePlugin',
|
|
613
|
+
preprocessNode(tile) {
|
|
614
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
|
|
512
618
|
function getGaussianMeshSplatCount(mesh) {
|
|
513
619
|
if (!mesh || typeof mesh !== 'object') {
|
|
514
620
|
return 0;
|
|
@@ -605,6 +711,21 @@ function setGeometricErrorScaleExponent(exponent) {
|
|
|
605
711
|
updateTilesetErrorTarget();
|
|
606
712
|
}
|
|
607
713
|
|
|
714
|
+
function setGeometricErrorLayerScaleExponent(exponent) {
|
|
715
|
+
geometricErrorLayerScaleExponent = clamp(
|
|
716
|
+
Number(exponent),
|
|
717
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT,
|
|
718
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT,
|
|
719
|
+
);
|
|
720
|
+
geometricErrorLayerScale = exponentToGeometricErrorScale(
|
|
721
|
+
geometricErrorLayerScaleExponent,
|
|
722
|
+
);
|
|
723
|
+
geometricErrorLayerScaleInput.value =
|
|
724
|
+
geometricErrorLayerScaleExponent.toFixed(1);
|
|
725
|
+
updateGeometricErrorLayerScaleDisplay();
|
|
726
|
+
applyGeometricErrorLayerScaleToTileset();
|
|
727
|
+
}
|
|
728
|
+
|
|
608
729
|
function syncTerrainButton() {
|
|
609
730
|
terrainButton.classList.toggle('active', terrainEnabled);
|
|
610
731
|
terrainLight.visible = terrainEnabled;
|
|
@@ -702,7 +823,15 @@ function toggleTransformMode(mode) {
|
|
|
702
823
|
geometricErrorScaleInput.min = String(GEOMETRIC_ERROR_SCALE_MIN_EXPONENT);
|
|
703
824
|
geometricErrorScaleInput.max = String(GEOMETRIC_ERROR_SCALE_MAX_EXPONENT);
|
|
704
825
|
geometricErrorScaleInput.step = String(GEOMETRIC_ERROR_SCALE_STEP);
|
|
826
|
+
geometricErrorLayerScaleInput.min = String(
|
|
827
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT,
|
|
828
|
+
);
|
|
829
|
+
geometricErrorLayerScaleInput.max = String(
|
|
830
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT,
|
|
831
|
+
);
|
|
832
|
+
geometricErrorLayerScaleInput.step = String(GEOMETRIC_ERROR_LAYER_SCALE_STEP);
|
|
705
833
|
setGeometricErrorScaleExponent(geometricErrorScaleExponent);
|
|
834
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleExponent);
|
|
706
835
|
setTerrainEnabled(terrainEnabled);
|
|
707
836
|
setTransformMode(activeTransformMode);
|
|
708
837
|
syncToolbarVisibility();
|
|
@@ -1324,7 +1453,9 @@ function loadTileset(url) {
|
|
|
1324
1453
|
|
|
1325
1454
|
resetEditableGroup();
|
|
1326
1455
|
lastSavedGeometricErrorScale = 1;
|
|
1456
|
+
lastSavedGeometricErrorLayerScale = 1;
|
|
1327
1457
|
setGeometricErrorScaleExponent(0);
|
|
1458
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
1328
1459
|
savedRootMatrix.identity();
|
|
1329
1460
|
savedRootMatrixLoadError = null;
|
|
1330
1461
|
savedRootMatrixPromise = refreshSavedRootMatrix(url).then(
|
|
@@ -1348,7 +1479,16 @@ function loadTileset(url) {
|
|
|
1348
1479
|
next.registerPlugin(new TileCompressionPlugin());
|
|
1349
1480
|
next.registerPlugin(new UnloadTilesPlugin());
|
|
1350
1481
|
next.registerPlugin(new ImplicitTilingPlugin());
|
|
1351
|
-
next.registerPlugin(
|
|
1482
|
+
next.registerPlugin(geometricErrorLayerScalePlugin);
|
|
1483
|
+
next.registerPlugin(
|
|
1484
|
+
new GaussianSplatPlugin({
|
|
1485
|
+
renderer,
|
|
1486
|
+
scene,
|
|
1487
|
+
sparkRendererOptions: {
|
|
1488
|
+
accumExtSplats: true,
|
|
1489
|
+
},
|
|
1490
|
+
}),
|
|
1491
|
+
);
|
|
1352
1492
|
debugTilesPlugin = new DebugTilesPlugin({
|
|
1353
1493
|
displayBoxBounds: showBoundingVolume,
|
|
1354
1494
|
displaySphereBounds: showBoundingVolume,
|
|
@@ -1399,6 +1539,7 @@ function loadTileset(url) {
|
|
|
1399
1539
|
}
|
|
1400
1540
|
};
|
|
1401
1541
|
|
|
1542
|
+
next.addEventListener('load-tileset', applyGeometricErrorLayerScaleToTileset);
|
|
1402
1543
|
next.addEventListener('load-tile-set', tryFrame);
|
|
1403
1544
|
next.addEventListener('load-tileset', tryFrame);
|
|
1404
1545
|
}
|
|
@@ -1414,6 +1555,8 @@ async function saveTransform() {
|
|
|
1414
1555
|
.multiply(lastSavedMatrix.clone().invert());
|
|
1415
1556
|
const incrementalGeometricErrorScale = geometricErrorScale;
|
|
1416
1557
|
const savedGeometricErrorScale = getEffectiveGeometricErrorScale();
|
|
1558
|
+
const incrementalGeometricErrorLayerScale = geometricErrorLayerScale;
|
|
1559
|
+
const savedGeometricErrorLayerScale = getEffectiveGeometricErrorLayerScale();
|
|
1417
1560
|
|
|
1418
1561
|
try {
|
|
1419
1562
|
const response = await fetch(SAVE_URL, {
|
|
@@ -1422,6 +1565,7 @@ async function saveTransform() {
|
|
|
1422
1565
|
'Content-Type': 'application/json',
|
|
1423
1566
|
},
|
|
1424
1567
|
body: JSON.stringify({
|
|
1568
|
+
geometricErrorLayerScale: incrementalGeometricErrorLayerScale,
|
|
1425
1569
|
geometricErrorScale: incrementalGeometricErrorScale,
|
|
1426
1570
|
transform: incrementalMatrix.toArray(),
|
|
1427
1571
|
}),
|
|
@@ -1452,13 +1596,17 @@ async function saveTransform() {
|
|
|
1452
1596
|
}
|
|
1453
1597
|
}
|
|
1454
1598
|
lastSavedGeometricErrorScale = savedGeometricErrorScale;
|
|
1599
|
+
lastSavedGeometricErrorLayerScale = savedGeometricErrorLayerScale;
|
|
1455
1600
|
lastSavedMatrix.copy(currentMatrix);
|
|
1456
1601
|
setGeometricErrorScaleExponent(0);
|
|
1602
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
1457
1603
|
syncTransformHandleFromTilesTransform();
|
|
1458
1604
|
syncCoordinateInputsFromTilesTransform();
|
|
1459
1605
|
setStatus(
|
|
1460
|
-
`Saved transform
|
|
1606
|
+
`Saved transform, geometric-error scale x${formatGeometricErrorScale(
|
|
1461
1607
|
savedGeometricErrorScale,
|
|
1608
|
+
)}, and layer multiplier x${formatGeometricErrorScale(
|
|
1609
|
+
savedGeometricErrorLayerScale,
|
|
1462
1610
|
)} to ${ROOT_TILESET_LABEL} and build_summary.json.`,
|
|
1463
1611
|
);
|
|
1464
1612
|
} catch (err) {
|
|
@@ -1506,6 +1654,16 @@ geometricErrorScaleInput.addEventListener('change', () => {
|
|
|
1506
1654
|
)}.`,
|
|
1507
1655
|
);
|
|
1508
1656
|
});
|
|
1657
|
+
geometricErrorLayerScaleInput.addEventListener('input', () => {
|
|
1658
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleInput.value);
|
|
1659
|
+
});
|
|
1660
|
+
geometricErrorLayerScaleInput.addEventListener('change', () => {
|
|
1661
|
+
setStatus(
|
|
1662
|
+
`Geometric-error layer multiplier set to x${formatGeometricErrorScale(
|
|
1663
|
+
geometricErrorLayerScale,
|
|
1664
|
+
)}.`,
|
|
1665
|
+
);
|
|
1666
|
+
});
|
|
1509
1667
|
moveToTilesButton.addEventListener('click', moveCameraToTiles);
|
|
1510
1668
|
moveCameraToCoordinateButton.addEventListener('click', moveCameraToCoordinate);
|
|
1511
1669
|
moveTilesToCoordinateButton.addEventListener('click', moveTilesToCoordinate);
|
package/src/viewer/session.js
CHANGED
|
@@ -174,7 +174,14 @@ function normalizePositiveFinite(value, name) {
|
|
|
174
174
|
return number;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
function scaleGeometricErrorValue(
|
|
177
|
+
function scaleGeometricErrorValue(
|
|
178
|
+
target,
|
|
179
|
+
key,
|
|
180
|
+
geometricErrorScale,
|
|
181
|
+
geometricErrorLayerScale,
|
|
182
|
+
leafGeometricError,
|
|
183
|
+
label,
|
|
184
|
+
) {
|
|
178
185
|
if (target[key] == null) {
|
|
179
186
|
return;
|
|
180
187
|
}
|
|
@@ -184,10 +191,164 @@ function scaleGeometricErrorValue(target, key, scale, label) {
|
|
|
184
191
|
throw new InspectorError(`${label} must be a finite number.`);
|
|
185
192
|
}
|
|
186
193
|
|
|
187
|
-
|
|
194
|
+
if (!Number.isFinite(leafGeometricError)) {
|
|
195
|
+
throw new InspectorError(`${label} leaf geometricError must be finite.`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const adjusted =
|
|
199
|
+
leafGeometricError +
|
|
200
|
+
(number - leafGeometricError) * geometricErrorLayerScale;
|
|
201
|
+
const next = adjusted * geometricErrorScale;
|
|
202
|
+
if (!Number.isFinite(next)) {
|
|
203
|
+
throw new InspectorError(`${label} scaled value must be finite.`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
target[key] = next;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function assertTilesetPathInsideRoot(resolvedPath, rootDir) {
|
|
210
|
+
if (
|
|
211
|
+
resolvedPath !== rootDir &&
|
|
212
|
+
!resolvedPath.startsWith(`${rootDir}${path.sep}`)
|
|
213
|
+
) {
|
|
214
|
+
throw new InspectorError(
|
|
215
|
+
`Nested tileset path escapes the viewer root: ${resolvedPath}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getLocalExternalTilesetPaths(tile, baseDir) {
|
|
221
|
+
const paths = [];
|
|
222
|
+
if (!tile || typeof tile !== 'object') {
|
|
223
|
+
return paths;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (tile.content && typeof tile.content === 'object') {
|
|
227
|
+
const filePath = getLocalJsonReferencePath(
|
|
228
|
+
baseDir,
|
|
229
|
+
tile.content.uri || tile.content.url,
|
|
230
|
+
);
|
|
231
|
+
if (filePath) {
|
|
232
|
+
paths.push(filePath);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (Array.isArray(tile.contents)) {
|
|
237
|
+
tile.contents.forEach((entry) => {
|
|
238
|
+
if (entry && typeof entry === 'object') {
|
|
239
|
+
const filePath = getLocalJsonReferencePath(
|
|
240
|
+
baseDir,
|
|
241
|
+
entry.uri || entry.url,
|
|
242
|
+
);
|
|
243
|
+
if (filePath) {
|
|
244
|
+
paths.push(filePath);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return paths;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getTilesetRootLeafGeometricError(
|
|
254
|
+
tilesetPath,
|
|
255
|
+
rootDir,
|
|
256
|
+
leafGeometricErrorCache,
|
|
257
|
+
stack,
|
|
258
|
+
) {
|
|
259
|
+
const resolvedPath = path.resolve(tilesetPath);
|
|
260
|
+
if (leafGeometricErrorCache.has(resolvedPath)) {
|
|
261
|
+
return leafGeometricErrorCache.get(resolvedPath);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (stack.has(resolvedPath)) {
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
assertTilesetPathInsideRoot(resolvedPath, rootDir);
|
|
269
|
+
|
|
270
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
271
|
+
throw new InspectorError(
|
|
272
|
+
`Referenced nested tileset does not exist: ${resolvedPath}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const tileset = readJsonFile(resolvedPath);
|
|
277
|
+
if (!tileset || typeof tileset !== 'object' || !tileset.root) {
|
|
278
|
+
throw new InspectorError(`${resolvedPath} must contain a root object.`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
stack.add(resolvedPath);
|
|
282
|
+
const leafGeometricError = getTileLeafGeometricError(
|
|
283
|
+
tileset.root,
|
|
284
|
+
path.dirname(resolvedPath),
|
|
285
|
+
rootDir,
|
|
286
|
+
leafGeometricErrorCache,
|
|
287
|
+
stack,
|
|
288
|
+
);
|
|
289
|
+
stack.delete(resolvedPath);
|
|
290
|
+
leafGeometricErrorCache.set(resolvedPath, leafGeometricError);
|
|
291
|
+
return leafGeometricError;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function getTileLeafGeometricError(
|
|
295
|
+
tile,
|
|
296
|
+
baseDir,
|
|
297
|
+
rootDir,
|
|
298
|
+
leafGeometricErrorCache,
|
|
299
|
+
stack,
|
|
300
|
+
) {
|
|
301
|
+
if (!tile || typeof tile !== 'object') {
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const ownGeometricError = Number(tile.geometricError);
|
|
306
|
+
if (!Number.isFinite(ownGeometricError)) {
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let leafGeometricError = null;
|
|
311
|
+
if (Array.isArray(tile.children)) {
|
|
312
|
+
tile.children.forEach((child) => {
|
|
313
|
+
const childLeafGeometricError = getTileLeafGeometricError(
|
|
314
|
+
child,
|
|
315
|
+
baseDir,
|
|
316
|
+
rootDir,
|
|
317
|
+
leafGeometricErrorCache,
|
|
318
|
+
stack,
|
|
319
|
+
);
|
|
320
|
+
leafGeometricError =
|
|
321
|
+
leafGeometricError === null
|
|
322
|
+
? childLeafGeometricError
|
|
323
|
+
: Math.min(leafGeometricError, childLeafGeometricError);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
getLocalExternalTilesetPaths(tile, baseDir).forEach((childTilesetPath) => {
|
|
328
|
+
const childLeafGeometricError = getTilesetRootLeafGeometricError(
|
|
329
|
+
childTilesetPath,
|
|
330
|
+
rootDir,
|
|
331
|
+
leafGeometricErrorCache,
|
|
332
|
+
stack,
|
|
333
|
+
);
|
|
334
|
+
leafGeometricError =
|
|
335
|
+
leafGeometricError === null
|
|
336
|
+
? childLeafGeometricError
|
|
337
|
+
: Math.min(leafGeometricError, childLeafGeometricError);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return leafGeometricError === null ? ownGeometricError : leafGeometricError;
|
|
188
341
|
}
|
|
189
342
|
|
|
190
|
-
function scaleTilesetGeometricErrors(
|
|
343
|
+
function scaleTilesetGeometricErrors(
|
|
344
|
+
tile,
|
|
345
|
+
geometricErrorScale,
|
|
346
|
+
geometricErrorLayerScale,
|
|
347
|
+
baseDir,
|
|
348
|
+
rootDir,
|
|
349
|
+
leafGeometricErrorCache,
|
|
350
|
+
pathLabel = 'tileset.root',
|
|
351
|
+
) {
|
|
191
352
|
if (!tile || typeof tile !== 'object') {
|
|
192
353
|
return;
|
|
193
354
|
}
|
|
@@ -195,7 +356,15 @@ function scaleTilesetGeometricErrors(tile, scale, pathLabel = 'tileset.root') {
|
|
|
195
356
|
scaleGeometricErrorValue(
|
|
196
357
|
tile,
|
|
197
358
|
'geometricError',
|
|
198
|
-
|
|
359
|
+
geometricErrorScale,
|
|
360
|
+
geometricErrorLayerScale,
|
|
361
|
+
getTileLeafGeometricError(
|
|
362
|
+
tile,
|
|
363
|
+
baseDir,
|
|
364
|
+
rootDir,
|
|
365
|
+
leafGeometricErrorCache,
|
|
366
|
+
new Set(),
|
|
367
|
+
),
|
|
199
368
|
`${pathLabel}.geometricError`,
|
|
200
369
|
);
|
|
201
370
|
|
|
@@ -206,7 +375,11 @@ function scaleTilesetGeometricErrors(tile, scale, pathLabel = 'tileset.root') {
|
|
|
206
375
|
tile.children.forEach((child, index) => {
|
|
207
376
|
scaleTilesetGeometricErrors(
|
|
208
377
|
child,
|
|
209
|
-
|
|
378
|
+
geometricErrorScale,
|
|
379
|
+
geometricErrorLayerScale,
|
|
380
|
+
baseDir,
|
|
381
|
+
rootDir,
|
|
382
|
+
leafGeometricErrorCache,
|
|
210
383
|
`${pathLabel}.children[${index}]`,
|
|
211
384
|
);
|
|
212
385
|
});
|
|
@@ -234,27 +407,8 @@ function collectExternalTilesetPaths(tile, baseDir, results) {
|
|
|
234
407
|
return;
|
|
235
408
|
}
|
|
236
409
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
maybePaths.push(
|
|
240
|
-
getLocalJsonReferencePath(baseDir, tile.content.uri || tile.content.url),
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (Array.isArray(tile.contents)) {
|
|
245
|
-
tile.contents.forEach((entry) => {
|
|
246
|
-
if (entry && typeof entry === 'object') {
|
|
247
|
-
maybePaths.push(
|
|
248
|
-
getLocalJsonReferencePath(baseDir, entry.uri || entry.url),
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
maybePaths.forEach((filePath) => {
|
|
255
|
-
if (filePath) {
|
|
256
|
-
results.add(filePath);
|
|
257
|
-
}
|
|
410
|
+
getLocalExternalTilesetPaths(tile, baseDir).forEach((filePath) => {
|
|
411
|
+
results.add(filePath);
|
|
258
412
|
});
|
|
259
413
|
|
|
260
414
|
if (!Array.isArray(tile.children)) {
|
|
@@ -268,7 +422,13 @@ function collectExternalTilesetPaths(tile, baseDir, results) {
|
|
|
268
422
|
|
|
269
423
|
function updateTilesetJsonFile(
|
|
270
424
|
tilesetPath,
|
|
271
|
-
{
|
|
425
|
+
{
|
|
426
|
+
geometricErrorLayerScale,
|
|
427
|
+
geometricErrorScale,
|
|
428
|
+
rootDir,
|
|
429
|
+
rootTransform = null,
|
|
430
|
+
leafGeometricErrorCache = new Map(),
|
|
431
|
+
},
|
|
272
432
|
visited = new Set(),
|
|
273
433
|
) {
|
|
274
434
|
const resolvedPath = path.resolve(tilesetPath);
|
|
@@ -277,14 +437,7 @@ function updateTilesetJsonFile(
|
|
|
277
437
|
}
|
|
278
438
|
visited.add(resolvedPath);
|
|
279
439
|
|
|
280
|
-
|
|
281
|
-
resolvedPath !== rootDir &&
|
|
282
|
-
!resolvedPath.startsWith(`${rootDir}${path.sep}`)
|
|
283
|
-
) {
|
|
284
|
-
throw new InspectorError(
|
|
285
|
-
`Nested tileset path escapes the viewer root: ${resolvedPath}`,
|
|
286
|
-
);
|
|
287
|
-
}
|
|
440
|
+
assertTilesetPathInsideRoot(resolvedPath, rootDir);
|
|
288
441
|
|
|
289
442
|
if (!fs.existsSync(resolvedPath)) {
|
|
290
443
|
throw new InspectorError(
|
|
@@ -301,30 +454,41 @@ function updateTilesetJsonFile(
|
|
|
301
454
|
tileset.root.transform = rootTransform.slice();
|
|
302
455
|
}
|
|
303
456
|
|
|
457
|
+
const tilesetDir = path.dirname(resolvedPath);
|
|
304
458
|
scaleGeometricErrorValue(
|
|
305
459
|
tileset,
|
|
306
460
|
'geometricError',
|
|
307
461
|
geometricErrorScale,
|
|
462
|
+
geometricErrorLayerScale,
|
|
463
|
+
getTileLeafGeometricError(
|
|
464
|
+
tileset.root,
|
|
465
|
+
tilesetDir,
|
|
466
|
+
rootDir,
|
|
467
|
+
leafGeometricErrorCache,
|
|
468
|
+
new Set(),
|
|
469
|
+
),
|
|
308
470
|
`${resolvedPath}.geometricError`,
|
|
309
471
|
);
|
|
310
472
|
scaleTilesetGeometricErrors(
|
|
311
473
|
tileset.root,
|
|
312
474
|
geometricErrorScale,
|
|
475
|
+
geometricErrorLayerScale,
|
|
476
|
+
tilesetDir,
|
|
477
|
+
rootDir,
|
|
478
|
+
leafGeometricErrorCache,
|
|
313
479
|
`${resolvedPath}.root`,
|
|
314
480
|
);
|
|
315
481
|
writeJsonAtomic(resolvedPath, tileset);
|
|
316
482
|
|
|
317
483
|
const nestedTilesets = new Set();
|
|
318
|
-
collectExternalTilesetPaths(
|
|
319
|
-
tileset.root,
|
|
320
|
-
path.dirname(resolvedPath),
|
|
321
|
-
nestedTilesets,
|
|
322
|
-
);
|
|
484
|
+
collectExternalTilesetPaths(tileset.root, tilesetDir, nestedTilesets);
|
|
323
485
|
nestedTilesets.forEach((childTilesetPath) => {
|
|
324
486
|
updateTilesetJsonFile(
|
|
325
487
|
childTilesetPath,
|
|
326
488
|
{
|
|
489
|
+
geometricErrorLayerScale,
|
|
327
490
|
geometricErrorScale,
|
|
491
|
+
leafGeometricErrorCache,
|
|
328
492
|
rootDir,
|
|
329
493
|
},
|
|
330
494
|
visited,
|
|
@@ -337,13 +501,17 @@ function updateTilesetJsonFile(
|
|
|
337
501
|
function saveViewerTransform(
|
|
338
502
|
rootTilesetPath,
|
|
339
503
|
editMatrix,
|
|
340
|
-
{ geometricErrorScale = 1 } = {},
|
|
504
|
+
{ geometricErrorLayerScale = 1, geometricErrorScale = 1 } = {},
|
|
341
505
|
) {
|
|
342
506
|
const normalizedEdit = normalizeMatrix4Array(editMatrix, 'transform');
|
|
343
507
|
const normalizedGeometricErrorScale = normalizePositiveFinite(
|
|
344
508
|
geometricErrorScale,
|
|
345
509
|
'geometricErrorScale',
|
|
346
510
|
);
|
|
511
|
+
const normalizedGeometricErrorLayerScale = normalizePositiveFinite(
|
|
512
|
+
geometricErrorLayerScale,
|
|
513
|
+
'geometricErrorLayerScale',
|
|
514
|
+
);
|
|
347
515
|
const tilesetPath = path.resolve(rootTilesetPath);
|
|
348
516
|
const rootDir = path.dirname(tilesetPath);
|
|
349
517
|
|
|
@@ -366,6 +534,7 @@ function saveViewerTransform(
|
|
|
366
534
|
const nextRoot = multiplyMatrix4(normalizedEdit, currentRoot);
|
|
367
535
|
|
|
368
536
|
updateTilesetJsonFile(tilesetPath, {
|
|
537
|
+
geometricErrorLayerScale: normalizedGeometricErrorLayerScale,
|
|
369
538
|
geometricErrorScale: normalizedGeometricErrorScale,
|
|
370
539
|
rootDir,
|
|
371
540
|
rootTransform: nextRoot,
|
|
@@ -386,6 +555,15 @@ function saveViewerTransform(
|
|
|
386
555
|
summary.root_coordinate = null;
|
|
387
556
|
summary.viewer_geometric_error_scale =
|
|
388
557
|
previousGeometricErrorScale * normalizedGeometricErrorScale;
|
|
558
|
+
const previousGeometricErrorLayerScale =
|
|
559
|
+
summary.viewer_geometric_error_layer_scale == null
|
|
560
|
+
? 1
|
|
561
|
+
: normalizePositiveFinite(
|
|
562
|
+
summary.viewer_geometric_error_layer_scale,
|
|
563
|
+
'build_summary.viewer_geometric_error_layer_scale',
|
|
564
|
+
);
|
|
565
|
+
summary.viewer_geometric_error_layer_scale =
|
|
566
|
+
previousGeometricErrorLayerScale * normalizedGeometricErrorLayerScale;
|
|
389
567
|
writeJsonAtomic(summaryPath, summary);
|
|
390
568
|
}
|
|
391
569
|
|
|
@@ -529,9 +707,28 @@ async function handleSaveTransformRequest(rootTilesetPath, req, res) {
|
|
|
529
707
|
return;
|
|
530
708
|
}
|
|
531
709
|
|
|
710
|
+
let normalizedGeometricErrorLayerScale;
|
|
711
|
+
try {
|
|
712
|
+
normalizedGeometricErrorLayerScale = normalizePositiveFinite(
|
|
713
|
+
payload.geometricErrorLayerScale == null
|
|
714
|
+
? 1
|
|
715
|
+
: payload.geometricErrorLayerScale,
|
|
716
|
+
'geometricErrorLayerScale',
|
|
717
|
+
);
|
|
718
|
+
} catch (err) {
|
|
719
|
+
sendJson(res, 400, {
|
|
720
|
+
error:
|
|
721
|
+
err instanceof Error && err.message
|
|
722
|
+
? err.message
|
|
723
|
+
: 'geometricErrorLayerScale must be a finite number greater than 0.',
|
|
724
|
+
});
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
|
|
532
728
|
let nextRoot;
|
|
533
729
|
try {
|
|
534
730
|
nextRoot = saveViewerTransform(rootTilesetPath, normalizedEdit, {
|
|
731
|
+
geometricErrorLayerScale: normalizedGeometricErrorLayerScale,
|
|
535
732
|
geometricErrorScale: normalizedGeometricErrorScale,
|
|
536
733
|
});
|
|
537
734
|
} catch (err) {
|
|
@@ -547,6 +744,7 @@ async function handleSaveTransformRequest(rootTilesetPath, req, res) {
|
|
|
547
744
|
sendJson(res, 200, {
|
|
548
745
|
ok: true,
|
|
549
746
|
transform: nextRoot,
|
|
747
|
+
geometricErrorLayerScale: normalizedGeometricErrorLayerScale,
|
|
550
748
|
geometricErrorScale: normalizedGeometricErrorScale,
|
|
551
749
|
});
|
|
552
750
|
}
|
|
@@ -756,8 +954,8 @@ function buildViewerHtml(viewerConfig) {
|
|
|
756
954
|
.toolbar {
|
|
757
955
|
display: grid;
|
|
758
956
|
align-content: start;
|
|
759
|
-
gap:
|
|
760
|
-
padding: 14px;
|
|
957
|
+
gap: 8px;
|
|
958
|
+
padding: 10px 14px;
|
|
761
959
|
border: 1px solid rgba(22, 50, 79, 0.12);
|
|
762
960
|
border-top: 0;
|
|
763
961
|
border-radius: 0 0 20px 20px;
|
|
@@ -782,7 +980,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
782
980
|
justify-content: center;
|
|
783
981
|
width: 100%;
|
|
784
982
|
min-height: 32px;
|
|
785
|
-
padding:
|
|
983
|
+
padding: 8px 12px;
|
|
786
984
|
border: 1px solid rgba(22, 50, 79, 0.08);
|
|
787
985
|
border-radius: 20px 20px 0 0;
|
|
788
986
|
font: inherit;
|
|
@@ -804,7 +1002,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
804
1002
|
justify-self: start;
|
|
805
1003
|
width: auto;
|
|
806
1004
|
min-height: 36px;
|
|
807
|
-
padding:
|
|
1005
|
+
padding: 6px 12px 7px;
|
|
808
1006
|
border-radius: 999px;
|
|
809
1007
|
color: #506377;
|
|
810
1008
|
background: rgba(255, 255, 255, 0.94);
|
|
@@ -830,7 +1028,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
830
1028
|
.toolbar-section {
|
|
831
1029
|
display: grid;
|
|
832
1030
|
gap: 10px;
|
|
833
|
-
padding: 12px;
|
|
1031
|
+
padding: 10px 12px;
|
|
834
1032
|
border: 1px solid rgba(22, 50, 79, 0.08);
|
|
835
1033
|
border-radius: 14px;
|
|
836
1034
|
background:
|
|
@@ -839,7 +1037,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
839
1037
|
|
|
840
1038
|
.toolbar-section-header {
|
|
841
1039
|
display: flex;
|
|
842
|
-
align-items:
|
|
1040
|
+
align-items: flex-start;
|
|
843
1041
|
justify-content: space-between;
|
|
844
1042
|
gap: 10px;
|
|
845
1043
|
}
|
|
@@ -857,7 +1055,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
857
1055
|
margin: 0;
|
|
858
1056
|
font-size: 12px;
|
|
859
1057
|
font-weight: 700;
|
|
860
|
-
color: #
|
|
1058
|
+
color: #5d738b;
|
|
861
1059
|
}
|
|
862
1060
|
|
|
863
1061
|
.button-row {
|
|
@@ -886,7 +1084,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
886
1084
|
justify-content: center;
|
|
887
1085
|
border: 0;
|
|
888
1086
|
border-radius: 999px;
|
|
889
|
-
padding:
|
|
1087
|
+
padding: 7px 14px;
|
|
890
1088
|
font: inherit;
|
|
891
1089
|
font-size: 14px;
|
|
892
1090
|
font-weight: 600;
|
|
@@ -922,6 +1120,14 @@ function buildViewerHtml(viewerConfig) {
|
|
|
922
1120
|
|
|
923
1121
|
.range-field {
|
|
924
1122
|
display: grid;
|
|
1123
|
+
gap: 4px;
|
|
1124
|
+
min-width: 0;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.range-field-header {
|
|
1128
|
+
display: flex;
|
|
1129
|
+
align-items: center;
|
|
1130
|
+
justify-content: space-between;
|
|
925
1131
|
gap: 8px;
|
|
926
1132
|
min-width: 0;
|
|
927
1133
|
}
|
|
@@ -1105,10 +1311,12 @@ function buildViewerHtml(viewerConfig) {
|
|
|
1105
1311
|
<div class="toolbar-section">
|
|
1106
1312
|
<div class="toolbar-section-header">
|
|
1107
1313
|
<p class="toolbar-section-title">LOD</p>
|
|
1108
|
-
<p id="geometric-error-value" class="toolbar-value">x1.00</p>
|
|
1109
1314
|
</div>
|
|
1110
1315
|
<label class="range-field">
|
|
1111
|
-
<
|
|
1316
|
+
<div class="range-field-header">
|
|
1317
|
+
<span>Geometric Error</span>
|
|
1318
|
+
<p id="geometric-error-value" class="toolbar-value">x1.00</p>
|
|
1319
|
+
</div>
|
|
1112
1320
|
<input
|
|
1113
1321
|
id="geometric-error-scale"
|
|
1114
1322
|
type="range"
|
|
@@ -1118,6 +1326,20 @@ function buildViewerHtml(viewerConfig) {
|
|
|
1118
1326
|
value="0"
|
|
1119
1327
|
/>
|
|
1120
1328
|
</label>
|
|
1329
|
+
<label class="range-field">
|
|
1330
|
+
<div class="range-field-header">
|
|
1331
|
+
<span>Layer Multiplier</span>
|
|
1332
|
+
<p id="geometric-error-layer-value" class="toolbar-value">x1.00</p>
|
|
1333
|
+
</div>
|
|
1334
|
+
<input
|
|
1335
|
+
id="geometric-error-layer-scale"
|
|
1336
|
+
type="range"
|
|
1337
|
+
min="-3"
|
|
1338
|
+
max="3"
|
|
1339
|
+
step="0.1"
|
|
1340
|
+
value="0"
|
|
1341
|
+
/>
|
|
1342
|
+
</label>
|
|
1121
1343
|
</div>
|
|
1122
1344
|
<div class="toolbar-section status-panel">
|
|
1123
1345
|
<div class="status-actions">
|