import AttributeCompression from '../Core/AttributeCompression.js';
|
import binarySearch from '../Core/binarySearch.js';
|
import BoundingSphere from '../Core/BoundingSphere.js';
|
import Cartesian2 from '../Core/Cartesian2.js';
|
import Cartesian3 from '../Core/Cartesian3.js';
|
import Cartesian4 from '../Core/Cartesian4.js';
|
import Cartographic from '../Core/Cartographic.js';
|
import defined from '../Core/defined.js';
|
import DeveloperError from '../Core/DeveloperError.js';
|
import HeightmapTerrainData from '../Core/HeightmapTerrainData.js';
|
import CesiumMath from '../Core/Math.js';
|
import OrientedBoundingBox from '../Core/OrientedBoundingBox.js';
|
import Queue from '../Core/Queue.js';
|
import Rectangle from '../Core/Rectangle.js';
|
import TerrainEncoding from '../Core/TerrainEncoding.js';
|
import TerrainMesh from '../Core/TerrainMesh.js';
|
import TileEdge from '../Core/TileEdge.js';
|
import WebMercatorProjection from '../Core/WebMercatorProjection.js';
|
import GlobeSurfaceTile from './GlobeSurfaceTile.js';
|
import TileSelectionResult from './TileSelectionResult.js';
|
|
function TerrainFillMesh(tile) {
|
this.tile = tile;
|
this.frameLastUpdated = undefined;
|
this.westMeshes = []; // north to south (CCW)
|
this.westTiles = [];
|
this.southMeshes = []; // west to east (CCW)
|
this.southTiles = [];
|
this.eastMeshes = []; // south to north (CCW)
|
this.eastTiles = [];
|
this.northMeshes = []; // east to west (CCW)
|
this.northTiles = [];
|
this.southwestMesh = undefined;
|
this.southwestTile = undefined;
|
this.southeastMesh = undefined;
|
this.southeastTile = undefined;
|
this.northwestMesh = undefined;
|
this.northwestTile = undefined;
|
this.northeastMesh = undefined;
|
this.northeastTile = undefined;
|
this.changedThisFrame = true;
|
this.visitedFrame = undefined;
|
this.enqueuedFrame = undefined;
|
this.mesh = undefined;
|
this.vertexArray = undefined;
|
this.waterMaskTexture = undefined;
|
this.waterMaskTranslationAndScale = new Cartesian4();
|
}
|
|
TerrainFillMesh.prototype.update = function(tileProvider, frameState, vertexArraysToDestroy) {
|
if (this.changedThisFrame) {
|
createFillMesh(tileProvider, frameState, this.tile, vertexArraysToDestroy);
|
this.changedThisFrame = false;
|
}
|
};
|
|
TerrainFillMesh.prototype.destroy = function(vertexArraysToDestroy) {
|
if (defined(this.vertexArray)) {
|
if (defined(vertexArraysToDestroy)) {
|
vertexArraysToDestroy.push(this.vertexArray);
|
} else {
|
GlobeSurfaceTile._freeVertexArray(this.vertexArray, vertexArraysToDestroy);
|
}
|
this.vertexArray = undefined;
|
}
|
|
if (defined(this.waterMaskTexture)) {
|
--this.waterMaskTexture.referenceCount;
|
if (this.waterMaskTexture.referenceCount === 0) {
|
this.waterMaskTexture.destroy();
|
}
|
this.waterMaskTexture = undefined;
|
}
|
|
return undefined;
|
};
|
|
var traversalQueueScratch = new Queue();
|
|
TerrainFillMesh.updateFillTiles = function(tileProvider, renderedTiles, frameState, vertexArraysToDestroy) {
|
// We want our fill tiles to look natural, which means they should align perfectly with
|
// adjacent loaded tiles, and their edges that are not adjacent to loaded tiles should have
|
// sensible heights (e.g. the average of the heights of loaded edges). Some fill tiles may
|
// be adjacent only to other fill tiles, and in that case heights should be assigned fanning
|
// outward from the loaded tiles so that there are no sudden changes in height.
|
|
// We do this with a breadth-first traversal of the rendered tiles, starting with the loaded
|
// ones. Graph nodes are tiles and graph edges connect to other rendered tiles that are spatially adjacent
|
// to those tiles. As we visit each node, we propagate tile edges to adjacent tiles. If there's no data
|
// for a tile edge, we create an edge with an average height and then propagate it. If an edge is partially defined
|
// (e.g. an edge is adjacent to multiple more-detailed tiles and only some of them are loaded), we
|
// fill in the rest of the edge with the same height.
|
var quadtree = tileProvider._quadtree;
|
var levelZeroTiles = quadtree._levelZeroTiles;
|
var lastSelectionFrameNumber = quadtree._lastSelectionFrameNumber;
|
|
var traversalQueue = traversalQueueScratch;
|
traversalQueue.clear();
|
|
// Add the tiles with real geometry to the traversal queue.
|
for (var i = 0; i < renderedTiles.length; ++i) {
|
var renderedTile = renderedTiles[i];
|
if (defined(renderedTile.data.vertexArray)) {
|
traversalQueue.enqueue(renderedTiles[i]);
|
}
|
}
|
|
var tile = traversalQueue.dequeue();
|
|
while (tile !== undefined) {
|
var tileToWest = tile.findTileToWest(levelZeroTiles);
|
var tileToSouth = tile.findTileToSouth(levelZeroTiles);
|
var tileToEast = tile.findTileToEast(levelZeroTiles);
|
var tileToNorth = tile.findTileToNorth(levelZeroTiles);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToWest, lastSelectionFrameNumber, TileEdge.EAST, false, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToSouth, lastSelectionFrameNumber, TileEdge.NORTH, false, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToEast, lastSelectionFrameNumber, TileEdge.WEST, false, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToNorth, lastSelectionFrameNumber, TileEdge.SOUTH, false, traversalQueue, vertexArraysToDestroy);
|
|
var tileToNorthwest = tileToWest.findTileToNorth(levelZeroTiles);
|
var tileToSouthwest = tileToWest.findTileToSouth(levelZeroTiles);
|
var tileToNortheast = tileToEast.findTileToNorth(levelZeroTiles);
|
var tileToSoutheast = tileToEast.findTileToSouth(levelZeroTiles);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToNorthwest, lastSelectionFrameNumber, TileEdge.SOUTHEAST, false, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToNortheast, lastSelectionFrameNumber, TileEdge.SOUTHWEST, false, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToSouthwest, lastSelectionFrameNumber, TileEdge.NORTHEAST, false, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, tile, tileToSoutheast, lastSelectionFrameNumber, TileEdge.NORTHWEST, false, traversalQueue, vertexArraysToDestroy);
|
|
tile = traversalQueue.dequeue();
|
}
|
};
|
|
function visitRenderedTiles(tileProvider, frameState, sourceTile, startTile, currentFrameNumber, tileEdge, downOnly, traversalQueue, vertexArraysToDestroy) {
|
if (startTile === undefined) {
|
// There are no tiles North or South of the poles.
|
return;
|
}
|
|
var tile = startTile;
|
while (tile && (tile._lastSelectionResultFrame !== currentFrameNumber || TileSelectionResult.wasKicked(tile._lastSelectionResult) || TileSelectionResult.originalResult(tile._lastSelectionResult) === TileSelectionResult.CULLED)) {
|
// This tile wasn't visited or it was visited and then kicked, so walk up to find the closest ancestor that was rendered.
|
// We also walk up if the tile was culled, because if siblings were kicked an ancestor may have been rendered.
|
if (downOnly) {
|
return;
|
}
|
|
var parent = tile.parent;
|
if (tileEdge >= TileEdge.NORTHWEST && parent !== undefined) {
|
// When we're looking for a corner, verify that the parent tile is still relevant.
|
// That is, the parent and child must share the corner in question.
|
switch (tileEdge) {
|
case TileEdge.NORTHWEST:
|
tile = tile === parent.northwestChild ? parent : undefined;
|
break;
|
case TileEdge.NORTHEAST:
|
tile = tile === parent.northeastChild ? parent : undefined;
|
break;
|
case TileEdge.SOUTHWEST:
|
tile = tile === parent.southwestChild ? parent : undefined;
|
break;
|
case TileEdge.SOUTHEAST:
|
tile = tile === parent.southeastChild ? parent : undefined;
|
break;
|
}
|
} else {
|
tile = parent;
|
}
|
}
|
|
if (tile === undefined) {
|
return;
|
}
|
|
if (tile._lastSelectionResult === TileSelectionResult.RENDERED) {
|
if (defined(tile.data.vertexArray)) {
|
// No further processing necessary for renderable tiles.
|
return;
|
}
|
visitTile(tileProvider, frameState, sourceTile, tile, tileEdge, currentFrameNumber, traversalQueue, vertexArraysToDestroy);
|
return;
|
}
|
|
if (TileSelectionResult.originalResult(startTile._lastSelectionResult) === TileSelectionResult.CULLED) {
|
return;
|
}
|
|
// This tile was refined, so find rendered children, if any.
|
// Visit the tiles in counter-clockwise order.
|
switch (tileEdge) {
|
case TileEdge.WEST:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
case TileEdge.EAST:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
case TileEdge.SOUTH:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
case TileEdge.NORTH:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
case TileEdge.NORTHWEST:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
case TileEdge.NORTHEAST:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
case TileEdge.SOUTHWEST:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
case TileEdge.SOUTHEAST:
|
visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
|
break;
|
default:
|
throw new DeveloperError('Invalid edge');
|
}
|
}
|
|
function visitTile(tileProvider, frameState, sourceTile, destinationTile, tileEdge, frameNumber, traversalQueue, vertexArraysToDestroy) {
|
var destinationSurfaceTile = destinationTile.data;
|
|
if (destinationSurfaceTile.fill === undefined) {
|
destinationSurfaceTile.fill = new TerrainFillMesh(destinationTile);
|
} else if (destinationSurfaceTile.fill.visitedFrame === frameNumber) {
|
// Don't propagate edges to tiles that have already been visited this frame.
|
return;
|
}
|
|
if (destinationSurfaceTile.fill.enqueuedFrame !== frameNumber) {
|
// First time visiting this tile this frame, add it to the traversal queue.
|
destinationSurfaceTile.fill.enqueuedFrame = frameNumber;
|
destinationSurfaceTile.fill.changedThisFrame = false;
|
traversalQueue.enqueue(destinationTile);
|
}
|
|
propagateEdge(tileProvider, frameState, sourceTile, destinationTile, tileEdge, vertexArraysToDestroy);
|
}
|
|
function propagateEdge(tileProvider, frameState, sourceTile, destinationTile, tileEdge, vertexArraysToDestroy) {
|
var destinationFill = destinationTile.data.fill;
|
|
var sourceMesh;
|
var sourceFill = sourceTile.data.fill;
|
if (defined(sourceFill)) {
|
sourceFill.visitedFrame = frameState.frameNumber;
|
|
// Source is a fill, create/update it if necessary.
|
if (sourceFill.changedThisFrame) {
|
createFillMesh(tileProvider, frameState, sourceTile, vertexArraysToDestroy);
|
sourceFill.changedThisFrame = false;
|
}
|
sourceMesh = sourceTile.data.fill.mesh;
|
} else {
|
sourceMesh = sourceTile.data.mesh;
|
}
|
|
var edgeMeshes;
|
var edgeTiles;
|
|
switch (tileEdge) {
|
case TileEdge.WEST:
|
edgeMeshes = destinationFill.westMeshes;
|
edgeTiles = destinationFill.westTiles;
|
break;
|
case TileEdge.SOUTH:
|
edgeMeshes = destinationFill.southMeshes;
|
edgeTiles = destinationFill.southTiles;
|
break;
|
case TileEdge.EAST:
|
edgeMeshes = destinationFill.eastMeshes;
|
edgeTiles = destinationFill.eastTiles;
|
break;
|
case TileEdge.NORTH:
|
edgeMeshes = destinationFill.northMeshes;
|
edgeTiles = destinationFill.northTiles;
|
break;
|
// Corners are simpler.
|
case TileEdge.NORTHWEST:
|
destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.northwestMesh !== sourceMesh;
|
destinationFill.northwestMesh = sourceMesh;
|
destinationFill.northwestTile = sourceTile;
|
return;
|
case TileEdge.NORTHEAST:
|
destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.northeastMesh !== sourceMesh;
|
destinationFill.northeastMesh = sourceMesh;
|
destinationFill.northeastTile = sourceTile;
|
return;
|
case TileEdge.SOUTHWEST:
|
destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.southwestMesh !== sourceMesh;
|
destinationFill.southwestMesh = sourceMesh;
|
destinationFill.southwestTile = sourceTile;
|
return;
|
case TileEdge.SOUTHEAST:
|
destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.southeastMesh !== sourceMesh;
|
destinationFill.southeastMesh = sourceMesh;
|
destinationFill.southeastTile = sourceTile;
|
return;
|
}
|
|
if (sourceTile.level <= destinationTile.level) {
|
// Source edge completely spans the destination edge.
|
destinationFill.changedThisFrame = destinationFill.changedThisFrame || edgeMeshes[0] !== sourceMesh || edgeMeshes.length !== 1;
|
edgeMeshes[0] = sourceMesh;
|
edgeTiles[0] = sourceTile;
|
edgeMeshes.length = 1;
|
edgeTiles.length = 1;
|
return;
|
}
|
|
// Source edge is a subset of the destination edge.
|
// Figure out the range of meshes we're replacing.
|
var startIndex, endIndex, existingTile, existingRectangle;
|
var sourceRectangle = sourceTile.rectangle;
|
|
var epsilon;
|
var destinationRectangle = destinationTile.rectangle;
|
|
switch (tileEdge) {
|
case TileEdge.WEST:
|
epsilon = (destinationRectangle.north - destinationRectangle.south) * CesiumMath.EPSILON5;
|
|
for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
|
existingTile = edgeTiles[startIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.greaterThan(sourceRectangle.north, existingRectangle.south, epsilon)) {
|
break;
|
}
|
}
|
for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
|
existingTile = edgeTiles[endIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.greaterThanOrEquals(sourceRectangle.south, existingRectangle.north, epsilon)) {
|
break;
|
}
|
}
|
break;
|
case TileEdge.SOUTH:
|
epsilon = (destinationRectangle.east - destinationRectangle.west) * CesiumMath.EPSILON5;
|
|
for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
|
existingTile = edgeTiles[startIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.lessThan(sourceRectangle.west, existingRectangle.east, epsilon)) {
|
break;
|
}
|
}
|
for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
|
existingTile = edgeTiles[endIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.lessThanOrEquals(sourceRectangle.east, existingRectangle.west, epsilon)) {
|
break;
|
}
|
}
|
break;
|
case TileEdge.EAST:
|
epsilon = (destinationRectangle.north - destinationRectangle.south) * CesiumMath.EPSILON5;
|
|
for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
|
existingTile = edgeTiles[startIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.lessThan(sourceRectangle.south, existingRectangle.north, epsilon)) {
|
break;
|
}
|
}
|
for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
|
existingTile = edgeTiles[endIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.lessThanOrEquals(sourceRectangle.north, existingRectangle.south, epsilon)) {
|
break;
|
}
|
}
|
break;
|
case TileEdge.NORTH:
|
epsilon = (destinationRectangle.east - destinationRectangle.west) * CesiumMath.EPSILON5;
|
|
for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
|
existingTile = edgeTiles[startIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.greaterThan(sourceRectangle.east, existingRectangle.west, epsilon)) {
|
break;
|
}
|
}
|
for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
|
existingTile = edgeTiles[endIndex];
|
existingRectangle = existingTile.rectangle;
|
if (CesiumMath.greaterThanOrEquals(sourceRectangle.west, existingRectangle.east, epsilon)) {
|
break;
|
}
|
}
|
break;
|
}
|
|
if (endIndex - startIndex === 1) {
|
destinationFill.changedThisFrame = destinationFill.changedThisFrame || edgeMeshes[startIndex] !== sourceMesh;
|
edgeMeshes[startIndex] = sourceMesh;
|
edgeTiles[startIndex] = sourceTile;
|
} else {
|
destinationFill.changedThisFrame = true;
|
edgeMeshes.splice(startIndex, endIndex - startIndex, sourceMesh);
|
edgeTiles.splice(startIndex, endIndex - startIndex, sourceTile);
|
}
|
}
|
|
var cartographicScratch = new Cartographic();
|
var centerCartographicScratch = new Cartographic();
|
var cartesianScratch = new Cartesian3();
|
var normalScratch = new Cartesian3();
|
var octEncodedNormalScratch = new Cartesian2();
|
var uvScratch2 = new Cartesian2();
|
var uvScratch = new Cartesian2();
|
|
function HeightAndNormal() {
|
this.height = 0.0;
|
this.encodedNormal = new Cartesian2();
|
}
|
|
function fillMissingCorner(fill, ellipsoid, u, v, corner, adjacentCorner1, adjacentCorner2, oppositeCorner, vertex) {
|
if (defined(corner)) {
|
return corner;
|
}
|
|
var height;
|
|
if (defined(adjacentCorner1) && defined(adjacentCorner2)) {
|
height = (adjacentCorner1.height + adjacentCorner2.height) * 0.5;
|
} else if (defined(adjacentCorner1)) {
|
height = adjacentCorner1.height;
|
} else if (defined(adjacentCorner2)) {
|
height = adjacentCorner2.height;
|
} else if (defined(oppositeCorner)) {
|
height = oppositeCorner.height;
|
} else {
|
var surfaceTile = fill.tile.data;
|
var tileBoundingRegion = surfaceTile.tileBoundingRegion;
|
var minimumHeight = 0.0;
|
var maximumHeight = 0.0;
|
if (defined(tileBoundingRegion)) {
|
minimumHeight = tileBoundingRegion.minimumHeight;
|
maximumHeight = tileBoundingRegion.maximumHeight;
|
}
|
height = (minimumHeight + maximumHeight) * 0.5;
|
}
|
|
getVertexWithHeightAtCorner(fill, ellipsoid, u, v, height, vertex);
|
return vertex;
|
}
|
|
var heightRangeScratch = {
|
minimumHeight: 0.0,
|
maximumHeight: 0.0
|
};
|
|
var swVertexScratch = new HeightAndNormal();
|
var seVertexScratch = new HeightAndNormal();
|
var nwVertexScratch = new HeightAndNormal();
|
var neVertexScratch = new HeightAndNormal();
|
var heightmapBuffer = typeof Uint8Array !== 'undefined' ? new Uint8Array(9 * 9) : undefined;
|
|
function createFillMesh(tileProvider, frameState, tile, vertexArraysToDestroy) {
|
GlobeSurfaceTile.initialize(tile, tileProvider.terrainProvider, tileProvider._imageryLayers);
|
|
var surfaceTile = tile.data;
|
var fill = surfaceTile.fill;
|
var rectangle = tile.rectangle;
|
|
var ellipsoid = tile.tilingScheme.ellipsoid;
|
|
var nwCorner = getCorner(fill, ellipsoid, 0.0, 1.0, fill.northwestTile, fill.northwestMesh, fill.northTiles, fill.northMeshes, fill.westTiles, fill.westMeshes, nwVertexScratch);
|
var swCorner = getCorner(fill, ellipsoid, 0.0, 0.0, fill.southwestTile, fill.southwestMesh, fill.westTiles, fill.westMeshes, fill.southTiles, fill.southMeshes, swVertexScratch);
|
var seCorner = getCorner(fill, ellipsoid, 1.0, 0.0, fill.southeastTile, fill.southeastMesh, fill.southTiles, fill.southMeshes, fill.eastTiles, fill.eastMeshes, seVertexScratch);
|
var neCorner = getCorner(fill, ellipsoid, 1.0, 1.0, fill.northeastTile, fill.northeastMesh, fill.eastTiles, fill.eastMeshes, fill.northTiles, fill.northMeshes, neVertexScratch);
|
|
nwCorner = fillMissingCorner(fill, ellipsoid, 0.0, 1.0, nwCorner, swCorner, neCorner, seCorner, nwVertexScratch);
|
swCorner = fillMissingCorner(fill, ellipsoid, 0.0, 0.0, swCorner, nwCorner, seCorner, neCorner, swVertexScratch);
|
seCorner = fillMissingCorner(fill, ellipsoid, 1.0, 1.0, seCorner, swCorner, neCorner, nwCorner, seVertexScratch);
|
neCorner = fillMissingCorner(fill, ellipsoid, 1.0, 1.0, neCorner, seCorner, nwCorner, swCorner, neVertexScratch);
|
|
var southwestHeight = swCorner.height;
|
var southeastHeight = seCorner.height;
|
var northwestHeight = nwCorner.height;
|
var northeastHeight = neCorner.height;
|
|
var minimumHeight = Math.min(southwestHeight, southeastHeight, northwestHeight, northeastHeight);
|
var maximumHeight = Math.max(southwestHeight, southeastHeight, northwestHeight, northeastHeight);
|
|
var middleHeight = (minimumHeight + maximumHeight) * 0.5;
|
|
var i;
|
var len;
|
|
// For low-detail tiles, our usual fill tile approach will create tiles that
|
// look really blocky because they don't have enough vertices to account for the
|
// Earth's curvature. But the height range will also typically be well within
|
// the allowed geometric error for those levels. So fill such tiles with a
|
// constant-height heightmap.
|
var geometricError = tileProvider.getLevelMaximumGeometricError(tile.level);
|
var minCutThroughRadius = ellipsoid.maximumRadius - geometricError;
|
var maxTileWidth = Math.acos(minCutThroughRadius / ellipsoid.maximumRadius) * 4.0;
|
|
// When the tile width is greater than maxTileWidth as computed above, the error
|
// of a normal fill tile from globe curvature alone will exceed the allowed geometric
|
// error. Terrain won't change that much. However, we can allow more error than that.
|
// A little blockiness during load is acceptable. For the WGS84 ellipsoid and
|
// standard geometric error setup, the value here will have us use a heightmap
|
// at levels 1, 2, and 3.
|
maxTileWidth *= 1.5;
|
|
if (rectangle.width > maxTileWidth && (maximumHeight - minimumHeight) <= geometricError) {
|
var terrainData = new HeightmapTerrainData({
|
width: 9,
|
height: 9,
|
buffer: heightmapBuffer,
|
structure: {
|
// Use the maximum as the constant height so that this tile's skirt
|
// covers any cracks with adjacent tiles.
|
heightOffset: maximumHeight
|
}
|
});
|
fill.mesh = terrainData._createMeshSync(tile.tilingScheme, tile.x, tile.y, tile.level, 1.0);
|
} else {
|
var encoding = new TerrainEncoding(undefined, undefined, undefined, undefined, true, true);
|
|
var centerCartographic = centerCartographicScratch;
|
centerCartographic.longitude = (rectangle.east + rectangle.west) * 0.5;
|
centerCartographic.latitude = (rectangle.north + rectangle.south) * 0.5;
|
centerCartographic.height = middleHeight;
|
encoding.center = ellipsoid.cartographicToCartesian(centerCartographic, encoding.center);
|
|
// At _most_, we have vertices for the 4 corners, plus 1 center, plus every adjacent edge vertex.
|
// In reality there will be less most of the time, but close enough; better
|
// to overestimate than to re-allocate/copy/traverse the vertices twice.
|
// Also, we'll often be able to squeeze the index data into the extra space in the buffer.
|
var maxVertexCount = 5;
|
var meshes;
|
|
meshes = fill.westMeshes;
|
for (i = 0, len = meshes.length; i < len; ++i) {
|
maxVertexCount += meshes[i].eastIndicesNorthToSouth.length;
|
}
|
|
meshes = fill.southMeshes;
|
for (i = 0, len = meshes.length; i < len; ++i) {
|
maxVertexCount += meshes[i].northIndicesWestToEast.length;
|
}
|
|
meshes = fill.eastMeshes;
|
for (i = 0, len = meshes.length; i < len; ++i) {
|
maxVertexCount += meshes[i].westIndicesSouthToNorth.length;
|
}
|
|
meshes = fill.northMeshes;
|
for (i = 0, len = meshes.length; i < len; ++i) {
|
maxVertexCount += meshes[i].southIndicesEastToWest.length;
|
}
|
|
var heightRange = heightRangeScratch;
|
heightRange.minimumHeight = minimumHeight;
|
heightRange.maximumHeight = maximumHeight;
|
|
var stride = encoding.getStride();
|
var typedArray = new Float32Array(maxVertexCount * stride);
|
|
var nextIndex = 0;
|
var northwestIndex = nextIndex;
|
nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 0.0, 1.0, nwCorner.height, nwCorner.encodedNormal, 1.0, heightRange);
|
nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.westTiles, fill.westMeshes, TileEdge.EAST, heightRange);
|
var southwestIndex = nextIndex;
|
nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 0.0, 0.0, swCorner.height, swCorner.encodedNormal, 0.0, heightRange);
|
nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.southTiles, fill.southMeshes, TileEdge.NORTH, heightRange);
|
var southeastIndex = nextIndex;
|
nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 1.0, 0.0, seCorner.height, seCorner.encodedNormal, 0.0, heightRange);
|
nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.eastTiles, fill.eastMeshes, TileEdge.WEST, heightRange);
|
var northeastIndex = nextIndex;
|
nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 1.0, 1.0, neCorner.height, neCorner.encodedNormal, 1.0, heightRange);
|
nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.northTiles, fill.northMeshes, TileEdge.SOUTH, heightRange);
|
|
minimumHeight = heightRange.minimumHeight;
|
maximumHeight = heightRange.maximumHeight;
|
|
var obb = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, tile.tilingScheme.ellipsoid);
|
|
// Add a single vertex at the center of the tile.
|
var southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(rectangle.south);
|
var oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(rectangle.north) - southMercatorY);
|
var centerWebMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(centerCartographic.latitude) - southMercatorY) * oneOverMercatorHeight;
|
|
ellipsoid.geodeticSurfaceNormalCartographic(cartographicScratch, normalScratch);
|
var centerEncodedNormal = AttributeCompression.octEncode(normalScratch, octEncodedNormalScratch);
|
|
var centerIndex = nextIndex;
|
encoding.encode(typedArray, nextIndex * stride, obb.center, Cartesian2.fromElements(0.5, 0.5, uvScratch), middleHeight, centerEncodedNormal, centerWebMercatorT);
|
++nextIndex;
|
|
var vertexCount = nextIndex;
|
|
var bytesPerIndex = vertexCount < 256 ? 1 : 2;
|
var indexCount = (vertexCount - 1) * 3; // one triangle per edge vertex
|
var indexDataBytes = indexCount * bytesPerIndex;
|
var availableBytesInBuffer = (typedArray.length - vertexCount * stride) * Float32Array.BYTES_PER_ELEMENT;
|
|
var indices;
|
if (availableBytesInBuffer >= indexDataBytes) {
|
// Store the index data in the same buffer as the vertex data.
|
var startIndex = vertexCount * stride * Float32Array.BYTES_PER_ELEMENT;
|
indices = vertexCount < 256
|
? new Uint8Array(typedArray.buffer, startIndex, indexCount)
|
: new Uint16Array(typedArray.buffer, startIndex, indexCount);
|
} else {
|
// Allocate a new buffer for the index data.
|
indices = vertexCount < 256 ? new Uint8Array(indexCount) : new Uint16Array(indexCount);
|
}
|
|
typedArray = new Float32Array(typedArray.buffer, 0, vertexCount * stride);
|
|
var indexOut = 0;
|
for (i = 0; i < vertexCount - 2; ++i) {
|
indices[indexOut++] = centerIndex;
|
indices[indexOut++] = i;
|
indices[indexOut++] = i + 1;
|
}
|
|
indices[indexOut++] = centerIndex;
|
indices[indexOut++] = i;
|
indices[indexOut++] = 0;
|
|
var westIndicesSouthToNorth = [];
|
for (i = southwestIndex; i >= northwestIndex; --i) {
|
westIndicesSouthToNorth.push(i);
|
}
|
|
var southIndicesEastToWest = [];
|
for (i = southeastIndex; i >= southwestIndex; --i) {
|
southIndicesEastToWest.push(i);
|
}
|
|
var eastIndicesNorthToSouth = [];
|
for (i = northeastIndex; i >= southeastIndex; --i) {
|
eastIndicesNorthToSouth.push(i);
|
}
|
|
var northIndicesWestToEast = [];
|
northIndicesWestToEast.push(0);
|
for (i = centerIndex - 1; i >= northeastIndex; --i) {
|
northIndicesWestToEast.push(i);
|
}
|
|
fill.mesh = new TerrainMesh(
|
encoding.center,
|
typedArray,
|
indices,
|
indexCount,
|
vertexCount,
|
minimumHeight,
|
maximumHeight,
|
BoundingSphere.fromOrientedBoundingBox(obb),
|
computeOccludeePoint(tileProvider, obb.center, rectangle, minimumHeight, maximumHeight),
|
encoding.getStride(),
|
obb,
|
encoding,
|
frameState.terrainExaggeration,
|
westIndicesSouthToNorth,
|
southIndicesEastToWest,
|
eastIndicesNorthToSouth,
|
northIndicesWestToEast
|
);
|
}
|
|
var context = frameState.context;
|
|
if (defined(fill.vertexArray)) {
|
if (defined(vertexArraysToDestroy)) {
|
vertexArraysToDestroy.push(fill.vertexArray);
|
} else {
|
GlobeSurfaceTile._freeVertexArray(fill.vertexArray);
|
}
|
}
|
|
fill.vertexArray = GlobeSurfaceTile._createVertexArrayForMesh(context, fill.mesh);
|
surfaceTile.processImagery(tile, tileProvider.terrainProvider, frameState, true);
|
|
var oldTexture = fill.waterMaskTexture;
|
fill.waterMaskTexture = undefined;
|
|
if (tileProvider.terrainProvider.hasWaterMask) {
|
var waterSourceTile = surfaceTile._findAncestorTileWithTerrainData(tile);
|
if (defined(waterSourceTile) && defined(waterSourceTile.data.waterMaskTexture)) {
|
fill.waterMaskTexture = waterSourceTile.data.waterMaskTexture;
|
++fill.waterMaskTexture.referenceCount;
|
surfaceTile._computeWaterMaskTranslationAndScale(tile, waterSourceTile, fill.waterMaskTranslationAndScale);
|
}
|
}
|
|
if (defined(oldTexture)) {
|
--oldTexture.referenceCount;
|
if (oldTexture.referenceCount === 0) {
|
oldTexture.destroy();
|
}
|
}
|
}
|
|
function addVertexWithComputedPosition(ellipsoid, rectangle, encoding, buffer, index, u, v, height, encodedNormal, webMercatorT, heightRange) {
|
var cartographic = cartographicScratch;
|
cartographic.longitude = CesiumMath.lerp(rectangle.west, rectangle.east, u);
|
cartographic.latitude = CesiumMath.lerp(rectangle.south, rectangle.north, v);
|
cartographic.height = height;
|
var position = ellipsoid.cartographicToCartesian(cartographic, cartesianScratch);
|
|
var uv = uvScratch2;
|
uv.x = u;
|
uv.y = v;
|
|
encoding.encode(buffer, index * encoding.getStride(), position, uv, height, encodedNormal, webMercatorT);
|
|
heightRange.minimumHeight = Math.min(heightRange.minimumHeight, height);
|
heightRange.maximumHeight = Math.max(heightRange.maximumHeight, height);
|
|
return index + 1;
|
}
|
|
var sourceRectangleScratch = new Rectangle();
|
|
function transformTextureCoordinates(sourceTile, targetTile, coordinates, result) {
|
var sourceRectangle = sourceTile.rectangle;
|
var targetRectangle = targetTile.rectangle;
|
|
// Handle transforming across the anti-meridian.
|
if (targetTile.x === 0 && coordinates.x === 1.0 && sourceTile.x === sourceTile.tilingScheme.getNumberOfXTilesAtLevel(sourceTile.level) - 1) {
|
sourceRectangle = Rectangle.clone(sourceTile.rectangle, sourceRectangleScratch);
|
sourceRectangle.west -= CesiumMath.TWO_PI;
|
sourceRectangle.east -= CesiumMath.TWO_PI;
|
} else if (sourceTile.x === 0 && coordinates.x === 0.0 && targetTile.x === targetTile.tilingScheme.getNumberOfXTilesAtLevel(targetTile.level) - 1) {
|
sourceRectangle = Rectangle.clone(sourceTile.rectangle, sourceRectangleScratch);
|
sourceRectangle.west += CesiumMath.TWO_PI;
|
sourceRectangle.east += CesiumMath.TWO_PI;
|
}
|
|
var sourceWidth = sourceRectangle.east - sourceRectangle.west;
|
var umin = (targetRectangle.west - sourceRectangle.west) / sourceWidth;
|
var umax = (targetRectangle.east - sourceRectangle.west) / sourceWidth;
|
|
var sourceHeight = sourceRectangle.north - sourceRectangle.south;
|
var vmin = (targetRectangle.south - sourceRectangle.south) / sourceHeight;
|
var vmax = (targetRectangle.north - sourceRectangle.south) / sourceHeight;
|
|
var u = (coordinates.x - umin) / (umax - umin);
|
var v = (coordinates.y - vmin) / (vmax - vmin);
|
|
// Ensure that coordinates very near the corners are at the corners.
|
if (Math.abs(u) < Math.EPSILON5) {
|
u = 0.0;
|
} else if (Math.abs(u - 1.0) < Math.EPSILON5) {
|
u = 1.0;
|
}
|
|
if (Math.abs(v) < Math.EPSILON5) {
|
v = 0.0;
|
} else if (Math.abs(v - 1.0) < Math.EPSILON5) {
|
v = 1.0;
|
}
|
|
result.x = u;
|
result.y = v;
|
return result;
|
}
|
|
var encodedNormalScratch = new Cartesian2();
|
|
function getVertexFromTileAtCorner(sourceMesh, sourceIndex, u, v, vertex) {
|
var sourceEncoding = sourceMesh.encoding;
|
var sourceVertices = sourceMesh.vertices;
|
|
vertex.height = sourceEncoding.decodeHeight(sourceVertices, sourceIndex);
|
|
if (sourceEncoding.hasVertexNormals) {
|
sourceEncoding.getOctEncodedNormal(sourceVertices, sourceIndex, vertex.encodedNormal);
|
} else {
|
var normal = vertex.encodedNormal;
|
normal.x = 0.0;
|
normal.y = 0.0;
|
}
|
}
|
|
var encodedNormalScratch2 = new Cartesian2();
|
var cartesianScratch2 = new Cartesian3();
|
|
function getInterpolatedVertexAtCorner(ellipsoid, sourceTile, targetTile, sourceMesh, previousIndex, nextIndex, u, v, interpolateU, vertex) {
|
var sourceEncoding = sourceMesh.encoding;
|
var sourceVertices = sourceMesh.vertices;
|
|
var previousUv = transformTextureCoordinates(sourceTile, targetTile, sourceEncoding.decodeTextureCoordinates(sourceVertices, previousIndex, uvScratch), uvScratch);
|
var nextUv = transformTextureCoordinates(sourceTile, targetTile, sourceEncoding.decodeTextureCoordinates(sourceVertices, nextIndex, uvScratch2), uvScratch2);
|
|
var ratio;
|
if (interpolateU) {
|
ratio = (u - previousUv.x) / (nextUv.x - previousUv.x);
|
} else {
|
ratio = (v - previousUv.y) / (nextUv.y - previousUv.y);
|
}
|
|
var height1 = sourceEncoding.decodeHeight(sourceVertices, previousIndex);
|
var height2 = sourceEncoding.decodeHeight(sourceVertices, nextIndex);
|
|
var targetRectangle = targetTile.rectangle;
|
cartographicScratch.longitude = CesiumMath.lerp(targetRectangle.west, targetRectangle.east, u);
|
cartographicScratch.latitude = CesiumMath.lerp(targetRectangle.south, targetRectangle.north, v);
|
vertex.height = cartographicScratch.height = CesiumMath.lerp(height1, height2, ratio);
|
|
var normal;
|
if (sourceEncoding.hasVertexNormals) {
|
var encodedNormal1 = sourceEncoding.getOctEncodedNormal(sourceVertices, previousIndex, encodedNormalScratch);
|
var encodedNormal2 = sourceEncoding.getOctEncodedNormal(sourceVertices, nextIndex, encodedNormalScratch2);
|
var normal1 = AttributeCompression.octDecode(encodedNormal1.x, encodedNormal1.y, cartesianScratch);
|
var normal2 = AttributeCompression.octDecode(encodedNormal2.x, encodedNormal2.y, cartesianScratch2);
|
normal = Cartesian3.lerp(normal1, normal2, ratio, cartesianScratch);
|
Cartesian3.normalize(normal, normal);
|
AttributeCompression.octEncode(normal, vertex.encodedNormal);
|
} else {
|
normal = ellipsoid.geodeticSurfaceNormalCartographic(cartographicScratch, cartesianScratch);
|
AttributeCompression.octEncode(normal, vertex.encodedNormal);
|
}
|
}
|
|
function getVertexWithHeightAtCorner(terrainFillMesh, ellipsoid, u, v, height, vertex) {
|
vertex.height = height;
|
var normal = ellipsoid.geodeticSurfaceNormalCartographic(cartographicScratch, cartesianScratch);
|
AttributeCompression.octEncode(normal, vertex.encodedNormal);
|
}
|
|
function getCorner(
|
terrainFillMesh,
|
ellipsoid,
|
u, v,
|
cornerTile, cornerMesh,
|
previousEdgeTiles, previousEdgeMeshes,
|
nextEdgeTiles, nextEdgeMeshes,
|
vertex
|
) {
|
var gotCorner =
|
getCornerFromEdge(terrainFillMesh, ellipsoid, previousEdgeMeshes, previousEdgeTiles, false, u, v, vertex) ||
|
getCornerFromEdge(terrainFillMesh, ellipsoid, nextEdgeMeshes, nextEdgeTiles, true, u, v, vertex);
|
if (gotCorner) {
|
return vertex;
|
}
|
|
var vertexIndex;
|
|
if (meshIsUsable(cornerTile, cornerMesh)) {
|
// Corner mesh is valid, copy its corner vertex to this mesh.
|
if (u === 0.0) {
|
if (v === 0.0) {
|
// southwest destination, northeast source
|
vertexIndex = cornerMesh.eastIndicesNorthToSouth[0];
|
} else {
|
// northwest destination, southeast source
|
vertexIndex = cornerMesh.southIndicesEastToWest[0];
|
}
|
} else if (v === 0.0) {
|
// southeast destination, northwest source
|
vertexIndex = cornerMesh.northIndicesWestToEast[0];
|
} else {
|
// northeast destination, southwest source
|
vertexIndex = cornerMesh.westIndicesSouthToNorth[0];
|
}
|
getVertexFromTileAtCorner(cornerMesh, vertexIndex, u, v, vertex);
|
return vertex;
|
}
|
|
// There is no precise vertex available from the corner or from either adjacent edge.
|
// This is either because there are no tiles at all at the edges and corner, or
|
// because the tiles at the edge are higher-level-number and don't extend all the way
|
// to the corner.
|
// Try to grab a height from the adjacent edges.
|
var height;
|
if (u === 0.0) {
|
if (v === 0.0) {
|
// southwest
|
height = getClosestHeightToCorner(
|
terrainFillMesh.westMeshes, terrainFillMesh.westTiles, TileEdge.EAST,
|
terrainFillMesh.southMeshes, terrainFillMesh.southTiles, TileEdge.NORTH,
|
u, v);
|
} else {
|
// northwest
|
height = getClosestHeightToCorner(
|
terrainFillMesh.northMeshes, terrainFillMesh.northTiles, TileEdge.SOUTH,
|
terrainFillMesh.westMeshes, terrainFillMesh.westTiles, TileEdge.EAST,
|
u, v);
|
}
|
} else if (v === 0.0) {
|
// southeast
|
height = getClosestHeightToCorner(
|
terrainFillMesh.southMeshes, terrainFillMesh.southTiles, TileEdge.NORTH,
|
terrainFillMesh.eastMeshes, terrainFillMesh.eastTiles, TileEdge.WEST,
|
u, v);
|
} else {
|
// northeast
|
height = getClosestHeightToCorner(
|
terrainFillMesh.eastMeshes, terrainFillMesh.eastTiles, TileEdge.WEST,
|
terrainFillMesh.northMeshes, terrainFillMesh.northTiles, TileEdge.SOUTH,
|
u, v);
|
}
|
|
if (defined(height)) {
|
getVertexWithHeightAtCorner(terrainFillMesh, ellipsoid, u, v, height, vertex);
|
return vertex;
|
}
|
|
// No heights available that are closer than the adjacent corners.
|
return undefined;
|
}
|
|
function getClosestHeightToCorner(
|
previousMeshes, previousTiles, previousEdge,
|
nextMeshes, nextTiles, nextEdge,
|
u, v
|
) {
|
var height1 = getNearestHeightOnEdge(previousMeshes, previousTiles, false, previousEdge, u, v);
|
var height2 = getNearestHeightOnEdge(nextMeshes, nextTiles, true, nextEdge, u, v);
|
if (defined(height1) && defined(height2)) {
|
// It would be slightly better to do a weighted average of the two heights
|
// based on their distance from the corner, but it shouldn't matter much in practice.
|
return (height1 + height2) * 0.5;
|
} else if (defined(height1)) {
|
return height1;
|
}
|
return height2;
|
}
|
|
function addEdge(terrainFillMesh, ellipsoid, encoding, typedArray, nextIndex, edgeTiles, edgeMeshes, tileEdge, heightRange) {
|
for (var i = 0; i < edgeTiles.length; ++i) {
|
nextIndex = addEdgeMesh(terrainFillMesh, ellipsoid, encoding, typedArray, nextIndex, edgeTiles[i], edgeMeshes[i], tileEdge, heightRange);
|
}
|
return nextIndex;
|
}
|
|
function addEdgeMesh(terrainFillMesh, ellipsoid, encoding, typedArray, nextIndex, edgeTile, edgeMesh, tileEdge, heightRange) {
|
// Handle copying edges across the anti-meridian.
|
var sourceRectangle = edgeTile.rectangle;
|
if (tileEdge === TileEdge.EAST && terrainFillMesh.tile.x === 0) {
|
sourceRectangle = Rectangle.clone(edgeTile.rectangle, sourceRectangleScratch);
|
sourceRectangle.west -= CesiumMath.TWO_PI;
|
sourceRectangle.east -= CesiumMath.TWO_PI;
|
} else if (tileEdge === TileEdge.WEST && edgeTile.x === 0) {
|
sourceRectangle = Rectangle.clone(edgeTile.rectangle, sourceRectangleScratch);
|
sourceRectangle.west += CesiumMath.TWO_PI;
|
sourceRectangle.east += CesiumMath.TWO_PI;
|
}
|
|
var targetRectangle = terrainFillMesh.tile.rectangle;
|
|
var lastU;
|
var lastV;
|
|
if (nextIndex > 0) {
|
encoding.decodeTextureCoordinates(typedArray, nextIndex - 1, uvScratch);
|
lastU = uvScratch.x;
|
lastV = uvScratch.y;
|
}
|
|
var indices;
|
var compareU;
|
|
switch (tileEdge) {
|
case TileEdge.WEST:
|
indices = edgeMesh.westIndicesSouthToNorth;
|
compareU = false;
|
break;
|
case TileEdge.NORTH:
|
indices = edgeMesh.northIndicesWestToEast;
|
compareU = true;
|
break;
|
case TileEdge.EAST:
|
indices = edgeMesh.eastIndicesNorthToSouth;
|
compareU = false;
|
break;
|
case TileEdge.SOUTH:
|
indices = edgeMesh.southIndicesEastToWest;
|
compareU = true;
|
break;
|
}
|
|
var sourceTile = edgeTile;
|
var targetTile = terrainFillMesh.tile;
|
var sourceEncoding = edgeMesh.encoding;
|
var sourceVertices = edgeMesh.vertices;
|
var targetStride = encoding.getStride();
|
|
var southMercatorY;
|
var oneOverMercatorHeight;
|
if (sourceEncoding.hasWebMercatorT) {
|
southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(targetRectangle.south);
|
oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(targetRectangle.north) - southMercatorY);
|
}
|
|
for (var i = 0; i < indices.length; ++i) {
|
var index = indices[i];
|
|
var uv = sourceEncoding.decodeTextureCoordinates(sourceVertices, index, uvScratch);
|
transformTextureCoordinates(sourceTile, targetTile, uv, uv);
|
var u = uv.x;
|
var v = uv.y;
|
var uOrV = compareU ? u : v;
|
|
if (uOrV < 0.0 || uOrV > 1.0) {
|
// Vertex is outside the target tile - skip it.
|
continue;
|
}
|
|
if (Math.abs(u - lastU) < CesiumMath.EPSILON5 && Math.abs(v - lastV) < CesiumMath.EPSILON5) {
|
// Vertex is very close to the previous one - skip it.
|
continue;
|
}
|
|
var nearlyEdgeU = Math.abs(u) < CesiumMath.EPSILON5 || Math.abs(u - 1.0) < CesiumMath.EPSILON5;
|
var nearlyEdgeV = Math.abs(v) < CesiumMath.EPSILON5 || Math.abs(v - 1.0) < CesiumMath.EPSILON5;
|
|
if (nearlyEdgeU && nearlyEdgeV) {
|
// Corner vertex - skip it.
|
continue;
|
}
|
|
var position = sourceEncoding.decodePosition(sourceVertices, index, cartesianScratch);
|
var height = sourceEncoding.decodeHeight(sourceVertices, index);
|
|
var normal;
|
if (sourceEncoding.hasVertexNormals) {
|
normal = sourceEncoding.getOctEncodedNormal(sourceVertices, index, octEncodedNormalScratch);
|
} else {
|
normal = octEncodedNormalScratch;
|
normal.x = 0.0;
|
normal.y = 0.0;
|
}
|
|
var webMercatorT = v;
|
if (sourceEncoding.hasWebMercatorT) {
|
var latitude = CesiumMath.lerp(targetRectangle.south, targetRectangle.north, v);
|
webMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight;
|
}
|
|
encoding.encode(typedArray, nextIndex * targetStride, position, uv, height, normal, webMercatorT);
|
|
heightRange.minimumHeight = Math.min(heightRange.minimumHeight, height);
|
heightRange.maximumHeight = Math.max(heightRange.maximumHeight, height);
|
|
++nextIndex;
|
}
|
|
return nextIndex;
|
}
|
|
function getNearestHeightOnEdge(meshes, tiles, isNext, edge, u, v) {
|
var meshStart;
|
var meshEnd;
|
var meshStep;
|
|
if (isNext) {
|
meshStart = 0;
|
meshEnd = meshes.length;
|
meshStep = 1;
|
} else {
|
meshStart = meshes.length - 1;
|
meshEnd = -1;
|
meshStep = -1;
|
}
|
|
for (var meshIndex = meshStart; meshIndex !== meshEnd; meshIndex += meshStep) {
|
var mesh = meshes[meshIndex];
|
var tile = tiles[meshIndex];
|
if (!meshIsUsable(tile, mesh)) {
|
continue;
|
}
|
|
var indices;
|
switch (edge) {
|
case TileEdge.WEST:
|
indices = mesh.westIndicesSouthToNorth;
|
break;
|
case TileEdge.SOUTH:
|
indices = mesh.southIndicesEastToWest;
|
break;
|
case TileEdge.EAST:
|
indices = mesh.eastIndicesNorthToSouth;
|
break;
|
case TileEdge.NORTH:
|
indices = mesh.northIndicesWestToEast;
|
break;
|
}
|
|
var index = indices[isNext ? 0 : indices.length - 1];
|
if (defined(index)) {
|
return mesh.encoding.decodeHeight(mesh.vertices, index);
|
}
|
}
|
|
return undefined;
|
}
|
|
function meshIsUsable(tile, mesh) {
|
return defined(mesh) && (!defined(tile.data.fill) || !tile.data.fill.changedThisFrame);
|
}
|
|
function getCornerFromEdge(terrainFillMesh, ellipsoid, edgeMeshes, edgeTiles, isNext, u, v, vertex) {
|
var edgeVertices;
|
var compareU;
|
var increasing;
|
var vertexIndexIndex;
|
var vertexIndex;
|
var sourceTile = edgeTiles[isNext ? 0 : edgeMeshes.length - 1];
|
var sourceMesh = edgeMeshes[isNext ? 0 : edgeMeshes.length - 1];
|
|
if (meshIsUsable(sourceTile, sourceMesh)) {
|
// Previous mesh is valid, but we don't know yet if it covers this corner.
|
if (u === 0.0) {
|
if (v === 0.0) {
|
// southwest
|
edgeVertices = isNext ? sourceMesh.northIndicesWestToEast : sourceMesh.eastIndicesNorthToSouth;
|
compareU = isNext;
|
increasing = isNext;
|
} else {
|
// northwest
|
edgeVertices = isNext ? sourceMesh.eastIndicesNorthToSouth : sourceMesh.southIndicesEastToWest;
|
compareU = !isNext;
|
increasing = false;
|
}
|
} else if (v === 0.0) {
|
// southeast
|
edgeVertices = isNext ? sourceMesh.westIndicesSouthToNorth : sourceMesh.northIndicesWestToEast;
|
compareU = !isNext;
|
increasing = true;
|
} else {
|
// northeast
|
edgeVertices = isNext ? sourceMesh.southIndicesEastToWest : sourceMesh.westIndicesSouthToNorth;
|
compareU = isNext;
|
increasing = !isNext;
|
}
|
|
if (edgeVertices.length > 0) {
|
// The vertex we want will very often be the first/last vertex so check that first.
|
vertexIndexIndex = isNext ? 0 : edgeVertices.length - 1;
|
vertexIndex = edgeVertices[vertexIndexIndex];
|
sourceMesh.encoding.decodeTextureCoordinates(sourceMesh.vertices, vertexIndex, uvScratch);
|
var targetUv = transformTextureCoordinates(sourceTile, terrainFillMesh.tile, uvScratch, uvScratch);
|
if (targetUv.x === u && targetUv.y === v) {
|
// Vertex is good!
|
getVertexFromTileAtCorner(sourceMesh, vertexIndex, u, v, vertex);
|
return true;
|
}
|
|
// The last vertex is not the one we need, try binary searching for the right one.
|
vertexIndexIndex = binarySearch(edgeVertices, compareU ? u : v, function(vertexIndex, textureCoordinate) {
|
sourceMesh.encoding.decodeTextureCoordinates(sourceMesh.vertices, vertexIndex, uvScratch);
|
var targetUv = transformTextureCoordinates(sourceTile, terrainFillMesh.tile, uvScratch, uvScratch);
|
if (increasing) {
|
if (compareU) {
|
return targetUv.x - u;
|
}
|
return targetUv.y - v;
|
} else if (compareU) {
|
return u - targetUv.x;
|
}
|
return v - targetUv.y;
|
});
|
|
if (vertexIndexIndex < 0) {
|
vertexIndexIndex = ~vertexIndexIndex;
|
|
if (vertexIndexIndex > 0 && vertexIndexIndex < edgeVertices.length) {
|
// The corner falls between two vertices, so interpolate between them.
|
getInterpolatedVertexAtCorner(ellipsoid, sourceTile, terrainFillMesh.tile, sourceMesh, edgeVertices[vertexIndexIndex - 1], edgeVertices[vertexIndexIndex], u, v, compareU, vertex);
|
return true;
|
}
|
} else {
|
// Found a vertex that fits in the corner exactly.
|
getVertexFromTileAtCorner(sourceMesh, edgeVertices[vertexIndexIndex], u, v, vertex);
|
return true;
|
}
|
}
|
}
|
|
return false;
|
}
|
|
var cornerPositionsScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
|
|
function computeOccludeePoint(tileProvider, center, rectangle, minimumHeight, maximumHeight, result) {
|
var ellipsoidalOccluder = tileProvider.quadtree._occluders.ellipsoid;
|
var ellipsoid = ellipsoidalOccluder.ellipsoid;
|
|
var cornerPositions = cornerPositionsScratch;
|
Cartesian3.fromRadians(rectangle.west, rectangle.south, maximumHeight, ellipsoid, cornerPositions[0]);
|
Cartesian3.fromRadians(rectangle.east, rectangle.south, maximumHeight, ellipsoid, cornerPositions[1]);
|
Cartesian3.fromRadians(rectangle.west, rectangle.north, maximumHeight, ellipsoid, cornerPositions[2]);
|
Cartesian3.fromRadians(rectangle.east, rectangle.north, maximumHeight, ellipsoid, cornerPositions[3]);
|
|
return ellipsoidalOccluder.computeHorizonCullingPointPossiblyUnderEllipsoid(center, cornerPositions, minimumHeight, result);
|
}
|
export default TerrainFillMesh;
|