@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
362 lines (319 loc) • 12.9 kB
JavaScript
import SceneMode from "../Scene/SceneMode.js";
import Cartesian3 from "./Cartesian3.js";
import Cartographic from "./Cartographic.js";
import defined from "./defined.js";
import Ellipsoid from "./Ellipsoid.js";
import Matrix4 from "./Matrix4.js";
import OrientedBoundingBox from "./OrientedBoundingBox.js";
import TerrainPicker from "./TerrainPicker.js";
import Transforms from "./Transforms.js";
import VerticalExaggeration from "./VerticalExaggeration.js";
/**
* A mesh plus related metadata for a single tile of terrain. Instances of this type are
* usually created from raw {@link TerrainData}.
*
* @alias TerrainMesh
* @constructor
*
* @param {Cartesian3} center The center of the tile. Vertex positions are specified relative to this center.
* @param {Float32Array} vertices The vertex data, including positions, texture coordinates, and heights.
* The vertex data is in the order [X, Y, Z, H, U, V], where X, Y, and Z represent
* the Cartesian position of the vertex, H is the height above the ellipsoid, and
* U and V are the texture coordinates.
* @param {Uint8Array|Uint16Array|Uint32Array} indices The indices describing how the vertices are connected to form triangles.
* @param {number} indexCountWithoutSkirts The index count of the mesh not including skirts.
* @param {number} vertexCountWithoutSkirts The vertex count of the mesh not including skirts.
* @param {number} minimumHeight The lowest height in the tile, in meters above the ellipsoid.
* @param {number} maximumHeight The highest height in the tile, in meters above the ellipsoid.
* @param {Rectangle} rectangle The rectangle, in radians, covered by this tile.
* @param {BoundingSphere} boundingSphere3D A bounding sphere that completely contains the tile.
* @param {Cartesian3} occludeePointInScaledSpace The occludee point of the tile, represented in ellipsoid-
* scaled space, and used for horizon culling. If this point is below the horizon,
* the tile is considered to be entirely below the horizon.
* @param {number} [vertexStride=6] The number of components in each vertex.
* @param {OrientedBoundingBox} [orientedBoundingBox] A bounding box that completely contains the tile.
* @param {TerrainEncoding} encoding Information used to decode the mesh.
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} westIndicesSouthToNorth The indices of the vertices on the Western edge of the tile, ordered from South to North (clockwise).
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} southIndicesEastToWest The indices of the vertices on the Southern edge of the tile, ordered from East to West (clockwise).
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} eastIndicesNorthToSouth The indices of the vertices on the Eastern edge of the tile, ordered from North to South (clockwise).
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} northIndicesWestToEast The indices of the vertices on the Northern edge of the tile, ordered from West to East (clockwise).
*
* @private
*/
function TerrainMesh(
center,
vertices,
indices,
indexCountWithoutSkirts,
vertexCountWithoutSkirts,
minimumHeight,
maximumHeight,
rectangle,
boundingSphere3D,
occludeePointInScaledSpace,
vertexStride,
orientedBoundingBox,
encoding,
westIndicesSouthToNorth,
southIndicesEastToWest,
eastIndicesNorthToSouth,
northIndicesWestToEast,
) {
/**
* The center of the tile. Vertex positions are specified relative to this center.
* @type {Cartesian3}
*/
this.center = center;
/**
* The vertex data, including positions, texture coordinates, and heights.
* The vertex data is in the order [X, Y, Z, H, U, V], where X, Y, and Z represent
* the Cartesian position of the vertex, H is the height above the ellipsoid, and
* U and V are the texture coordinates. The vertex data may have additional attributes after those
* mentioned above when the {@link TerrainMesh#stride} is greater than 6.
* @type {Float32Array}
*/
this.vertices = vertices;
/**
* The number of components in each vertex. Typically this is 6 for the 6 components
* [X, Y, Z, H, U, V], but if each vertex has additional data (such as a vertex normal), this value
* may be higher.
* @type {number}
*/
this.stride = vertexStride ?? 6;
/**
* The indices describing how the vertices are connected to form triangles.
* @type {Uint8Array|Uint16Array|Uint32Array}
*/
this.indices = indices;
/**
* The index count of the mesh not including skirts.
* @type {number}
*/
this.indexCountWithoutSkirts = indexCountWithoutSkirts;
/**
* The vertex count of the mesh not including skirts.
* @type {number}
*/
this.vertexCountWithoutSkirts = vertexCountWithoutSkirts;
/**
* The lowest height in the tile, in meters above the ellipsoid.
* @type {number}
*/
this.minimumHeight = minimumHeight;
/**
* The highest height in the tile, in meters above the ellipsoid.
* @type {number}
*/
this.maximumHeight = maximumHeight;
/**
* The rectangle, in radians, covered by this tile.
* @type {Rectangle}
*/
this.rectangle = rectangle;
/**
* A bounding sphere that completely contains the tile.
* @type {BoundingSphere}
*/
this.boundingSphere3D = boundingSphere3D;
/**
* The occludee point of the tile, represented in ellipsoid-
* scaled space, and used for horizon culling. If this point is below the horizon,
* the tile is considered to be entirely below the horizon.
* @type {Cartesian3}
*/
this.occludeePointInScaledSpace = occludeePointInScaledSpace;
/**
* A bounding box that completely contains the tile.
* @type {OrientedBoundingBox}
*/
this.orientedBoundingBox = orientedBoundingBox;
/**
* Information for decoding the mesh vertices.
* @type {TerrainEncoding}
*/
this.encoding = encoding;
/**
* The indices of the vertices on the Western edge of the tile, ordered from South to North (clockwise).
* @type {number[]|Uint8Array|Uint16Array|Uint32Array}
*/
this.westIndicesSouthToNorth = westIndicesSouthToNorth;
/**
* The indices of the vertices on the Southern edge of the tile, ordered from East to West (clockwise).
* @type {number[]|Uint8Array|Uint16Array|Uint32Array}
*/
this.southIndicesEastToWest = southIndicesEastToWest;
/**
* The indices of the vertices on the Eastern edge of the tile, ordered from North to South (clockwise).
* @type {number[]|Uint8Array|Uint16Array|Uint32Array}
*/
this.eastIndicesNorthToSouth = eastIndicesNorthToSouth;
/**
* The indices of the vertices on the Northern edge of the tile, ordered from West to East (clockwise).
* @type {number[]|Uint8Array|Uint16Array|Uint32Array}
*/
this.northIndicesWestToEast = northIndicesWestToEast;
/**
* The transform from model to world coordinates based on the terrain mesh's oriented bounding box.
* In 3D mode, this is computed from the oriented bounding box. In 2D and Columbus View modes,
* this is computed from the tile's rectangle's projected coordinates.
* @type {Matrix4}
*/
this._transform = new Matrix4();
/**
* True if the transform needs to be recomputed (due to changes in exaggeration or scene mode).
* @type {boolean}
*/
this._recomputeTransform = true;
/**
* The terrain picker for this mesh, used for ray intersection tests.
* @type {TerrainPicker}
*/
this._terrainPicker = new TerrainPicker(vertices, indices, encoding);
}
/**
* Get the terrain tile's model-to-world transform matrix for the given scene mode and projection.
* @param {SceneMode} mode The scene mode (3D, 2D, or Columbus View).
* @param {MapProjection} projection The map projection.
* @returns {Matrix4} The transform matrix.
* @private
*/
TerrainMesh.prototype.getTransform = function (mode, projection) {
if (!this._recomputeTransform) {
return this._transform;
}
this._recomputeTransform = false;
if (!defined(mode) || mode === SceneMode.SCENE3D) {
return computeTransform(this, this._transform);
}
return computeTransform2D(this, projection, this._transform);
};
function computeTransform(mesh, result) {
const exaggeration = mesh.encoding.exaggeration;
const exaggerationRelativeHeight = mesh.encoding.exaggerationRelativeHeight;
const exaggeratedMinHeight = VerticalExaggeration.getHeight(
mesh.minimumHeight,
exaggeration,
exaggerationRelativeHeight,
);
const exaggeratedMaxHeight = VerticalExaggeration.getHeight(
mesh.maximumHeight,
exaggeration,
exaggerationRelativeHeight,
);
const obb = OrientedBoundingBox.fromRectangle(
mesh.rectangle,
exaggeratedMinHeight,
exaggeratedMaxHeight,
Ellipsoid.default,
mesh.orientedBoundingBox,
);
return OrientedBoundingBox.computeTransformation(obb, result);
}
const scratchSWCartesian = new Cartesian3();
const scratchNECartesian = new Cartesian3();
const scratchSWCartographic = new Cartographic();
const scratchNECartographic = new Cartographic();
const scratchScale2D = new Cartesian3();
const scratchCenter2D = new Cartesian3();
/**
* Get the terrain tile's model-to-world transform matrix for 2D or Columbus View modes.
* Assumes tiles in 2D are axis-aligned and still rectangular. (This is true for Web Mercator and Geographic projections.)
* @param {TerrainMesh} mesh The terrain mesh.
* @param {MapProjection} projection The map projection.
* @param {Matrix4} result The object in which to store the result.
* @returns {Matrix4} The transform matrix.
* @private
*/
function computeTransform2D(mesh, projection, result) {
const exaggeration = mesh.encoding.exaggeration;
const exaggerationRelativeHeight = mesh.encoding.exaggerationRelativeHeight;
const exaggeratedMinHeight = VerticalExaggeration.getHeight(
mesh.minimumHeight,
exaggeration,
exaggerationRelativeHeight,
);
const exaggeratedMaxHeight = VerticalExaggeration.getHeight(
mesh.maximumHeight,
exaggeration,
exaggerationRelativeHeight,
);
const southwest = projection.project(
Cartographic.fromRadians(
mesh.rectangle.west,
mesh.rectangle.south,
0,
scratchSWCartographic,
),
scratchSWCartesian,
);
const northeast = projection.project(
Cartographic.fromRadians(
mesh.rectangle.east,
mesh.rectangle.north,
0,
scratchNECartographic,
),
scratchNECartesian,
);
const heightRange = exaggeratedMaxHeight - exaggeratedMinHeight;
const scale = Cartesian3.fromElements(
northeast.x - southwest.x,
northeast.y - southwest.y,
heightRange > 0 ? heightRange : 1.0, // Avoid zero scale
scratchScale2D,
);
const center = Cartesian3.fromElements(
southwest.x + scale.x * 0.5,
southwest.y + scale.y * 0.5,
exaggeratedMinHeight + scale.z * 0.5,
scratchCenter2D,
);
Matrix4.fromTranslation(center, result);
Matrix4.setScale(result, scale, result);
Matrix4.multiply(Transforms.SWIZZLE_3D_TO_2D_MATRIX, result, result);
return result;
}
/**
* Gives the point on this terrain tile where the given ray intersects
* @param {Ray} ray The ray to test for intersection.
* @param {boolean} cullBackFaces Whether to consider back-facing triangles as intersections.
* @param {SceneMode} mode The scene mode (3D, 2D, or Columbus View).
* @param {MapProjection} projection The map projection.
* @returns {Cartesian3} The point on the mesh where the ray intersects, or undefined if there is no intersection.
* @private
*/
TerrainMesh.prototype.pick = function (ray, cullBackFaces, mode, projection) {
return this._terrainPicker.rayIntersect(
ray,
this.getTransform(mode, projection),
cullBackFaces,
mode,
projection,
);
};
/**
* Updates the terrain mesh to account for changes in vertical exaggeration.
* @param {Number} exaggeration A scalar used to exaggerate terrain.
* @param {Number} exaggerationRelativeHeight The relative height from which terrain is exaggerated.
* @private
*/
TerrainMesh.prototype.updateExaggeration = function (
exaggeration,
exaggerationRelativeHeight,
) {
// The encoding stored on the TerrainMesh references the updated exaggeration values already. This is just used
// to trigger a rebuild on the terrain picker.
this._terrainPicker._vertices = this.vertices;
this._terrainPicker.needsRebuild = true;
this._recomputeTransform = true;
};
/**
* Updates the terrain mesh to account for changes in scene mode.
* @param {SceneMode} mode The scene mode (3D, 2D, or Columbus View).
* @private
*/
TerrainMesh.prototype.updateSceneMode = function (mode) {
this._terrainPicker.needsRebuild = true;
this._recomputeTransform = true;
};
export default TerrainMesh;