import BoundingSphere from "../Core/BoundingSphere.js"; import buildModuleUrl from "../Core/buildModuleUrl.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartographic from "../Core/Cartographic.js"; import Color from "../Core/Color.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 Ellipsoid from "../Core/Ellipsoid.js"; import EllipsoidTerrainProvider from "../Core/EllipsoidTerrainProvider.js"; import Event from "../Core/Event.js"; import IntersectionTests from "../Core/IntersectionTests.js"; import NearFarScalar from "../Core/NearFarScalar.js"; import Ray from "../Core/Ray.js"; import Rectangle from "../Core/Rectangle.js"; import Resource from "../Core/Resource.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import Texture from "../Renderer/Texture.js"; import GlobeFS from "../Shaders/GlobeFS.js"; import GlobeVS from "../Shaders/GlobeVS.js"; import AtmosphereCommon from "../Shaders/AtmosphereCommon.js"; import GroundAtmosphere from "../Shaders/GroundAtmosphere.js"; import GlobeSurfaceShaderSet from "./GlobeSurfaceShaderSet.js"; import GlobeSurfaceTileProvider from "./GlobeSurfaceTileProvider.js"; import GlobeTranslucency from "./GlobeTranslucency.js"; import ImageryLayerCollection from "./ImageryLayerCollection.js"; import QuadtreePrimitive from "./QuadtreePrimitive.js"; import SceneMode from "./SceneMode.js"; import ShadowMode from "./ShadowMode.js"; /** * The globe rendered in the scene, including its terrain ({@link Globe#terrainProvider}) * and imagery layers ({@link Globe#imageryLayers}). Access the globe using {@link Scene#globe}. * * @alias Globe * @constructor * * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] Determines the size and shape of the * globe. */ function Globe(ellipsoid) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); const terrainProvider = new EllipsoidTerrainProvider({ ellipsoid: ellipsoid, }); const imageryLayerCollection = new ImageryLayerCollection(); this._ellipsoid = ellipsoid; this._imageryLayerCollection = imageryLayerCollection; this._surfaceShaderSet = new GlobeSurfaceShaderSet(); this._material = undefined; this._surface = new QuadtreePrimitive({ tileProvider: new GlobeSurfaceTileProvider({ terrainProvider: terrainProvider, imageryLayers: imageryLayerCollection, surfaceShaderSet: this._surfaceShaderSet, }), }); this._terrainProvider = terrainProvider; this._terrainProviderChanged = new Event(); this._undergroundColor = Color.clone(Color.BLACK); this._undergroundColorAlphaByDistance = new NearFarScalar( ellipsoid.maximumRadius / 1000.0, 0.0, ellipsoid.maximumRadius / 5.0, 1.0 ); this._translucency = new GlobeTranslucency(); makeShadersDirty(this); /** * Determines if the globe will be shown. * * @type {boolean} * @default true */ this.show = true; this._oceanNormalMapResourceDirty = true; this._oceanNormalMapResource = new Resource({ url: buildModuleUrl("Assets/Textures/waterNormalsSmall.jpg"), }); /** * The maximum screen-space error used to drive level-of-detail refinement. Higher * values will provide better performance but lower visual quality. * * @type {number} * @default 2 */ this.maximumScreenSpaceError = 2; /** * The size of the terrain tile cache, expressed as a number of tiles. Any additional * tiles beyond this number will be freed, as long as they aren't needed for rendering * this frame. A larger number will consume more memory but will show detail faster * when, for example, zooming out and then back in. * * @type {number} * @default 100 */ this.tileCacheSize = 100; /** * Gets or sets the number of loading descendant tiles that is considered "too many". * If a tile has too many loading descendants, that tile will be loaded and rendered before any of * its descendants are loaded and rendered. This means more feedback for the user that something * is happening at the cost of a longer overall load time. Setting this to 0 will cause each * tile level to be loaded successively, significantly increasing load time. Setting it to a large * number (e.g. 1000) will minimize the number of tiles that are loaded but tend to make * detail appear all at once after a long wait. * @type {number} * @default 20 */ this.loadingDescendantLimit = 20; /** * Gets or sets a value indicating whether the ancestors of rendered tiles should be preloaded. * Setting this to true optimizes the zoom-out experience and provides more detail in * newly-exposed areas when panning. The down side is that it requires loading more tiles. * @type {boolean} * @default true */ this.preloadAncestors = true; /** * Gets or sets a value indicating whether the siblings of rendered tiles should be preloaded. * Setting this to true causes tiles with the same parent as a rendered tile to be loaded, even * if they are culled. Setting this to true may provide a better panning experience at the * cost of loading more tiles. * @type {boolean} * @default false */ this.preloadSiblings = false; /** * 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; /** * Enable lighting the globe with the scene's light source. * * @type {boolean} * @default false */ this.enableLighting = false; /** * A multiplier to adjust terrain lambert lighting. * This number is multiplied by the result of czm_getLambertDiffuse in GlobeFS.glsl. * This only takes effect when enableLighting is true. * * @type {number} * @default 0.9 */ this.lambertDiffuseMultiplier = 0.9; /** * Enable dynamic lighting effects on atmosphere and fog. This only takes effect * when enableLighting is true. * * @type {boolean} * @default true */ this.dynamicAtmosphereLighting = true; /** * Whether dynamic atmosphere lighting uses the sun direction instead of the scene's * light direction. This only takes effect when enableLighting and * dynamicAtmosphereLighting are true. * * @type {boolean} * @default false */ this.dynamicAtmosphereLightingFromSun = false; /** * Enable the ground atmosphere, which is drawn over the globe when viewed from a distance between lightingFadeInDistance and lightingFadeOutDistance. * * @type {boolean} * @default true */ this.showGroundAtmosphere = true; /** * The intensity of the light that is used for computing the ground atmosphere color. * * @type {number} * @default 10.0 */ this.atmosphereLightIntensity = 10.0; /** * The Rayleigh scattering coefficient used in the atmospheric scattering equations for the ground atmosphere. * * @type {Cartesian3} * @default Cartesian3(5.5e-6, 13.0e-6, 28.4e-6) */ this.atmosphereRayleighCoefficient = new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6); /** * The Mie scattering coefficient used in the atmospheric scattering equations for the ground atmosphere. * * @type {Cartesian3} * @default Cartesian3(21e-6, 21e-6, 21e-6) */ this.atmosphereMieCoefficient = new Cartesian3(21e-6, 21e-6, 21e-6); /** * The Rayleigh scale height used in the atmospheric scattering equations for the ground atmosphere, in meters. * * @type {number} * @default 10000.0 */ this.atmosphereRayleighScaleHeight = 10000.0; /** * The Mie scale height used in the atmospheric scattering equations for the ground atmosphere, in meters. * * @type {number} * @default 3200.0 */ this.atmosphereMieScaleHeight = 3200.0; /** * The anisotropy of the medium to consider for Mie scattering. *

* Valid values are between -1.0 and 1.0. *

* @type {number} * @default 0.9 */ this.atmosphereMieAnisotropy = 0.9; /** * The distance where everything becomes lit. This only takes effect * when enableLighting or showGroundAtmosphere is true. * * @type {number} * @default 10000000.0 */ this.lightingFadeOutDistance = 1.0e7; /** * The distance where lighting resumes. This only takes effect * when enableLighting or showGroundAtmosphere is true. * * @type {number} * @default 20000000.0 */ this.lightingFadeInDistance = 2.0e7; /** * The distance where the darkness of night from the ground atmosphere fades out to a lit ground atmosphere. * This only takes effect when showGroundAtmosphere, enableLighting, and * dynamicAtmosphereLighting are true. * * @type {number} * @default 10000000.0 */ this.nightFadeOutDistance = 1.0e7; /** * The distance where the darkness of night from the ground atmosphere fades in to an unlit ground atmosphere. * This only takes effect when showGroundAtmosphere, enableLighting, and * dynamicAtmosphereLighting are true. * * @type {number} * @default 50000000.0 */ this.nightFadeInDistance = 5.0e7; /** * True if an animated wave effect should be shown in areas of the globe * covered by water; otherwise, false. This property is ignored if the * terrainProvider does not provide a water mask. * * @type {boolean} * @default true */ this.showWaterEffect = true; /** * True if primitives such as billboards, polylines, labels, etc. should be depth-tested * against the terrain surface, or false if such primitives should always be drawn on top * of terrain unless they're on the opposite side of the globe. The disadvantage of depth * testing primitives against terrain is that slight numerical noise or terrain level-of-detail * switched can sometimes make a primitive that should be on the surface disappear underneath it. * * @type {boolean} * @default false * */ this.depthTestAgainstTerrain = false; /** * Determines whether the globe casts or receives shadows from light sources. Setting the globe * to cast shadows may impact performance since the terrain is rendered again from the light's perspective. * Currently only terrain that is in view casts shadows. By default the globe does not cast shadows. * * @type {ShadowMode} * @default ShadowMode.RECEIVE_ONLY */ this.shadows = ShadowMode.RECEIVE_ONLY; /** * The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift). * A hue shift of 1.0 indicates a complete rotation of the hues available. * @type {number} * @default 0.0 */ this.atmosphereHueShift = 0.0; /** * The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift). * A saturation shift of -1.0 is monochrome. * @type {number} * @default 0.0 */ this.atmosphereSaturationShift = 0.0; /** * The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift). * A brightness shift of -1.0 is complete darkness, which will let space show through. * @type {number} * @default 0.0 */ this.atmosphereBrightnessShift = 0.0; /** * A scalar used to exaggerate the terrain. Defaults to 1.0 (no exaggeration). * A value of 2.0 scales the terrain by 2x. * A value of 0.0 makes the terrain completely flat. * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid. * @type {number} * @default 1.0 */ this.terrainExaggeration = 1.0; /** * The height from which terrain is exaggerated. Defaults to 0.0 (scaled relative to ellipsoid surface). * Terrain that is above this height will scale upwards and terrain that is below this height will scale downwards. * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid. * If {@link Globe#terrainExaggeration} is 1.0 this value will have no effect. * @type {number} * @default 0.0 */ this.terrainExaggerationRelativeHeight = 0.0; /** * Whether to show terrain skirts. Terrain skirts are geometry extending downwards from a tile's edges used to hide seams between neighboring tiles. * Skirts are always hidden when the camera is underground or translucency is enabled. * * @type {boolean} * @default true */ this.showSkirts = true; /** * Whether to cull back-facing terrain. Back faces are not culled when the camera is underground or translucency is enabled. * * @type {boolean} * @default true */ this.backFaceCulling = true; this._oceanNormalMap = undefined; this._zoomedOutOceanSpecularIntensity = undefined; /** * Determines the darkness of the vertex shadow. * This only takes effect when enableLighting is true. * * @type {number} * @default 0.3 */ this.vertexShadowDarkness = 0.3; } Object.defineProperties(Globe.prototype, { /** * Gets an ellipsoid describing the shape of this globe. * @memberof Globe.prototype * @type {Ellipsoid} */ ellipsoid: { get: function () { return this._ellipsoid; }, }, /** * Gets the collection of image layers that will be rendered on this globe. * @memberof Globe.prototype * @type {ImageryLayerCollection} */ imageryLayers: { get: function () { return this._imageryLayerCollection; }, }, /** * Gets an event that's raised when an imagery layer is added, shown, hidden, moved, or removed. * * @memberof Globe.prototype * @type {Event} * @readonly */ imageryLayersUpdatedEvent: { get: function () { return this._surface.tileProvider.imageryLayersUpdatedEvent; }, }, /** * Returns true when the tile load queue is empty, false otherwise. When the load queue is empty, * all terrain and imagery for the current view have been loaded. * @memberof Globe.prototype * @type {boolean} * @readonly */ tilesLoaded: { get: function () { if (!defined(this._surface)) { return true; } return ( // ready is deprecated. This is here for backwards compatibility this._surface.tileProvider.ready && this._surface._tileLoadQueueHigh.length === 0 && this._surface._tileLoadQueueMedium.length === 0 && this._surface._tileLoadQueueLow.length === 0 ); }, }, /** * Gets or sets the color of the globe when no imagery is available. * @memberof Globe.prototype * @type {Color} */ baseColor: { get: function () { return this._surface.tileProvider.baseColor; }, set: function (value) { this._surface.tileProvider.baseColor = value; }, }, /** * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane. * * @memberof Globe.prototype * @type {ClippingPlaneCollection} */ clippingPlanes: { get: function () { return this._surface.tileProvider.clippingPlanes; }, set: function (value) { this._surface.tileProvider.clippingPlanes = value; }, }, /** * A property specifying a {@link Rectangle} used to limit globe rendering to a cartographic area. * Defaults to the maximum extent of cartographic coordinates. * * @memberof Globe.prototype * @type {Rectangle} * @default {@link Rectangle.MAX_VALUE} */ cartographicLimitRectangle: { get: function () { return this._surface.tileProvider.cartographicLimitRectangle; }, set: function (value) { if (!defined(value)) { value = Rectangle.clone(Rectangle.MAX_VALUE); } this._surface.tileProvider.cartographicLimitRectangle = value; }, }, /** * The normal map to use for rendering waves in the ocean. Setting this property will * only have an effect if the configured terrain provider includes a water mask. * @memberof Globe.prototype * @type {string} * @default buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg') */ oceanNormalMapUrl: { get: function () { return this._oceanNormalMapResource.url; }, set: function (value) { this._oceanNormalMapResource.url = value; this._oceanNormalMapResourceDirty = true; }, }, /** * The terrain provider providing surface geometry for this globe. * @type {TerrainProvider} * * @memberof Globe.prototype * @type {TerrainProvider} * */ terrainProvider: { get: function () { return this._terrainProvider; }, set: function (value) { if (value !== this._terrainProvider) { this._terrainProvider = value; this._terrainProviderChanged.raiseEvent(value); if (defined(this._material)) { makeShadersDirty(this); } } }, }, /** * Gets an event that's raised when the terrain provider is changed * * @memberof Globe.prototype * @type {Event} * @readonly */ terrainProviderChanged: { get: function () { return this._terrainProviderChanged; }, }, /** * Gets an event that's raised when the length of the tile load queue has changed since the last render frame. When the load queue is empty, * all terrain and imagery for the current view have been loaded. The event passes the new length of the tile load queue. * * @memberof Globe.prototype * @type {Event} */ tileLoadProgressEvent: { get: function () { return this._surface.tileLoadProgressEvent; }, }, /** * Gets or sets the material appearance of the Globe. This can be one of several built-in {@link Material} objects or a custom material, scripted with * {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric}. * @memberof Globe.prototype * @type {Material | undefined} */ material: { get: function () { return this._material; }, set: function (material) { if (this._material !== material) { this._material = material; makeShadersDirty(this); } }, }, /** * The color to render the back side of the globe when the camera is underground or the globe is translucent, * blended with the globe color based on the camera's distance. *

* To disable underground coloring, set undergroundColor to undefined. * * @memberof Globe.prototype * @type {Color} * @default {@link Color.BLACK} * * @see Globe#undergroundColorAlphaByDistance */ undergroundColor: { get: function () { return this._undergroundColor; }, set: function (value) { this._undergroundColor = Color.clone(value, this._undergroundColor); }, }, /** * Gets or sets the near and far distance for blending {@link Globe#undergroundColor} with the globe color. * The alpha will interpolate between the {@link NearFarScalar#nearValue} and * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the alpha remains clamped to the nearest bound. If undefined, * the underground color will not be blended with the globe color. *

* When the camera is above the ellipsoid the distance is computed from the nearest * point on the ellipsoid instead of the camera's position. * * @memberof Globe.prototype * @type {NearFarScalar} * * @see Globe#undergroundColor * */ undergroundColorAlphaByDistance: { get: function () { return this._undergroundColorAlphaByDistance; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (defined(value) && value.far < value.near) { throw new DeveloperError( "far distance must be greater than near distance." ); } //>>includeEnd('debug'); this._undergroundColorAlphaByDistance = NearFarScalar.clone( value, this._undergroundColorAlphaByDistance ); }, }, /** * Properties for controlling globe translucency. * * @memberof Globe.prototype * @type {GlobeTranslucency} */ translucency: { get: function () { return this._translucency; }, }, }); function makeShadersDirty(globe) { const defines = []; const requireNormals = defined(globe._material) && (globe._material.shaderSource.match(/slope/) || globe._material.shaderSource.match("normalEC")); const fragmentSources = [AtmosphereCommon, GroundAtmosphere]; if ( defined(globe._material) && (!requireNormals || globe._terrainProvider.requestVertexNormals) ) { fragmentSources.push(globe._material.shaderSource); defines.push("APPLY_MATERIAL"); globe._surface._tileProvider.materialUniformMap = globe._material._uniforms; } else { globe._surface._tileProvider.materialUniformMap = undefined; } fragmentSources.push(GlobeFS); globe._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({ sources: [AtmosphereCommon, GroundAtmosphere, GlobeVS], defines: defines, }); globe._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({ sources: fragmentSources, defines: defines, }); globe._surfaceShaderSet.material = globe._material; } function createComparePickTileFunction(rayOrigin) { return function (a, b) { const aDist = BoundingSphere.distanceSquaredTo( a.pickBoundingSphere, rayOrigin ); const bDist = BoundingSphere.distanceSquaredTo( b.pickBoundingSphere, rayOrigin ); return aDist - bDist; }; } const scratchArray = []; const scratchSphereIntersectionResult = { start: 0.0, stop: 0.0, }; /** * Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {Scene} scene The scene. * @param {boolean} [cullBackFaces=true] Set to true to not pick back faces. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. The returned position is in projected coordinates for 2D and Columbus View. * * @private */ Globe.prototype.pickWorldCoordinates = function ( ray, scene, cullBackFaces, result ) { //>>includeStart('debug', pragmas.debug); if (!defined(ray)) { throw new DeveloperError("ray is required"); } if (!defined(scene)) { throw new DeveloperError("scene is required"); } //>>includeEnd('debug'); cullBackFaces = defaultValue(cullBackFaces, true); const mode = scene.mode; const projection = scene.mapProjection; const sphereIntersections = scratchArray; sphereIntersections.length = 0; const tilesToRender = this._surface._tilesToRender; let length = tilesToRender.length; let tile; let i; for (i = 0; i < length; ++i) { tile = tilesToRender[i]; const surfaceTile = tile.data; if (!defined(surfaceTile)) { continue; } let boundingVolume = surfaceTile.pickBoundingSphere; if (mode !== SceneMode.SCENE3D) { surfaceTile.pickBoundingSphere = boundingVolume = BoundingSphere.fromRectangleWithHeights2D( tile.rectangle, projection, surfaceTile.tileBoundingRegion.minimumHeight, surfaceTile.tileBoundingRegion.maximumHeight, boundingVolume ); Cartesian3.fromElements( boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center ); } else if (defined(surfaceTile.renderedMesh)) { BoundingSphere.clone( surfaceTile.tileBoundingRegion.boundingSphere, boundingVolume ); } else { // So wait how did we render this thing then? It shouldn't be possible to get here. continue; } const boundingSphereIntersection = IntersectionTests.raySphere( ray, boundingVolume, scratchSphereIntersectionResult ); if (defined(boundingSphereIntersection)) { sphereIntersections.push(surfaceTile); } } sphereIntersections.sort(createComparePickTileFunction(ray.origin)); let intersection; length = sphereIntersections.length; for (i = 0; i < length; ++i) { intersection = sphereIntersections[i].pick( ray, scene.mode, scene.mapProjection, cullBackFaces, result ); if (defined(intersection)) { break; } } return intersection; }; const cartoScratch = new Cartographic(); /** * Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {Scene} scene The scene. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @example * // find intersection of ray through a pixel and the globe * const ray = viewer.camera.getPickRay(windowCoordinates); * const intersection = globe.pick(ray, scene); */ Globe.prototype.pick = function (ray, scene, result) { result = this.pickWorldCoordinates(ray, scene, true, result); if (defined(result) && scene.mode !== SceneMode.SCENE3D) { result = Cartesian3.fromElements(result.y, result.z, result.x, result); const carto = scene.mapProjection.unproject(result, cartoScratch); result = scene.globe.ellipsoid.cartographicToCartesian(carto, result); } return result; }; const scratchGetHeightCartesian = new Cartesian3(); const scratchGetHeightIntersection = new Cartesian3(); const scratchGetHeightCartographic = new Cartographic(); const scratchGetHeightRay = new Ray(); function tileIfContainsCartographic(tile, cartographic) { return defined(tile) && Rectangle.contains(tile.rectangle, cartographic) ? tile : undefined; } /** * Get the height of the surface at a given cartographic. * * @param {Cartographic} cartographic The cartographic for which to find the height. * @returns {number|undefined} The height of the cartographic or undefined if it could not be found. */ Globe.prototype.getHeight = function (cartographic) { //>>includeStart('debug', pragmas.debug); if (!defined(cartographic)) { throw new DeveloperError("cartographic is required"); } //>>includeEnd('debug'); const levelZeroTiles = this._surface._levelZeroTiles; if (!defined(levelZeroTiles)) { return; } let tile; let i; const length = levelZeroTiles.length; for (i = 0; i < length; ++i) { tile = levelZeroTiles[i]; if (Rectangle.contains(tile.rectangle, cartographic)) { break; } } if (i >= length) { return undefined; } let tileWithMesh = tile; while (defined(tile)) { tile = tileIfContainsCartographic(tile._southwestChild, cartographic) || tileIfContainsCartographic(tile._southeastChild, cartographic) || tileIfContainsCartographic(tile._northwestChild, cartographic) || tile._northeastChild; if ( defined(tile) && defined(tile.data) && defined(tile.data.renderedMesh) ) { tileWithMesh = tile; } } tile = tileWithMesh; // This tile was either rendered or culled. // It is sometimes useful to get a height from a culled tile, // e.g. when we're getting a height in order to place a billboard // on terrain, and the camera is looking at that same billboard. // The culled tile must have a valid mesh, though. if ( !defined(tile) || !defined(tile.data) || !defined(tile.data.renderedMesh) ) { // Tile was not rendered (culled). return undefined; } const projection = this._surface._tileProvider.tilingScheme.projection; const ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid; //cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal` const cartesian = Cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, 0.0, ellipsoid, scratchGetHeightCartesian ); const ray = scratchGetHeightRay; const surfaceNormal = ellipsoid.geodeticSurfaceNormal( cartesian, ray.direction ); // Try to find the intersection point between the surface normal and z-axis. // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider const rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis( cartesian, 11500.0, ray.origin ); // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid if (!defined(rayOrigin)) { // intersection point is outside the ellipsoid, try other value // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider let minimumHeight; if (defined(tile.data.tileBoundingRegion)) { minimumHeight = tile.data.tileBoundingRegion.minimumHeight; } const magnitude = Math.min(defaultValue(minimumHeight, 0.0), -11500.0); // multiply by the *positive* value of the magnitude const vectorToMinimumPoint = Cartesian3.multiplyByScalar( surfaceNormal, Math.abs(magnitude) + 1, scratchGetHeightIntersection ); Cartesian3.subtract(cartesian, vectorToMinimumPoint, ray.origin); } const intersection = tile.data.pick( ray, undefined, projection, false, scratchGetHeightIntersection ); if (!defined(intersection)) { return undefined; } return ellipsoid.cartesianToCartographic( intersection, scratchGetHeightCartographic ).height; }; /** * @private */ Globe.prototype.update = function (frameState) { if (!this.show) { return; } if (frameState.passes.render) { this._surface.update(frameState); } }; /** * @private */ Globe.prototype.beginFrame = function (frameState) { const surface = this._surface; const tileProvider = surface.tileProvider; const terrainProvider = this.terrainProvider; const hasWaterMask = this.showWaterEffect && defined(terrainProvider) && terrainProvider.hasWaterMask && // ready is deprecated; This is here for backwards compatibility terrainProvider._ready && terrainProvider.hasWaterMask; if (hasWaterMask && this._oceanNormalMapResourceDirty) { // url changed, load new normal map asynchronously this._oceanNormalMapResourceDirty = false; const oceanNormalMapResource = this._oceanNormalMapResource; const oceanNormalMapUrl = oceanNormalMapResource.url; if (defined(oceanNormalMapUrl)) { const that = this; oceanNormalMapResource.fetchImage().then(function (image) { if (oceanNormalMapUrl !== that._oceanNormalMapResource.url) { // url changed while we were loading return; } that._oceanNormalMap = that._oceanNormalMap && that._oceanNormalMap.destroy(); that._oceanNormalMap = new Texture({ context: frameState.context, source: image, }); }); } else { this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy(); } } const pass = frameState.passes; const mode = frameState.mode; if (pass.render) { if (this.showGroundAtmosphere) { this._zoomedOutOceanSpecularIntensity = 0.4; } else { this._zoomedOutOceanSpecularIntensity = 0.5; } surface.maximumScreenSpaceError = this.maximumScreenSpaceError; surface.tileCacheSize = this.tileCacheSize; surface.loadingDescendantLimit = this.loadingDescendantLimit; surface.preloadAncestors = this.preloadAncestors; surface.preloadSiblings = this.preloadSiblings; tileProvider.terrainProvider = this.terrainProvider; tileProvider.lightingFadeOutDistance = this.lightingFadeOutDistance; tileProvider.lightingFadeInDistance = this.lightingFadeInDistance; tileProvider.nightFadeOutDistance = this.nightFadeOutDistance; tileProvider.nightFadeInDistance = this.nightFadeInDistance; tileProvider.zoomedOutOceanSpecularIntensity = mode === SceneMode.SCENE3D ? this._zoomedOutOceanSpecularIntensity : 0.0; tileProvider.hasWaterMask = hasWaterMask; tileProvider.oceanNormalMap = this._oceanNormalMap; tileProvider.enableLighting = this.enableLighting; tileProvider.dynamicAtmosphereLighting = this.dynamicAtmosphereLighting; tileProvider.dynamicAtmosphereLightingFromSun = this.dynamicAtmosphereLightingFromSun; tileProvider.showGroundAtmosphere = this.showGroundAtmosphere; tileProvider.atmosphereLightIntensity = this.atmosphereLightIntensity; tileProvider.atmosphereRayleighCoefficient = this.atmosphereRayleighCoefficient; tileProvider.atmosphereMieCoefficient = this.atmosphereMieCoefficient; tileProvider.atmosphereRayleighScaleHeight = this.atmosphereRayleighScaleHeight; tileProvider.atmosphereMieScaleHeight = this.atmosphereMieScaleHeight; tileProvider.atmosphereMieAnisotropy = this.atmosphereMieAnisotropy; tileProvider.shadows = this.shadows; tileProvider.hueShift = this.atmosphereHueShift; tileProvider.saturationShift = this.atmosphereSaturationShift; tileProvider.brightnessShift = this.atmosphereBrightnessShift; tileProvider.fillHighlightColor = this.fillHighlightColor; tileProvider.showSkirts = this.showSkirts; tileProvider.backFaceCulling = this.backFaceCulling; tileProvider.vertexShadowDarkness = this.vertexShadowDarkness; tileProvider.undergroundColor = this._undergroundColor; tileProvider.undergroundColorAlphaByDistance = this._undergroundColorAlphaByDistance; tileProvider.lambertDiffuseMultiplier = this.lambertDiffuseMultiplier; surface.beginFrame(frameState); } }; /** * @private */ Globe.prototype.render = function (frameState) { if (!this.show) { return; } if (defined(this._material)) { this._material.update(frameState.context); } this._surface.render(frameState); }; /** * @private */ Globe.prototype.endFrame = function (frameState) { if (!this.show) { return; } if (frameState.passes.render) { this._surface.endFrame(frameState); } }; /** * 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 Globe#destroy */ Globe.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 * globe = globe && globe.destroy(); * * @see Globe#isDestroyed */ Globe.prototype.destroy = function () { this._surfaceShaderSet = this._surfaceShaderSet && this._surfaceShaderSet.destroy(); this._surface = this._surface && this._surface.destroy(); this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy(); return destroyObject(this); }; export default Globe;