import BoundingSphere from "../Core/BoundingSphere.js";
|
import Cartesian3 from "../Core/Cartesian3.js";
|
import Cartesian4 from "../Core/Cartesian4.js";
|
import Cartographic from "../Core/Cartographic.js";
|
import defined from "../Core/defined.js";
|
import IndexDatatype from "../Core/IndexDatatype.js";
|
import IntersectionTests from "../Core/IntersectionTests.js";
|
import PixelFormat from "../Core/PixelFormat.js";
|
import Ray from "../Core/Ray.js";
|
import Request from "../Core/Request.js";
|
import RequestState from "../Core/RequestState.js";
|
import RequestType from "../Core/RequestType.js";
|
import TerrainEncoding from "../Core/TerrainEncoding.js";
|
import TileProviderError from "../Core/TileProviderError.js";
|
import Buffer from "../Renderer/Buffer.js";
|
import BufferUsage from "../Renderer/BufferUsage.js";
|
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
import Sampler from "../Renderer/Sampler.js";
|
import Texture from "../Renderer/Texture.js";
|
import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";
|
import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
|
import TextureWrap from "../Renderer/TextureWrap.js";
|
import VertexArray from "../Renderer/VertexArray.js";
|
import when from "../ThirdParty/when.js";
|
import ImageryState from "./ImageryState.js";
|
import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js";
|
import SceneMode from "./SceneMode.js";
|
import TerrainState from "./TerrainState.js";
|
|
/**
|
* Contains additional information about a {@link QuadtreeTile} of the globe's surface, and
|
* encapsulates state transition logic for loading tiles.
|
*
|
* @constructor
|
* @alias GlobeSurfaceTile
|
* @private
|
*/
|
function GlobeSurfaceTile() {
|
/**
|
* The {@link TileImagery} attached to this tile.
|
* @type {TileImagery[]}
|
* @default []
|
*/
|
this.imagery = [];
|
|
this.waterMaskTexture = undefined;
|
this.waterMaskTranslationAndScale = new Cartesian4(0.0, 0.0, 1.0, 1.0);
|
|
this.terrainData = undefined;
|
this.vertexArray = undefined;
|
|
/**
|
* A bounding region used to estimate distance to the tile. The horizontal bounds are always tight-fitting,
|
* but the `minimumHeight` and `maximumHeight` properties may be derived from the min/max of an ancestor tile
|
* and be quite loose-fitting and thus very poor for estimating distance.
|
* @type {TileBoundingRegion}
|
*/
|
this.tileBoundingRegion = undefined;
|
this.occludeePointInScaledSpace = new Cartesian3();
|
this.boundingVolumeSourceTile = undefined;
|
this.boundingVolumeIsFromMesh = false;
|
|
this.terrainState = TerrainState.UNLOADED;
|
this.mesh = undefined;
|
this.fill = undefined;
|
|
this.pickBoundingSphere = new BoundingSphere();
|
|
this.surfaceShader = undefined;
|
this.isClipped = true;
|
|
this.clippedByBoundaries = false;
|
}
|
|
Object.defineProperties(GlobeSurfaceTile.prototype, {
|
/**
|
* Gets a value indicating whether or not this tile is eligible to be unloaded.
|
* Typically, a tile is ineligible to be unloaded while an asynchronous operation,
|
* such as a request for data, is in progress on it. A tile will never be
|
* unloaded while it is needed for rendering, regardless of the value of this
|
* property.
|
* @memberof GlobeSurfaceTile.prototype
|
* @type {Boolean}
|
*/
|
eligibleForUnloading: {
|
get: function () {
|
// Do not remove tiles that are transitioning or that have
|
// imagery that is transitioning.
|
var terrainState = this.terrainState;
|
var loadingIsTransitioning =
|
terrainState === TerrainState.RECEIVING ||
|
terrainState === TerrainState.TRANSFORMING;
|
|
var shouldRemoveTile = !loadingIsTransitioning;
|
|
var imagery = this.imagery;
|
for (var i = 0, len = imagery.length; shouldRemoveTile && i < len; ++i) {
|
var tileImagery = imagery[i];
|
shouldRemoveTile =
|
!defined(tileImagery.loadingImagery) ||
|
tileImagery.loadingImagery.state !== ImageryState.TRANSITIONING;
|
}
|
|
return shouldRemoveTile;
|
},
|
},
|
|
/**
|
* Gets the {@link TerrainMesh} that is used for rendering this tile, if any.
|
* Returns the value of the {@link GlobeSurfaceTile#mesh} property if
|
* {@link GlobeSurfaceTile#vertexArray} is defined. Otherwise, It returns the
|
* {@link TerrainFillMesh#mesh} property of the {@link GlobeSurfaceTile#fill}.
|
* If there is no fill, it returns undefined.
|
*
|
* @memberof GlobeSurfaceTile.prototype
|
* @type {TerrainMesh}
|
*/
|
renderedMesh: {
|
get: function () {
|
if (defined(this.vertexArray)) {
|
return this.mesh;
|
} else if (defined(this.fill)) {
|
return this.fill.mesh;
|
}
|
return undefined;
|
},
|
},
|
});
|
|
var scratchCartographic = new Cartographic();
|
|
function getPosition(encoding, mode, projection, vertices, index, result) {
|
var position = encoding.getExaggeratedPosition(vertices, index, result);
|
|
if (defined(mode) && mode !== SceneMode.SCENE3D) {
|
var ellipsoid = projection.ellipsoid;
|
var positionCartographic = ellipsoid.cartesianToCartographic(
|
position,
|
scratchCartographic
|
);
|
position = projection.project(positionCartographic, result);
|
position = Cartesian3.fromElements(
|
position.z,
|
position.x,
|
position.y,
|
result
|
);
|
}
|
|
return position;
|
}
|
|
var scratchV0 = new Cartesian3();
|
var scratchV1 = new Cartesian3();
|
var scratchV2 = new Cartesian3();
|
|
GlobeSurfaceTile.prototype.pick = function (
|
ray,
|
mode,
|
projection,
|
cullBackFaces,
|
result
|
) {
|
var mesh = this.renderedMesh;
|
if (!defined(mesh)) {
|
return undefined;
|
}
|
|
var vertices = mesh.vertices;
|
var indices = mesh.indices;
|
var encoding = mesh.encoding;
|
var indicesLength = indices.length;
|
|
var minT = Number.MAX_VALUE;
|
|
for (var i = 0; i < indicesLength; i += 3) {
|
var i0 = indices[i];
|
var i1 = indices[i + 1];
|
var i2 = indices[i + 2];
|
|
var v0 = getPosition(encoding, mode, projection, vertices, i0, scratchV0);
|
var v1 = getPosition(encoding, mode, projection, vertices, i1, scratchV1);
|
var v2 = getPosition(encoding, mode, projection, vertices, i2, scratchV2);
|
|
var t = IntersectionTests.rayTriangleParametric(
|
ray,
|
v0,
|
v1,
|
v2,
|
cullBackFaces
|
);
|
if (defined(t) && t < minT && t >= 0.0) {
|
minT = t;
|
}
|
}
|
|
return minT !== Number.MAX_VALUE
|
? Ray.getPoint(ray, minT, result)
|
: undefined;
|
};
|
|
GlobeSurfaceTile.prototype.freeResources = function () {
|
if (defined(this.waterMaskTexture)) {
|
--this.waterMaskTexture.referenceCount;
|
if (this.waterMaskTexture.referenceCount === 0) {
|
this.waterMaskTexture.destroy();
|
}
|
this.waterMaskTexture = undefined;
|
}
|
|
this.terrainData = undefined;
|
|
this.terrainState = TerrainState.UNLOADED;
|
this.mesh = undefined;
|
this.fill = this.fill && this.fill.destroy();
|
|
var imageryList = this.imagery;
|
for (var i = 0, len = imageryList.length; i < len; ++i) {
|
imageryList[i].freeResources();
|
}
|
this.imagery.length = 0;
|
|
this.freeVertexArray();
|
};
|
|
GlobeSurfaceTile.prototype.freeVertexArray = function () {
|
GlobeSurfaceTile._freeVertexArray(this.vertexArray);
|
this.vertexArray = undefined;
|
GlobeSurfaceTile._freeVertexArray(this.wireframeVertexArray);
|
this.wireframeVertexArray = undefined;
|
};
|
|
GlobeSurfaceTile.initialize = function (
|
tile,
|
terrainProvider,
|
imageryLayerCollection
|
) {
|
var surfaceTile = tile.data;
|
if (!defined(surfaceTile)) {
|
surfaceTile = tile.data = new GlobeSurfaceTile();
|
}
|
|
if (tile.state === QuadtreeTileLoadState.START) {
|
prepareNewTile(tile, terrainProvider, imageryLayerCollection);
|
tile.state = QuadtreeTileLoadState.LOADING;
|
}
|
};
|
|
GlobeSurfaceTile.processStateMachine = function (
|
tile,
|
frameState,
|
terrainProvider,
|
imageryLayerCollection,
|
quadtree,
|
vertexArraysToDestroy,
|
terrainOnly
|
) {
|
GlobeSurfaceTile.initialize(tile, terrainProvider, imageryLayerCollection);
|
|
var surfaceTile = tile.data;
|
|
if (tile.state === QuadtreeTileLoadState.LOADING) {
|
processTerrainStateMachine(
|
tile,
|
frameState,
|
terrainProvider,
|
imageryLayerCollection,
|
quadtree,
|
vertexArraysToDestroy
|
);
|
}
|
|
// From here down we're loading imagery, not terrain. We don't want to load imagery until
|
// we're certain that the terrain tiles are actually visible, though. We'll load terrainOnly
|
// in these scenarios:
|
// * our bounding volume isn't accurate so we're not certain this tile is really visible (see GlobeSurfaceTileProvider#loadTile).
|
// * we want to upsample from this tile but don't plan to render it (see processTerrainStateMachine).
|
if (terrainOnly) {
|
return;
|
}
|
|
var wasAlreadyRenderable = tile.renderable;
|
|
// The terrain is renderable as soon as we have a valid vertex array.
|
tile.renderable = defined(surfaceTile.vertexArray);
|
|
// But it's not done loading until it's in the READY state.
|
var isTerrainDoneLoading = surfaceTile.terrainState === TerrainState.READY;
|
|
// If this tile's terrain and imagery are just upsampled from its parent, mark the tile as
|
// upsampled only. We won't refine a tile if its four children are upsampled only.
|
tile.upsampledFromParent =
|
defined(surfaceTile.terrainData) &&
|
surfaceTile.terrainData.wasCreatedByUpsampling();
|
|
var isImageryDoneLoading = surfaceTile.processImagery(
|
tile,
|
terrainProvider,
|
frameState
|
);
|
|
if (isTerrainDoneLoading && isImageryDoneLoading) {
|
var callbacks = tile._loadedCallbacks;
|
var newCallbacks = {};
|
for (var layerId in callbacks) {
|
if (callbacks.hasOwnProperty(layerId)) {
|
if (!callbacks[layerId](tile)) {
|
newCallbacks[layerId] = callbacks[layerId];
|
}
|
}
|
}
|
tile._loadedCallbacks = newCallbacks;
|
|
tile.state = QuadtreeTileLoadState.DONE;
|
}
|
|
// Once a tile is renderable, it stays renderable, because doing otherwise would
|
// cause detail (or maybe even the entire globe) to vanish when adding a new
|
// imagery layer. `GlobeSurfaceTileProvider._onLayerAdded` sets renderable to
|
// false for all affected tiles that are not currently being rendered.
|
if (wasAlreadyRenderable) {
|
tile.renderable = true;
|
}
|
};
|
|
GlobeSurfaceTile.prototype.processImagery = function (
|
tile,
|
terrainProvider,
|
frameState,
|
skipLoading
|
) {
|
var surfaceTile = tile.data;
|
var isUpsampledOnly = tile.upsampledFromParent;
|
var isAnyTileLoaded = false;
|
var isDoneLoading = true;
|
|
// Transition imagery states
|
var tileImageryCollection = surfaceTile.imagery;
|
var i, len;
|
for (i = 0, len = tileImageryCollection.length; i < len; ++i) {
|
var tileImagery = tileImageryCollection[i];
|
if (!defined(tileImagery.loadingImagery)) {
|
isUpsampledOnly = false;
|
continue;
|
}
|
|
if (tileImagery.loadingImagery.state === ImageryState.PLACEHOLDER) {
|
var imageryLayer = tileImagery.loadingImagery.imageryLayer;
|
if (imageryLayer.imageryProvider.ready) {
|
// Remove the placeholder and add the actual skeletons (if any)
|
// at the same position. Then continue the loop at the same index.
|
tileImagery.freeResources();
|
tileImageryCollection.splice(i, 1);
|
imageryLayer._createTileImagerySkeletons(tile, terrainProvider, i);
|
--i;
|
len = tileImageryCollection.length;
|
continue;
|
} else {
|
isUpsampledOnly = false;
|
}
|
}
|
|
var thisTileDoneLoading = tileImagery.processStateMachine(
|
tile,
|
frameState,
|
skipLoading
|
);
|
isDoneLoading = isDoneLoading && thisTileDoneLoading;
|
|
// The imagery is renderable as soon as we have any renderable imagery for this region.
|
isAnyTileLoaded =
|
isAnyTileLoaded ||
|
thisTileDoneLoading ||
|
defined(tileImagery.readyImagery);
|
|
isUpsampledOnly =
|
isUpsampledOnly &&
|
defined(tileImagery.loadingImagery) &&
|
(tileImagery.loadingImagery.state === ImageryState.FAILED ||
|
tileImagery.loadingImagery.state === ImageryState.INVALID);
|
}
|
|
tile.upsampledFromParent = isUpsampledOnly;
|
|
// Allow rendering if any available layers are loaded
|
tile.renderable = tile.renderable && (isAnyTileLoaded || isDoneLoading);
|
|
return isDoneLoading;
|
};
|
|
function toggleGeodeticSurfaceNormals(
|
surfaceTile,
|
enabled,
|
ellipsoid,
|
frameState
|
) {
|
var renderedMesh = surfaceTile.renderedMesh;
|
var vertexBuffer = renderedMesh.vertices;
|
var encoding = renderedMesh.encoding;
|
var vertexCount = vertexBuffer.length / encoding.stride;
|
|
// Calculate the new stride and generate a new buffer
|
// Clone the other encoding, toggle geodetic surface normals, then clone again to get updated stride
|
var newEncoding = TerrainEncoding.clone(encoding);
|
newEncoding.hasGeodeticSurfaceNormals = enabled;
|
newEncoding = TerrainEncoding.clone(newEncoding);
|
var newStride = newEncoding.stride;
|
var newVertexBuffer = new Float32Array(vertexCount * newStride);
|
|
if (enabled) {
|
encoding.addGeodeticSurfaceNormals(
|
vertexBuffer,
|
newVertexBuffer,
|
ellipsoid
|
);
|
} else {
|
encoding.removeGeodeticSurfaceNormals(vertexBuffer, newVertexBuffer);
|
}
|
|
renderedMesh.vertices = newVertexBuffer;
|
renderedMesh.stride = newStride;
|
|
// delete the old vertex array (which deletes the vertex buffer attached to it), and create a new vertex array with the new vertex buffer
|
var isFill = renderedMesh !== surfaceTile.mesh;
|
if (isFill) {
|
GlobeSurfaceTile._freeVertexArray(surfaceTile.fill.vertexArray);
|
surfaceTile.fill.vertexArray = GlobeSurfaceTile._createVertexArrayForMesh(
|
frameState.context,
|
renderedMesh
|
);
|
} else {
|
GlobeSurfaceTile._freeVertexArray(surfaceTile.vertexArray);
|
surfaceTile.vertexArray = GlobeSurfaceTile._createVertexArrayForMesh(
|
frameState.context,
|
renderedMesh
|
);
|
}
|
GlobeSurfaceTile._freeVertexArray(surfaceTile.wireframeVertexArray);
|
surfaceTile.wireframeVertexArray = undefined;
|
}
|
|
GlobeSurfaceTile.prototype.addGeodeticSurfaceNormals = function (
|
ellipsoid,
|
frameState
|
) {
|
toggleGeodeticSurfaceNormals(this, true, ellipsoid, frameState);
|
};
|
|
GlobeSurfaceTile.prototype.removeGeodeticSurfaceNormals = function (
|
frameState
|
) {
|
toggleGeodeticSurfaceNormals(this, false, undefined, frameState);
|
};
|
|
GlobeSurfaceTile.prototype.updateExaggeration = function (
|
tile,
|
frameState,
|
quadtree
|
) {
|
var surfaceTile = this;
|
var mesh = surfaceTile.renderedMesh;
|
if (mesh === undefined) {
|
return;
|
}
|
|
// Check the tile's terrain encoding to see if it has been exaggerated yet
|
var exaggeration = frameState.terrainExaggeration;
|
var exaggerationRelativeHeight = frameState.terrainExaggerationRelativeHeight;
|
var hasExaggerationScale = exaggeration !== 1.0;
|
|
var encoding = mesh.encoding;
|
var encodingExaggerationScaleChanged = encoding.exaggeration !== exaggeration;
|
var encodingRelativeHeightChanged =
|
encoding.exaggerationRelativeHeight !== exaggerationRelativeHeight;
|
|
if (encodingExaggerationScaleChanged || encodingRelativeHeightChanged) {
|
// Turning exaggeration scale on/off requires adding or removing geodetic surface normals
|
// Relative height only translates, so it has no effect on normals
|
if (encodingExaggerationScaleChanged) {
|
if (hasExaggerationScale && !encoding.hasGeodeticSurfaceNormals) {
|
var ellipsoid = tile.tilingScheme.ellipsoid;
|
surfaceTile.addGeodeticSurfaceNormals(ellipsoid, frameState);
|
} else if (!hasExaggerationScale && encoding.hasGeodeticSurfaceNormals) {
|
surfaceTile.removeGeodeticSurfaceNormals(frameState);
|
}
|
}
|
|
encoding.exaggeration = exaggeration;
|
encoding.exaggerationRelativeHeight = exaggerationRelativeHeight;
|
|
// Notify the quadtree that this tile's height has changed
|
if (quadtree !== undefined) {
|
quadtree._tileToUpdateHeights.push(tile);
|
var customData = tile.customData;
|
var customDataLength = customData.length;
|
for (var i = 0; i < customDataLength; i++) {
|
// Restart the level so that a height update is triggered
|
var data = customData[i];
|
data.level = -1;
|
}
|
}
|
}
|
};
|
|
function prepareNewTile(tile, terrainProvider, imageryLayerCollection) {
|
var available = terrainProvider.getTileDataAvailable(
|
tile.x,
|
tile.y,
|
tile.level
|
);
|
|
if (!defined(available) && defined(tile.parent)) {
|
// Provider doesn't know if this tile is available. Does the parent tile know?
|
var parent = tile.parent;
|
var parentSurfaceTile = parent.data;
|
if (defined(parentSurfaceTile) && defined(parentSurfaceTile.terrainData)) {
|
available = parentSurfaceTile.terrainData.isChildAvailable(
|
parent.x,
|
parent.y,
|
tile.x,
|
tile.y
|
);
|
}
|
}
|
|
if (available === false) {
|
// This tile is not available, so mark it failed so we start upsampling right away.
|
tile.data.terrainState = TerrainState.FAILED;
|
}
|
|
// Map imagery tiles to this terrain tile
|
for (var i = 0, len = imageryLayerCollection.length; i < len; ++i) {
|
var layer = imageryLayerCollection.get(i);
|
if (layer.show) {
|
layer._createTileImagerySkeletons(tile, terrainProvider);
|
}
|
}
|
}
|
|
function processTerrainStateMachine(
|
tile,
|
frameState,
|
terrainProvider,
|
imageryLayerCollection,
|
quadtree,
|
vertexArraysToDestroy
|
) {
|
var surfaceTile = tile.data;
|
|
// If this tile is FAILED, we'll need to upsample from the parent. If the parent isn't
|
// ready for that, let's push it along.
|
var parent = tile.parent;
|
if (
|
surfaceTile.terrainState === TerrainState.FAILED &&
|
parent !== undefined
|
) {
|
var parentReady =
|
parent.data !== undefined &&
|
parent.data.terrainData !== undefined &&
|
parent.data.terrainData.canUpsample !== false;
|
if (!parentReady) {
|
GlobeSurfaceTile.processStateMachine(
|
parent,
|
frameState,
|
terrainProvider,
|
imageryLayerCollection,
|
quadtree,
|
vertexArraysToDestroy,
|
true
|
);
|
}
|
}
|
|
if (surfaceTile.terrainState === TerrainState.FAILED) {
|
upsample(
|
surfaceTile,
|
tile,
|
frameState,
|
terrainProvider,
|
tile.x,
|
tile.y,
|
tile.level
|
);
|
}
|
|
if (surfaceTile.terrainState === TerrainState.UNLOADED) {
|
requestTileGeometry(
|
surfaceTile,
|
terrainProvider,
|
tile.x,
|
tile.y,
|
tile.level
|
);
|
}
|
|
if (surfaceTile.terrainState === TerrainState.RECEIVED) {
|
transform(
|
surfaceTile,
|
frameState,
|
terrainProvider,
|
tile.x,
|
tile.y,
|
tile.level
|
);
|
}
|
|
if (surfaceTile.terrainState === TerrainState.TRANSFORMED) {
|
createResources(
|
surfaceTile,
|
frameState.context,
|
terrainProvider,
|
tile.x,
|
tile.y,
|
tile.level,
|
vertexArraysToDestroy
|
);
|
|
// Update the tile's exaggeration in case the globe's exaggeration changed while the tile was being processed
|
surfaceTile.updateExaggeration(tile, frameState, quadtree);
|
}
|
|
if (
|
surfaceTile.terrainState >= TerrainState.RECEIVED &&
|
surfaceTile.waterMaskTexture === undefined &&
|
terrainProvider.hasWaterMask
|
) {
|
var terrainData = surfaceTile.terrainData;
|
if (terrainData.waterMask !== undefined) {
|
createWaterMaskTextureIfNeeded(frameState.context, surfaceTile);
|
} else {
|
var sourceTile = surfaceTile._findAncestorTileWithTerrainData(tile);
|
if (defined(sourceTile) && defined(sourceTile.data.waterMaskTexture)) {
|
surfaceTile.waterMaskTexture = sourceTile.data.waterMaskTexture;
|
++surfaceTile.waterMaskTexture.referenceCount;
|
surfaceTile._computeWaterMaskTranslationAndScale(
|
tile,
|
sourceTile,
|
surfaceTile.waterMaskTranslationAndScale
|
);
|
}
|
}
|
}
|
}
|
|
function upsample(surfaceTile, tile, frameState, terrainProvider, x, y, level) {
|
var parent = tile.parent;
|
if (!parent) {
|
// Trying to upsample from a root tile. No can do. This tile is a failure.
|
tile.state = QuadtreeTileLoadState.FAILED;
|
return;
|
}
|
|
var sourceData = parent.data.terrainData;
|
var sourceX = parent.x;
|
var sourceY = parent.y;
|
var sourceLevel = parent.level;
|
|
if (!defined(sourceData)) {
|
// Parent is not available, so we can't upsample this tile yet.
|
return;
|
}
|
|
var terrainDataPromise = sourceData.upsample(
|
terrainProvider.tilingScheme,
|
sourceX,
|
sourceY,
|
sourceLevel,
|
x,
|
y,
|
level
|
);
|
if (!defined(terrainDataPromise)) {
|
// The upsample request has been deferred - try again later.
|
return;
|
}
|
|
surfaceTile.terrainState = TerrainState.RECEIVING;
|
|
when(
|
terrainDataPromise,
|
function (terrainData) {
|
surfaceTile.terrainData = terrainData;
|
surfaceTile.terrainState = TerrainState.RECEIVED;
|
},
|
function () {
|
surfaceTile.terrainState = TerrainState.FAILED;
|
}
|
);
|
}
|
|
function requestTileGeometry(surfaceTile, terrainProvider, x, y, level) {
|
function success(terrainData) {
|
surfaceTile.terrainData = terrainData;
|
surfaceTile.terrainState = TerrainState.RECEIVED;
|
surfaceTile.request = undefined;
|
}
|
|
function failure(error) {
|
if (surfaceTile.request.state === RequestState.CANCELLED) {
|
// Cancelled due to low priority - try again later.
|
surfaceTile.terrainData = undefined;
|
surfaceTile.terrainState = TerrainState.UNLOADED;
|
surfaceTile.request = undefined;
|
return;
|
}
|
|
// Initially assume failure. handleError may retry, in which case the state will
|
// change to RECEIVING or UNLOADED.
|
surfaceTile.terrainState = TerrainState.FAILED;
|
surfaceTile.request = undefined;
|
|
var message =
|
"Failed to obtain terrain tile X: " +
|
x +
|
" Y: " +
|
y +
|
" Level: " +
|
level +
|
'. Error message: "' +
|
error +
|
'"';
|
terrainProvider._requestError = TileProviderError.handleError(
|
terrainProvider._requestError,
|
terrainProvider,
|
terrainProvider.errorEvent,
|
message,
|
x,
|
y,
|
level,
|
doRequest
|
);
|
}
|
|
function doRequest() {
|
// Request the terrain from the terrain provider.
|
var request = new Request({
|
throttle: false,
|
throttleByServer: true,
|
type: RequestType.TERRAIN,
|
});
|
surfaceTile.request = request;
|
|
var requestPromise = terrainProvider.requestTileGeometry(
|
x,
|
y,
|
level,
|
request
|
);
|
|
// If the request method returns undefined (instead of a promise), the request
|
// has been deferred.
|
if (defined(requestPromise)) {
|
surfaceTile.terrainState = TerrainState.RECEIVING;
|
when(requestPromise, success, failure);
|
} else {
|
// Deferred - try again later.
|
surfaceTile.terrainState = TerrainState.UNLOADED;
|
surfaceTile.request = undefined;
|
}
|
}
|
|
doRequest();
|
}
|
|
var scratchCreateMeshOptions = {
|
tilingScheme: undefined,
|
x: 0,
|
y: 0,
|
level: 0,
|
exaggeration: 1.0,
|
exaggerationRelativeHeight: 0.0,
|
throttle: true,
|
};
|
|
function transform(surfaceTile, frameState, terrainProvider, x, y, level) {
|
var tilingScheme = terrainProvider.tilingScheme;
|
|
var createMeshOptions = scratchCreateMeshOptions;
|
createMeshOptions.tilingScheme = tilingScheme;
|
createMeshOptions.x = x;
|
createMeshOptions.y = y;
|
createMeshOptions.level = level;
|
createMeshOptions.exaggeration = frameState.terrainExaggeration;
|
createMeshOptions.exaggerationRelativeHeight =
|
frameState.terrainExaggerationRelativeHeight;
|
createMeshOptions.throttle = true;
|
|
var terrainData = surfaceTile.terrainData;
|
var meshPromise = terrainData.createMesh(createMeshOptions);
|
|
if (!defined(meshPromise)) {
|
// Postponed.
|
return;
|
}
|
|
surfaceTile.terrainState = TerrainState.TRANSFORMING;
|
|
when(
|
meshPromise,
|
function (mesh) {
|
surfaceTile.mesh = mesh;
|
surfaceTile.terrainState = TerrainState.TRANSFORMED;
|
},
|
function () {
|
surfaceTile.terrainState = TerrainState.FAILED;
|
}
|
);
|
}
|
|
GlobeSurfaceTile._createVertexArrayForMesh = function (context, mesh) {
|
var typedArray = mesh.vertices;
|
var buffer = Buffer.createVertexBuffer({
|
context: context,
|
typedArray: typedArray,
|
usage: BufferUsage.STATIC_DRAW,
|
});
|
var attributes = mesh.encoding.getAttributes(buffer);
|
|
var indexBuffers = mesh.indices.indexBuffers || {};
|
var indexBuffer = indexBuffers[context.id];
|
if (!defined(indexBuffer) || indexBuffer.isDestroyed()) {
|
var indices = mesh.indices;
|
indexBuffer = Buffer.createIndexBuffer({
|
context: context,
|
typedArray: indices,
|
usage: BufferUsage.STATIC_DRAW,
|
indexDatatype: IndexDatatype.fromSizeInBytes(indices.BYTES_PER_ELEMENT),
|
});
|
indexBuffer.vertexArrayDestroyable = false;
|
indexBuffer.referenceCount = 1;
|
indexBuffers[context.id] = indexBuffer;
|
mesh.indices.indexBuffers = indexBuffers;
|
} else {
|
++indexBuffer.referenceCount;
|
}
|
|
return new VertexArray({
|
context: context,
|
attributes: attributes,
|
indexBuffer: indexBuffer,
|
});
|
};
|
|
GlobeSurfaceTile._freeVertexArray = function (vertexArray) {
|
if (defined(vertexArray)) {
|
var indexBuffer = vertexArray.indexBuffer;
|
|
if (!vertexArray.isDestroyed()) {
|
vertexArray.destroy();
|
}
|
|
if (
|
defined(indexBuffer) &&
|
!indexBuffer.isDestroyed() &&
|
defined(indexBuffer.referenceCount)
|
) {
|
--indexBuffer.referenceCount;
|
if (indexBuffer.referenceCount === 0) {
|
indexBuffer.destroy();
|
}
|
}
|
}
|
};
|
|
function createResources(
|
surfaceTile,
|
context,
|
terrainProvider,
|
x,
|
y,
|
level,
|
vertexArraysToDestroy
|
) {
|
surfaceTile.vertexArray = GlobeSurfaceTile._createVertexArrayForMesh(
|
context,
|
surfaceTile.mesh
|
);
|
surfaceTile.terrainState = TerrainState.READY;
|
surfaceTile.fill =
|
surfaceTile.fill && surfaceTile.fill.destroy(vertexArraysToDestroy);
|
}
|
|
function getContextWaterMaskData(context) {
|
var data = context.cache.tile_waterMaskData;
|
|
if (!defined(data)) {
|
var allWaterTexture = Texture.create({
|
context: context,
|
pixelFormat: PixelFormat.LUMINANCE,
|
pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
|
source: {
|
arrayBufferView: new Uint8Array([255]),
|
width: 1,
|
height: 1,
|
},
|
});
|
allWaterTexture.referenceCount = 1;
|
|
var sampler = new Sampler({
|
wrapS: TextureWrap.CLAMP_TO_EDGE,
|
wrapT: TextureWrap.CLAMP_TO_EDGE,
|
minificationFilter: TextureMinificationFilter.LINEAR,
|
magnificationFilter: TextureMagnificationFilter.LINEAR,
|
});
|
|
data = {
|
allWaterTexture: allWaterTexture,
|
sampler: sampler,
|
destroy: function () {
|
this.allWaterTexture.destroy();
|
},
|
};
|
|
context.cache.tile_waterMaskData = data;
|
}
|
|
return data;
|
}
|
|
function createWaterMaskTextureIfNeeded(context, surfaceTile) {
|
var waterMask = surfaceTile.terrainData.waterMask;
|
var waterMaskData = getContextWaterMaskData(context);
|
var texture;
|
|
var waterMaskLength = waterMask.length;
|
if (waterMaskLength === 1) {
|
// Length 1 means the tile is entirely land or entirely water.
|
// A value of 0 indicates entirely land, a value of 1 indicates entirely water.
|
if (waterMask[0] !== 0) {
|
texture = waterMaskData.allWaterTexture;
|
} else {
|
// Leave the texture undefined if the tile is entirely land.
|
return;
|
}
|
} else {
|
var textureSize = Math.sqrt(waterMaskLength);
|
texture = Texture.create({
|
context: context,
|
pixelFormat: PixelFormat.LUMINANCE,
|
pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
|
source: {
|
width: textureSize,
|
height: textureSize,
|
arrayBufferView: waterMask,
|
},
|
sampler: waterMaskData.sampler,
|
flipY: false,
|
});
|
|
texture.referenceCount = 0;
|
}
|
|
++texture.referenceCount;
|
surfaceTile.waterMaskTexture = texture;
|
|
Cartesian4.fromElements(
|
0.0,
|
0.0,
|
1.0,
|
1.0,
|
surfaceTile.waterMaskTranslationAndScale
|
);
|
}
|
|
GlobeSurfaceTile.prototype._findAncestorTileWithTerrainData = function (tile) {
|
var sourceTile = tile.parent;
|
|
while (
|
defined(sourceTile) &&
|
(!defined(sourceTile.data) ||
|
!defined(sourceTile.data.terrainData) ||
|
sourceTile.data.terrainData.wasCreatedByUpsampling())
|
) {
|
sourceTile = sourceTile.parent;
|
}
|
|
return sourceTile;
|
};
|
|
GlobeSurfaceTile.prototype._computeWaterMaskTranslationAndScale = function (
|
tile,
|
sourceTile,
|
result
|
) {
|
var sourceTileRectangle = sourceTile.rectangle;
|
var tileRectangle = tile.rectangle;
|
var tileWidth = tileRectangle.width;
|
var tileHeight = tileRectangle.height;
|
|
var scaleX = tileWidth / sourceTileRectangle.width;
|
var scaleY = tileHeight / sourceTileRectangle.height;
|
result.x =
|
(scaleX * (tileRectangle.west - sourceTileRectangle.west)) / tileWidth;
|
result.y =
|
(scaleY * (tileRectangle.south - sourceTileRectangle.south)) / tileHeight;
|
result.z = scaleX;
|
result.w = scaleY;
|
|
return result;
|
};
|
export default GlobeSurfaceTile;
|