cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
982 lines (845 loc) • 82.1 kB
JavaScript
/* This file is automatically rebuilt by the Cesium build process. */
define(['./when-e6e3e713', './Check-1df6b9a0', './Math-c5f6c994', './Cartesian2-1d7364fa', './Transforms-943e8463', './RuntimeError-717c34db', './WebGLConstants-7f7d68ac', './ComponentDatatype-2b8834a4', './GeometryAttribute-3a303898', './EncodedCartesian3-d723731d', './IntersectionTests-c05f88ce', './Plane-2e419ea5', './WebMercatorProjection-2eb538cc', './arrayRemoveDuplicates-11ba5123', './ArcType-4e1c0bc3', './EllipsoidRhumbLine-0dab698e', './EllipsoidGeodesic-8b33d834'], function (when, Check, _Math, Cartesian2, Transforms, RuntimeError, WebGLConstants, ComponentDatatype, GeometryAttribute, EncodedCartesian3, IntersectionTests, Plane, WebMercatorProjection, arrayRemoveDuplicates, ArcType, EllipsoidRhumbLine, EllipsoidGeodesic) { 'use strict';
/**
* A tiling scheme for geometry referenced to a simple {@link GeographicProjection} where
* longitude and latitude are directly mapped to X and Y. This projection is commonly
* known as geographic, equirectangular, equidistant cylindrical, or plate carrée.
*
* @alias GeographicTilingScheme
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
* the WGS84 ellipsoid.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the tiling scheme.
* @param {Number} [options.numberOfLevelZeroTilesX=2] The number of tiles in the X direction at level zero of
* the tile tree.
* @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
* the tile tree.
*/
function GeographicTilingScheme(options) {
options = when.defaultValue(options, when.defaultValue.EMPTY_OBJECT);
this._ellipsoid = when.defaultValue(options.ellipsoid, Cartesian2.Ellipsoid.WGS84);
this._rectangle = when.defaultValue(options.rectangle, Cartesian2.Rectangle.MAX_VALUE);
this._projection = new Transforms.GeographicProjection(this._ellipsoid);
this._numberOfLevelZeroTilesX = when.defaultValue(options.numberOfLevelZeroTilesX, 2);
this._numberOfLevelZeroTilesY = when.defaultValue(options.numberOfLevelZeroTilesY, 1);
}
Object.defineProperties(GeographicTilingScheme.prototype, {
/**
* Gets the ellipsoid that is tiled by this tiling scheme.
* @memberof GeographicTilingScheme.prototype
* @type {Ellipsoid}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},
/**
* Gets the rectangle, in radians, covered by this tiling scheme.
* @memberof GeographicTilingScheme.prototype
* @type {Rectangle}
*/
rectangle : {
get : function() {
return this._rectangle;
}
},
/**
* Gets the map projection used by this tiling scheme.
* @memberof GeographicTilingScheme.prototype
* @type {MapProjection}
*/
projection : {
get : function() {
return this._projection;
}
}
});
/**
* Gets the total number of tiles in the X direction at a specified level-of-detail.
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the X direction at the given level.
*/
GeographicTilingScheme.prototype.getNumberOfXTilesAtLevel = function(level) {
return this._numberOfLevelZeroTilesX << level;
};
/**
* Gets the total number of tiles in the Y direction at a specified level-of-detail.
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the Y direction at the given level.
*/
GeographicTilingScheme.prototype.getNumberOfYTilesAtLevel = function(level) {
return this._numberOfLevelZeroTilesY << level;
};
/**
* Transforms a rectangle specified in geodetic radians to the native coordinate system
* of this tiling scheme.
*
* @param {Rectangle} rectangle The rectangle to transform.
* @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
* is undefined.
*/
GeographicTilingScheme.prototype.rectangleToNativeRectangle = function(rectangle, result) {
//>>includeStart('debug', pragmas.debug);
Check.Check.defined('rectangle', rectangle);
//>>includeEnd('debug');
var west = _Math.CesiumMath.toDegrees(rectangle.west);
var south = _Math.CesiumMath.toDegrees(rectangle.south);
var east = _Math.CesiumMath.toDegrees(rectangle.east);
var north = _Math.CesiumMath.toDegrees(rectangle.north);
if (!when.defined(result)) {
return new Cartesian2.Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
* of the tiling scheme.
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
GeographicTilingScheme.prototype.tileXYToNativeRectangle = function(x, y, level, result) {
var rectangleRadians = this.tileXYToRectangle(x, y, level, result);
rectangleRadians.west = _Math.CesiumMath.toDegrees(rectangleRadians.west);
rectangleRadians.south = _Math.CesiumMath.toDegrees(rectangleRadians.south);
rectangleRadians.east = _Math.CesiumMath.toDegrees(rectangleRadians.east);
rectangleRadians.north = _Math.CesiumMath.toDegrees(rectangleRadians.north);
return rectangleRadians;
};
/**
* Converts tile x, y coordinates and level to a cartographic rectangle in radians.
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
GeographicTilingScheme.prototype.tileXYToRectangle = function(x, y, level, result) {
var rectangle = this._rectangle;
var xTiles = this.getNumberOfXTilesAtLevel(level);
var yTiles = this.getNumberOfYTilesAtLevel(level);
var xTileWidth = rectangle.width / xTiles;
var west = x * xTileWidth + rectangle.west;
var east = (x + 1) * xTileWidth + rectangle.west;
var yTileHeight = rectangle.height / yTiles;
var north = rectangle.north - y * yTileHeight;
var south = rectangle.north - (y + 1) * yTileHeight;
if (!when.defined(result)) {
result = new Cartesian2.Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Calculates the tile x, y coordinates of the tile containing
* a given cartographic position.
*
* @param {Cartographic} position The position.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
* if 'result' is undefined.
*/
GeographicTilingScheme.prototype.positionToTileXY = function(position, level, result) {
var rectangle = this._rectangle;
if (!Cartesian2.Rectangle.contains(rectangle, position)) {
// outside the bounds of the tiling scheme
return undefined;
}
var xTiles = this.getNumberOfXTilesAtLevel(level);
var yTiles = this.getNumberOfYTilesAtLevel(level);
var xTileWidth = rectangle.width / xTiles;
var yTileHeight = rectangle.height / yTiles;
var longitude = position.longitude;
if (rectangle.east < rectangle.west) {
longitude += _Math.CesiumMath.TWO_PI;
}
var xTileCoordinate = (longitude - rectangle.west) / xTileWidth | 0;
if (xTileCoordinate >= xTiles) {
xTileCoordinate = xTiles - 1;
}
var yTileCoordinate = (rectangle.north - position.latitude) / yTileHeight | 0;
if (yTileCoordinate >= yTiles) {
yTileCoordinate = yTiles - 1;
}
if (!when.defined(result)) {
return new Cartesian2.Cartesian2(xTileCoordinate, yTileCoordinate);
}
result.x = xTileCoordinate;
result.y = yTileCoordinate;
return result;
};
var scratchDiagonalCartesianNE = new Cartesian2.Cartesian3();
var scratchDiagonalCartesianSW = new Cartesian2.Cartesian3();
var scratchDiagonalCartographic = new Cartesian2.Cartographic();
var scratchCenterCartesian = new Cartesian2.Cartesian3();
var scratchSurfaceCartesian = new Cartesian2.Cartesian3();
var scratchBoundingSphere = new Transforms.BoundingSphere();
var tilingScheme = new GeographicTilingScheme();
var scratchCorners = [new Cartesian2.Cartographic(), new Cartesian2.Cartographic(), new Cartesian2.Cartographic(), new Cartesian2.Cartographic()];
var scratchTileXY = new Cartesian2.Cartesian2();
/**
* A collection of functions for approximating terrain height
* @private
*/
var ApproximateTerrainHeights = {};
/**
* Initializes the minimum and maximum terrain heights
* @return {Promise}
*/
ApproximateTerrainHeights.initialize = function() {
var initPromise = ApproximateTerrainHeights._initPromise;
if (when.defined(initPromise)) {
return initPromise;
}
initPromise = Transforms.Resource.fetchJson(Transforms.buildModuleUrl('Assets/approximateTerrainHeights.json'))
.then(function(json) {
ApproximateTerrainHeights._terrainHeights = json;
});
ApproximateTerrainHeights._initPromise = initPromise;
return initPromise;
};
/**
* Computes the minimum and maximum terrain heights for a given rectangle
* @param {Rectangle} rectangle The bounding rectangle
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
* @return {{minimumTerrainHeight: Number, maximumTerrainHeight: Number}}
*/
ApproximateTerrainHeights.getMinimumMaximumHeights = function(rectangle, ellipsoid) {
//>>includeStart('debug', pragmas.debug);
Check.Check.defined('rectangle', rectangle);
if (!when.defined(ApproximateTerrainHeights._terrainHeights)) {
throw new Check.DeveloperError('You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function');
}
//>>includeEnd('debug');
ellipsoid = when.defaultValue(ellipsoid, Cartesian2.Ellipsoid.WGS84);
var xyLevel = getTileXYLevel(rectangle);
// Get the terrain min/max for that tile
var minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
if (when.defined(xyLevel)) {
var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
var heights = ApproximateTerrainHeights._terrainHeights[key];
if (when.defined(heights)) {
minTerrainHeight = heights[0];
maxTerrainHeight = heights[1];
}
// Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
ellipsoid.cartographicToCartesian(Cartesian2.Rectangle.northeast(rectangle, scratchDiagonalCartographic),
scratchDiagonalCartesianNE);
ellipsoid.cartographicToCartesian(Cartesian2.Rectangle.southwest(rectangle, scratchDiagonalCartographic),
scratchDiagonalCartesianSW);
Cartesian2.Cartesian3.midpoint(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian);
var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian);
if (when.defined(surfacePosition)) {
var distance = Cartesian2.Cartesian3.distance(scratchCenterCartesian, surfacePosition);
minTerrainHeight = Math.min(minTerrainHeight, -distance);
} else {
minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
}
}
minTerrainHeight = Math.max(ApproximateTerrainHeights._defaultMinTerrainHeight, minTerrainHeight);
return {
minimumTerrainHeight: minTerrainHeight,
maximumTerrainHeight: maxTerrainHeight
};
};
/**
* Computes the bounding sphere based on the tile heights in the rectangle
* @param {Rectangle} rectangle The bounding rectangle
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
* @return {BoundingSphere} The result bounding sphere
*/
ApproximateTerrainHeights.getBoundingSphere = function(rectangle, ellipsoid) {
//>>includeStart('debug', pragmas.debug);
Check.Check.defined('rectangle', rectangle);
if (!when.defined(ApproximateTerrainHeights._terrainHeights)) {
throw new Check.DeveloperError('You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function');
}
//>>includeEnd('debug');
ellipsoid = when.defaultValue(ellipsoid, Cartesian2.Ellipsoid.WGS84);
var xyLevel = getTileXYLevel(rectangle);
// Get the terrain max for that tile
var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
if (when.defined(xyLevel)) {
var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
var heights = ApproximateTerrainHeights._terrainHeights[key];
if (when.defined(heights)) {
maxTerrainHeight = heights[1];
}
}
var result = Transforms.BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
Transforms.BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere);
return Transforms.BoundingSphere.union(result, scratchBoundingSphere, result);
};
function getTileXYLevel(rectangle) {
Cartesian2.Cartographic.fromRadians(rectangle.east, rectangle.north, 0.0, scratchCorners[0]);
Cartesian2.Cartographic.fromRadians(rectangle.west, rectangle.north, 0.0, scratchCorners[1]);
Cartesian2.Cartographic.fromRadians(rectangle.east, rectangle.south, 0.0, scratchCorners[2]);
Cartesian2.Cartographic.fromRadians(rectangle.west, rectangle.south, 0.0, scratchCorners[3]);
// Determine which tile the bounding rectangle is in
var lastLevelX = 0, lastLevelY = 0;
var currentX = 0, currentY = 0;
var maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel;
var i;
for(i = 0; i <= maxLevel; ++i) {
var failed = false;
for(var j = 0; j < 4; ++j) {
var corner = scratchCorners[j];
tilingScheme.positionToTileXY(corner, i, scratchTileXY);
if (j === 0) {
currentX = scratchTileXY.x;
currentY = scratchTileXY.y;
} else if(currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
failed = true;
break;
}
}
if (failed) {
break;
}
lastLevelX = currentX;
lastLevelY = currentY;
}
if (i === 0) {
return undefined;
}
return {
x : lastLevelX,
y : lastLevelY,
level : (i > maxLevel) ? maxLevel : (i - 1)
};
}
ApproximateTerrainHeights._terrainHeightsMaxLevel = 6;
ApproximateTerrainHeights._defaultMaxTerrainHeight = 9000.0;
ApproximateTerrainHeights._defaultMinTerrainHeight = -100000.0;
ApproximateTerrainHeights._terrainHeights = undefined;
ApproximateTerrainHeights._initPromise = undefined;
Object.defineProperties(ApproximateTerrainHeights, {
/**
* Determines if the terrain heights are initialized and ready to use. To initialize the terrain heights,
* call {@link ApproximateTerrainHeights#initialize} and wait for the returned promise to resolve.
* @type {Boolean}
* @readonly
* @memberof ApproximateTerrainHeights
*/
initialized: {
get: function() {
return when.defined(ApproximateTerrainHeights._terrainHeights);
}
}
});
var PROJECTIONS = [Transforms.GeographicProjection, WebMercatorProjection.WebMercatorProjection];
var PROJECTION_COUNT = PROJECTIONS.length;
var MITER_BREAK_SMALL = Math.cos(_Math.CesiumMath.toRadians(30.0));
var MITER_BREAK_LARGE = Math.cos(_Math.CesiumMath.toRadians(150.0));
// Initial heights for constructing the wall.
// Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps
// prevent precision problems with planes in the shader.
// Putting the start point of a plane at ApproximateTerrainHeights._defaultMinTerrainHeight,
// which is a highly conservative bound, usually puts the plane origin several thousands
// of meters away from the actual terrain, causing floating point problems when checking
// fragments on terrain against the plane.
// Ellipsoid height is generally much closer.
// The initial max height is arbitrary.
// Both heights are corrected using ApproximateTerrainHeights for computing the actual volume geometry.
var WALL_INITIAL_MIN_HEIGHT = 0.0;
var WALL_INITIAL_MAX_HEIGHT = 1000.0;
/**
* A description of a polyline on terrain or 3D Tiles. Only to be used with {@link GroundPolylinePrimitive}.
*
* @alias GroundPolylineGeometry
* @constructor
*
* @param {Object} options Options with the following properties:
* @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored.
* @param {Number} [options.width=1.0] The screen space width in pixels.
* @param {Number} [options.granularity=9999.0] The distance interval in meters used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation.
* @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
*
* @exception {DeveloperError} At least two positions are required.
*
* @see GroundPolylinePrimitive
*
* @example
* var positions = Cesium.Cartesian3.fromDegreesArray([
* -112.1340164450331, 36.05494287836128,
* -112.08821010582645, 36.097804071380715,
* -112.13296079730024, 36.168769146801104
* ]);
*
* var geometry = new Cesium.GroundPolylineGeometry({
* positions : positions
* });
*/
function GroundPolylineGeometry(options) {
options = when.defaultValue(options, when.defaultValue.EMPTY_OBJECT);
var positions = options.positions;
//>>includeStart('debug', pragmas.debug);
if ((!when.defined(positions)) || (positions.length < 2)) {
throw new Check.DeveloperError('At least two positions are required.');
}
if (when.defined(options.arcType) && options.arcType !== ArcType.ArcType.GEODESIC && options.arcType !== ArcType.ArcType.RHUMB) {
throw new Check.DeveloperError('Valid options for arcType are ArcType.GEODESIC and ArcType.RHUMB.');
}
//>>includeEnd('debug');
/**
* The screen space width in pixels.
* @type {Number}
*/
this.width = when.defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry.
this._positions = positions;
/**
* The distance interval used for interpolating options.points. Zero indicates no interpolation.
* Default of 9999.0 allows centimeter accuracy with 32 bit floating point.
* @type {Boolean}
* @default 9999.0
*/
this.granularity = when.defaultValue(options.granularity, 9999.0);
/**
* Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
* If the geometry has two positions this parameter will be ignored.
* @type {Boolean}
* @default false
*/
this.loop = when.defaultValue(options.loop, false);
/**
* The type of path the polyline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
* @type {ArcType}
* @default ArcType.GEODESIC
*/
this.arcType = when.defaultValue(options.arcType, ArcType.ArcType.GEODESIC);
this._ellipsoid = Cartesian2.Ellipsoid.WGS84;
// MapProjections can't be packed, so store the index to a known MapProjection.
this._projectionIndex = 0;
this._workerName = 'createGroundPolylineGeometry';
// Used by GroundPolylinePrimitive to signal worker that scenemode is 3D only.
this._scene3DOnly = false;
}
Object.defineProperties(GroundPolylineGeometry.prototype, {
/**
* The number of elements used to pack the object into an array.
* @memberof GroundPolylineGeometry.prototype
* @type {Number}
* @readonly
* @private
*/
packedLength: {
get: function() {
return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + 1.0 + Cartesian2.Ellipsoid.packedLength + 1.0 + 1.0;
}
}
});
/**
* Set the GroundPolylineGeometry's projection and ellipsoid.
* Used by GroundPolylinePrimitive to signal scene information to the geometry for generating 2D attributes.
*
* @param {GroundPolylineGeometry} groundPolylineGeometry GroundPolylinGeometry describing a polyline on terrain or 3D Tiles.
* @param {Projection} mapProjection A MapProjection used for projecting cartographic coordinates to 2D.
* @private
*/
GroundPolylineGeometry.setProjectionAndEllipsoid = function(groundPolylineGeometry, mapProjection) {
var projectionIndex = 0;
for (var i = 0; i < PROJECTION_COUNT; i++) {
if (mapProjection instanceof PROJECTIONS[i]) {
projectionIndex = i;
break;
}
}
groundPolylineGeometry._projectionIndex = projectionIndex;
groundPolylineGeometry._ellipsoid = mapProjection.ellipsoid;
};
var cart3Scratch1 = new Cartesian2.Cartesian3();
var cart3Scratch2 = new Cartesian2.Cartesian3();
var cart3Scratch3 = new Cartesian2.Cartesian3();
function computeRightNormal(start, end, maxHeight, ellipsoid, result) {
var startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1);
var startTop = getPosition(ellipsoid, start, maxHeight, cart3Scratch2);
var endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3);
var up = direction(startTop, startBottom, cart3Scratch2);
var forward = direction(endBottom, startBottom, cart3Scratch3);
Cartesian2.Cartesian3.cross(forward, up, result);
return Cartesian2.Cartesian3.normalize(result, result);
}
var interpolatedCartographicScratch = new Cartesian2.Cartographic();
var interpolatedBottomScratch = new Cartesian2.Cartesian3();
var interpolatedTopScratch = new Cartesian2.Cartesian3();
var interpolatedNormalScratch = new Cartesian2.Cartesian3();
function interpolateSegment(start, end, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray) {
if (granularity === 0.0) {
return;
}
var ellipsoidLine;
if (arcType === ArcType.ArcType.GEODESIC) {
ellipsoidLine = new EllipsoidGeodesic.EllipsoidGeodesic(start, end, ellipsoid);
} else if (arcType === ArcType.ArcType.RHUMB) {
ellipsoidLine = new EllipsoidRhumbLine.EllipsoidRhumbLine(start, end, ellipsoid);
}
var surfaceDistance = ellipsoidLine.surfaceDistance;
if (surfaceDistance < granularity) {
return;
}
// Compute rightwards normal applicable at all interpolated points
var interpolatedNormal = computeRightNormal(start, end, maxHeight, ellipsoid, interpolatedNormalScratch);
var segments = Math.ceil(surfaceDistance / granularity);
var interpointDistance = surfaceDistance / segments;
var distanceFromStart = interpointDistance;
var pointsToAdd = segments - 1;
var packIndex = normalsArray.length;
for (var i = 0; i < pointsToAdd; i++) {
var interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch);
var interpolatedBottom = getPosition(ellipsoid, interpolatedCartographic, minHeight, interpolatedBottomScratch);
var interpolatedTop = getPosition(ellipsoid, interpolatedCartographic, maxHeight, interpolatedTopScratch);
Cartesian2.Cartesian3.pack(interpolatedNormal, normalsArray, packIndex);
Cartesian2.Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex);
Cartesian2.Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex);
cartographicsArray.push(interpolatedCartographic.latitude);
cartographicsArray.push(interpolatedCartographic.longitude);
packIndex += 3;
distanceFromStart += interpointDistance;
}
}
var heightlessCartographicScratch = new Cartesian2.Cartographic();
function getPosition(ellipsoid, cartographic, height, result) {
Cartesian2.Cartographic.clone(cartographic, heightlessCartographicScratch);
heightlessCartographicScratch.height = height;
return Cartesian2.Cartographic.toCartesian(heightlessCartographicScratch, ellipsoid, result);
}
/**
* Stores the provided instance into the provided array.
*
* @param {PolygonGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
GroundPolylineGeometry.pack = function(value, array, startingIndex) {
//>>includeStart('debug', pragmas.debug);
Check.Check.typeOf.object('value', value);
Check.Check.defined('array', array);
//>>includeEnd('debug');
var index = when.defaultValue(startingIndex, 0);
var positions = value._positions;
var positionsLength = positions.length;
array[index++] = positionsLength;
for (var i = 0; i < positionsLength; ++i) {
var cartesian = positions[i];
Cartesian2.Cartesian3.pack(cartesian, array, index);
index += 3;
}
array[index++] = value.granularity;
array[index++] = value.loop ? 1.0 : 0.0;
array[index++] = value.arcType;
Cartesian2.Ellipsoid.pack(value._ellipsoid, array, index);
index += Cartesian2.Ellipsoid.packedLength;
array[index++] = value._projectionIndex;
array[index++] = value._scene3DOnly ? 1.0 : 0.0;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {PolygonGeometry} [result] The object into which to store the result.
*/
GroundPolylineGeometry.unpack = function(array, startingIndex, result) {
//>>includeStart('debug', pragmas.debug);
Check.Check.defined('array', array);
//>>includeEnd('debug');
var index = when.defaultValue(startingIndex, 0);
var positionsLength = array[index++];
var positions = new Array(positionsLength);
for (var i = 0; i < positionsLength; i++) {
positions[i] = Cartesian2.Cartesian3.unpack(array, index);
index += 3;
}
var granularity = array[index++];
var loop = array[index++] === 1.0;
var arcType = array[index++];
var ellipsoid = Cartesian2.Ellipsoid.unpack(array, index);
index += Cartesian2.Ellipsoid.packedLength;
var projectionIndex = array[index++];
var scene3DOnly = (array[index++] === 1.0);
if (!when.defined(result)) {
result = new GroundPolylineGeometry({
positions : positions
});
}
result._positions = positions;
result.granularity = granularity;
result.loop = loop;
result.arcType = arcType;
result._ellipsoid = ellipsoid;
result._projectionIndex = projectionIndex;
result._scene3DOnly = scene3DOnly;
return result;
};
function direction(target, origin, result) {
Cartesian2.Cartesian3.subtract(target, origin, result);
Cartesian2.Cartesian3.normalize(result, result);
return result;
}
function tangentDirection(target, origin, up, result) {
result = direction(target, origin, result);
// orthogonalize
result = Cartesian2.Cartesian3.cross(result, up, result);
result = Cartesian2.Cartesian3.normalize(result, result);
result = Cartesian2.Cartesian3.cross(up, result, result);
return result;
}
var toPreviousScratch = new Cartesian2.Cartesian3();
var toNextScratch = new Cartesian2.Cartesian3();
var forwardScratch = new Cartesian2.Cartesian3();
var vertexUpScratch = new Cartesian2.Cartesian3();
var cosine90 = 0.0;
var cosine180 = -1.0;
function computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, result) {
var up = direction(vertexTop, vertexBottom, vertexUpScratch);
// Compute vectors pointing towards neighboring points but tangent to this point on the ellipsoid
var toPrevious = tangentDirection(previousBottom, vertexBottom, up, toPreviousScratch);
var toNext = tangentDirection(nextBottom, vertexBottom, up, toNextScratch);
// Check if tangents are almost opposite - if so, no need to miter.
if (_Math.CesiumMath.equalsEpsilon(Cartesian2.Cartesian3.dot(toPrevious, toNext), cosine180, _Math.CesiumMath.EPSILON5)) {
result = Cartesian2.Cartesian3.cross(up, toPrevious, result);
result = Cartesian2.Cartesian3.normalize(result, result);
return result;
}
// Average directions to previous and to next in the plane of Up
result = Cartesian2.Cartesian3.add(toNext, toPrevious, result);
result = Cartesian2.Cartesian3.normalize(result, result);
// Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards")
var forward = Cartesian2.Cartesian3.cross(up, result, forwardScratch);
if (Cartesian2.Cartesian3.dot(toNext, forward) < cosine90) {
result = Cartesian2.Cartesian3.negate(result, result);
}
return result;
}
var XZ_PLANE = Plane.Plane.fromPointNormal(Cartesian2.Cartesian3.ZERO, Cartesian2.Cartesian3.UNIT_Y);
var previousBottomScratch = new Cartesian2.Cartesian3();
var vertexBottomScratch = new Cartesian2.Cartesian3();
var vertexTopScratch = new Cartesian2.Cartesian3();
var nextBottomScratch = new Cartesian2.Cartesian3();
var vertexNormalScratch = new Cartesian2.Cartesian3();
var intersectionScratch = new Cartesian2.Cartesian3();
var cartographicScratch0 = new Cartesian2.Cartographic();
var cartographicScratch1 = new Cartesian2.Cartographic();
var cartographicIntersectionScratch = new Cartesian2.Cartographic();
/**
* Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere.
* Vertices are "fat," packing all the data needed in each volume to describe a line on terrain or 3D Tiles.
* Should not be called independent of {@link GroundPolylinePrimitive}.
*
* @param {GroundPolylineGeometry} groundPolylineGeometry
* @private
*/
GroundPolylineGeometry.createGeometry = function(groundPolylineGeometry) {
var compute2dAttributes = !groundPolylineGeometry._scene3DOnly;
var loop = groundPolylineGeometry.loop;
var ellipsoid = groundPolylineGeometry._ellipsoid;
var granularity = groundPolylineGeometry.granularity;
var arcType = groundPolylineGeometry.arcType;
var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](ellipsoid);
var minHeight = WALL_INITIAL_MIN_HEIGHT;
var maxHeight = WALL_INITIAL_MAX_HEIGHT;
var index;
var i;
var positions = groundPolylineGeometry._positions;
var positionsLength = positions.length;
if (positionsLength === 2) {
loop = false;
}
// Split positions across the IDL and the Prime Meridian as well.
// Split across prime meridian because very large geometries crossing the Prime Meridian but not the IDL
// may get split by the plane of IDL + Prime Meridian.
var p0;
var p1;
var c0;
var c1;
var rhumbLine = new EllipsoidRhumbLine.EllipsoidRhumbLine(undefined, undefined, ellipsoid);
var intersection;
var intersectionCartographic;
var intersectionLongitude;
var splitPositions = [positions[0]];
for (i = 0; i < positionsLength - 1; i++) {
p0 = positions[i];
p1 = positions[i + 1];
intersection = IntersectionTests.IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch);
if (when.defined(intersection) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)) {
if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
} else if (groundPolylineGeometry.arcType === ArcType.ArcType.RHUMB) {
intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude;
c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
rhumbLine.setEndPoints(c0, c1);
intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch);
intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch);
if (when.defined(intersection) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)) {
splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
}
}
}
splitPositions.push(p1);
}
if (loop) {
p0 = positions[positionsLength - 1];
p1 = positions[0];
intersection = IntersectionTests.IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch);
if (when.defined(intersection) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)) {
if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
} else if (groundPolylineGeometry.arcType === ArcType.ArcType.RHUMB) {
intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude;
c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
rhumbLine.setEndPoints(c0, c1);
intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch);
intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch);
if (when.defined(intersection) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
!Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)) {
splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
}
}
}
}
var cartographicsLength = splitPositions.length;
var cartographics = new Array(cartographicsLength);
for (i = 0; i < cartographicsLength; i++) {
var cartographic = Cartesian2.Cartographic.fromCartesian(splitPositions[i], ellipsoid);
cartographic.height = 0.0;
cartographics[i] = cartographic;
}
cartographics = arrayRemoveDuplicates.arrayRemoveDuplicates(cartographics, Cartesian2.Cartographic.equalsEpsilon);
cartographicsLength = cartographics.length;
if (cartographicsLength < 2) {
return undefined;
}
/**** Build heap-side arrays for positions, interpolated cartographics, and normals from which to compute vertices ****/
// We build a "wall" and then decompose it into separately connected component "volumes" because we need a lot
// of information about the wall. Also, this simplifies interpolation.
// Convention: "next" and "end" are locally forward to each segment of the wall,
// and we are computing normals pointing towards the local right side of the vertices in each segment.
var cartographicsArray = [];
var normalsArray = [];
var bottomPositionsArray = [];
var topPositionsArray = [];
var previousBottom = previousBottomScratch;
var vertexBottom = vertexBottomScratch;
var vertexTop = vertexTopScratch;
var nextBottom = nextBottomScratch;
var vertexNormal = vertexNormalScratch;
// First point - either loop or attach a "perpendicular" normal
var startCartographic = cartographics[0];
var nextCartographic = cartographics[1];
var prestartCartographic = cartographics[cartographicsLength - 1];
previousBottom = getPosition(ellipsoid, prestartCartographic, minHeight, previousBottom);
nextBottom = getPosition(ellipsoid, nextCartographic, minHeight, nextBottom);
vertexBottom = getPosition(ellipsoid, startCartographic, minHeight, vertexBottom);
vertexTop = getPosition(ellipsoid, startCartographic, maxHeight, vertexTop);
if (loop) {
vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal);
} else {
vertexNormal = computeRightNormal(startCartographic, nextCartographic, maxHeight, ellipsoid, vertexNormal);
}
Cartesian2.Cartesian3.pack(vertexNormal, normalsArray, 0);
Cartesian2.Cartesian3.pack(vertexBottom, bottomPositionsArray, 0);
Cartesian2.Cartesian3.pack(vertexTop, topPositionsArray, 0);
cartographicsArray.push(startCartographic.latitude);
cartographicsArray.push(startCartographic.longitude);
interpolateSegment(startCartographic, nextCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
// All inbetween points
for (i = 1; i < cartographicsLength - 1; ++i) {
previousBottom = Cartesian2.Cartesian3.clone(vertexBottom, previousBottom);
vertexBottom = Cartesian2.Cartesian3.clone(nextBottom, vertexBottom);
var vertexCartographic = cartographics[i];
getPosition(ellipsoid, vertexCartographic, maxHeight, vertexTop);
getPosition(ellipsoid, cartographics[i + 1], minHeight, nextBottom);
computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal);
index = normalsArray.length;
Cartesian2.Cartesian3.pack(vertexNormal, normalsArray, index);
Cartesian2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
Cartesian2.Cartesian3.pack(vertexTop, topPositionsArray, index);
cartographicsArray.push(vertexCartographic.latitude);
cartographicsArray.push(vertexCartographic.longitude);
interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
}
// Last point - either loop or attach a normal "perpendicular" to the wall.
var endCartographic = cartographics[cartographicsLength - 1];
var preEndCartographic = cartographics[cartographicsLength - 2];
vertexBottom = getPosition(ellipsoid, endCartographic, minHeight, vertexBottom);
vertexTop = getPosition(ellipsoid, endCartographic, maxHeight, vertexTop);
if (loop) {
var postEndCartographic = cartographics[0];
previousBottom = getPosition(ellipsoid, preEndCartographic, minHeight, previousBottom);
nextBottom = getPosition(ellipsoid, postEndCartographic, minHeight, nextBottom);
vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal);
} else {
vertexNormal = computeRightNormal(preEndCartographic, endCartographic, maxHeight, ellipsoid, vertexNormal);
}
index = normalsArray.length;
Cartesian2.Cartesian3.pack(vertexNormal, normalsArray, index);
Cartesian2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
Cartesian2.Cartesian3.pack(vertexTop, topPositionsArray, index);
cartographicsArray.push(endCartographic.latitude);
cartographicsArray.push(endCartographic.longitude);
if (loop) {
interpolateSegment(endCartographic, startCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
index = normalsArray.length;
for (i = 0; i < 3; ++i) {
normalsArray[index + i] = normalsArray[i];
bottomPositionsArray[index + i] = bottomPositionsArray[i];
topPositionsArray[index + i] = topPositionsArray[i];
}
cartographicsArray.push(startCartographic.latitude);
cartographicsArray.push(startCartographic.longitude);
}
return generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, compute2dAttributes);
};
// If the end normal angle is too steep compared to the direction of the line segment,
// "break" the miter by rotating the normal 90 degrees around the "up" direction at the point
// For ultra precision we would want to project into a plane, but in practice this is sufficient.
var lineDirectionScratch = new Cartesian2.Cartesian3();
var matrix3Scratch = new Transforms.Matrix3();
var quaternionScratch = new Transforms.Quaternion();
function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) {
var lineDirection = direction(endBottom, startBottom, lineDirectionScratch);
var dot = Cartesian2.Cartesian3.dot(lineDirection, endGeometryNormal);
if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) {
var vertexUp = direction(endTop, endBottom, vertexUpScratch);
var angle = dot < MITER_BREAK_LARGE ? _Math.CesiumMath.PI_OVER_TWO : -_Math.CesiumMath.PI_OVER_TWO;
var quaternion = Transforms.Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch);
var rotationMatrix = Transforms.Matrix3.fromQuaternion(quaternion, matrix3Scratch);
Transforms.Matrix3.multiplyByVector(rotationMatrix, endGeometr