3dtiles-inspector 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +2 -0
- package/dist/inspector-assets/viewer/app.js +136 -4
- package/package.json +1 -1
- package/src/viewer/app.js +160 -8
- package/src/viewer/session.js +254 -50
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.5] - 2026-04-27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added an LOD `Layer Multiplier` slider to scale geometric errors progressively by distance from leaf tiles.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Tightened inspector toolbar spacing and LOD slider value labels.
|
|
18
|
+
|
|
19
|
+
## [0.1.4] - 2026-04-25
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Raised the default tileset and terrain geometric-error targets to `16` for more consistent LOD behavior.
|
|
24
|
+
|
|
9
25
|
## [0.1.3] - 2026-04-25
|
|
10
26
|
|
|
11
27
|
### Fixed
|
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/1.5x` to `1.5x` for leaf-based geometric error changes between tile depths
|
|
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,14 +65484,28 @@ 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;
|
|
65493
|
-
var
|
|
65494
|
-
var
|
|
65499
|
+
var GEOMETRIC_ERROR_LAYER_SCALE_MAX = 1.5;
|
|
65500
|
+
var GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT = -Math.log2(
|
|
65501
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX
|
|
65502
|
+
);
|
|
65503
|
+
var GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT = Math.log2(
|
|
65504
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX
|
|
65505
|
+
);
|
|
65506
|
+
var GEOMETRIC_ERROR_LAYER_SCALE_STEP = "any";
|
|
65507
|
+
var DEFAULT_ERROR_TARGET = 16;
|
|
65508
|
+
var DEFAULT_TERRAIN_ERROR_TARGET = 16;
|
|
65495
65509
|
var RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
|
|
65496
65510
|
function normalizeLocalResourceUrl(value) {
|
|
65497
65511
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -65801,12 +65815,16 @@ var savedRootInverseMatrix = new Matrix4();
|
|
|
65801
65815
|
var pointerCoords = new Vector2();
|
|
65802
65816
|
var pickRaycaster = new Raycaster();
|
|
65803
65817
|
var pickTargets = [];
|
|
65818
|
+
var originalTileGeometricErrors = /* @__PURE__ */ new WeakMap();
|
|
65804
65819
|
var tiles = null;
|
|
65805
65820
|
var toolbarVisible = true;
|
|
65806
65821
|
var activeTransformMode = null;
|
|
65807
65822
|
var geometricErrorScaleExponent = 0;
|
|
65808
65823
|
var geometricErrorScale = 1;
|
|
65809
65824
|
var lastSavedGeometricErrorScale = 1;
|
|
65825
|
+
var geometricErrorLayerScaleExponent = 0;
|
|
65826
|
+
var geometricErrorLayerScale = 1;
|
|
65827
|
+
var lastSavedGeometricErrorLayerScale = 1;
|
|
65810
65828
|
var lastSavedMatrix = new Matrix4();
|
|
65811
65829
|
var savedRootMatrix = new Matrix4();
|
|
65812
65830
|
var savedRootMatrixPromise = Promise.resolve();
|
|
@@ -65831,9 +65849,73 @@ function updateGeometricErrorScaleDisplay() {
|
|
|
65831
65849
|
geometricErrorScale
|
|
65832
65850
|
)}`;
|
|
65833
65851
|
}
|
|
65852
|
+
function updateGeometricErrorLayerScaleDisplay() {
|
|
65853
|
+
geometricErrorLayerValueEl.textContent = `x${formatGeometricErrorScale(
|
|
65854
|
+
geometricErrorLayerScale
|
|
65855
|
+
)}`;
|
|
65856
|
+
}
|
|
65834
65857
|
function getEffectiveGeometricErrorScale() {
|
|
65835
65858
|
return lastSavedGeometricErrorScale * geometricErrorScale;
|
|
65836
65859
|
}
|
|
65860
|
+
function getEffectiveGeometricErrorLayerScale() {
|
|
65861
|
+
return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
|
|
65862
|
+
}
|
|
65863
|
+
function getKnownTileLeafDistance(tile, visited = /* @__PURE__ */ new Set()) {
|
|
65864
|
+
if (!tile || typeof tile !== "object" || visited.has(tile)) {
|
|
65865
|
+
return 0;
|
|
65866
|
+
}
|
|
65867
|
+
visited.add(tile);
|
|
65868
|
+
let maxDistance = 0;
|
|
65869
|
+
const children = Array.isArray(tile.children) ? tile.children : [];
|
|
65870
|
+
for (const child of children) {
|
|
65871
|
+
maxDistance = Math.max(
|
|
65872
|
+
maxDistance,
|
|
65873
|
+
getKnownTileLeafDistance(child, visited) + 1
|
|
65874
|
+
);
|
|
65875
|
+
}
|
|
65876
|
+
visited.delete(tile);
|
|
65877
|
+
return maxDistance;
|
|
65878
|
+
}
|
|
65879
|
+
function getOriginalTileGeometricError(tile) {
|
|
65880
|
+
if (!tile || typeof tile !== "object") {
|
|
65881
|
+
return null;
|
|
65882
|
+
}
|
|
65883
|
+
if (!originalTileGeometricErrors.has(tile)) {
|
|
65884
|
+
const number = Number(tile.geometricError);
|
|
65885
|
+
if (!Number.isFinite(number)) {
|
|
65886
|
+
return null;
|
|
65887
|
+
}
|
|
65888
|
+
originalTileGeometricErrors.set(tile, number);
|
|
65889
|
+
}
|
|
65890
|
+
return originalTileGeometricErrors.get(tile);
|
|
65891
|
+
}
|
|
65892
|
+
function applyGeometricErrorLayerScaleToTile(tile) {
|
|
65893
|
+
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
65894
|
+
if (originalGeometricError === null) {
|
|
65895
|
+
return;
|
|
65896
|
+
}
|
|
65897
|
+
const leafDistance = getKnownTileLeafDistance(tile);
|
|
65898
|
+
tile.geometricError = originalGeometricError * getEffectiveGeometricErrorLayerScale() ** leafDistance;
|
|
65899
|
+
}
|
|
65900
|
+
function applyGeometricErrorLayerScaleToTileset() {
|
|
65901
|
+
if (!tiles) {
|
|
65902
|
+
return;
|
|
65903
|
+
}
|
|
65904
|
+
tiles.traverse(
|
|
65905
|
+
(tile) => {
|
|
65906
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
65907
|
+
return false;
|
|
65908
|
+
},
|
|
65909
|
+
null,
|
|
65910
|
+
false
|
|
65911
|
+
);
|
|
65912
|
+
}
|
|
65913
|
+
var geometricErrorLayerScalePlugin = {
|
|
65914
|
+
name: "GeometricErrorLayerScalePlugin",
|
|
65915
|
+
preprocessNode(tile) {
|
|
65916
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
65917
|
+
}
|
|
65918
|
+
};
|
|
65837
65919
|
function getGaussianMeshSplatCount(mesh) {
|
|
65838
65920
|
if (!mesh || typeof mesh !== "object") {
|
|
65839
65921
|
return 0;
|
|
@@ -65898,6 +65980,19 @@ function setGeometricErrorScaleExponent(exponent) {
|
|
|
65898
65980
|
updateGeometricErrorScaleDisplay();
|
|
65899
65981
|
updateTilesetErrorTarget();
|
|
65900
65982
|
}
|
|
65983
|
+
function setGeometricErrorLayerScaleExponent(exponent) {
|
|
65984
|
+
geometricErrorLayerScaleExponent = clamp2(
|
|
65985
|
+
Number(exponent),
|
|
65986
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT,
|
|
65987
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT
|
|
65988
|
+
);
|
|
65989
|
+
geometricErrorLayerScale = exponentToGeometricErrorScale(
|
|
65990
|
+
geometricErrorLayerScaleExponent
|
|
65991
|
+
);
|
|
65992
|
+
geometricErrorLayerScaleInput.value = geometricErrorLayerScaleExponent.toFixed(1);
|
|
65993
|
+
updateGeometricErrorLayerScaleDisplay();
|
|
65994
|
+
applyGeometricErrorLayerScaleToTileset();
|
|
65995
|
+
}
|
|
65901
65996
|
function syncTerrainButton() {
|
|
65902
65997
|
terrainButton.classList.toggle("active", terrainEnabled);
|
|
65903
65998
|
terrainLight.visible = terrainEnabled;
|
|
@@ -65979,7 +66074,15 @@ function toggleTransformMode(mode) {
|
|
|
65979
66074
|
geometricErrorScaleInput.min = String(GEOMETRIC_ERROR_SCALE_MIN_EXPONENT);
|
|
65980
66075
|
geometricErrorScaleInput.max = String(GEOMETRIC_ERROR_SCALE_MAX_EXPONENT);
|
|
65981
66076
|
geometricErrorScaleInput.step = String(GEOMETRIC_ERROR_SCALE_STEP);
|
|
66077
|
+
geometricErrorLayerScaleInput.min = String(
|
|
66078
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT
|
|
66079
|
+
);
|
|
66080
|
+
geometricErrorLayerScaleInput.max = String(
|
|
66081
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT
|
|
66082
|
+
);
|
|
66083
|
+
geometricErrorLayerScaleInput.step = String(GEOMETRIC_ERROR_LAYER_SCALE_STEP);
|
|
65982
66084
|
setGeometricErrorScaleExponent(geometricErrorScaleExponent);
|
|
66085
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleExponent);
|
|
65983
66086
|
setTerrainEnabled(terrainEnabled);
|
|
65984
66087
|
setTransformMode(activeTransformMode);
|
|
65985
66088
|
syncToolbarVisibility();
|
|
@@ -66473,7 +66576,9 @@ function loadTileset(url) {
|
|
|
66473
66576
|
updateRuntimeStats(true);
|
|
66474
66577
|
resetEditableGroup();
|
|
66475
66578
|
lastSavedGeometricErrorScale = 1;
|
|
66579
|
+
lastSavedGeometricErrorLayerScale = 1;
|
|
66476
66580
|
setGeometricErrorScaleExponent(0);
|
|
66581
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
66477
66582
|
savedRootMatrix.identity();
|
|
66478
66583
|
savedRootMatrixLoadError = null;
|
|
66479
66584
|
savedRootMatrixPromise = refreshSavedRootMatrix(url).then(
|
|
@@ -66496,7 +66601,16 @@ function loadTileset(url) {
|
|
|
66496
66601
|
next.registerPlugin(new go());
|
|
66497
66602
|
next.registerPlugin(new To());
|
|
66498
66603
|
next.registerPlugin(new w2());
|
|
66499
|
-
next.registerPlugin(
|
|
66604
|
+
next.registerPlugin(geometricErrorLayerScalePlugin);
|
|
66605
|
+
next.registerPlugin(
|
|
66606
|
+
new GaussianSplatPlugin({
|
|
66607
|
+
renderer,
|
|
66608
|
+
scene,
|
|
66609
|
+
sparkRendererOptions: {
|
|
66610
|
+
accumExtSplats: true
|
|
66611
|
+
}
|
|
66612
|
+
})
|
|
66613
|
+
);
|
|
66500
66614
|
debugTilesPlugin = new Oo({
|
|
66501
66615
|
displayBoxBounds: showBoundingVolume,
|
|
66502
66616
|
displaySphereBounds: showBoundingVolume,
|
|
@@ -66543,6 +66657,7 @@ function loadTileset(url) {
|
|
|
66543
66657
|
setStatus("Tileset ready.");
|
|
66544
66658
|
}
|
|
66545
66659
|
};
|
|
66660
|
+
next.addEventListener("load-tileset", applyGeometricErrorLayerScaleToTileset);
|
|
66546
66661
|
next.addEventListener("load-tile-set", tryFrame);
|
|
66547
66662
|
next.addEventListener("load-tileset", tryFrame);
|
|
66548
66663
|
}
|
|
@@ -66554,6 +66669,8 @@ async function saveTransform() {
|
|
|
66554
66669
|
const incrementalMatrix = currentMatrix.clone().multiply(lastSavedMatrix.clone().invert());
|
|
66555
66670
|
const incrementalGeometricErrorScale = geometricErrorScale;
|
|
66556
66671
|
const savedGeometricErrorScale = getEffectiveGeometricErrorScale();
|
|
66672
|
+
const incrementalGeometricErrorLayerScale = geometricErrorLayerScale;
|
|
66673
|
+
const savedGeometricErrorLayerScale = getEffectiveGeometricErrorLayerScale();
|
|
66557
66674
|
try {
|
|
66558
66675
|
const response = await fetch(SAVE_URL, {
|
|
66559
66676
|
method: "POST",
|
|
@@ -66561,6 +66678,7 @@ async function saveTransform() {
|
|
|
66561
66678
|
"Content-Type": "application/json"
|
|
66562
66679
|
},
|
|
66563
66680
|
body: JSON.stringify({
|
|
66681
|
+
geometricErrorLayerScale: incrementalGeometricErrorLayerScale,
|
|
66564
66682
|
geometricErrorScale: incrementalGeometricErrorScale,
|
|
66565
66683
|
transform: incrementalMatrix.toArray()
|
|
66566
66684
|
})
|
|
@@ -66591,13 +66709,17 @@ async function saveTransform() {
|
|
|
66591
66709
|
}
|
|
66592
66710
|
}
|
|
66593
66711
|
lastSavedGeometricErrorScale = savedGeometricErrorScale;
|
|
66712
|
+
lastSavedGeometricErrorLayerScale = savedGeometricErrorLayerScale;
|
|
66594
66713
|
lastSavedMatrix.copy(currentMatrix);
|
|
66595
66714
|
setGeometricErrorScaleExponent(0);
|
|
66715
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
66596
66716
|
syncTransformHandleFromTilesTransform();
|
|
66597
66717
|
syncCoordinateInputsFromTilesTransform();
|
|
66598
66718
|
setStatus(
|
|
66599
|
-
`Saved transform
|
|
66719
|
+
`Saved transform, geometric-error scale x${formatGeometricErrorScale(
|
|
66600
66720
|
savedGeometricErrorScale
|
|
66721
|
+
)}, and layer multiplier x${formatGeometricErrorScale(
|
|
66722
|
+
savedGeometricErrorLayerScale
|
|
66601
66723
|
)} to ${ROOT_TILESET_LABEL} and build_summary.json.`
|
|
66602
66724
|
);
|
|
66603
66725
|
} catch (err2) {
|
|
@@ -66638,6 +66760,16 @@ geometricErrorScaleInput.addEventListener("change", () => {
|
|
|
66638
66760
|
)}.`
|
|
66639
66761
|
);
|
|
66640
66762
|
});
|
|
66763
|
+
geometricErrorLayerScaleInput.addEventListener("input", () => {
|
|
66764
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleInput.value);
|
|
66765
|
+
});
|
|
66766
|
+
geometricErrorLayerScaleInput.addEventListener("change", () => {
|
|
66767
|
+
setStatus(
|
|
66768
|
+
`Geometric-error layer multiplier set to x${formatGeometricErrorScale(
|
|
66769
|
+
geometricErrorLayerScale
|
|
66770
|
+
)}.`
|
|
66771
|
+
);
|
|
66772
|
+
});
|
|
66641
66773
|
moveToTilesButton.addEventListener("click", moveCameraToTiles);
|
|
66642
66774
|
moveCameraToCoordinateButton.addEventListener("click", moveCameraToCoordinate);
|
|
66643
66775
|
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.5",
|
|
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,14 +100,28 @@ 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;
|
|
109
|
-
const
|
|
110
|
-
const
|
|
115
|
+
const GEOMETRIC_ERROR_LAYER_SCALE_MAX = 1.5;
|
|
116
|
+
const GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT = -Math.log2(
|
|
117
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX,
|
|
118
|
+
);
|
|
119
|
+
const GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT = Math.log2(
|
|
120
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX,
|
|
121
|
+
);
|
|
122
|
+
const GEOMETRIC_ERROR_LAYER_SCALE_STEP = 'any';
|
|
123
|
+
const DEFAULT_ERROR_TARGET = 16;
|
|
124
|
+
const DEFAULT_TERRAIN_ERROR_TARGET = 16;
|
|
111
125
|
const RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
|
|
112
126
|
|
|
113
127
|
function normalizeLocalResourceUrl(value) {
|
|
@@ -470,12 +484,16 @@ const savedRootInverseMatrix = new Matrix4();
|
|
|
470
484
|
const pointerCoords = new Vector2();
|
|
471
485
|
const pickRaycaster = new Raycaster();
|
|
472
486
|
const pickTargets = [];
|
|
487
|
+
const originalTileGeometricErrors = new WeakMap();
|
|
473
488
|
let tiles = null;
|
|
474
489
|
let toolbarVisible = true;
|
|
475
490
|
let activeTransformMode = null;
|
|
476
491
|
let geometricErrorScaleExponent = 0;
|
|
477
492
|
let geometricErrorScale = 1;
|
|
478
493
|
let lastSavedGeometricErrorScale = 1;
|
|
494
|
+
let geometricErrorLayerScaleExponent = 0;
|
|
495
|
+
let geometricErrorLayerScale = 1;
|
|
496
|
+
let lastSavedGeometricErrorLayerScale = 1;
|
|
479
497
|
let lastSavedMatrix = new Matrix4();
|
|
480
498
|
const savedRootMatrix = new Matrix4();
|
|
481
499
|
let savedRootMatrixPromise = Promise.resolve();
|
|
@@ -496,8 +514,7 @@ function updateTilesetErrorTarget() {
|
|
|
496
514
|
return;
|
|
497
515
|
}
|
|
498
516
|
|
|
499
|
-
tiles.errorTarget =
|
|
500
|
-
DEFAULT_ERROR_TARGET / getEffectiveGeometricErrorScale();
|
|
517
|
+
tiles.errorTarget = DEFAULT_ERROR_TARGET / getEffectiveGeometricErrorScale();
|
|
501
518
|
}
|
|
502
519
|
|
|
503
520
|
function updateGeometricErrorScaleDisplay() {
|
|
@@ -506,10 +523,88 @@ function updateGeometricErrorScaleDisplay() {
|
|
|
506
523
|
)}`;
|
|
507
524
|
}
|
|
508
525
|
|
|
526
|
+
function updateGeometricErrorLayerScaleDisplay() {
|
|
527
|
+
geometricErrorLayerValueEl.textContent = `x${formatGeometricErrorScale(
|
|
528
|
+
geometricErrorLayerScale,
|
|
529
|
+
)}`;
|
|
530
|
+
}
|
|
531
|
+
|
|
509
532
|
function getEffectiveGeometricErrorScale() {
|
|
510
533
|
return lastSavedGeometricErrorScale * geometricErrorScale;
|
|
511
534
|
}
|
|
512
535
|
|
|
536
|
+
function getEffectiveGeometricErrorLayerScale() {
|
|
537
|
+
return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function getKnownTileLeafDistance(tile, visited = new Set()) {
|
|
541
|
+
if (!tile || typeof tile !== 'object' || visited.has(tile)) {
|
|
542
|
+
return 0;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
visited.add(tile);
|
|
546
|
+
let maxDistance = 0;
|
|
547
|
+
const children = Array.isArray(tile.children) ? tile.children : [];
|
|
548
|
+
for (const child of children) {
|
|
549
|
+
maxDistance = Math.max(
|
|
550
|
+
maxDistance,
|
|
551
|
+
getKnownTileLeafDistance(child, visited) + 1,
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
visited.delete(tile);
|
|
555
|
+
return maxDistance;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function getOriginalTileGeometricError(tile) {
|
|
559
|
+
if (!tile || typeof tile !== 'object') {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (!originalTileGeometricErrors.has(tile)) {
|
|
564
|
+
const number = Number(tile.geometricError);
|
|
565
|
+
if (!Number.isFinite(number)) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
originalTileGeometricErrors.set(tile, number);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return originalTileGeometricErrors.get(tile);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function applyGeometricErrorLayerScaleToTile(tile) {
|
|
575
|
+
const originalGeometricError = getOriginalTileGeometricError(tile);
|
|
576
|
+
if (originalGeometricError === null) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const leafDistance = getKnownTileLeafDistance(tile);
|
|
581
|
+
tile.geometricError =
|
|
582
|
+
originalGeometricError *
|
|
583
|
+
getEffectiveGeometricErrorLayerScale() ** leafDistance;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function applyGeometricErrorLayerScaleToTileset() {
|
|
587
|
+
if (!tiles) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
tiles.traverse(
|
|
592
|
+
(tile) => {
|
|
593
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
594
|
+
return false;
|
|
595
|
+
},
|
|
596
|
+
null,
|
|
597
|
+
false,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const geometricErrorLayerScalePlugin = {
|
|
602
|
+
name: 'GeometricErrorLayerScalePlugin',
|
|
603
|
+
preprocessNode(tile) {
|
|
604
|
+
applyGeometricErrorLayerScaleToTile(tile);
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
|
|
513
608
|
function getGaussianMeshSplatCount(mesh) {
|
|
514
609
|
if (!mesh || typeof mesh !== 'object') {
|
|
515
610
|
return 0;
|
|
@@ -572,7 +667,10 @@ function updateRuntimeStats(force = false) {
|
|
|
572
667
|
}
|
|
573
668
|
|
|
574
669
|
const now = performance.now();
|
|
575
|
-
if (
|
|
670
|
+
if (
|
|
671
|
+
!force &&
|
|
672
|
+
now - lastRuntimeStatsUpdateTime < RUNTIME_STATS_UPDATE_INTERVAL_MS
|
|
673
|
+
) {
|
|
576
674
|
return;
|
|
577
675
|
}
|
|
578
676
|
|
|
@@ -581,7 +679,9 @@ function updateRuntimeStats(force = false) {
|
|
|
581
679
|
const cacheBytes = tiles?.lruCache?.cachedBytes ?? 0;
|
|
582
680
|
const activeSparkSplats = getActiveSparkSplatsCount();
|
|
583
681
|
const splatCount =
|
|
584
|
-
activeSparkSplats !== null
|
|
682
|
+
activeSparkSplats !== null
|
|
683
|
+
? activeSparkSplats
|
|
684
|
+
: getLoadedGaussianSplatCount();
|
|
585
685
|
|
|
586
686
|
cacheBytesValueEl.textContent = formatBytes(cacheBytes);
|
|
587
687
|
splatsCountValueEl.textContent = formatInteger(splatCount);
|
|
@@ -601,6 +701,21 @@ function setGeometricErrorScaleExponent(exponent) {
|
|
|
601
701
|
updateTilesetErrorTarget();
|
|
602
702
|
}
|
|
603
703
|
|
|
704
|
+
function setGeometricErrorLayerScaleExponent(exponent) {
|
|
705
|
+
geometricErrorLayerScaleExponent = clamp(
|
|
706
|
+
Number(exponent),
|
|
707
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT,
|
|
708
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT,
|
|
709
|
+
);
|
|
710
|
+
geometricErrorLayerScale = exponentToGeometricErrorScale(
|
|
711
|
+
geometricErrorLayerScaleExponent,
|
|
712
|
+
);
|
|
713
|
+
geometricErrorLayerScaleInput.value =
|
|
714
|
+
geometricErrorLayerScaleExponent.toFixed(1);
|
|
715
|
+
updateGeometricErrorLayerScaleDisplay();
|
|
716
|
+
applyGeometricErrorLayerScaleToTileset();
|
|
717
|
+
}
|
|
718
|
+
|
|
604
719
|
function syncTerrainButton() {
|
|
605
720
|
terrainButton.classList.toggle('active', terrainEnabled);
|
|
606
721
|
terrainLight.visible = terrainEnabled;
|
|
@@ -698,7 +813,15 @@ function toggleTransformMode(mode) {
|
|
|
698
813
|
geometricErrorScaleInput.min = String(GEOMETRIC_ERROR_SCALE_MIN_EXPONENT);
|
|
699
814
|
geometricErrorScaleInput.max = String(GEOMETRIC_ERROR_SCALE_MAX_EXPONENT);
|
|
700
815
|
geometricErrorScaleInput.step = String(GEOMETRIC_ERROR_SCALE_STEP);
|
|
816
|
+
geometricErrorLayerScaleInput.min = String(
|
|
817
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MIN_EXPONENT,
|
|
818
|
+
);
|
|
819
|
+
geometricErrorLayerScaleInput.max = String(
|
|
820
|
+
GEOMETRIC_ERROR_LAYER_SCALE_MAX_EXPONENT,
|
|
821
|
+
);
|
|
822
|
+
geometricErrorLayerScaleInput.step = String(GEOMETRIC_ERROR_LAYER_SCALE_STEP);
|
|
701
823
|
setGeometricErrorScaleExponent(geometricErrorScaleExponent);
|
|
824
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleExponent);
|
|
702
825
|
setTerrainEnabled(terrainEnabled);
|
|
703
826
|
setTransformMode(activeTransformMode);
|
|
704
827
|
syncToolbarVisibility();
|
|
@@ -1320,7 +1443,9 @@ function loadTileset(url) {
|
|
|
1320
1443
|
|
|
1321
1444
|
resetEditableGroup();
|
|
1322
1445
|
lastSavedGeometricErrorScale = 1;
|
|
1446
|
+
lastSavedGeometricErrorLayerScale = 1;
|
|
1323
1447
|
setGeometricErrorScaleExponent(0);
|
|
1448
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
1324
1449
|
savedRootMatrix.identity();
|
|
1325
1450
|
savedRootMatrixLoadError = null;
|
|
1326
1451
|
savedRootMatrixPromise = refreshSavedRootMatrix(url).then(
|
|
@@ -1344,7 +1469,16 @@ function loadTileset(url) {
|
|
|
1344
1469
|
next.registerPlugin(new TileCompressionPlugin());
|
|
1345
1470
|
next.registerPlugin(new UnloadTilesPlugin());
|
|
1346
1471
|
next.registerPlugin(new ImplicitTilingPlugin());
|
|
1347
|
-
next.registerPlugin(
|
|
1472
|
+
next.registerPlugin(geometricErrorLayerScalePlugin);
|
|
1473
|
+
next.registerPlugin(
|
|
1474
|
+
new GaussianSplatPlugin({
|
|
1475
|
+
renderer,
|
|
1476
|
+
scene,
|
|
1477
|
+
sparkRendererOptions: {
|
|
1478
|
+
accumExtSplats: true,
|
|
1479
|
+
},
|
|
1480
|
+
}),
|
|
1481
|
+
);
|
|
1348
1482
|
debugTilesPlugin = new DebugTilesPlugin({
|
|
1349
1483
|
displayBoxBounds: showBoundingVolume,
|
|
1350
1484
|
displaySphereBounds: showBoundingVolume,
|
|
@@ -1395,6 +1529,7 @@ function loadTileset(url) {
|
|
|
1395
1529
|
}
|
|
1396
1530
|
};
|
|
1397
1531
|
|
|
1532
|
+
next.addEventListener('load-tileset', applyGeometricErrorLayerScaleToTileset);
|
|
1398
1533
|
next.addEventListener('load-tile-set', tryFrame);
|
|
1399
1534
|
next.addEventListener('load-tileset', tryFrame);
|
|
1400
1535
|
}
|
|
@@ -1410,6 +1545,8 @@ async function saveTransform() {
|
|
|
1410
1545
|
.multiply(lastSavedMatrix.clone().invert());
|
|
1411
1546
|
const incrementalGeometricErrorScale = geometricErrorScale;
|
|
1412
1547
|
const savedGeometricErrorScale = getEffectiveGeometricErrorScale();
|
|
1548
|
+
const incrementalGeometricErrorLayerScale = geometricErrorLayerScale;
|
|
1549
|
+
const savedGeometricErrorLayerScale = getEffectiveGeometricErrorLayerScale();
|
|
1413
1550
|
|
|
1414
1551
|
try {
|
|
1415
1552
|
const response = await fetch(SAVE_URL, {
|
|
@@ -1418,6 +1555,7 @@ async function saveTransform() {
|
|
|
1418
1555
|
'Content-Type': 'application/json',
|
|
1419
1556
|
},
|
|
1420
1557
|
body: JSON.stringify({
|
|
1558
|
+
geometricErrorLayerScale: incrementalGeometricErrorLayerScale,
|
|
1421
1559
|
geometricErrorScale: incrementalGeometricErrorScale,
|
|
1422
1560
|
transform: incrementalMatrix.toArray(),
|
|
1423
1561
|
}),
|
|
@@ -1448,13 +1586,17 @@ async function saveTransform() {
|
|
|
1448
1586
|
}
|
|
1449
1587
|
}
|
|
1450
1588
|
lastSavedGeometricErrorScale = savedGeometricErrorScale;
|
|
1589
|
+
lastSavedGeometricErrorLayerScale = savedGeometricErrorLayerScale;
|
|
1451
1590
|
lastSavedMatrix.copy(currentMatrix);
|
|
1452
1591
|
setGeometricErrorScaleExponent(0);
|
|
1592
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
1453
1593
|
syncTransformHandleFromTilesTransform();
|
|
1454
1594
|
syncCoordinateInputsFromTilesTransform();
|
|
1455
1595
|
setStatus(
|
|
1456
|
-
`Saved transform
|
|
1596
|
+
`Saved transform, geometric-error scale x${formatGeometricErrorScale(
|
|
1457
1597
|
savedGeometricErrorScale,
|
|
1598
|
+
)}, and layer multiplier x${formatGeometricErrorScale(
|
|
1599
|
+
savedGeometricErrorLayerScale,
|
|
1458
1600
|
)} to ${ROOT_TILESET_LABEL} and build_summary.json.`,
|
|
1459
1601
|
);
|
|
1460
1602
|
} catch (err) {
|
|
@@ -1502,6 +1644,16 @@ geometricErrorScaleInput.addEventListener('change', () => {
|
|
|
1502
1644
|
)}.`,
|
|
1503
1645
|
);
|
|
1504
1646
|
});
|
|
1647
|
+
geometricErrorLayerScaleInput.addEventListener('input', () => {
|
|
1648
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleInput.value);
|
|
1649
|
+
});
|
|
1650
|
+
geometricErrorLayerScaleInput.addEventListener('change', () => {
|
|
1651
|
+
setStatus(
|
|
1652
|
+
`Geometric-error layer multiplier set to x${formatGeometricErrorScale(
|
|
1653
|
+
geometricErrorLayerScale,
|
|
1654
|
+
)}.`,
|
|
1655
|
+
);
|
|
1656
|
+
});
|
|
1505
1657
|
moveToTilesButton.addEventListener('click', moveCameraToTiles);
|
|
1506
1658
|
moveCameraToCoordinateButton.addEventListener('click', moveCameraToCoordinate);
|
|
1507
1659
|
moveTilesToCoordinateButton.addEventListener('click', moveTilesToCoordinate);
|
package/src/viewer/session.js
CHANGED
|
@@ -184,10 +184,153 @@ function scaleGeometricErrorValue(target, key, scale, label) {
|
|
|
184
184
|
throw new InspectorError(`${label} must be a finite number.`);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
const next = number * scale;
|
|
188
|
+
if (!Number.isFinite(next)) {
|
|
189
|
+
throw new InspectorError(`${label} scaled value must be finite.`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
target[key] = next;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getGeometricErrorScaleForLeafDistance(
|
|
196
|
+
geometricErrorScale,
|
|
197
|
+
geometricErrorLayerScale,
|
|
198
|
+
leafDistance,
|
|
199
|
+
label,
|
|
200
|
+
) {
|
|
201
|
+
const scale =
|
|
202
|
+
geometricErrorScale * geometricErrorLayerScale ** Math.max(0, leafDistance);
|
|
203
|
+
if (!Number.isFinite(scale)) {
|
|
204
|
+
throw new InspectorError(`${label} scale must be a finite number.`);
|
|
205
|
+
}
|
|
206
|
+
return scale;
|
|
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 getTilesetRootLeafDistance(
|
|
254
|
+
tilesetPath,
|
|
255
|
+
rootDir,
|
|
256
|
+
leafDistanceCache,
|
|
257
|
+
stack,
|
|
258
|
+
) {
|
|
259
|
+
const resolvedPath = path.resolve(tilesetPath);
|
|
260
|
+
if (leafDistanceCache.has(resolvedPath)) {
|
|
261
|
+
return leafDistanceCache.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 leafDistance = getTileLeafDistance(
|
|
283
|
+
tileset.root,
|
|
284
|
+
path.dirname(resolvedPath),
|
|
285
|
+
rootDir,
|
|
286
|
+
leafDistanceCache,
|
|
287
|
+
stack,
|
|
288
|
+
);
|
|
289
|
+
stack.delete(resolvedPath);
|
|
290
|
+
leafDistanceCache.set(resolvedPath, leafDistance);
|
|
291
|
+
return leafDistance;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function getTileLeafDistance(tile, baseDir, rootDir, leafDistanceCache, stack) {
|
|
295
|
+
if (!tile || typeof tile !== 'object') {
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let maxDistance = 0;
|
|
300
|
+
if (Array.isArray(tile.children)) {
|
|
301
|
+
tile.children.forEach((child) => {
|
|
302
|
+
maxDistance = Math.max(
|
|
303
|
+
maxDistance,
|
|
304
|
+
getTileLeafDistance(child, baseDir, rootDir, leafDistanceCache, stack) +
|
|
305
|
+
1,
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
getLocalExternalTilesetPaths(tile, baseDir).forEach((childTilesetPath) => {
|
|
311
|
+
maxDistance = Math.max(
|
|
312
|
+
maxDistance,
|
|
313
|
+
getTilesetRootLeafDistance(
|
|
314
|
+
childTilesetPath,
|
|
315
|
+
rootDir,
|
|
316
|
+
leafDistanceCache,
|
|
317
|
+
stack,
|
|
318
|
+
) + 1,
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return maxDistance;
|
|
188
323
|
}
|
|
189
324
|
|
|
190
|
-
function scaleTilesetGeometricErrors(
|
|
325
|
+
function scaleTilesetGeometricErrors(
|
|
326
|
+
tile,
|
|
327
|
+
geometricErrorScale,
|
|
328
|
+
geometricErrorLayerScale,
|
|
329
|
+
baseDir,
|
|
330
|
+
rootDir,
|
|
331
|
+
leafDistanceCache,
|
|
332
|
+
pathLabel = 'tileset.root',
|
|
333
|
+
) {
|
|
191
334
|
if (!tile || typeof tile !== 'object') {
|
|
192
335
|
return;
|
|
193
336
|
}
|
|
@@ -195,7 +338,12 @@ function scaleTilesetGeometricErrors(tile, scale, pathLabel = 'tileset.root') {
|
|
|
195
338
|
scaleGeometricErrorValue(
|
|
196
339
|
tile,
|
|
197
340
|
'geometricError',
|
|
198
|
-
|
|
341
|
+
getGeometricErrorScaleForLeafDistance(
|
|
342
|
+
geometricErrorScale,
|
|
343
|
+
geometricErrorLayerScale,
|
|
344
|
+
getTileLeafDistance(tile, baseDir, rootDir, leafDistanceCache, new Set()),
|
|
345
|
+
pathLabel,
|
|
346
|
+
),
|
|
199
347
|
`${pathLabel}.geometricError`,
|
|
200
348
|
);
|
|
201
349
|
|
|
@@ -206,7 +354,11 @@ function scaleTilesetGeometricErrors(tile, scale, pathLabel = 'tileset.root') {
|
|
|
206
354
|
tile.children.forEach((child, index) => {
|
|
207
355
|
scaleTilesetGeometricErrors(
|
|
208
356
|
child,
|
|
209
|
-
|
|
357
|
+
geometricErrorScale,
|
|
358
|
+
geometricErrorLayerScale,
|
|
359
|
+
baseDir,
|
|
360
|
+
rootDir,
|
|
361
|
+
leafDistanceCache,
|
|
210
362
|
`${pathLabel}.children[${index}]`,
|
|
211
363
|
);
|
|
212
364
|
});
|
|
@@ -234,27 +386,8 @@ function collectExternalTilesetPaths(tile, baseDir, results) {
|
|
|
234
386
|
return;
|
|
235
387
|
}
|
|
236
388
|
|
|
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
|
-
}
|
|
389
|
+
getLocalExternalTilesetPaths(tile, baseDir).forEach((filePath) => {
|
|
390
|
+
results.add(filePath);
|
|
258
391
|
});
|
|
259
392
|
|
|
260
393
|
if (!Array.isArray(tile.children)) {
|
|
@@ -268,7 +401,13 @@ function collectExternalTilesetPaths(tile, baseDir, results) {
|
|
|
268
401
|
|
|
269
402
|
function updateTilesetJsonFile(
|
|
270
403
|
tilesetPath,
|
|
271
|
-
{
|
|
404
|
+
{
|
|
405
|
+
geometricErrorLayerScale,
|
|
406
|
+
geometricErrorScale,
|
|
407
|
+
rootDir,
|
|
408
|
+
rootTransform = null,
|
|
409
|
+
leafDistanceCache = new Map(),
|
|
410
|
+
},
|
|
272
411
|
visited = new Set(),
|
|
273
412
|
) {
|
|
274
413
|
const resolvedPath = path.resolve(tilesetPath);
|
|
@@ -277,14 +416,7 @@ function updateTilesetJsonFile(
|
|
|
277
416
|
}
|
|
278
417
|
visited.add(resolvedPath);
|
|
279
418
|
|
|
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
|
-
}
|
|
419
|
+
assertTilesetPathInsideRoot(resolvedPath, rootDir);
|
|
288
420
|
|
|
289
421
|
if (!fs.existsSync(resolvedPath)) {
|
|
290
422
|
throw new InspectorError(
|
|
@@ -301,30 +433,44 @@ function updateTilesetJsonFile(
|
|
|
301
433
|
tileset.root.transform = rootTransform.slice();
|
|
302
434
|
}
|
|
303
435
|
|
|
436
|
+
const tilesetDir = path.dirname(resolvedPath);
|
|
304
437
|
scaleGeometricErrorValue(
|
|
305
438
|
tileset,
|
|
306
439
|
'geometricError',
|
|
307
|
-
|
|
440
|
+
getGeometricErrorScaleForLeafDistance(
|
|
441
|
+
geometricErrorScale,
|
|
442
|
+
geometricErrorLayerScale,
|
|
443
|
+
getTileLeafDistance(
|
|
444
|
+
tileset.root,
|
|
445
|
+
tilesetDir,
|
|
446
|
+
rootDir,
|
|
447
|
+
leafDistanceCache,
|
|
448
|
+
new Set(),
|
|
449
|
+
),
|
|
450
|
+
resolvedPath,
|
|
451
|
+
),
|
|
308
452
|
`${resolvedPath}.geometricError`,
|
|
309
453
|
);
|
|
310
454
|
scaleTilesetGeometricErrors(
|
|
311
455
|
tileset.root,
|
|
312
456
|
geometricErrorScale,
|
|
457
|
+
geometricErrorLayerScale,
|
|
458
|
+
tilesetDir,
|
|
459
|
+
rootDir,
|
|
460
|
+
leafDistanceCache,
|
|
313
461
|
`${resolvedPath}.root`,
|
|
314
462
|
);
|
|
315
463
|
writeJsonAtomic(resolvedPath, tileset);
|
|
316
464
|
|
|
317
465
|
const nestedTilesets = new Set();
|
|
318
|
-
collectExternalTilesetPaths(
|
|
319
|
-
tileset.root,
|
|
320
|
-
path.dirname(resolvedPath),
|
|
321
|
-
nestedTilesets,
|
|
322
|
-
);
|
|
466
|
+
collectExternalTilesetPaths(tileset.root, tilesetDir, nestedTilesets);
|
|
323
467
|
nestedTilesets.forEach((childTilesetPath) => {
|
|
324
468
|
updateTilesetJsonFile(
|
|
325
469
|
childTilesetPath,
|
|
326
470
|
{
|
|
471
|
+
geometricErrorLayerScale,
|
|
327
472
|
geometricErrorScale,
|
|
473
|
+
leafDistanceCache,
|
|
328
474
|
rootDir,
|
|
329
475
|
},
|
|
330
476
|
visited,
|
|
@@ -337,13 +483,17 @@ function updateTilesetJsonFile(
|
|
|
337
483
|
function saveViewerTransform(
|
|
338
484
|
rootTilesetPath,
|
|
339
485
|
editMatrix,
|
|
340
|
-
{ geometricErrorScale = 1 } = {},
|
|
486
|
+
{ geometricErrorLayerScale = 1, geometricErrorScale = 1 } = {},
|
|
341
487
|
) {
|
|
342
488
|
const normalizedEdit = normalizeMatrix4Array(editMatrix, 'transform');
|
|
343
489
|
const normalizedGeometricErrorScale = normalizePositiveFinite(
|
|
344
490
|
geometricErrorScale,
|
|
345
491
|
'geometricErrorScale',
|
|
346
492
|
);
|
|
493
|
+
const normalizedGeometricErrorLayerScale = normalizePositiveFinite(
|
|
494
|
+
geometricErrorLayerScale,
|
|
495
|
+
'geometricErrorLayerScale',
|
|
496
|
+
);
|
|
347
497
|
const tilesetPath = path.resolve(rootTilesetPath);
|
|
348
498
|
const rootDir = path.dirname(tilesetPath);
|
|
349
499
|
|
|
@@ -366,6 +516,7 @@ function saveViewerTransform(
|
|
|
366
516
|
const nextRoot = multiplyMatrix4(normalizedEdit, currentRoot);
|
|
367
517
|
|
|
368
518
|
updateTilesetJsonFile(tilesetPath, {
|
|
519
|
+
geometricErrorLayerScale: normalizedGeometricErrorLayerScale,
|
|
369
520
|
geometricErrorScale: normalizedGeometricErrorScale,
|
|
370
521
|
rootDir,
|
|
371
522
|
rootTransform: nextRoot,
|
|
@@ -386,6 +537,15 @@ function saveViewerTransform(
|
|
|
386
537
|
summary.root_coordinate = null;
|
|
387
538
|
summary.viewer_geometric_error_scale =
|
|
388
539
|
previousGeometricErrorScale * normalizedGeometricErrorScale;
|
|
540
|
+
const previousGeometricErrorLayerScale =
|
|
541
|
+
summary.viewer_geometric_error_layer_scale == null
|
|
542
|
+
? 1
|
|
543
|
+
: normalizePositiveFinite(
|
|
544
|
+
summary.viewer_geometric_error_layer_scale,
|
|
545
|
+
'build_summary.viewer_geometric_error_layer_scale',
|
|
546
|
+
);
|
|
547
|
+
summary.viewer_geometric_error_layer_scale =
|
|
548
|
+
previousGeometricErrorLayerScale * normalizedGeometricErrorLayerScale;
|
|
389
549
|
writeJsonAtomic(summaryPath, summary);
|
|
390
550
|
}
|
|
391
551
|
|
|
@@ -529,9 +689,28 @@ async function handleSaveTransformRequest(rootTilesetPath, req, res) {
|
|
|
529
689
|
return;
|
|
530
690
|
}
|
|
531
691
|
|
|
692
|
+
let normalizedGeometricErrorLayerScale;
|
|
693
|
+
try {
|
|
694
|
+
normalizedGeometricErrorLayerScale = normalizePositiveFinite(
|
|
695
|
+
payload.geometricErrorLayerScale == null
|
|
696
|
+
? 1
|
|
697
|
+
: payload.geometricErrorLayerScale,
|
|
698
|
+
'geometricErrorLayerScale',
|
|
699
|
+
);
|
|
700
|
+
} catch (err) {
|
|
701
|
+
sendJson(res, 400, {
|
|
702
|
+
error:
|
|
703
|
+
err instanceof Error && err.message
|
|
704
|
+
? err.message
|
|
705
|
+
: 'geometricErrorLayerScale must be a finite number greater than 0.',
|
|
706
|
+
});
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
532
710
|
let nextRoot;
|
|
533
711
|
try {
|
|
534
712
|
nextRoot = saveViewerTransform(rootTilesetPath, normalizedEdit, {
|
|
713
|
+
geometricErrorLayerScale: normalizedGeometricErrorLayerScale,
|
|
535
714
|
geometricErrorScale: normalizedGeometricErrorScale,
|
|
536
715
|
});
|
|
537
716
|
} catch (err) {
|
|
@@ -547,6 +726,7 @@ async function handleSaveTransformRequest(rootTilesetPath, req, res) {
|
|
|
547
726
|
sendJson(res, 200, {
|
|
548
727
|
ok: true,
|
|
549
728
|
transform: nextRoot,
|
|
729
|
+
geometricErrorLayerScale: normalizedGeometricErrorLayerScale,
|
|
550
730
|
geometricErrorScale: normalizedGeometricErrorScale,
|
|
551
731
|
});
|
|
552
732
|
}
|
|
@@ -756,7 +936,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
756
936
|
.toolbar {
|
|
757
937
|
display: grid;
|
|
758
938
|
align-content: start;
|
|
759
|
-
gap:
|
|
939
|
+
gap: 10px;
|
|
760
940
|
padding: 14px;
|
|
761
941
|
border: 1px solid rgba(22, 50, 79, 0.12);
|
|
762
942
|
border-top: 0;
|
|
@@ -782,7 +962,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
782
962
|
justify-content: center;
|
|
783
963
|
width: 100%;
|
|
784
964
|
min-height: 32px;
|
|
785
|
-
padding:
|
|
965
|
+
padding: 8px 12px;
|
|
786
966
|
border: 1px solid rgba(22, 50, 79, 0.08);
|
|
787
967
|
border-radius: 20px 20px 0 0;
|
|
788
968
|
font: inherit;
|
|
@@ -804,7 +984,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
804
984
|
justify-self: start;
|
|
805
985
|
width: auto;
|
|
806
986
|
min-height: 36px;
|
|
807
|
-
padding:
|
|
987
|
+
padding: 6px 12px 7px;
|
|
808
988
|
border-radius: 999px;
|
|
809
989
|
color: #506377;
|
|
810
990
|
background: rgba(255, 255, 255, 0.94);
|
|
@@ -830,7 +1010,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
830
1010
|
.toolbar-section {
|
|
831
1011
|
display: grid;
|
|
832
1012
|
gap: 10px;
|
|
833
|
-
padding: 12px;
|
|
1013
|
+
padding: 10px 12px;
|
|
834
1014
|
border: 1px solid rgba(22, 50, 79, 0.08);
|
|
835
1015
|
border-radius: 14px;
|
|
836
1016
|
background:
|
|
@@ -839,7 +1019,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
839
1019
|
|
|
840
1020
|
.toolbar-section-header {
|
|
841
1021
|
display: flex;
|
|
842
|
-
align-items:
|
|
1022
|
+
align-items: flex-start;
|
|
843
1023
|
justify-content: space-between;
|
|
844
1024
|
gap: 10px;
|
|
845
1025
|
}
|
|
@@ -857,7 +1037,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
857
1037
|
margin: 0;
|
|
858
1038
|
font-size: 12px;
|
|
859
1039
|
font-weight: 700;
|
|
860
|
-
color: #
|
|
1040
|
+
color: #5d738b;
|
|
861
1041
|
}
|
|
862
1042
|
|
|
863
1043
|
.button-row {
|
|
@@ -886,7 +1066,7 @@ function buildViewerHtml(viewerConfig) {
|
|
|
886
1066
|
justify-content: center;
|
|
887
1067
|
border: 0;
|
|
888
1068
|
border-radius: 999px;
|
|
889
|
-
padding:
|
|
1069
|
+
padding: 7px 14px;
|
|
890
1070
|
font: inherit;
|
|
891
1071
|
font-size: 14px;
|
|
892
1072
|
font-weight: 600;
|
|
@@ -926,6 +1106,14 @@ function buildViewerHtml(viewerConfig) {
|
|
|
926
1106
|
min-width: 0;
|
|
927
1107
|
}
|
|
928
1108
|
|
|
1109
|
+
.range-field-header {
|
|
1110
|
+
display: flex;
|
|
1111
|
+
align-items: center;
|
|
1112
|
+
justify-content: space-between;
|
|
1113
|
+
gap: 8px;
|
|
1114
|
+
min-width: 0;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
929
1117
|
.range-field span {
|
|
930
1118
|
font-size: 11px;
|
|
931
1119
|
font-weight: 600;
|
|
@@ -1105,10 +1293,12 @@ function buildViewerHtml(viewerConfig) {
|
|
|
1105
1293
|
<div class="toolbar-section">
|
|
1106
1294
|
<div class="toolbar-section-header">
|
|
1107
1295
|
<p class="toolbar-section-title">LOD</p>
|
|
1108
|
-
<p id="geometric-error-value" class="toolbar-value">x1.00</p>
|
|
1109
1296
|
</div>
|
|
1110
1297
|
<label class="range-field">
|
|
1111
|
-
<
|
|
1298
|
+
<div class="range-field-header">
|
|
1299
|
+
<span>Geometric Error</span>
|
|
1300
|
+
<p id="geometric-error-value" class="toolbar-value">x1.00</p>
|
|
1301
|
+
</div>
|
|
1112
1302
|
<input
|
|
1113
1303
|
id="geometric-error-scale"
|
|
1114
1304
|
type="range"
|
|
@@ -1118,6 +1308,20 @@ function buildViewerHtml(viewerConfig) {
|
|
|
1118
1308
|
value="0"
|
|
1119
1309
|
/>
|
|
1120
1310
|
</label>
|
|
1311
|
+
<label class="range-field">
|
|
1312
|
+
<div class="range-field-header">
|
|
1313
|
+
<span>Layer Multiplier</span>
|
|
1314
|
+
<p id="geometric-error-layer-value" class="toolbar-value">x1.00</p>
|
|
1315
|
+
</div>
|
|
1316
|
+
<input
|
|
1317
|
+
id="geometric-error-layer-scale"
|
|
1318
|
+
type="range"
|
|
1319
|
+
min="-0.5849625007211562"
|
|
1320
|
+
max="0.5849625007211562"
|
|
1321
|
+
step="any"
|
|
1322
|
+
value="0"
|
|
1323
|
+
/>
|
|
1324
|
+
</label>
|
|
1121
1325
|
</div>
|
|
1122
1326
|
<div class="toolbar-section status-panel">
|
|
1123
1327
|
<div class="status-actions">
|