/*global define*/
define([
'../Core/BoundingRectangle',
'../Core/BoundingSphere',
'../Core/buildModuleUrl',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/combine',
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/EllipsoidTerrainProvider',
'../Core/FeatureDetection',
'../Core/GeographicProjection',
'../Core/Geometry',
'../Core/GeometryAttribute',
'../Core/Intersect',
'../Core/IntersectionTests',
'../Core/loadImage',
'../Core/Math',
'../Core/Matrix4',
'../Core/Occluder',
'../Core/PrimitiveType',
'../Core/Ray',
'../Core/Rectangle',
'../Core/Transforms',
'../Renderer/BufferUsage',
'../Renderer/ClearCommand',
'../Renderer/DrawCommand',
'../Renderer/ShaderSource',
'../Shaders/GlobeFS',
'../Shaders/GlobeFSDepth',
'../Shaders/GlobeFSPole',
'../Shaders/GlobeVS',
'../Shaders/GlobeVSDepth',
'../Shaders/GlobeVSPole',
'../ThirdParty/when',
'./DepthFunction',
'./GlobeSurfaceShaderSet',
'./GlobeSurfaceTileProvider',
'./ImageryLayerCollection',
'./Pass',
'./QuadtreePrimitive',
'./SceneMode',
'./terrainAttributeLocations'
], function(
BoundingRectangle,
BoundingSphere,
buildModuleUrl,
Cartesian2,
Cartesian3,
Cartographic,
combine,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Ellipsoid,
EllipsoidTerrainProvider,
FeatureDetection,
GeographicProjection,
Geometry,
GeometryAttribute,
Intersect,
IntersectionTests,
loadImage,
CesiumMath,
Matrix4,
Occluder,
PrimitiveType,
Ray,
Rectangle,
Transforms,
BufferUsage,
ClearCommand,
DrawCommand,
ShaderSource,
GlobeFS,
GlobeFSDepth,
GlobeFSPole,
GlobeVS,
GlobeVSDepth,
GlobeVSPole,
when,
DepthFunction,
GlobeSurfaceShaderSet,
GlobeSurfaceTileProvider,
ImageryLayerCollection,
Pass,
QuadtreePrimitive,
SceneMode,
terrainAttributeLocations) {
"use strict";
/**
* 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.
*/
var Globe = function(ellipsoid) {
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
var terrainProvider = new EllipsoidTerrainProvider({
ellipsoid : ellipsoid
});
var imageryLayerCollection = new ImageryLayerCollection();
this._ellipsoid = ellipsoid;
this._imageryLayerCollection = imageryLayerCollection;
this._surfaceShaderSet = new GlobeSurfaceShaderSet();
this._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
sources : [GlobeVS]
});
this._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
sources : [GlobeFS]
});
this._surface = new QuadtreePrimitive({
tileProvider : new GlobeSurfaceTileProvider({
terrainProvider : terrainProvider,
imageryLayers : imageryLayerCollection,
surfaceShaderSet : this._surfaceShaderSet
})
});
this._occluder = new Occluder(new BoundingSphere(Cartesian3.ZERO, ellipsoid.minimumRadius), Cartesian3.ZERO);
this._rsColor = undefined;
this._rsColorWithoutDepthTest = undefined;
this._clearDepthCommand = new ClearCommand({
depth : 1.0,
stencil : 0,
owner : this
});
this._depthCommand = new DrawCommand({
boundingVolume : new BoundingSphere(Cartesian3.ZERO, ellipsoid.maximumRadius),
pass : Pass.OPAQUE,
owner : this
});
this._northPoleCommand = new DrawCommand({
pass : Pass.OPAQUE,
owner : this
});
this._southPoleCommand = new DrawCommand({
pass : Pass.OPAQUE,
owner : this
});
this._drawNorthPole = false;
this._drawSouthPole = false;
this._mode = SceneMode.SCENE3D;
/**
* The terrain provider providing surface geometry for this globe.
* @type {TerrainProvider}
*/
this.terrainProvider = terrainProvider;
/**
* Determines the color of the north pole. If the day tile provider imagery does not
* extend over the north pole, it will be filled with this color before applying lighting.
*
* @type {Cartesian3}
* @default Cartesian3(2.0 / 255.0, 6.0 / 255.0, 18.0 / 255.0)
*/
this.northPoleColor = new Cartesian3(2.0 / 255.0, 6.0 / 255.0, 18.0 / 255.0);
/**
* Determines the color of the south pole. If the day tile provider imagery does not
* extend over the south pole, it will be filled with this color before applying lighting.
*
* @type {Cartesian3}
* @default Cartesian3(1.0, 1.0, 1.0)
*/
this.southPoleColor = new Cartesian3(1.0, 1.0, 1.0);
/**
* Determines if the globe will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = true;
/**
* 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.
*
* @type {String}
* @default buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg')
*/
this.oceanNormalMapUrl = buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg');
this._oceanNormalMapUrl = undefined;
/**
* 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;
/**
* 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;
/**
* Enable lighting the globe with the sun as a light source.
*
* @type {Boolean}
* @default false
*/
this.enableLighting = false;
/**
* The distance where everything becomes lit. This only takes effect
* when enableLighting
is true
.
*
* @type {Number}
* @default 6500000.0
*/
this.lightingFadeOutDistance = 6500000.0;
/**
* The distance where lighting resumes. This only takes effect
* when enableLighting
is true
.
*
* @type {Number}
* @default 9000000.0
*/
this.lightingFadeInDistance = 9000000.0;
/**
* 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;
this._oceanNormalMap = undefined;
this._zoomedOutOceanSpecularIntensity = 0.5;
this._lightingFadeDistance = new Cartesian2(this.lightingFadeOutDistance, this.lightingFadeInDistance);
var that = this;
this._drawUniforms = {
u_zoomedOutOceanSpecularIntensity : function() {
return that._zoomedOutOceanSpecularIntensity;
},
u_oceanNormalMap : function() {
return that._oceanNormalMap;
},
u_lightingFadeDistance : function() {
return that._lightingFadeDistance;
}
};
};
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 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;
}
}
});
function createComparePickTileFunction(rayOrigin) {
return function(a, b) {
var aDist = BoundingSphere.distanceSquaredTo(a.pickBoundingSphere, rayOrigin);
var bDist = BoundingSphere.distanceSquaredTo(b.pickBoundingSphere, rayOrigin);
return aDist - bDist;
};
}
var scratchArray = [];
var 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 {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
* var ray = viewer.camera.getPickRay(windowCoordinates);
* var intersection = globe.pick(ray, scene);
*/
Globe.prototype.pick = function(ray, scene, 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');
var mode = scene.mode;
var projection = scene.mapProjection;
var sphereIntersections = scratchArray;
sphereIntersections.length = 0;
var tilesToRender = this._surface._tilesToRender;
var length = tilesToRender.length;
var tile;
var i;
for (i = 0; i < length; ++i) {
tile = tilesToRender[i];
var tileData = tile.data;
if (!defined(tileData)) {
continue;
}
var boundingVolume = tileData.pickBoundingSphere;
if (mode !== SceneMode.SCENE3D) {
BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, projection, tileData.minimumHeight, tileData.maximumHeight, boundingVolume);
Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
} else {
BoundingSphere.clone(tileData.boundingSphere3D, boundingVolume);
}
var boundingSphereIntersection = IntersectionTests.raySphere(ray, boundingVolume, scratchSphereIntersectionResult);
if (defined(boundingSphereIntersection)) {
sphereIntersections.push(tileData);
}
}
sphereIntersections.sort(createComparePickTileFunction(ray.origin));
var intersection;
length = sphereIntersections.length;
for (i = 0; i < length; ++i) {
intersection = sphereIntersections[i].pick(ray, scene, true, result);
if (defined(intersection)) {
break;
}
}
return intersection;
};
var scratchGetHeightCartesian = new Cartesian3();
var scratchGetHeightIntersection = new Cartesian3();
var scratchGetHeightCartographic = new Cartographic();
var scratchGetHeightRay = new Ray();
/**
* 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');
var levelZeroTiles = this._surface._levelZeroTiles;
if (!defined(levelZeroTiles)) {
return;
}
var tile;
var i;
var length = levelZeroTiles.length;
for (i = 0; i < length; ++i) {
tile = levelZeroTiles[i];
if (Rectangle.contains(tile.rectangle, cartographic)) {
break;
}
}
if (!defined(tile) || !Rectangle.contains(tile.rectangle, cartographic)) {
return undefined;
}
while (tile.renderable) {
var children = tile.children;
length = children.length;
for (i = 0; i < length; ++i) {
tile = children[i];
if (Rectangle.contains(tile.rectangle, cartographic)) {
break;
}
}
}
while (defined(tile) && (!defined(tile.data) || !defined(tile.data.pickTerrain))) {
tile = tile.parent;
}
if (!defined(tile)) {
return undefined;
}
var ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid;
var cartesian = ellipsoid.cartographicToCartesian(cartographic, scratchGetHeightCartesian);
var ray = scratchGetHeightRay;
Cartesian3.normalize(cartesian, ray.direction);
var intersection = tile.data.pick(ray, undefined, false, scratchGetHeightIntersection);
if (!defined(intersection)) {
return undefined;
}
return ellipsoid.cartesianToCartographic(intersection, scratchGetHeightCartographic).height;
};
var depthQuadScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(12) : [];
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchCartesian4 = new Cartesian3();
function computeDepthQuad(globe, frameState) {
var radii = globe._ellipsoid.radii;
var p = frameState.camera.positionWC;
// Find the corresponding position in the scaled space of the ellipsoid.
var q = Cartesian3.multiplyComponents(globe._ellipsoid.oneOverRadii, p, scratchCartesian1);
var qMagnitude = Cartesian3.magnitude(q);
var qUnit = Cartesian3.normalize(q, scratchCartesian2);
// Determine the east and north directions at q.
var eUnit = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, q, scratchCartesian3), scratchCartesian3);
var nUnit = Cartesian3.normalize(Cartesian3.cross(qUnit, eUnit, scratchCartesian4), scratchCartesian4);
// Determine the radius of the 'limb' of the ellipsoid.
var wMagnitude = Math.sqrt(Cartesian3.magnitudeSquared(q) - 1.0);
// Compute the center and offsets.
var center = Cartesian3.multiplyByScalar(qUnit, 1.0 / qMagnitude, scratchCartesian1);
var scalar = wMagnitude / qMagnitude;
var eastOffset = Cartesian3.multiplyByScalar(eUnit, scalar, scratchCartesian2);
var northOffset = Cartesian3.multiplyByScalar(nUnit, scalar, scratchCartesian3);
// A conservative measure for the longitudes would be to use the min/max longitudes of the bounding frustum.
var upperLeft = Cartesian3.add(center, northOffset, scratchCartesian4);
Cartesian3.subtract(upperLeft, eastOffset, upperLeft);
Cartesian3.multiplyComponents(radii, upperLeft, upperLeft);
Cartesian3.pack(upperLeft, depthQuadScratch, 0);
var lowerLeft = Cartesian3.subtract(center, northOffset, scratchCartesian4);
Cartesian3.subtract(lowerLeft, eastOffset, lowerLeft);
Cartesian3.multiplyComponents(radii, lowerLeft, lowerLeft);
Cartesian3.pack(lowerLeft, depthQuadScratch, 3);
var upperRight = Cartesian3.add(center, northOffset, scratchCartesian4);
Cartesian3.add(upperRight, eastOffset, upperRight);
Cartesian3.multiplyComponents(radii, upperRight, upperRight);
Cartesian3.pack(upperRight, depthQuadScratch, 6);
var lowerRight = Cartesian3.subtract(center, northOffset, scratchCartesian4);
Cartesian3.add(lowerRight, eastOffset, lowerRight);
Cartesian3.multiplyComponents(radii, lowerRight, lowerRight);
Cartesian3.pack(lowerRight, depthQuadScratch, 9);
return depthQuadScratch;
}
var rightScratch = new Cartesian3();
var upScratch = new Cartesian3();
var negativeZ = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3());
var cartographicScratch = new Cartographic(0.0, 0.0);
var pt1Scratch = new Cartesian3();
var pt2Scratch = new Cartesian3();
function computePoleQuad(globe, frameState, maxLat, maxGivenLat, viewProjMatrix, viewportTransformation) {
cartographicScratch.longitude = 0.0;
cartographicScratch.latitude = maxGivenLat;
var pt1 = globe._ellipsoid.cartographicToCartesian(cartographicScratch, pt1Scratch);
cartographicScratch.longitude = Math.PI;
var pt2 = globe._ellipsoid.cartographicToCartesian(cartographicScratch, pt2Scratch);
var radius = Cartesian3.magnitude(Cartesian3.subtract(pt1, pt2, rightScratch), rightScratch) * 0.5;
cartographicScratch.longitude = 0.0;
cartographicScratch.latitude = maxLat;
var center = globe._ellipsoid.cartographicToCartesian(cartographicScratch, pt1Scratch);
var right;
var dir = frameState.camera.direction;
if (1.0 - Cartesian3.dot(negativeZ, dir) < CesiumMath.EPSILON6) {
right = Cartesian3.UNIT_X;
} else {
right = Cartesian3.normalize(Cartesian3.cross(dir, Cartesian3.UNIT_Z, rightScratch), rightScratch);
}
var screenRight = Cartesian3.add(center, Cartesian3.multiplyByScalar(right, radius, rightScratch), rightScratch);
var screenUp = Cartesian3.add(center, Cartesian3.multiplyByScalar(Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, right, upScratch), upScratch), radius, upScratch), upScratch);
Transforms.pointToGLWindowCoordinates(viewProjMatrix, viewportTransformation, center, center);
Transforms.pointToGLWindowCoordinates(viewProjMatrix, viewportTransformation, screenRight, screenRight);
Transforms.pointToGLWindowCoordinates(viewProjMatrix, viewportTransformation, screenUp, screenUp);
var halfWidth = Math.floor(Math.max(Cartesian3.distance(screenUp, center), Cartesian3.distance(screenRight, center)));
var halfHeight = halfWidth;
return new BoundingRectangle(
Math.floor(center.x) - halfWidth,
Math.floor(center.y) - halfHeight,
halfWidth * 2.0,
halfHeight * 2.0);
}
var viewportScratch = new BoundingRectangle();
var vpTransformScratch = new Matrix4();
var polePositionsScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
function fillPoles(globe, context, frameState) {
var terrainProvider = globe.terrainProvider;
if (frameState.mode !== SceneMode.SCENE3D) {
return;
}
if (!terrainProvider.ready) {
return;
}
var terrainMaxRectangle = terrainProvider.tilingScheme.rectangle;
var viewProjMatrix = context.uniformState.viewProjection;
var viewport = viewportScratch;
viewport.width = context.drawingBufferWidth;
viewport.height = context.drawingBufferHeight;
var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, vpTransformScratch);
var latitudeExtension = 0.05;
var rectangle;
var boundingVolume;
var frustumCull;
var occludeePoint;
var occluded;
var geometry;
var rect;
var occluder = globe._occluder;
// handle north pole
if (terrainMaxRectangle.north < CesiumMath.PI_OVER_TWO) {
rectangle = new Rectangle(-Math.PI, terrainMaxRectangle.north, Math.PI, CesiumMath.PI_OVER_TWO);
boundingVolume = BoundingSphere.fromRectangle3D(rectangle, globe._ellipsoid);
frustumCull = frameState.cullingVolume.computeVisibility(boundingVolume) === Intersect.OUTSIDE;
occludeePoint = Occluder.computeOccludeePointFromRectangle(rectangle, globe._ellipsoid);
occluded = (occludeePoint && !occluder.isPointVisible(occludeePoint, 0.0)) || !occluder.isBoundingSphereVisible(boundingVolume);
globe._drawNorthPole = !frustumCull && !occluded;
if (globe._drawNorthPole) {
rect = computePoleQuad(globe, frameState, rectangle.north, rectangle.south - latitudeExtension, viewProjMatrix, viewportTransformation);
polePositionsScratch[0] = rect.x;
polePositionsScratch[1] = rect.y;
polePositionsScratch[2] = rect.x + rect.width;
polePositionsScratch[3] = rect.y;
polePositionsScratch[4] = rect.x + rect.width;
polePositionsScratch[5] = rect.y + rect.height;
polePositionsScratch[6] = rect.x;
polePositionsScratch[7] = rect.y + rect.height;
if (!defined(globe._northPoleCommand.vertexArray)) {
globe._northPoleCommand.boundingVolume = BoundingSphere.fromRectangle3D(rectangle, globe._ellipsoid);
geometry = new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : polePositionsScratch
})
}
});
globe._northPoleCommand.vertexArray = context.createVertexArrayFromGeometry({
geometry : geometry,
attributeLocations : {
position : 0
},
bufferUsage : BufferUsage.STREAM_DRAW
});
} else {
globe._northPoleCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(polePositionsScratch);
}
}
}
// handle south pole
if (terrainMaxRectangle.south > -CesiumMath.PI_OVER_TWO) {
rectangle = new Rectangle(-Math.PI, -CesiumMath.PI_OVER_TWO, Math.PI, terrainMaxRectangle.south);
boundingVolume = BoundingSphere.fromRectangle3D(rectangle, globe._ellipsoid);
frustumCull = frameState.cullingVolume.computeVisibility(boundingVolume) === Intersect.OUTSIDE;
occludeePoint = Occluder.computeOccludeePointFromRectangle(rectangle, globe._ellipsoid);
occluded = (occludeePoint && !occluder.isPointVisible(occludeePoint)) || !occluder.isBoundingSphereVisible(boundingVolume);
globe._drawSouthPole = !frustumCull && !occluded;
if (globe._drawSouthPole) {
rect = computePoleQuad(globe, frameState, rectangle.south, rectangle.north + latitudeExtension, viewProjMatrix, viewportTransformation);
polePositionsScratch[0] = rect.x;
polePositionsScratch[1] = rect.y;
polePositionsScratch[2] = rect.x + rect.width;
polePositionsScratch[3] = rect.y;
polePositionsScratch[4] = rect.x + rect.width;
polePositionsScratch[5] = rect.y + rect.height;
polePositionsScratch[6] = rect.x;
polePositionsScratch[7] = rect.y + rect.height;
if (!defined(globe._southPoleCommand.vertexArray)) {
globe._southPoleCommand.boundingVolume = BoundingSphere.fromRectangle3D(rectangle, globe._ellipsoid);
geometry = new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : polePositionsScratch
})
}
});
globe._southPoleCommand.vertexArray = context.createVertexArrayFromGeometry({
geometry : geometry,
attributeLocations : {
position : 0
},
bufferUsage : BufferUsage.STREAM_DRAW
});
} else {
globe._southPoleCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(polePositionsScratch);
}
}
}
var poleIntensity = 0.0;
var baseLayer = globe._imageryLayerCollection.length > 0 ? globe._imageryLayerCollection.get(0) : undefined;
if (defined(baseLayer) && defined(baseLayer.imageryProvider) && defined(baseLayer.imageryProvider.getPoleIntensity)) {
poleIntensity = baseLayer.imageryProvider.getPoleIntensity();
}
var drawUniforms = {
u_dayIntensity : function() {
return poleIntensity;
}
};
if (!defined(globe._northPoleCommand.uniformMap)) {
var northPoleUniforms = combine(drawUniforms, {
u_color : function() {
return globe.northPoleColor;
}
});
globe._northPoleCommand.uniformMap = combine(northPoleUniforms, globe._drawUniforms);
}
if (!defined(globe._southPoleCommand.uniformMap)) {
var southPoleUniforms = combine(drawUniforms, {
u_color : function() {
return globe.southPoleColor;
}
});
globe._southPoleCommand.uniformMap = combine(southPoleUniforms, globe._drawUniforms);
}
}
/**
* @private
*/
Globe.prototype.update = function(context, frameState, commandList) {
if (!this.show) {
return;
}
var width = context.drawingBufferWidth;
var height = context.drawingBufferHeight;
if (width === 0 || height === 0) {
return;
}
var mode = frameState.mode;
var projection = frameState.mapProjection;
var modeChanged = false;
if (this._mode !== mode || !defined(this._rsColor)) {
modeChanged = true;
if (mode === SceneMode.SCENE3D || mode === SceneMode.COLUMBUS_VIEW) {
this._rsColor = context.createRenderState({ // Write color and depth
cull : {
enabled : true
},
depthTest : {
enabled : true
}
});
this._rsColorWithoutDepthTest = context.createRenderState({ // Write color, not depth
cull : {
enabled : true
}
});
this._depthCommand.renderState = context.createRenderState({ // Write depth, not color
cull : {
enabled : true
},
depthTest : {
enabled : true,
func : DepthFunction.ALWAYS
},
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
}
});
} else {
this._rsColor = context.createRenderState({
cull : {
enabled : true
}
});
this._rsColorWithoutDepthTest = context.createRenderState({
cull : {
enabled : true
}
});
this._depthCommand.renderState = context.createRenderState({
cull : {
enabled : true
}
});
}
}
this._mode = mode;
var northPoleCommand = this._northPoleCommand;
var southPoleCommand = this._southPoleCommand;
northPoleCommand.renderState = this._rsColorWithoutDepthTest;
southPoleCommand.renderState = this._rsColorWithoutDepthTest;
// update depth plane
var depthQuad = computeDepthQuad(this, frameState);
// depth plane
if (!this._depthCommand.vertexArray) {
var geometry = new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : depthQuad
})
},
indices : [0, 1, 2, 2, 1, 3],
primitiveType : PrimitiveType.TRIANGLES
});
this._depthCommand.vertexArray = context.createVertexArrayFromGeometry({
geometry : geometry,
attributeLocations : {
position : 0
},
bufferUsage : BufferUsage.DYNAMIC_DRAW
});
} else {
this._depthCommand.vertexArray.getAttribute(0).vertexBuffer.copyFromArrayView(depthQuad);
}
if (!defined(this._depthCommand.shaderProgram)) {
this._depthCommand.shaderProgram = context.createShaderProgram(GlobeVSDepth, GlobeFSDepth, {
position : 0
});
}
var surface = this._surface;
var tileProvider = surface.tileProvider;
var terrainProvider = this.terrainProvider;
var hasWaterMask = this.showWaterEffect && terrainProvider.ready && terrainProvider.hasWaterMask;
if (hasWaterMask && this.oceanNormalMapUrl !== this._oceanNormalMapUrl) {
// url changed, load new normal map asynchronously
var oceanNormalMapUrl = this.oceanNormalMapUrl;
this._oceanNormalMapUrl = oceanNormalMapUrl;
if (defined(oceanNormalMapUrl)) {
var that = this;
when(loadImage(oceanNormalMapUrl), function(image) {
if (oceanNormalMapUrl !== that.oceanNormalMapUrl) {
// url changed while we were loading
return;
}
that._oceanNormalMap = that._oceanNormalMap && that._oceanNormalMap.destroy();
that._oceanNormalMap = context.createTexture2D({
source : image
});
});
} else {
this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();
}
}
if (!defined(northPoleCommand.shaderProgram) ||
!defined(southPoleCommand.shaderProgram)) {
var poleShaderProgram = context.replaceShaderProgram(northPoleCommand.shaderProgram, GlobeVSPole, GlobeFSPole, terrainAttributeLocations);
northPoleCommand.shaderProgram = poleShaderProgram;
southPoleCommand.shaderProgram = poleShaderProgram;
}
this._occluder.cameraPosition = frameState.camera.positionWC;
fillPoles(this, context, frameState);
var pass = frameState.passes;
if (pass.render) {
// render quads to fill the poles
if (mode === SceneMode.SCENE3D) {
if (this._drawNorthPole) {
commandList.push(northPoleCommand);
}
if (this._drawSouthPole) {
commandList.push(southPoleCommand);
}
}
// Don't show the ocean specular highlights when zoomed out in 2D and Columbus View.
if (mode === SceneMode.SCENE3D) {
this._zoomedOutOceanSpecularIntensity = 0.5;
} else {
this._zoomedOutOceanSpecularIntensity = 0.0;
}
surface.maximumScreenSpaceError = this.maximumScreenSpaceError;
surface.tileCacheSize = this.tileCacheSize;
tileProvider.terrainProvider = this.terrainProvider;
tileProvider.lightingFadeOutDistance = this.lightingFadeOutDistance;
tileProvider.lightingFadeInDistance = this.lightingFadeInDistance;
tileProvider.zoomedOutOceanSpecularIntensity = this._zoomedOutOceanSpecularIntensity;
tileProvider.hasWaterMask = hasWaterMask;
tileProvider.oceanNormalMap = this._oceanNormalMap;
tileProvider.enableLighting = this.enableLighting;
surface.update(context, frameState, commandList);
// render depth plane
if (mode === SceneMode.SCENE3D || mode === SceneMode.COLUMBUS_VIEW) {
if (!this.depthTestAgainstTerrain) {
commandList.push(this._clearDepthCommand);
if (mode === SceneMode.SCENE3D) {
commandList.push(this._depthCommand);
}
}
}
}
if (pass.pick) {
// Not actually pickable, but render depth-only so primitives on the backface
// of the globe are not picked.
commandList.push(this._depthCommand);
}
};
/**
* 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.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see Globe#isDestroyed
*
* @example
* globe = globe && globe.destroy();
*/
Globe.prototype.destroy = function() {
this._northPoleCommand.vertexArray = this._northPoleCommand.vertexArray && this._northPoleCommand.vertexArray.destroy();
this._southPoleCommand.vertexArray = this._southPoleCommand.vertexArray && this._southPoleCommand.vertexArray.destroy();
this._surfaceShaderSet = this._surfaceShaderSet && this._surfaceShaderSet.destroy();
this._northPoleCommand.shaderProgram = this._northPoleCommand.shaderProgram && this._northPoleCommand.shaderProgram.destroy();
this._southPoleCommand.shaderProgram = this._northPoleCommand.shaderProgram;
this._depthCommand.shaderProgram = this._depthCommand.shaderProgram && this._depthCommand.shaderProgram.destroy();
this._depthCommand.vertexArray = this._depthCommand.vertexArray && this._depthCommand.vertexArray.destroy();
this._surface = this._surface && this._surface.destroy();
this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();
return destroyObject(this);
};
return Globe;
});