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 * whenenableLighting
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.
* 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.
* 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.
* 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.
* 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;