3dtiles-inspector 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/inspector-assets/viewer/app.js +265 -184
- package/package.json +2 -2
- package/src/server/saveTransform.js +2 -0
- package/src/server/saveTransformHandler.js +2 -0
- package/src/server/splatCrop/gaussianPrimitives.js +48 -0
- package/src/server/splatCrop/index.js +21 -3
- package/src/server/splatCrop/traversal.js +131 -1
- package/src/server/splatCrop/worker.js +20 -2
- package/src/server/splatCrop/workerPool.js +2 -0
- package/src/viewer/app.js +23 -2
- package/src/viewer/navigation/cameraUrlPose.js +172 -0
- package/src/viewer/navigation/flyTo.js +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.7] - 2026-05-10
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Changed crop saves to remove local orphaned Gaussian Splat `.glb` / `.gltf` resources and private external buffers after fully cropped content is pruned from the tileset.
|
|
14
|
+
- Updated `3d-tiles-rendererjs-3dgs-plugin` from `0.1.5` to `0.1.7`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed cropped Gaussian Splat resources keeping stale glTF accessor counts after SPZ data is rewritten, which could make Cesium fail while generating splat textures.
|
|
19
|
+
|
|
20
|
+
## [0.2.6] - 2026-05-05
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Added realtime `camerapose` URL synchronization so shared viewer URLs can restore the current camera pose.
|
|
25
|
+
|
|
9
26
|
## [0.2.5] - 2026-05-05
|
|
10
27
|
|
|
11
28
|
### Fixed
|
|
@@ -43855,6 +43855,12 @@ async function buildGaussianMeshSource(descriptor, abortSignal) {
|
|
|
43855
43855
|
}
|
|
43856
43856
|
return { extSplats };
|
|
43857
43857
|
}
|
|
43858
|
+
function isXrPresenting(renderer) {
|
|
43859
|
+
return renderer.xr.isPresenting;
|
|
43860
|
+
}
|
|
43861
|
+
function getUpdateSourceCamera(renderer, camera, xrPresenting = isXrPresenting(renderer)) {
|
|
43862
|
+
return xrPresenting ? renderer.xr.getCamera() : camera;
|
|
43863
|
+
}
|
|
43858
43864
|
function ensureCameraClone(cached, source) {
|
|
43859
43865
|
if (!cached || cached.constructor !== source.constructor) {
|
|
43860
43866
|
return source.clone();
|
|
@@ -43862,111 +43868,11 @@ function ensureCameraClone(cached, source) {
|
|
|
43862
43868
|
cached.copy(source, false);
|
|
43863
43869
|
return cached;
|
|
43864
43870
|
}
|
|
43865
|
-
function
|
|
43866
|
-
|
|
43867
|
-
while (ancestor) {
|
|
43868
|
-
if (ancestor instanceof SplatMesh || isGaussianSplat(ancestor)) {
|
|
43869
|
-
return true;
|
|
43870
|
-
}
|
|
43871
|
-
ancestor = ancestor.parent;
|
|
43872
|
-
}
|
|
43873
|
-
return false;
|
|
43874
|
-
}
|
|
43875
|
-
function isGlobalSplatEdit(node) {
|
|
43876
|
-
return node instanceof SplatEdit && !hasGaussianSplatAncestor(node);
|
|
43871
|
+
function isGaussianSplatNode(node) {
|
|
43872
|
+
return node instanceof SplatMesh || isGaussianSplat(node);
|
|
43877
43873
|
}
|
|
43878
|
-
function
|
|
43879
|
-
return
|
|
43880
|
-
}
|
|
43881
|
-
function hasCameraRelativeRootAncestor(node) {
|
|
43882
|
-
let ancestor = node.parent;
|
|
43883
|
-
while (ancestor) {
|
|
43884
|
-
if (isCameraRelativeNode(ancestor)) {
|
|
43885
|
-
return true;
|
|
43886
|
-
}
|
|
43887
|
-
ancestor = ancestor.parent;
|
|
43888
|
-
}
|
|
43889
|
-
return false;
|
|
43890
|
-
}
|
|
43891
|
-
function cloneSplatRootSnapshot(node) {
|
|
43892
|
-
return {
|
|
43893
|
-
kind: "splat",
|
|
43894
|
-
opacity: node.opacity,
|
|
43895
|
-
matrixWorld: node.matrixWorld.clone()
|
|
43896
|
-
};
|
|
43897
|
-
}
|
|
43898
|
-
function cloneSplatEditSdfSnapshot(sdf) {
|
|
43899
|
-
return {
|
|
43900
|
-
uuid: sdf.uuid,
|
|
43901
|
-
matrixWorld: sdf.matrixWorld.clone(),
|
|
43902
|
-
type: sdf.type,
|
|
43903
|
-
invert: sdf.invert,
|
|
43904
|
-
opacity: sdf.opacity,
|
|
43905
|
-
color: sdf.color.clone(),
|
|
43906
|
-
radius: sdf.radius,
|
|
43907
|
-
displace: sdf.displace.clone(),
|
|
43908
|
-
scale: sdf.scale.clone()
|
|
43909
|
-
};
|
|
43910
|
-
}
|
|
43911
|
-
function cloneSplatEditRootSnapshot(edit) {
|
|
43912
|
-
edit.updateMatrixWorld(true);
|
|
43913
|
-
const sdfs = [];
|
|
43914
|
-
const sourceSdfs = edit.sdfs;
|
|
43915
|
-
if (sourceSdfs != null) {
|
|
43916
|
-
for (const sdf of sourceSdfs) {
|
|
43917
|
-
sdf.updateMatrixWorld(true);
|
|
43918
|
-
sdfs.push(cloneSplatEditSdfSnapshot(sdf));
|
|
43919
|
-
}
|
|
43920
|
-
} else {
|
|
43921
|
-
edit.traverseVisible((child) => {
|
|
43922
|
-
if (child instanceof SplatEditSdf) {
|
|
43923
|
-
child.updateMatrixWorld(true);
|
|
43924
|
-
sdfs.push(cloneSplatEditSdfSnapshot(child));
|
|
43925
|
-
}
|
|
43926
|
-
});
|
|
43927
|
-
}
|
|
43928
|
-
return {
|
|
43929
|
-
kind: "edit",
|
|
43930
|
-
matrixWorld: edit.matrixWorld.clone(),
|
|
43931
|
-
ordering: edit.ordering,
|
|
43932
|
-
rgbaBlendMode: edit.rgbaBlendMode,
|
|
43933
|
-
sdfSmooth: edit.sdfSmooth,
|
|
43934
|
-
softEdge: edit.softEdge,
|
|
43935
|
-
invert: edit.invert,
|
|
43936
|
-
sdfs
|
|
43937
|
-
};
|
|
43938
|
-
}
|
|
43939
|
-
function cloneCameraRelativeRootSnapshot(node) {
|
|
43940
|
-
return node instanceof SplatEdit ? cloneSplatEditRootSnapshot(node) : cloneSplatRootSnapshot(node);
|
|
43941
|
-
}
|
|
43942
|
-
function areSplatRootStatesEqual(a2, b5) {
|
|
43943
|
-
return a2.opacity === b5.opacity && a2.matrixWorld.equals(b5.matrixWorld);
|
|
43944
|
-
}
|
|
43945
|
-
function areSplatEditSdfStatesEqual(a2, b5) {
|
|
43946
|
-
return a2.uuid === b5.uuid && a2.type === b5.type && a2.invert === b5.invert && a2.opacity === b5.opacity && a2.radius === b5.radius && a2.matrixWorld.equals(b5.matrixWorld) && a2.color.equals(b5.color) && a2.displace.equals(b5.displace) && a2.scale.equals(b5.scale);
|
|
43947
|
-
}
|
|
43948
|
-
function areSplatEditRootStatesEqual(a2, b5) {
|
|
43949
|
-
if (!a2.matrixWorld.equals(b5.matrixWorld) || a2.ordering !== b5.ordering || a2.rgbaBlendMode !== b5.rgbaBlendMode || a2.sdfSmooth !== b5.sdfSmooth || a2.softEdge !== b5.softEdge || a2.invert !== b5.invert || a2.sdfs.length !== b5.sdfs.length) {
|
|
43950
|
-
return false;
|
|
43951
|
-
}
|
|
43952
|
-
for (let i = 0; i < a2.sdfs.length; i++) {
|
|
43953
|
-
if (!areSplatEditSdfStatesEqual(a2.sdfs[i], b5.sdfs[i])) {
|
|
43954
|
-
return false;
|
|
43955
|
-
}
|
|
43956
|
-
}
|
|
43957
|
-
return true;
|
|
43958
|
-
}
|
|
43959
|
-
function areCameraRelativeRootStatesEqual(a2, b5) {
|
|
43960
|
-
if (a2 === b5) {
|
|
43961
|
-
return true;
|
|
43962
|
-
}
|
|
43963
|
-
if (!a2 || !b5 || a2.kind !== b5.kind) {
|
|
43964
|
-
return false;
|
|
43965
|
-
}
|
|
43966
|
-
if (a2.kind === "splat") {
|
|
43967
|
-
return b5.kind === "splat" && areSplatRootStatesEqual(a2, b5);
|
|
43968
|
-
}
|
|
43969
|
-
return b5.kind === "edit" && areSplatEditRootStatesEqual(a2, b5);
|
|
43874
|
+
function isCameraRelativeEdit(node, hasGaussianSplatAncestor) {
|
|
43875
|
+
return node instanceof SplatEdit && !hasGaussianSplatAncestor;
|
|
43970
43876
|
}
|
|
43971
43877
|
function normalizeSparkRendererOptions(host, includeCustomDefaults = true) {
|
|
43972
43878
|
const source = host.sparkRendererOptions ?? {};
|
|
@@ -44154,7 +44060,7 @@ function isGaussianSplat(object) {
|
|
|
44154
44060
|
function isGaussianSplatScene(scene) {
|
|
44155
44061
|
return Boolean(scene?.userData?.gaussianSplatScene);
|
|
44156
44062
|
}
|
|
44157
|
-
var _translation, _rotation, _scale, _identityMatrix2, _tempNodeMatrix, _textDecoder, _identityMatrix22, _cameraInverseWorldMatrix, _parentInverseWorldMatrix, _rebasedLocalMatrix, _displayFrameInverseWorldMatrix, _relativeRenderCameraMatrix,
|
|
44063
|
+
var _translation, _rotation, _scale, _identityMatrix2, _tempNodeMatrix, _textDecoder, _identityMatrix22, _cameraInverseWorldMatrix, _parentInverseWorldMatrix, _rebasedLocalMatrix, _displayFrameInverseWorldMatrix, _relativeRenderCameraMatrix, _updateCamera, _renderCamera, _cameraWorldSnapshot, _lastXrHandledFrame, _rebasedRootsPool, _rebasedRootsCount, _hadRebasedLastFrame, _CameraRelativeSparkRenderer_instances, updateSparkIfNeeded_fn, getUpdateCamera_fn, getRenderCamera_fn, rebaseCameraRelativeRoots_fn, visitVisibleCameraRelativeRoots_fn, rebaseCameraRelativeRoot_fn, restoreCameraRelativeRoots_fn, _a2, CameraRelativeSparkRenderer, _sharedSparkManagersByScene, _sharedSparkManagersByRenderer, CUSTOM_DEFAULT_OPTIONS, _sparkRenderer, _scene, _sparkRendererOptions, _notifyHandle, _disposeHandle, _tilesRenderers, _disposed, _SharedSparkRendererManager_instances, dispose_fn, stopScheduledNotifications_fn, scheduleSortUpdatedNotification_fn, waitForSortAndDispose_fn, _a3, SharedSparkRendererManager, SPARK_RENDERER_OPTION_KEYS, MAX_GAUSSIAN_MESH_INIT_CONCURRENCY, _sceneMatrix, _gaussianFadeValueWatched, _host, _sparkManager, _GaussianSplatPlugin_instances, disposeSplatScene_fn, getSplatMeshes_fn, createMeshForDescriptor_fn, _a4, GaussianSplatPlugin;
|
|
44158
44064
|
var init_dist = __esm({
|
|
44159
44065
|
"node_modules/3d-tiles-rendererjs-3dgs-plugin/dist/index.js"() {
|
|
44160
44066
|
init_spark_module();
|
|
@@ -44175,10 +44081,6 @@ var init_dist = __esm({
|
|
|
44175
44081
|
_rebasedLocalMatrix = new Matrix4();
|
|
44176
44082
|
_displayFrameInverseWorldMatrix = new Matrix4();
|
|
44177
44083
|
_relativeRenderCameraMatrix = new Matrix4();
|
|
44178
|
-
_cameraWorldPosition = new Vector3();
|
|
44179
|
-
_cameraWorldDirection = new Vector3();
|
|
44180
|
-
_cameraPositionEpsilonSq = 1e-6;
|
|
44181
|
-
_cameraDirectionDotThreshold = 1 - 1e-3;
|
|
44182
44084
|
CameraRelativeSparkRenderer = (_a2 = class extends SparkRenderer {
|
|
44183
44085
|
constructor(renderer, options = {}) {
|
|
44184
44086
|
super({
|
|
@@ -44190,11 +44092,8 @@ var init_dist = __esm({
|
|
|
44190
44092
|
__privateAdd(this, _CameraRelativeSparkRenderer_instances);
|
|
44191
44093
|
__privateAdd(this, _updateCamera, null);
|
|
44192
44094
|
__privateAdd(this, _renderCamera, null);
|
|
44193
|
-
__privateAdd(this,
|
|
44194
|
-
__privateAdd(this,
|
|
44195
|
-
__privateAdd(this, _hasLastCameraPose, false);
|
|
44196
|
-
__privateAdd(this, _lastRootStates, /* @__PURE__ */ new Map());
|
|
44197
|
-
__privateAdd(this, _currentRootStates, /* @__PURE__ */ new Map());
|
|
44095
|
+
__privateAdd(this, _cameraWorldSnapshot, new Matrix4());
|
|
44096
|
+
__privateAdd(this, _lastXrHandledFrame, -1);
|
|
44198
44097
|
__privateAdd(this, _rebasedRootsPool, []);
|
|
44199
44098
|
__privateAdd(this, _rebasedRootsCount, 0);
|
|
44200
44099
|
__privateAdd(this, _hadRebasedLastFrame, false);
|
|
@@ -44203,56 +44102,64 @@ var init_dist = __esm({
|
|
|
44203
44102
|
};
|
|
44204
44103
|
}
|
|
44205
44104
|
onBeforeRender(renderer, scene, camera) {
|
|
44206
|
-
|
|
44207
|
-
const
|
|
44105
|
+
const xrPresenting = isXrPresenting(renderer);
|
|
44106
|
+
const updateSourceCamera = getUpdateSourceCamera(
|
|
44107
|
+
renderer,
|
|
44108
|
+
camera,
|
|
44109
|
+
xrPresenting
|
|
44110
|
+
);
|
|
44111
|
+
if (!xrPresenting) {
|
|
44112
|
+
camera.updateMatrixWorld(true);
|
|
44113
|
+
}
|
|
44114
|
+
const rebasedCount = __privateMethod(this, _CameraRelativeSparkRenderer_instances, rebaseCameraRelativeRoots_fn).call(this, scene, updateSourceCamera);
|
|
44208
44115
|
const hasRebased = rebasedCount > 0;
|
|
44116
|
+
const renderFrame = renderer.info.render.frame;
|
|
44117
|
+
const shouldHandleFrameState = !xrPresenting || __privateGet(this, _lastXrHandledFrame) !== renderFrame;
|
|
44209
44118
|
try {
|
|
44210
|
-
if (
|
|
44211
|
-
|
|
44119
|
+
if (shouldHandleFrameState) {
|
|
44120
|
+
__privateSet(this, _lastXrHandledFrame, renderFrame);
|
|
44121
|
+
}
|
|
44122
|
+
if ((hasRebased || __privateGet(this, _hadRebasedLastFrame)) && shouldHandleFrameState) {
|
|
44123
|
+
const updateCamera = __privateMethod(this, _CameraRelativeSparkRenderer_instances, getUpdateCamera_fn).call(this, updateSourceCamera);
|
|
44212
44124
|
const prevDisplay = this.display;
|
|
44213
44125
|
const prevCurrent = this.current;
|
|
44214
|
-
const cameraWorldSnapshot =
|
|
44215
|
-
|
|
44126
|
+
const cameraWorldSnapshot = __privateGet(this, _cameraWorldSnapshot).copy(
|
|
44127
|
+
updateSourceCamera.matrixWorld
|
|
44128
|
+
);
|
|
44129
|
+
void __privateMethod(this, _CameraRelativeSparkRenderer_instances, updateSparkIfNeeded_fn).call(this, {
|
|
44216
44130
|
scene,
|
|
44217
44131
|
camera: updateCamera
|
|
44132
|
+
}).catch((error) => {
|
|
44133
|
+
console.error(
|
|
44134
|
+
"CameraRelativeSparkRenderer: Spark update failed",
|
|
44135
|
+
error
|
|
44136
|
+
);
|
|
44218
44137
|
});
|
|
44219
|
-
const updateAccepted = this.current !== prevCurrent || this.display !== prevDisplay;
|
|
44220
44138
|
if (this.current !== prevCurrent) {
|
|
44221
44139
|
this.current.viewToWorld.copy(cameraWorldSnapshot);
|
|
44222
44140
|
}
|
|
44223
44141
|
if (this.display !== prevDisplay) {
|
|
44224
44142
|
this.display.viewToWorld.copy(cameraWorldSnapshot);
|
|
44225
44143
|
}
|
|
44226
|
-
if (updateAccepted) {
|
|
44227
|
-
__privateGet(this, _lastCameraPosition).copy(_cameraWorldPosition);
|
|
44228
|
-
__privateGet(this, _lastCameraDirection).copy(_cameraWorldDirection);
|
|
44229
|
-
__privateSet(this, _hasLastCameraPose, true);
|
|
44230
|
-
__privateSet(this, _lastRootStates, new Map(__privateGet(this, _currentRootStates)));
|
|
44231
|
-
}
|
|
44232
44144
|
}
|
|
44233
|
-
|
|
44145
|
+
if (shouldHandleFrameState) {
|
|
44146
|
+
__privateSet(this, _hadRebasedLastFrame, hasRebased);
|
|
44147
|
+
}
|
|
44234
44148
|
const renderCamera = hasRebased ? __privateMethod(this, _CameraRelativeSparkRenderer_instances, getRenderCamera_fn).call(this, camera) : camera;
|
|
44235
44149
|
super.onBeforeRender(renderer, scene, renderCamera);
|
|
44236
44150
|
} finally {
|
|
44237
44151
|
__privateMethod(this, _CameraRelativeSparkRenderer_instances, restoreCameraRelativeRoots_fn).call(this);
|
|
44238
44152
|
}
|
|
44239
44153
|
}
|
|
44240
|
-
}, _updateCamera = new WeakMap(), _renderCamera = new WeakMap(),
|
|
44241
|
-
|
|
44242
|
-
camera
|
|
44243
|
-
|
|
44244
|
-
|
|
44245
|
-
|
|
44246
|
-
|
|
44247
|
-
|
|
44248
|
-
|
|
44249
|
-
if (!areCameraRelativeRootStatesEqual(state, last.get(uuid))) {
|
|
44250
|
-
rootsChanged = true;
|
|
44251
|
-
break;
|
|
44252
|
-
}
|
|
44253
|
-
}
|
|
44254
|
-
}
|
|
44255
|
-
return poseChanged || rootsChanged;
|
|
44154
|
+
}, _updateCamera = new WeakMap(), _renderCamera = new WeakMap(), _cameraWorldSnapshot = new WeakMap(), _lastXrHandledFrame = new WeakMap(), _rebasedRootsPool = new WeakMap(), _rebasedRootsCount = new WeakMap(), _hadRebasedLastFrame = new WeakMap(), _CameraRelativeSparkRenderer_instances = new WeakSet(), updateSparkIfNeeded_fn = function({
|
|
44155
|
+
scene,
|
|
44156
|
+
camera
|
|
44157
|
+
}) {
|
|
44158
|
+
return this.updateInternal({
|
|
44159
|
+
scene,
|
|
44160
|
+
camera,
|
|
44161
|
+
autoUpdate: true
|
|
44162
|
+
});
|
|
44256
44163
|
}, /**
|
|
44257
44164
|
* Identity camera for the update pass - makes Spark treat
|
|
44258
44165
|
* the camera's own frame as the reference frame.
|
|
@@ -44291,55 +44198,63 @@ var init_dist = __esm({
|
|
|
44291
44198
|
return renderCamera;
|
|
44292
44199
|
}, rebaseCameraRelativeRoots_fn = function(scene, camera) {
|
|
44293
44200
|
__privateSet(this, _rebasedRootsCount, 0);
|
|
44294
|
-
__privateGet(this, _currentRootStates).clear();
|
|
44295
44201
|
_cameraInverseWorldMatrix.copy(camera.matrixWorld).invert();
|
|
44296
|
-
|
|
44297
|
-
if (!isCameraRelativeNode(node)) {
|
|
44298
|
-
return;
|
|
44299
|
-
}
|
|
44300
|
-
__privateGet(this, _currentRootStates).set(
|
|
44301
|
-
node.uuid,
|
|
44302
|
-
cloneCameraRelativeRootSnapshot(node)
|
|
44303
|
-
);
|
|
44304
|
-
if (hasCameraRelativeRootAncestor(node)) {
|
|
44305
|
-
return;
|
|
44306
|
-
}
|
|
44307
|
-
const idx = __privateWrapper(this, _rebasedRootsCount)._++;
|
|
44308
|
-
const pool = __privateGet(this, _rebasedRootsPool);
|
|
44309
|
-
if (idx >= pool.length) {
|
|
44310
|
-
pool.push({
|
|
44311
|
-
target: node,
|
|
44312
|
-
originalMatrix: node.matrix.clone(),
|
|
44313
|
-
originalMatrixAutoUpdate: node.matrixAutoUpdate
|
|
44314
|
-
});
|
|
44315
|
-
} else {
|
|
44316
|
-
const entry = pool[idx];
|
|
44317
|
-
entry.target = node;
|
|
44318
|
-
entry.originalMatrix.copy(node.matrix);
|
|
44319
|
-
entry.originalMatrixAutoUpdate = node.matrixAutoUpdate;
|
|
44320
|
-
}
|
|
44321
|
-
const parent2 = node.parent;
|
|
44322
|
-
if (!parent2 || parent2 === scene) {
|
|
44323
|
-
_rebasedLocalMatrix.copy(_cameraInverseWorldMatrix).multiply(node.matrixWorld);
|
|
44324
|
-
} else {
|
|
44325
|
-
_rebasedLocalMatrix.copy(_parentInverseWorldMatrix.copy(parent2.matrixWorld).invert()).multiply(_cameraInverseWorldMatrix).multiply(node.matrixWorld);
|
|
44326
|
-
}
|
|
44327
|
-
node.matrixAutoUpdate = false;
|
|
44328
|
-
node.matrix.copy(_rebasedLocalMatrix);
|
|
44329
|
-
node.matrixWorldNeedsUpdate = true;
|
|
44330
|
-
node.updateMatrixWorld(true);
|
|
44331
|
-
});
|
|
44202
|
+
__privateMethod(this, _CameraRelativeSparkRenderer_instances, visitVisibleCameraRelativeRoots_fn).call(this, scene, scene, false, false);
|
|
44332
44203
|
return __privateGet(this, _rebasedRootsCount);
|
|
44204
|
+
}, visitVisibleCameraRelativeRoots_fn = function(node, scene, hasGaussianSplatAncestor, hasCameraRelativeAncestor) {
|
|
44205
|
+
if (!node.visible) {
|
|
44206
|
+
return;
|
|
44207
|
+
}
|
|
44208
|
+
const isSplatNode = isGaussianSplatNode(node);
|
|
44209
|
+
const isCameraRelativeNode = isSplatNode || isCameraRelativeEdit(node, hasGaussianSplatAncestor);
|
|
44210
|
+
if (isCameraRelativeNode && !hasCameraRelativeAncestor) {
|
|
44211
|
+
__privateMethod(this, _CameraRelativeSparkRenderer_instances, rebaseCameraRelativeRoot_fn).call(this, node);
|
|
44212
|
+
}
|
|
44213
|
+
const nextHasGaussianSplatAncestor = hasGaussianSplatAncestor || isSplatNode;
|
|
44214
|
+
const nextHasCameraRelativeAncestor = hasCameraRelativeAncestor || isCameraRelativeNode;
|
|
44215
|
+
const { children } = node;
|
|
44216
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
44217
|
+
__privateMethod(this, _CameraRelativeSparkRenderer_instances, visitVisibleCameraRelativeRoots_fn).call(this, children[i], scene, nextHasGaussianSplatAncestor, nextHasCameraRelativeAncestor);
|
|
44218
|
+
}
|
|
44219
|
+
}, rebaseCameraRelativeRoot_fn = function(node) {
|
|
44220
|
+
const idx = __privateWrapper(this, _rebasedRootsCount)._++;
|
|
44221
|
+
const pool = __privateGet(this, _rebasedRootsPool);
|
|
44222
|
+
if (idx >= pool.length) {
|
|
44223
|
+
pool.push({
|
|
44224
|
+
target: node,
|
|
44225
|
+
originalMatrix: node.matrix.clone(),
|
|
44226
|
+
originalMatrixAutoUpdate: node.matrixAutoUpdate
|
|
44227
|
+
});
|
|
44228
|
+
} else {
|
|
44229
|
+
const entry = pool[idx];
|
|
44230
|
+
entry.target = node;
|
|
44231
|
+
entry.originalMatrix.copy(node.matrix);
|
|
44232
|
+
entry.originalMatrixAutoUpdate = node.matrixAutoUpdate;
|
|
44233
|
+
}
|
|
44234
|
+
const parent2 = node.parent;
|
|
44235
|
+
if (!parent2) {
|
|
44236
|
+
_rebasedLocalMatrix.copy(_cameraInverseWorldMatrix).multiply(node.matrixWorld);
|
|
44237
|
+
} else {
|
|
44238
|
+
_rebasedLocalMatrix.copy(_parentInverseWorldMatrix.copy(parent2.matrixWorld).invert()).multiply(_cameraInverseWorldMatrix).multiply(node.matrixWorld);
|
|
44239
|
+
}
|
|
44240
|
+
node.matrixAutoUpdate = false;
|
|
44241
|
+
node.matrix.copy(_rebasedLocalMatrix);
|
|
44242
|
+
node.matrixWorldNeedsUpdate = true;
|
|
44243
|
+
node.updateMatrixWorld(true);
|
|
44333
44244
|
}, restoreCameraRelativeRoots_fn = function() {
|
|
44334
44245
|
const pool = __privateGet(this, _rebasedRootsPool);
|
|
44335
44246
|
for (let i = __privateGet(this, _rebasedRootsCount) - 1; i >= 0; i--) {
|
|
44336
44247
|
const { target, originalMatrix, originalMatrixAutoUpdate } = pool[i];
|
|
44248
|
+
if (!target) continue;
|
|
44337
44249
|
target.matrix.copy(originalMatrix);
|
|
44338
44250
|
target.matrixAutoUpdate = originalMatrixAutoUpdate;
|
|
44339
44251
|
target.matrixWorldNeedsUpdate = true;
|
|
44340
44252
|
}
|
|
44341
44253
|
for (let i = 0; i < __privateGet(this, _rebasedRootsCount); i++) {
|
|
44342
|
-
pool[i].target
|
|
44254
|
+
pool[i].target?.updateMatrixWorld(true);
|
|
44255
|
+
}
|
|
44256
|
+
for (let i = __privateGet(this, _rebasedRootsCount); i < pool.length; i++) {
|
|
44257
|
+
pool[i].target = null;
|
|
44343
44258
|
}
|
|
44344
44259
|
}, _a2);
|
|
44345
44260
|
_sharedSparkManagersByScene = /* @__PURE__ */ new WeakMap();
|
|
@@ -68245,6 +68160,10 @@ function createFlyToController({
|
|
|
68245
68160
|
}
|
|
68246
68161
|
return true;
|
|
68247
68162
|
}
|
|
68163
|
+
function cancelCameraFlight() {
|
|
68164
|
+
activeCameraFlight = null;
|
|
68165
|
+
activeCameraFlightStatus = "";
|
|
68166
|
+
}
|
|
68248
68167
|
async function applyTilesSetPositionFromPointerEvent(event) {
|
|
68249
68168
|
const coordinate = pickCoordinateFromPointerEvent(event);
|
|
68250
68169
|
if (!coordinate) {
|
|
@@ -68309,6 +68228,7 @@ function createFlyToController({
|
|
|
68309
68228
|
}
|
|
68310
68229
|
return {
|
|
68311
68230
|
applyTilesSetPositionFromPointerEvent,
|
|
68231
|
+
cancelCameraFlight,
|
|
68312
68232
|
frameTileset,
|
|
68313
68233
|
getActiveEllipsoid,
|
|
68314
68234
|
moveCameraToCoordinate,
|
|
@@ -68325,6 +68245,148 @@ var init_flyTo = __esm({
|
|
|
68325
68245
|
}
|
|
68326
68246
|
});
|
|
68327
68247
|
|
|
68248
|
+
// src/viewer/navigation/cameraUrlPose.js
|
|
68249
|
+
function formatPoseNumber(value) {
|
|
68250
|
+
const fixed = value.toFixed(9);
|
|
68251
|
+
const trimmed = fixed.replace(/(\.\d*?[1-9])0+$/, "$1").replace(/\.0+$/, "");
|
|
68252
|
+
return trimmed === "-0" ? "0" : trimmed;
|
|
68253
|
+
}
|
|
68254
|
+
function serializeCameraPose(camera) {
|
|
68255
|
+
camera.updateMatrixWorld(true);
|
|
68256
|
+
const values = [
|
|
68257
|
+
camera.position.x,
|
|
68258
|
+
camera.position.y,
|
|
68259
|
+
camera.position.z,
|
|
68260
|
+
camera.quaternion.x,
|
|
68261
|
+
camera.quaternion.y,
|
|
68262
|
+
camera.quaternion.z,
|
|
68263
|
+
camera.quaternion.w
|
|
68264
|
+
];
|
|
68265
|
+
if (camera.isOrthographicCamera) {
|
|
68266
|
+
values.push(camera.zoom);
|
|
68267
|
+
}
|
|
68268
|
+
if (!values.every(Number.isFinite)) {
|
|
68269
|
+
return null;
|
|
68270
|
+
}
|
|
68271
|
+
return values.map(formatPoseNumber).join(CAMERA_POSE_VALUE_SEPARATOR);
|
|
68272
|
+
}
|
|
68273
|
+
function getCameraPoseParam(url) {
|
|
68274
|
+
return url.searchParams.get(CAMERA_POSE_PARAM) ?? url.searchParams.get(CAMERA_POSE_ALIAS_PARAM);
|
|
68275
|
+
}
|
|
68276
|
+
function parseCameraPose(value) {
|
|
68277
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
68278
|
+
return null;
|
|
68279
|
+
}
|
|
68280
|
+
const parts = value.trim().split(/[,_\s]+/).filter(Boolean);
|
|
68281
|
+
if (parts.length !== CAMERA_POSE_COMPONENT_COUNT && parts.length !== CAMERA_POSE_COMPONENT_COUNT_WITH_ZOOM) {
|
|
68282
|
+
return null;
|
|
68283
|
+
}
|
|
68284
|
+
const values = parts.map(Number);
|
|
68285
|
+
if (!values.every(Number.isFinite)) {
|
|
68286
|
+
return null;
|
|
68287
|
+
}
|
|
68288
|
+
const quaternionLength = Math.hypot(
|
|
68289
|
+
values[3],
|
|
68290
|
+
values[4],
|
|
68291
|
+
values[5],
|
|
68292
|
+
values[6]
|
|
68293
|
+
);
|
|
68294
|
+
if (quaternionLength <= 1e-12) {
|
|
68295
|
+
return null;
|
|
68296
|
+
}
|
|
68297
|
+
const zoom = values.length === CAMERA_POSE_COMPONENT_COUNT_WITH_ZOOM ? values[7] : null;
|
|
68298
|
+
if (zoom !== null && zoom <= 0) {
|
|
68299
|
+
return null;
|
|
68300
|
+
}
|
|
68301
|
+
return {
|
|
68302
|
+
position: values.slice(0, 3),
|
|
68303
|
+
quaternion: values.slice(3, 7),
|
|
68304
|
+
zoom
|
|
68305
|
+
};
|
|
68306
|
+
}
|
|
68307
|
+
function replaceCameraPoseUrl(serializedPose) {
|
|
68308
|
+
const url = new URL(window.location.href);
|
|
68309
|
+
url.searchParams.delete(CAMERA_POSE_ALIAS_PARAM);
|
|
68310
|
+
url.searchParams.set(CAMERA_POSE_PARAM, serializedPose);
|
|
68311
|
+
window.history.replaceState(window.history.state, "", url.href);
|
|
68312
|
+
}
|
|
68313
|
+
function getInvalidCameraPoseMessage() {
|
|
68314
|
+
return "Invalid camerapose URL parameter ignored. Expected x_y_z_qx_qy_qz_qw.";
|
|
68315
|
+
}
|
|
68316
|
+
function createCameraUrlPoseController({
|
|
68317
|
+
camera,
|
|
68318
|
+
cameraController,
|
|
68319
|
+
setStatus
|
|
68320
|
+
}) {
|
|
68321
|
+
let lastSerializedPose = null;
|
|
68322
|
+
let lastUrlWriteTime = 0;
|
|
68323
|
+
function applyPose(pose) {
|
|
68324
|
+
camera.position.fromArray(pose.position);
|
|
68325
|
+
camera.quaternion.fromArray(pose.quaternion).normalize();
|
|
68326
|
+
if (pose.zoom !== null && camera.isOrthographicCamera) {
|
|
68327
|
+
camera.zoom = Math.max(pose.zoom, 1e-6);
|
|
68328
|
+
camera.updateProjectionMatrix();
|
|
68329
|
+
}
|
|
68330
|
+
camera.updateMatrixWorld(true);
|
|
68331
|
+
cameraController.setCamera(camera);
|
|
68332
|
+
}
|
|
68333
|
+
function applyFromUrl({ showStatus = false } = {}) {
|
|
68334
|
+
const url = new URL(window.location.href);
|
|
68335
|
+
const value = getCameraPoseParam(url);
|
|
68336
|
+
if (value === null) {
|
|
68337
|
+
return false;
|
|
68338
|
+
}
|
|
68339
|
+
const pose = parseCameraPose(value);
|
|
68340
|
+
if (!pose) {
|
|
68341
|
+
if (showStatus && setStatus) {
|
|
68342
|
+
setStatus(getInvalidCameraPoseMessage(), true);
|
|
68343
|
+
}
|
|
68344
|
+
return false;
|
|
68345
|
+
}
|
|
68346
|
+
applyPose(pose);
|
|
68347
|
+
lastSerializedPose = serializeCameraPose(camera);
|
|
68348
|
+
lastUrlWriteTime = performance.now();
|
|
68349
|
+
if (lastSerializedPose) {
|
|
68350
|
+
replaceCameraPoseUrl(lastSerializedPose);
|
|
68351
|
+
}
|
|
68352
|
+
if (showStatus && setStatus) {
|
|
68353
|
+
setStatus("Applied camera pose from URL.");
|
|
68354
|
+
}
|
|
68355
|
+
return true;
|
|
68356
|
+
}
|
|
68357
|
+
function flush() {
|
|
68358
|
+
const serializedPose = serializeCameraPose(camera);
|
|
68359
|
+
if (!serializedPose || serializedPose === lastSerializedPose) {
|
|
68360
|
+
return;
|
|
68361
|
+
}
|
|
68362
|
+
replaceCameraPoseUrl(serializedPose);
|
|
68363
|
+
lastSerializedPose = serializedPose;
|
|
68364
|
+
lastUrlWriteTime = performance.now();
|
|
68365
|
+
}
|
|
68366
|
+
function update(time = performance.now()) {
|
|
68367
|
+
if (time - lastUrlWriteTime < CAMERA_POSE_UPDATE_INTERVAL_MS) {
|
|
68368
|
+
return;
|
|
68369
|
+
}
|
|
68370
|
+
flush();
|
|
68371
|
+
}
|
|
68372
|
+
return {
|
|
68373
|
+
applyFromUrl,
|
|
68374
|
+
flush,
|
|
68375
|
+
update
|
|
68376
|
+
};
|
|
68377
|
+
}
|
|
68378
|
+
var CAMERA_POSE_PARAM, CAMERA_POSE_ALIAS_PARAM, CAMERA_POSE_COMPONENT_COUNT, CAMERA_POSE_COMPONENT_COUNT_WITH_ZOOM, CAMERA_POSE_UPDATE_INTERVAL_MS, CAMERA_POSE_VALUE_SEPARATOR;
|
|
68379
|
+
var init_cameraUrlPose = __esm({
|
|
68380
|
+
"src/viewer/navigation/cameraUrlPose.js"() {
|
|
68381
|
+
CAMERA_POSE_PARAM = "camerapose";
|
|
68382
|
+
CAMERA_POSE_ALIAS_PARAM = "cameraPose";
|
|
68383
|
+
CAMERA_POSE_COMPONENT_COUNT = 7;
|
|
68384
|
+
CAMERA_POSE_COMPONENT_COUNT_WITH_ZOOM = 8;
|
|
68385
|
+
CAMERA_POSE_UPDATE_INTERVAL_MS = 100;
|
|
68386
|
+
CAMERA_POSE_VALUE_SEPARATOR = "_";
|
|
68387
|
+
}
|
|
68388
|
+
});
|
|
68389
|
+
|
|
68328
68390
|
// src/viewer/screenSelection/state.js
|
|
68329
68391
|
function clamp3(value, min, max) {
|
|
68330
68392
|
return Math.min(max, Math.max(min, value));
|
|
@@ -71052,6 +71114,7 @@ var require_app = __commonJS({
|
|
|
71052
71114
|
init_shutdown();
|
|
71053
71115
|
init_setPositionController();
|
|
71054
71116
|
init_flyTo();
|
|
71117
|
+
init_cameraUrlPose();
|
|
71055
71118
|
init_cropController();
|
|
71056
71119
|
init_rootTransformController();
|
|
71057
71120
|
init_transformModeController();
|
|
@@ -71256,6 +71319,14 @@ var require_app = __commonJS({
|
|
|
71256
71319
|
getTiles: () => tiles,
|
|
71257
71320
|
getTilesetBoundingSphere
|
|
71258
71321
|
});
|
|
71322
|
+
var cameraUrlPose = createCameraUrlPoseController({
|
|
71323
|
+
camera,
|
|
71324
|
+
cameraController,
|
|
71325
|
+
setStatus
|
|
71326
|
+
});
|
|
71327
|
+
var appliedInitialCameraPose = cameraUrlPose.applyFromUrl({
|
|
71328
|
+
showStatus: true
|
|
71329
|
+
});
|
|
71259
71330
|
setPositionController = createSetPositionController({
|
|
71260
71331
|
cameraController,
|
|
71261
71332
|
maxClickDistanceSq: SET_POSITION_CLICK_MAX_DISTANCE_SQ,
|
|
@@ -71496,10 +71567,11 @@ var require_app = __commonJS({
|
|
|
71496
71567
|
const processedSplatResources = Number(
|
|
71497
71568
|
payload.processedSplatResources || 0
|
|
71498
71569
|
);
|
|
71570
|
+
const deletedSplatFiles = Number(payload.deletedSplatFiles || 0);
|
|
71499
71571
|
cropController.clearAll();
|
|
71500
71572
|
loadTileset(TILESET_URL, { frameOnLoad: false });
|
|
71501
71573
|
setStatus(
|
|
71502
|
-
`Saved transform and deleted ${deletedSplats} cropped splats from ${processedSplatResources} splat resource${processedSplatResources === 1 ? "" : "s"}. Reloading tileset.`
|
|
71574
|
+
`Saved transform and deleted ${deletedSplats} cropped splats from ${processedSplatResources} splat resource${processedSplatResources === 1 ? "" : "s"}${deletedSplatFiles > 0 ? `, removing ${deletedSplatFiles} orphaned file${deletedSplatFiles === 1 ? "" : "s"}` : ""}. Reloading tileset.`
|
|
71503
71575
|
);
|
|
71504
71576
|
} else {
|
|
71505
71577
|
setStatus(
|
|
@@ -71559,10 +71631,19 @@ var require_app = __commonJS({
|
|
|
71559
71631
|
renderer,
|
|
71560
71632
|
setStatus
|
|
71561
71633
|
});
|
|
71562
|
-
|
|
71634
|
+
window.addEventListener("popstate", () => {
|
|
71635
|
+
if (cameraUrlPose.applyFromUrl({ showStatus: true })) {
|
|
71636
|
+
flyTo2.cancelCameraFlight();
|
|
71637
|
+
cancelPositionPickModes();
|
|
71638
|
+
}
|
|
71639
|
+
});
|
|
71640
|
+
window.addEventListener("pagehide", cameraUrlPose.flush);
|
|
71641
|
+
cameraController.addEventListener("finish", cameraUrlPose.flush);
|
|
71642
|
+
loadTileset(TILESET_URL, { frameOnLoad: !appliedInitialCameraPose });
|
|
71563
71643
|
function frame() {
|
|
71564
71644
|
cameraController.update();
|
|
71565
71645
|
flyTo2.update();
|
|
71646
|
+
cameraUrlPose.update();
|
|
71566
71647
|
rootTransform.flush();
|
|
71567
71648
|
globeController.update();
|
|
71568
71649
|
tiles?.update();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "3dtiles-inspector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"3d-tiles-renderer": "0.4.24",
|
|
58
|
-
"3d-tiles-rendererjs-3dgs-plugin": "0.1.
|
|
58
|
+
"3d-tiles-rendererjs-3dgs-plugin": "0.1.7",
|
|
59
59
|
"cesium": "1.140.0",
|
|
60
60
|
"esbuild": "^0.25.11"
|
|
61
61
|
},
|
|
@@ -471,6 +471,8 @@ async function saveViewerTransform(
|
|
|
471
471
|
return {
|
|
472
472
|
transform: nextRoot,
|
|
473
473
|
deletedSplats: cropResult.deletedSplats,
|
|
474
|
+
deletedSplatFiles: cropResult.deletedSplatFiles,
|
|
475
|
+
failedSplatFileDeletes: cropResult.failedSplatFileDeletes,
|
|
474
476
|
processedSplatResources: cropResult.processedSplatResources,
|
|
475
477
|
};
|
|
476
478
|
}
|
|
@@ -23,6 +23,8 @@ function createSaveTransformResponsePayload(
|
|
|
23
23
|
transform: saveResult.transform,
|
|
24
24
|
geometricErrorLayerScale: normalizedGeometricErrorLayerScale,
|
|
25
25
|
geometricErrorScale: normalizedGeometricErrorScale,
|
|
26
|
+
deletedSplatFiles: saveResult.deletedSplatFiles,
|
|
27
|
+
failedSplatFileDeletes: saveResult.failedSplatFileDeletes,
|
|
26
28
|
deletedSplats: saveResult.deletedSplats,
|
|
27
29
|
processedSplatResources: saveResult.processedSplatResources,
|
|
28
30
|
};
|
|
@@ -239,6 +239,53 @@ function removeMeshPrimitives(resource, descriptors) {
|
|
|
239
239
|
return removed;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
function updateGaussianPrimitiveAccessorCounts(resource, descriptors, count) {
|
|
243
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
244
|
+
throw new InspectorError('Gaussian accessor count must be a non-negative integer.');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const updatedAccessors = new Set();
|
|
248
|
+
descriptors.forEach((descriptor) => {
|
|
249
|
+
if (
|
|
250
|
+
!Number.isInteger(descriptor.meshIndex) ||
|
|
251
|
+
!Number.isInteger(descriptor.primitiveIndex)
|
|
252
|
+
) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const primitive =
|
|
257
|
+
resource.json.meshes?.[descriptor.meshIndex]?.primitives?.[
|
|
258
|
+
descriptor.primitiveIndex
|
|
259
|
+
];
|
|
260
|
+
const attributes = primitive?.attributes;
|
|
261
|
+
if (!attributes || typeof attributes !== 'object') {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
Object.values(attributes).forEach((accessorIndex) => {
|
|
266
|
+
if (
|
|
267
|
+
!Number.isInteger(accessorIndex) ||
|
|
268
|
+
accessorIndex < 0 ||
|
|
269
|
+
accessorIndex >= (resource.json.accessors?.length || 0)
|
|
270
|
+
) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const accessor = resource.json.accessors[accessorIndex];
|
|
275
|
+
if (!accessor || typeof accessor !== 'object') {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (accessor.count !== count) {
|
|
280
|
+
accessor.count = count;
|
|
281
|
+
updatedAccessors.add(accessorIndex);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return updatedAccessors.size;
|
|
287
|
+
}
|
|
288
|
+
|
|
242
289
|
module.exports = {
|
|
243
290
|
collectGaussianPrimitiveDescriptors,
|
|
244
291
|
getRootUpRotationMatrix,
|
|
@@ -247,4 +294,5 @@ module.exports = {
|
|
|
247
294
|
hasNonGaussianScenePrimitives,
|
|
248
295
|
hasScenePrimitives,
|
|
249
296
|
removeMeshPrimitives,
|
|
297
|
+
updateGaussianPrimitiveAccessorCounts,
|
|
250
298
|
};
|
|
@@ -6,6 +6,7 @@ const { assertPathInsideRoot } = require('./gltfResource');
|
|
|
6
6
|
const { getRootUpRotationMatrix } = require('./gaussianPrimitives');
|
|
7
7
|
const {
|
|
8
8
|
collectCandidateSplatResources,
|
|
9
|
+
deleteOrphanedSplatResources,
|
|
9
10
|
readTilesetJson,
|
|
10
11
|
traverseTileset,
|
|
11
12
|
} = require('./traversal');
|
|
@@ -35,6 +36,8 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
35
36
|
if (normalizedScreenSelections.length === 0) {
|
|
36
37
|
return {
|
|
37
38
|
deletedSplats: 0,
|
|
39
|
+
deletedSplatFiles: 0,
|
|
40
|
+
failedSplatFileDeletes: 0,
|
|
38
41
|
processedSplatResources: 0,
|
|
39
42
|
};
|
|
40
43
|
}
|
|
@@ -45,11 +48,12 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
45
48
|
|
|
46
49
|
const { THREE } = await getSplatCropModules();
|
|
47
50
|
const rootTileset = readTilesetJson(tilesetPath);
|
|
48
|
-
const
|
|
51
|
+
const initialResourcePaths = collectCandidateSplatResources({
|
|
49
52
|
rootDir,
|
|
50
53
|
tileset: rootTileset,
|
|
51
54
|
tilesetPath,
|
|
52
|
-
})
|
|
55
|
+
});
|
|
56
|
+
const totalResources = initialResourcePaths.size;
|
|
53
57
|
if (typeof onProgress === 'function') {
|
|
54
58
|
const readStreamHint = tileReadStreamsClosed
|
|
55
59
|
? ' Tile read streams closed.'
|
|
@@ -77,7 +81,7 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
77
81
|
const workerPool = new SplatCropWorkerPool(SPLAT_CROP_WORKER_COUNT);
|
|
78
82
|
|
|
79
83
|
try {
|
|
80
|
-
|
|
84
|
+
const traversalResult = await traverseTileset({
|
|
81
85
|
THREE,
|
|
82
86
|
tilesetPath,
|
|
83
87
|
tileset: rootTileset,
|
|
@@ -102,6 +106,20 @@ async function deleteSplatsInNormalizedSelections(
|
|
|
102
106
|
resourceLocks: new Map(),
|
|
103
107
|
workerPool,
|
|
104
108
|
});
|
|
109
|
+
const remainingResourcePaths = collectCandidateSplatResources({
|
|
110
|
+
rootDir,
|
|
111
|
+
tilesetPath,
|
|
112
|
+
});
|
|
113
|
+
const cleanupResult = await deleteOrphanedSplatResources({
|
|
114
|
+
initialResourcePaths,
|
|
115
|
+
remainingResourcePaths,
|
|
116
|
+
rootDir,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
...traversalResult,
|
|
120
|
+
deletedSplatFiles: cleanupResult.deletedFiles.length,
|
|
121
|
+
failedSplatFileDeletes: cleanupResult.failedFiles.length,
|
|
122
|
+
};
|
|
105
123
|
} finally {
|
|
106
124
|
await workerPool.close();
|
|
107
125
|
}
|
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
hasNonGaussianScenePrimitives,
|
|
21
21
|
hasScenePrimitives,
|
|
22
22
|
removeMeshPrimitives,
|
|
23
|
+
updateGaussianPrimitiveAccessorCounts,
|
|
23
24
|
} = require('./gaussianPrimitives');
|
|
24
25
|
|
|
25
26
|
function getContentSlots(tile) {
|
|
@@ -399,6 +400,126 @@ function collectCandidateSplatResources({
|
|
|
399
400
|
return resourcePaths;
|
|
400
401
|
}
|
|
401
402
|
|
|
403
|
+
function collectLocalGltfBufferPaths(filePath, rootDir) {
|
|
404
|
+
const bufferPaths = new Set();
|
|
405
|
+
if (path.extname(filePath).toLowerCase() !== '.gltf') {
|
|
406
|
+
return bufferPaths;
|
|
407
|
+
}
|
|
408
|
+
if (!fs.existsSync(filePath)) {
|
|
409
|
+
return bufferPaths;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const json = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
413
|
+
const buffers = Array.isArray(json.buffers) ? json.buffers : [];
|
|
414
|
+
const resourceDir = path.dirname(filePath);
|
|
415
|
+
buffers.forEach((buffer, index) => {
|
|
416
|
+
const uri = buffer?.uri;
|
|
417
|
+
if (
|
|
418
|
+
typeof uri !== 'string' ||
|
|
419
|
+
uri.length === 0 ||
|
|
420
|
+
/^data:/i.test(uri) ||
|
|
421
|
+
isRemoteOrProtocolUri(uri)
|
|
422
|
+
) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
bufferPaths.add(
|
|
427
|
+
resolveLocalUri(
|
|
428
|
+
resourceDir,
|
|
429
|
+
rootDir,
|
|
430
|
+
uri,
|
|
431
|
+
`${filePath}.buffers[${index}].uri`,
|
|
432
|
+
),
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
return bufferPaths;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function collectReferencedGltfBufferPaths(resourcePaths, rootDir) {
|
|
439
|
+
const bufferPaths = new Set();
|
|
440
|
+
resourcePaths.forEach((resourcePath) => {
|
|
441
|
+
collectLocalGltfBufferPaths(resourcePath, rootDir).forEach((bufferPath) => {
|
|
442
|
+
bufferPaths.add(bufferPath);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
return bufferPaths;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function deleteFileIfSafe(filePath, rootDir) {
|
|
449
|
+
const resolvedPath = assertPathInsideRoot(
|
|
450
|
+
filePath,
|
|
451
|
+
rootDir,
|
|
452
|
+
'Orphaned splat resource path',
|
|
453
|
+
);
|
|
454
|
+
let stats;
|
|
455
|
+
try {
|
|
456
|
+
stats = await fs.promises.stat(resolvedPath);
|
|
457
|
+
} catch (err) {
|
|
458
|
+
if (err && err.code === 'ENOENT') {
|
|
459
|
+
return { deleted: false };
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
deleted: false,
|
|
463
|
+
error: err && err.message ? err.message : String(err),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!stats.isFile()) {
|
|
468
|
+
return { deleted: false };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
await fs.promises.unlink(resolvedPath);
|
|
473
|
+
return { deleted: true };
|
|
474
|
+
} catch (err) {
|
|
475
|
+
return {
|
|
476
|
+
deleted: false,
|
|
477
|
+
error: err && err.message ? err.message : String(err),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function deleteOrphanedSplatResources({
|
|
483
|
+
initialResourcePaths,
|
|
484
|
+
remainingResourcePaths,
|
|
485
|
+
rootDir,
|
|
486
|
+
}) {
|
|
487
|
+
const remainingBuffers = collectReferencedGltfBufferPaths(
|
|
488
|
+
remainingResourcePaths,
|
|
489
|
+
rootDir,
|
|
490
|
+
);
|
|
491
|
+
const deletePaths = new Set();
|
|
492
|
+
|
|
493
|
+
initialResourcePaths.forEach((resourcePath) => {
|
|
494
|
+
if (remainingResourcePaths.has(resourcePath)) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
deletePaths.add(resourcePath);
|
|
499
|
+
collectLocalGltfBufferPaths(resourcePath, rootDir).forEach((bufferPath) => {
|
|
500
|
+
if (
|
|
501
|
+
!remainingResourcePaths.has(bufferPath) &&
|
|
502
|
+
!remainingBuffers.has(bufferPath)
|
|
503
|
+
) {
|
|
504
|
+
deletePaths.add(bufferPath);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const deletedFiles = [];
|
|
510
|
+
const failedFiles = [];
|
|
511
|
+
for (const filePath of deletePaths) {
|
|
512
|
+
const result = await deleteFileIfSafe(filePath, rootDir);
|
|
513
|
+
if (result.deleted) {
|
|
514
|
+
deletedFiles.push(filePath);
|
|
515
|
+
} else if (result.error) {
|
|
516
|
+
failedFiles.push({ error: result.error, filePath });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return { deletedFiles, failedFiles };
|
|
521
|
+
}
|
|
522
|
+
|
|
402
523
|
function markSplatResourceProgress(context, resourcePath) {
|
|
403
524
|
const progress = context.progress;
|
|
404
525
|
if (
|
|
@@ -472,6 +593,7 @@ async function processGltfResource({
|
|
|
472
593
|
const resourceBounds = createBounds();
|
|
473
594
|
let hasResourceBounds = false;
|
|
474
595
|
let deletedSplats = 0;
|
|
596
|
+
let modified = false;
|
|
475
597
|
const rewriteTasks = [];
|
|
476
598
|
|
|
477
599
|
for (const [bufferViewIndex, viewDescriptors] of byBufferView) {
|
|
@@ -511,6 +633,14 @@ async function processGltfResource({
|
|
|
511
633
|
emptyDescriptors.push(...viewDescriptors);
|
|
512
634
|
continue;
|
|
513
635
|
}
|
|
636
|
+
if (Number.isInteger(rewrite.survivorCount)) {
|
|
637
|
+
modified =
|
|
638
|
+
updateGaussianPrimitiveAccessorCounts(
|
|
639
|
+
resource,
|
|
640
|
+
viewDescriptors,
|
|
641
|
+
rewrite.survivorCount,
|
|
642
|
+
) > 0 || modified;
|
|
643
|
+
}
|
|
514
644
|
if (!rewrite.bytes) {
|
|
515
645
|
continue;
|
|
516
646
|
}
|
|
@@ -526,7 +656,6 @@ async function processGltfResource({
|
|
|
526
656
|
});
|
|
527
657
|
}
|
|
528
658
|
|
|
529
|
-
let modified = false;
|
|
530
659
|
if (emptyDescriptors.length > 0) {
|
|
531
660
|
modified = removeMeshPrimitives(resource, emptyDescriptors) > 0 || modified;
|
|
532
661
|
}
|
|
@@ -948,6 +1077,7 @@ async function traverseTileset({
|
|
|
948
1077
|
|
|
949
1078
|
module.exports = {
|
|
950
1079
|
collectCandidateSplatResources,
|
|
1080
|
+
deleteOrphanedSplatResources,
|
|
951
1081
|
readTilesetJson,
|
|
952
1082
|
traverseTileset,
|
|
953
1083
|
};
|
|
@@ -344,10 +344,24 @@ async function rewriteSpzBytes({
|
|
|
344
344
|
descriptors,
|
|
345
345
|
);
|
|
346
346
|
if (survivors.length === 0) {
|
|
347
|
-
return {
|
|
347
|
+
return {
|
|
348
|
+
bounds,
|
|
349
|
+
bytes: null,
|
|
350
|
+
deleted,
|
|
351
|
+
empty: true,
|
|
352
|
+
splatCount: spz.numSplats,
|
|
353
|
+
survivorCount: 0,
|
|
354
|
+
};
|
|
348
355
|
}
|
|
349
356
|
if (deleted === 0) {
|
|
350
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
bounds,
|
|
359
|
+
bytes: null,
|
|
360
|
+
deleted: 0,
|
|
361
|
+
empty: false,
|
|
362
|
+
splatCount: spz.numSplats,
|
|
363
|
+
survivorCount: survivors.length,
|
|
364
|
+
};
|
|
351
365
|
}
|
|
352
366
|
|
|
353
367
|
return {
|
|
@@ -355,6 +369,8 @@ async function rewriteSpzBytes({
|
|
|
355
369
|
bytes: await writeSurvivingSpzBytes(SpzWriter, spz, splatData, survivors),
|
|
356
370
|
deleted,
|
|
357
371
|
empty: false,
|
|
372
|
+
splatCount: spz.numSplats,
|
|
373
|
+
survivorCount: survivors.length,
|
|
358
374
|
};
|
|
359
375
|
}
|
|
360
376
|
|
|
@@ -380,6 +396,8 @@ parentPort.on('message', async (message) => {
|
|
|
380
396
|
bytes,
|
|
381
397
|
deleted: result.deleted,
|
|
382
398
|
empty: result.empty,
|
|
399
|
+
splatCount: result.splatCount,
|
|
400
|
+
survivorCount: result.survivorCount,
|
|
383
401
|
},
|
|
384
402
|
},
|
|
385
403
|
transferList,
|
|
@@ -77,6 +77,8 @@ class SplatCropWorkerPool {
|
|
|
77
77
|
bytes: result.bytes ? Buffer.from(result.bytes) : null,
|
|
78
78
|
deleted: Number(result.deleted || 0),
|
|
79
79
|
empty: !!result.empty,
|
|
80
|
+
splatCount: Number(result.splatCount || 0),
|
|
81
|
+
survivorCount: Number(result.survivorCount || 0),
|
|
80
82
|
});
|
|
81
83
|
}
|
|
82
84
|
|
package/src/viewer/app.js
CHANGED
|
@@ -17,6 +17,7 @@ import { bindViewerEvents } from './dom/events.js';
|
|
|
17
17
|
import { createViewerShutdownRequester } from './io/shutdown.js';
|
|
18
18
|
import { createSetPositionController } from './io/setPositionController.js';
|
|
19
19
|
import { createFlyToController } from './navigation/flyTo.js';
|
|
20
|
+
import { createCameraUrlPoseController } from './navigation/cameraUrlPose.js';
|
|
20
21
|
import { createCropController } from './screenSelection/cropController.js';
|
|
21
22
|
import { createRootTransformController } from './transform/rootTransformController.js';
|
|
22
23
|
import { createTransformModeController } from './transform/transformModeController.js';
|
|
@@ -269,6 +270,15 @@ const flyTo = createFlyToController({
|
|
|
269
270
|
getTilesetBoundingSphere,
|
|
270
271
|
});
|
|
271
272
|
|
|
273
|
+
const cameraUrlPose = createCameraUrlPoseController({
|
|
274
|
+
camera,
|
|
275
|
+
cameraController,
|
|
276
|
+
setStatus,
|
|
277
|
+
});
|
|
278
|
+
const appliedInitialCameraPose = cameraUrlPose.applyFromUrl({
|
|
279
|
+
showStatus: true,
|
|
280
|
+
});
|
|
281
|
+
|
|
272
282
|
setPositionController = createSetPositionController({
|
|
273
283
|
cameraController,
|
|
274
284
|
maxClickDistanceSq: SET_POSITION_CLICK_MAX_DISTANCE_SQ,
|
|
@@ -540,10 +550,11 @@ async function saveTransform() {
|
|
|
540
550
|
const processedSplatResources = Number(
|
|
541
551
|
payload.processedSplatResources || 0,
|
|
542
552
|
);
|
|
553
|
+
const deletedSplatFiles = Number(payload.deletedSplatFiles || 0);
|
|
543
554
|
cropController.clearAll();
|
|
544
555
|
loadTileset(TILESET_URL, { frameOnLoad: false });
|
|
545
556
|
setStatus(
|
|
546
|
-
`Saved transform and deleted ${deletedSplats} cropped splats from ${processedSplatResources} splat resource${processedSplatResources === 1 ? '' : 's'}. Reloading tileset.`,
|
|
557
|
+
`Saved transform and deleted ${deletedSplats} cropped splats from ${processedSplatResources} splat resource${processedSplatResources === 1 ? '' : 's'}${deletedSplatFiles > 0 ? `, removing ${deletedSplatFiles} orphaned file${deletedSplatFiles === 1 ? '' : 's'}` : ''}. Reloading tileset.`,
|
|
547
558
|
);
|
|
548
559
|
} else {
|
|
549
560
|
setStatus(
|
|
@@ -605,11 +616,21 @@ bindViewerEvents({
|
|
|
605
616
|
setStatus,
|
|
606
617
|
});
|
|
607
618
|
|
|
608
|
-
|
|
619
|
+
window.addEventListener('popstate', () => {
|
|
620
|
+
if (cameraUrlPose.applyFromUrl({ showStatus: true })) {
|
|
621
|
+
flyTo.cancelCameraFlight();
|
|
622
|
+
cancelPositionPickModes();
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
window.addEventListener('pagehide', cameraUrlPose.flush);
|
|
626
|
+
cameraController.addEventListener('finish', cameraUrlPose.flush);
|
|
627
|
+
|
|
628
|
+
loadTileset(TILESET_URL, { frameOnLoad: !appliedInitialCameraPose });
|
|
609
629
|
|
|
610
630
|
function frame() {
|
|
611
631
|
cameraController.update();
|
|
612
632
|
flyTo.update();
|
|
633
|
+
cameraUrlPose.update();
|
|
613
634
|
rootTransform.flush();
|
|
614
635
|
globeController.update();
|
|
615
636
|
tiles?.update();
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const CAMERA_POSE_PARAM = 'camerapose';
|
|
2
|
+
const CAMERA_POSE_ALIAS_PARAM = 'cameraPose';
|
|
3
|
+
const CAMERA_POSE_COMPONENT_COUNT = 7;
|
|
4
|
+
const CAMERA_POSE_COMPONENT_COUNT_WITH_ZOOM = 8;
|
|
5
|
+
const CAMERA_POSE_UPDATE_INTERVAL_MS = 100;
|
|
6
|
+
const CAMERA_POSE_VALUE_SEPARATOR = '_';
|
|
7
|
+
|
|
8
|
+
function formatPoseNumber(value) {
|
|
9
|
+
const fixed = value.toFixed(9);
|
|
10
|
+
const trimmed = fixed
|
|
11
|
+
.replace(/(\.\d*?[1-9])0+$/, '$1')
|
|
12
|
+
.replace(/\.0+$/, '');
|
|
13
|
+
|
|
14
|
+
return trimmed === '-0' ? '0' : trimmed;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function serializeCameraPose(camera) {
|
|
18
|
+
camera.updateMatrixWorld(true);
|
|
19
|
+
|
|
20
|
+
const values = [
|
|
21
|
+
camera.position.x,
|
|
22
|
+
camera.position.y,
|
|
23
|
+
camera.position.z,
|
|
24
|
+
camera.quaternion.x,
|
|
25
|
+
camera.quaternion.y,
|
|
26
|
+
camera.quaternion.z,
|
|
27
|
+
camera.quaternion.w,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (camera.isOrthographicCamera) {
|
|
31
|
+
values.push(camera.zoom);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!values.every(Number.isFinite)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return values.map(formatPoseNumber).join(CAMERA_POSE_VALUE_SEPARATOR);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getCameraPoseParam(url) {
|
|
42
|
+
return (
|
|
43
|
+
url.searchParams.get(CAMERA_POSE_PARAM) ??
|
|
44
|
+
url.searchParams.get(CAMERA_POSE_ALIAS_PARAM)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseCameraPose(value) {
|
|
49
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const parts = value.trim().split(/[,_\s]+/).filter(Boolean);
|
|
54
|
+
if (
|
|
55
|
+
parts.length !== CAMERA_POSE_COMPONENT_COUNT &&
|
|
56
|
+
parts.length !== CAMERA_POSE_COMPONENT_COUNT_WITH_ZOOM
|
|
57
|
+
) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const values = parts.map(Number);
|
|
62
|
+
if (!values.every(Number.isFinite)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const quaternionLength = Math.hypot(
|
|
67
|
+
values[3],
|
|
68
|
+
values[4],
|
|
69
|
+
values[5],
|
|
70
|
+
values[6],
|
|
71
|
+
);
|
|
72
|
+
if (quaternionLength <= 1e-12) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const zoom = values.length === CAMERA_POSE_COMPONENT_COUNT_WITH_ZOOM
|
|
77
|
+
? values[7]
|
|
78
|
+
: null;
|
|
79
|
+
if (zoom !== null && zoom <= 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
position: values.slice(0, 3),
|
|
85
|
+
quaternion: values.slice(3, 7),
|
|
86
|
+
zoom,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function replaceCameraPoseUrl(serializedPose) {
|
|
91
|
+
const url = new URL(window.location.href);
|
|
92
|
+
url.searchParams.delete(CAMERA_POSE_ALIAS_PARAM);
|
|
93
|
+
url.searchParams.set(CAMERA_POSE_PARAM, serializedPose);
|
|
94
|
+
window.history.replaceState(window.history.state, '', url.href);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getInvalidCameraPoseMessage() {
|
|
98
|
+
return 'Invalid camerapose URL parameter ignored. Expected x_y_z_qx_qy_qz_qw.';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createCameraUrlPoseController({
|
|
102
|
+
camera,
|
|
103
|
+
cameraController,
|
|
104
|
+
setStatus,
|
|
105
|
+
}) {
|
|
106
|
+
let lastSerializedPose = null;
|
|
107
|
+
let lastUrlWriteTime = 0;
|
|
108
|
+
|
|
109
|
+
function applyPose(pose) {
|
|
110
|
+
camera.position.fromArray(pose.position);
|
|
111
|
+
camera.quaternion.fromArray(pose.quaternion).normalize();
|
|
112
|
+
|
|
113
|
+
if (pose.zoom !== null && camera.isOrthographicCamera) {
|
|
114
|
+
camera.zoom = Math.max(pose.zoom, 1e-6);
|
|
115
|
+
camera.updateProjectionMatrix();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
camera.updateMatrixWorld(true);
|
|
119
|
+
cameraController.setCamera(camera);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function applyFromUrl({ showStatus = false } = {}) {
|
|
123
|
+
const url = new URL(window.location.href);
|
|
124
|
+
const value = getCameraPoseParam(url);
|
|
125
|
+
if (value === null) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const pose = parseCameraPose(value);
|
|
130
|
+
if (!pose) {
|
|
131
|
+
if (showStatus && setStatus) {
|
|
132
|
+
setStatus(getInvalidCameraPoseMessage(), true);
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
applyPose(pose);
|
|
138
|
+
lastSerializedPose = serializeCameraPose(camera);
|
|
139
|
+
lastUrlWriteTime = performance.now();
|
|
140
|
+
if (lastSerializedPose) {
|
|
141
|
+
replaceCameraPoseUrl(lastSerializedPose);
|
|
142
|
+
}
|
|
143
|
+
if (showStatus && setStatus) {
|
|
144
|
+
setStatus('Applied camera pose from URL.');
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function flush() {
|
|
150
|
+
const serializedPose = serializeCameraPose(camera);
|
|
151
|
+
if (!serializedPose || serializedPose === lastSerializedPose) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
replaceCameraPoseUrl(serializedPose);
|
|
156
|
+
lastSerializedPose = serializedPose;
|
|
157
|
+
lastUrlWriteTime = performance.now();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function update(time = performance.now()) {
|
|
161
|
+
if (time - lastUrlWriteTime < CAMERA_POSE_UPDATE_INTERVAL_MS) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
flush();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
applyFromUrl,
|
|
169
|
+
flush,
|
|
170
|
+
update,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -133,6 +133,11 @@ export function createFlyToController({
|
|
|
133
133
|
return true;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
function cancelCameraFlight() {
|
|
137
|
+
activeCameraFlight = null;
|
|
138
|
+
activeCameraFlightStatus = '';
|
|
139
|
+
}
|
|
140
|
+
|
|
136
141
|
async function applyTilesSetPositionFromPointerEvent(event) {
|
|
137
142
|
const coordinate = pickCoordinateFromPointerEvent(event);
|
|
138
143
|
if (!coordinate) {
|
|
@@ -204,6 +209,7 @@ export function createFlyToController({
|
|
|
204
209
|
|
|
205
210
|
return {
|
|
206
211
|
applyTilesSetPositionFromPointerEvent,
|
|
212
|
+
cancelCameraFlight,
|
|
207
213
|
frameTileset,
|
|
208
214
|
getActiveEllipsoid,
|
|
209
215
|
moveCameraToCoordinate,
|