diff --git a/Apps/Sandcastle/gallery/development/3D Tiles Performance Testing.html b/Apps/Sandcastle/gallery/development/3D Tiles Performance Testing.html new file mode 100644 index 00000000000..dd1d6644622 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/3D Tiles Performance Testing.html @@ -0,0 +1,211 @@ + + + + + + + + + Streaming Performance Testing + + + + + + +
+

Loading...

+
+ + + diff --git a/CHANGES.md b/CHANGES.md index f1485052964..78ff1633c38 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,25 @@ Change Log ### 1.57 - 2019-05-01 ##### Additions :tada: +* Improved 3D Tiles streaming performance, resulting in ~67% camera tour load time reduction, ~44% camera tour load count reduction. And for general camera movement, ~20% load time reduction with ~27% tile load count reduction. Tile load priority changed to focus on loading tiles in the center of the screen first. Added the following tileset optimizations, which unless stated otherwise are enabled by default. [#7774](https://github.com/AnalyticalGraphicsInc/cesium/pull/7774) + * Added `Cesium3DTileset.cullRequestsWhileMoving` option to ignore requests for tiles that will likely be out-of-view due to the camera's movement when they come back from the server. + * Added `Cesium3DTileset.cullRequestsWhileMovingMultiplier` option to act as a multiplier when used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling. + * Added `Cesium3DTileset.preloadFlightDestinations` option to preload tiles at the camera's flight destination while the camera is in flight. + * Added `Cesium3DTileset.preferLeaves` option to prefer loading of leaves. Good for additive refinement point clouds. Set to `false` by default. + * Added `Cesium3DTileset.progressiveResolutionHeightFraction` option to load tiles at a smaller resolution first. This can help get a quick layer of tiles down while full resolution tiles continue to load. + * Added `Cesium3DTileset.foveatedScreenSpaceError` option to prioritize loading tiles in the center of the screen. + * Added `Cesium3DTileset.foveatedConeSize` option to control the cone size that determines which tiles are deferred for loading. Tiles outside the cone are potentially deferred. + * Added `Cesium3DTileset.foveatedMinimumScreenSpaceErrorRelaxation` option to control the starting screen space error relaxation for tiles outside the foveated cone. + * Added `Cesium3DTileset.foveatedInterpolationCallback` option to control how screen space error threshold is interpolated for tiles outside the foveated cone. + * Added `Cesium3DTileset.foveatedTimeDelay` option to control how long in seconds to wait after the camera stops moving before deferred tiles start loading in. * Added new parameter to `PolylineGlowMaterial` called `taperPower`, that works similar to the existing `glowPower` parameter, to taper the back of the line away. [#7626](https://github.com/AnalyticalGraphicsInc/cesium/pull/7626) +* Added `Cesium3DTileset.preloadWhenHidden` tileset option to preload tiles when `tileset.show` is false. Loads tiles as if the tileset is visible but does not render them. [#7774](https://github.com/AnalyticalGraphicsInc/cesium/pull/7774) * Added support for the `KHR_texture_transform` glTF extension. [#7549](https://github.com/AnalyticalGraphicsInc/cesium/pull/7549) * Added functions to remove samples from `SampledProperty` and `SampledPositionProperty`. [#7723](https://github.com/AnalyticalGraphicsInc/cesium/pull/7723) * Added support for color-to-alpha with a threshold on imagery layers. [#7727](https://github.com/AnalyticalGraphicsInc/cesium/pull/7727) * Add CZML processing for `heightReference` and `extrudedHeightReference` for geoemtry types that support it. +* `CesiumMath.toSNorm` documentation changed to reflect the function's implementation. [#7774](https://github.com/AnalyticalGraphicsInc/cesium/pull/7774) +* Added `CesiumMath.normalize` to convert a scalar value in an arbitrary range to a scalar in the range [0.0, 1.0]. [#7774](https://github.com/AnalyticalGraphicsInc/cesium/pull/7774) ##### Fixes :wrench: * Fixed an error where `clampToHeightMostDetailed` or `sampleHeightMostDetailed` would crash if entities were created when the promise resolved. [#7690](https://github.com/AnalyticalGraphicsInc/cesium/pull/7690) diff --git a/Source/Core/Math.js b/Source/Core/Math.js index 1322788ac12..fad64ab7eec 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -227,29 +227,41 @@ define([ }; /** - * Converts a scalar value in the range [-1.0, 1.0] to a SNORM in the range [0, rangeMax] + * Converts a scalar value in the range [-1.0, 1.0] to a SNORM in the range [0, rangeMaximum] * @param {Number} value The scalar value in the range [-1.0, 1.0] - * @param {Number} [rangeMax=255] The maximum value in the mapped range, 255 by default. - * @returns {Number} A SNORM value, where 0 maps to -1.0 and rangeMax maps to 1.0. + * @param {Number} [rangeMaximum=255] The maximum value in the mapped range, 255 by default. + * @returns {Number} A SNORM value, where 0 maps to -1.0 and rangeMaximum maps to 1.0. * * @see CesiumMath.fromSNorm */ - CesiumMath.toSNorm = function(value, rangeMax) { - rangeMax = defaultValue(rangeMax, 255); - return Math.round((CesiumMath.clamp(value, -1.0, 1.0) * 0.5 + 0.5) * rangeMax); + CesiumMath.toSNorm = function(value, rangeMaximum) { + rangeMaximum = defaultValue(rangeMaximum, 255); + return Math.round((CesiumMath.clamp(value, -1.0, 1.0) * 0.5 + 0.5) * rangeMaximum); }; /** - * Converts a SNORM value in the range [0, rangeMax] to a scalar in the range [-1.0, 1.0]. - * @param {Number} value SNORM value in the range [0, 255] - * @param {Number} [rangeMax=255] The maximum value in the SNORM range, 255 by default. + * Converts a SNORM value in the range [0, rangeMaximum] to a scalar in the range [-1.0, 1.0]. + * @param {Number} value SNORM value in the range [0, rangeMaximum] + * @param {Number} [rangeMaximum=255] The maximum value in the SNORM range, 255 by default. * @returns {Number} Scalar in the range [-1.0, 1.0]. * * @see CesiumMath.toSNorm */ - CesiumMath.fromSNorm = function(value, rangeMax) { - rangeMax = defaultValue(rangeMax, 255); - return CesiumMath.clamp(value, 0.0, rangeMax) / rangeMax * 2.0 - 1.0; + CesiumMath.fromSNorm = function(value, rangeMaximum) { + rangeMaximum = defaultValue(rangeMaximum, 255); + return CesiumMath.clamp(value, 0.0, rangeMaximum) / rangeMaximum * 2.0 - 1.0; + }; + + /** + * Converts a scalar value in the range [rangeMinimum, rangeMaximum] to a scalar in the range [0.0, 1.0] + * @param {Number} value The scalar value in the range [rangeMinimum, rangeMaximum] + * @param {Number} rangeMinimum The minimum value in the mapped range. + * @param {Number} rangeMaximum The maximum value in the mapped range. + * @returns {Number} A scalar value, where rangeMinimum maps to 0.0 and rangeMaximum maps to 1.0. + */ + CesiumMath.normalize = function(value, rangeMinimum, rangeMaximum) { + rangeMaximum = Math.max(rangeMaximum - rangeMinimum, 0.0); + return rangeMaximum === 0.0 ? 0.0 : CesiumMath.clamp((value - rangeMinimum) / rangeMaximum, 0.0, 1.0); }; /** diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index a60a5696efe..d19161c383a 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -34,7 +34,8 @@ define([ numberOfCancelledRequests : 0, numberOfCancelledActiveRequests : 0, numberOfFailedRequests : 0, - numberOfActiveRequestsEver : 0 + numberOfActiveRequestsEver : 0, + lastNumberOfActiveRequests : 0 }; var priorityHeapLength = 20; @@ -373,34 +374,34 @@ define([ return issueRequest(request); }; - function clearStatistics() { - statistics.numberOfAttemptedRequests = 0; - statistics.numberOfCancelledRequests = 0; - statistics.numberOfCancelledActiveRequests = 0; - } - function updateStatistics() { if (!RequestScheduler.debugShowStatistics) { return; } - if (statistics.numberOfAttemptedRequests > 0) { - console.log('Number of attempted requests: ' + statistics.numberOfAttemptedRequests); - } - if (statistics.numberOfActiveRequests > 0) { - console.log('Number of active requests: ' + statistics.numberOfActiveRequests); - } - if (statistics.numberOfCancelledRequests > 0) { - console.log('Number of cancelled requests: ' + statistics.numberOfCancelledRequests); - } - if (statistics.numberOfCancelledActiveRequests > 0) { - console.log('Number of cancelled active requests: ' + statistics.numberOfCancelledActiveRequests); - } - if (statistics.numberOfFailedRequests > 0) { - console.log('Number of failed requests: ' + statistics.numberOfFailedRequests); + if (statistics.numberOfActiveRequests === 0 && statistics.lastNumberOfActiveRequests > 0) { + if (statistics.numberOfAttemptedRequests > 0) { + console.log('Number of attempted requests: ' + statistics.numberOfAttemptedRequests); + statistics.numberOfAttemptedRequests = 0; + } + + if (statistics.numberOfCancelledRequests > 0) { + console.log('Number of cancelled requests: ' + statistics.numberOfCancelledRequests); + statistics.numberOfCancelledRequests = 0; + } + + if (statistics.numberOfCancelledActiveRequests > 0) { + console.log('Number of cancelled active requests: ' + statistics.numberOfCancelledActiveRequests); + statistics.numberOfCancelledActiveRequests = 0; + } + + if (statistics.numberOfFailedRequests > 0) { + console.log('Number of failed requests: ' + statistics.numberOfFailedRequests); + statistics.numberOfFailedRequests = 0; + } } - clearStatistics(); + statistics.lastNumberOfActiveRequests = statistics.numberOfActiveRequests; } /** @@ -427,6 +428,7 @@ define([ statistics.numberOfCancelledActiveRequests = 0; statistics.numberOfFailedRequests = 0; statistics.numberOfActiveRequestsEver = 0; + statistics.lastNumberOfActiveRequests = 0; }; /** diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js index 788b28820e2..e16cfe554c5 100644 --- a/Source/Scene/Camera.js +++ b/Source/Scene/Camera.js @@ -12,6 +12,7 @@ define([ '../Core/Ellipsoid', '../Core/EllipsoidGeodesic', '../Core/Event', + '../Core/getTimestamp', '../Core/HeadingPitchRange', '../Core/HeadingPitchRoll', '../Core/Intersect', @@ -43,6 +44,7 @@ define([ Ellipsoid, EllipsoidGeodesic, Event, + getTimestamp, HeadingPitchRange, HeadingPitchRoll, Intersect, @@ -115,6 +117,29 @@ define([ this._position = new Cartesian3(); this._positionWC = new Cartesian3(); this._positionCartographic = new Cartographic(); + this._oldPositionWC = undefined; + + /** + * The position delta magnitude. + * + * @private + */ + this.positionWCDeltaMagnitude = 0.0; + + /** + * The position delta magnitude last frame. + * + * @private + */ + this.positionWCDeltaMagnitudeLastFrame = 0.0; + + /** + * How long in seconds since the camera has stopped moving + * + * @private + */ + this.timeSinceMoved = 0.0; + this._lastMovedTimestamp = 0.0; /** * The view direction of the camera. @@ -275,9 +300,43 @@ define([ Matrix4.inverseTransformation(camera._viewMatrix, camera._invViewMatrix); } + function updateCameraDeltas(camera) { + if (!defined(camera._oldPositionWC)) { + camera._oldPositionWC = Cartesian3.clone(camera.positionWC, camera._oldPositionWC); + } else { + camera.positionWCDeltaMagnitudeLastFrame = camera.positionWCDeltaMagnitude; + var delta = Cartesian3.subtract(camera.positionWC, camera._oldPositionWC, camera._oldPositionWC); + camera.positionWCDeltaMagnitude = Cartesian3.magnitude(delta); + camera._oldPositionWC = Cartesian3.clone(camera.positionWC, camera._oldPositionWC); + + // Update move timers + if (camera.positionWCDeltaMagnitude > 0.0) { + camera.timeSinceMoved = 0.0; + camera._lastMovedTimestamp = getTimestamp(); + } else { + camera.timeSinceMoved = Math.max(getTimestamp() - camera._lastMovedTimestamp, 0.0) / 1000.0; + } + } + } + + /** + * Checks if there's a camera flight for this camera. + * + * @returns {Boolean} Whether or not this camera has a current flight with a valid preloadFlightCamera in scene. + * + * @private + * + */ + Camera.prototype.hasCurrentFlight = function() { + // The preload flight camera defined check only here since it can be set to undefined when not 3D mode. + return defined(this._currentFlight) && defined(this._scene.preloadFlightCamera); + }; + Camera.prototype._updateCameraChanged = function() { var camera = this; + updateCameraDeltas(camera); + if (camera._changed.numberOfListeners === 0) { return; } @@ -2861,6 +2920,19 @@ define([ var scene = this._scene; flightTween = scene.tweens.add(CameraFlightPath.createTween(scene, newOptions)); this._currentFlight = flightTween; + + // Save the final destination view information for the PRELOAD_FLIGHT pass. + var preloadFlightCamera = this._scene.preloadFlightCamera; + if (this._mode !== SceneMode.SCENE2D) { + if (!defined(preloadFlightCamera)) { + preloadFlightCamera = Camera.clone(this); + } + preloadFlightCamera.setView({ destination: destination, orientation: orientation }); + + this._scene.preloadFlightCullingVolume = preloadFlightCamera.frustum.computeCullingVolume(preloadFlightCamera.positionWC, preloadFlightCamera.directionWC, preloadFlightCamera.upWC); + } else { + preloadFlightCamera = undefined; + } }; function distanceToBoundingSphere3D(camera, radius) { diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index ce8c95085c1..2035b69b2c7 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -30,6 +30,7 @@ define([ './Cesium3DTileContentFactory', './Cesium3DTileContentState', './Cesium3DTileOptimizationHint', + './Cesium3DTilePass', './Cesium3DTileRefine', './Empty3DTileContent', './SceneMode', @@ -68,6 +69,7 @@ define([ Cesium3DTileContentFactory, Cesium3DTileContentState, Cesium3DTileOptimizationHint, + Cesium3DTilePass, Cesium3DTileRefine, Empty3DTileContent, SceneMode, @@ -290,7 +292,7 @@ define([ * * @private */ - this.lastStyleTime = 0; + this.lastStyleTime = 0.0; /** * Marks whether the tile's children bounds are fully contained within the tile's bounds @@ -311,10 +313,21 @@ define([ */ this.clippingPlanesDirty = false; + /** + * Tracks if the tile's request should be deferred until all non-deferred + * tiles load. + * + * @type {Boolean} + * + * @private + */ + this.priorityDeferred = false; + // Members that are updated every frame for tree traversal and rendering optimizations: - this._distanceToCamera = 0; - this._centerZDepth = 0; - this._screenSpaceError = 0; + this._distanceToCamera = 0.0; + this._centerZDepth = 0.0; + this._screenSpaceError = 0.0; + this._screenSpaceErrorProgressiveResolution = 0.0; // The screen space error at a given screen height of tileset.progressiveResolutionHeightFraction * screenHeight this._visibilityPlaneMask = 0; this._visible = false; this._inRequestVolume = false; @@ -333,7 +346,6 @@ define([ this._ancestorWithContentAvailable = undefined; this._refines = false; this._shouldSelect = false; - this._priority = 0.0; this._isClipped = true; this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function this._debugBoundingVolume = undefined; @@ -342,10 +354,22 @@ define([ this._debugColor = Color.fromRandom({ alpha : 1.0 }); this._debugColorizeTiles = false; + this._priority = 0.0; // The priority used for request sorting + this._priorityHolder = this; // Reference to the ancestor up the tree that holds the _foveatedFactor and _distanceToCamera for all tiles in the refinement chain. + this._priorityProgressiveResolution = false; + this._priorityProgressiveResolutionScreenSpaceErrorLeaf = false; + this._priorityReverseScreenSpaceError = 0.0; + this._foveatedFactor = 0.0; + this._wasMinPriorityChild = false; // Needed for knowing when to continue a refinement chain. Gets reset in updateTile in traversal and gets set in updateAndPushChildren in traversal. + + this._loadTimestamp = new JulianDate(); + this._commandsLength = 0; this._color = undefined; this._colorDirty = false; + + this._request = undefined; } // This can be overridden for testing purposes @@ -607,6 +631,66 @@ define([ } }); + var scratchCartesian = new Cartesian3(); + function isPriorityDeferred(tile, frameState) { + var tileset = tile._tileset; + + // If closest point on line is inside the sphere then set foveatedFactor to 0. Otherwise, the dot product is with the line from camera to the point on the sphere that is closest to the line. + var camera = frameState.camera; + var boundingSphere = tile.boundingSphere; + var radius = boundingSphere.radius; + var scaledCameraDirection = Cartesian3.multiplyByScalar(camera.directionWC, tile._centerZDepth, scratchCartesian); + var closestPointOnLine = Cartesian3.add(camera.positionWC, scaledCameraDirection, scratchCartesian); + // The distance from the camera's view direction to the tile. + var toLine = Cartesian3.subtract(closestPointOnLine, boundingSphere.center, scratchCartesian); + var distanceToCenterLine = Cartesian3.magnitude(toLine); + var notTouchingSphere = distanceToCenterLine > radius; + + // If camera's direction vector is inside the bounding sphere then consider + // this tile right along the line of sight and set _foveatedFactor to 0. + // Otherwise,_foveatedFactor is one minus the dot product of the camera's direction + // and the vector between the camera and the point on the bounding sphere closest to the view line. + if (notTouchingSphere) { + var toLineNormalized = Cartesian3.normalize(toLine, scratchCartesian); + var scaledToLine = Cartesian3.multiplyByScalar(toLineNormalized, radius, scratchCartesian); + var closestOnSphere = Cartesian3.add(boundingSphere.center, scaledToLine, scratchCartesian); + var toClosestOnSphere = Cartesian3.subtract(closestOnSphere, camera.positionWC, scratchCartesian); + var toClosestOnSphereNormalize = Cartesian3.normalize(toClosestOnSphere, scratchCartesian); + tile._foveatedFactor = 1.0 - Math.abs(Cartesian3.dot(camera.directionWC, toClosestOnSphereNormalize)); + } else { + tile._foveatedFactor = 0.0; + } + + // Skip this feature if: non-skipLevelOfDetail and replace refine, if the foveated settings are turned off, if tile is progressive resolution and replace refine and skipLevelOfDetail (will help get rid of ancestor artifacts faster) + // Or if the tile is a preload of any kind + var replace = tile.refine === Cesium3DTileRefine.REPLACE; + var skipLevelOfDetail = tileset._skipLevelOfDetail; + if ((replace && !skipLevelOfDetail) || + !tileset.foveatedScreenSpaceError || + tileset.foveatedConeSize === 1.0 || + (tile._priorityProgressiveResolution && replace && skipLevelOfDetail) || + tileset._pass === Cesium3DTilePass.PRELOAD_FLIGHT || + tileset._pass === Cesium3DTilePass.PRELOAD) { + return false; + } + + var maximumFovatedFactor = 1.0 - Math.cos(camera.frustum.fov * 0.5); // 0.14 for fov = 60. NOTE very hard to defer vertically foveated tiles since max is based on fovy (which is fov). Lowering the 0.5 to a smaller fraction of the screen height will start to defer vertically foveated tiles. + var foveatedConeFactor = tileset.foveatedConeSize * maximumFovatedFactor; + + // If it's inside the user-defined view cone, then it should not be deferred. + if (tile._foveatedFactor <= foveatedConeFactor) { + return false; + } + + // Relax SSE based on how big the angle is between the tile and the edge of the foveated cone. + var range = maximumFovatedFactor - foveatedConeFactor; + var normalizedFoveatedFactor = CesiumMath.clamp((tile._foveatedFactor - foveatedConeFactor) / range, 0.0, 1.0); + var sseRelaxation = tileset.foveatedInterpolationCallback(tileset.foveatedMinimumScreenSpaceErrorRelaxation, tileset.maximumScreenSpaceError, normalizedFoveatedFactor); + var sse = tile._screenSpaceError === 0.0 && defined(tile.parent) ? tile.parent._screenSpaceError * 0.5 : tile._screenSpaceError; + + return (tileset.maximumScreenSpaceError - sseRelaxation) <= sse; + } + var scratchJulianDate = new JulianDate(); /** @@ -614,8 +698,9 @@ define([ * * @private */ - Cesium3DTile.prototype.getScreenSpaceError = function(frameState, useParentGeometricError) { + Cesium3DTile.prototype.getScreenSpaceError = function(frameState, useParentGeometricError, progressiveResolutionHeightFraction) { var tileset = this._tileset; + var heightFraction = defaultValue(progressiveResolutionHeightFraction, 1.0); var parentGeometricError = defined(this.parent) ? this.parent.geometricError : tileset._geometricError; var geometricError = useParentGeometricError ? parentGeometricError : this.geometricError; if (geometricError === 0.0) { @@ -626,7 +711,7 @@ define([ var frustum = camera.frustum; var context = frameState.context; var width = context.drawingBufferWidth; - var height = context.drawingBufferHeight; + var height = context.drawingBufferHeight * heightFraction; var error; if (frameState.mode === SceneMode.SCENE2D || frustum instanceof OrthographicFrustum) { if (defined(frustum._offCenterFrustum)) { @@ -649,6 +734,31 @@ define([ return error; }; + function isPriorityProgressiveResolution(tileset, tile) { + if (tileset.progressiveResolutionHeightFraction <= 0.0 || tileset.progressiveResolutionHeightFraction > 0.5) { + return false; + } + + var isProgressiveResolutionTile = tile._screenSpaceErrorProgressiveResolution > tileset._maximumScreenSpaceError; // Mark non-SSE leaves + tile._priorityProgressiveResolutionScreenSpaceErrorLeaf = false; // Needed for skipLOD + var parent = tile.parent; + var maximumScreenSpaceError = tileset._maximumScreenSpaceError; + var tilePasses = tile._screenSpaceErrorProgressiveResolution <= maximumScreenSpaceError; + var parentFails = defined(parent) && parent._screenSpaceErrorProgressiveResolution > maximumScreenSpaceError; + if (tilePasses && parentFails) { // A progressive resolution SSE leaf, promote its priority as well + tile._priorityProgressiveResolutionScreenSpaceErrorLeaf = true; + isProgressiveResolutionTile = true; + } + return isProgressiveResolutionTile; + } + + function getPriorityReverseScreenSpaceError(tileset, tile) { + var parent = tile.parent; + var useParentScreenSpaceError = defined(parent) && (!tileset._skipLevelOfDetail || (tile._screenSpaceError === 0.0) || parent.hasTilesetContent); + var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError; + return tileset.root._screenSpaceError - screenSpaceError; + } + /** * Update the tile's visibility. * @@ -656,15 +766,20 @@ define([ */ Cesium3DTile.prototype.updateVisibility = function(frameState) { var parent = this.parent; - var parentTransform = defined(parent) ? parent.computedTransform : this._tileset.modelMatrix; + var tileset = this._tileset; + var parentTransform = defined(parent) ? parent.computedTransform : tileset.modelMatrix; var parentVisibilityPlaneMask = defined(parent) ? parent._visibilityPlaneMask : CullingVolume.MASK_INDETERMINATE; this.updateTransform(parentTransform); this._distanceToCamera = this.distanceToTile(frameState); this._centerZDepth = this.distanceToTileCenter(frameState); this._screenSpaceError = this.getScreenSpaceError(frameState, false); + this._screenSpaceErrorProgressiveResolution = this.getScreenSpaceError(frameState, false, tileset.progressiveResolutionHeightFraction); this._visibilityPlaneMask = this.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test this._visible = this._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE; this._inRequestVolume = this.insideViewerRequestVolume(frameState); + this._priorityReverseScreenSpaceError = getPriorityReverseScreenSpaceError(tileset, this); + this._priorityProgressiveResolution = isPriorityProgressiveResolution(tileset, this); + this.priorityDeferred = isPriorityDeferred(this, frameState); }; /** @@ -744,6 +859,7 @@ define([ serverKey : this._serverKey }); + this._request = request; resource.request = request; var promise = resource.fetchArrayBuffer(); @@ -798,8 +914,9 @@ define([ // Refresh style for expired content that._selectedFrame = 0; - that.lastStyleTime = 0; + that.lastStyleTime = 0.0; + JulianDate.now(that._loadTimestamp); that._contentState = Cesium3DTileContentState.READY; that._contentReadyPromise.resolve(content); }); @@ -832,7 +949,7 @@ define([ this._contentReadyToProcessPromise = undefined; this._contentReadyPromise = undefined; - this.lastStyleTime = 0; + this.lastStyleTime = 0.0; this.clippingPlanesDirty = (this._clippingPlanesState === 0); this._clippingPlanesState = 0; @@ -946,7 +1063,7 @@ define([ var scratchToTileCenter = new Cartesian3(); /** - * Computes the distance from the center of the tile's bounding volume to the camera. + * Computes the distance from the center of the tile's bounding volume to the camera's plane defined by its position and view direction. * * @param {FrameState} frameState The frame state. * @returns {Number} The distance, in meters. @@ -957,10 +1074,7 @@ define([ var tileBoundingVolume = getBoundingVolume(this, frameState); var boundingVolume = tileBoundingVolume.boundingVolume; // Gets the underlying OrientedBoundingBox or BoundingSphere var toCenter = Cartesian3.subtract(boundingVolume.center, frameState.camera.positionWC, scratchToTileCenter); - var distance = Cartesian3.magnitude(toCenter); - Cartesian3.divideByScalar(toCenter, distance, toCenter); - var dot = Cartesian3.dot(frameState.camera.directionWC, toCenter); - return distance * dot; + return Cartesian3.dot(frameState.camera.directionWC, toCenter); }; /** @@ -1166,10 +1280,11 @@ define([ tile._debugViewerRequestVolume = tile._debugViewerRequestVolume.destroy(); } - var debugColorizeTilesOn = tileset.debugColorizeTiles && !tile._debugColorizeTiles; + var debugColorizeTilesOn = (tileset.debugColorizeTiles && !tile._debugColorizeTiles) || defined(tileset._heatmap.tilePropertyName); var debugColorizeTilesOff = !tileset.debugColorizeTiles && tile._debugColorizeTiles; if (debugColorizeTilesOn) { + tileset._heatmap.colorize(tile, frameState); // Skipped if tileset._heatmap.tilePropertyName is undefined tile._debugColorizeTiles = true; tile.color = tile._debugColor; } else if (debugColorizeTilesOff) { @@ -1258,6 +1373,78 @@ define([ frameState.commandList = savedCommandList; }; + function isolateDigits(normalizedValue, numberOfDigits, leftShift) { + var scaled = normalizedValue * Math.pow(10, numberOfDigits); + var integer = parseInt(scaled); + return integer * Math.pow(10, leftShift); + } + + function priorityNormalizeAndClamp(value, minimum, maximum) { + return Math.max(CesiumMath.normalize(value, minimum, maximum) - CesiumMath.EPSILON7, 0.0); // Subtract epsilon since we only want decimal digits present in the output. + } + + /** + * Sets the priority of the tile based on distance and depth + * @private + */ + Cesium3DTile.prototype.updatePriority = function() { + var tileset = this.tileset; + var preferLeaves = tileset.preferLeaves; + var minimumPriority = tileset._minimumPriority; + var maximumPriority = tileset._maximumPriority; + + // Combine priority systems together by mapping them into a base 10 number where each priority controls a specific set of digits in the number. + // For number priorities, map them to a 0.xxxxx number then left shift it up into a set number of digits before the decimal point. Chop of the fractional part then left shift again into the position it needs to go. + // For blending number priorities, normalize them to 0-1 and interpolate to get a combined 0-1 number, then proceed as normal. + // Booleans can just be 0 or 10^leftshift. + // Think of digits as penalties since smaller numbers are higher priority. If a tile has some large quantity or has a flag raised it's (usually) penalized for it, expressed as a higher number for the digit. + // Priority number format: preloadFlightDigits(1) | foveatedDeferDigits(1) | foveatedDigits(4) | preloadProgressiveResolutionDigits(1) | preferredSortingDigits(4) . depthDigits(the decimal digits) + // Certain flags like preferLeaves will flip / turn off certain digits to get desired load order. + + // Setup leftShifts, digit counts, and scales (for booleans) + var digitsForANumber = 4; + var digitsForABoolean = 1; + + var preferredSortingLeftShift = 0; + var preferredSortingDigitsCount = digitsForANumber; + + var foveatedLeftShift = preferredSortingLeftShift + preferredSortingDigitsCount; + var foveatedDigitsCount = digitsForANumber; + + var preloadProgressiveResolutionLeftShift = foveatedLeftShift + foveatedDigitsCount; + var preloadProgressiveResolutionDigitsCount = digitsForABoolean; + var preloadProgressiveResolutionScale = Math.pow(10, preloadProgressiveResolutionLeftShift); + + var foveatedDeferLeftShift = preloadProgressiveResolutionLeftShift + preloadProgressiveResolutionDigitsCount; + var foveatedDeferDigitsCount = digitsForABoolean; + var foveatedDeferScale = Math.pow(10, foveatedDeferLeftShift); + + var preloadFlightLeftShift = foveatedDeferLeftShift + foveatedDeferDigitsCount; + var preloadFlightScale = Math.pow(10, preloadFlightLeftShift); + + // Compute the digits for each priority + var depthDigits = priorityNormalizeAndClamp(this._depth, minimumPriority.depth, maximumPriority.depth); + depthDigits = preferLeaves ? 1.0 - depthDigits : depthDigits; + + // Map 0-1 then convert to digit. Include a distance sort when doing non-skipLOD and replacement refinement, helps things like non-skipLOD photogrammetry + var useDistance = !tileset._skipLevelOfDetail && this.refine === Cesium3DTileRefine.REPLACE; + var normalizedPreferredSorting = useDistance ? priorityNormalizeAndClamp(this._priorityHolder._distanceToCamera, minimumPriority.distance, maximumPriority.distance) : + priorityNormalizeAndClamp(this._priorityReverseScreenSpaceError, minimumPriority.reverseScreenSpaceError, maximumPriority.reverseScreenSpaceError); + var preferredSortingDigits = isolateDigits(normalizedPreferredSorting, preferredSortingDigitsCount, preferredSortingLeftShift); + + var preloadProgressiveResolutionDigits = this._priorityProgressiveResolution ? 0 : preloadProgressiveResolutionScale; + + var normalizedFoveatedFactor = priorityNormalizeAndClamp(this._priorityHolder._foveatedFactor, minimumPriority.foveatedFactor, maximumPriority.foveatedFactor); + var foveatedDigits = isolateDigits(normalizedFoveatedFactor, foveatedDigitsCount, foveatedLeftShift); + + var foveatedDeferDigits = this.priorityDeferred ? foveatedDeferScale : 0; + + var preloadFlightDigits = tileset._pass === Cesium3DTilePass.PRELOAD_FLIGHT ? 0 : preloadFlightScale; + + // Get the final base 10 number + this._priority = depthDigits + preferredSortingDigits + preloadProgressiveResolutionDigits + foveatedDigits + foveatedDeferDigits + preloadFlightDigits; + }; + /** * @private */ diff --git a/Source/Scene/Cesium3DTilePass.js b/Source/Scene/Cesium3DTilePass.js new file mode 100644 index 00000000000..f454ebe0fb4 --- /dev/null +++ b/Source/Scene/Cesium3DTilePass.js @@ -0,0 +1,85 @@ +define([ + '../Core/Check', + '../Core/freezeObject', + './Cesium3DTilesetMostDetailedTraversal', + './Cesium3DTilesetTraversal' + ], function( + Check, + freezeObject, + Cesium3DTilesetMostDetailedTraversal, + Cesium3DTilesetTraversal) { + 'use strict'; + + /** + * The pass in which a 3D Tileset is updated. + * + * @private + */ + var Cesium3DTilePass = { + RENDER : 0, + PICK : 1, + SHADOW : 2, + PRELOAD : 3, + PRELOAD_FLIGHT : 4, + MOST_DETAILED_PRELOAD : 5, + MOST_DETAILED_PICK : 6, + NUMBER_OF_PASSES : 7 + }; + + var passOptions = new Array(Cesium3DTilePass.NUMBER_OF_PASSES); + + passOptions[Cesium3DTilePass.RENDER] = freezeObject({ + traversal : Cesium3DTilesetTraversal, + isRender : true, + requestTiles : true, + ignoreCommands : false + }); + + passOptions[Cesium3DTilePass.PICK] = freezeObject({ + traversal : Cesium3DTilesetTraversal, + isRender : false, + requestTiles : false, + ignoreCommands : false + }); + + passOptions[Cesium3DTilePass.SHADOW] = freezeObject({ + traversal : Cesium3DTilesetTraversal, + isRender : false, + requestTiles : true, + ignoreCommands : false + }); + + passOptions[Cesium3DTilePass.PRELOAD] = freezeObject({ + traversal : Cesium3DTilesetTraversal, + isRender : false, + requestTiles : true, + ignoreCommands : true + }); + + passOptions[Cesium3DTilePass.PRELOAD_FLIGHT] = freezeObject({ + traversal : Cesium3DTilesetTraversal, + isRender : false, + requestTiles : true, + ignoreCommands : true + }); + + passOptions[Cesium3DTilePass.MOST_DETAILED_PRELOAD] = freezeObject({ + traversal : Cesium3DTilesetMostDetailedTraversal, + isRender : false, + requestTiles : true, + ignoreCommands : true + }); + + passOptions[Cesium3DTilePass.MOST_DETAILED_PICK] = freezeObject({ + traversal : Cesium3DTilesetMostDetailedTraversal, + isRender : false, + requestTiles : false, + ignoreCommands : false + }); + + Cesium3DTilePass.getPassOptions = function(pass) { + return passOptions[pass]; + }; + + return freezeObject(Cesium3DTilePass); +}); diff --git a/Source/Scene/Cesium3DTilePassState.js b/Source/Scene/Cesium3DTilePassState.js new file mode 100644 index 00000000000..4ba7ff191ce --- /dev/null +++ b/Source/Scene/Cesium3DTilePassState.js @@ -0,0 +1,57 @@ +define([ + '../Core/Check' + ], function( + Check) { + 'use strict'; + + /** + * The state for a 3D Tiles update pass. + * + * @private + */ + function Cesium3DTilePassState(options) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('options', options); + Check.typeOf.number('options.pass', options.pass); + //>>includeEnd('debug'); + + /** + * The pass. + * + * @type {Cesium3DTilePass} + */ + this.pass = options.pass; + + /** + * An array of rendering commands to use instead of {@link FrameState.commandList} for the current pass. + * + * @type {DrawCommand[]} + */ + this.commandList = options.commandList; + + /** + * A camera to use instead of {@link FrameState.camera} for the current pass. + * + * @type {Camera} + */ + this.camera = options.camera; + + /** + * A culling volume to use instead of {@link FrameState.cullingVolume} for the current pass. + * + * @type {CullingVolume} + */ + this.cullingVolume = options.cullingVolume; + + /** + * A read-only property that indicates whether the pass is ready, i.e. all tiles needed by the pass are loaded. + * + * @type {Boolean} + * @readonly + * @default false + */ + this.ready = false; + } + + return Cesium3DTilePassState; +}); diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index dfea3829286..37d2182fe7a 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -30,9 +30,12 @@ define([ './Cesium3DTileColorBlendMode', './Cesium3DTileContentState', './Cesium3DTileOptimizations', + './Cesium3DTilePass', + './Cesium3DTilePassState', './Cesium3DTileRefine', - './Cesium3DTilesetAsyncTraversal', './Cesium3DTilesetCache', + './Cesium3DTilesetHeatmap', + './Cesium3DTilesetMostDetailedTraversal', './Cesium3DTilesetStatistics', './Cesium3DTilesetTraversal', './Cesium3DTileStyleEngine', @@ -78,9 +81,12 @@ define([ Cesium3DTileColorBlendMode, Cesium3DTileContentState, Cesium3DTileOptimizations, + Cesium3DTilePass, + Cesium3DTilePassState, Cesium3DTileRefine, - Cesium3DTilesetAsyncTraversal, Cesium3DTilesetCache, + Cesium3DTilesetHeatmap, + Cesium3DTilesetMostDetailedTraversal, Cesium3DTilesetStatistics, Cesium3DTilesetTraversal, Cesium3DTileStyleEngine, @@ -111,10 +117,21 @@ define([ * @param {Number} [options.maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement. * @param {Number} [options.maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset. * @param {Boolean} [options.cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes. + * @param {Boolean} [options.cullRequestsWhileMoving=true] Optimization option. Don't request tiles that will likely be unused when they come back because of the camera's movement. + * @param {Number} [options.cullRequestsWhileMovingMultiplier=60.0] Optimization option. Multiplier used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling. + * @param {Boolean} [options.preloadWhenHidden=false] Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. + * @param {Boolean} [options.preloadFlightDestinations=true] Optimization option. Preload tiles at the camera's flight destination while the camera is in flight. + * @param {Boolean} [options.preferLeaves=false] Optimization option. Prefer loading of leaves first. * @param {Boolean} [options.dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera. * @param {Number} [options.dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density. * @param {Number} [options.dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error. * @param {Number} [options.dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff. + * @param {Number} [options.progressiveResolutionHeightFraction=0.3] Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. + * @param {Boolean} [options.foveatedScreenSpaceError=true] Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the screen space error for tiles around the edge of the screen. Screen space error returns to normal once all the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. + * @param {Number} [options.foveatedConeSize=0.1] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and their screen space error. This is controlled by {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, disabling the effect. + * @param {Number} [options.foveatedMinimumScreenSpaceErrorRelaxation=0.0] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the starting screen space error relaxation for tiles outside the foveated cone. The screen space error will be raised starting with tileset value up to {@link Cesium3DTileset#maximumScreenSpaceError} based on the provided {@link Cesium3DTileset#foveatedInterpolationCallback}. + * @param {Cesium3DTileset~foveatedInterpolationCallback} [options.foveatedInterpolationCallback=Math.lerp] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how much to raise the screen space error for tiles outside the foveated cone, interpolating between {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation} and {@link Cesium3DTileset#maximumScreenSpaceError} + * @param {Number} [options.foveatedTimeDelay=0.2] Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how long in seconds to wait after the camera stops moving before deferred tiles start loading in. This time delay prevents requesting tiles around the edges of the screen when the camera is moving. Setting this to 0.0 will immediately request all tiles in any given view. * @param {Boolean} [options.skipLevelOfDetail=true] Optimization option. Determines if level of detail skipping should be applied during the traversal. * @param {Number} [options.baseScreenSpaceError=1024] When skipLevelOfDetail is true, the screen space error that must be reached before skipping levels of detail. * @param {Number} [options.skipScreenSpaceErrorFactor=16] When skipLevelOfDetail is true, a multiplier defining the minimum screen space error to skip. Used in conjunction with skipLevels to determine which tiles to load. @@ -130,6 +147,7 @@ define([ * @param {Number} [options.luminanceAtZenith=0.5] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map. * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting. * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps. + * @param {String} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. * @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. @@ -216,9 +234,50 @@ define([ this._modelMatrix = defined(options.modelMatrix) ? Matrix4.clone(options.modelMatrix) : Matrix4.clone(Matrix4.IDENTITY); this._statistics = new Cesium3DTilesetStatistics(); - this._statisticsLastRender = new Cesium3DTilesetStatistics(); - this._statisticsLastPick = new Cesium3DTilesetStatistics(); - this._statisticsLastAsync = new Cesium3DTilesetStatistics(); + this._statisticsLast = new Cesium3DTilesetStatistics(); + this._statisticsPerPass = new Array(Cesium3DTilePass.NUMBER_OF_PASSES); + + for (var i = 0; i < Cesium3DTilePass.NUMBER_OF_PASSES; ++i) { + this._statisticsPerPass[i] = new Cesium3DTilesetStatistics(); + } + + this._requestedTilesInFlight = []; + + this._maximumPriority = { foveatedFactor: -Number.MAX_VALUE, depth: -Number.MAX_VALUE, distance: -Number.MAX_VALUE, reverseScreenSpaceError: -Number.MAX_VALUE }; + this._minimumPriority = { foveatedFactor: Number.MAX_VALUE, depth: Number.MAX_VALUE, distance: Number.MAX_VALUE, reverseScreenSpaceError: Number.MAX_VALUE }; + this._heatmap = new Cesium3DTilesetHeatmap(options.debugHeatmapTilePropertyName); + + /** + * Optimization option. Don't request tiles that will likely be unused when they come back because of the camera's movement. + * + * @type {Boolean} + * @default true + */ + this.cullRequestsWhileMoving = defaultValue(options.cullRequestsWhileMoving, true); + + /** + * Optimization option. Multiplier used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling. + * + * @type {Number} + * @default 60.0 + */ + this.cullRequestsWhileMovingMultiplier = defaultValue(options.cullRequestsWhileMovingMultiplier, 60.0); + + /** + * Optimization option. If between (0.0, 0.5], tiles at or above the screen space error for the reduced screen resolution of progressiveResolutionHeightFraction*screenHeight will be prioritized first. This can help get a quick layer of tiles down while full resolution tiles continue to load. + * + * @type {Number} + * @default 0.3 + */ + this.progressiveResolutionHeightFraction = CesiumMath.clamp(defaultValue(options.progressiveResolutionHeightFraction, 0.3), 0.0, 0.5); + + /** + * Optimization option. Prefer loading of leaves first. + * + * @type {Boolean} + * @default false + */ + this.preferLeaves = defaultValue(options.preferLeaves, false); this._tilesLoaded = false; this._initialTilesLoaded = false; @@ -235,6 +294,23 @@ define([ this._clippingPlanesOriginMatrix = undefined; // Combines the above with any run-time transforms. this._clippingPlanesOriginMatrixDirty = true; + /** + * Preload tiles when tileset.show is false. Loads tiles as if the tileset is visible but does not render them. + * + * @type {Boolean} + * @default false + */ + this.preloadWhenHidden = defaultValue(options.preloadWhenHidden, false); + + /** + * Optimization option. Fetch tiles at the camera's flight destination while the camera is in flight. + * + * @type {Boolean} + * @default true + */ + this.preloadFlightDestinations = defaultValue(options.preloadFlightDestinations, true); + this._pass = undefined; // Cesium3DTilePass + /** * Optimization option. Whether the tileset should refine based on a dynamic screen space error. Tiles that are further * away will be rendered with lower detail than closer tiles. This improves performance by rendering fewer @@ -247,6 +323,36 @@ define([ */ this.dynamicScreenSpaceError = defaultValue(options.dynamicScreenSpaceError, false); + /** + * Optimization option. Prioritize loading tiles in the center of the screen by temporarily raising the + * screen space error for tiles around the edge of the screen. Screen space error returns to normal once all + * the tiles in the center of the screen as determined by the {@link Cesium3DTileset#foveatedConeSize} are loaded. + * + * @type {Boolean} + * @default true + */ + this.foveatedScreenSpaceError = defaultValue(options.foveatedScreenSpaceError, true); + this._foveatedConeSize = defaultValue(options.foveatedConeSize, 0.1); + this._foveatedMinimumScreenSpaceErrorRelaxation = defaultValue(options.foveatedMinimumScreenSpaceErrorRelaxation, 0.0); + + /** + * Gets a function that will update the foveated screen space error for a tile. + * + * @type {Cesium3DTileset~foveatedInterpolationCallback} A callback to control how much to raise the screen space error for tiles outside the foveated cone, interpolating between {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation} and {@link Cesium3DTileset#maximumScreenSpaceError}. + */ + this.foveatedInterpolationCallback = defaultValue(options.foveatedInterpolationCallback, CesiumMath.lerp); + + /** + * Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control + * how long in seconds to wait after the camera stops moving before deferred tiles start loading in. + * This time delay prevents requesting tiles around the edges of the screen when the camera is moving. + * Setting this to 0.0 will immediately request all tiles in any given view. + * + * @type {Number} + * @default 0.2 + */ + this.foveatedTimeDelay = defaultValue(options.foveatedTimeDelay, 0.2); + /** * A scalar that determines the density used to adjust the dynamic screen space error, similar to {@link Fog}. Increasing this * value has the effect of increasing the maximum screen space error for all tiles, but in a non-linear fashion. @@ -804,7 +910,7 @@ define([ credits = []; that._credits = credits; } - for (var i = 0; i < extraCredits.length; i++) { + for (var i = 0; i < extraCredits.length; ++i) { var credit = extraCredits[i]; credits.push(new Credit(credit.html, credit.showOnScreen)); } @@ -1316,6 +1422,53 @@ define([ } }, + /** + * Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the cone size that determines which tiles are deferred. + * Tiles that are inside this cone are loaded immediately. Tiles outside the cone are potentially deferred based on how far outside the cone they are and {@link Cesium3DTileset#foveatedInterpolationCallback} and {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation}. + * Setting this to 0.0 means the cone will be the line formed by the camera position and its view direction. Setting this to 1.0 means the cone encompasses the entire field of view of the camera, essentially disabling the effect. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Number} + * @default 0.3 + */ + foveatedConeSize : { + get : function() { + return this._foveatedConeSize; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.number.greaterThanOrEquals('foveatedConeSize', value, 0.0); + Check.typeOf.number.lessThanOrEquals('foveatedConeSize', value, 1.0); + //>>includeEnd('debug'); + + this._foveatedConeSize = value; + } + }, + + /** + * Optimization option. Used when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control the starting screen space error relaxation for tiles outside the foveated cone. + * The screen space error will be raised starting with this value up to {@link Cesium3DTileset#maximumScreenSpaceError} based on the provided {@link Cesium3DTileset#foveatedInterpolationCallback}. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Number} + * @default 0.0 + */ + foveatedMinimumScreenSpaceErrorRelaxation : { + get : function() { + return this._foveatedMinimumScreenSpaceErrorRelaxation; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.number.greaterThanOrEquals('foveatedMinimumScreenSpaceErrorRelaxation', value, 0.0); + Check.typeOf.number.lessThanOrEquals('foveatedMinimumScreenSpaceErrorRelaxation', value, this.maximumScreenSpaceError); + //>>includeEnd('debug'); + + this._foveatedMinimumScreenSpaceErrorRelaxation = value; + } + }, + /** * Returns the extras property at the top-level of the tileset JSON, which contains application specific metadata. * Returns undefined if extras does not exist. @@ -1552,6 +1705,7 @@ define([ } ++statistics.numberOfPendingRequests; + tileset._requestedTilesInFlight.push(tile); tile.contentReadyToProcessPromise.then(addToProcessingQueue(tileset, tile)); tile.contentReadyPromise.then(handleTileSuccess(tileset, tile)).otherwise(handleTileFailure(tileset, tile)); @@ -1561,7 +1715,89 @@ define([ return a._priority - b._priority; } - function requestTiles(tileset) { + /** + * Perform any pass invariant tasks here. Called after the render pass. + * @private + */ + Cesium3DTileset.prototype.postPassesUpdate = function(frameState) { + if (!this.ready) { + return; + } + + cancelOutOfViewRequests(this, frameState); + raiseLoadProgressEvent(this, frameState); + this._cache.unloadTiles(this, unloadTile); + this._cache.reset(); + + var statistics = this._statisticsPerPass[Cesium3DTilePass.RENDER]; + var credits = this._credits; + if (defined(credits) && statistics.selected !== 0) { + var length = credits.length; + for (var i = 0; i < length; ++i) { + frameState.creditDisplay.addCredit(credits[i]); + } + } + }; + + /** + * Perform any pass invariant tasks here. Called before any passes are executed. + * @private + */ + Cesium3DTileset.prototype.prePassesUpdate = function(frameState) { + if (!this.ready) { + return; + } + + processTiles(this, frameState); + + // Update clipping planes + var clippingPlanes = this._clippingPlanes; + this._clippingPlanesOriginMatrixDirty = true; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + clippingPlanes.update(frameState); + } + + if (!defined(this._loadTimestamp)) { + this._loadTimestamp = JulianDate.clone(frameState.time); + } + this._timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, 0.0); + + this._skipLevelOfDetail = this.skipLevelOfDetail && !defined(this._classificationType) && !this._disableSkipLevelOfDetail && !this._allTilesAdditive; + + if (this.dynamicScreenSpaceError) { + updateDynamicScreenSpaceError(this, frameState); + } + }; + + function cancelOutOfViewRequests(tileset, frameState) { + var requestedTilesInFlight = tileset._requestedTilesInFlight; + var removeCount = 0; + var length = requestedTilesInFlight.length; + for (var i = 0; i < length; ++i) { + var tile = requestedTilesInFlight[i]; + + // NOTE: This is framerate dependant so make sure the threshold check is small + var outOfView = (frameState.frameNumber - tile._touchedFrame) >= 1; + if (tile._contentState !== Cesium3DTileContentState.LOADING) { + // No longer fetching from host, don't need to track it anymore. Gets marked as LOADING in Cesium3DTile::requestContent(). + ++removeCount; + continue; + } else if (outOfView) { + // RequestScheduler will take care of cancelling it + tile._request.cancel(); + ++removeCount; + continue; + } + + if (removeCount > 0) { + requestedTilesInFlight[i - removeCount] = tile; + } + } + + requestedTilesInFlight.length -= removeCount; + } + + function requestTiles(tileset, isAsync) { // Sort requests by priority before making any requests. // This makes it less likely that requests will be cancelled after being issued. var requestedTiles = tileset._requestedTiles; @@ -1614,6 +1850,7 @@ define([ // external tileset when all the tiles are unloaded. tileset._statistics.incrementLoadCounts(tile.content); ++tileset._statistics.numberOfTilesWithContentReady; + ++tileset._statistics.numberOfLoadedTilesTotal; // Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added. tileset._cache.add(tile); @@ -1767,12 +2004,10 @@ define([ tileset._tileDebugLabels.update(frameState); } - function updateTiles(tileset, frameState) { + function updateTiles(tileset, frameState, isRender) { tileset._styleEngine.applyStyle(tileset, frameState); var statistics = tileset._statistics; - var passes = frameState.passes; - var isRender = passes.render; var commandList = frameState.commandList; var numberOfInitialCommands = commandList.length; var selectedTiles = tileset._selectedTiles; @@ -1919,10 +2154,6 @@ define([ tile.destroy(); } - function unloadTiles(tileset) { - tileset._cache.unloadTiles(tileset, unloadTile); - } - /** * Unloads all tiles that weren't selected the previous frame. This can be used to * explicitly manage the tile cache and reduce the total number of tiles loaded below @@ -1940,12 +2171,15 @@ define([ function raiseLoadProgressEvent(tileset, frameState) { var statistics = tileset._statistics; - var statisticsLast = tileset._statisticsLastRender; + var statisticsLast = tileset._statisticsLast; + var numberOfPendingRequests = statistics.numberOfPendingRequests; var numberOfTilesProcessing = statistics.numberOfTilesProcessing; var lastNumberOfPendingRequest = statisticsLast.numberOfPendingRequests; var lastNumberOfTilesProcessing = statisticsLast.numberOfTilesProcessing; + Cesium3DTilesetStatistics.clone(statistics, statisticsLast); + var progressChanged = (numberOfPendingRequests !== lastNumberOfPendingRequest) || (numberOfTilesProcessing !== lastNumberOfTilesProcessing); if (progressChanged) { @@ -1956,6 +2190,9 @@ define([ tileset._tilesLoaded = (statistics.numberOfPendingRequests === 0) && (statistics.numberOfTilesProcessing === 0) && (statistics.numberOfAttemptedRequests === 0); + // Events are raised (added to the afterRender queue) here since promises + // may resolve outside of the update loop that then raise events, e.g., + // model's readyPromise. if (progressChanged && tileset._tilesLoaded) { frameState.afterRender.push(function() { tileset.allTilesLoaded.raiseEvent(); @@ -1969,92 +2206,50 @@ define([ } } + function resetMinimumMaximum(tileset) { + tileset._heatmap.resetMinimumMaximum(); + tileset._minimumPriority.depth = Number.MAX_VALUE; + tileset._maximumPriority.depth = -Number.MAX_VALUE; + tileset._minimumPriority.foveatedFactor = Number.MAX_VALUE; + tileset._maximumPriority.foveatedFactor = -Number.MAX_VALUE; + tileset._minimumPriority.distance = Number.MAX_VALUE; + tileset._maximumPriority.distance = -Number.MAX_VALUE; + tileset._minimumPriority.reverseScreenSpaceError = Number.MAX_VALUE; + tileset._maximumPriority.reverseScreenSpaceError = -Number.MAX_VALUE; + } + /////////////////////////////////////////////////////////////////////////// - function update(tileset, frameState) { + function update(tileset, frameState, passStatistics, passOptions) { if (frameState.mode === SceneMode.MORPHING) { return false; } - if (!tileset.show || !tileset.ready) { + if (!tileset.ready) { return false; } - if (!defined(tileset._loadTimestamp)) { - tileset._loadTimestamp = JulianDate.clone(frameState.time); - } - - // Update clipping planes - var clippingPlanes = tileset._clippingPlanes; - tileset._clippingPlanesOriginMatrixDirty = true; - if (defined(clippingPlanes) && clippingPlanes.enabled) { - clippingPlanes.update(frameState); - } - - tileset._timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, tileset._loadTimestamp) * 1000, 0.0); - - tileset._skipLevelOfDetail = tileset.skipLevelOfDetail && !defined(tileset._classificationType) && !tileset._disableSkipLevelOfDetail && !tileset._allTilesAdditive; - - // Do out-of-core operations (new content requests, cache removal, - // process new tiles) only during the render pass. - var passes = frameState.passes; - var isRender = passes.render; - var isPick = passes.pick; - var isAsync = passes.asynchronous; - var statistics = tileset._statistics; statistics.clear(); - if (tileset.dynamicScreenSpaceError) { - updateDynamicScreenSpaceError(tileset, frameState); - } - - if (isRender) { - tileset._cache.reset(); - } + var isRender = passOptions.isRender; + // Resets the visibility check for each pass ++tileset._updatedVisibilityFrame; - var ready; + // Update any tracked min max values + resetMinimumMaximum(tileset); - if (isAsync) { - ready = Cesium3DTilesetAsyncTraversal.selectTiles(tileset, frameState); - } else { - ready = Cesium3DTilesetTraversal.selectTiles(tileset, frameState); - } + var ready = passOptions.traversal.selectTiles(tileset, frameState); - if (isRender || isAsync) { + if (passOptions.requestTiles) { requestTiles(tileset); } - if (isRender) { - processTiles(tileset, frameState); - } + updateTiles(tileset, frameState, isRender); - updateTiles(tileset, frameState); - - if (isRender) { - unloadTiles(tileset); - - // Events are raised (added to the afterRender queue) here since promises - // may resolve outside of the update loop that then raise events, e.g., - // model's readyPromise. - raiseLoadProgressEvent(tileset, frameState); - - if (statistics.selected !== 0) { - var credits = tileset._credits; - if (defined(credits)) { - var length = credits.length; - for (var i = 0; i < length; i++) { - frameState.creditDisplay.addCredit(credits[i]); - } - } - } - } - - // Update last statistics - var statisticsLast = isAsync ? tileset._statisticsLastAsync : (isPick ? tileset._statisticsLastPick : tileset._statisticsLastRender); - Cesium3DTilesetStatistics.clone(statistics, statisticsLast); + // Update pass statistics + Cesium3DTilesetStatistics.clone(statistics, passStatistics); return ready; } @@ -2063,14 +2258,54 @@ define([ * @private */ Cesium3DTileset.prototype.update = function(frameState) { - update(this, frameState); + this.updateForPass(frameState, frameState.tilesetPassState); }; /** * @private */ - Cesium3DTileset.prototype.updateAsync = function(frameState) { - return update(this, frameState); + Cesium3DTileset.prototype.updateForPass = function(frameState, tilesetPassState) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('frameState', frameState); + Check.typeOf.object('tilesetPassState', tilesetPassState); + //>>includeEnd('debug'); + + var pass = tilesetPassState.pass; + if ((pass === Cesium3DTilePass.PRELOAD && (!this.preloadWhenHidden || this.show)) || + (pass === Cesium3DTilePass.PRELOAD_FLIGHT & (!this.preloadFlightDestinations || !this.show))) { + return; + } + + var originalCommandList = frameState.commandList; + var originalCamera = frameState.camera; + var originalCullingVolume = frameState.cullingVolume; + + tilesetPassState.ready = false; + + var passOptions = Cesium3DTilePass.getPassOptions(pass); + var ignoreCommands = passOptions.ignoreCommands; + + var commandList = defaultValue(tilesetPassState.commandList, originalCommandList); + var commandStart = commandList.length; + + frameState.commandList = commandList; + frameState.camera = defaultValue(tilesetPassState.camera, originalCamera); + frameState.cullingVolume = defaultValue(tilesetPassState.cullingVolume, originalCullingVolume); + + var passStatistics = this._statisticsPerPass[pass]; + + if (this.show || ignoreCommands) { + this._pass = pass; + tilesetPassState.ready = update(this, frameState, passStatistics, passOptions); + } + + if (ignoreCommands) { + commandList.length = commandStart; + } + + frameState.commandList = originalCommandList; + frameState.camera = originalCamera; + frameState.cullingVolume = originalCullingVolume; }; /** @@ -2141,5 +2376,18 @@ define([ return destroyObject(this); }; + /** + * Optimization option. Used as a callback when {@link Cesium3DTileset#foveatedScreenSpaceError} is true to control how much to raise the screen space error for tiles outside the foveated cone, + * interpolating between {@link Cesium3DTileset#foveatedMinimumScreenSpaceErrorRelaxation} and {@link Cesium3DTileset#maximumScreenSpaceError}. + * + * @callback Cesium3DTileset~foveatedInterpolationCallback + * @default Math.lerp + * + * @param {Number} p The start value to interpolate. + * @param {Number} q The end value to interpolate. + * @param {Number} time The time of interpolation generally in the range [0.0, 1.0]. + * @returns {Number} The interpolated value. + */ + return Cesium3DTileset; }); diff --git a/Source/Scene/Cesium3DTilesetHeatmap.js b/Source/Scene/Cesium3DTilesetHeatmap.js new file mode 100644 index 00000000000..20a33fe378f --- /dev/null +++ b/Source/Scene/Cesium3DTilesetHeatmap.js @@ -0,0 +1,154 @@ +define([ + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/destroyObject', + '../Core/JulianDate', + '../Core/Math' + ], function( + Color, + defaultValue, + defined, + destroyObject, + JulianDate, + CesiumMath) { + 'use strict'; + + /** + * A heatmap colorizer in a {@link Cesium3DTileset}. A tileset can colorize its visible tiles in a heatmap style. + * + * @alias Cesium3DTilesetHeatmap + * @constructor + * @private + */ + function Cesium3DTilesetHeatmap(tilePropertyName) { + /** + * The tile variable to track for heatmap colorization. + * Tile's will be colorized relative to the other visible tile's values for this variable. + * + * @type {String} + */ + this.tilePropertyName = tilePropertyName; + + // Members that are updated every time a tile is colorized + this._minimum = Number.MAX_VALUE; + this._maximum = -Number.MAX_VALUE; + + // Members that are updated once every frame + this._previousMinimum = Number.MAX_VALUE; + this._previousMaximum = -Number.MAX_VALUE; + + // If defined uses a reference minimum maximum to colorize by instead of using last frames minimum maximum of rendered tiles. + // For example, the _loadTimestamp can get a better colorization using setReferenceMinimumMaximum in order to take accurate colored timing diffs of various scenes. + this._referenceMinimum = {}; + this._referenceMaximum = {}; + } + + /** + * Convert to a usable heatmap value (i.e. a number). Ensures that tile values that aren't stored as numbers can be used for colorization. + */ + function getHeatmapValue(tileValue, tilePropertyName) { + var value; + if (tilePropertyName === '_loadTimestamp') { + value = JulianDate.toDate(tileValue).getTime(); + } else { + value = tileValue; + } + return value; + } + + /** + * Sets the reference minimum and maximum for the variable name. Converted to numbers before they are stored. + * + * @param {Object} minimum The minimum reference value. + * @param {Object} maximum The maximum reference value. + * @param {String} tilePropertyName The tile variable that will use these reference values when it is colorized. + */ + Cesium3DTilesetHeatmap.prototype.setReferenceMinimumMaximum = function(minimum, maximum, tilePropertyName) { + this._referenceMinimum[tilePropertyName] = getHeatmapValue(minimum, tilePropertyName); + this._referenceMaximum[tilePropertyName] = getHeatmapValue(maximum, tilePropertyName); + }; + + function getHeatmapValueAndUpdateMinimumMaximum(heatmap, tile) { + var tilePropertyName = heatmap.tilePropertyName; + if (defined(tilePropertyName)) { + var heatmapValue = getHeatmapValue(tile[tilePropertyName], tilePropertyName); + if (!defined(heatmapValue)) { + heatmap.tilePropertyName = undefined; + return heatmapValue; + } + heatmap._maximum = Math.max(heatmapValue, heatmap._maximum); + heatmap._minimum = Math.min(heatmapValue, heatmap._minimum); + return heatmapValue; + } + } + + var heatmapColors = [new Color(0.100, 0.100, 0.100, 1), // Dark Gray + new Color(0.153, 0.278, 0.878, 1), // Blue + new Color(0.827, 0.231, 0.490, 1), // Pink + new Color(0.827, 0.188, 0.220, 1), // Red + new Color(1.000, 0.592, 0.259, 1), // Orange + new Color(1.000, 0.843, 0.000, 1)]; // Yellow + /** + * Colorize the tile in heat map style based on where it lies within the minimum maximum window. + * Heatmap colors are black, blue, pink, red, orange, yellow. 'Cold' or low numbers will be black and blue, 'Hot' or high numbers will be orange and yellow, + * @param {Cesium3DTile} tile The tile to colorize relative to last frame's minimum and maximum values of all visible tiles. + * @param {FrameState} frameState The frame state. + */ + Cesium3DTilesetHeatmap.prototype.colorize = function (tile, frameState) { + var tilePropertyName = this.tilePropertyName; + if (!defined(tilePropertyName) || !tile.contentAvailable || tile._selectedFrame !== frameState.frameNumber) { + return; + } + + var heatmapValue = getHeatmapValueAndUpdateMinimumMaximum(this, tile); + var minimum = this._previousMinimum; + var maximum = this._previousMaximum; + + if (minimum === Number.MAX_VALUE || maximum === -Number.MAX_VALUE) { + return; + } + + // Shift the minimum maximum window down to 0 + var shiftedMax = (maximum - minimum) + CesiumMath.EPSILON7; // Prevent divide by 0 + var shiftedValue = CesiumMath.clamp(heatmapValue - minimum, 0.0, shiftedMax); + + // Get position between minimum and maximum and convert that to a position in the color array + var zeroToOne = shiftedValue / shiftedMax; + var lastIndex = heatmapColors.length - 1.0; + var colorPosition = zeroToOne * lastIndex; + + // Take floor and ceil of the value to get the two colors to lerp between, lerp using the fractional portion + var colorPositionFloor = Math.floor(colorPosition); + var colorPositionCeil = Math.ceil(colorPosition); + var t = colorPosition - colorPositionFloor; + var colorZero = heatmapColors[colorPositionFloor]; + var colorOne = heatmapColors[colorPositionCeil]; + + // Perform the lerp + var finalColor = Color.clone(Color.WHITE); + finalColor.red = CesiumMath.lerp(colorZero.red, colorOne.red, t); + finalColor.green = CesiumMath.lerp(colorZero.green, colorOne.green, t); + finalColor.blue = CesiumMath.lerp(colorZero.blue, colorOne.blue, t); + tile._debugColor = finalColor; + }; + + /** + * Resets the tracked minimum maximum values for heatmap colorization. Happens right before tileset traversal. + */ + Cesium3DTilesetHeatmap.prototype.resetMinimumMaximum = function() { + // For heat map colorization + var tilePropertyName = this.tilePropertyName; + if (defined(tilePropertyName)) { + var referenceMinimum = this._referenceMinimum[tilePropertyName]; + var referenceMaximum = this._referenceMaximum[tilePropertyName]; + var useReference = defined(referenceMinimum) && defined(referenceMaximum); + this._previousMinimum = useReference ? referenceMinimum : this._minimum; + this._previousMaximum = useReference ? referenceMaximum : this._maximum; + this._minimum = Number.MAX_VALUE; + this._maximum = -Number.MAX_VALUE; + } + }; + + return Cesium3DTilesetHeatmap; +}); diff --git a/Source/Scene/Cesium3DTilesetAsyncTraversal.js b/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js similarity index 82% rename from Source/Scene/Cesium3DTilesetAsyncTraversal.js rename to Source/Scene/Cesium3DTilesetMostDetailedTraversal.js index 60496104113..2b7726ebfe8 100644 --- a/Source/Scene/Cesium3DTilesetAsyncTraversal.js +++ b/Source/Scene/Cesium3DTilesetMostDetailedTraversal.js @@ -14,15 +14,15 @@ define([ * * @private */ - function Cesium3DTilesetAsyncTraversal() { + function Cesium3DTilesetMostDetailedTraversal() { } - var asyncTraversal = { + var traversal = { stack : new ManagedArray(), stackMaximumLength : 0 }; - Cesium3DTilesetAsyncTraversal.selectTiles = function(tileset, frameState) { + Cesium3DTilesetMostDetailedTraversal.selectTiles = function(tileset, frameState) { tileset._selectedTiles.length = 0; tileset._requestedTiles.length = 0; tileset._hasMixedContent = false; @@ -36,11 +36,11 @@ define([ return ready; } - var stack = asyncTraversal.stack; + var stack = traversal.stack; stack.push(tileset.root); while (stack.length > 0) { - asyncTraversal.stackMaximumLength = Math.max(asyncTraversal.stackMaximumLength, stack.length); + traversal.stackMaximumLength = Math.max(traversal.stackMaximumLength, stack.length); var tile = stack.pop(); var add = (tile.refine === Cesium3DTileRefine.ADD); @@ -53,6 +53,7 @@ define([ if (add || (replace && !traverse)) { loadTile(tileset, tile); + touchTile(tileset, tile, frameState); selectDesiredTile(tileset, tile, frameState); if (!hasEmptyContent(tile) && !tile.contentAvailable) { @@ -61,10 +62,9 @@ define([ } visitTile(tileset); - touchTile(tileset, tile); } - asyncTraversal.stack.trim(asyncTraversal.stackMaximumLength); + traversal.stack.trim(traversal.stackMaximumLength); return ready; }; @@ -119,8 +119,13 @@ define([ } } - function touchTile(tileset, tile) { + function touchTile(tileset, tile, frameState) { + if (tile._touchedFrame === frameState.frameNumber) { + // Prevents another pass from touching the frame again + return; + } tileset._cache.touch(tile); + tile._touchedFrame = frameState.frameNumber; } function visitTile(tileset) { @@ -133,5 +138,5 @@ define([ } } - return Cesium3DTilesetAsyncTraversal; + return Cesium3DTilesetMostDetailedTraversal; }); diff --git a/Source/Scene/Cesium3DTilesetStatistics.js b/Source/Scene/Cesium3DTilesetStatistics.js index c77dc37728a..6ce9e39e334 100644 --- a/Source/Scene/Cesium3DTilesetStatistics.js +++ b/Source/Scene/Cesium3DTilesetStatistics.js @@ -18,6 +18,7 @@ define([ this.numberOfTilesProcessing = 0; this.numberOfTilesWithContentReady = 0; // Number of tiles with content loaded, does not include empty tiles this.numberOfTilesTotal = 0; // Number of tiles in tileset JSON (and other tileset JSON files as they are loaded) + this.numberOfLoadedTilesTotal = 0; // Running total of loaded tiles for the lifetime of the session // Features statistics this.numberOfFeaturesSelected = 0; // Number of features rendered this.numberOfFeaturesLoaded = 0; // Number of features in memory diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index 7b26cea5462..85945081135 100644 --- a/Source/Scene/Cesium3DTilesetTraversal.js +++ b/Source/Scene/Cesium3DTilesetTraversal.js @@ -1,13 +1,17 @@ define([ + '../Core/Cartesian3', '../Core/defined', '../Core/Intersect', '../Core/ManagedArray', + '../Core/Math', './Cesium3DTileOptimizationHint', './Cesium3DTileRefine' ], function( + Cartesian3, defined, Intersect, ManagedArray, + CesiumMath, Cesium3DTileOptimizationHint, Cesium3DTileRefine) { 'use strict'; @@ -85,7 +89,13 @@ define([ selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength); selectionTraversal.ancestorStack.trim(selectionTraversal.ancestorStackMaximumLength); - return true; + // Update the priority for any requests found during traversal + // Update after traversal so that min and max values can be used to normalize priority values + var requestedTiles = tileset._requestedTiles; + var length = requestedTiles.length; + for (var i = 0; i < length; ++i) { + requestedTiles[i].updatePriority(); + } }; function executeBaseTraversal(tileset, root, frameState) { @@ -192,28 +202,48 @@ define([ tile._touchedFrame = frameState.frameNumber; } - function getPriority(tileset, tile) { - // If skipLevelOfDetail is off try to load child tiles as soon as possible so that their parent can refine sooner. - // Additive tiles are prioritized by distance because it subjectively looks better. - // Replacement tiles are prioritized by screen space error. - // A tileset that has both additive and replacement tiles may not prioritize tiles as effectively since SSE and distance - // are different types of values. Maybe all priorities need to be normalized to 0-1 range. - if (tile.refine === Cesium3DTileRefine.ADD) { - return tile._distanceToCamera; + function updateMinimumMaximumPriority(tileset, tile) { + tileset._maximumPriority.distance = Math.max(tile._priorityHolder._distanceToCamera, tileset._maximumPriority.distance); + tileset._minimumPriority.distance = Math.min(tile._priorityHolder._distanceToCamera, tileset._minimumPriority.distance); + tileset._maximumPriority.depth = Math.max(tile._depth, tileset._maximumPriority.depth); + tileset._minimumPriority.depth = Math.min(tile._depth, tileset._minimumPriority.depth); + tileset._maximumPriority.foveatedFactor = Math.max(tile._priorityHolder._foveatedFactor, tileset._maximumPriority.foveatedFactor); + tileset._minimumPriority.foveatedFactor = Math.min(tile._priorityHolder._foveatedFactor, tileset._minimumPriority.foveatedFactor); + tileset._maximumPriority.reverseScreenSpaceError = Math.max(tile._priorityReverseScreenSpaceError, tileset._maximumPriority.reverseScreenSpaceError); + tileset._minimumPriority.reverseScreenSpaceError = Math.min(tile._priorityReverseScreenSpaceError, tileset._minimumPriority.reverseScreenSpaceError); + } + + function isOnScreenLongEnough(tileset, tile, frameState) { + // Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size. + if (!tileset.cullRequestsWhileMoving) { + return true; } - var parent = tile.parent; - var useParentScreenSpaceError = defined(parent) && (!skipLevelOfDetail(tileset) || (tile._screenSpaceError === 0.0) || parent.hasTilesetContent); - var screenSpaceError = useParentScreenSpaceError ? parent._screenSpaceError : tile._screenSpaceError; - var rootScreenSpaceError = tileset.root._screenSpaceError; - return rootScreenSpaceError - screenSpaceError; // Map higher SSE to lower values (e.g. root tile is highest priority) + + var sphere = tile.boundingSphere; + var diameter = Math.max(sphere.radius * 2.0, 1.0); + + var camera = frameState.camera; + var deltaMagnitude = camera.positionWCDeltaMagnitude !== 0.0 ? camera.positionWCDeltaMagnitude : camera.positionWCDeltaMagnitudeLastFrame; + var movementRatio = tileset.cullRequestsWhileMovingMultiplier * deltaMagnitude / diameter; // How do n frames of this movement compare to the tile's physical size. + return movementRatio < 1.0; } function loadTile(tileset, tile, frameState) { - if (hasUnloadedContent(tile) || tile.contentExpired) { - tile._requestedFrame = frameState.frameNumber; - tile._priority = getPriority(tileset, tile); - tileset._requestedTiles.push(tile); + if (tile._requestedFrame === frameState.frameNumber || (!hasUnloadedContent(tile) && !tile.contentExpired)) { + return; + } + + if (!isOnScreenLongEnough(tileset, tile, frameState)) { + return; + } + + var cameraHasNotStoppedMovingLongEnough = frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay; + if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) { + return; } + + tile._requestedFrame = frameState.frameNumber; + tileset._requestedTiles.push(tile); } function updateVisibility(tileset, tile, frameState) { @@ -285,11 +315,21 @@ define([ } function updateTile(tileset, tile, frameState) { + // Reset some of the tile's flags and re-evaluate visibility updateTileVisibility(tileset, tile, frameState); tile.updateExpiration(); + // Request priority + tile._wasMinPriorityChild = false; + tile._priorityHolder = tile; + updateMinimumMaximumPriority(tileset, tile); + + // SkipLOD tile._shouldSelect = false; tile._finalResolution = true; + } + + function updateTileAncestorContentLinks(tile, frameState) { tile._ancestorWithContent = undefined; tile._ancestorWithContentAvailable = undefined; @@ -300,7 +340,7 @@ define([ // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded. var hasContent = !hasUnloadedContent(parent) || (parent._requestedFrame === frameState.frameNumber); tile._ancestorWithContent = hasContent ? parent : parent._ancestorWithContent; - tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable; + tile._ancestorWithContentAvailable = parent.contentAvailable ? parent : parent._ancestorWithContentAvailable; // Links a descendant up to its contentAvailable ancestor as the traversal progresses. } } @@ -315,9 +355,10 @@ define([ function reachedSkippingThreshold(tileset, tile) { var ancestor = tile._ancestorWithContent; return !tileset.immediatelyLoadDesiredLevelOfDetail && - defined(ancestor) && + (tile._priorityProgressiveResolutionScreenSpaceErrorLeaf || + (defined(ancestor) && (tile._screenSpaceError < (ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor)) && - (tile._depth > (ancestor._depth + tileset.skipLevels)); + (tile._depth > (ancestor._depth + tileset.skipLevels)))); } function sortChildrenByDistanceToCamera(a, b) { @@ -348,14 +389,28 @@ define([ var refines = true; var anyChildrenVisible = false; + + // Determining min child + var minIndex = -1; + var minimumPriority = Number.MAX_VALUE; + + var child; for (i = 0; i < length; ++i) { - var child = children[i]; + child = children[i]; if (isVisible(child)) { stack.push(child); + if (child._foveatedFactor < minimumPriority) { + minIndex = i; + minimumPriority = child._foveatedFactor; + } anyChildrenVisible = true; } else if (checkRefines || tileset.loadSiblings) { // Keep non-visible children loaded since they are still needed before the parent can refine. // Or loadSiblings is true so always load tiles regardless of visibility. + if (child._foveatedFactor < minimumPriority) { + minIndex = i; + minimumPriority = child._foveatedFactor; + } loadTile(tileset, child, frameState); touchTile(tileset, child, frameState); } @@ -376,6 +431,21 @@ define([ refines = false; } + if (minIndex !== -1 && !skipLevelOfDetail(tileset) && replace) { + // An ancestor will hold the _foveatedFactor and _distanceToCamera for descendants between itself and its highest priority descendant. Siblings of a min children along the way use this ancestor as their priority holder as well. + // Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth. + var minPriorityChild = children[minIndex]; + minPriorityChild._wasMinPriorityChild = true; + var priorityHolder = (tile._wasMinPriorityChild || tile === tileset.root) && minimumPriority <= tile._priorityHolder._foveatedFactor ? tile._priorityHolder : tile; // This is where priority dependency chains are wired up or started anew. + priorityHolder._foveatedFactor = Math.min(minPriorityChild._foveatedFactor, priorityHolder._foveatedFactor); + priorityHolder._distanceToCamera = Math.min(minPriorityChild._distanceToCamera, priorityHolder._distanceToCamera); + + for (i = 0; i < length; ++i) { + child = children[i]; + child._priorityHolder = priorityHolder; + } + } + return refines; } @@ -423,6 +493,8 @@ define([ traversal.stackMaximumLength = Math.max(traversal.stackMaximumLength, stack.length); var tile = stack.pop(); + + updateTileAncestorContentLinks(tile, frameState); var baseTraversal = inBaseTraversal(tileset, tile, baseScreenSpaceError); var add = tile.refine === Cesium3DTileRefine.ADD; var replace = tile.refine === Cesium3DTileRefine.REPLACE; diff --git a/Source/Scene/FrameState.js b/Source/Scene/FrameState.js index ddb9518c662..d836aa26966 100644 --- a/Source/Scene/FrameState.js +++ b/Source/Scene/FrameState.js @@ -184,14 +184,7 @@ define([ * @type {Boolean} * @default false */ - offscreen : false, - - /** - * true if the primitive should update for an asynchronous pass, false otherwise. - * @type {Boolean} - * @default false - */ - asynchronous : false + offscreen : false }; /** @@ -378,6 +371,13 @@ define([ * @default false */ this.useLogDepth = false; + + /** + * Additional state used to update 3D Tilesets. + * + * @type {Cesium3DTilePassState} + */ + this.tilesetPassState = undefined; } /** diff --git a/Source/Scene/PrimitiveCollection.js b/Source/Scene/PrimitiveCollection.js index 5e7a9289960..1430c57d03d 100644 --- a/Source/Scene/PrimitiveCollection.js +++ b/Source/Scene/PrimitiveCollection.js @@ -370,6 +370,54 @@ define([ } }; + /** + * @private + */ + PrimitiveCollection.prototype.prePassesUpdate = function(frameState) { + var primitives = this._primitives; + // Using primitives.length in the loop is a temporary workaround + // to allow quadtree updates to add and remove primitives in + // update(). This will be changed to manage added and removed lists. + for (var i = 0; i < primitives.length; ++i) { + var primitive = primitives[i]; + if (defined(primitive.prePassesUpdate)) { + primitive.prePassesUpdate(frameState); + } + } + }; + + /** + * @private + */ + PrimitiveCollection.prototype.updateForPass = function(frameState, passState) { + var primitives = this._primitives; + // Using primitives.length in the loop is a temporary workaround + // to allow quadtree updates to add and remove primitives in + // update(). This will be changed to manage added and removed lists. + for (var i = 0; i < primitives.length; ++i) { + var primitive = primitives[i]; + if (defined(primitive.updateForPass)) { + primitive.updateForPass(frameState, passState); + } + } + }; + + /** + * @private + */ + PrimitiveCollection.prototype.postPassesUpdate = function(frameState) { + var primitives = this._primitives; + // Using primitives.length in the loop is a temporary workaround + // to allow quadtree updates to add and remove primitives in + // update(). This will be changed to manage added and removed lists. + for (var i = 0; i < primitives.length; ++i) { + var primitive = primitives[i]; + if (defined(primitive.postPassesUpdate)) { + primitive.postPassesUpdate(frameState); + } + } + }; + /** * Returns true if this object was destroyed; otherwise, false. *

diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index bf5621a29b8..cd5dea0dc71 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -53,6 +53,8 @@ define([ './BrdfLutGenerator', './Camera', './Cesium3DTileFeature', + './Cesium3DTilePass', + './Cesium3DTilePassState', './Cesium3DTileset', './CreditDisplay', './DebugCameraPrimitive', @@ -136,6 +138,8 @@ define([ BrdfLutGenerator, Camera, Cesium3DTileFeature, + Cesium3DTilePass, + Cesium3DTilePassState, Cesium3DTileset, CreditDisplay, DebugCameraPrimitive, @@ -174,10 +178,10 @@ define([ }; }; - function AsyncRayPick(ray, width, primitives) { + function MostDetailedRayPick(ray, width, tilesets) { this.ray = ray; this.width = width; - this.primitives = primitives; + this.tilesets = tilesets; this.ready = false; this.deferred = when.defer(); this.promise = this.deferred.promise; @@ -296,7 +300,7 @@ define([ this._primitives = new PrimitiveCollection(); this._groundPrimitives = new PrimitiveCollection(); - this._asyncRayPicks = []; + this._mostDetailedRayPicks = []; this._logDepthBuffer = context.fragmentDepth; this._logDepthBufferDirty = true; @@ -795,9 +799,22 @@ define([ near: 0.1 }); - this._view = new View(this, camera, viewport); this._pickOffscreenView = new View(this, pickOffscreenCamera, pickOffscreenViewport); + /** + * The camera view for the scene camera flight destination. Used for preloading flight destination tiles. + * @type {Camera} + * @private + */ + this.preloadFlightCamera = new Camera(this); + + /** + * The culling volume for the scene camera flight destination. Used for preloading flight destination tiles. + * @type {CullingVolume} + * @private + */ + this.preloadFlightCullingVolume = undefined; + /** * @private */ @@ -1686,6 +1703,30 @@ define([ } }; + var mostDetailedPreloadTilesetPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.MOST_DETAILED_PRELOAD + }); + + var mostDetailedPickTilesetPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.MOST_DETAILED_PICK + }); + + var renderTilesetPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.RENDER + }); + + var pickTilesetPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.PICK + }); + + var preloadTilesetPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.PRELOAD + }); + + var preloadFlightTilesetPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.PRELOAD_FLIGHT + }); + var scratchOccluderBoundingSphere = new BoundingSphere(); var scratchOccluder; @@ -1709,7 +1750,6 @@ define([ passes.depth = false; passes.postProcess = false; passes.offscreen = false; - passes.asynchronous = false; } function updateFrameNumber(scene, frameNumber, time) { @@ -1762,6 +1802,8 @@ define([ } clearPasses(frameState.passes); + + frameState.tilesetPassState = undefined; } var scratchCullingVolume = new CullingVolume(); @@ -3151,26 +3193,38 @@ define([ } } - function update(scene) { + function prePassesUpdate(scene) { + scene._jobScheduler.resetBudgets(); + var frameState = scene._frameState; + var primitives = scene.primitives; + primitives.prePassesUpdate(frameState); if (defined(scene.globe)) { scene.globe.update(frameState); } - updateAsyncRayPicks(scene); - + scene._pickPositionCacheDirty = true; frameState.creditDisplay.update(); + frameState.creditDisplay.beginFrame(); + } + + function postPassesUpdate(scene) { + var frameState = scene._frameState; + var primitives = scene.primitives; + primitives.postPassesUpdate(frameState); + + RequestScheduler.update(); + frameState.creditDisplay.endFrame(); } var scratchBackgroundColor = new Color(); function render(scene) { - scene._pickPositionCacheDirty = true; + var frameState = scene._frameState; var context = scene.context; var us = context.uniformState; - var frameState = scene._frameState; var view = scene._defaultView; scene._view = view; @@ -3178,6 +3232,7 @@ define([ updateFrameState(scene); frameState.passes.render = true; frameState.passes.postProcess = scene.postProcessStages.hasSelected; + frameState.tilesetPassState = renderTilesetPassState; var backgroundColor = defaultValue(scene.backgroundColor, Color.BLACK); if (scene._hdr) { @@ -3188,8 +3243,6 @@ define([ } frameState.backgroundColor = backgroundColor; - frameState.creditDisplay.beginFrame(); - scene.fog.update(frameState); us.update(frameState); @@ -3235,7 +3288,6 @@ define([ } } - frameState.creditDisplay.endFrame(); context.endFrame(); } @@ -3258,13 +3310,20 @@ define([ * @private */ Scene.prototype.render = function(time) { + /** + * + * Pre passes update. Execute any pass invariant code that should run before the passes here. + * + */ + this._preUpdate.raiseEvent(this, time); + + var frameState = this._frameState; + if (!defined(time)) { time = JulianDate.now(); } - var frameState = this._frameState; - this._jobScheduler.resetBudgets(); - + // Determine if shouldRender var cameraChanged = this._view.checkForCameraUpdates(this); var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || this._logDepthBufferDirty || this._hdrDirty || (this.mode === SceneMode.MORPHING); if (!shouldRender && defined(this.maximumRenderTimeChange) && defined(this._lastRenderTime)) { @@ -3282,20 +3341,34 @@ define([ updateFrameNumber(this, frameNumber, time); } - // Update - this._preUpdate.raiseEvent(this, time); - tryAndCatchError(this, update); + tryAndCatchError(this, prePassesUpdate); + + /** + * + * Passes update. Add any passes here + * + */ + tryAndCatchError(this, updateMostDetailedRayPicks); + tryAndCatchError(this, updatePreloadPass); + tryAndCatchError(this, updatePreloadFlightPass); + this._postUpdate.raiseEvent(this, time); if (shouldRender) { - // Render this._preRender.raiseEvent(this, time); tryAndCatchError(this, render); - - RequestScheduler.update(); } + /** + * + * Post passes update. Execute any pass invariant code that should run after the passes here. + * + */ updateDebugShowFramesPerSecond(this, shouldRender); + tryAndCatchError(this, postPassesUpdate); + + // Often used to trigger events (so don't want in trycatch) that the user might be subscribed to. Things like the tile load events, ready promises, etc. + // We don't want those events to resolve during the render loop because the events might add new primitives callAfterRenderFunctions(this); if (shouldRender) { @@ -3481,6 +3554,7 @@ define([ frameState.cullingVolume = getPickCullingVolume(this, drawingBufferPosition, rectangleWidth, rectangleHeight, viewport); frameState.invertClassification = false; frameState.passes.pick = true; + frameState.tilesetPassState = pickTilesetPassState; us.update(frameState); @@ -3522,6 +3596,7 @@ define([ frameState.passes.pick = true; frameState.passes.depth = true; frameState.cullingVolume = getPickCullingVolume(scene, drawingBufferPosition, 1, 1, viewport); + frameState.tilesetPassState = pickTilesetPassState; updateEnvironment(scene); environmentState.renderTranslucentDepthForPick = true; @@ -3796,6 +3871,29 @@ define([ }); }; + function updatePreloadPass(scene) { + var frameState = scene._frameState; + preloadTilesetPassState.camera = frameState.camera; + preloadTilesetPassState.cullingVolume = frameState.cullingVolume; + + var primitives = scene.primitives; + primitives.updateForPass(frameState, preloadTilesetPassState); + } + + function updatePreloadFlightPass(scene) { + var frameState = scene._frameState; + var camera = frameState.camera; + if (!camera.hasCurrentFlight()) { + return; + } + + preloadFlightTilesetPassState.camera = scene.preloadFlightCamera; + preloadFlightTilesetPassState.cullingVolume = scene.preloadFlightCullingVolume; + + var primitives = scene.primitives; + primitives.updateForPass(frameState, preloadFlightTilesetPassState); + } + var scratchRight = new Cartesian3(); var scratchUp = new Cartesian3(); @@ -3811,84 +3909,78 @@ define([ camera.right = right; camera.frustum.width = defaultValue(width, scene.pickOffscreenDefaultWidth); + return camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC); } - function updateAsyncRayPick(scene, asyncRayPick) { - var context = scene._context; - var uniformState = context.uniformState; + function updateMostDetailedRayPick(scene, rayPick) { var frameState = scene._frameState; - var view = scene._pickOffscreenView; - scene._view = view; - - var ray = asyncRayPick.ray; - var width = asyncRayPick.width; - var primitives = asyncRayPick.primitives; - - updateOffscreenCameraFromRay(scene, ray, width, view.camera); - - updateFrameState(scene); - frameState.passes.offscreen = true; - frameState.passes.asynchronous = true; + var ray = rayPick.ray; + var width = rayPick.width; + var tilesets = rayPick.tilesets; - uniformState.update(frameState); + var camera = scene._pickOffscreenView.camera; + var cullingVolume = updateOffscreenCameraFromRay(scene, ray, width, camera); - var commandList = frameState.commandList; - var commandsLength = commandList.length; + var tilesetPassState = mostDetailedPreloadTilesetPassState; + tilesetPassState.camera = camera; + tilesetPassState.cullingVolume = cullingVolume; var ready = true; - var primitivesLength = primitives.length; - for (var i = 0; i < primitivesLength; ++i) { - var primitive = primitives[i]; - if (primitive.show && scene.primitives.contains(primitive)) { - // Only update primitives that are still contained in the scene's primitive collection and are still visible - // Update primitives continually until all primitives are ready. This way tiles are never removed from the cache. - var primitiveReady = primitive.updateAsync(frameState); - ready = (ready && primitiveReady); + var tilesetsLength = tilesets.length; + for (var i = 0; i < tilesetsLength; ++i) { + var tileset = tilesets[i]; + if (tileset.show && scene.primitives.contains(tileset)) { + // Only update tilesets that are still contained in the scene's primitive collection and are still visible + // Update tilesets continually until all tilesets are ready. This way tiles are never removed from the cache. + tileset.updateForPass(frameState, tilesetPassState); + ready = (ready && tilesetPassState.ready); } } - // Ignore commands pushed during asynchronous pass - commandList.length = commandsLength; - - scene._view = scene._defaultView; - if (ready) { - asyncRayPick.deferred.resolve(); + rayPick.deferred.resolve(); } return ready; } - function updateAsyncRayPicks(scene) { + function updateMostDetailedRayPicks(scene) { // Modifies array during iteration - var asyncRayPicks = scene._asyncRayPicks; - for (var i = 0; i < asyncRayPicks.length; ++i) { - if (updateAsyncRayPick(scene, asyncRayPicks[i])) { - asyncRayPicks.splice(i--, 1); + var rayPicks = scene._mostDetailedRayPicks; + for (var i = 0; i < rayPicks.length; ++i) { + if (updateMostDetailedRayPick(scene, rayPicks[i])) { + rayPicks.splice(i--, 1); } } } - function launchAsyncRayPick(scene, ray, objectsToExclude, width, callback) { - var asyncPrimitives = []; - var primitives = scene.primitives; + function getTilesets(primitives, objectsToExclude, tilesets) { var length = primitives.length; for (var i = 0; i < length; ++i) { var primitive = primitives.get(i); - if ((primitive instanceof Cesium3DTileset) && primitive.show) { - if (!defined(objectsToExclude) || objectsToExclude.indexOf(primitive) === -1) { - asyncPrimitives.push(primitive); + if (primitive.show) { + if ((primitive instanceof Cesium3DTileset)) { + if (!defined(objectsToExclude) || objectsToExclude.indexOf(primitive) === -1) { + tilesets.push(primitive); + } + } else if (primitive instanceof PrimitiveCollection) { + getTilesets(primitive, objectsToExclude, tilesets); } } } - if (asyncPrimitives.length === 0) { + } + + function launchMostDetailedRayPick(scene, ray, objectsToExclude, width, callback) { + var tilesets = []; + getTilesets(scene.primitives, objectsToExclude, tilesets); + if (tilesets.length === 0) { return when.resolve(callback()); } - var asyncRayPick = new AsyncRayPick(ray, width, asyncPrimitives); - scene._asyncRayPicks.push(asyncRayPick); - return asyncRayPick.promise.then(function() { + var rayPick = new MostDetailedRayPick(ray, width, tilesets); + scene._mostDetailedRayPicks.push(rayPick); + return rayPick.promise.then(function() { return callback(); }); } @@ -3902,7 +3994,7 @@ define([ (objectsToExclude.indexOf(object.id) > -1); } - function getRayIntersection(scene, ray, objectsToExclude, width, requirePosition, asynchronous) { + function getRayIntersection(scene, ray, objectsToExclude, width, requirePosition, mostDetailed) { var context = scene._context; var uniformState = context.uniformState; var frameState = scene._frameState; @@ -3922,7 +4014,12 @@ define([ frameState.invertClassification = false; frameState.passes.pick = true; frameState.passes.offscreen = true; - frameState.passes.asynchronous = asynchronous; + + if (mostDetailed) { + frameState.tilesetPassState = mostDetailedPickTilesetPassState; + } else { + frameState.tilesetPassState = pickTilesetPassState; + } uniformState.update(frameState); @@ -3961,22 +4058,22 @@ define([ } } - function getRayIntersections(scene, ray, limit, objectsToExclude, width, requirePosition, asynchronous) { + function getRayIntersections(scene, ray, limit, objectsToExclude, width, requirePosition, mostDetailed) { var pickCallback = function() { - return getRayIntersection(scene, ray, objectsToExclude, width, requirePosition, asynchronous); + return getRayIntersection(scene, ray, objectsToExclude, width, requirePosition, mostDetailed); }; return drillPick(limit, pickCallback); } - function pickFromRay(scene, ray, objectsToExclude, width, requirePosition, asynchronous) { - var results = getRayIntersections(scene, ray, 1, objectsToExclude, width, requirePosition, asynchronous); + function pickFromRay(scene, ray, objectsToExclude, width, requirePosition, mostDetailed) { + var results = getRayIntersections(scene, ray, 1, objectsToExclude, width, requirePosition, mostDetailed); if (results.length > 0) { return results[0]; } } - function drillPickFromRay(scene, ray, limit, objectsToExclude, width, requirePosition, asynchronous) { - return getRayIntersections(scene, ray, limit, objectsToExclude, width, requirePosition, asynchronous); + function drillPickFromRay(scene, ray, limit, objectsToExclude, width, requirePosition, mostDetailed) { + return getRayIntersections(scene, ray, limit, objectsToExclude, width, requirePosition, mostDetailed); } function deferPromiseUntilPostRender(scene, promise) { @@ -4075,7 +4172,7 @@ define([ var that = this; ray = Ray.clone(ray); objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude; - return deferPromiseUntilPostRender(this, launchAsyncRayPick(this, ray, objectsToExclude, width, function() { + return deferPromiseUntilPostRender(this, launchMostDetailedRayPick(this, ray, objectsToExclude, width, function() { return pickFromRay(that, ray, objectsToExclude, width, false, true); })); }; @@ -4104,7 +4201,7 @@ define([ var that = this; ray = Ray.clone(ray); objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude; - return deferPromiseUntilPostRender(this, launchAsyncRayPick(this, ray, objectsToExclude, width, function() { + return deferPromiseUntilPostRender(this, launchMostDetailedRayPick(this, ray, objectsToExclude, width, function() { return drillPickFromRay(that, ray, limit, objectsToExclude, width, false, true); })); }; @@ -4145,7 +4242,7 @@ define([ function sampleHeightMostDetailed(scene, cartographic, objectsToExclude, width) { var ray = getRayForSampleHeight(scene, cartographic); - return launchAsyncRayPick(scene, ray, objectsToExclude, width, function() { + return launchMostDetailedRayPick(scene, ray, objectsToExclude, width, function() { var pickResult = pickFromRay(scene, ray, objectsToExclude, width, true, true); if (defined(pickResult)) { return getHeightFromCartesian(scene, pickResult.position); @@ -4155,7 +4252,7 @@ define([ function clampToHeightMostDetailed(scene, cartesian, objectsToExclude, width, result) { var ray = getRayForClampToHeight(scene, cartesian); - return launchAsyncRayPick(scene, ray, objectsToExclude, width, function() { + return launchMostDetailedRayPick(scene, ray, objectsToExclude, width, function() { var pickResult = pickFromRay(scene, ray, objectsToExclude, width, true, true); if (defined(pickResult)) { return Cartesian3.clone(pickResult.position, result); diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index bca09a0834c..06b9d24d5f9 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -8,6 +8,7 @@ define([ '../../Core/ScreenSpaceEventType', '../../Scene/Cesium3DTileColorBlendMode', '../../Scene/Cesium3DTileFeature', + '../../Scene/Cesium3DTilePass', '../../Scene/Cesium3DTileset', '../../Scene/Cesium3DTileStyle', '../../Scene/PerformanceDisplay', @@ -22,6 +23,7 @@ define([ ScreenSpaceEventType, Cesium3DTileColorBlendMode, Cesium3DTileFeature, + Cesium3DTilePass, Cesium3DTileset, Cesium3DTileStyle, PerformanceDisplay, @@ -71,7 +73,8 @@ define([ return ''; } - var statistics = isPick ? tileset._statisticsLastPick : tileset._statisticsLastRender; + var statistics = isPick ? tileset._statisticsPerPass[Cesium3DTilePass.PICK] : + tileset._statisticsPerPass[Cesium3DTilePass.RENDER]; // Since the pick pass uses a smaller frustum around the pixel of interest, // the statistics will be different than the normal render pass. diff --git a/Specs/Cesium3DTilesTester.js b/Specs/Cesium3DTilesTester.js index 8a959d8cf1d..af0aae8a1ba 100644 --- a/Specs/Cesium3DTilesTester.js +++ b/Specs/Cesium3DTilesTester.js @@ -114,6 +114,7 @@ define([ Cesium3DTilesTester.loadTileset = function(scene, url, options) { options = defaultValue(options, {}); options.url = url; + options.cullRequestsWhileMoving = defaultValue(options.cullRequestsWhileMoving, false); // Load all visible tiles var tileset = scene.primitives.add(new Cesium3DTileset(options)); @@ -162,8 +163,8 @@ define([ }); }; - Cesium3DTilesTester.resolvesReadyPromise = function(scene, url) { - return Cesium3DTilesTester.loadTileset(scene, url).then(function(tileset) { + Cesium3DTilesTester.resolvesReadyPromise = function(scene, url, options) { + return Cesium3DTilesTester.loadTileset(scene, url, options).then(function(tileset) { var content = tileset.root.content; return content.readyPromise.then(function(content) { expect(content).toBeDefined(); @@ -171,8 +172,8 @@ define([ }); }; - Cesium3DTilesTester.tileDestroys = function(scene, url) { - return Cesium3DTilesTester.loadTileset(scene, url).then(function(tileset) { + Cesium3DTilesTester.tileDestroys = function(scene, url, options) { + return Cesium3DTilesTester.loadTileset(scene, url, options).then(function(tileset) { var content = tileset.root.content; expect(content.isDestroyed()).toEqual(false); scene.primitives.remove(tileset); diff --git a/Specs/Core/MathSpec.js b/Specs/Core/MathSpec.js index c2ec6b6d357..164daa2717e 100644 --- a/Specs/Core/MathSpec.js +++ b/Specs/Core/MathSpec.js @@ -77,6 +77,27 @@ defineSuite([ expect(CesiumMath.fromSNorm(255.0 / 2)).toEqual(0.0); }); + ////////////////////////////////////////////////////////////////////// + it('normalize 0 with max 10 min -10', function() { + expect(CesiumMath.normalize(0, -10, 10)).toEqual(0.5); + }); + + it('normalize 10 with max 10 min -10', function() { + expect(CesiumMath.normalize(10, -10, 10)).toEqual(1.0); + }); + + it('normalize -10 with max 10 min -10', function() { + expect(CesiumMath.normalize(-10, -10, 10)).toEqual(0.0); + }); + + it('normalize -10.0001 with max 10 min -10', function() { + expect(CesiumMath.normalize(-10.0001, -10, 10)).toEqual(0.0); + }); + + it('normalize 10.00001 with max 10 min -10', function() { + expect(CesiumMath.normalize(10.00001, -10, 10)).toEqual(1.0); + }); + ////////////////////////////////////////////////////////////////////// it('cosh', function() { diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js index d01723d7e39..bcc1f309644 100644 --- a/Specs/Core/RequestSchedulerSpec.js +++ b/Specs/Core/RequestSchedulerSpec.js @@ -679,21 +679,23 @@ defineSuite([ }); } - var requestToCancel = createRequest(); - RequestScheduler.request(createRequest()); - RequestScheduler.request(createRequest()); - RequestScheduler.request(requestToCancel); + var requests = [createRequest(), + createRequest(), + createRequest()]; + RequestScheduler.request(requests[0]); + RequestScheduler.request(requests[1]); + RequestScheduler.request(requests[2]); RequestScheduler.update(); - expect(console.log).toHaveBeenCalledWith('Number of attempted requests: 3'); - expect(console.log).toHaveBeenCalledWith('Number of active requests: 3'); - deferreds[0].reject(); - requestToCancel.cancel(); + requests[0].cancel(); + requests[1].cancel(); + requests[2].cancel(); RequestScheduler.update(); - expect(console.log).toHaveBeenCalledWith('Number of cancelled requests: 1'); - expect(console.log).toHaveBeenCalledWith('Number of cancelled active requests: 1'); + expect(console.log).toHaveBeenCalledWith('Number of attempted requests: 3'); + expect(console.log).toHaveBeenCalledWith('Number of cancelled requests: 3'); + expect(console.log).toHaveBeenCalledWith('Number of cancelled active requests: 2'); expect(console.log).toHaveBeenCalledWith('Number of failed requests: 1'); var length = deferreds.length; diff --git a/Specs/Scene/CameraSpec.js b/Specs/Scene/CameraSpec.js index 819ba609bf0..1234989df92 100644 --- a/Specs/Scene/CameraSpec.js +++ b/Specs/Scene/CameraSpec.js @@ -75,6 +75,7 @@ defineSuite([ maximumZoomDistance: 5906376272000.0 // distance from the Sun to Pluto in meters. }; this.camera = undefined; + this.preloadFlightCamera = undefined; this.context = { drawingBufferWidth : 1024, drawingBufferHeight : 768 @@ -99,6 +100,8 @@ defineSuite([ camera.minimumZoomDistance = 0.0; scene.camera = camera; + scene.preloadFlightCamera = Camera.clone(camera); + camera._scene = scene; scene.mapMode2D = MapMode2D.INFINITE_2D; }); @@ -3059,4 +3062,20 @@ defineSuite([ expect(Cartesian3.magnitude(camera.rightWC)).toEqualEpsilon(1.0, CesiumMath.EPSILON15); expect(Cartesian3.magnitude(camera.upWC)).toEqualEpsilon(1.0, CesiumMath.EPSILON15); }); + + it('get camera deltas', function() { + camera._updateCameraChanged(); + expect(camera.positionWCDeltaMagnitude).toEqual(0); + expect(camera.positionWCDeltaMagnitudeLastFrame).toEqual(0); + + camera.moveUp(moveAmount); + + camera._updateCameraChanged(); + expect(camera.positionWCDeltaMagnitude).toEqualEpsilon(moveAmount, CesiumMath.EPSILON10); + expect(camera.positionWCDeltaMagnitudeLastFrame).toEqual(0); + + camera._updateCameraChanged(); + expect(camera.positionWCDeltaMagnitude).toEqual(0); + expect(camera.positionWCDeltaMagnitudeLastFrame).toEqualEpsilon(moveAmount, CesiumMath.EPSILON10); + }); }); diff --git a/Specs/Scene/Cesium3DTilePassStateSpec.js b/Specs/Scene/Cesium3DTilePassStateSpec.js new file mode 100644 index 00000000000..40dce19a96a --- /dev/null +++ b/Specs/Scene/Cesium3DTilePassStateSpec.js @@ -0,0 +1,47 @@ +defineSuite([ + 'Scene/Cesium3DTilePassState', + 'Scene/Cesium3DTilePass' + ], function( + Cesium3DTilePassState, + Cesium3DTilePass) { + 'use strict'; + + it('sets default values', function() { + var passState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.RENDER + }); + expect(passState.pass).toBe(Cesium3DTilePass.RENDER); + expect(passState.commandList).toBeUndefined(); + expect(passState.camera).toBeUndefined(); + expect(passState.cullingVolume).toBeUndefined(); + expect(passState.ready).toBe(false); + }); + + it('constructed with options', function() { + var mockCommandList = []; + var mockCamera = {}; + var mockCullingVolume = {}; + var passState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.RENDER, + commandList : mockCommandList, + camera : mockCamera, + cullingVolume : mockCullingVolume + }); + expect(passState.pass).toBe(Cesium3DTilePass.RENDER); + expect(passState.commandList).toBe(mockCommandList); + expect(passState.camera).toBe(mockCamera); + expect(passState.cullingVolume).toBe(mockCullingVolume); + }); + + it('throws if options is undefined', function() { + expect(function() { + return new Cesium3DTilePassState(); + }).toThrowDeveloperError(); + }); + + it('throws if options.pass is undefined', function() { + expect(function() { + return new Cesium3DTilePassState({}); + }).toThrowDeveloperError(); + }); +}); diff --git a/Specs/Scene/Cesium3DTileSpec.js b/Specs/Scene/Cesium3DTileSpec.js index cd5661d1fd2..32e70a9132f 100644 --- a/Specs/Scene/Cesium3DTileSpec.js +++ b/Specs/Scene/Cesium3DTileSpec.js @@ -9,6 +9,7 @@ defineSuite([ 'Core/Rectangle', 'Core/Transforms', 'Scene/Cesium3DTileRefine', + 'Scene/Cesium3DTilesetHeatmap', 'Scene/TileBoundingRegion', 'Scene/TileOrientedBoundingBox', 'Specs/createScene' @@ -23,6 +24,7 @@ defineSuite([ Rectangle, Transforms, Cesium3DTileRefine, + Cesium3DTilesetHeatmap, TileBoundingRegion, TileOrientedBoundingBox, createScene) { @@ -116,7 +118,8 @@ defineSuite([ debugShowBoundingVolume : true, debugShowViewerRequestVolume : true, modelMatrix : Matrix4.IDENTITY, - _geometricError : 2 + _geometricError : 2, + _heatmap : new Cesium3DTilesetHeatmap() }; var centerLongitude = -1.31968; @@ -354,4 +357,47 @@ defineSuite([ expect(tile._debugViewerRequestVolume).toBeDefined(); }); }); + + it('updates priority', function() { + var tile1 = new Cesium3DTile(mockTileset, '/some_url', tileWithBoundingSphere, undefined); + tile1._priorityHolder = tile1; + tile1._foveatedFactor = 0.0; + tile1._distanceToCamera = 1.0; + tile1._depth = 0.0; + tile1._priorityProgressiveResolution = true; + + var tile2 = new Cesium3DTile(mockTileset, '/some_url', tileWithBoundingSphere, undefined); + tile2._priorityHolder = tile1; + tile2._foveatedFactor = 1.0; // foveatedFactor (when considered for priority in certain modes) is actually 0 since its linked up to tile1 + tile2._distanceToCamera = 0.0; + tile2._depth = 1.0; + tile2._priorityProgressiveResolution = true; + + mockTileset._minimumPriority = { depth: 0.0, distance: 0.0, foveatedFactor: 0.0 }; + mockTileset._maximumPriority = { depth: 1.0, distance: 1.0, foveatedFactor: 1.0 }; + + tile1.updatePriority(); + tile2.updatePriority(); + + var nonPreloadFlightPenalty = 10000000000.0; + var tile1ExpectedPriority = nonPreloadFlightPenalty + 0.0; + var tile2ExpectedPriority = nonPreloadFlightPenalty + 1.0; + expect(CesiumMath.equalsEpsilon(tile1._priority, tile1ExpectedPriority, CesiumMath.EPSILON2)).toBe(true); + expect(CesiumMath.equalsEpsilon(tile2._priority, tile2ExpectedPriority, CesiumMath.EPSILON2)).toBe(true); + + // Penalty for not being a progressive resolution + tile2._priorityProgressiveResolution = false; + tile2.updatePriority(); + var nonProgressiveResoutionPenalty = 100000000.0; + expect(tile2._priority).toBeGreaterThan(nonProgressiveResoutionPenalty); + tile2._priorityProgressiveResolution = true; + + // Penalty for being a foveated deferral + tile2.priorityDeferred = true; + tile2.updatePriority(); + var foveatedDeferralPenalty = 10000000.0; + expect(tile2._priority).toBeGreaterThanOrEqualTo(foveatedDeferralPenalty); + tile2._priorityDeferred = false; + }); + }, 'WebGL'); diff --git a/Specs/Scene/Cesium3DTilesetHeatmapSpec.js b/Specs/Scene/Cesium3DTilesetHeatmapSpec.js new file mode 100644 index 00000000000..e9095f5af0a --- /dev/null +++ b/Specs/Scene/Cesium3DTilesetHeatmapSpec.js @@ -0,0 +1,126 @@ +defineSuite([ + 'Scene/Cesium3DTilesetHeatmap', + 'Scene/Cesium3DTile', + 'Scene/Cesium3DTileset', + 'Core/clone', + 'Core/Color', + 'Core/JulianDate', + 'Core/Math', + 'Core/Matrix4', + 'Scene/Cesium3DTileContentState', + 'Specs/createScene' + ], function( + Cesium3DTilesetHeatmap, + Cesium3DTile, + Cesium3DTileset, + clone, + Color, + JulianDate, + CesiumMath, + Matrix4, + Cesium3DTileContentState, + createScene) { + 'use strict'; + + var tileWithBoundingSphere = { + geometricError : 1, + refine : 'REPLACE', + children : [], + boundingVolume : { + sphere: [0.0, 0.0, 0.0, 5.0] + } + }; + + var mockTileset = { + debugShowBoundingVolume : true, + debugShowViewerRequestVolume : true, + modelMatrix : Matrix4.IDENTITY, + _geometricError : 2 + }; + + var scene; + beforeEach(function() { + scene = createScene(); + scene.frameState.passes.render = true; + }); + + afterEach(function() { + scene.destroyForSpecs(); + }); + + function verifyColor(tileColor, expectedColor) { + var diff = new Color (Math.abs(expectedColor.red - tileColor.red), + Math.abs(expectedColor.green - tileColor.green), + Math.abs(expectedColor.blue - tileColor.blue)); + + var threshold = 0.11; + expect(diff.red).toBeLessThan(threshold); + expect(diff.green).toBeLessThan(threshold); + expect(diff.blue).toBeLessThan(threshold); + } + + it('resetMinimumMaximum', function() { + var heatmap = new Cesium3DTilesetHeatmap('_centerZDepth'); + heatmap._minimum = -1; + heatmap._maximum = 1; + heatmap.resetMinimumMaximum(); // Preparing for next frame, previousMinimum/Maximum take current frame's values + + expect(heatmap._minimum).toBe(Number.MAX_VALUE); + expect(heatmap._maximum).toBe(-Number.MAX_VALUE); + expect(heatmap._previousMinimum).toBe(-1); + expect(heatmap._previousMaximum).toBe( 1); + }); + + it('uses reference minimum maximum', function() { + var tilePropertyName = '_loadTimestamp'; + var heatmap = new Cesium3DTilesetHeatmap(tilePropertyName); + + var referenceMinimumJulianDate = new JulianDate(); + var referenceMaximumJulianDate = new JulianDate(); + JulianDate.now(referenceMinimumJulianDate); + JulianDate.addSeconds(referenceMinimumJulianDate, 10, referenceMaximumJulianDate); + + heatmap.setReferenceMinimumMaximum(referenceMinimumJulianDate, referenceMaximumJulianDate, tilePropertyName); // User wants to colorize to a fixed reference. + var referenceMinimum = heatmap._referenceMinimum[tilePropertyName]; + var referenceMaximum = heatmap._referenceMaximum[tilePropertyName]; + + heatmap._minimum = -1; + heatmap._maximum = 1; + heatmap.resetMinimumMaximum(); // Preparing for next frame, previousMinimum/Maximum always uses the reference values if they exist for the variable. + + expect(heatmap._minimum).toBe(Number.MAX_VALUE); + expect(heatmap._maximum).toBe(-Number.MAX_VALUE); + expect(heatmap._previousMinimum).toBe(referenceMinimum); + expect(heatmap._previousMaximum).toBe(referenceMaximum); + }); + + it('expected color', function() { + var heatmap = new Cesium3DTilesetHeatmap('_centerZDepth'); + + var tile = new Cesium3DTile(mockTileset, '/some_url', tileWithBoundingSphere, undefined); + tile._contentState = Cesium3DTileContentState.READY; + tile.hasEmptyContent = false; + var frameState = scene.frameState; + tile._selectedFrame = frameState.frameNumber; + var originalColor = tile._debugColor; + + // This is first frame, previousMinimum/Maximum are unititialized so no coloring occurs + tile._centerZDepth = 1; + heatmap.colorize(tile, frameState); + tile._centerZDepth = -1; + heatmap.colorize(tile, frameState); + + expect(heatmap._minimum).toBe(-1); + expect(heatmap._maximum).toBe( 1); + verifyColor(tile._debugColor, originalColor); + + // Preparing for next frame, previousMinimum/Maximum take current frame's values + heatmap.resetMinimumMaximum(); + + tile._centerZDepth = -1; + heatmap.colorize(tile, frameState); + + var expectedColor = Color.BLACK; + verifyColor(tile._debugColor, expectedColor); + }); +}); diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index b93a4bac261..51b71e6491a 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -21,9 +21,12 @@ defineSuite([ 'Core/Transforms', 'Renderer/ClearCommand', 'Renderer/ContextLimits', + 'Scene/Camera', 'Scene/Cesium3DTile', 'Scene/Cesium3DTileColorBlendMode', 'Scene/Cesium3DTileContentState', + 'Scene/Cesium3DTilePass', + 'Scene/Cesium3DTilePassState', 'Scene/Cesium3DTileRefine', 'Scene/Cesium3DTileStyle', 'Scene/ClippingPlane', @@ -56,9 +59,12 @@ defineSuite([ Transforms, ClearCommand, ContextLimits, + Camera, Cesium3DTile, Cesium3DTileColorBlendMode, Cesium3DTileContentState, + Cesium3DTilePass, + Cesium3DTilePassState, Cesium3DTileRefine, Cesium3DTileStyle, ClippingPlane, @@ -70,13 +76,14 @@ defineSuite([ when) { 'use strict'; - // It's not easily possible to mock the asynchronous pick functions + // It's not easily possible to mock the most detailed pick functions // so don't run those tests when using the WebGL stub var webglStub = !!window.webglStub; var scene; var centerLongitude = -1.31968; var centerLatitude = 0.698874; + var options; // Parent tile with content and four child tiles with content var tilesetUrl = 'Data/Cesium3DTiles/Tilesets/Tileset/tileset.json'; @@ -167,6 +174,10 @@ defineSuite([ camera.frustum.fov = CesiumMath.toRadians(60.0); viewAllTiles(); + + options = { + cullRequestsWhileMoving : false + }; }); afterEach(function() { @@ -225,9 +236,8 @@ defineSuite([ deferred.reject(); }); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : 'invalid.json' - })); + options.url = 'invalid.json'; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function() { fail('should not resolve'); }).otherwise(function(error) { @@ -319,9 +329,8 @@ defineSuite([ var uri = 'data:text/plain;base64,' + btoa(JSON.stringify(tilesetJson)); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : uri - })); + options.url = uri; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function() { fail('should not resolve'); }).otherwise(function(error) { @@ -392,9 +401,8 @@ defineSuite([ }); it('gets root tile', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); expect(function() { return tileset.root; }).toThrowDeveloperError(); @@ -471,9 +479,8 @@ defineSuite([ var invalidMagicBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ magic : [120, 120, 120, 120] }); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function(tileset) { // Start spying after the tileset json has been loaded spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { @@ -492,9 +499,8 @@ defineSuite([ it('handles failed tile requests', function() { viewRootOnly(); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function(tileset) { // Start spying after the tileset json has been loaded spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { @@ -517,9 +523,8 @@ defineSuite([ it('handles failed tile processing', function() { viewRootOnly(); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function(tileset) { // Start spying after the tileset json has been loaded spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { @@ -591,9 +596,8 @@ defineSuite([ }); it('verify statistics', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); // Verify initial values var statistics = tileset._statistics; @@ -656,41 +660,36 @@ defineSuite([ } it('verify batched features statistics', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : withBatchTableUrl - })); + options.url = withBatchTableUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 10, 0, 120); }); it('verify no batch table features statistics', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : noBatchIdsUrl - })); + options.url = noBatchIdsUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 0, 0, 120); }); it('verify instanced features statistics', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : instancedRedMaterialUrl - })); + options.url = instancedRedMaterialUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 25, 0, 12); }); it('verify composite features statistics', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : compositeUrl - })); + options.url = compositeUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 35, 0, 132); }); it('verify tileset of tilesets features statistics', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetOfTilesetsUrl - })); + options.url = tilesetOfTilesetsUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 50, 0, 600); }); @@ -698,17 +697,15 @@ defineSuite([ it('verify points statistics', function() { viewPointCloud(); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : pointCloudUrl - })); + options.url = pointCloudUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 0, 1000, 0); }); it('verify triangle statistics', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetEmptyRootUrl - })); + options.url = tilesetEmptyRootUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 40, 0, 480); }); @@ -716,9 +713,8 @@ defineSuite([ it('verify batched points statistics', function() { viewPointCloud(); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : pointCloudBatchedUrl - })); + options.url = pointCloudBatchedUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 8, 1000, 0); }); @@ -1064,10 +1060,9 @@ defineSuite([ }); it('replacement refinement - selects tile when inside viewer request volume', function() { - var options = { + return Cesium3DTilesTester.loadTileset(scene, tilesetWithViewerRequestVolumeUrl, { skipLevelOfDetail : false - }; - return Cesium3DTilesTester.loadTileset(scene, tilesetWithViewerRequestVolumeUrl, options).then(function(tileset) { + }).then(function(tileset) { var statistics = tileset._statistics; var root = tileset.root; @@ -1287,11 +1282,10 @@ defineSuite([ }); it('does select visible tiles with visible children failing request volumes', function() { - var options = { - cullWithChildrenBounds : false - }; viewRootOnly(); - return Cesium3DTilesTester.loadTileset(scene, tilesetReplacementWithViewerRequestVolumeUrl, options).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, tilesetReplacementWithViewerRequestVolumeUrl, { + cullWithChildrenBounds : false + }).then(function(tileset) { var root = tileset.root; var childRoot = root.children[0]; @@ -1747,10 +1741,9 @@ defineSuite([ it('does not request tiles when the request scheduler is full', function() { viewRootOnly(); // Root tiles are loaded initially - var options = { + return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, { skipLevelOfDetail : false - }; - return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, options).then(function(tileset) { + }).then(function(tileset) { // Try to load 4 children. Only 3 requests will go through, 1 will be attempted. var oldMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer; RequestScheduler.maximumRequestsPerServer = 3; @@ -1786,9 +1779,8 @@ defineSuite([ }); it('tilesLoaded', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); expect(tileset.tilesLoaded).toBe(false); tileset.readyPromise.then(function() { expect(tileset.tilesLoaded).toBe(false); @@ -1804,9 +1796,8 @@ defineSuite([ var spyUpdate1 = jasmine.createSpy('listener'); var spyUpdate2 = jasmine.createSpy('listener'); viewRootOnly(); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); tileset.allTilesLoaded.addEventListener(spyUpdate1); tileset.initialTilesLoaded.addEventListener(spyUpdate2); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then(function() { @@ -1918,9 +1909,8 @@ defineSuite([ it('destroys before tile finishes loading', function() { viewRootOnly(); - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function(tileset) { var root = tileset.root; scene.renderForSpecs(); // Request root @@ -2125,9 +2115,9 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then(function(tileset) { tileset.style = new Cesium3DTileStyle({color: 'color("red")'}); scene.renderForSpecs(); - expect(tileset._statisticsLastRender.numberOfTilesStyled).toBe(1); + expect(tileset._statisticsPerPass[Cesium3DTilePass.RENDER].numberOfTilesStyled).toBe(1); scene.pickForSpecs(); - expect(tileset._statisticsLastPick.numberOfTilesStyled).toBe(0); + expect(tileset._statisticsPerPass[Cesium3DTilePass.PICK].numberOfTilesStyled).toBe(0); }); }); @@ -2816,9 +2806,8 @@ defineSuite([ }); it('does not add commands or stencil clear command with no selected tiles', function() { - var tileset = scene.primitives.add(new Cesium3DTileset({ - url : tilesetUrl - })); + options.url = tilesetUrl; + var tileset = scene.primitives.add(new Cesium3DTileset(options)); scene.renderForSpecs(); var statistics = tileset._statistics; expect(tileset._selectedTiles.length).toEqual(0); @@ -2844,7 +2833,8 @@ defineSuite([ it('loadSiblings', function() { viewBottomLeft(); return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement3Url, { - loadSiblings : false + loadSiblings : false, + foveatedTimeDelay : 0 }).then(function(tileset) { var statistics = tileset._statistics; expect(statistics.numberOfTilesWithContentReady).toBe(2); @@ -3329,6 +3319,63 @@ defineSuite([ }); }); + describe('updateForPass', function() { + it('updates for pass', function() { + viewAllTiles(); + var passCamera = Camera.clone(scene.camera); + var passCullingVolume = passCamera.frustum.computeCullingVolume(passCamera.positionWC, passCamera.directionWC, passCamera.upWC); + viewNothing(); // Main camera views nothing, pass camera views all tiles + + var preloadFlightPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.PRELOAD_FLIGHT, + camera : passCamera, + cullingVolume : passCullingVolume + }); + + return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) { + expect(tileset.statistics.selected).toBe(0); + tileset.updateForPass(scene.frameState, preloadFlightPassState); + expect(tileset._requestedTiles.length).toBe(5); + }); + }); + + it('uses custom command list', function() { + var passCommandList = []; + + var renderPassState = new Cesium3DTilePassState({ + pass : Cesium3DTilePass.RENDER, + commandList : passCommandList + }); + + viewAllTiles(); + + return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) { + tileset.updateForPass(scene.frameState, renderPassState); + expect(passCommandList.length).toBe(5); + }); + }); + + it('throws if frameState is undefined', function() { + var tileset = new Cesium3DTileset({ + url : tilesetUrl + }); + + expect(function() { + tileset.updateForPass(); + }).toThrowDeveloperError(); + }); + + it('throws if tilesetPassState is undefined', function() { + var tileset = new Cesium3DTileset({ + url : tilesetUrl + }); + + expect(function() { + tileset.updateForPass(scene.frameState); + }).toThrowDeveloperError(); + }); + }); + function sampleHeightMostDetailed(cartographics, objectsToExclude) { var result; var completed = false; @@ -3376,13 +3423,13 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then(function(tileset) { return sampleHeightMostDetailed(cartographics).then(function() { expect(centerCartographic.height).toEqualEpsilon(2.47, CesiumMath.EPSILON1); - var statisticsAsync = tileset._statisticsLastAsync; - var statisticsRender = tileset._statisticsLastRender; - expect(statisticsAsync.numberOfCommands).toBe(1); - expect(statisticsAsync.numberOfTilesWithContentReady).toBe(1); - expect(statisticsAsync.selected).toBe(1); - expect(statisticsAsync.visited).toBeGreaterThan(1); - expect(statisticsAsync.numberOfTilesTotal).toBe(21); + var statisticsMostDetailedPick = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; + var statisticsRender = tileset._statisticsPerPass[Cesium3DTilePass.RENDER]; + expect(statisticsMostDetailedPick.numberOfCommands).toBe(1); + expect(statisticsMostDetailedPick.numberOfTilesWithContentReady).toBe(1); + expect(statisticsMostDetailedPick.selected).toBe(1); + expect(statisticsMostDetailedPick.visited).toBeGreaterThan(1); + expect(statisticsMostDetailedPick.numberOfTilesTotal).toBe(21); expect(statisticsRender.selected).toBe(0); }); }); @@ -3402,13 +3449,13 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then(function(tileset) { return sampleHeightMostDetailed(cartographics).then(function() { expect(centerCartographic.height).toEqualEpsilon(2.47, CesiumMath.EPSILON1); - var statisticsAsync = tileset._statisticsLastAsync; - var statisticsRender = tileset._statisticsLastRender; - expect(statisticsAsync.numberOfCommands).toBe(1); - expect(statisticsAsync.numberOfTilesWithContentReady).toBeGreaterThan(1); - expect(statisticsAsync.selected).toBe(1); - expect(statisticsAsync.visited).toBeGreaterThan(1); - expect(statisticsAsync.numberOfTilesTotal).toBe(21); + var statisticsMostDetailedPick = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; + var statisticsRender = tileset._statisticsPerPass[Cesium3DTilePass.RENDER]; + expect(statisticsMostDetailedPick.numberOfCommands).toBe(1); + expect(statisticsMostDetailedPick.numberOfTilesWithContentReady).toBeGreaterThan(1); + expect(statisticsMostDetailedPick.selected).toBe(1); + expect(statisticsMostDetailedPick.visited).toBeGreaterThan(1); + expect(statisticsMostDetailedPick.numberOfTilesTotal).toBe(21); expect(statisticsRender.selected).toBeGreaterThan(0); }); }); @@ -3434,7 +3481,7 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then(function(tileset) { return sampleHeightMostDetailed(cartographics).then(function() { - var statistics = tileset._statisticsLastAsync; + var statistics = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; expect(offcenterCartographic.height).toEqualEpsilon(7.407, CesiumMath.EPSILON1); expect(statistics.numberOfCommands).toBe(3); // One for each level of the tree expect(statistics.numberOfTilesWithContentReady).toBeGreaterThanOrEqualTo(3); @@ -3504,9 +3551,164 @@ defineSuite([ expect(centerCartographic.height).toEqualEpsilon(2.47, CesiumMath.EPSILON1); expect(offcenterCartographic.height).toEqualEpsilon(2.47, CesiumMath.EPSILON1); expect(missCartographic.height).toBeUndefined(); - expect(tileset._statisticsLastAsync.numberOfTilesWithContentReady).toBe(2); + + var statistics = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; + expect(statistics.numberOfTilesWithContentReady).toBe(2); }); }); }); }); + + it('cancels out-of-view tiles', function() { + viewNothing(); + + return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then(function(tileset) { + // Make requests + viewAllTiles(); + scene.renderForSpecs(); + var requestedTilesInFlight = tileset._requestedTilesInFlight; + var requestedTilesInFlightLength = requestedTilesInFlight.length; + expect(requestedTilesInFlightLength).toBeGreaterThan(0); + + // Save off old requests + var oldRequests = []; + var i; + for (i = 0; i < requestedTilesInFlightLength; i++) { + oldRequests.push(requestedTilesInFlight[i]); + } + + // Cancel requests + viewNothing(); + scene.renderForSpecs(); + expect(requestedTilesInFlight.length).toBe(0); + + // Make sure old requests were marked for cancelling + var allCancelled = true; + var oldRequestsLength = oldRequests.length; + for (i = 0; i < oldRequestsLength; i++) { + var tile = oldRequests[i]; + allCancelled = allCancelled && tile._request.cancelled; + } + expect(allCancelled).toBe(true); + }); + }); + + it('sorts requests by priority', function() { + viewNothing(); + + return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then(function(tileset) { + // Make requests + viewAllTiles(); + scene.renderForSpecs(); + var requestedTilesInFlight = tileset._requestedTilesInFlight; + var requestedTilesInFlightLength = requestedTilesInFlight.length; + expect(requestedTilesInFlightLength).toBeGreaterThan(0); + + // Verify sort + var allSorted = true; + var lastPriority = -Number.MAX_VALUE; + var thisPriority; + for (var i = 0; i < requestedTilesInFlightLength; i++) { + thisPriority = requestedTilesInFlight[i]._priority; + allSorted = allSorted && thisPriority >= lastPriority; + lastPriority = thisPriority; + } + + expect(allSorted).toBe(true); + expect(lastPriority !== requestedTilesInFlight[0]._priority).toBe(true); // Not all the same value + }); + }); + + it('defers requests when foveatedScreenSpaceError is true', function() { + viewNothing(); + return Cesium3DTilesTester.loadTileset(scene, tilesetRefinementMix).then(function(tileset) { + tileset.foveatedScreenSpaceError = true; + tileset.foveatedConeSize = 0; + tileset.maximumScreenSpaceError = 8; + tileset.foveatedTimeDelay = 0; + + // Position camera such that some tiles are outside the foveated cone but still on screen. + viewAllTiles(); + scene.camera.moveLeft(205.0); + scene.camera.moveDown(205.0); + + scene.renderForSpecs(); + + // Verify deferred + var requestedTilesInFlight = tileset._requestedTilesInFlight; + expect(requestedTilesInFlight.length).toBe(1); + expect(requestedTilesInFlight[0].priorityDeferred).toBe(true); + }); + }); + + it('loads deferred requests only after time delay.', function() { + viewNothing(); + return Cesium3DTilesTester.loadTileset(scene, tilesetRefinementMix).then(function(tileset) { + tileset.foveatedScreenSpaceError = true; + tileset.foveatedConeSize = 0; + tileset.maximumScreenSpaceError = 8; + tileset.foveatedTimeDelay = 0.1; + + // Position camera such that some tiles are outside the foveated cone but still on screen. + viewAllTiles(); + scene.camera.moveLeft(205.0); + scene.camera.moveDown(205.0); + + scene.renderForSpecs(); + + // Nothing should be loaded yet. + expect(tileset._requestedTilesInFlight.length).toBe(0); + // Eventually, a deferred tile should load. + return pollToPromise(function() { + scene.renderForSpecs(); + return tileset._requestedTilesInFlight.length !== 0; + }).then(function() { + expect(tileset._requestedTilesInFlight[0].priorityDeferred).toBe(true); + }); + }); + }); + + it('preloads tiles', function() { + // Flight destination + viewAllTiles(); + scene.preloadFlightCamera = Camera.clone(scene.camera); + scene.preloadFlightCullingVolume = scene.camera.frustum.computeCullingVolume(scene.camera.positionWC, scene.camera.directionWC, scene.camera.upWC); + + // Reset view + viewNothing(); + + return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then(function(tileset) { + spyOn(Camera.prototype, 'hasCurrentFlight').and.callFake(function() { + return true; + }); + scene.renderForSpecs(); + expect(tileset._requestedTilesInFlight.length).toBeGreaterThan(0); + }); + }); + + it('does not fetch tiles while camera is moving', function() { + viewNothing(); + return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then(function(tileset) { + tileset.cullRequestsWhileMoving = true; + viewAllTiles(); + scene.renderForSpecs(); + expect(tileset._requestedTilesInFlight.length).toEqual(0); // Big camera delta so no fetches should occur. + }); + }); + + it('loads tiles when preloadWhenHidden is true', function() { + var options = { + show : false, + preloadWhenHidden : true + }; + + return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, options).then(function(tileset) { + var selectedLength = tileset.statistics.selected; + expect(selectedLength).toBeGreaterThan(0); + tileset.show = true; + scene.renderForSpecs(); + expect(tileset.statistics.selected).toBe(selectedLength); + expect(tileset.statistics.numberOfPendingRequests).toBe(0); + }); + }); }, 'WebGL'); diff --git a/Specs/Scene/Geometry3DTileContentSpec.js b/Specs/Scene/Geometry3DTileContentSpec.js index 3b71f98f369..55daac0acdf 100644 --- a/Specs/Scene/Geometry3DTileContentSpec.js +++ b/Specs/Scene/Geometry3DTileContentSpec.js @@ -175,6 +175,7 @@ defineSuite([ // wrap rectangle primitive so it gets executed during the globe pass and 3D Tiles pass to lay down depth globePrimitive = new MockPrimitive(reusableGlobePrimitive, Pass.GLOBE); tilesetPrimitive = new MockPrimitive(reusableTilesetPrimitive, Pass.CESIUM_3D_TILE); + scene.camera.lookAt(ellipsoid.cartographicToCartesian(Rectangle.center(tilesetRectangle)), new Cartesian3(0.0, 0.0, 0.01)); }); afterEach(function() { @@ -184,11 +185,6 @@ defineSuite([ tileset = tileset && !tileset.isDestroyed() && tileset.destroy(); }); - function loadTileset(tileset) { - scene.camera.lookAt(ellipsoid.cartographicToCartesian(Rectangle.center(tilesetRectangle)), new Cartesian3(0.0, 0.0, 0.01)); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); - } - function expectPick(scene) { expect(scene).toPickAndCall(function(result) { expect(result).toBeDefined(); @@ -271,11 +267,9 @@ defineSuite([ it('renders on 3D Tiles', function() { scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxes, + return Cesium3DTilesTester.loadTileset(scene, geometryBoxes, { classificationType : ClassificationType.CESIUM_3D_TILE - })); - return loadTileset(tileset).then(function(tileset) { + }).then(function(tileset) { globePrimitive.show = false; tilesetPrimitive.show = true; verifyRender(tileset, scene); @@ -291,11 +285,9 @@ defineSuite([ it('renders on globe', function() { scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxes, + return Cesium3DTilesTester.loadTileset(scene, geometryBoxes, { classificationType : ClassificationType.TERRAIN - })); - return loadTileset(tileset).then(function(tileset) { + }).then(function(tileset) { globePrimitive.show = false; tilesetPrimitive.show = true; expectRender(scene, depthColor); @@ -311,11 +303,9 @@ defineSuite([ it('renders on 3D Tiles and globe', function() { scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxes, + return Cesium3DTilesTester.loadTileset(scene, geometryBoxes, { classificationType : ClassificationType.BOTH - })); - return loadTileset(tileset).then(function(tileset) { + }).then(function(tileset) { globePrimitive.show = false; tilesetPrimitive.show = true; verifyRender(tileset, scene); @@ -331,10 +321,7 @@ defineSuite([ it('renders boxes', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxes - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryBoxes).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -342,10 +329,7 @@ defineSuite([ it('renders batched boxes', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxesBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryBoxesBatchedChildren).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -353,10 +337,7 @@ defineSuite([ it('renders boxes with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxesWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryBoxesWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -364,10 +345,7 @@ defineSuite([ it('renders batched boxes with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxesBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryBoxesBatchedChildrenWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -375,10 +353,7 @@ defineSuite([ it('renders boxes with batch ids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxesWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryBoxesWithBatchIds).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -386,10 +361,7 @@ defineSuite([ it('renders cylinders', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryCylinders - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryCylinders).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -397,10 +369,7 @@ defineSuite([ it('renders batched cylinders', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryCylindersBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryCylindersBatchedChildren).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -408,10 +377,7 @@ defineSuite([ it('renders cylinders with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryCylindersWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryCylindersWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -419,10 +385,7 @@ defineSuite([ it('renders batched cylinders with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryCylindersBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryCylindersBatchedChildrenWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -430,10 +393,7 @@ defineSuite([ it('renders cylinders with batch ids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryCylindersWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryCylindersWithBatchIds).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -441,10 +401,7 @@ defineSuite([ it('renders ellipsoids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryEllipsoids - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryEllipsoids).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -452,10 +409,7 @@ defineSuite([ it('renders batched ellipsoids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryEllipsoidsBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryEllipsoidsBatchedChildren).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -463,10 +417,7 @@ defineSuite([ it('renders ellipsoids with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryEllipsoidsWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryEllipsoidsWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -474,10 +425,7 @@ defineSuite([ it('renders batched ellipsoids with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryEllipsoidsBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryEllipsoidsBatchedChildrenWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -485,10 +433,7 @@ defineSuite([ it('renders ellipsoids with batch ids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryEllipsoidsWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryEllipsoidsWithBatchIds).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -496,10 +441,7 @@ defineSuite([ it('renders spheres', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometrySpheres - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometrySpheres).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -507,10 +449,7 @@ defineSuite([ it('renders batched spheres', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometrySpheresBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometrySpheresBatchedChildren).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -518,10 +457,7 @@ defineSuite([ it('renders spheres with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometrySpheresWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometrySpheresWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -529,10 +465,7 @@ defineSuite([ it('renders batched spheres with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometrySpheresBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometrySpheresBatchedChildrenWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -540,10 +473,7 @@ defineSuite([ it('renders spheres with batch ids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometrySpheresWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometrySpheresWithBatchIds).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -551,10 +481,7 @@ defineSuite([ it('renders all geometries', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryAll - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryAll).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -562,10 +489,7 @@ defineSuite([ it('renders batched all geometries', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryAllBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryAllBatchedChildren).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -573,10 +497,7 @@ defineSuite([ it('renders all geometries with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryAllWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryAllWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -584,10 +505,7 @@ defineSuite([ it('renders batched all geometries with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryAllBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryAllBatchedChildrenWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -595,10 +513,7 @@ defineSuite([ it('renders all geometries with batch ids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryAllWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryAllWithBatchIds).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -606,11 +521,9 @@ defineSuite([ it('renders all geometries with debug color', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryAllWithBatchTable, + return Cesium3DTilesTester.loadTileset(scene, geometryAllWithBatchTable, { debugColorizeTiles : true - })); - return loadTileset(tileset).then(function(tileset) { + }).then(function(tileset) { var center = Rectangle.center(tilesetRectangle); var ulRect = new Rectangle(tilesetRectangle.west, center.latitude, center.longitude, tilesetRectangle.north); var urRect = new Rectangle(center.longitude, center.longitude, tilesetRectangle.east, tilesetRectangle.north); @@ -641,10 +554,7 @@ defineSuite([ }); it('can get features and properties', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxesWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryBoxesWithBatchTable).then(function(tileset) { var content = tileset.root.content; expect(content.featuresLength).toBe(1); expect(content.innerContents).toBeUndefined(); @@ -654,10 +564,7 @@ defineSuite([ }); it('throws when calling getFeature with invalid index', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : geometryBoxesWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, geometryBoxesWithBatchTable).then(function(tileset) { var content = tileset.root.content; expect(function(){ content.getFeature(-1); diff --git a/Specs/Scene/PickSpec.js b/Specs/Scene/PickSpec.js index 138966a9183..c70982d0b3d 100644 --- a/Specs/Scene/PickSpec.js +++ b/Specs/Scene/PickSpec.js @@ -58,7 +58,7 @@ defineSuite([ when) { 'use strict'; - // It's not easily possible to mock the asynchronous pick functions + // It's not easily possible to mock the most detailed pick functions // so don't run those tests when using the WebGL stub var webglStub = !!window.webglStub; diff --git a/Specs/Scene/Vector3DTileContentSpec.js b/Specs/Scene/Vector3DTileContentSpec.js index f966b33393d..889b40d6ae7 100644 --- a/Specs/Scene/Vector3DTileContentSpec.js +++ b/Specs/Scene/Vector3DTileContentSpec.js @@ -167,6 +167,7 @@ defineSuite([ // wrap rectangle primitive so it gets executed during the globe pass and 3D Tiles pass to lay down depth globePrimitive = new MockPrimitive(reusableGlobePrimitive, Pass.GLOBE); tilesetPrimitive = new MockPrimitive(reusableTilesetPrimitive, Pass.CESIUM_3D_TILE); + scene.camera.lookAt(ellipsoid.cartographicToCartesian(Rectangle.center(tilesetRectangle)), new Cartesian3(0.0, 0.0, 0.01)); }); afterEach(function() { @@ -176,11 +177,6 @@ defineSuite([ tileset = tileset && !tileset.isDestroyed() && tileset.destroy(); }); - function loadTileset(tileset) { - scene.camera.lookAt(ellipsoid.cartographicToCartesian(Rectangle.center(tilesetRectangle)), new Cartesian3(0.0, 0.0, 0.01)); - return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset); - } - function expectPick(scene) { expect(scene).toPickAndCall(function(result) { expect(result).toBeDefined(); @@ -443,50 +439,35 @@ defineSuite([ } it('renders points', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPoints - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPoints).then(function(tileset) { verifyRenderPoints(tileset, scene); verifyPickPoints(scene); }); }); it('renders batched points', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPointsBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPointsBatchedChildren).then(function(tileset) { verifyRenderPoints(tileset, scene); verifyPickPoints(scene); }); }); it('renders points with a batch table', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPointsWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPointsWithBatchTable).then(function(tileset) { verifyRenderPoints(tileset, scene); verifyPickPoints(scene); }); }); it('renders batched points with a batch table', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPointsBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPointsBatchedChildrenWithBatchTable).then(function(tileset) { verifyRenderPoints(tileset, scene); verifyPickPoints(scene); }); }); it('renders points with batch ids', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPointsWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPointsWithBatchIds).then(function(tileset) { verifyRenderPoints(tileset, scene); verifyPickPoints(scene); }); @@ -494,10 +475,7 @@ defineSuite([ it('renders polygons', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygons - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolygons).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -505,10 +483,7 @@ defineSuite([ it('renders batched polygons', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsBatchedChildren).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -516,10 +491,7 @@ defineSuite([ it('renders polygons with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -527,10 +499,7 @@ defineSuite([ it('renders batched polygons with a batch table', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsBatchedChildrenWithBatchTable).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); @@ -538,87 +507,61 @@ defineSuite([ it('renders polygons with batch ids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsWithBatchIds).then(function(tileset) { verifyRender(tileset, scene); verifyPick(scene); }); }); it('renders polylines', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolylines - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolylines).then(function(tileset) { verifyRenderPolylines(tileset, scene); }); }); it('renders batched polylines', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolylinesBatchedChildren - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolylinesBatchedChildren).then(function(tileset) { verifyRenderPolylines(tileset, scene); }); }); it('renders polylines with a batch table', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolylinesWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolylinesWithBatchTable).then(function(tileset) { verifyRenderPolylines(tileset, scene); }); }); it('renders batched polylines with a batch table', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolylinesBatchedChildrenWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolylinesBatchedChildrenWithBatchTable).then(function(tileset) { verifyRenderPolylines(tileset, scene); }); }); it('renders polylines with batch ids', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolylinesWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolylinesWithBatchIds).then(function(tileset) { verifyRenderPolylines(tileset, scene); }); }); it('renders combined tile', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorCombined - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorCombined).then(function(tileset) { verifyRenderCombined(tileset, scene); }); }); it('renders combined tile with batch ids', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorCombinedWithBatchIds - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorCombinedWithBatchIds).then(function(tileset) { verifyRenderCombined(tileset, scene); }); }); it('renders with debug color', function() { scene.primitives.add(globePrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorCombined, + return Cesium3DTilesTester.loadTileset(scene, vectorCombined, { debugColorizeTiles : true - })); - return loadTileset(tileset).then(function() { + }).then(function() { var width = combinedRectangle.width; var step = width / 3; @@ -638,11 +581,9 @@ defineSuite([ it('renders on 3D Tiles', function() { scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsBatchedChildren, + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsBatchedChildren, { classificationType : ClassificationType.CESIUM_3D_TILE - })); - return loadTileset(tileset).then(function(tileset) { + }).then(function(tileset) { globePrimitive.show = false; tilesetPrimitive.show = true; verifyRender(tileset, scene); @@ -658,11 +599,9 @@ defineSuite([ it('renders on globe', function() { scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsBatchedChildren, + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsBatchedChildren, { classificationType : ClassificationType.TERRAIN - })); - return loadTileset(tileset).then(function(tileset) { + }).then(function(tileset) { globePrimitive.show = false; tilesetPrimitive.show = true; expectRender(scene, depthColor); @@ -678,11 +617,9 @@ defineSuite([ it('renders on 3D Tiles and globe', function() { scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsBatchedChildren, + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsBatchedChildren, { classificationType : ClassificationType.BOTH - })); - return loadTileset(tileset).then(function(tileset) { + }).then(function(tileset) { globePrimitive.show = false; tilesetPrimitive.show = true; verifyRender(tileset, scene); @@ -697,10 +634,7 @@ defineSuite([ }); it('can get features and properties', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsWithBatchTable).then(function(tileset) { var content = tileset.root.content; expect(content.featuresLength).toBe(1); expect(content.innerContents).toBeUndefined(); @@ -710,10 +644,7 @@ defineSuite([ }); it('throws when calling getFeature with invalid index', function() { - tileset = scene.primitives.add(new Cesium3DTileset({ - url : vectorPolygonsWithBatchTable - })); - return loadTileset(tileset).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, vectorPolygonsWithBatchTable).then(function(tileset) { var content = tileset.root.content; expect(function(){ content.getFeature(-1);