3dtiles-inspector 0.1.5 → 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 CHANGED
@@ -6,6 +6,13 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.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
+
9
16
  ## [0.1.5] - 2026-04-27
10
17
 
11
18
  ### Added
package/README.md CHANGED
@@ -82,7 +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
+ - `Layer Multiplier` scaling from `1/8x` to `8x` for each tile's geometric-error difference from its leaf baseline
86
86
  - `Save` to persist the updated root transform and geometric-error scale back to disk
87
87
 
88
88
  If `build_summary.json` exists next to the root tileset, `Save` also updates:
@@ -65496,14 +65496,9 @@ var saveButton = document.getElementById("save");
65496
65496
  var GEOMETRIC_ERROR_SCALE_MIN_EXPONENT = -4;
65497
65497
  var GEOMETRIC_ERROR_SCALE_MAX_EXPONENT = 4;
65498
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";
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;
65507
65502
  var DEFAULT_ERROR_TARGET = 16;
65508
65503
  var DEFAULT_TERRAIN_ERROR_TARGET = 16;
65509
65504
  var RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
@@ -65860,22 +65855,6 @@ function getEffectiveGeometricErrorScale() {
65860
65855
  function getEffectiveGeometricErrorLayerScale() {
65861
65856
  return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
65862
65857
  }
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
65858
  function getOriginalTileGeometricError(tile) {
65880
65859
  if (!tile || typeof tile !== "object") {
65881
65860
  return null;
@@ -65889,13 +65868,33 @@ function getOriginalTileGeometricError(tile) {
65889
65868
  }
65890
65869
  return originalTileGeometricErrors.get(tile);
65891
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
+ }
65892
65891
  function applyGeometricErrorLayerScaleToTile(tile) {
65893
65892
  const originalGeometricError = getOriginalTileGeometricError(tile);
65894
- if (originalGeometricError === null) {
65893
+ const leafGeometricError = getKnownTileLeafGeometricError(tile);
65894
+ if (originalGeometricError === null || leafGeometricError === null) {
65895
65895
  return;
65896
65896
  }
65897
- const leafDistance = getKnownTileLeafDistance(tile);
65898
- tile.geometricError = originalGeometricError * getEffectiveGeometricErrorLayerScale() ** leafDistance;
65897
+ tile.geometricError = leafGeometricError + (originalGeometricError - leafGeometricError) * getEffectiveGeometricErrorLayerScale();
65899
65898
  }
65900
65899
  function applyGeometricErrorLayerScaleToTileset() {
65901
65900
  if (!tiles) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "3dtiles-inspector",
3
- "version": "0.1.5",
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
@@ -112,14 +112,9 @@ const saveButton = document.getElementById('save');
112
112
  const GEOMETRIC_ERROR_SCALE_MIN_EXPONENT = -4;
113
113
  const GEOMETRIC_ERROR_SCALE_MAX_EXPONENT = 4;
114
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';
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;
123
118
  const DEFAULT_ERROR_TARGET = 16;
124
119
  const DEFAULT_TERRAIN_ERROR_TARGET = 16;
125
120
  const RUNTIME_STATS_UPDATE_INTERVAL_MS = 250;
@@ -537,24 +532,6 @@ function getEffectiveGeometricErrorLayerScale() {
537
532
  return lastSavedGeometricErrorLayerScale * geometricErrorLayerScale;
538
533
  }
539
534
 
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
535
  function getOriginalTileGeometricError(tile) {
559
536
  if (!tile || typeof tile !== 'object') {
560
537
  return null;
@@ -571,16 +548,49 @@ function getOriginalTileGeometricError(tile) {
571
548
  return originalTileGeometricErrors.get(tile);
572
549
  }
573
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
+
574
583
  function applyGeometricErrorLayerScaleToTile(tile) {
575
584
  const originalGeometricError = getOriginalTileGeometricError(tile);
576
- if (originalGeometricError === null) {
585
+ const leafGeometricError = getKnownTileLeafGeometricError(tile);
586
+ if (originalGeometricError === null || leafGeometricError === null) {
577
587
  return;
578
588
  }
579
589
 
580
- const leafDistance = getKnownTileLeafDistance(tile);
581
590
  tile.geometricError =
582
- originalGeometricError *
583
- getEffectiveGeometricErrorLayerScale() ** leafDistance;
591
+ leafGeometricError +
592
+ (originalGeometricError - leafGeometricError) *
593
+ getEffectiveGeometricErrorLayerScale();
584
594
  }
585
595
 
586
596
  function applyGeometricErrorLayerScaleToTileset() {
@@ -174,7 +174,14 @@ function normalizePositiveFinite(value, name) {
174
174
  return number;
175
175
  }
176
176
 
177
- function scaleGeometricErrorValue(target, key, scale, label) {
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,7 +191,14 @@ function scaleGeometricErrorValue(target, key, scale, label) {
184
191
  throw new InspectorError(`${label} must be a finite number.`);
185
192
  }
186
193
 
187
- const next = number * scale;
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;
188
202
  if (!Number.isFinite(next)) {
189
203
  throw new InspectorError(`${label} scaled value must be finite.`);
190
204
  }
@@ -192,20 +206,6 @@ function scaleGeometricErrorValue(target, key, scale, label) {
192
206
  target[key] = next;
193
207
  }
194
208
 
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
209
  function assertTilesetPathInsideRoot(resolvedPath, rootDir) {
210
210
  if (
211
211
  resolvedPath !== rootDir &&
@@ -250,15 +250,15 @@ function getLocalExternalTilesetPaths(tile, baseDir) {
250
250
  return paths;
251
251
  }
252
252
 
253
- function getTilesetRootLeafDistance(
253
+ function getTilesetRootLeafGeometricError(
254
254
  tilesetPath,
255
255
  rootDir,
256
- leafDistanceCache,
256
+ leafGeometricErrorCache,
257
257
  stack,
258
258
  ) {
259
259
  const resolvedPath = path.resolve(tilesetPath);
260
- if (leafDistanceCache.has(resolvedPath)) {
261
- return leafDistanceCache.get(resolvedPath);
260
+ if (leafGeometricErrorCache.has(resolvedPath)) {
261
+ return leafGeometricErrorCache.get(resolvedPath);
262
262
  }
263
263
 
264
264
  if (stack.has(resolvedPath)) {
@@ -279,47 +279,65 @@ function getTilesetRootLeafDistance(
279
279
  }
280
280
 
281
281
  stack.add(resolvedPath);
282
- const leafDistance = getTileLeafDistance(
282
+ const leafGeometricError = getTileLeafGeometricError(
283
283
  tileset.root,
284
284
  path.dirname(resolvedPath),
285
285
  rootDir,
286
- leafDistanceCache,
286
+ leafGeometricErrorCache,
287
287
  stack,
288
288
  );
289
289
  stack.delete(resolvedPath);
290
- leafDistanceCache.set(resolvedPath, leafDistance);
291
- return leafDistance;
290
+ leafGeometricErrorCache.set(resolvedPath, leafGeometricError);
291
+ return leafGeometricError;
292
292
  }
293
293
 
294
- function getTileLeafDistance(tile, baseDir, rootDir, leafDistanceCache, stack) {
294
+ function getTileLeafGeometricError(
295
+ tile,
296
+ baseDir,
297
+ rootDir,
298
+ leafGeometricErrorCache,
299
+ stack,
300
+ ) {
295
301
  if (!tile || typeof tile !== 'object') {
296
302
  return 0;
297
303
  }
298
304
 
299
- let maxDistance = 0;
305
+ const ownGeometricError = Number(tile.geometricError);
306
+ if (!Number.isFinite(ownGeometricError)) {
307
+ return 0;
308
+ }
309
+
310
+ let leafGeometricError = null;
300
311
  if (Array.isArray(tile.children)) {
301
312
  tile.children.forEach((child) => {
302
- maxDistance = Math.max(
303
- maxDistance,
304
- getTileLeafDistance(child, baseDir, rootDir, leafDistanceCache, stack) +
305
- 1,
313
+ const childLeafGeometricError = getTileLeafGeometricError(
314
+ child,
315
+ baseDir,
316
+ rootDir,
317
+ leafGeometricErrorCache,
318
+ stack,
306
319
  );
320
+ leafGeometricError =
321
+ leafGeometricError === null
322
+ ? childLeafGeometricError
323
+ : Math.min(leafGeometricError, childLeafGeometricError);
307
324
  });
308
325
  }
309
326
 
310
327
  getLocalExternalTilesetPaths(tile, baseDir).forEach((childTilesetPath) => {
311
- maxDistance = Math.max(
312
- maxDistance,
313
- getTilesetRootLeafDistance(
314
- childTilesetPath,
315
- rootDir,
316
- leafDistanceCache,
317
- stack,
318
- ) + 1,
328
+ const childLeafGeometricError = getTilesetRootLeafGeometricError(
329
+ childTilesetPath,
330
+ rootDir,
331
+ leafGeometricErrorCache,
332
+ stack,
319
333
  );
334
+ leafGeometricError =
335
+ leafGeometricError === null
336
+ ? childLeafGeometricError
337
+ : Math.min(leafGeometricError, childLeafGeometricError);
320
338
  });
321
339
 
322
- return maxDistance;
340
+ return leafGeometricError === null ? ownGeometricError : leafGeometricError;
323
341
  }
324
342
 
325
343
  function scaleTilesetGeometricErrors(
@@ -328,7 +346,7 @@ function scaleTilesetGeometricErrors(
328
346
  geometricErrorLayerScale,
329
347
  baseDir,
330
348
  rootDir,
331
- leafDistanceCache,
349
+ leafGeometricErrorCache,
332
350
  pathLabel = 'tileset.root',
333
351
  ) {
334
352
  if (!tile || typeof tile !== 'object') {
@@ -338,11 +356,14 @@ function scaleTilesetGeometricErrors(
338
356
  scaleGeometricErrorValue(
339
357
  tile,
340
358
  'geometricError',
341
- getGeometricErrorScaleForLeafDistance(
342
- geometricErrorScale,
343
- geometricErrorLayerScale,
344
- getTileLeafDistance(tile, baseDir, rootDir, leafDistanceCache, new Set()),
345
- pathLabel,
359
+ geometricErrorScale,
360
+ geometricErrorLayerScale,
361
+ getTileLeafGeometricError(
362
+ tile,
363
+ baseDir,
364
+ rootDir,
365
+ leafGeometricErrorCache,
366
+ new Set(),
346
367
  ),
347
368
  `${pathLabel}.geometricError`,
348
369
  );
@@ -358,7 +379,7 @@ function scaleTilesetGeometricErrors(
358
379
  geometricErrorLayerScale,
359
380
  baseDir,
360
381
  rootDir,
361
- leafDistanceCache,
382
+ leafGeometricErrorCache,
362
383
  `${pathLabel}.children[${index}]`,
363
384
  );
364
385
  });
@@ -406,7 +427,7 @@ function updateTilesetJsonFile(
406
427
  geometricErrorScale,
407
428
  rootDir,
408
429
  rootTransform = null,
409
- leafDistanceCache = new Map(),
430
+ leafGeometricErrorCache = new Map(),
410
431
  },
411
432
  visited = new Set(),
412
433
  ) {
@@ -437,17 +458,14 @@ function updateTilesetJsonFile(
437
458
  scaleGeometricErrorValue(
438
459
  tileset,
439
460
  'geometricError',
440
- getGeometricErrorScaleForLeafDistance(
441
- geometricErrorScale,
442
- geometricErrorLayerScale,
443
- getTileLeafDistance(
444
- tileset.root,
445
- tilesetDir,
446
- rootDir,
447
- leafDistanceCache,
448
- new Set(),
449
- ),
450
- resolvedPath,
461
+ geometricErrorScale,
462
+ geometricErrorLayerScale,
463
+ getTileLeafGeometricError(
464
+ tileset.root,
465
+ tilesetDir,
466
+ rootDir,
467
+ leafGeometricErrorCache,
468
+ new Set(),
451
469
  ),
452
470
  `${resolvedPath}.geometricError`,
453
471
  );
@@ -457,7 +475,7 @@ function updateTilesetJsonFile(
457
475
  geometricErrorLayerScale,
458
476
  tilesetDir,
459
477
  rootDir,
460
- leafDistanceCache,
478
+ leafGeometricErrorCache,
461
479
  `${resolvedPath}.root`,
462
480
  );
463
481
  writeJsonAtomic(resolvedPath, tileset);
@@ -470,7 +488,7 @@ function updateTilesetJsonFile(
470
488
  {
471
489
  geometricErrorLayerScale,
472
490
  geometricErrorScale,
473
- leafDistanceCache,
491
+ leafGeometricErrorCache,
474
492
  rootDir,
475
493
  },
476
494
  visited,
@@ -936,8 +954,8 @@ function buildViewerHtml(viewerConfig) {
936
954
  .toolbar {
937
955
  display: grid;
938
956
  align-content: start;
939
- gap: 10px;
940
- padding: 14px;
957
+ gap: 8px;
958
+ padding: 10px 14px;
941
959
  border: 1px solid rgba(22, 50, 79, 0.12);
942
960
  border-top: 0;
943
961
  border-radius: 0 0 20px 20px;
@@ -1102,7 +1120,7 @@ function buildViewerHtml(viewerConfig) {
1102
1120
 
1103
1121
  .range-field {
1104
1122
  display: grid;
1105
- gap: 8px;
1123
+ gap: 4px;
1106
1124
  min-width: 0;
1107
1125
  }
1108
1126
 
@@ -1316,9 +1334,9 @@ function buildViewerHtml(viewerConfig) {
1316
1334
  <input
1317
1335
  id="geometric-error-layer-scale"
1318
1336
  type="range"
1319
- min="-0.5849625007211562"
1320
- max="0.5849625007211562"
1321
- step="any"
1337
+ min="-3"
1338
+ max="3"
1339
+ step="0.1"
1322
1340
  value="0"
1323
1341
  />
1324
1342
  </label>