From fe81c3d448cc2affde1e9e3d3473d50b99271334 Mon Sep 17 00:00:00 2001 From: Trevor Parscal Date: Fri, 22 Oct 2021 16:58:25 -0400 Subject: [PATCH 1/2] Add wait and progress options to Viewer.switchScene wait: Delays transitioning to the new scene until all visible tiles are loaded. If the user rotates the camera while loading, it may take longer to load. progress: Calls a function as progress loading visible tiles is made. can be used with or without wait. Progress values are given in the range of [0..1] and may go down if the user rotates the camera, bringing new tiles into view. --- src/Viewer.js | 103 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/src/Viewer.js b/src/Viewer.js index 31c795d45..e9f0be4b7 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -631,6 +631,12 @@ function defaultTransitionUpdate(val, newScene, oldScene) { * * @param {Scene} newScene The scene to switch to. * @param {Object} opts Transition options. + * @param {boolean} [opts.wait] Wait until all visible tiles are loaded before + * switching to the new scene. + * @param {boolean} [opts.progress] Function to call as the new scene is + * loading. A `progress` parameter is passed as a number in the range of + * [0..1]. If the new scene is already loaded this function is still called + * with a value of 1. * @param {number} [opts.transitionDuration=1000] Transition duration, in * milliseconds. * @param {number} [opts.transitionUpdate=defaultTransitionUpdate] @@ -654,6 +660,7 @@ Viewer.prototype.switchScene = function(newScene, opts, done) { // Do nothing if the target scene is the current one. if (oldScene === newScene) { + opts.progress && opts.progress(1); done(); return; } @@ -687,11 +694,6 @@ Viewer.prototype.switchScene = function(newScene, opts, done) { var update = opts.transitionUpdate != null ? opts.transitionUpdate : defaultTransitionUpdate; - // Add new scene layers into the stage before starting the transition. - for (var i = 0; i < newSceneLayers.length; i++) { - this._addLayerToStage(newSceneLayers[i]); - } - // Update function to be called on every frame. function tweenUpdate(val) { update(val, newScene, oldScene); @@ -715,22 +717,87 @@ Viewer.prototype.switchScene = function(newScene, opts, done) { done(); } - // Store the cancelable for the transition. - this._cancelCurrentTween = tween(duration, tweenUpdate, tweenDone); + function tweenLayers() { + // Store the cancelable for the transition. + self._cancelCurrentTween = tween(duration, tweenUpdate, tweenDone); + + // Update the current and replaced scene. + self._currentScene = newScene; + self._replacedScene = oldScene; + + // Emit scene and view change events. + self.emit('sceneChange'); + self.emit('viewChange'); + + // Add event listeners to the new scene. + // Note that event listeners can only be removed from the old scene once the + // transition is complete, since layers might get added or removed in the + // interim. + self._addSceneEventListeners(newScene); + } + + // If waiting to transition, report the overall progress and also check the + // progress of each layer and start the transition when they have all finished. + var loadProgress = []; + function layerProgress(layerIndex, value) { + // Stop early if the value is the same as last time + if (loadProgress[layerIndex] === value) { + return; + } + loadProgress[layerIndex] = value; + // Calculate the progress of all layers + var total = 0; + for (var i = 0; i < loadProgress.length; i++) { + total += loadProgress[i]; + } + // Report progress and start tweening when they're all finished + if (total === loadProgress.length) { + opts.progress && opts.progress(1); + opts.wait && tweenLayers(); + } else { + opts.progress && opts.progress(total / loadProgress.length); + } + } - // Update the current and replaced scene. - this._currentScene = newScene; - this._replacedScene = oldScene; + // Monitor the loading progress of a specific layer + function monitorLayerProgress(layerIndex) { + var sceneLayer = newSceneLayers[layerIndex]; + var textureStore = sceneLayer.textureStore(); + function onRenderComplete() { + var tileList = []; + sceneLayer.visibleTiles(tileList); + var count = 0; + for (var i = 0; i < tileList.length; i++) { + if (textureStore.query(tileList[ i ]).hasTexture) { + count++; + } + } + layerProgress(layerIndex, count / tileList.length); + if (count === tileList.length) { + sceneLayer.removeEventListener('renderComplete', onRenderComplete); + } + } + sceneLayer.addEventListener('renderComplete', onRenderComplete); + } - // Emit scene and view change events. - this.emit('sceneChange'); - this.emit('viewChange'); + // Add new scene layers into the stage before starting the transition. + for (var i = 0; i < newSceneLayers.length; i++) { + this._addLayerToStage(newSceneLayers[i]); + if (opts.wait) { + // Initially hide while loading finishes + newSceneLayers[i].mergeEffects({ opacity: 0 }); + loadProgress.push( 0 ); + } + } - // Add event listeners to the new scene. - // Note that event listeners can only be removed from the old scene once the - // transition is complete, since layers might get added or removed in the - // interim. - this._addSceneEventListeners(newScene); + if (opts.wait || opts.progress) { + for (var i = 0; i < newSceneLayers.length; i++) { + monitorLayerProgress(i) + } + } + if (!opts.wait) { + tweenLayers(); + } }; From 1f950ef38d03632e83540b83bc9459093276fa8c Mon Sep 17 00:00:00 2001 From: Trevor Parscal Date: Thu, 28 Oct 2021 10:57:32 -0400 Subject: [PATCH 2/2] Fix bug where calling switchScene rapidly would throw an error When switching to another scene while already waiting for a scene to load, remove the previous monitoring events and layers of the previously requested scene. --- src/Viewer.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Viewer.js b/src/Viewer.js index e9f0be4b7..463b7a3ab 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -136,6 +136,9 @@ function Viewer(domElement, opts) { // The current transition. this._cancelCurrentTween = null; + // The current scene load monitoring. + this._cancelCurrentMonitor = null; + // The event listener fired when the current scene layers change. // This is attached to the correct scene whenever the current scene changes. this._layerChangeHandler = this._updateSceneLayers.bind(this); @@ -206,6 +209,10 @@ Viewer.prototype.destroy = function() { this._cancelCurrentTween(); } + if (this._cancelCurrentMonitor) { + this._cancelCurrentMonitor(); + } + clearOwnProperties(this); }; @@ -424,6 +431,10 @@ Viewer.prototype.destroyScene = function(scene) { this._cancelCurrentTween(); this._cancelCurrentTween = null; } + if (this._cancelCurrentMonitor) { + this._cancelCurrentMonitor(); + this._cancelCurrentMonitor = null; + } this._currentScene = null; this.emit('sceneChange'); } @@ -676,6 +687,13 @@ Viewer.prototype.switchScene = function(newScene, opts, done) { this._cancelCurrentTween = null; } + // Cancel an already ongoing monitor. This ensures that the stage doesn't get + // out of sync. + if (this._cancelCurrentMonitor) { + this._cancelCurrentMonitor(); + this._cancelCurrentMonitor = null; + } + var oldSceneLayers = oldScene ? oldScene.listLayers() : []; var newSceneLayers = newScene.listLayers(); var stageLayers = stage.listLayers(); @@ -752,6 +770,7 @@ Viewer.prototype.switchScene = function(newScene, opts, done) { } // Report progress and start tweening when they're all finished if (total === loadProgress.length) { + self._cancelCurrentMonitor = null; opts.progress && opts.progress(1); opts.wait && tweenLayers(); } else { @@ -777,6 +796,14 @@ Viewer.prototype.switchScene = function(newScene, opts, done) { sceneLayer.removeEventListener('renderComplete', onRenderComplete); } } + self._cancelCurrentMonitor = function () { + sceneLayer.removeEventListener('renderComplete', onRenderComplete); + for (var i = 0; i < newSceneLayers.length; i++) { + this._removeLayerFromStage(newSceneLayers[i]); + } + opts.progress && opts.progress(1); + done(); + }; sceneLayer.addEventListener('renderComplete', onRenderComplete); }