3dtiles-inspector 0.1.4 → 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 +10 -0
- package/README.md +2 -0
- package/dist/inspector-assets/viewer/app.js +134 -2
- package/package.json +1 -1
- package/src/viewer/app.js +150 -2
- package/src/viewer/session.js +254 -50
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ 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
|
+
|
|
9
19
|
## [0.1.4] - 2026-04-25
|
|
10
20
|
|
|
11
21
|
### 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/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,12 +65484,26 @@ 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_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";
|
|
65493
65507
|
var DEFAULT_ERROR_TARGET = 16;
|
|
65494
65508
|
var DEFAULT_TERRAIN_ERROR_TARGET = 16;
|
|
65495
65509
|
var RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
|
|
@@ -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,12 +100,26 @@ 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_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';
|
|
109
123
|
const DEFAULT_ERROR_TARGET = 16;
|
|
110
124
|
const DEFAULT_TERRAIN_ERROR_TARGET = 16;
|
|
111
125
|
const RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
|
|
@@ -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();
|
|
@@ -505,10 +523,88 @@ function updateGeometricErrorScaleDisplay() {
|
|
|
505
523
|
)}`;
|
|
506
524
|
}
|
|
507
525
|
|
|
526
|
+
function updateGeometricErrorLayerScaleDisplay() {
|
|
527
|
+
geometricErrorLayerValueEl.textContent = `x${formatGeometricErrorScale(
|
|
528
|
+
geometricErrorLayerScale,
|
|
529
|
+
)}`;
|
|
530
|
+
}
|
|
531
|
+
|
|
508
532
|
function getEffectiveGeometricErrorScale() {
|
|
509
533
|
return lastSavedGeometricErrorScale * geometricErrorScale;
|
|
510
534
|
}
|
|
511
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
|
+
|
|
512
608
|
function getGaussianMeshSplatCount(mesh) {
|
|
513
609
|
if (!mesh || typeof mesh !== 'object') {
|
|
514
610
|
return 0;
|
|
@@ -605,6 +701,21 @@ function setGeometricErrorScaleExponent(exponent) {
|
|
|
605
701
|
updateTilesetErrorTarget();
|
|
606
702
|
}
|
|
607
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
|
+
|
|
608
719
|
function syncTerrainButton() {
|
|
609
720
|
terrainButton.classList.toggle('active', terrainEnabled);
|
|
610
721
|
terrainLight.visible = terrainEnabled;
|
|
@@ -702,7 +813,15 @@ function toggleTransformMode(mode) {
|
|
|
702
813
|
geometricErrorScaleInput.min = String(GEOMETRIC_ERROR_SCALE_MIN_EXPONENT);
|
|
703
814
|
geometricErrorScaleInput.max = String(GEOMETRIC_ERROR_SCALE_MAX_EXPONENT);
|
|
704
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);
|
|
705
823
|
setGeometricErrorScaleExponent(geometricErrorScaleExponent);
|
|
824
|
+
setGeometricErrorLayerScaleExponent(geometricErrorLayerScaleExponent);
|
|
706
825
|
setTerrainEnabled(terrainEnabled);
|
|
707
826
|
setTransformMode(activeTransformMode);
|
|
708
827
|
syncToolbarVisibility();
|
|
@@ -1324,7 +1443,9 @@ function loadTileset(url) {
|
|
|
1324
1443
|
|
|
1325
1444
|
resetEditableGroup();
|
|
1326
1445
|
lastSavedGeometricErrorScale = 1;
|
|
1446
|
+
lastSavedGeometricErrorLayerScale = 1;
|
|
1327
1447
|
setGeometricErrorScaleExponent(0);
|
|
1448
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
1328
1449
|
savedRootMatrix.identity();
|
|
1329
1450
|
savedRootMatrixLoadError = null;
|
|
1330
1451
|
savedRootMatrixPromise = refreshSavedRootMatrix(url).then(
|
|
@@ -1348,7 +1469,16 @@ function loadTileset(url) {
|
|
|
1348
1469
|
next.registerPlugin(new TileCompressionPlugin());
|
|
1349
1470
|
next.registerPlugin(new UnloadTilesPlugin());
|
|
1350
1471
|
next.registerPlugin(new ImplicitTilingPlugin());
|
|
1351
|
-
next.registerPlugin(
|
|
1472
|
+
next.registerPlugin(geometricErrorLayerScalePlugin);
|
|
1473
|
+
next.registerPlugin(
|
|
1474
|
+
new GaussianSplatPlugin({
|
|
1475
|
+
renderer,
|
|
1476
|
+
scene,
|
|
1477
|
+
sparkRendererOptions: {
|
|
1478
|
+
accumExtSplats: true,
|
|
1479
|
+
},
|
|
1480
|
+
}),
|
|
1481
|
+
);
|
|
1352
1482
|
debugTilesPlugin = new DebugTilesPlugin({
|
|
1353
1483
|
displayBoxBounds: showBoundingVolume,
|
|
1354
1484
|
displaySphereBounds: showBoundingVolume,
|
|
@@ -1399,6 +1529,7 @@ function loadTileset(url) {
|
|
|
1399
1529
|
}
|
|
1400
1530
|
};
|
|
1401
1531
|
|
|
1532
|
+
next.addEventListener('load-tileset', applyGeometricErrorLayerScaleToTileset);
|
|
1402
1533
|
next.addEventListener('load-tile-set', tryFrame);
|
|
1403
1534
|
next.addEventListener('load-tileset', tryFrame);
|
|
1404
1535
|
}
|
|
@@ -1414,6 +1545,8 @@ async function saveTransform() {
|
|
|
1414
1545
|
.multiply(lastSavedMatrix.clone().invert());
|
|
1415
1546
|
const incrementalGeometricErrorScale = geometricErrorScale;
|
|
1416
1547
|
const savedGeometricErrorScale = getEffectiveGeometricErrorScale();
|
|
1548
|
+
const incrementalGeometricErrorLayerScale = geometricErrorLayerScale;
|
|
1549
|
+
const savedGeometricErrorLayerScale = getEffectiveGeometricErrorLayerScale();
|
|
1417
1550
|
|
|
1418
1551
|
try {
|
|
1419
1552
|
const response = await fetch(SAVE_URL, {
|
|
@@ -1422,6 +1555,7 @@ async function saveTransform() {
|
|
|
1422
1555
|
'Content-Type': 'application/json',
|
|
1423
1556
|
},
|
|
1424
1557
|
body: JSON.stringify({
|
|
1558
|
+
geometricErrorLayerScale: incrementalGeometricErrorLayerScale,
|
|
1425
1559
|
geometricErrorScale: incrementalGeometricErrorScale,
|
|
1426
1560
|
transform: incrementalMatrix.toArray(),
|
|
1427
1561
|
}),
|
|
@@ -1452,13 +1586,17 @@ async function saveTransform() {
|
|
|
1452
1586
|
}
|
|
1453
1587
|
}
|
|
1454
1588
|
lastSavedGeometricErrorScale = savedGeometricErrorScale;
|
|
1589
|
+
lastSavedGeometricErrorLayerScale = savedGeometricErrorLayerScale;
|
|
1455
1590
|
lastSavedMatrix.copy(currentMatrix);
|
|
1456
1591
|
setGeometricErrorScaleExponent(0);
|
|
1592
|
+
setGeometricErrorLayerScaleExponent(0);
|
|
1457
1593
|
syncTransformHandleFromTilesTransform();
|
|
1458
1594
|
syncCoordinateInputsFromTilesTransform();
|
|
1459
1595
|
setStatus(
|
|
1460
|
-
`Saved transform
|
|
1596
|
+
`Saved transform, geometric-error scale x${formatGeometricErrorScale(
|
|
1461
1597
|
savedGeometricErrorScale,
|
|
1598
|
+
)}, and layer multiplier x${formatGeometricErrorScale(
|
|
1599
|
+
savedGeometricErrorLayerScale,
|
|
1462
1600
|
)} to ${ROOT_TILESET_LABEL} and build_summary.json.`,
|
|
1463
1601
|
);
|
|
1464
1602
|
} catch (err) {
|
|
@@ -1506,6 +1644,16 @@ geometricErrorScaleInput.addEventListener('change', () => {
|
|
|
1506
1644
|
)}.`,
|
|
1507
1645
|
);
|
|
1508
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
|
+
});
|
|
1509
1657
|
moveToTilesButton.addEventListener('click', moveCameraToTiles);
|
|
1510
1658
|
moveCameraToCoordinateButton.addEventListener('click', moveCameraToCoordinate);
|
|
1511
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">
|