const EPSILON14 = 0.00000000000001; const PI_OVER_TWO = Math.PI / 2.0; const SHIFT_LEFT_12 = Math.pow(2.0, 12.0); const TerrainQuantization = { NONE: 0, BITS12: 1 } function defined(target) { return target !== undefined && target !== null; } function defaultValue(value, defaultValue) { if (!defined(value)) return defaultValue; return value; } function lerp(p, q, time) { return (1.0 - time) * p + time * q; }; function clamp(value, min, max) { return value < min ? min : value > max ? max : value; }; function equalsEpsilon(a, b, epsilon) { return Math.abs(a - b) <= epsilon; } function sign(a) { if (a == 0) return 0; return Math.abs(a) === a ? 1.0 : -1.0; } class Vector3 { constructor (x,y,z) { this.x = x || 0; this.y = y || 0; this.z = z || 0; } static minimumByComponent(v1, v2, result) { if (!defined(result)) result = new Vector3(); result.set( Math.min(v1.x, v2.x), Math.min(v1.y, v2.y), Math.min(v1.z, v2.z) ) return result; } static maximumByComponent(v1, v2, result) { if (!defined(result)) result = new Vector3(); result.set( Math.max(v1.x, v2.x), Math.max(v1.y, v2.y), Math.max(v1.z, v2.z) ); return result; } static maximumComponent(v) { return Math.max(v.x, v.y, v.z); } static equals(v1, v2) { return (v1.x == v2.x && v1.y == v2.x && v1.z == v2.z); } static equalsEpsilon(v1, v2, epsilon) { return (Math.abs(v1.x - v2.x) <= epsilon && Math.abs(v1.y - v2.y) <= epsilon && Math.abs(v1.z - v2.z) <= epsilon ); } static unpack(array, startIndex, v) { v.x = array[startIndex]; v.y = array[startIndex + 1]; v.z = array[startIndex + 2]; } static multiplyByScalar(v, scalar, result) { if (!defined(result)) result = new Vector3(); result.x = v.x * scalar; result.y = v.y * scalar; result.z = v.z * scalar; return result; } static multiplyComponents(left, right, result) { if (!defined(result)) result = new Vector3(); result.x = left.x * right.x; result.y = left.y * right.y; result.z = left.z * right.z; return result; } static divideByScalar(v, scalar, result) { if (!defined(result)) result = new Vector3(); result.x = v.x / scalar; result.y = v.y / scalar; result.z = v.z / scalar; return result; } static subtract(left, right, result) { if (!defined(result)) { result = new Vector3(); } result.set( left.x - right.x, left.y - right.y, left.z - right.z ); return result; } static negate(v, result) { if (!defined(result)) { result = new Vector3(); } result.set( -v.x, -v.y, -v.z ); return result; } static dot(left, right) { return left.x * right.x + left.y * right.y + left.z * right.z; } static cross(left, right, result) { if (!defined(result)) result = new Vector3(); const leftX = left.x; const leftY = left.y; const leftZ = left.z; const rightX = right.x; const rightY = right.y; const rightZ = right.z; const x = leftY * rightZ - leftZ * rightY; const y = leftZ * rightX - leftX * rightZ; const z = leftX * rightY - leftY * rightX; result.x = x; result.y = y; result.z = z; return result; } clone() { return new Vector3(this.x, this.y, this.z); } static clone(source) { return new Vector3(source.x, source.y, source.z); } copy(source) { this.x = source.x; this.y = source.y; this.z = source.z; return this; } length() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } lengthSquare() { return this.x * this.x + this.y * this.y + this.z * this.z; } normalize() { const length = this.length() || 1; return Vector3.divideByScalar(this, length, this); } set(x, y, z) { this.x = x; this.y = y; this.z = z; } static add(left, right, result) { if (!defined(result)) result = new Vector3(); result.set( left.x + right.x, left.y + right.y, left.z + right.z ); return result; } static normalize(v, result) { if (!defined(result)) result = new Vector3(); const length = v.length() || 1.0; return Vector3.divideByScalar(v, length, result); } static ZERO = Object.freeze(new Vector3(0, 0, 0)) } class Vector2 { constructor(x, y) { this.x = x || 0; this.y = y || 0; } set(x, y) { this.x = x; this.y = y; } } class Matrix4 { constructor( column0Row0, column1Row0, column2Row0, column3Row0, column0Row1, column1Row1, column2Row1, column3Row1, column0Row2, column1Row2, column2Row2, column3Row2, column0Row3, column1Row3, column2Row3, column3Row3 ) { this[0] = defaultValue(column0Row0, 0.0); this[1] = defaultValue(column0Row1, 0.0); this[2] = defaultValue(column0Row2, 0.0); this[3] = defaultValue(column0Row3, 0.0); this[4] = defaultValue(column1Row0, 0.0); this[5] = defaultValue(column1Row1, 0.0); this[6] = defaultValue(column1Row2, 0.0); this[7] = defaultValue(column1Row3, 0.0); this[8] = defaultValue(column2Row0, 0.0); this[9] = defaultValue(column2Row1, 0.0); this[10] = defaultValue(column2Row2, 0.0); this[11] = defaultValue(column2Row3, 0.0); this[12] = defaultValue(column3Row0, 0.0); this[13] = defaultValue(column3Row1, 0.0); this[14] = defaultValue(column3Row2, 0.0); this[15] = defaultValue(column3Row3, 0.0); } static multiplyByPoint(matrix, cartesian, result) { if (!defined(result)) result = new Vector3(); const vX = cartesian.x; const vY = cartesian.y; const vZ = cartesian.z; const x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12]; const y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13]; const z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14]; result.x = x; result.y = y; result.z = z; return result; } static inverseTransformation(matrix, result) { if (!defined(result)) result = new Matrix4(); const matrix0 = matrix[0]; const matrix1 = matrix[1]; const matrix2 = matrix[2]; const matrix4 = matrix[4]; const matrix5 = matrix[5]; const matrix6 = matrix[6]; const matrix8 = matrix[8]; const matrix9 = matrix[9]; const matrix10 = matrix[10]; const vX = matrix[12]; const vY = matrix[13]; const vZ = matrix[14]; const x = -matrix0 * vX - matrix1 * vY - matrix2 * vZ; const y = -matrix4 * vX - matrix5 * vY - matrix6 * vZ; const z = -matrix8 * vX - matrix9 * vY - matrix10 * vZ; result[0] = matrix0; result[1] = matrix4; result[2] = matrix8; result[3] = 0.0; result[4] = matrix1; result[5] = matrix5; result[6] = matrix9; result[7] = 0.0; result[8] = matrix2; result[9] = matrix6; result[10] = matrix10; result[11] = 0.0; result[12] = x; result[13] = y; result[14] = z; result[15] = 1.0; return result; } static multiply(left, right, result) { if (!defined(result)) { result = new Matrix4(); } const left0 = left[0]; const left1 = left[1]; const left2 = left[2]; const left3 = left[3]; const left4 = left[4]; const left5 = left[5]; const left6 = left[6]; const left7 = left[7]; const left8 = left[8]; const left9 = left[9]; const left10 = left[10]; const left11 = left[11]; const left12 = left[12]; const left13 = left[13]; const left14 = left[14]; const left15 = left[15]; const right0 = right[0]; const right1 = right[1]; const right2 = right[2]; const right3 = right[3]; const right4 = right[4]; const right5 = right[5]; const right6 = right[6]; const right7 = right[7]; const right8 = right[8]; const right9 = right[9]; const right10 = right[10]; const right11 = right[11]; const right12 = right[12]; const right13 = right[13]; const right14 = right[14]; const right15 = right[15]; const column0Row0 = left0 * right0 + left4 * right1 + left8 * right2 + left12 * right3; const column0Row1 = left1 * right0 + left5 * right1 + left9 * right2 + left13 * right3; const column0Row2 = left2 * right0 + left6 * right1 + left10 * right2 + left14 * right3; const column0Row3 = left3 * right0 + left7 * right1 + left11 * right2 + left15 * right3; const column1Row0 = left0 * right4 + left4 * right5 + left8 * right6 + left12 * right7; const column1Row1 = left1 * right4 + left5 * right5 + left9 * right6 + left13 * right7; const column1Row2 = left2 * right4 + left6 * right5 + left10 * right6 + left14 * right7; const column1Row3 = left3 * right4 + left7 * right5 + left11 * right6 + left15 * right7; const column2Row0 = left0 * right8 + left4 * right9 + left8 * right10 + left12 * right11; const column2Row1 = left1 * right8 + left5 * right9 + left9 * right10 + left13 * right11; const column2Row2 = left2 * right8 + left6 * right9 + left10 * right10 + left14 * right11; const column2Row3 = left3 * right8 + left7 * right9 + left11 * right10 + left15 * right11; const column3Row0 = left0 * right12 + left4 * right13 + left8 * right14 + left12 * right15; const column3Row1 = left1 * right12 + left5 * right13 + left9 * right14 + left13 * right15; const column3Row2 = left2 * right12 + left6 * right13 + left10 * right14 + left14 * right15; const column3Row3 = left3 * right12 + left7 * right13 + left11 * right14 + left15 * right15; result[0] = column0Row0; result[1] = column0Row1; result[2] = column0Row2; result[3] = column0Row3; result[4] = column1Row0; result[5] = column1Row1; result[6] = column1Row2; result[7] = column1Row3; result[8] = column2Row0; result[9] = column2Row1; result[10] = column2Row2; result[11] = column2Row3; result[12] = column3Row0; result[13] = column3Row1; result[14] = column3Row2; result[15] = column3Row3; return result; } static fromTranslation(translation, result) { if (!defined(result)) { return new Matrix4( 1.0, 0.0, 0.0, translation.x, 0.0, 1.0, 0.0, translation.y, 0.0, 0.0, 1.0, translation.z, 0.0, 0.0, 0.0, 1.0 ); } result[0] = 1.0; result[1] = 0.0; result[2] = 0.0; result[3] = 0.0; result[4] = 0.0; result[5] = 1.0; result[6] = 0.0; result[7] = 0.0; result[8] = 0.0; result[9] = 0.0; result[10] = 1.0; result[11] = 0.0; result[12] = translation.x; result[13] = translation.y; result[14] = translation.z; result[15] = 1.0; return result; } static fromRotationTranslation(rotation, translation, result) { translation = defaultValue(translation, Vector3.ZERO); if (!defined(result)) { return new Matrix4( rotation[0], rotation[3], rotation[6], translation.x, rotation[1], rotation[4], rotation[7], translation.y, rotation[2], rotation[5], rotation[8], translation.z, 0.0, 0.0, 0.0, 1.0 ); } result[0] = rotation[0]; result[1] = rotation[1]; result[2] = rotation[2]; result[3] = 0.0; result[4] = rotation[3]; result[5] = rotation[4]; result[6] = rotation[5]; result[7] = 0.0; result[8] = rotation[6]; result[9] = rotation[7]; result[10] = rotation[8]; result[11] = 0.0; result[12] = translation.x; result[13] = translation.y; result[14] = translation.z; result[15] = 1.0; return result; } static fromScale(scale, result) { if (!defined(result)) { return new Matrix4( scale.x, 0.0, 0.0, 0.0, 0.0, scale.y, 0.0, 0.0, 0.0, 0.0, scale.z, 0.0, 0.0, 0.0, 0.0, 1.0 ); } result[0] = scale.x; result[1] = 0.0; result[2] = 0.0; result[3] = 0.0; result[4] = 0.0; result[5] = scale.y; result[6] = 0.0; result[7] = 0.0; result[8] = 0.0; result[9] = 0.0; result[10] = scale.z; result[11] = 0.0; result[12] = 0.0; result[13] = 0.0; result[14] = 0.0; result[15] = 1.0; return result; } static clone(matrix, result) { if (!defined(result)) { return new Matrix4( matrix[0], matrix[4], matrix[8], matrix[12], matrix[1], matrix[5], matrix[9], matrix[13], matrix[2], matrix[6], matrix[10], matrix[14], matrix[3], matrix[7], matrix[11], matrix[15] ); } result[0] = matrix[0]; result[1] = matrix[1]; result[2] = matrix[2]; result[3] = matrix[3]; result[4] = matrix[4]; result[5] = matrix[5]; result[6] = matrix[6]; result[7] = matrix[7]; result[8] = matrix[8]; result[9] = matrix[9]; result[10] = matrix[10]; result[11] = matrix[11]; result[12] = matrix[12]; result[13] = matrix[13]; result[14] = matrix[14]; result[15] = matrix[15]; return result; } static setTranslation(matrix, translation, result) { if (!defined(result)) result = new Matrix4(); result[0] = matrix[0]; result[1] = matrix[1]; result[2] = matrix[2]; result[3] = matrix[3]; result[4] = matrix[4]; result[5] = matrix[5]; result[6] = matrix[6]; result[7] = matrix[7]; result[8] = matrix[8]; result[9] = matrix[9]; result[10] = matrix[10]; result[11] = matrix[11]; result[12] = translation.x; result[13] = translation.y; result[14] = translation.z; result[15] = matrix[15]; return result; } } class Rectangle { constructor(west, south, east, north) { this.west = west || 0; this.south = south || 0; this.east = east || 0; this.north = north || 0; } clone(source) { return new Rectangle( source.west, source.south, source.east, source.north ); } } class Cartographic { constructor(longitude, latitude, height) { this.longitude = longitude || 0; this.latitude = latitude || 0; this.height = height || 0; } } const scratchFirstArray = [0, 1, 0]; const scratchSecondArray = [-1, 0, 0]; const scratchThirdArray = [0, 0, 1]; const scratchFirstCartesian = new Vector3(); const scratchSecondCartesian = new Vector3(); const scratchThirdCartesian = new Vector3(); const scratchCalculateCartesian = { east: new Vector3(), north: new Vector3(), up: new Vector3(), west: new Vector3(), south: new Vector3(), down: new Vector3(), } class Transforms { constructor(){} static eastNorthUpToFixedFrame(origin, ellipsoid, result) { if (!defined(result)) result = new Matrix4(); if (Vector3.equalsEpsilon(origin, Vector3.ZERO, EPSILON14)) { Vector3.unpack(scratchFirstArray, 0, scratchFirstCartesian); Vector3.unpack(scratchSecondArray, 0, scratchSecondCartesian); Vector3.unpack(scratchThirdArray, 0, scratchThirdCartesian); } else if ( equalsEpsilon(origin.x, 0.0, EPSILON14) && equalsEpsilon(origin.y, 0.0, EPSILON14) ) { const sign = sign(origin.z); Vector3.unpack(scratchFirstArray, 0, scratchFirstCartesian); Vector3.unpack(scratchSecondArray, 0, scratchSecondCartesian); Vector3.multiplyByScalar(scratchSecondCartesian, sign, scratchSecondCartesian); Vector3.unpack(scratchThirdArray, 0, scratchThirdCartesian); Vector3.multiplyByScalar(scratchThirdCartesian, sign, scratchThirdCartesian); } else { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); ellipsoid.geodeticSurfaceNormal(origin, scratchCalculateCartesian.up); const up = scratchCalculateCartesian.up; const east = scratchCalculateCartesian.east; east.x = -origin.y; east.y = origin.x; east.z = 0.0; Vector3.normalize(east, scratchCalculateCartesian.east); Vector3.cross(up, east, scratchCalculateCartesian.north); Vector3.multiplyByScalar(scratchCalculateCartesian.up, -1, scratchCalculateCartesian.down); Vector3.multiplyByScalar(scratchCalculateCartesian.east, -1, scratchCalculateCartesian.west); Vector3.multiplyByScalar(scratchCalculateCartesian.north, -1, scratchCalculateCartesian.south); scratchFirstCartesian.copy(scratchCalculateCartesian.east); scratchSecondCartesian.copy(scratchCalculateCartesian.north); scratchThirdCartesian.copy(scratchCalculateCartesian.up); } result[0] = scratchFirstCartesian.x; result[1] = scratchFirstCartesian.y; result[2] = scratchFirstCartesian.z; result[3] = 0.0; result[4] = scratchSecondCartesian.x; result[5] = scratchSecondCartesian.y; result[6] = scratchSecondCartesian.z; result[7] = 0.0; result[8] = scratchThirdCartesian.x; result[9] = scratchThirdCartesian.y; result[10] = scratchThirdCartesian.z; result[11] = 0.0; result[12] = origin.x; result[13] = origin.y; result[14] = origin.z; result[15] = 1.0; return result; } } class Ellipsoid { constructor(x, y, z) { x = x || 0; y = y || 0; z = z || 0; this.x = x; this.y = y; this.z = z; this.radii = new Vector3(x, y, z); this.radiiSquared = new Vector3(x * x, y * y, z * z); this.radiiToTheFourth = new Vector3( x * x * x * x, y * y * y * y, z * z * z * z ); this.oneOverRadii = new Vector3( x === 0.0 ? 0.0 : 1.0 / x, y === 0.0 ? 0.0 : 1.0 / y, z === 0.0 ? 0.0 : 1.0 / z ); this.oneOverRadiiSquared = new Vector3( x === 0.0 ? 0.0 : 1.0 / (x * x), y === 0.0 ? 0.0 : 1.0 / (y * y), z === 0.0 ? 0.0 : 1.0 / (z * z) ); this.minimumRadius = Math.min(x, y, z); this.maximumRadius = Math.max(x, y, z); this.centerToleranceSquared = 0.1; this.squaredFirstEccentricity = 1 - (z * z) / (x * x); if (this.radiiSquared.z !== 0) { this.squaredXOverSquaredZ = this.radiiSquared.x / this.radiiSquared.z; } } transformPositionToScaledSpace(position, result) { if (!defined(result)) { result = new Vector3(); } return Vector3.multiplyComponents(position, this.oneOverRadii, result); } geodeticSurfaceNormal(cartesian, result) { if (Vector3.equalsEpsilon(cartesian, Vector3.ZERO, EPSILON14)) { return undefined; } if (!defined(result)) result = new Vector3(); let temp = this.oneOverRadiiSquared.clone(); result = Vector3.multiplyComponents(cartesian, temp, result); return result.normalize(); } cartographicToCartesian(cartographic, result) { if (!defined(result)) result = new Vector3(); const cartographicToCartesianNormal = new Vector3(); const cartographicToCartesianK = new Vector3(); this.geodeticSurfaceNormalCartographic(cartographic, cartographicToCartesianNormal); Vector3.multiplyComponents(this.radiiSquared, cartographicToCartesianNormal, cartographicToCartesianK); const gamma = Math.sqrt(Vector3.dot(cartographicToCartesianNormal, cartographicToCartesianK)); Vector3.divideByScalar(cartographicToCartesianK, gamma, cartographicToCartesianK); Vector3.multiplyByScalar(cartographicToCartesianNormal, cartographic.height, cartographicToCartesianNormal); return Vector3.add(cartographicToCartesianK, cartographicToCartesianNormal, result); } geodeticSurfaceNormalCartographic(cartographic, result) { if (!defined(result)) result = new Vector3(); const longitude = cartographic.longitude; const latitude = cartographic.latitude; const cosLatitude = Math.cos(latitude); const x = cosLatitude * Math.cos(longitude); const y = cosLatitude * Math.sin(longitude); const z = Math.sin(latitude); result.set(x, y, z); return result.normalize(); } } Ellipsoid.WGS84 = Object.freeze(new Ellipsoid(6378137.0, 6378137.0, 6356752.3142451793)) class WebMercatorProjection { constructor(){} static mercatorAngleToGeodeticLatitude(mercatorAngle) { return PI_OVER_TWO - 2.0 * Math.atan(Math.exp(-mercatorAngle)); } static geodeticLatitudeToMercatorAngle(latitude) { if (latitude > WebMercatorProjection.MaximumLatitude) { latitude = WebMercatorProjection.MaximumLatitude; } else if (latitude < -WebMercatorProjection.MaximumLatitude) { latitude = -WebMercatorProjection.MaximumLatitude; } const sinLatitude = Math.sin(latitude); return 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude)); } } WebMercatorProjection.MaximumLatitude = WebMercatorProjection.mercatorAngleToGeodeticLatitude(Math.PI); class EllipsoidalOccluder { constructor(ellipsoid) { this.ellipsoid = ellipsoid; this.cameraPosition = new Vector3(); this.cameraPositionInScaledSpace = new Vector3(); this.distanceToLimbInScaledSquared = 0.0; } computeHorizonCullingPointPossiblyUnderEllipsoid(directionToPoint, positions, minimumHeight, result) { const possiblyShrunkEllipsoid = EllipsoidalOccluder.getPossiblyShrunkEllipsoid( this.ellipsoid, minimumHeight ); return EllipsoidalOccluder.computeHorizonCullingPointFromPositions( possiblyShrunkEllipsoid, directionToPoint, positions, result ); } static getPossiblyShrunkEllipsoid(ellipsoid, minimumHeight) { if (defined(minimumHeight) && minimumHeight < 0.0 && ellipsoid.minimumRadius > - minimumHeight ) { const ellipsoidShrunkRadii = new Vector3( ellipsoid.radii.x + minimumHeight, ellipsoid.radii.y + minimumHeight, ellipsoid.radii.z + minimumHeight, ); ellipsoid = new Ellipsoid( ellipsoidShrunkRadii.x, ellipsoidShrunkRadii.y, ellipsoidShrunkRadii.z ); } return ellipsoid; } static computeHorizonCullingPointFromPositions(ellipsoid, directionToPoint, positions, result) { if (!defined(result)) result = new Vector3(); const scaledSpaceDirectionToPoint = EllipsoidalOccluder.computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint); let resultMagnitude = 0.0; for (let i = 0, len = positions.length; i < len; ++i) { const position = positions[i]; const candidateMagnitude = EllipsoidalOccluder.computeMagnitude( ellipsoid, position, scaledSpaceDirectionToPoint ); if (candidateMagnitude < 0.0) { // all points should face the same direction, but this one doesn't, so return undefined return undefined; } resultMagnitude = Math.max(resultMagnitude, candidateMagnitude); } return EllipsoidalOccluder.magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result); } static computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint){ if (Vector3.equals(directionToPoint, Vector3.ZERO)) { return directionToPoint; } const directionToPointScratch = ellipsoid.transformPositionToScaledSpace( directionToPoint ); return directionToPointScratch.normalize(); } static computeMagnitude(ellipsoid, position, scaledSpaceDirectionToPoint) { const scaledSpacePosition = ellipsoid.transformPositionToScaledSpace( position ); let magnitudeSquared = scaledSpacePosition.lengthSquare(); let magnitude = scaledSpacePosition.length(); const direction = Vector3.divideByScalar(scaledSpacePosition, magnitude); magnitudeSquared = Math.max(1.0, magnitudeSquared); magnitude = Math.max(1.0, magnitude); const cosAlpha = Vector3.dot(direction, scaledSpaceDirectionToPoint); const sinAlpha = Vector3.cross(direction, scaledSpaceDirectionToPoint, direction).length() const cosBeta = 1.0 / magnitude; const sinBeta = Math.sqrt(magnitudeSquared - 1.0) * cosBeta; return 1.0 / (cosAlpha * cosBeta - sinAlpha * sinBeta); } static magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result) { if (resultMagnitude <= 0.0 || resultMagnitude === 1.0 / 0.0 || resultMagnitude !== resultMagnitude) { return undefined; } return Vector3.multiplyByScalar(scaledSpaceDirectionToPoint, resultMagnitude, result); } } class AxisAlignedBoundingBox { constructor(minimum, maximum, center) { this.minimum = defaultValue(minimum, Vector3.ZERO).clone(); this.maximum = defaultValue(maximum, Vector3.ZERO).clone(); if (!defined(center)) { center = new Vector3( (this.minimum.x + this.maximum.x) * 0.5, (this.minimum.y + this.maximum.y) * 0.5, (this.minimum.z + this.maximum.z) * 0.5 ); } else { center = new Vector3(center.x, center.y, center.z); } this.center = center; } } class TerrainEncoding { constructor( center, axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals, hasWebMercatorT, hasGeodeticSurfaceNormals, exaggeration, exaggerationRelativeHeight, name ) { let quantization = TerrainQuantization.NONE; let toENU; let matrix; const cartesian3DimScratch = new Vector3(); if ( defined(axisAlignedBoundingBox) && defined(minimumHeight) && defined(maximumHeight) && defined(fromENU) ) { const minimum = axisAlignedBoundingBox.minimum; const maximum = axisAlignedBoundingBox.maximum; const dimensions = Vector3.subtract( maximum, minimum, cartesian3DimScratch ); const hDim = maximumHeight - minimumHeight; const maxDim = Math.max(Vector3.maximumComponent(dimensions), hDim); if (maxDim < SHIFT_LEFT_12 - 1.0) { quantization = TerrainQuantization.BITS12; } else { quantization = TerrainQuantization.NONE; } toENU = Matrix4.inverseTransformation(fromENU, new Matrix4()); const matrix4Scratch13T = new Matrix4(); const cartesian3Scratch13T = new Vector3(); const matrix4Scratch2 = new Matrix4(); const translation = Vector3.negate(minimum, cartesian3Scratch13T); Matrix4.multiply( Matrix4.fromTranslation(translation, matrix4Scratch13T), toENU, toENU ); const scale = cartesian3Scratch13T; scale.x = 1.0 / dimensions.x; scale.y = 1.0 / dimensions.y; scale.z = 1.0 / dimensions.z; Matrix4.multiply(Matrix4.fromScale(scale, matrix4Scratch13T), toENU, toENU); matrix = Matrix4.clone(fromENU); Matrix4.setTranslation(matrix, Vector3.ZERO, matrix); fromENU = Matrix4.clone(fromENU, new Matrix4()); const translationMatrix = Matrix4.fromTranslation(minimum, matrix4Scratch13T); const scaleMatrix = Matrix4.fromScale(dimensions, matrix4Scratch2); const st = Matrix4.multiply(translationMatrix, scaleMatrix, matrix4Scratch13T); Matrix4.multiply(fromENU, st, fromENU); Matrix4.multiply(matrix, st, matrix); } this.quantization = quantization; this.minimumHeight = minimumHeight; this.maximumHeight = maximumHeight; this.center = Vector3.clone(center); this.toScaledENU = toENU; this.fromScaledENU = fromENU; this.matrix = matrix; this.hasVertexNormals = hasVertexNormals; this.hasWebMercatorT = defaultValue(hasWebMercatorT, false); this.hasGeodeticSurfaceNormals = defaultValue( hasGeodeticSurfaceNormals, false ); this.exaggeration = defaultValue(exaggeration, 1.0); this.exaggerationRelativeHeight = defaultValue( exaggerationRelativeHeight, 0.0 ); this.stride = 0; this._offsetGeodeticSurfaceNormal = 0; this._offsetVertexNormal = 0; this._calculateStrideAndOffsets(); } _calculateStrideAndOffsets() { let vertexStride = 0; switch (this.quantization) { case TerrainQuantization.BITS12: vertexStride += 3; break; default: vertexStride += 6; } if (this.hasWebMercatorT) { vertexStride += 1; } if (this.hasVertexNormals) { this._offsetVertexNormal = vertexStride; vertexStride += 1; } if (this.hasGeodeticSurfaceNormals) { this._offsetGeodeticSurfaceNormal = vertexStride; vertexStride += 3; } this.stride = vertexStride; } encode(vertexBuffer, bufferIndex, position, uv, height, normalToPack, webMercatorT, geodeticSurfaceNormal) { const cartesian3Scratch13T = new Vector3(); const cartesian2Scratch = new Vector2(); const u = uv.x; const v = uv.y; if (this.quantization === TerrainQuantization.BITS12) { position = Matrix4.multiplyByPoint( this.toScaledENU, position, cartesian3Scratch13T ); position.x = clamp(position.x, 0.0, 1.0); position.y = clamp(position.y, 0.0, 1.0); position.z = clamp(position.z, 0.0, 1.0); const hDim = this.maximumHeight - this.minimumHeight; const h = clamp((height - this.minimumHeight) / hDim, 0.0, 1.0); cartesian2Scratch.set(position.x, position.y); const compressed0 = AttributeCompression.compressTextureCoordinates( cartesian2Scratch ); cartesian2Scratch.set(position.z, h); const compressed1 = AttributeCompression.compressTextureCoordinates( cartesian2Scratch ); cartesian2Scratch.set(u, v); const compressed2 = AttributeCompression.compressTextureCoordinates( cartesian2Scratch ); vertexBuffer[bufferIndex++] = compressed0; vertexBuffer[bufferIndex++] = compressed1; vertexBuffer[bufferIndex++] = compressed2; if (this.hasWebMercatorT) { cartesian2Scratch.set(webMercatorT, 0.0); const compressed3 = AttributeCompression.compressTextureCoordinates( cartesian2Scratch ); vertexBuffer[bufferIndex++] = compressed3; } } else { Vector3.subtract(position, this.center, cartesian3Scratch13T); vertexBuffer[bufferIndex++] = cartesian3Scratch13T.x; vertexBuffer[bufferIndex++] = cartesian3Scratch13T.y; vertexBuffer[bufferIndex++] = cartesian3Scratch13T.z; vertexBuffer[bufferIndex++] = height; vertexBuffer[bufferIndex++] = u; vertexBuffer[bufferIndex++] = v; if (this.hasWebMercatorT) { vertexBuffer[bufferIndex++] = webMercatorT; } } if (this.hasVertexNormals) { vertexBuffer[bufferIndex++] = AttributeCompression.octPackFloat( normalToPack ); } if (this.hasGeodeticSurfaceNormals) { vertexBuffer[bufferIndex++] = geodeticSurfaceNormal.x; vertexBuffer[bufferIndex++] = geodeticSurfaceNormal.y; vertexBuffer[bufferIndex++] = geodeticSurfaceNormal.z; } return bufferIndex; } } class IndexDatatype { constructor() {} static createTypedArray(numberOfVertices, indicesLengthOrArray) { if (!defined(numberOfVertices)) { throw new Error("numberOfVertices is required"); } if (numberOfVertices >= 6 * 1024 * 1024) { return new Uint32Array(indicesLengthOrArray); } return new Uint16Array(indicesLengthOrArray); } } class TerrainProvider { constructor(){} static addSkirtIndice(edgeIndices, vertexIndex, indices, offset) { let previousIndex = edgeIndices[0]; const length = edgeIndices.length; for (let i = 1; i < length; ++i) { const index = edgeIndices[i]; indices[offset++] = previousIndex; indices[offset++] = index; indices[offset++] = vertexIndex; indices[offset++] = vertexIndex; indices[offset++] = index; indices[offset++] = vertexIndex + 1; previousIndex = index; ++vertexIndex; } return offset; } static addSkirtIndices( westIndicesSouthToNorth, southIndicesEastToWest, eastIndicesNorthToSouth, northIndicesWestToEast, vertexCount, indices, offset ) { let vertexIndex = vertexCount; offset = TerrainProvider.addSkirtIndice( westIndicesSouthToNorth, vertexIndex, indices, offset ); vertexIndex += westIndicesSouthToNorth.length; offset = TerrainProvider.addSkirtIndice( southIndicesEastToWest, vertexIndex, indices, offset ); vertexIndex += southIndicesEastToWest.length; offset = TerrainProvider.addSkirtIndice( eastIndicesNorthToSouth, vertexIndex, indices, offset ); vertexIndex += eastIndicesNorthToSouth.length; TerrainProvider.addSkirtIndice(northIndicesWestToEast, vertexIndex, indices, offset); } } // class Vertex {} class AttributeCompression { constructor(){} static compressTextureCoordinates(textureCoordinates) { const x = (textureCoordinates.x * 4095.0) | 0; const y = (textureCoordinates.y * 4095.0) | 0; return 4096 * x + y; } static octPackFloat(encoded) { return 256.0 * encoded.x + encoded.y; } } const cartographicScratch = new Cartographic(); const cartesian3Scratch = new Vector3(); const toPack = new Vector2(); const maxShort = 32767; const halfMaxShort = (maxShort / 2) || 0; function createVerticesFromQuantizedTerrainMesh(parameters) { const quantizedVertices = parameters.quantizedVertices; const quantizedVerticeCount = quantizedVertices.length / 3; const octEncodedNormals = parameters.octEncodedNormals; const edgeVertexCount = parameters.westIndices.length + parameters.eastIndices.length + parameters.southIndices.length + parameters.northIndices.length; const includeWebMercatorT = parameters.includeWebMercatorT; const exaggeration = parameters.exaggeration; const exaggerationRelativeHeight = parameters.exaggerationRelativeHeight; const hasExaggeration = exaggeration !== 1.0; const includeGeodeticSurfaceNormals = hasExaggeration; const rectangle = new Rectangle().clone(parameters.rectangle); const [west, east, north, south] = [rectangle.west, rectangle.east, rectangle.north, rectangle.south]; const ellipsoid = new Ellipsoid(parameters.ellipsoid.x, parameters.ellipsoid.y, parameters.ellipsoid.z); // parameters.ellipsoid const minimumHeight = parameters.minimumHeight; const maximumHeight = parameters.maximumHeight; const center = parameters.relativeToCenter; const fromENU = Transforms.eastNorthUpToFixedFrame(center, ellipsoid); const name = parameters.name; const toENU = Matrix4.inverseTransformation(fromENU); let southMercatorY, oneOverMercatorHeight; if (includeWebMercatorT) { southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(south); oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(north) - southMercatorY); } const uBuffer = quantizedVertices.subarray(0, quantizedVerticeCount); const vBuffer = quantizedVertices.subarray(quantizedVerticeCount, quantizedVerticeCount * 2); const heightBuffer = quantizedVertices.subarray(quantizedVerticeCount * 2, quantizedVerticeCount * 3); const hasVertexNormals = defined(octEncodedNormals); // 准备UV、高度、经纬度数组 const uvs = new Array(quantizedVerticeCount), heights = new Array(quantizedVerticeCount), positions = new Array(quantizedVerticeCount); const webMercatorTs = includeWebMercatorT ? new Array(quantizedVerticeCount) : []; const geodeticSurfaceNormals = includeGeodeticSurfaceNormals ? new Array(quantizedVerticeCount) : []; let minimum = new Vector3( Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY ); let maximum = new Vector3( Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY ); let minLongitude = Number.POSITIVE_INFINITY; let maxLongitude = Number.NEGATIVE_INFINITY; let minLatitude = Number.POSITIVE_INFINITY; let maxLatitude = Number.NEGATIVE_INFINITY; for (let i = 0; i < quantizedVerticeCount; ++i) { const rawU = uBuffer[i]; const rawV = vBuffer[i]; const u = rawU / maxShort; const v = rawV / maxShort; const height = lerp(minimumHeight, maximumHeight, heightBuffer[i] / maxShort); cartographicScratch.longitude = lerp(west, east, u); cartographicScratch.latitude = lerp(south, north, v); cartographicScratch.height = height; minLongitude = Math.min( cartographicScratch.longitude, minLongitude ); maxLongitude = Math.max( cartographicScratch.longitude, maxLongitude ); minLatitude = Math.min( cartographicScratch.latitude, minLatitude ); maxLatitude = Math.max( cartographicScratch.latitude, maxLatitude ); const position = ellipsoid.cartographicToCartesian(cartographicScratch); uvs[i] = new Vector3(u, v, 0); heights[i] = height; positions[i] = position; if (includeWebMercatorT) { webMercatorTs[i] = ( WebMercatorProjection.geodeticLatitudeToMercatorAngle(cartographicScratch.latitude) - southMercatorY ) * oneOverMercatorHeight; } if (includeGeodeticSurfaceNormals) { geodeticSurfaceNormals[i] = ellipsoid.geodeticSurfaceNormal(position); } Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch); Vector3.minimumByComponent(cartesian3Scratch, minimum, minimum); Vector3.maximumByComponent(cartesian3Scratch, maximum, maximum); } // 处理四条边的索引 const westIndicesSouthToNorth = copyAndSort( parameters.westIndices, function(a, b) { return uvs[a].y - uvs[b].y; } ); const eastIndicesNorthToSouth = copyAndSort( parameters.eastIndices, function(a, b) { return uvs[b].y - uvs[a].y; } ); const southIndicesEastToWest = copyAndSort( parameters.southIndices, function(a, b) { return uvs[b].x - uvs[a].x; } ); const northIndicesWestToEast = copyAndSort( parameters.northIndices, function(a, b) { return uvs[a].x - uvs[b].x; } ); // 处理地平线 let occludeePointInScaledSpace; if (minimumHeight < 0.0) { const occluder = new EllipsoidalOccluder(ellipsoid); occludeePointInScaledSpace = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(center, positions, minimumHeight); } // 处理高程 let hMin = minimumHeight; hMin = Math.min( hMin, findMinMaxSkirts( parameters.westIndices, parameters.westSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum ) ); hMin = Math.min( hMin, findMinMaxSkirts( parameters.southIndices, parameters.southSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum ) ); hMin = Math.min( hMin, findMinMaxSkirts( parameters.eastIndices, parameters.eastSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum ) ); hMin = Math.min( hMin, findMinMaxSkirts( parameters.northIndices, parameters.northSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum ) ); const aaBox = new AxisAlignedBoundingBox(minimum, maximum, center); // console.log(name, center, aaBox, hMin, maximumHeight, fromENU, hasVertexNormals, includeWebMercatorT, includeGeodeticSurfaceNormals, exaggeration, exaggerationRelativeHeight) const encoding = new TerrainEncoding(center, aaBox, hMin, maximumHeight, fromENU, hasVertexNormals, includeWebMercatorT, includeGeodeticSurfaceNormals, exaggeration, exaggerationRelativeHeight, name); const vertexStride = encoding.stride; const size = (quantizedVerticeCount + edgeVertexCount) * vertexStride; const vertexBuffer = new Float32Array(size); let bufferIndex = 0; for (let j = 0; j < quantizedVerticeCount; ++j) { if (hasVertexNormals) { const n = j * 2.0; toPack.x = octEncodedNormals[n]; toPack.y = octEncodedNormals[n + 1]; } bufferIndex = encoding.encode( vertexBuffer, bufferIndex, positions[j], uvs[j], heights[j], toPack, webMercatorTs[j], geodeticSurfaceNormals[j] ); } const edgeTraiangleCount = Math.max(0, (edgeVertexCount - 4) * 2); const indexBufferLength = parameters.indices.length + edgeTraiangleCount * 3; const indexBuffer = IndexDatatype.createTypedArray(quantizedVerticeCount + edgeVertexCount, indexBufferLength); indexBuffer.set(parameters.indices, 0); const percentage = 0.0001; const lonOffset = (maxLongitude - minLongitude) * percentage; const latOffset = (maxLatitude - minLatitude) * percentage; const westLongitudeOffset = -lonOffset; const westLatitudeOffset = 0.0; const eastLongitudeOffset = lonOffset; const eastLatitudeOffset = 0.0; const northLongitudeOffset = 0.0; const northLatitudeOffset = latOffset; const southLongitudeOffset = 0.0; const southLatitudeOffset = -latOffset; // 添加裙边 let vertexBufferIndex = quantizedVerticeCount * vertexStride; addSkirt(vertexBuffer, vertexBufferIndex, westIndicesSouthToNorth, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.westSkirtHeight, southMercatorY, oneOverMercatorHeight, westLongitudeOffset, westLatitudeOffset); vertexBufferIndex += parameters.westIndices.length * vertexStride; addSkirt(vertexBuffer, vertexBufferIndex, southIndicesEastToWest, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.southSkirtHeight, southMercatorY, oneOverMercatorHeight, southLongitudeOffset, southLatitudeOffset); vertexBufferIndex += parameters.southIndices.length * vertexStride; addSkirt(vertexBuffer, vertexBufferIndex, eastIndicesNorthToSouth, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.eastSkirtHeight, southMercatorY, oneOverMercatorHeight, eastLongitudeOffset, eastLatitudeOffset); vertexBufferIndex += parameters.eastIndices.length * vertexStride; addSkirt(vertexBuffer, vertexBufferIndex, northIndicesWestToEast, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.northSkirtHeight, southMercatorY, oneOverMercatorHeight, northLongitudeOffset, northLatitudeOffset); TerrainProvider.addSkirtIndices( westIndicesSouthToNorth, southIndicesEastToWest, eastIndicesNorthToSouth, northIndicesWestToEast, quantizedVerticeCount, indexBuffer, parameters.indices.length ); return { vertices: vertexBuffer.buffer, indices: indexBuffer.buffer, westIndicesSouthToNorth: westIndicesSouthToNorth, southIndicesEastToWest: southIndicesEastToWest, eastIndicesNorthToSouth: eastIndicesNorthToSouth, northIndicesWestToEast: northIndicesWestToEast, vertexStride: vertexStride, center: center, minimumHeight: minimumHeight, maximumHeight: maximumHeight, occludeePointInScaledSpace: occludeePointInScaledSpace, encoding: encoding, indexCountWithoutSkirts: parameters.indices.length }; } function copyAndSort(typedArray, comparator) { let copy; if (typeof typedArray.slice === "function") { copy = typedArray.slice(); if (typeof copy.sort !== "function") { copy = undefined; } } if (!defined(copy)) { copy = Array.prototype.slice.call(typedArray); } copy.sort(comparator); return copy; } function findMinMaxSkirts( edgeIndices, edgeHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum ) { let hMin = Number.POSITIVE_INFINITY; const north = rectangle.north; const south = rectangle.south; let east = rectangle.east; const west = rectangle.west; if (east < west) { east += Math.PI * 2; } const length = edgeIndices.length; for (let i = 0; i < length; ++i) { const index = edgeIndices[i]; const h = heights[index]; const uv = uvs[index]; cartographicScratch.longitude = lerp(west, east, uv.x); cartographicScratch.latitude = lerp(south, north, uv.y); cartographicScratch.height = h - edgeHeight; const position = ellipsoid.cartographicToCartesian( cartographicScratch, cartesian3Scratch, ); Matrix4.multiplyByPoint(toENU, position, position); Vector3.minimumByComponent(position, minimum, minimum); Vector3.maximumByComponent(position, maximum, maximum); hMin = Math.min(hMin, cartographicScratch.height); } return hMin; } function addSkirt( vertexBuffer, vertexBufferIndex, edgeVertices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, skirtLength, southMercatorY, oneOverMercatorHeight, longitudeOffset, latitudeOffset ) { const hasVertexNormals = defined(octEncodedNormals); const north = rectangle.north; const south = rectangle.south; let east = rectangle.east; const west = rectangle.west; if (east < west) { east += Math.PI * 2; } const length = edgeVertices.length; for(let i = 0; i < length; ++i) { const index = edgeVertices[i]; const h = heights[index]; const uv = uvs[index]; cartographicScratch.longitude = lerp(west, east, uv.x) + longitudeOffset; cartographicScratch.latitude = lerp(south, north, uv.y) + latitudeOffset; cartographicScratch.height = h - skirtLength; const position = ellipsoid.cartographicToCartesian( cartographicScratch, cartesian3Scratch ); if (hasVertexNormals) { const n = index * 2.0; toPack.x = octEncodedNormals[n]; toPack.y = octEncodedNormals[n + 1]; } let webMercatorT; if (encoding.hasWebMercatorT) { webMercatorT = ( WebMercatorProjection.geodeticLatitudeToMercatorAngle(cartographicScratch.latitude) - southMercatorY ) * oneOverMercatorHeight; } let geodeticSurfaceNormal; if (encoding.hasGeodeticSurfaceNormals) { geodeticSurfaceNormal = ellipsoid.geodeticSurfaceNormal(position, geodeticSurfaceNormalScratch); } vertexBufferIndex = encoding.encode( vertexBuffer, vertexBufferIndex, position, uv, cartographicScratch.height, toPack, webMercatorT, geodeticSurfaceNormal ); } } onmessage = function(e) { const data = e.data; const id = data.id; const parameters = data.parameters; const responseData = { id: id, result: undefined, error: undefined } return Promise.resolve( createVerticesFromQuantizedTerrainMesh(parameters) ).then(result => { responseData.result = result }).catch(e => { if (e instanceof Error) { responseData.error = { name: e.name, message: e.message, stack: e.stack } } else { responseData.error = e; } }).finally(() => { postMessage(responseData); }) }