import BoundingSphere from "../Core/BoundingSphere.js"; import BoxOutlineGeometry from "../Core/BoxOutlineGeometry.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; import Cartographic from "../Core/Cartographic.js"; import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js"; import combine from "../Core/combine.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import Event from "../Core/Event.js"; import GeometryInstance from "../Core/GeometryInstance.js"; import GeometryPipeline from "../Core/GeometryPipeline.js"; import IndexDatatype from "../Core/IndexDatatype.js"; import Intersect from "../Core/Intersect.js"; import CesiumMath from "../Core/Math.js"; import Matrix4 from "../Core/Matrix4.js"; import NearFarScalar from "../Core/NearFarScalar.js"; import OrientedBoundingBox from "../Core/OrientedBoundingBox.js"; import OrthographicFrustum from "../Core/OrthographicFrustum.js"; import PrimitiveType from "../Core/PrimitiveType.js"; import Rectangle from "../Core/Rectangle.js"; import SphereOutlineGeometry from "../Core/SphereOutlineGeometry.js"; import TerrainExaggeration from "../Core/TerrainExaggeration.js"; import TerrainQuantization from "../Core/TerrainQuantization.js"; import Visibility from "../Core/Visibility.js"; import WebMercatorProjection from "../Core/WebMercatorProjection.js"; import Buffer from "../Renderer/Buffer.js"; import BufferUsage from "../Renderer/BufferUsage.js"; import ContextLimits from "../Renderer/ContextLimits.js"; import DrawCommand from "../Renderer/DrawCommand.js"; import Pass from "../Renderer/Pass.js"; import RenderState from "../Renderer/RenderState.js"; import VertexArray from "../Renderer/VertexArray.js"; import BlendingState from "./BlendingState.js"; import ClippingPlaneCollection from "./ClippingPlaneCollection.js"; import DepthFunction from "./DepthFunction.js"; import GlobeSurfaceTile from "./GlobeSurfaceTile.js"; import ImageryLayer from "./ImageryLayer.js"; import ImageryState from "./ImageryState.js"; import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js"; import Primitive from "./Primitive.js"; import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js"; import SceneMode from "./SceneMode.js"; import ShadowMode from "./ShadowMode.js"; import TerrainFillMesh from "./TerrainFillMesh.js"; import TerrainState from "./TerrainState.js"; import TileBoundingRegion from "./TileBoundingRegion.js"; import TileSelectionResult from "./TileSelectionResult.js"; /** * Provides quadtree tiles representing the surface of the globe. This type is intended to be used * with {@link QuadtreePrimitive}. * * @alias GlobeSurfaceTileProvider * @constructor * * @param {TerrainProvider} options.terrainProvider The terrain provider that describes the surface geometry. * @param {ImageryLayerCollection} option.imageryLayers The collection of imagery layers describing the shading of the surface. * @param {GlobeSurfaceShaderSet} options.surfaceShaderSet The set of shaders used to render the surface. * * @private */ function GlobeSurfaceTileProvider(options) { //>>includeStart('debug', pragmas.debug); if (!defined(options)) { throw new DeveloperError("options is required."); } if (!defined(options.terrainProvider)) { throw new DeveloperError("options.terrainProvider is required."); } else if (!defined(options.imageryLayers)) { throw new DeveloperError("options.imageryLayers is required."); } else if (!defined(options.surfaceShaderSet)) { throw new DeveloperError("options.surfaceShaderSet is required."); } //>>includeEnd('debug'); this.lightingFadeOutDistance = 6500000.0; this.lightingFadeInDistance = 9000000.0; this.hasWaterMask = false; this.oceanNormalMap = undefined; this.zoomedOutOceanSpecularIntensity = 0.5; this.enableLighting = false; this.dynamicAtmosphereLighting = false; this.dynamicAtmosphereLightingFromSun = false; this.showGroundAtmosphere = false; this.shadows = ShadowMode.RECEIVE_ONLY; /** * The color to use to highlight terrain fill tiles. If undefined, fill tiles are not * highlighted at all. The alpha value is used to alpha blend with the tile's * actual color. Because terrain fill tiles do not represent the actual terrain surface, * it may be useful in some applications to indicate visually that they are not to be trusted. * @type {Color} * @default undefined */ this.fillHighlightColor = undefined; this.hueShift = 0.0; this.saturationShift = 0.0; this.brightnessShift = 0.0; this.showSkirts = true; this.backFaceCulling = true; this.undergroundColor = undefined; this.undergroundColorAlphaByDistance = undefined; this.lambertDiffuseMultiplier = 0.0; this.materialUniformMap = undefined; this._materialUniformMap = undefined; this._quadtree = undefined; this._terrainProvider = options.terrainProvider; this._imageryLayers = options.imageryLayers; this._surfaceShaderSet = options.surfaceShaderSet; this._renderState = undefined; this._blendRenderState = undefined; this._disableCullingRenderState = undefined; this._disableCullingBlendRenderState = undefined; this._errorEvent = new Event(); this._imageryLayers.layerAdded.addEventListener( GlobeSurfaceTileProvider.prototype._onLayerAdded, this ); this._imageryLayers.layerRemoved.addEventListener( GlobeSurfaceTileProvider.prototype._onLayerRemoved, this ); this._imageryLayers.layerMoved.addEventListener( GlobeSurfaceTileProvider.prototype._onLayerMoved, this ); this._imageryLayers.layerShownOrHidden.addEventListener( GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden, this ); this._imageryLayersUpdatedEvent = new Event(); this._layerOrderChanged = false; this._tilesToRenderByTextureCount = []; this._drawCommands = []; this._uniformMaps = []; this._usedDrawCommands = 0; this._vertexArraysToDestroy = []; this._debug = { wireframe: false, boundingSphereTile: undefined, }; this._baseColor = undefined; this._firstPassInitialColor = undefined; this.baseColor = new Color(0.0, 0.0, 0.5, 1.0); /** * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane. * @type {ClippingPlaneCollection} * @private */ this._clippingPlanes = undefined; /** * A property specifying a {@link Rectangle} used to selectively limit terrain and imagery rendering. * @type {Rectangle} */ this.cartographicLimitRectangle = Rectangle.clone(Rectangle.MAX_VALUE); this._hasLoadedTilesThisFrame = false; this._hasFillTilesThisFrame = false; this._oldTerrainExaggeration = undefined; this._oldTerrainExaggerationRelativeHeight = undefined; } Object.defineProperties(GlobeSurfaceTileProvider.prototype, { /** * Gets or sets the color of the globe when no imagery is available. * @memberof GlobeSurfaceTileProvider.prototype * @type {Color} */ baseColor: { get: function () { return this._baseColor; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); this._baseColor = value; this._firstPassInitialColor = Cartesian4.fromColor( value, this._firstPassInitialColor ); }, }, /** * Gets or sets the {@link QuadtreePrimitive} for which this provider is * providing tiles. This property may be undefined if the provider is not yet associated * with a {@link QuadtreePrimitive}. * @memberof GlobeSurfaceTileProvider.prototype * @type {QuadtreePrimitive} */ quadtree: { get: function () { return this._quadtree; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); this._quadtree = value; }, }, /** * Gets a value indicating whether or not the provider is ready for use. * @memberof GlobeSurfaceTileProvider.prototype * @type {Boolean} */ ready: { get: function () { return ( this._terrainProvider.ready && (this._imageryLayers.length === 0 || this._imageryLayers.get(0).imageryProvider.ready) ); }, }, /** * Gets the tiling scheme used by the provider. This property should * not be accessed before {@link GlobeSurfaceTileProvider#ready} returns true. * @memberof GlobeSurfaceTileProvider.prototype * @type {TilingScheme} */ tilingScheme: { get: function () { return this._terrainProvider.tilingScheme; }, }, /** * Gets an event that is raised when the geometry provider encounters an asynchronous error. By subscribing * to the event, you will be notified of the error and can potentially recover from it. Event listeners * are passed an instance of {@link TileProviderError}. * @memberof GlobeSurfaceTileProvider.prototype * @type {Event} */ errorEvent: { get: function () { return this._errorEvent; }, }, /** * Gets an event that is raised when an imagery layer is added, shown, hidden, moved, or removed. * @memberof GlobeSurfaceTileProvider.prototype * @type {Event} */ imageryLayersUpdatedEvent: { get: function () { return this._imageryLayersUpdatedEvent; }, }, /** * Gets or sets the terrain provider that describes the surface geometry. * @memberof GlobeSurfaceTileProvider.prototype * @type {TerrainProvider} */ terrainProvider: { get: function () { return this._terrainProvider; }, set: function (terrainProvider) { if (this._terrainProvider === terrainProvider) { return; } //>>includeStart('debug', pragmas.debug); if (!defined(terrainProvider)) { throw new DeveloperError("terrainProvider is required."); } //>>includeEnd('debug'); this._terrainProvider = terrainProvider; if (defined(this._quadtree)) { this._quadtree.invalidateAllTiles(); } }, }, /** * The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. * * @type {ClippingPlaneCollection} * * @private */ clippingPlanes: { get: function () { return this._clippingPlanes; }, set: function (value) { ClippingPlaneCollection.setOwner(value, this, "_clippingPlanes"); }, }, }); function sortTileImageryByLayerIndex(a, b) { let aImagery = a.loadingImagery; if (!defined(aImagery)) { aImagery = a.readyImagery; } let bImagery = b.loadingImagery; if (!defined(bImagery)) { bImagery = b.readyImagery; } return aImagery.imageryLayer._layerIndex - bImagery.imageryLayer._layerIndex; } /** * Make updates to the tile provider that are not involved in rendering. Called before the render update cycle. */ GlobeSurfaceTileProvider.prototype.update = function (frameState) { // update collection: imagery indices, base layers, raise layer show/hide event this._imageryLayers._update(); }; function updateCredits(surface, frameState) { const creditDisplay = frameState.creditDisplay; if ( surface._terrainProvider.ready && defined(surface._terrainProvider.credit) ) { creditDisplay.addCredit(surface._terrainProvider.credit); } const imageryLayers = surface._imageryLayers; for (let i = 0, len = imageryLayers.length; i < len; ++i) { const imageryProvider = imageryLayers.get(i).imageryProvider; if (imageryProvider.ready && defined(imageryProvider.credit)) { creditDisplay.addCredit(imageryProvider.credit); } } } /** * Called at the beginning of each render frame, before {@link QuadtreeTileProvider#showTileThisFrame} * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.initialize = function (frameState) { // update each layer for texture reprojection. this._imageryLayers.queueReprojectionCommands(frameState); if (this._layerOrderChanged) { this._layerOrderChanged = false; // Sort the TileImagery instances in each tile by the layer index. this._quadtree.forEachLoadedTile(function (tile) { tile.data.imagery.sort(sortTileImageryByLayerIndex); }); } // Add credits for terrain and imagery providers. updateCredits(this, frameState); const vertexArraysToDestroy = this._vertexArraysToDestroy; const length = vertexArraysToDestroy.length; for (let j = 0; j < length; ++j) { GlobeSurfaceTile._freeVertexArray(vertexArraysToDestroy[j]); } vertexArraysToDestroy.length = 0; }; /** * Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame} * or any other functions. * * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.beginUpdate = function (frameState) { const tilesToRenderByTextureCount = this._tilesToRenderByTextureCount; for (let i = 0, len = tilesToRenderByTextureCount.length; i < len; ++i) { const tiles = tilesToRenderByTextureCount[i]; if (defined(tiles)) { tiles.length = 0; } } // update clipping planes const clippingPlanes = this._clippingPlanes; if (defined(clippingPlanes) && clippingPlanes.enabled) { clippingPlanes.update(frameState); } this._usedDrawCommands = 0; this._hasLoadedTilesThisFrame = false; this._hasFillTilesThisFrame = false; }; /** * Called at the end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame} * and any other functions. * * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.endUpdate = function (frameState) { if (!defined(this._renderState)) { this._renderState = RenderState.fromCache({ // Write color and depth cull: { enabled: true, }, depthTest: { enabled: true, func: DepthFunction.LESS, }, }); this._blendRenderState = RenderState.fromCache({ // Write color and depth cull: { enabled: true, }, depthTest: { enabled: true, func: DepthFunction.LESS_OR_EQUAL, }, blending: BlendingState.ALPHA_BLEND, }); let rs = clone(this._renderState, true); rs.cull.enabled = false; this._disableCullingRenderState = RenderState.fromCache(rs); rs = clone(this._blendRenderState, true); rs.cull.enabled = false; this._disableCullingBlendRenderState = RenderState.fromCache(rs); } // If this frame has a mix of loaded and fill tiles, we need to propagate // loaded heights to the fill tiles. if (this._hasFillTilesThisFrame && this._hasLoadedTilesThisFrame) { TerrainFillMesh.updateFillTiles( this, this._quadtree._tilesToRender, frameState, this._vertexArraysToDestroy ); } // When terrain exaggeration changes, all of the loaded tiles need to generate // geodetic surface normals so they can scale properly when rendered. // When exaggeration is reset, geodetic surface normals are removed to decrease // memory usage. Some tiles might have been constructed with the correct // exaggeration already, so skip over them. // If the geodetic surface normals can't be created because the tile doesn't // have a mesh, keep checking until the tile does have a mesh. This can happen // if the tile's mesh starts construction in a worker thread right before the // exaggeration changes. const quadtree = this.quadtree; const exaggeration = frameState.terrainExaggeration; const exaggerationRelativeHeight = frameState.terrainExaggerationRelativeHeight; const exaggerationChanged = this._oldTerrainExaggeration !== exaggeration || this._oldTerrainExaggerationRelativeHeight !== exaggerationRelativeHeight; // Keep track of the next time there is a change in exaggeration this._oldTerrainExaggeration = exaggeration; this._oldTerrainExaggerationRelativeHeight = exaggerationRelativeHeight; if (exaggerationChanged) { quadtree.forEachLoadedTile(function (tile) { const surfaceTile = tile.data; surfaceTile.updateExaggeration(tile, frameState, quadtree); }); } // Add the tile render commands to the command list, sorted by texture count. const tilesToRenderByTextureCount = this._tilesToRenderByTextureCount; for ( let textureCountIndex = 0, textureCountLength = tilesToRenderByTextureCount.length; textureCountIndex < textureCountLength; ++textureCountIndex ) { const tilesToRender = tilesToRenderByTextureCount[textureCountIndex]; if (!defined(tilesToRender)) { continue; } for ( let tileIndex = 0, tileLength = tilesToRender.length; tileIndex < tileLength; ++tileIndex ) { const tile = tilesToRender[tileIndex]; const tileBoundingRegion = tile.data.tileBoundingRegion; addDrawCommandsForTile(this, tile, frameState); frameState.minimumTerrainHeight = Math.min( frameState.minimumTerrainHeight, tileBoundingRegion.minimumHeight ); } } }; function pushCommand(command, frameState) { const globeTranslucencyState = frameState.globeTranslucencyState; if (globeTranslucencyState.translucent) { const isBlendCommand = command.renderState.blending.enabled; globeTranslucencyState.pushDerivedCommands( command, isBlendCommand, frameState ); } else { frameState.commandList.push(command); } } /** * Adds draw commands for tiles rendered in the previous frame for a pick pass. * * @param {FrameState} frameState The frame state. */ GlobeSurfaceTileProvider.prototype.updateForPick = function (frameState) { // Add the tile pick commands from the tiles drawn last frame. const drawCommands = this._drawCommands; for (let i = 0, length = this._usedDrawCommands; i < length; ++i) { pushCommand(drawCommands[i], frameState); } }; /** * Cancels any imagery re-projections in the queue. */ GlobeSurfaceTileProvider.prototype.cancelReprojections = function () { this._imageryLayers.cancelReprojections(); }; /** * Gets the maximum geometric error allowed in a tile at a given level, in meters. This function should not be * called before {@link GlobeSurfaceTileProvider#ready} returns true. * * @param {Number} level The tile level for which to get the maximum geometric error. * @returns {Number} The maximum geometric error in meters. */ GlobeSurfaceTileProvider.prototype.getLevelMaximumGeometricError = function ( level ) { return this._terrainProvider.getLevelMaximumGeometricError(level); }; /** * Loads, or continues loading, a given tile. This function will continue to be called * until {@link QuadtreeTile#state} is no longer {@link QuadtreeTileLoadState#LOADING}. This function should * not be called before {@link GlobeSurfaceTileProvider#ready} returns true. * * @param {FrameState} frameState The frame state. * @param {QuadtreeTile} tile The tile to load. * * @exception {DeveloperError} loadTile must not be called before the tile provider is ready. */ GlobeSurfaceTileProvider.prototype.loadTile = function (frameState, tile) { // We don't want to load imagery until we're certain that the terrain tiles are actually visible. // So if our bounding volume isn't accurate because it came from another tile, load terrain only // initially. If we load some terrain and suddenly have a more accurate bounding volume and the // tile is _still_ visible, give the tile a chance to load imagery immediately rather than // waiting for next frame. let surfaceTile = tile.data; let terrainOnly = true; let terrainStateBefore; if (defined(surfaceTile)) { terrainOnly = surfaceTile.boundingVolumeSourceTile !== tile || tile._lastSelectionResult === TileSelectionResult.CULLED_BUT_NEEDED; terrainStateBefore = surfaceTile.terrainState; } GlobeSurfaceTile.processStateMachine( tile, frameState, this.terrainProvider, this._imageryLayers, this.quadtree, this._vertexArraysToDestroy, terrainOnly ); surfaceTile = tile.data; if (terrainOnly && terrainStateBefore !== tile.data.terrainState) { // Terrain state changed. If: // a) The tile is visible, and // b) The bounding volume is accurate (updated as a side effect of computing visibility) // Then we'll load imagery, too. if ( this.computeTileVisibility(tile, frameState, this.quadtree.occluders) !== Visibility.NONE && surfaceTile.boundingVolumeSourceTile === tile ) { terrainOnly = false; GlobeSurfaceTile.processStateMachine( tile, frameState, this.terrainProvider, this._imageryLayers, this.quadtree, this._vertexArraysToDestroy, terrainOnly ); } } }; const boundingSphereScratch = new BoundingSphere(); const rectangleIntersectionScratch = new Rectangle(); const splitCartographicLimitRectangleScratch = new Rectangle(); const rectangleCenterScratch = new Cartographic(); // cartographicLimitRectangle may span the IDL, but tiles never will. function clipRectangleAntimeridian(tileRectangle, cartographicLimitRectangle) { if (cartographicLimitRectangle.west < cartographicLimitRectangle.east) { return cartographicLimitRectangle; } const splitRectangle = Rectangle.clone( cartographicLimitRectangle, splitCartographicLimitRectangleScratch ); const tileCenter = Rectangle.center(tileRectangle, rectangleCenterScratch); if (tileCenter.longitude > 0.0) { splitRectangle.east = CesiumMath.PI; } else { splitRectangle.west = -CesiumMath.PI; } return splitRectangle; } function isUndergroundVisible(tileProvider, frameState) { if (frameState.cameraUnderground) { return true; } if (frameState.globeTranslucencyState.translucent) { return true; } if (tileProvider.backFaceCulling) { return false; } const clippingPlanes = tileProvider._clippingPlanes; if (defined(clippingPlanes) && clippingPlanes.enabled) { return true; } if ( !Rectangle.equals( tileProvider.cartographicLimitRectangle, Rectangle.MAX_VALUE ) ) { return true; } return false; } /** * Determines the visibility of a given tile. The tile may be fully visible, partially visible, or not * visible at all. Tiles that are renderable and are at least partially visible will be shown by a call * to {@link GlobeSurfaceTileProvider#showTileThisFrame}. * * @param {QuadtreeTile} tile The tile instance. * @param {FrameState} frameState The state information about the current frame. * @param {QuadtreeOccluders} occluders The objects that may occlude this tile. * * @returns {Visibility} Visibility.NONE if the tile is not visible, * Visibility.PARTIAL if the tile is partially visible, or * Visibility.FULL if the tile is fully visible. */ GlobeSurfaceTileProvider.prototype.computeTileVisibility = function ( tile, frameState, occluders ) { const distance = this.computeDistanceToTile(tile, frameState); tile._distance = distance; const undergroundVisible = isUndergroundVisible(this, frameState); if (frameState.fog.enabled && !undergroundVisible) { if (CesiumMath.fog(distance, frameState.fog.density) >= 1.0) { // Tile is completely in fog so return that it is not visible. return Visibility.NONE; } } const surfaceTile = tile.data; const tileBoundingRegion = surfaceTile.tileBoundingRegion; if (surfaceTile.boundingVolumeSourceTile === undefined) { // We have no idea where this tile is, so let's just call it partially visible. return Visibility.PARTIAL; } const cullingVolume = frameState.cullingVolume; let boundingVolume = tileBoundingRegion.boundingVolume; if (!defined(boundingVolume)) { boundingVolume = tileBoundingRegion.boundingSphere; } // Check if the tile is outside the limit area in cartographic space surfaceTile.clippedByBoundaries = false; const clippedCartographicLimitRectangle = clipRectangleAntimeridian( tile.rectangle, this.cartographicLimitRectangle ); const areaLimitIntersection = Rectangle.simpleIntersection( clippedCartographicLimitRectangle, tile.rectangle, rectangleIntersectionScratch ); if (!defined(areaLimitIntersection)) { return Visibility.NONE; } if (!Rectangle.equals(areaLimitIntersection, tile.rectangle)) { surfaceTile.clippedByBoundaries = true; } if (frameState.mode !== SceneMode.SCENE3D) { boundingVolume = boundingSphereScratch; BoundingSphere.fromRectangleWithHeights2D( tile.rectangle, frameState.mapProjection, tileBoundingRegion.minimumHeight, tileBoundingRegion.maximumHeight, boundingVolume ); Cartesian3.fromElements( boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center ); if ( frameState.mode === SceneMode.MORPHING && defined(surfaceTile.renderedMesh) ) { boundingVolume = BoundingSphere.union( tileBoundingRegion.boundingSphere, boundingVolume, boundingVolume ); } } if (!defined(boundingVolume)) { return Visibility.PARTIAL; } const clippingPlanes = this._clippingPlanes; if (defined(clippingPlanes) && clippingPlanes.enabled) { const planeIntersection = clippingPlanes.computeIntersectionWithBoundingVolume( boundingVolume ); tile.isClipped = planeIntersection !== Intersect.INSIDE; if (planeIntersection === Intersect.OUTSIDE) { return Visibility.NONE; } } let visibility; const intersection = cullingVolume.computeVisibility(boundingVolume); if (intersection === Intersect.OUTSIDE) { visibility = Visibility.NONE; } else if (intersection === Intersect.INTERSECTING) { visibility = Visibility.PARTIAL; } else if (intersection === Intersect.INSIDE) { visibility = Visibility.FULL; } if (visibility === Visibility.NONE) { return visibility; } const ortho3D = frameState.mode === SceneMode.SCENE3D && frameState.camera.frustum instanceof OrthographicFrustum; if ( frameState.mode === SceneMode.SCENE3D && !ortho3D && defined(occluders) && !undergroundVisible ) { const occludeePointInScaledSpace = surfaceTile.occludeePointInScaledSpace; if (!defined(occludeePointInScaledSpace)) { return visibility; } if ( occluders.ellipsoid.isScaledSpacePointVisiblePossiblyUnderEllipsoid( occludeePointInScaledSpace, tileBoundingRegion.minimumHeight ) ) { return visibility; } return Visibility.NONE; } return visibility; }; /** * Determines if the given tile can be refined * @param {QuadtreeTile} tile The tile to check. * @returns {boolean} True if the tile can be refined, false if it cannot. */ GlobeSurfaceTileProvider.prototype.canRefine = function (tile) { // Only allow refinement it we know whether or not the children of this tile exist. // For a tileset with `availability`, we'll always be able to refine. // We can ask for availability of _any_ child tile because we only need to confirm // that we get a yes or no answer, it doesn't matter what the answer is. if (defined(tile.data.terrainData)) { return true; } const childAvailable = this.terrainProvider.getTileDataAvailable( tile.x * 2, tile.y * 2, tile.level + 1 ); return childAvailable !== undefined; }; const readyImageryScratch = []; const canRenderTraversalStack = []; /** * Determines if the given not-fully-loaded tile can be rendered without losing detail that * was present last frame as a result of rendering descendant tiles. This method will only be * called if this tile's descendants were rendered last frame. If the tile is fully loaded, * it is assumed that this method will return true and it will not be called. * @param {QuadtreeTile} tile The tile to check. * @returns {boolean} True if the tile can be rendered without losing detail. */ GlobeSurfaceTileProvider.prototype.canRenderWithoutLosingDetail = function ( tile, frameState ) { const surfaceTile = tile.data; const readyImagery = readyImageryScratch; readyImagery.length = this._imageryLayers.length; let terrainReady = false; let initialImageryState = false; let imagery; if (defined(surfaceTile)) { // We can render even with non-ready terrain as long as all our rendered descendants // are missing terrain geometry too. i.e. if we rendered fills for more detailed tiles // last frame, it's ok to render a fill for this tile this frame. terrainReady = surfaceTile.terrainState === TerrainState.READY; // Initially assume all imagery layers are ready, unless imagery hasn't been initialized at all. initialImageryState = true; imagery = surfaceTile.imagery; } let i; let len; for (i = 0, len = readyImagery.length; i < len; ++i) { readyImagery[i] = initialImageryState; } if (defined(imagery)) { for (i = 0, len = imagery.length; i < len; ++i) { const tileImagery = imagery[i]; const loadingImagery = tileImagery.loadingImagery; const isReady = !defined(loadingImagery) || loadingImagery.state === ImageryState.FAILED || loadingImagery.state === ImageryState.INVALID; const layerIndex = ( tileImagery.loadingImagery || tileImagery.readyImagery ).imageryLayer._layerIndex; // For a layer to be ready, all tiles belonging to that layer must be ready. readyImagery[layerIndex] = isReady && readyImagery[layerIndex]; } } const lastFrame = this.quadtree._lastSelectionFrameNumber; // Traverse the descendants looking for one with terrain or imagery that is not loaded on this tile. const stack = canRenderTraversalStack; stack.length = 0; stack.push( tile.southwestChild, tile.southeastChild, tile.northwestChild, tile.northeastChild ); while (stack.length > 0) { const descendant = stack.pop(); const lastFrameSelectionResult = descendant._lastSelectionResultFrame === lastFrame ? descendant._lastSelectionResult : TileSelectionResult.NONE; if (lastFrameSelectionResult === TileSelectionResult.RENDERED) { const descendantSurface = descendant.data; if (!defined(descendantSurface)) { // Descendant has no data, so it can't block rendering. continue; } if ( !terrainReady && descendant.data.terrainState === TerrainState.READY ) { // Rendered descendant has real terrain, but we don't. Rendering is blocked. return false; } const descendantImagery = descendant.data.imagery; for (i = 0, len = descendantImagery.length; i < len; ++i) { const descendantTileImagery = descendantImagery[i]; const descendantLoadingImagery = descendantTileImagery.loadingImagery; const descendantIsReady = !defined(descendantLoadingImagery) || descendantLoadingImagery.state === ImageryState.FAILED || descendantLoadingImagery.state === ImageryState.INVALID; const descendantLayerIndex = ( descendantTileImagery.loadingImagery || descendantTileImagery.readyImagery ).imageryLayer._layerIndex; // If this imagery tile of a descendant is ready but the layer isn't ready in this tile, // then rendering is blocked. if (descendantIsReady && !readyImagery[descendantLayerIndex]) { return false; } } } else if (lastFrameSelectionResult === TileSelectionResult.REFINED) { stack.push( descendant.southwestChild, descendant.southeastChild, descendant.northwestChild, descendant.northeastChild ); } } return true; }; const tileDirectionScratch = new Cartesian3(); /** * Determines the priority for loading this tile. Lower priority values load sooner. * @param {QuadtreeTile} tile The tile. * @param {FrameState} frameState The frame state. * @returns {Number} The load priority value. */ GlobeSurfaceTileProvider.prototype.computeTileLoadPriority = function ( tile, frameState ) { const surfaceTile = tile.data; if (surfaceTile === undefined) { return 0.0; } const obb = surfaceTile.tileBoundingRegion.boundingVolume; if (obb === undefined) { return 0.0; } const cameraPosition = frameState.camera.positionWC; const cameraDirection = frameState.camera.directionWC; const tileDirection = Cartesian3.subtract( obb.center, cameraPosition, tileDirectionScratch ); const magnitude = Cartesian3.magnitude(tileDirection); if (magnitude < CesiumMath.EPSILON5) { return 0.0; } Cartesian3.divideByScalar(tileDirection, magnitude, tileDirection); return ( (1.0 - Cartesian3.dot(tileDirection, cameraDirection)) * tile._distance ); }; const modifiedModelViewScratch = new Matrix4(); const modifiedModelViewProjectionScratch = new Matrix4(); const tileRectangleScratch = new Cartesian4(); const localizedCartographicLimitRectangleScratch = new Cartesian4(); const localizedTranslucencyRectangleScratch = new Cartesian4(); const rtcScratch = new Cartesian3(); const centerEyeScratch = new Cartesian3(); const southwestScratch = new Cartesian3(); const northeastScratch = new Cartesian3(); /** * Shows a specified tile in this frame. The provider can cause the tile to be shown by adding * render commands to the commandList, or use any other method as appropriate. The tile is not * expected to be visible next frame as well, unless this method is called next frame, too. * * @param {QuadtreeTile} tile The tile instance. * @param {FrameState} frameState The state information of the current rendering frame. */ GlobeSurfaceTileProvider.prototype.showTileThisFrame = function ( tile, frameState ) { let readyTextureCount = 0; const tileImageryCollection = tile.data.imagery; for (let i = 0, len = tileImageryCollection.length; i < len; ++i) { const tileImagery = tileImageryCollection[i]; if ( defined(tileImagery.readyImagery) && tileImagery.readyImagery.imageryLayer.alpha !== 0.0 ) { ++readyTextureCount; } } let tileSet = this._tilesToRenderByTextureCount[readyTextureCount]; if (!defined(tileSet)) { tileSet = []; this._tilesToRenderByTextureCount[readyTextureCount] = tileSet; } tileSet.push(tile); const surfaceTile = tile.data; if (!defined(surfaceTile.vertexArray)) { this._hasFillTilesThisFrame = true; } else { this._hasLoadedTilesThisFrame = true; } const debug = this._debug; ++debug.tilesRendered; debug.texturesRendered += readyTextureCount; }; const cornerPositionsScratch = [ new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), ]; function computeOccludeePoint( tileProvider, center, rectangle, minimumHeight, maximumHeight, result ) { const ellipsoidalOccluder = tileProvider.quadtree._occluders.ellipsoid; const ellipsoid = ellipsoidalOccluder.ellipsoid; const cornerPositions = cornerPositionsScratch; Cartesian3.fromRadians( rectangle.west, rectangle.south, maximumHeight, ellipsoid, cornerPositions[0] ); Cartesian3.fromRadians( rectangle.east, rectangle.south, maximumHeight, ellipsoid, cornerPositions[1] ); Cartesian3.fromRadians( rectangle.west, rectangle.north, maximumHeight, ellipsoid, cornerPositions[2] ); Cartesian3.fromRadians( rectangle.east, rectangle.north, maximumHeight, ellipsoid, cornerPositions[3] ); return ellipsoidalOccluder.computeHorizonCullingPointPossiblyUnderEllipsoid( center, cornerPositions, minimumHeight, result ); } /** * Gets the distance from the camera to the closest point on the tile. This is used for level-of-detail selection. * * @param {QuadtreeTile} tile The tile instance. * @param {FrameState} frameState The state information of the current rendering frame. * * @returns {Number} The distance from the camera to the closest point on the tile, in meters. */ GlobeSurfaceTileProvider.prototype.computeDistanceToTile = function ( tile, frameState ) { // The distance should be: // 1. the actual distance to the tight-fitting bounding volume, or // 2. a distance that is equal to or greater than the actual distance to the tight-fitting bounding volume. // // When we don't know the min/max heights for a tile, but we do know the min/max of an ancestor tile, we can // build a tight-fitting bounding volume horizontally, but not vertically. The min/max heights from the // ancestor will likely form a volume that is much bigger than it needs to be. This means that the volume may // be deemed to be much closer to the camera than it really is, causing us to select tiles that are too detailed. // Loading too-detailed tiles is super expensive, so we don't want to do that. We don't know where the child // tile really lies within the parent range of heights, but we _do_ know the child tile can't be any closer than // the ancestor height surface (min or max) that is _farthest away_ from the camera. So if we compute distance // based on that conservative metric, we may end up loading tiles that are not detailed enough, but that's much // better (faster) than loading tiles that are too detailed. updateTileBoundingRegion(tile, this, frameState); const surfaceTile = tile.data; const boundingVolumeSourceTile = surfaceTile.boundingVolumeSourceTile; if (boundingVolumeSourceTile === undefined) { // Can't find any min/max heights anywhere? Ok, let's just say the // tile is really far away so we'll load and render it rather than // refining. return 9999999999.0; } const tileBoundingRegion = surfaceTile.tileBoundingRegion; const min = tileBoundingRegion.minimumHeight; const max = tileBoundingRegion.maximumHeight; if (surfaceTile.boundingVolumeSourceTile !== tile) { const cameraHeight = frameState.camera.positionCartographic.height; const distanceToMin = Math.abs(cameraHeight - min); const distanceToMax = Math.abs(cameraHeight - max); if (distanceToMin > distanceToMax) { tileBoundingRegion.minimumHeight = min; tileBoundingRegion.maximumHeight = min; } else { tileBoundingRegion.minimumHeight = max; tileBoundingRegion.maximumHeight = max; } } const result = tileBoundingRegion.distanceToCamera(frameState); tileBoundingRegion.minimumHeight = min; tileBoundingRegion.maximumHeight = max; return result; }; function updateTileBoundingRegion(tile, tileProvider, frameState) { let surfaceTile = tile.data; if (surfaceTile === undefined) { surfaceTile = tile.data = new GlobeSurfaceTile(); } const ellipsoid = tile.tilingScheme.ellipsoid; if (surfaceTile.tileBoundingRegion === undefined) { surfaceTile.tileBoundingRegion = new TileBoundingRegion({ computeBoundingVolumes: false, rectangle: tile.rectangle, ellipsoid: ellipsoid, minimumHeight: 0.0, maximumHeight: 0.0, }); } const tileBoundingRegion = surfaceTile.tileBoundingRegion; const oldMinimumHeight = tileBoundingRegion.minimumHeight; const oldMaximumHeight = tileBoundingRegion.maximumHeight; let hasBoundingVolumesFromMesh = false; let sourceTile = tile; // Get min and max heights from the mesh. // If the mesh is not available, get them from the terrain data. // If the terrain data is not available either, get them from an ancestor. // If none of the ancestors are available, then there are no min and max heights for this tile at this time. const mesh = surfaceTile.mesh; const terrainData = surfaceTile.terrainData; if ( mesh !== undefined && mesh.minimumHeight !== undefined && mesh.maximumHeight !== undefined ) { tileBoundingRegion.minimumHeight = mesh.minimumHeight; tileBoundingRegion.maximumHeight = mesh.maximumHeight; hasBoundingVolumesFromMesh = true; } else if ( terrainData !== undefined && terrainData._minimumHeight !== undefined && terrainData._maximumHeight !== undefined ) { tileBoundingRegion.minimumHeight = terrainData._minimumHeight; tileBoundingRegion.maximumHeight = terrainData._maximumHeight; } else { // No accurate min/max heights available, so we're stuck with min/max heights from an ancestor tile. tileBoundingRegion.minimumHeight = Number.NaN; tileBoundingRegion.maximumHeight = Number.NaN; let ancestorTile = tile.parent; while (ancestorTile !== undefined) { const ancestorSurfaceTile = ancestorTile.data; if (ancestorSurfaceTile !== undefined) { const ancestorMesh = ancestorSurfaceTile.mesh; const ancestorTerrainData = ancestorSurfaceTile.terrainData; if ( ancestorMesh !== undefined && ancestorMesh.minimumHeight !== undefined && ancestorMesh.maximumHeight !== undefined ) { tileBoundingRegion.minimumHeight = ancestorMesh.minimumHeight; tileBoundingRegion.maximumHeight = ancestorMesh.maximumHeight; break; } else if ( ancestorTerrainData !== undefined && ancestorTerrainData._minimumHeight !== undefined && ancestorTerrainData._maximumHeight !== undefined ) { tileBoundingRegion.minimumHeight = ancestorTerrainData._minimumHeight; tileBoundingRegion.maximumHeight = ancestorTerrainData._maximumHeight; break; } } ancestorTile = ancestorTile.parent; } sourceTile = ancestorTile; } // Update bounding regions from the min and max heights if (sourceTile !== undefined) { const exaggeration = frameState.terrainExaggeration; const exaggerationRelativeHeight = frameState.terrainExaggerationRelativeHeight; const hasExaggeration = exaggeration !== 1.0; if (hasExaggeration) { hasBoundingVolumesFromMesh = false; tileBoundingRegion.minimumHeight = TerrainExaggeration.getHeight( tileBoundingRegion.minimumHeight, exaggeration, exaggerationRelativeHeight ); tileBoundingRegion.maximumHeight = TerrainExaggeration.getHeight( tileBoundingRegion.maximumHeight, exaggeration, exaggerationRelativeHeight ); } if (hasBoundingVolumesFromMesh) { if (!surfaceTile.boundingVolumeIsFromMesh) { tileBoundingRegion._orientedBoundingBox = OrientedBoundingBox.clone( mesh.orientedBoundingBox, tileBoundingRegion._orientedBoundingBox ); tileBoundingRegion._boundingSphere = BoundingSphere.clone( mesh.boundingSphere3D, tileBoundingRegion._boundingSphere ); surfaceTile.occludeePointInScaledSpace = Cartesian3.clone( mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace ); // If the occludee point is not defined, fallback to calculating it from the OBB if (!defined(surfaceTile.occludeePointInScaledSpace)) { surfaceTile.occludeePointInScaledSpace = computeOccludeePoint( tileProvider, tileBoundingRegion._orientedBoundingBox.center, tile.rectangle, tileBoundingRegion.minimumHeight, tileBoundingRegion.maximumHeight, surfaceTile.occludeePointInScaledSpace ); } } } else { const needsBounds = tileBoundingRegion._orientedBoundingBox === undefined || tileBoundingRegion._boundingSphere === undefined; const heightChanged = tileBoundingRegion.minimumHeight !== oldMinimumHeight || tileBoundingRegion.maximumHeight !== oldMaximumHeight; if (heightChanged || needsBounds) { // Bounding volumes need to be recomputed in some circumstances tileBoundingRegion.computeBoundingVolumes(ellipsoid); surfaceTile.occludeePointInScaledSpace = computeOccludeePoint( tileProvider, tileBoundingRegion._orientedBoundingBox.center, tile.rectangle, tileBoundingRegion.minimumHeight, tileBoundingRegion.maximumHeight, surfaceTile.occludeePointInScaledSpace ); } } surfaceTile.boundingVolumeSourceTile = sourceTile; surfaceTile.boundingVolumeIsFromMesh = hasBoundingVolumesFromMesh; } else { surfaceTile.boundingVolumeSourceTile = undefined; surfaceTile.boundingVolumeIsFromMesh = false; } } /** * Returns true if this object was destroyed; otherwise, false. *

* If this object was destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. * * @returns {Boolean} True if this object was destroyed; otherwise, false. * * @see GlobeSurfaceTileProvider#destroy */ GlobeSurfaceTileProvider.prototype.isDestroyed = function () { return false; }; /** * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. *

* Once an object is destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. Therefore, * assign the return value (undefined) to the object as done in the example. * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * * @example * provider = provider && provider(); * * @see GlobeSurfaceTileProvider#isDestroyed */ GlobeSurfaceTileProvider.prototype.destroy = function () { this._tileProvider = this._tileProvider && this._tileProvider.destroy(); this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy(); return destroyObject(this); }; function getTileReadyCallback(tileImageriesToFree, layer, terrainProvider) { return function (tile) { let tileImagery; let imagery; let startIndex = -1; const tileImageryCollection = tile.data.imagery; const length = tileImageryCollection.length; let i; for (i = 0; i < length; ++i) { tileImagery = tileImageryCollection[i]; imagery = defaultValue( tileImagery.readyImagery, tileImagery.loadingImagery ); if (imagery.imageryLayer === layer) { startIndex = i; break; } } if (startIndex !== -1) { const endIndex = startIndex + tileImageriesToFree; tileImagery = tileImageryCollection[endIndex]; imagery = defined(tileImagery) ? defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery) : undefined; if (!defined(imagery) || imagery.imageryLayer !== layer) { // Return false to keep the callback if we have to wait on the skeletons // Return true to remove the callback if something went wrong return !layer._createTileImagerySkeletons( tile, terrainProvider, endIndex ); } for (i = startIndex; i < endIndex; ++i) { tileImageryCollection[i].freeResources(); } tileImageryCollection.splice(startIndex, tileImageriesToFree); } return true; // Everything is done, so remove the callback }; } GlobeSurfaceTileProvider.prototype._onLayerAdded = function (layer, index) { if (layer.show) { const terrainProvider = this._terrainProvider; const that = this; const imageryProvider = layer.imageryProvider; const tileImageryUpdatedEvent = this._imageryLayersUpdatedEvent; imageryProvider._reload = function () { // Clear the layer's cache layer._imageryCache = {}; that._quadtree.forEachLoadedTile(function (tile) { // If this layer is still waiting to for the loaded callback, just return if (defined(tile._loadedCallbacks[layer._layerIndex])) { return; } let i; // Figure out how many TileImageries we will need to remove and where to insert new ones const tileImageryCollection = tile.data.imagery; const length = tileImageryCollection.length; let startIndex = -1; let tileImageriesToFree = 0; for (i = 0; i < length; ++i) { const tileImagery = tileImageryCollection[i]; const imagery = defaultValue( tileImagery.readyImagery, tileImagery.loadingImagery ); if (imagery.imageryLayer === layer) { if (startIndex === -1) { startIndex = i; } ++tileImageriesToFree; } else if (startIndex !== -1) { // iterated past the section of TileImageries belonging to this layer, no need to continue. break; } } if (startIndex === -1) { return; } // Insert immediately after existing TileImageries const insertionPoint = startIndex + tileImageriesToFree; // Create new TileImageries for all loaded tiles if ( layer._createTileImagerySkeletons( tile, terrainProvider, insertionPoint ) ) { // Add callback to remove old TileImageries when the new TileImageries are ready tile._loadedCallbacks[layer._layerIndex] = getTileReadyCallback( tileImageriesToFree, layer, terrainProvider ); tile.state = QuadtreeTileLoadState.LOADING; } }); }; // create TileImageries for this layer for all previously loaded tiles this._quadtree.forEachLoadedTile(function (tile) { if (layer._createTileImagerySkeletons(tile, terrainProvider)) { tile.state = QuadtreeTileLoadState.LOADING; // Tiles that are not currently being rendered need to load the new layer before they're renderable. // We don't mark the rendered tiles non-renderable, though, because that would make the globe disappear. if ( tile.level !== 0 && (tile._lastSelectionResultFrame !== that.quadtree._lastSelectionFrameNumber || tile._lastSelectionResult !== TileSelectionResult.RENDERED) ) { tile.renderable = false; } } }); this._layerOrderChanged = true; tileImageryUpdatedEvent.raiseEvent(); } }; GlobeSurfaceTileProvider.prototype._onLayerRemoved = function (layer, index) { // destroy TileImagerys for this layer for all previously loaded tiles this._quadtree.forEachLoadedTile(function (tile) { const tileImageryCollection = tile.data.imagery; let startIndex = -1; let numDestroyed = 0; for (let i = 0, len = tileImageryCollection.length; i < len; ++i) { const tileImagery = tileImageryCollection[i]; let imagery = tileImagery.loadingImagery; if (!defined(imagery)) { imagery = tileImagery.readyImagery; } if (imagery.imageryLayer === layer) { if (startIndex === -1) { startIndex = i; } tileImagery.freeResources(); ++numDestroyed; } else if (startIndex !== -1) { // iterated past the section of TileImagerys belonging to this layer, no need to continue. break; } } if (startIndex !== -1) { tileImageryCollection.splice(startIndex, numDestroyed); } }); if (defined(layer.imageryProvider)) { layer.imageryProvider._reload = undefined; } this._imageryLayersUpdatedEvent.raiseEvent(); }; GlobeSurfaceTileProvider.prototype._onLayerMoved = function ( layer, newIndex, oldIndex ) { this._layerOrderChanged = true; this._imageryLayersUpdatedEvent.raiseEvent(); }; GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden = function ( layer, index, show ) { if (show) { this._onLayerAdded(layer, index); } else { this._onLayerRemoved(layer, index); } }; const scratchClippingPlanesMatrix = new Matrix4(); const scratchInverseTransposeClippingPlanesMatrix = new Matrix4(); function createTileUniformMap(frameState, globeSurfaceTileProvider) { const uniformMap = { u_initialColor: function () { return this.properties.initialColor; }, u_fillHighlightColor: function () { return this.properties.fillHighlightColor; }, u_zoomedOutOceanSpecularIntensity: function () { return this.properties.zoomedOutOceanSpecularIntensity; }, u_oceanNormalMap: function () { return this.properties.oceanNormalMap; }, u_atmosphereLightIntensity: function () { return this.properties.atmosphereLightIntensity; }, u_atmosphereRayleighCoefficient: function () { return this.properties.atmosphereRayleighCoefficient; }, u_atmosphereMieCoefficient: function () { return this.properties.atmosphereMieCoefficient; }, u_atmosphereRayleighScaleHeight: function () { return this.properties.atmosphereRayleighScaleHeight; }, u_atmosphereMieScaleHeight: function () { return this.properties.atmosphereMieScaleHeight; }, u_atmosphereMieAnisotropy: function () { return this.properties.atmosphereMieAnisotropy; }, u_lightingFadeDistance: function () { return this.properties.lightingFadeDistance; }, u_nightFadeDistance: function () { return this.properties.nightFadeDistance; }, u_center3D: function () { return this.properties.center3D; }, u_terrainExaggerationAndRelativeHeight: function () { return this.properties.terrainExaggerationAndRelativeHeight; }, u_tileRectangle: function () { return this.properties.tileRectangle; }, u_modifiedModelView: function () { const viewMatrix = frameState.context.uniformState.view; const centerEye = Matrix4.multiplyByPoint( viewMatrix, this.properties.rtc, centerEyeScratch ); Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewScratch); return modifiedModelViewScratch; }, u_modifiedModelViewProjection: function () { const viewMatrix = frameState.context.uniformState.view; const projectionMatrix = frameState.context.uniformState.projection; const centerEye = Matrix4.multiplyByPoint( viewMatrix, this.properties.rtc, centerEyeScratch ); Matrix4.setTranslation( viewMatrix, centerEye, modifiedModelViewProjectionScratch ); Matrix4.multiply( projectionMatrix, modifiedModelViewProjectionScratch, modifiedModelViewProjectionScratch ); return modifiedModelViewProjectionScratch; }, u_dayTextures: function () { return this.properties.dayTextures; }, u_dayTextureTranslationAndScale: function () { return this.properties.dayTextureTranslationAndScale; }, u_dayTextureTexCoordsRectangle: function () { return this.properties.dayTextureTexCoordsRectangle; }, u_dayTextureUseWebMercatorT: function () { return this.properties.dayTextureUseWebMercatorT; }, u_dayTextureAlpha: function () { return this.properties.dayTextureAlpha; }, u_dayTextureNightAlpha: function () { return this.properties.dayTextureNightAlpha; }, u_dayTextureDayAlpha: function () { return this.properties.dayTextureDayAlpha; }, u_dayTextureBrightness: function () { return this.properties.dayTextureBrightness; }, u_dayTextureContrast: function () { return this.properties.dayTextureContrast; }, u_dayTextureHue: function () { return this.properties.dayTextureHue; }, u_dayTextureSaturation: function () { return this.properties.dayTextureSaturation; }, u_dayTextureOneOverGamma: function () { return this.properties.dayTextureOneOverGamma; }, u_dayIntensity: function () { return this.properties.dayIntensity; }, u_southAndNorthLatitude: function () { return this.properties.southAndNorthLatitude; }, u_southMercatorYAndOneOverHeight: function () { return this.properties.southMercatorYAndOneOverHeight; }, u_waterMask: function () { return this.properties.waterMask; }, u_waterMaskTranslationAndScale: function () { return this.properties.waterMaskTranslationAndScale; }, u_minMaxHeight: function () { return this.properties.minMaxHeight; }, u_scaleAndBias: function () { return this.properties.scaleAndBias; }, u_dayTextureSplit: function () { return this.properties.dayTextureSplit; }, u_dayTextureCutoutRectangles: function () { return this.properties.dayTextureCutoutRectangles; }, u_clippingPlanes: function () { const clippingPlanes = globeSurfaceTileProvider._clippingPlanes; if (defined(clippingPlanes) && defined(clippingPlanes.texture)) { // Check in case clippingPlanes hasn't been updated yet. return clippingPlanes.texture; } return frameState.context.defaultTexture; }, u_cartographicLimitRectangle: function () { return this.properties.localizedCartographicLimitRectangle; }, u_clippingPlanesMatrix: function () { const clippingPlanes = globeSurfaceTileProvider._clippingPlanes; const transform = defined(clippingPlanes) ? Matrix4.multiply( frameState.context.uniformState.view, clippingPlanes.modelMatrix, scratchClippingPlanesMatrix ) : Matrix4.IDENTITY; return Matrix4.inverseTranspose( transform, scratchInverseTransposeClippingPlanesMatrix ); }, u_clippingPlanesEdgeStyle: function () { const style = this.properties.clippingPlanesEdgeColor; style.alpha = this.properties.clippingPlanesEdgeWidth; return style; }, u_minimumBrightness: function () { return frameState.fog.minimumBrightness; }, u_hsbShift: function () { return this.properties.hsbShift; }, u_colorsToAlpha: function () { return this.properties.colorsToAlpha; }, u_frontFaceAlphaByDistance: function () { return this.properties.frontFaceAlphaByDistance; }, u_backFaceAlphaByDistance: function () { return this.properties.backFaceAlphaByDistance; }, u_translucencyRectangle: function () { return this.properties.localizedTranslucencyRectangle; }, u_undergroundColor: function () { return this.properties.undergroundColor; }, u_undergroundColorAlphaByDistance: function () { return this.properties.undergroundColorAlphaByDistance; }, u_lambertDiffuseMultiplier: function () { return this.properties.lambertDiffuseMultiplier; }, // make a separate object so that changes to the properties are seen on // derived commands that combine another uniform map with this one. properties: { initialColor: new Cartesian4(0.0, 0.0, 0.5, 1.0), fillHighlightColor: new Color(0.0, 0.0, 0.0, 0.0), zoomedOutOceanSpecularIntensity: 0.5, oceanNormalMap: undefined, lightingFadeDistance: new Cartesian2(6500000.0, 9000000.0), nightFadeDistance: new Cartesian2(10000000.0, 40000000.0), atmosphereLightIntensity: 10.0, atmosphereRayleighCoefficient: new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6), atmosphereMieCoefficient: new Cartesian3(21e-6, 21e-6, 21e-6), atmosphereRayleighScaleHeight: 10000.0, atmosphereMieScaleHeight: 3200.0, atmosphereMieAnisotropy: 0.9, hsbShift: new Cartesian3(), center3D: undefined, rtc: new Cartesian3(), modifiedModelView: new Matrix4(), tileRectangle: new Cartesian4(), terrainExaggerationAndRelativeHeight: new Cartesian2(1.0, 0.0), dayTextures: [], dayTextureTranslationAndScale: [], dayTextureTexCoordsRectangle: [], dayTextureUseWebMercatorT: [], dayTextureAlpha: [], dayTextureNightAlpha: [], dayTextureDayAlpha: [], dayTextureBrightness: [], dayTextureContrast: [], dayTextureHue: [], dayTextureSaturation: [], dayTextureOneOverGamma: [], dayTextureSplit: [], dayTextureCutoutRectangles: [], dayIntensity: 0.0, colorsToAlpha: [], southAndNorthLatitude: new Cartesian2(), southMercatorYAndOneOverHeight: new Cartesian2(), waterMask: undefined, waterMaskTranslationAndScale: new Cartesian4(), minMaxHeight: new Cartesian2(), scaleAndBias: new Matrix4(), clippingPlanesEdgeColor: Color.clone(Color.WHITE), clippingPlanesEdgeWidth: 0.0, localizedCartographicLimitRectangle: new Cartesian4(), frontFaceAlphaByDistance: new Cartesian4(), backFaceAlphaByDistance: new Cartesian4(), localizedTranslucencyRectangle: new Cartesian4(), undergroundColor: Color.clone(Color.TRANSPARENT), undergroundColorAlphaByDistance: new Cartesian4(), lambertDiffuseMultiplier: 0.0, }, }; if (defined(globeSurfaceTileProvider.materialUniformMap)) { return combine(uniformMap, globeSurfaceTileProvider.materialUniformMap); } return uniformMap; } function createWireframeVertexArrayIfNecessary(context, provider, tile) { const surfaceTile = tile.data; let mesh; let vertexArray; if (defined(surfaceTile.vertexArray)) { mesh = surfaceTile.mesh; vertexArray = surfaceTile.vertexArray; } else if ( defined(surfaceTile.fill) && defined(surfaceTile.fill.vertexArray) ) { mesh = surfaceTile.fill.mesh; vertexArray = surfaceTile.fill.vertexArray; } if (!defined(mesh) || !defined(vertexArray)) { return; } if (defined(surfaceTile.wireframeVertexArray)) { if (surfaceTile.wireframeVertexArray.mesh === mesh) { return; } surfaceTile.wireframeVertexArray.destroy(); surfaceTile.wireframeVertexArray = undefined; } surfaceTile.wireframeVertexArray = createWireframeVertexArray( context, vertexArray, mesh ); surfaceTile.wireframeVertexArray.mesh = mesh; } /** * Creates a vertex array for wireframe rendering of a terrain tile. * * @private * * @param {Context} context The context in which to create the vertex array. * @param {VertexArray} vertexArray The existing, non-wireframe vertex array. The new vertex array * will share vertex buffers with this existing one. * @param {TerrainMesh} terrainMesh The terrain mesh containing non-wireframe indices. * @returns {VertexArray} The vertex array for wireframe rendering. */ function createWireframeVertexArray(context, vertexArray, terrainMesh) { const indices = terrainMesh.indices; const geometry = { indices: indices, primitiveType: PrimitiveType.TRIANGLES, }; GeometryPipeline.toWireframe(geometry); const wireframeIndices = geometry.indices; const wireframeIndexBuffer = Buffer.createIndexBuffer({ context: context, typedArray: wireframeIndices, usage: BufferUsage.STATIC_DRAW, indexDatatype: IndexDatatype.fromSizeInBytes( wireframeIndices.BYTES_PER_ELEMENT ), }); return new VertexArray({ context: context, attributes: vertexArray._attributes, indexBuffer: wireframeIndexBuffer, }); } let getDebugOrientedBoundingBox; let getDebugBoundingSphere; let debugDestroyPrimitive; (function () { const instanceOBB = new GeometryInstance({ geometry: BoxOutlineGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0), }), }); const instanceSphere = new GeometryInstance({ geometry: new SphereOutlineGeometry({ radius: 1.0 }), }); let modelMatrix = new Matrix4(); let previousVolume; let primitive; function createDebugPrimitive(instance) { return new Primitive({ geometryInstances: instance, appearance: new PerInstanceColorAppearance({ translucent: false, flat: true, }), asynchronous: false, }); } getDebugOrientedBoundingBox = function (obb, color) { if (obb === previousVolume) { return primitive; } debugDestroyPrimitive(); previousVolume = obb; modelMatrix = Matrix4.fromRotationTranslation( obb.halfAxes, obb.center, modelMatrix ); instanceOBB.modelMatrix = modelMatrix; instanceOBB.attributes.color = ColorGeometryInstanceAttribute.fromColor( color ); primitive = createDebugPrimitive(instanceOBB); return primitive; }; getDebugBoundingSphere = function (sphere, color) { if (sphere === previousVolume) { return primitive; } debugDestroyPrimitive(); previousVolume = sphere; modelMatrix = Matrix4.fromTranslation(sphere.center, modelMatrix); modelMatrix = Matrix4.multiplyByUniformScale( modelMatrix, sphere.radius, modelMatrix ); instanceSphere.modelMatrix = modelMatrix; instanceSphere.attributes.color = ColorGeometryInstanceAttribute.fromColor( color ); primitive = createDebugPrimitive(instanceSphere); return primitive; }; debugDestroyPrimitive = function () { if (defined(primitive)) { primitive.destroy(); primitive = undefined; previousVolume = undefined; } }; })(); const otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); const surfaceShaderSetOptionsScratch = { frameState: undefined, surfaceTile: undefined, numberOfDayTextures: undefined, applyBrightness: undefined, applyContrast: undefined, applyHue: undefined, applySaturation: undefined, applyGamma: undefined, applyAlpha: undefined, applyDayNightAlpha: undefined, applySplit: undefined, showReflectiveOcean: undefined, showOceanWaves: undefined, enableLighting: undefined, dynamicAtmosphereLighting: undefined, dynamicAtmosphereLightingFromSun: undefined, showGroundAtmosphere: undefined, perFragmentGroundAtmosphere: undefined, hasVertexNormals: undefined, useWebMercatorProjection: undefined, enableFog: undefined, enableClippingPlanes: undefined, clippingPlanes: undefined, clippedByBoundaries: undefined, hasImageryLayerCutout: undefined, colorCorrect: undefined, colorToAlpha: undefined, hasGeodeticSurfaceNormals: undefined, hasExaggeration: undefined, }; const defaultUndergroundColor = Color.TRANSPARENT; const defaultUndergroundColorAlphaByDistance = new NearFarScalar(); function addDrawCommandsForTile(tileProvider, tile, frameState) { const surfaceTile = tile.data; if (!defined(surfaceTile.vertexArray)) { if (surfaceTile.fill === undefined) { // No fill was created for this tile, probably because this tile is not connected to // any renderable tiles. So create a simple tile in the middle of the tile's possible // height range. surfaceTile.fill = new TerrainFillMesh(tile); } surfaceTile.fill.update(tileProvider, frameState); } const creditDisplay = frameState.creditDisplay; const terrainData = surfaceTile.terrainData; if (defined(terrainData) && defined(terrainData.credits)) { const tileCredits = terrainData.credits; for ( let tileCreditIndex = 0, tileCreditLength = tileCredits.length; tileCreditIndex < tileCreditLength; ++tileCreditIndex ) { creditDisplay.addCredit(tileCredits[tileCreditIndex]); } } let maxTextures = ContextLimits.maximumTextureImageUnits; let waterMaskTexture = surfaceTile.waterMaskTexture; let waterMaskTranslationAndScale = surfaceTile.waterMaskTranslationAndScale; if (!defined(waterMaskTexture) && defined(surfaceTile.fill)) { waterMaskTexture = surfaceTile.fill.waterMaskTexture; waterMaskTranslationAndScale = surfaceTile.fill.waterMaskTranslationAndScale; } const cameraUnderground = frameState.cameraUnderground; const globeTranslucencyState = frameState.globeTranslucencyState; const translucent = globeTranslucencyState.translucent; const frontFaceAlphaByDistance = globeTranslucencyState.frontFaceAlphaByDistance; const backFaceAlphaByDistance = globeTranslucencyState.backFaceAlphaByDistance; const translucencyRectangle = globeTranslucencyState.rectangle; const undergroundColor = defaultValue( tileProvider.undergroundColor, defaultUndergroundColor ); const undergroundColorAlphaByDistance = defaultValue( tileProvider.undergroundColorAlphaByDistance, defaultUndergroundColorAlphaByDistance ); const showUndergroundColor = isUndergroundVisible(tileProvider, frameState) && frameState.mode === SceneMode.SCENE3D && undergroundColor.alpha > 0.0 && (undergroundColorAlphaByDistance.nearValue > 0.0 || undergroundColorAlphaByDistance.farValue > 0.0); const lambertDiffuseMultiplier = tileProvider.lambertDiffuseMultiplier; const showReflectiveOcean = tileProvider.hasWaterMask && defined(waterMaskTexture); const oceanNormalMap = tileProvider.oceanNormalMap; const showOceanWaves = showReflectiveOcean && defined(oceanNormalMap); const hasVertexNormals = tileProvider.terrainProvider.ready && tileProvider.terrainProvider.hasVertexNormals; const enableFog = frameState.fog.enabled && frameState.fog.renderable && !cameraUnderground; const showGroundAtmosphere = tileProvider.showGroundAtmosphere && frameState.mode === SceneMode.SCENE3D; const castShadows = ShadowMode.castShadows(tileProvider.shadows) && !translucent; const receiveShadows = ShadowMode.receiveShadows(tileProvider.shadows) && !translucent; const hueShift = tileProvider.hueShift; const saturationShift = tileProvider.saturationShift; const brightnessShift = tileProvider.brightnessShift; let colorCorrect = !( CesiumMath.equalsEpsilon(hueShift, 0.0, CesiumMath.EPSILON7) && CesiumMath.equalsEpsilon(saturationShift, 0.0, CesiumMath.EPSILON7) && CesiumMath.equalsEpsilon(brightnessShift, 0.0, CesiumMath.EPSILON7) ); let perFragmentGroundAtmosphere = false; if (showGroundAtmosphere) { const cameraDistance = Cartesian3.magnitude(frameState.camera.positionWC); const fadeOutDistance = tileProvider.nightFadeOutDistance; perFragmentGroundAtmosphere = cameraDistance > fadeOutDistance; } if (showReflectiveOcean) { --maxTextures; } if (showOceanWaves) { --maxTextures; } if ( defined(frameState.shadowState) && frameState.shadowState.shadowsEnabled ) { --maxTextures; } if ( defined(tileProvider.clippingPlanes) && tileProvider.clippingPlanes.enabled ) { --maxTextures; } maxTextures -= globeTranslucencyState.numberOfTextureUniforms; const mesh = surfaceTile.renderedMesh; let rtc = mesh.center; const encoding = mesh.encoding; const tileBoundingRegion = surfaceTile.tileBoundingRegion; const exaggeration = frameState.terrainExaggeration; const exaggerationRelativeHeight = frameState.terrainExaggerationRelativeHeight; const hasExaggeration = exaggeration !== 1.0; const hasGeodeticSurfaceNormals = encoding.hasGeodeticSurfaceNormals; // Not used in 3D. const tileRectangle = tileRectangleScratch; // Only used for Mercator projections. let southLatitude = 0.0; let northLatitude = 0.0; let southMercatorY = 0.0; let oneOverMercatorHeight = 0.0; let useWebMercatorProjection = false; if (frameState.mode !== SceneMode.SCENE3D) { const projection = frameState.mapProjection; const southwest = projection.project( Rectangle.southwest(tile.rectangle), southwestScratch ); const northeast = projection.project( Rectangle.northeast(tile.rectangle), northeastScratch ); tileRectangle.x = southwest.x; tileRectangle.y = southwest.y; tileRectangle.z = northeast.x; tileRectangle.w = northeast.y; // In 2D and Columbus View, use the center of the tile for RTC rendering. if (frameState.mode !== SceneMode.MORPHING) { rtc = rtcScratch; rtc.x = 0.0; rtc.y = (tileRectangle.z + tileRectangle.x) * 0.5; rtc.z = (tileRectangle.w + tileRectangle.y) * 0.5; tileRectangle.x -= rtc.y; tileRectangle.y -= rtc.z; tileRectangle.z -= rtc.y; tileRectangle.w -= rtc.z; } if ( frameState.mode === SceneMode.SCENE2D && encoding.quantization === TerrainQuantization.BITS12 ) { // In 2D, the texture coordinates of the tile are interpolated over the rectangle to get the position in the vertex shader. // When the texture coordinates are quantized, error is introduced. This can be seen through the 1px wide cracking // between the quantized tiles in 2D. To compensate for the error, move the expand the rectangle in each direction by // half the error amount. const epsilon = (1.0 / (Math.pow(2.0, 12.0) - 1.0)) * 0.5; const widthEpsilon = (tileRectangle.z - tileRectangle.x) * epsilon; const heightEpsilon = (tileRectangle.w - tileRectangle.y) * epsilon; tileRectangle.x -= widthEpsilon; tileRectangle.y -= heightEpsilon; tileRectangle.z += widthEpsilon; tileRectangle.w += heightEpsilon; } if (projection instanceof WebMercatorProjection) { southLatitude = tile.rectangle.south; northLatitude = tile.rectangle.north; southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle( southLatitude ); oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(northLatitude) - southMercatorY); useWebMercatorProjection = true; } } const surfaceShaderSetOptions = surfaceShaderSetOptionsScratch; surfaceShaderSetOptions.frameState = frameState; surfaceShaderSetOptions.surfaceTile = surfaceTile; surfaceShaderSetOptions.showReflectiveOcean = showReflectiveOcean; surfaceShaderSetOptions.showOceanWaves = showOceanWaves; surfaceShaderSetOptions.enableLighting = tileProvider.enableLighting; surfaceShaderSetOptions.dynamicAtmosphereLighting = tileProvider.dynamicAtmosphereLighting; surfaceShaderSetOptions.dynamicAtmosphereLightingFromSun = tileProvider.dynamicAtmosphereLightingFromSun; surfaceShaderSetOptions.showGroundAtmosphere = showGroundAtmosphere; surfaceShaderSetOptions.atmosphereLightIntensity = tileProvider.atmosphereLightIntensity; surfaceShaderSetOptions.atmosphereRayleighCoefficient = tileProvider.atmosphereRayleighCoefficient; surfaceShaderSetOptions.atmosphereMieCoefficient = tileProvider.atmosphereMieCoefficient; surfaceShaderSetOptions.atmosphereRayleighScaleHeight = tileProvider.atmosphereRayleighScaleHeight; surfaceShaderSetOptions.atmosphereMieScaleHeight = tileProvider.atmosphereMieScaleHeight; surfaceShaderSetOptions.atmosphereMieAnisotropy = tileProvider.atmosphereMieAnisotropy; surfaceShaderSetOptions.perFragmentGroundAtmosphere = perFragmentGroundAtmosphere; surfaceShaderSetOptions.hasVertexNormals = hasVertexNormals; surfaceShaderSetOptions.useWebMercatorProjection = useWebMercatorProjection; surfaceShaderSetOptions.clippedByBoundaries = surfaceTile.clippedByBoundaries; surfaceShaderSetOptions.hasGeodeticSurfaceNormals = hasGeodeticSurfaceNormals; surfaceShaderSetOptions.hasExaggeration = hasExaggeration; const tileImageryCollection = surfaceTile.imagery; let imageryIndex = 0; const imageryLen = tileImageryCollection.length; const showSkirts = tileProvider.showSkirts && !cameraUnderground && !translucent; const backFaceCulling = tileProvider.backFaceCulling && !cameraUnderground && !translucent; const firstPassRenderState = backFaceCulling ? tileProvider._renderState : tileProvider._disableCullingRenderState; const otherPassesRenderState = backFaceCulling ? tileProvider._blendRenderState : tileProvider._disableCullingBlendRenderState; let renderState = firstPassRenderState; let initialColor = tileProvider._firstPassInitialColor; const context = frameState.context; if (!defined(tileProvider._debug.boundingSphereTile)) { debugDestroyPrimitive(); } const materialUniformMapChanged = tileProvider._materialUniformMap !== tileProvider.materialUniformMap; if (materialUniformMapChanged) { tileProvider._materialUniformMap = tileProvider.materialUniformMap; const drawCommandsLength = tileProvider._drawCommands.length; for (let i = 0; i < drawCommandsLength; ++i) { tileProvider._uniformMaps[i] = createTileUniformMap( frameState, tileProvider ); } } do { let numberOfDayTextures = 0; let command; let uniformMap; if (tileProvider._drawCommands.length <= tileProvider._usedDrawCommands) { command = new DrawCommand(); command.owner = tile; command.cull = false; command.boundingVolume = new BoundingSphere(); command.orientedBoundingBox = undefined; uniformMap = createTileUniformMap(frameState, tileProvider); tileProvider._drawCommands.push(command); tileProvider._uniformMaps.push(uniformMap); } else { command = tileProvider._drawCommands[tileProvider._usedDrawCommands]; uniformMap = tileProvider._uniformMaps[tileProvider._usedDrawCommands]; } command.owner = tile; ++tileProvider._usedDrawCommands; if (tile === tileProvider._debug.boundingSphereTile) { const obb = tileBoundingRegion.boundingVolume; const boundingSphere = tileBoundingRegion.boundingSphere; // If a debug primitive already exists for this tile, it will not be // re-created, to avoid allocation every frame. If it were possible // to have more than one selected tile, this would have to change. if (defined(obb)) { getDebugOrientedBoundingBox(obb, Color.RED).update(frameState); } else if (defined(boundingSphere)) { getDebugBoundingSphere(boundingSphere, Color.RED).update(frameState); } } const uniformMapProperties = uniformMap.properties; Cartesian4.clone(initialColor, uniformMapProperties.initialColor); uniformMapProperties.oceanNormalMap = oceanNormalMap; uniformMapProperties.lightingFadeDistance.x = tileProvider.lightingFadeOutDistance; uniformMapProperties.lightingFadeDistance.y = tileProvider.lightingFadeInDistance; uniformMapProperties.nightFadeDistance.x = tileProvider.nightFadeOutDistance; uniformMapProperties.nightFadeDistance.y = tileProvider.nightFadeInDistance; uniformMapProperties.atmosphereLightIntensity = tileProvider.atmosphereLightIntensity; uniformMapProperties.atmosphereRayleighCoefficient = tileProvider.atmosphereRayleighCoefficient; uniformMapProperties.atmosphereMieCoefficient = tileProvider.atmosphereMieCoefficient; uniformMapProperties.atmosphereRayleighScaleHeight = tileProvider.atmosphereRayleighScaleHeight; uniformMapProperties.atmosphereMieScaleHeight = tileProvider.atmosphereMieScaleHeight; uniformMapProperties.atmosphereMieAnisotropy = tileProvider.atmosphereMieAnisotropy; uniformMapProperties.zoomedOutOceanSpecularIntensity = tileProvider.zoomedOutOceanSpecularIntensity; const frontFaceAlphaByDistanceFinal = cameraUnderground ? backFaceAlphaByDistance : frontFaceAlphaByDistance; const backFaceAlphaByDistanceFinal = cameraUnderground ? frontFaceAlphaByDistance : backFaceAlphaByDistance; if (defined(frontFaceAlphaByDistanceFinal)) { Cartesian4.fromElements( frontFaceAlphaByDistanceFinal.near, frontFaceAlphaByDistanceFinal.nearValue, frontFaceAlphaByDistanceFinal.far, frontFaceAlphaByDistanceFinal.farValue, uniformMapProperties.frontFaceAlphaByDistance ); Cartesian4.fromElements( backFaceAlphaByDistanceFinal.near, backFaceAlphaByDistanceFinal.nearValue, backFaceAlphaByDistanceFinal.far, backFaceAlphaByDistanceFinal.farValue, uniformMapProperties.backFaceAlphaByDistance ); } Cartesian4.fromElements( undergroundColorAlphaByDistance.near, undergroundColorAlphaByDistance.nearValue, undergroundColorAlphaByDistance.far, undergroundColorAlphaByDistance.farValue, uniformMapProperties.undergroundColorAlphaByDistance ); Color.clone(undergroundColor, uniformMapProperties.undergroundColor); uniformMapProperties.lambertDiffuseMultiplier = lambertDiffuseMultiplier; const highlightFillTile = !defined(surfaceTile.vertexArray) && defined(tileProvider.fillHighlightColor) && tileProvider.fillHighlightColor.alpha > 0.0; if (highlightFillTile) { Color.clone( tileProvider.fillHighlightColor, uniformMapProperties.fillHighlightColor ); } uniformMapProperties.terrainExaggerationAndRelativeHeight.x = exaggeration; uniformMapProperties.terrainExaggerationAndRelativeHeight.y = exaggerationRelativeHeight; uniformMapProperties.center3D = mesh.center; Cartesian3.clone(rtc, uniformMapProperties.rtc); Cartesian4.clone(tileRectangle, uniformMapProperties.tileRectangle); uniformMapProperties.southAndNorthLatitude.x = southLatitude; uniformMapProperties.southAndNorthLatitude.y = northLatitude; uniformMapProperties.southMercatorYAndOneOverHeight.x = southMercatorY; uniformMapProperties.southMercatorYAndOneOverHeight.y = oneOverMercatorHeight; // Convert tile limiter rectangle from cartographic to texture space using the tileRectangle. const localizedCartographicLimitRectangle = localizedCartographicLimitRectangleScratch; const cartographicLimitRectangle = clipRectangleAntimeridian( tile.rectangle, tileProvider.cartographicLimitRectangle ); const localizedTranslucencyRectangle = localizedTranslucencyRectangleScratch; const clippedTranslucencyRectangle = clipRectangleAntimeridian( tile.rectangle, translucencyRectangle ); Cartesian3.fromElements( hueShift, saturationShift, brightnessShift, uniformMapProperties.hsbShift ); const cartographicTileRectangle = tile.rectangle; const inverseTileWidth = 1.0 / cartographicTileRectangle.width; const inverseTileHeight = 1.0 / cartographicTileRectangle.height; localizedCartographicLimitRectangle.x = (cartographicLimitRectangle.west - cartographicTileRectangle.west) * inverseTileWidth; localizedCartographicLimitRectangle.y = (cartographicLimitRectangle.south - cartographicTileRectangle.south) * inverseTileHeight; localizedCartographicLimitRectangle.z = (cartographicLimitRectangle.east - cartographicTileRectangle.west) * inverseTileWidth; localizedCartographicLimitRectangle.w = (cartographicLimitRectangle.north - cartographicTileRectangle.south) * inverseTileHeight; Cartesian4.clone( localizedCartographicLimitRectangle, uniformMapProperties.localizedCartographicLimitRectangle ); localizedTranslucencyRectangle.x = (clippedTranslucencyRectangle.west - cartographicTileRectangle.west) * inverseTileWidth; localizedTranslucencyRectangle.y = (clippedTranslucencyRectangle.south - cartographicTileRectangle.south) * inverseTileHeight; localizedTranslucencyRectangle.z = (clippedTranslucencyRectangle.east - cartographicTileRectangle.west) * inverseTileWidth; localizedTranslucencyRectangle.w = (clippedTranslucencyRectangle.north - cartographicTileRectangle.south) * inverseTileHeight; Cartesian4.clone( localizedTranslucencyRectangle, uniformMapProperties.localizedTranslucencyRectangle ); // For performance, use fog in the shader only when the tile is in fog. const applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > CesiumMath.EPSILON3; colorCorrect = colorCorrect && (applyFog || showGroundAtmosphere); let applyBrightness = false; let applyContrast = false; let applyHue = false; let applySaturation = false; let applyGamma = false; let applyAlpha = false; let applyDayNightAlpha = false; let applySplit = false; let applyCutout = false; let applyColorToAlpha = false; while (numberOfDayTextures < maxTextures && imageryIndex < imageryLen) { const tileImagery = tileImageryCollection[imageryIndex]; const imagery = tileImagery.readyImagery; ++imageryIndex; if (!defined(imagery) || imagery.imageryLayer.alpha === 0.0) { continue; } const texture = tileImagery.useWebMercatorT ? imagery.textureWebMercator : imagery.texture; //>>includeStart('debug', pragmas.debug); if (!defined(texture)) { // Our "ready" texture isn't actually ready. This should never happen. // // Side note: It IS possible for it to not be in the READY ImageryState, though. // This can happen when a single imagery tile is shared by two terrain tiles (common) // and one of them (A) needs a geographic version of the tile because it is near the poles, // and the other (B) does not. B can and will transition the imagery tile to the READY state // without reprojecting to geographic. Then, later, A will deem that same tile not-ready-yet // because it only has the Web Mercator texture, and flip it back to the TRANSITIONING state. // The imagery tile won't be in the READY state anymore, but it's still READY enough for B's // purposes. throw new DeveloperError("readyImagery is not actually ready!"); } //>>includeEnd('debug'); const imageryLayer = imagery.imageryLayer; if (!defined(tileImagery.textureTranslationAndScale)) { tileImagery.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale( tile, tileImagery ); } uniformMapProperties.dayTextures[numberOfDayTextures] = texture; uniformMapProperties.dayTextureTranslationAndScale[numberOfDayTextures] = tileImagery.textureTranslationAndScale; uniformMapProperties.dayTextureTexCoordsRectangle[numberOfDayTextures] = tileImagery.textureCoordinateRectangle; uniformMapProperties.dayTextureUseWebMercatorT[numberOfDayTextures] = tileImagery.useWebMercatorT; uniformMapProperties.dayTextureAlpha[numberOfDayTextures] = imageryLayer.alpha; applyAlpha = applyAlpha || uniformMapProperties.dayTextureAlpha[numberOfDayTextures] !== 1.0; uniformMapProperties.dayTextureNightAlpha[numberOfDayTextures] = imageryLayer.nightAlpha; applyDayNightAlpha = applyDayNightAlpha || uniformMapProperties.dayTextureNightAlpha[numberOfDayTextures] !== 1.0; uniformMapProperties.dayTextureDayAlpha[numberOfDayTextures] = imageryLayer.dayAlpha; applyDayNightAlpha = applyDayNightAlpha || uniformMapProperties.dayTextureDayAlpha[numberOfDayTextures] !== 1.0; uniformMapProperties.dayTextureBrightness[numberOfDayTextures] = imageryLayer.brightness; applyBrightness = applyBrightness || uniformMapProperties.dayTextureBrightness[numberOfDayTextures] !== ImageryLayer.DEFAULT_BRIGHTNESS; uniformMapProperties.dayTextureContrast[numberOfDayTextures] = imageryLayer.contrast; applyContrast = applyContrast || uniformMapProperties.dayTextureContrast[numberOfDayTextures] !== ImageryLayer.DEFAULT_CONTRAST; uniformMapProperties.dayTextureHue[numberOfDayTextures] = imageryLayer.hue; applyHue = applyHue || uniformMapProperties.dayTextureHue[numberOfDayTextures] !== ImageryLayer.DEFAULT_HUE; uniformMapProperties.dayTextureSaturation[numberOfDayTextures] = imageryLayer.saturation; applySaturation = applySaturation || uniformMapProperties.dayTextureSaturation[numberOfDayTextures] !== ImageryLayer.DEFAULT_SATURATION; uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] = 1.0 / imageryLayer.gamma; applyGamma = applyGamma || uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] !== 1.0 / ImageryLayer.DEFAULT_GAMMA; uniformMapProperties.dayTextureSplit[numberOfDayTextures] = imageryLayer.splitDirection; applySplit = applySplit || uniformMapProperties.dayTextureSplit[numberOfDayTextures] !== 0.0; // Update cutout rectangle let dayTextureCutoutRectangle = uniformMapProperties.dayTextureCutoutRectangles[numberOfDayTextures]; if (!defined(dayTextureCutoutRectangle)) { dayTextureCutoutRectangle = uniformMapProperties.dayTextureCutoutRectangles[ numberOfDayTextures ] = new Cartesian4(); } Cartesian4.clone(Cartesian4.ZERO, dayTextureCutoutRectangle); if (defined(imageryLayer.cutoutRectangle)) { const cutoutRectangle = clipRectangleAntimeridian( cartographicTileRectangle, imageryLayer.cutoutRectangle ); const intersection = Rectangle.simpleIntersection( cutoutRectangle, cartographicTileRectangle, rectangleIntersectionScratch ); applyCutout = defined(intersection) || applyCutout; dayTextureCutoutRectangle.x = (cutoutRectangle.west - cartographicTileRectangle.west) * inverseTileWidth; dayTextureCutoutRectangle.y = (cutoutRectangle.south - cartographicTileRectangle.south) * inverseTileHeight; dayTextureCutoutRectangle.z = (cutoutRectangle.east - cartographicTileRectangle.west) * inverseTileWidth; dayTextureCutoutRectangle.w = (cutoutRectangle.north - cartographicTileRectangle.south) * inverseTileHeight; } // Update color to alpha let colorToAlpha = uniformMapProperties.colorsToAlpha[numberOfDayTextures]; if (!defined(colorToAlpha)) { colorToAlpha = uniformMapProperties.colorsToAlpha[ numberOfDayTextures ] = new Cartesian4(); } const hasColorToAlpha = defined(imageryLayer.colorToAlpha) && imageryLayer.colorToAlphaThreshold > 0.0; applyColorToAlpha = applyColorToAlpha || hasColorToAlpha; if (hasColorToAlpha) { const color = imageryLayer.colorToAlpha; colorToAlpha.x = color.red; colorToAlpha.y = color.green; colorToAlpha.z = color.blue; colorToAlpha.w = imageryLayer.colorToAlphaThreshold; } else { colorToAlpha.w = -1.0; } if (defined(imagery.credits)) { const credits = imagery.credits; for ( let creditIndex = 0, creditLength = credits.length; creditIndex < creditLength; ++creditIndex ) { creditDisplay.addCredit(credits[creditIndex]); } } ++numberOfDayTextures; } // trim texture array to the used length so we don't end up using old textures // which might get destroyed eventually uniformMapProperties.dayTextures.length = numberOfDayTextures; uniformMapProperties.waterMask = waterMaskTexture; Cartesian4.clone( waterMaskTranslationAndScale, uniformMapProperties.waterMaskTranslationAndScale ); uniformMapProperties.minMaxHeight.x = encoding.minimumHeight; uniformMapProperties.minMaxHeight.y = encoding.maximumHeight; Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias); // update clipping planes const clippingPlanes = tileProvider._clippingPlanes; const clippingPlanesEnabled = defined(clippingPlanes) && clippingPlanes.enabled && tile.isClipped; if (clippingPlanesEnabled) { uniformMapProperties.clippingPlanesEdgeColor = Color.clone( clippingPlanes.edgeColor, uniformMapProperties.clippingPlanesEdgeColor ); uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth; } surfaceShaderSetOptions.numberOfDayTextures = numberOfDayTextures; surfaceShaderSetOptions.applyBrightness = applyBrightness; surfaceShaderSetOptions.applyContrast = applyContrast; surfaceShaderSetOptions.applyHue = applyHue; surfaceShaderSetOptions.applySaturation = applySaturation; surfaceShaderSetOptions.applyGamma = applyGamma; surfaceShaderSetOptions.applyAlpha = applyAlpha; surfaceShaderSetOptions.applyDayNightAlpha = applyDayNightAlpha; surfaceShaderSetOptions.applySplit = applySplit; surfaceShaderSetOptions.enableFog = applyFog; surfaceShaderSetOptions.enableClippingPlanes = clippingPlanesEnabled; surfaceShaderSetOptions.clippingPlanes = clippingPlanes; surfaceShaderSetOptions.hasImageryLayerCutout = applyCutout; surfaceShaderSetOptions.colorCorrect = colorCorrect; surfaceShaderSetOptions.highlightFillTile = highlightFillTile; surfaceShaderSetOptions.colorToAlpha = applyColorToAlpha; surfaceShaderSetOptions.showUndergroundColor = showUndergroundColor; surfaceShaderSetOptions.translucent = translucent; let count = surfaceTile.renderedMesh.indices.length; if (!showSkirts) { count = surfaceTile.renderedMesh.indexCountWithoutSkirts; } command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram( surfaceShaderSetOptions ); command.castShadows = castShadows; command.receiveShadows = receiveShadows; command.renderState = renderState; command.primitiveType = PrimitiveType.TRIANGLES; command.vertexArray = surfaceTile.vertexArray || surfaceTile.fill.vertexArray; command.count = count; command.uniformMap = uniformMap; command.pass = Pass.GLOBE; if (tileProvider._debug.wireframe) { createWireframeVertexArrayIfNecessary(context, tileProvider, tile); if (defined(surfaceTile.wireframeVertexArray)) { command.vertexArray = surfaceTile.wireframeVertexArray; command.primitiveType = PrimitiveType.LINES; command.count = count * 2; } } let boundingVolume = command.boundingVolume; const orientedBoundingBox = command.orientedBoundingBox; if (frameState.mode !== SceneMode.SCENE3D) { BoundingSphere.fromRectangleWithHeights2D( tile.rectangle, frameState.mapProjection, tileBoundingRegion.minimumHeight, tileBoundingRegion.maximumHeight, boundingVolume ); Cartesian3.fromElements( boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center ); if (frameState.mode === SceneMode.MORPHING) { boundingVolume = BoundingSphere.union( tileBoundingRegion.boundingSphere, boundingVolume, boundingVolume ); } } else { command.boundingVolume = BoundingSphere.clone( tileBoundingRegion.boundingSphere, boundingVolume ); command.orientedBoundingBox = OrientedBoundingBox.clone( tileBoundingRegion.boundingVolume, orientedBoundingBox ); } command.dirty = true; if (translucent) { globeTranslucencyState.updateDerivedCommands(command, frameState); } pushCommand(command, frameState); renderState = otherPassesRenderState; initialColor = otherPassesInitialColor; } while (imageryIndex < imageryLen); } export default GlobeSurfaceTileProvider;