const EPSILON14 = 0.00000000000001; const EPSILON15 = 0.000000000000001; const PI_OVER_TWO = Math.PI / 2.0; const SHIFT_LEFT_12 = Math.pow(2.0, 12.0); const TerrainQuantization = { NONE: 0, BITS12: 1 } const maxShort = 32767; const halfMaxShort = (maxShort / 2) | 0; 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; } function fromSNorm(value, rangeMaximum) { rangeMaximum = defaultValue(rangeMaximum, 255); return (clamp(value, 0.0, rangeMaximum) / rangeMaximum) * 2.0 - 1.0; } function signNotZero(value) { return value < 0.0 ? -1.0 : 1.0; } function mod(m, n) { if (sign(m) == sign(n) && Math.abs(m) < Math.abs(n)) return m; return ((m % n) + n) % n; } function zeroToTwoPi(angle) { if (angle >= 0 && angle <= Math.PI * 2) { return angle; } const modr = mod(angle, Math.PI * 2); if ( Math.abs(modr) < EPSILON14 && Math.abs(angle) > EPSILON14 ) { return Math.PI * 2; } return modr; } function negativePiToPi(angle) { if (angle >= -Math.PI && angle <= Math.PI) { return angle; } return zeroToTwoPi(angle + Math.PI) - Math.PI; } function scaleToGeodeticSurface( cartesian, oneOverRadii, oneOverRadiiSquared, centerToleranceSquared, result ) { const positionX = cartesian.x; const positionY = cartesian.y; const positionZ = cartesian.z; const oneOverRadiiX = oneOverRadii.x; const oneOverRadiiY = oneOverRadii.y; const oneOverRadiiZ = oneOverRadii.z; const x2 = positionX * positionX * oneOverRadiiX * oneOverRadiiX; const y2 = positionY * positionY * oneOverRadiiY * oneOverRadiiY; const z2 = positionZ * positionZ * oneOverRadiiZ * oneOverRadiiZ; // Compute the squared ellipsoid norm. const squaredNorm = x2 + y2 + z2; const ratio = Math.sqrt(1.0 / squaredNorm); // As an initial approximation, assume that the radial intersection is the projection point. const intersection = Vector3.multiplyByScalar( cartesian, ratio ); // If the position is near the center, the iteration will not converge. if (squaredNorm < centerToleranceSquared) { return !isFinite(ratio) ? undefined : result.copy(intersection); } const oneOverRadiiSquaredX = oneOverRadiiSquared.x; const oneOverRadiiSquaredY = oneOverRadiiSquared.y; const oneOverRadiiSquaredZ = oneOverRadiiSquared.z; // Use the gradient at the intersection point in place of the true unit normal. // The difference in magnitude will be absorbed in the multiplier. const gradient = new Vector3(); gradient.x = intersection.x * oneOverRadiiSquaredX * 2.0; gradient.y = intersection.y * oneOverRadiiSquaredY * 2.0; gradient.z = intersection.z * oneOverRadiiSquaredZ * 2.0; // Compute the initial guess at the normal vector multiplier, lambda. let lambda = ((1.0 - ratio) * cartesian.length()) / (0.5 * gradient.length()); let correction = 0.0; let func; let denominator; let xMultiplier; let yMultiplier; let zMultiplier; let xMultiplier2; let yMultiplier2; let zMultiplier2; let xMultiplier3; let yMultiplier3; let zMultiplier3; do { lambda -= correction; xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredX); yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredY); zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredZ); xMultiplier2 = xMultiplier * xMultiplier; yMultiplier2 = yMultiplier * yMultiplier; zMultiplier2 = zMultiplier * zMultiplier; xMultiplier3 = xMultiplier2 * xMultiplier; yMultiplier3 = yMultiplier2 * yMultiplier; zMultiplier3 = zMultiplier2 * zMultiplier; func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0; // "denominator" here refers to the use of this expression in the velocity and acceleration // computations in the sections to follow. denominator = x2 * xMultiplier3 * oneOverRadiiSquaredX + y2 * yMultiplier3 * oneOverRadiiSquaredY + z2 * zMultiplier3 * oneOverRadiiSquaredZ; const derivative = -2.0 * denominator; correction = func / derivative; } while (Math.abs(func) > CesiumMath.EPSILON12); if (!defined(result)) { return new Vector3( positionX * xMultiplier, positionY * yMultiplier, positionZ * zMultiplier ); } result.x = positionX * xMultiplier; result.y = positionY * yMultiplier; result.z = positionZ * zMultiplier; return result; } class Vector3 { constructor (x,y,z) { this.x = x || 0; this.y = y || 0; this.z = z || 0; } static midpoint(left, right, result) { if (!defined(result)) result = new Vector3(); result.set( (left.x + right.x) * 0.5, (left.y + right.y) * 0.5, (left.z + right.z) * 0.5, ); return result; } 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; } negate(){ this.x = -this.x; this.y = -this.y; this.z = -this.z; return this; } 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.set( source.x, source.y, 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 fromCartesian4(v) { return new Vector3(v.x, v.y, v.z); } } Vector3.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 Vector4 { constructor(x, y, z, w) { this.x = x || 0; this.y = y || 0; this.z = z || 0; this.w = w || 0; } } 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; } static getColumn(matrix, index, result) { if (!defined(result)) result = new Vector4(); const startIndex = index * 4; const x = matrix[startIndex]; const y = matrix[startIndex + 1]; const z = matrix[startIndex + 2]; const w = matrix[startIndex + 3]; result.x = x; result.y = y; result.z = z; result.w = w; return result; } } class Matrix3 { constructor( column0Row0, column1Row0, column2Row0, column0Row1, column1Row1, column2Row1, column0Row2, column1Row2, column2Row2 ) { this[0] = defaultValue(column0Row0, 0.0); this[1] = defaultValue(column0Row1, 0.0); this[2] = defaultValue(column0Row2, 0.0); this[3] = defaultValue(column1Row0, 0.0); this[4] = defaultValue(column1Row1, 0.0); this[5] = defaultValue(column1Row2, 0.0); this[6] = defaultValue(column2Row0, 0.0); this[7] = defaultValue(column2Row1, 0.0); this[8] = defaultValue(column2Row2, 0.0); } clone() { return new Matrix3( this[0], this[3], this[6], this[1], this[4], this[7], this[2], this[5], this[8] ); } static setColumn(matrix, index, cartesian, result) { if (!defined(result)) result = new Matrix3(); result = matrix.clone(); const startIndex = index * 3; result[startIndex] = cartesian.x; result[startIndex + 1] = cartesian.y; result[startIndex + 2] = cartesian.z; return result; } static multiplyByVector(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[3] * vY + matrix[6] * vZ; const y = matrix[1] * vX + matrix[4] * vY + matrix[7] * vZ; const z = matrix[2] * vX + matrix[5] * vY + matrix[8] * vZ; result.x = x; result.y = y; result.z = z; return result; } static multiplyByScale(matrix, scale, result) { if (!defined(result)) result = new Matrix3(); result[0] = matrix[0] * scale.x; result[1] = matrix[1] * scale.x; result[2] = matrix[2] * scale.x; result[3] = matrix[3] * scale.y; result[4] = matrix[4] * scale.y; result[5] = matrix[5] * scale.y; result[6] = matrix[6] * scale.z; result[7] = matrix[7] * scale.z; result[8] = matrix[8] * scale.z; return result; } } Matrix3.ZERO = Object.freeze( new Matrix3(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) ); class Rectangle { constructor(west, south, east, north) { this.west = west || 0; this.south = south || 0; this.east = east || 0; this.north = north || 0; } static clone(source) { return new Rectangle( source.west, source.south, source.east, source.north ); } } class BoundingSphere { constructor(center, radius) { this.center = center || Vector3.ZERO; this.radius = radius || 0; } static center(rectangle, result) { if (!defined(result)) result = new Cartographic(); let east = rectangle.east; const west = rectangle.west; if (east < west) { east += Math.TWO_PI; } const longitude = negativePiToPi((west + east) * 0.5); const latitude = (rectangle.south + rectangle.north) * 0.5; result.longitude = longitude; result.latitude = latitude; result.height = 0.0; return result; } static fromPoints(positions, result) { if (!defined(result)) result = new BoundingSphere(); if (!defined(positions) || positions.length === 0) { result.center.copy(Vector3.ZERO); result.radius = 0.0; return result; } const currentPos = new Vector3().copy(positions[0]); const xMin = currentPos.clone(); const yMin = currentPos.clone(); const zMin = currentPos.clone(); const xMax = currentPos.clone(); const yMax = currentPos.clone(); const zMax = currentPos.clone(); const numPositions = positions.length; let i; for (i = 1; i < numPositions; i++) { currentPos.copy(positions[i]) const x = currentPos.x; const y = currentPos.y; const z = currentPos.z; // Store points containing the the smallest and largest components if (x < xMin.x) { xMin.copy(currentPos); } if (x > xMax.x) { xMin.copy(currentPos); } if (y < yMin.y) { yMin.copy(currentPos); } if (y > yMax.y) { yMax.copy(currentPos); } if (z < zMin.z) { zMin.copy(currentPos); } if (z > zMax.z) { zMax.copy(currentPos); } } // Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.). const xSpan = Vector3.subtract(xMax, xMin).lengthSquare(); const ySpan = Vector3.subtract(yMax, yMin).lengthSquare(); const zSpan = Vector3.subtract(zMax, zMin).lengthSquare(); // Set the diameter endpoints to the largest span. let diameter1 = xMin; let diameter2 = xMax; let maxSpan = xSpan; if (ySpan > maxSpan) { maxSpan = ySpan; diameter1 = yMin; diameter2 = yMax; } if (zSpan > maxSpan) { maxSpan = zSpan; diameter1 = zMin; diameter2 = zMax; } // Calculate the center of the initial sphere found by Ritter's algorithm const ritterCenter = new Vector3(); ritterCenter.x = (diameter1.x + diameter2.x) * 0.5; ritterCenter.y = (diameter1.y + diameter2.y) * 0.5; ritterCenter.z = (diameter1.z + diameter2.z) * 0.5; // Calculate the radius of the initial sphere found by Ritter's algorithm let radiusSquared = Vector3.subtract(diameter2, ritterCenter).lengthSquare(); let ritterRadius = Math.sqrt(radiusSquared); // Find the center of the sphere found using the Naive method. const minBoxPt = new Vector3(); minBoxPt.x = xMin.x; minBoxPt.y = yMin.y; minBoxPt.z = zMin.z; const maxBoxPt = new Vector3(); maxBoxPt.x = xMax.x; maxBoxPt.y = yMax.y; maxBoxPt.z = zMax.z; const naiveCenter = Vector3.midpoint(minBoxPt, maxBoxPt); // Begin 2nd pass to find naive radius and modify the ritter sphere. let naiveRadius = 0; for (i = 0; i < numPositions; i++) { currentPos.copy(positions[i]); const r = Vector3.subtract(currentPos, naiveCenter).length(); if (r > naiveRadius) { naiveRadius = r; } const oldCenterToPointSquared = Vector3.subtract(currentPos, ritterCenter).lengthSquare(); if (oldCenterToPointSquared > radiusSquared) { const oldCenterToPoint = Math.sqrt(oldCenterToPointSquared); // Calculate new radius to include the point that lies outside ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5; radiusSquared = ritterRadius * ritterRadius; // Calculate center of new Ritter sphere const oldToNew = oldCenterToPoint - ritterRadius; ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint; ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint; ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint; } } if (ritterRadius < naiveRadius) { result.center.copy(ritterCenter); result.radius = ritterRadius; } else { result.center.copy(naiveCenter); result.radius = naiveRadius; } return result; } static fromVertices(positions, center, stride, result) { if (!defined(result)) { result = new BoundingSphere(); } if (!defined(positions) || positions.length === 0) { result.center.copy(Vector3.ZERO); result.radius = 0.0; return result; } center =defaultValue(center, Vector3.ZERO); stride = defaultValue(stride, 3); const currentPos = new Vector3(); currentPos.x = positions[0] + center.x; currentPos.y = positions[1] + center.y; currentPos.z = positions[2] + center.z; const xMin = currentPos.clone(); const yMin = currentPos.clone(); const zMin = currentPos.clone(); const xMax = currentPos.clone(); const yMax = currentPos.clone(); const zMax = currentPos.clone(); const numElements = positions.length; let i; for (i = 0; i < numElements; i += stride) { const x = positions[i] + center.x; const y = positions[i + 1] + center.y; const z = positions[i + 2] + center.z; currentPos.x = x; currentPos.y = y; currentPos.z = z; // Store points containing the the smallest and largest components if (x < xMin.x) { xMin.copy(currentPos); } if (x > xMax.x) { xMin.copy(currentPos); } if (y < yMin.y) { yMin.copy(currentPos); } if (y > yMax.y) { yMax.copy(currentPos); } if (z < zMin.z) { zMin.copy(currentPos); } if (z > zMax.z) { zMax.copy(currentPos); } } // Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.). const xSpan = Vector3.subtract(xMax, xMin).lengthSquare(); const ySpan = Vector3.subtract(yMax, yMin).lengthSquare(); const zSpan = Vector3.subtract(zMax, zMin).lengthSquare(); // Set the diameter endpoints to the largest span. let diameter1 = xMin; let diameter2 = xMax; let maxSpan = xSpan; if (ySpan > maxSpan) { maxSpan = ySpan; diameter1 = yMin; diameter2 = yMax; } if (zSpan > maxSpan) { maxSpan = zSpan; diameter1 = zMin; diameter2 = zMax; } // Calculate the center of the initial sphere found by Ritter's algorithm const ritterCenter = new Vector3(); ritterCenter.x = (diameter1.x + diameter2.x) * 0.5; ritterCenter.y = (diameter1.y + diameter2.y) * 0.5; ritterCenter.z = (diameter1.z + diameter2.z) * 0.5; // Calculate the radius of the initial sphere found by Ritter's algorithm let radiusSquared = Vector3.subtract(diameter2, ritterCenter).lengthSquare(); let ritterRadius = Math.sqrt(radiusSquared); // Find the center of the sphere found using the Naive method. const minBoxPt = new Vector3(); minBoxPt.x = xMin.x; minBoxPt.y = yMin.y; minBoxPt.z = zMin.z; const maxBoxPt = new Vector3(); maxBoxPt.x = xMax.x; maxBoxPt.y = yMax.y; maxBoxPt.z = zMax.z; const naiveCenter = Vector3.midpoint(minBoxPt, maxBoxPt); // Begin 2nd pass to find naive radius and modify the ritter sphere. let naiveRadius = 0; for (i = 0; i < numElements; i += stride) { currentPos.x = positions[i] + center.x; currentPos.y = positions[i + 1] + center.y; currentPos.z = positions[i + 2] + center.z; // Find the furthest point from the naive center to calculate the naive radius. const r = Vector3.subtract(currentPos, naiveCenter).length(); if (r > naiveRadius) { naiveRadius = r; } // Make adjustments to the Ritter Sphere to include all points. const oldCenterToPointSquared = Vector3.subtract(currentPos, ritterCenter).lengthSquare(); if (oldCenterToPointSquared > radiusSquared) { const oldCenterToPoint = Math.sqrt(oldCenterToPointSquared); // Calculate new radius to include the point that lies outside ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5; radiusSquared = ritterRadius * ritterRadius; // Calculate center of new Ritter sphere const oldToNew = oldCenterToPoint - ritterRadius; ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint; ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint; ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint; } } if (ritterRadius < naiveRadius) { result.center = ritterCenter.clone(); result.radius = ritterRadius; } else { result.center = naiveCenter.clone(); result.radius = naiveRadius; } return result; } } class Plane { constructor(normal, distance) { this.normal = normal.clone(); this.distance = distance; } static fromPointNormal(point, normal, result) { const distance = -Vector3.dot(normal, point); if (!defined(result)) result = new Plane(normal, distance); result.normal = normal.clone(); result.distance = distance; return result; } static getPointDistance(plane, point) { return Vector3.dot(plane.normal, point) + plane.distance; } } class IntersectionTests { constructor(){} static rayPlane(ray, plane, result) { if (!defined(result)) result = new Vector3(); const origin = ray.origin; const direction = ray.direction; const normal = plane.normal; const denominator = Vector3.dot(normal, direction); if (Math.abs(denominator) < EPSILON15) { // Ray is parallel to plane. The ray may be in the polygon's plane. return undefined; } const t = (-plane.distance - Vector3.dot(normal, origin)) / denominator; if (t < 0) { return undefined; } Vector3.multiplyByScalar(direction, t, result); return result.add(origin); } } class Ray { constructor(origin, direction) { this.origin = origin || new Vector3(); this.direction = direction || new Vector3(); } } class EllipsoidTangentPlane { constructor(origin, ellipsoid) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); origin = ellipsoid.scaleToGeodeticSurface(origin); const eastNorthUp = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid); this.ellipsoid = ellipsoid; this.origin = origin; this.xAxis = Vector3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 0)); this.yAxis = Vector3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 1)); const normal = Vector3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 2)); this.plane = Plane.fromPointNormal(origin, normal); } projectPointToNearestOnPlane(cartesian, result) { if (!defined(result)) result = new Vector2(); const ray = new Ray(); ray.origin.copy(cartesian); ray.direction.copy(this.plane.normal); let intersectionPoint = IntersectionTests.rayPlane(ray, this.plane); if (!defined(intersectionPoint)) { ray.direction.negate(); intersectionPoint = IntersectionTests.rayPlane(ray, this.plane); } const v = Vector3.subtract(intersectionPoint, this.origin, intersectionPoint); const x = Vector3.dot(this.xAxis, v); const y = Vector3.dot(this.yAxis, v); result.x = x; result.y = y; return result; } } class OrientedBoundingBox { constructor (center, halfAxes) { this.center = defaultValue(center, Vector3.ZERO).clone(); this.halfAxes = defaultValue(halfAxes, Matrix3.ZERO).clone(); } static fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid, result) { if (!defined(result)) result = new OrientedBoundingBox(); minimumHeight = defaultValue(minimumHeight, 0.0); maximumHeight = defaultValue(maximumHeight, 0.0); ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); let minX, maxX, minY, maxY, minZ, maxZ, plane; if (rectangle.width <= Math.PI) { // The bounding box will be aligned with the tangent plane at the center of the rectangle. const tangentPointCartographic = BoundingSphere.center(rectangle); const tangentPoint = ellipsoid.cartographicToCartesian(tangentPointCartographic); const tangentPlane = new EllipsoidTangentPlane(tangentPoint, ellipsoid); plane = tangentPlane.plane; // If the rectangle spans the equator, CW is instead aligned with the equator (because it sticks out the farthest at the equator). const lonCenter = tangentPointCartographic.longitude; const latCenter = rectangle.south < 0.0 && rectangle.north > 0.0 ? 0.0 : tangentPointCartographic.latitude; // Compute XY extents using the rectangle at maximum height const perimeterCartographicNC = Cartographic.fromRadians( lonCenter, rectangle.north, maximumHeight, ); const perimeterCartographicNW = Cartographic.fromRadians( rectangle.west, rectangle.north, maximumHeight, ); const perimeterCartographicCW = Cartographic.fromRadians( rectangle.west, latCenter, maximumHeight, ); const perimeterCartographicSW = Cartographic.fromRadians( rectangle.west, rectangle.south, maximumHeight, ); const perimeterCartographicSC = Cartographic.fromRadians( lonCenter, rectangle.south, maximumHeight, ); const perimeterCartesianNC = ellipsoid.cartographicToCartesian( perimeterCartographicNC ); let perimeterCartesianNW = ellipsoid.cartographicToCartesian( perimeterCartographicNW ); const perimeterCartesianCW = ellipsoid.cartographicToCartesian( perimeterCartographicCW ); let perimeterCartesianSW = ellipsoid.cartographicToCartesian( perimeterCartographicSW ); const perimeterCartesianSC = ellipsoid.cartographicToCartesian( perimeterCartographicSC ); const perimeterProjectedNC = tangentPlane.projectPointToNearestOnPlane( perimeterCartesianNC ); const perimeterProjectedNW = tangentPlane.projectPointToNearestOnPlane( perimeterCartesianNW ); const perimeterProjectedCW = tangentPlane.projectPointToNearestOnPlane( perimeterCartesianCW ); const perimeterProjectedSW = tangentPlane.projectPointToNearestOnPlane( perimeterCartesianSW ); const perimeterProjectedSC = tangentPlane.projectPointToNearestOnPlane( perimeterCartesianSC ); minX = Math.min( perimeterProjectedNW.x, perimeterProjectedCW.x, perimeterProjectedSW.x ); maxX = -minX; // symmetrical maxY = Math.max(perimeterProjectedNW.y, perimeterProjectedNC.y); minY = Math.min(perimeterProjectedSW.y, perimeterProjectedSC.y); // Compute minimum Z using the rectangle at minimum height, since it will be deeper than the maximum height perimeterCartographicNW.height = perimeterCartographicSW.height = minimumHeight; perimeterCartesianNW = ellipsoid.cartographicToCartesian( perimeterCartographicNW ); perimeterCartesianSW = ellipsoid.cartographicToCartesian( perimeterCartographicSW ); minZ = Math.min( Plane.getPointDistance(plane, perimeterCartesianNW), Plane.getPointDistance(plane, perimeterCartesianSW) ); maxZ = maximumHeight; // Since the tangent plane touches the surface at height = 0, this is okay return OrientedBoundingBox.fromPlaneExtents( tangentPlane.origin, tangentPlane.xAxis, tangentPlane.yAxis, tangentPlane.zAxis, minX, maxX, minY, maxY, minZ, maxZ, result ); } } static fromPlaneExtents( planeOrigin, planeXAxis, planeYAxis, planeZAxis, minimumX, maximumX, minimumY, maximumY, minimumZ, maximumZ, result ) { if ( !defined(minimumX) || !defined(maximumX) || !defined(minimumY) || !defined(maximumY) || !defined(minimumZ) || !defined(maximumZ) ) { throw new Error( "all extents (minimum/maximum X/Y/Z) are required." ); } if (!defined(result)) result = new OrientedBoundingBox(); const halfAxes = result.halfAxes; Matrix3.setColumn(halfAxes, 0, planeXAxis, halfAxes); Matrix3.setColumn(halfAxes, 1, planeYAxis, halfAxes); Matrix3.setColumn(halfAxes, 2, planeZAxis, halfAxes); let centerOffset = scratchOffset; centerOffset.x = (minimumX + maximumX) / 2.0; centerOffset.y = (minimumY + maximumY) / 2.0; centerOffset.z = (minimumZ + maximumZ) / 2.0; const scale = scratchScale117; scale.x = (maximumX - minimumX) / 2.0; scale.y = (maximumY - minimumY) / 2.0; scale.z = (maximumZ - minimumZ) / 2.0; const center = result.center; centerOffset = Matrix3.multiplyByVector(halfAxes, centerOffset, centerOffset); Vector3.add(planeOrigin, centerOffset, center); Matrix3.multiplyByScale(halfAxes, scale, halfAxes); return result; } } class Cartographic { constructor(longitude, latitude, height) { this.longitude = longitude || 0; this.latitude = latitude || 0; this.height = height || 0; } static fromRadians(longitude, latitude, height, result) { height = defaultValue(height, 0.0); if (!defined(result)) { return new Cartographic(longitude, latitude, height); } result.longitude = longitude; result.latitude = latitude; result.height = height; 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; } } static clone(source) { return new Ellipsoid(source.x, source.y, source.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(); } scaleToGeodeticSurface(cartesian, result) { return scaleToGeodeticSurface( cartesian, this.oneOverRadii, this.oneOverRadiiSquared, this.centerToleranceSquared, result ); } } Ellipsoid.WGS84 = Object.freeze(new Ellipsoid(6378137.0, 6378137.0, 6356752.3142451793)); 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 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); } computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid( directionToPoint, vertices, stride, center, minimumHeight, result ) { const possiblyShrunkEllipsoid = EllipsoidalOccluder.getPossiblyShrunkEllipsoid( this.ellipsoid, minimumHeight ); return EllipsoidalOccluder.computeHorizonCullingPointFromVertices( possiblyShrunkEllipsoid, directionToPoint, vertices, stride, center, result ) } static computeHorizonCullingPointFromVertices( ellipsoid, directionToPoint, vertices, stride, center, result ) { if (!defined(result)) result = new Vector3(); stride = defaultValue(stride, 3); center = defaultValue(center, Vector3.ZERO.clone()); const scaledSpaceDirectionToPoint = EllipsoidalOccluder.computeScaledSpaceDirectionToPoint( ellipsoid, directionToPoint ); let resultMagnitude = 0.0; const positionScratch = new Vector3(); for (let i = 0, len = vertices.length; i < len; i += stride) { positionScratch.x = vertices[i] + center.x; positionScratch.y = vertices[i + 1] + center.y; positionScratch.z = vertices[i + 2] + center.z; const candidateMagnitude = EllipsoidalOccluder.computeMagnitude( ellipsoid, positionScratch, 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); } } 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 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; } static octDecode(x, y, result) { return AttributeCompression.octDecodeInRange(x, y, 255, result); } static decompressTextureCoordinates(compressed, result) { const temp = compressed / 4096.0; const xZeroTo4095 = Math.floor(temp); result.x = xZeroTo4095 / 4095.0; result.y = (compressed - xZeroTo4095 * 4096) / 4095; return result; } static octDecodeInRange(x, y, rangeMax, result) { if (x < 0 || x > rangeMax || y < 0 || y > rangeMax) { throw new Erro( `x and y must be unsigned normalized integers between 0 and ${rangeMax}` ); } if (!defined(result)) result = new Vector3(); result.x = fromSNorm(x, rangeMax); result.y = fromSNorm(y, rangeMax); result.z = 1.0 - (Math.abs(result.x) + Math.abs(result.y)); if (result.z < 0.0) { const oldVX = result.x; result.x = (1.0 - Math.abs(result.y)) * signNotZero(oldVX); result.y = (1.0 - Math.abs(oldVX)) * signNotZero(result.y); } return result.normalize(); } } const cartesian2Scratch = new Vector2(); class TerrainEncoding { constructor( center, axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals, hasWebMercatorT, hasGeodeticSurfaceNormals, exaggeration, exaggerationRelativeHeight, ) { 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 = defined(center) ? Vector3.clone(center) : new Vector3(); 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; } static clone(encoding, result) { if (!defined(encoding)) { return undefined; } if (!defined(result)) { result = new TerrainEncoding(); } result.quantization = encoding.quantization; result.minimumHeight = encoding.minimumHeight; result.maximumHeight = encoding.maximumHeight; result.center = Vector3.clone(encoding.center); result.toScaledENU = Matrix4.clone(encoding.toScaledENU); result.fromScaledENU = Matrix4.clone(encoding.fromScaledENU); result.matrix = Matrix4.clone(encoding.matrix); result.hasVertexNormals = encoding.hasVertexNormals; result.hasWebMercatorT = encoding.hasWebMercatorT; result.hasGeodeticSurfaceNormals = encoding.hasGeodeticSurfaceNormals; result.exaggeration = encoding.exaggeration; result.exaggerationRelativeHeight = encoding.exaggerationRelativeHeight; result._calculateStrideAndOffsets(); return result; } decodeTextureCoordinates(buffer, index, result) { if (!defined(result)) result = new Vector2(); index *= this.stride; if (this.quantization === TerrainQuantization.BITS12) { return AttributeCompression.decompressTextureCoordinates(buffer[index + 2], result); } result.set(buffer[index + 4], buffer[index + 5]); return result; } decodeHeight(buffer, index) { index *= this.stride; if (this.quantization === TerrainQuantization.BITS12) { const zh = AttributeCompression.decompressTextureCoordinates( buffer[index + 1], cartesian2Scratch ); return ( zh.y * (this.maximumHeight - this.minimumHeight) + this.minimumHeight ); } return buffer[index + 3]; } getOctEncodedNormal(buffer, index, result) { if (!defined(result)) result = new Vector2(); index = index * this.stride + this._offsetVertexNormal; const temp = buffer[index] / 256.0; const x = Math.floor(temp); const y = (temp - x) * 256.0; result.set(x, y); return result; } } 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 Vertex { constructor() { this.vertexBuffer = undefined; this.index = undefined; this.first = undefined; this.second = undefined; this.ratio = undefined; } clone(result) { if (!defined(result)) { result = new Vertex(); } result.uBuffer = this.uBuffer; result.vBuffer = this.vBuffer; result.heightBuffer = this.heightBuffer; result.normalBuffer = this.normalBuffer; result.index = this.index; result.first = this.first; result.second = this.second; result.ratio = this.ratio; return result; } initializeIndexed(uBuffer, vBuffer, heightBuffer, normalBuffer, index) { this.uBuffer = uBuffer; this.vBuffer = vBuffer; this.heightBuffer = heightBuffer; this.normalBuffer = normalBuffer; this.index = index; this.first = undefined; this.second = undefined; this.ratio = undefined; } initializeFromClipResult(clipResult, index, vertices) { let nextIndex = index + 1; if (clipResult[index] !== -1) { vertices[clipResult[index]].clone(this); } else { this.vertexBuffer = undefined; this.index = undefined; this.first = vertices[clipResult[nextIndex]]; ++nextIndex; this.second = vertices[clipResult[nextIndex]]; ++nextIndex; this.ratio = clipResult[nextIndex]; ++nextIndex; } return nextIndex; } getKey() { if (this.isIndexed()) { return this.index; } return JSON.stringify({ first: this.first.getKey(), second: this.second.getKey(), ratio: this.ratio, }); } isIndexed() { return defined(this.index); } getH() { if (this.isIndexed()) { return this.heightBuffer[this.index]; } return lerp(this.first.getH(), this.second.getH(), this.ratio); } getU() { if (this.isIndexed()) { return this.uBuffer[this.index]; } return lerp(this.first.getU(), this.second.getU(), this.ratio); } getV() { if (this.isIndexed()) { return this.vBuffer[this.index]; } return lerp(this.first.getV(), this.second.getV(), this.ratio); } getNormalX() { if (this.isIndexed()) { return this.normalBuffer[this.index * 2]; } encodedScratch = lerpOctEncodedNormal(this, encodedScratch); return encodedScratch.x; } getNormalY() { if (this.isIndexed()) { return this.normalBuffer[this.index * 2 + 1]; } encodedScratch = lerpOctEncodedNormal(this, encodedScratch); return encodedScratch.y; } } const polygonVertices = []; polygonVertices.push(new Vertex()); polygonVertices.push(new Vertex()); polygonVertices.push(new Vertex()); polygonVertices.push(new Vertex()); class Intersection { constructor(){} static clipTriangleAtAxisAlignedThreshold(threshold, keepAbove, u0, u1, u2, result) { if (!defined(result)) result = []; else result.length = 0; let u0Behind, u1Behind, u2Behind; if (keepAbove) { u0Behind = u0 < threshold; u1Behind = u1 < threshold; u2Behind = u2 < threshold; } else { u0Behind = u0 > threshold; u1Behind = u1 > threshold; u2Behind = u2 > threshold; } const numBehind = u0Behind + u1Behind + u2Behind; let u01Ratio, u02Ratio, u12Ratio, u10Ratio, u20Ratio, u21Ratio; if (numBehind === 1) { if (u0Behind) { u01Ratio = (threshold - u0) / (u1 - u0); u02Ratio = (threshold - u0) / (u2 - u0); result.push(1); result.push(2); if (u02Ratio !== 1.0) { result.push(-1); result.push(0); result.push(2); result.push(u02Ratio); } if (u01Ratio !== 1.0) { result.push(-1); result.push(0); result.push(1); result.push(u01Ratio); } } else if (u1Behind) { u12Ratio = (threshold - u1) / (u2 - u1); u10Ratio = (threshold - u1) / (u0 - u1); result.push(2); result.push(0); if (u10Ratio !== 1.0) { result.push(-1); result.push(1); result.push(0); result.push(u10Ratio); } if (u12Ratio !== 1.0) { result.push(-1); result.push(1); result.push(2); result.push(u12Ratio); } } else if (u2Behind) { u20Ratio = (threshold - u2) / (u0 - u2); u21Ratio = (threshold - u2) / (u1 - u2); result.push(0); result.push(1); if (u21Ratio !== 1.0) { result.push(-1); result.push(2); result.push(1); result.push(u21Ratio); } if (u20Ratio !== 1.0) { result.push(-1); result.push(2); result.push(0); result.push(u20Ratio); } } } else if (numBehind === 2) { if (!u0Behind && u0 !== threshold) { u10Ratio = (threshold - u1) / (u0 - u1); u20Ratio = (threshold - u2) / (u0 - u2); result.push(0); result.push(-1); result.push(1); result.push(0); result.push(u10Ratio); result.push(-1); result.push(2); result.push(0); result.push(u20Ratio); } else if (!u1Behind && u1 !== threshold) { u21Ratio = (threshold - u2) / (u1 - u2); u01Ratio = (threshold - u0) / (u1 - u0); result.push(1); result.push(-1); result.push(2); result.push(1); result.push(u21Ratio); result.push(-1); result.push(0); result.push(1); result.push(u01Ratio); } else if (!u2Behind && u2 !== threshold) { u02Ratio = (threshold - u0) / (u2 - u0); u12Ratio = (threshold - u1) / (u2 - u1); result.push(2); result.push(-1); result.push(0); result.push(2); result.push(u02Ratio); result.push(-1); result.push(1); result.push(2); result.push(u12Ratio); } } else if (numBehind !== 3) { result.push(0); result.push(1); result.push(2); } return result; } } const decodeTexCoordsScratch = new Vector2(); const octEncodedNormalScratch = new Vector3(); const uScratch = []; const vScratch = []; const heightScratch = []; const indicesScratch = []; const normalsScratch = []; let cartographicScratch = new Cartographic(); let cartesian3Scratch = new Vector3(); let depth = -1; const cartesianScratch1 = [new Vector3(), new Vector3()]; const cartesianScratch2 = [new Vector3(), new Vector3()]; let encodedScratch = new Vector2() function upsampleQuantizedTerrainMesh(parameters) { const isEastChild = parameters.isEastChild; const isNorthChild = parameters.isNorthChild; const name = parameters.name; // UV坐标 // U坐标 东边的瓦片是 [32767 / 2, 32767],西边的是[0, 32767 / 2] // V坐标 北边的瓦片是 [32767 / 2, 32767],南边的是[0, 32767 / 2] const minU = isEastChild ? halfMaxShort : 0; const maxU = isEastChild ? maxShort : halfMaxShort; const minV = isNorthChild ? halfMaxShort : 0; const maxV = isNorthChild ? maxShort : halfMaxShort; const uBuffer = uScratch; const vBuffer = vScratch; const heightBuffer = heightScratch; const normalBuffer = normalsScratch; uBuffer.length = vBuffer.length = heightBuffer.length = normalBuffer.length = 0; const indices = indicesScratch; indices.length = 0; const vertexMap = {}; // 父节点的顶点和索引(不算裙边) const parentVertices = parameters.vertices; let parentIndices = parameters.indices; parentIndices = parentIndices.subarray(0, parameters.indexCountWithoutSkirts); const encoding = TerrainEncoding.clone(parameters.encoding); const hasVertexNormals = encoding.hasVertexNormals; let vertexCount = 0; const quantizedVerticeCount = parameters.vertexCountWithoutSkirts; const parentMinimumHeight = parameters.minimumHeight; const parentMaximumHeight = parameters.maximumHeight; const parentUBuffer = new Array(quantizedVerticeCount); const parentVBuffer = new Array(quantizedVerticeCount); const parentHeightBuffer = new Array(quantizedVerticeCount); const parentNormalBuffer = hasVertexNormals ? new Array(quantizedVerticeCount * 2) : undefined; const threshold = 20; let height; let i, n; let u, v; for (i = 0, n = 0; i < quantizedVerticeCount; ++i, n += 2) { const texCoords = encoding.decodeTextureCoordinates(parentVertices, i, decodeTexCoordsScratch); height = encoding.decodeHeight(parentVertices, i); u = clamp((texCoords.x * maxShort) | 0, 0, maxShort); v = clamp((texCoords.y * maxShort) | 0, 0, maxShort); parentHeightBuffer[i] = clamp((((height - parentMinimumHeight) / (parentMaximumHeight - parentMinimumHeight)) * maxShort) | 0, 0, maxShort); if (u < threshold) u = 0; if (v < threshold) v = 0; if (maxShort - u < threshold) u = maxShort; if (maxShort - v < threshold) v = maxShort; parentUBuffer[i] = u; parentVBuffer[i] = v; if (hasVertexNormals) { const encodedNormal = encoding.getOctEncodedNormal(parentVertices, i, octEncodedNormalScratch); parentNormalBuffer[n] = encodedNormal.x; parentNormalBuffer[n + 1] = encodedNormal.y; } if ( ((isEastChild && u >= halfMaxShort) || (!isEastChild && u <= halfMaxShort)) && ((isNorthChild && v >= halfMaxShort) || (!isNorthChild && v <= halfMaxShort)) ) { vertexMap[i] = vertexCount; uBuffer.push(u); vBuffer.push(v); heightBuffer.push(parentHeightBuffer[i]); if (hasVertexNormals) { normalBuffer.push(parentNormalBuffer[n]); normalBuffer.push(parentNormalBuffer[n + 1]); } ++vertexCount; } } const triangleVertices = []; triangleVertices.push(new Vertex()); triangleVertices.push(new Vertex()); triangleVertices.push(new Vertex()); const clippedTriangleVertices = []; clippedTriangleVertices.push(new Vertex()); clippedTriangleVertices.push(new Vertex()); clippedTriangleVertices.push(new Vertex()); let clippedIndex; let clipped2; for (i = 0; i < parentIndices.length; i += 3) { const i0 = parentIndices[i]; const i1 = parentIndices[i + 1]; const i2 = parentIndices[i + 2]; const u0 = parentUBuffer[i0]; const u1 = parentUBuffer[i1]; const u2 = parentUBuffer[i2]; triangleVertices[0].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i0); triangleVertices[1].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i1); triangleVertices[2].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i2); const clipped = Intersection.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isEastChild, u0, u1, u2); clippedIndex = 0; if (clippedIndex >= clipped.length) continue; clippedIndex = clippedTriangleVertices[0].initializeFromClipResult(clipped, clippedIndex, triangleVertices); if (clippedIndex >= clipped.length) continue; clippedIndex = clippedTriangleVertices[1].initializeFromClipResult(clipped, clippedIndex, triangleVertices); if (clippedIndex >= clipped.length) continue; clippedIndex = clippedTriangleVertices[2].initializeFromClipResult(clipped, clippedIndex, triangleVertices); clipped2 = Intersection.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isNorthChild, clippedTriangleVertices[0].getV(), clippedTriangleVertices[1].getV(), clippedTriangleVertices[2].getV()); addClippedPolygon(uBuffer, vBuffer, heightBuffer, normalBuffer, indices, vertexMap, clipped2, clippedTriangleVertices, hasVertexNormals); if (clippedIndex < clipped.length) { clippedTriangleVertices[2].clone(clippedTriangleVertices[1]); clippedTriangleVertices[2].initializeFromClipResult(clipped, clippedIndex, triangleVertices); clipped2 = Intersection.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isNorthChild, clippedTriangleVertices[0].getV(), clippedTriangleVertices[1].getV(), clippedTriangleVertices[2].getV()); addClippedPolygon(uBuffer, vBuffer, heightBuffer, normalBuffer, indices, vertexMap, clipped2, clippedTriangleVertices, hasVertexNormals); } } const uOffset = isEastChild ? -maxShort : 0; const vOffset = isNorthChild ? -maxShort : 0; const westIndices = [], southIndices = [], eastIndices = [], northIndices = []; let minimumHeight = Number.MAX_VALUE, maximumHeight = -minimumHeight; const cartesianVertices = []; cartesianVertices.length = 0; const ellipsoid = Ellipsoid.clone(parameters.ellipsoid); const rectangle = Rectangle.clone(parameters.childRectangle); const north = rectangle.north, south = rectangle.south, west = rectangle.west; let east = rectangle.east; if (east < west) east += Math.PI * 2; for (i = 0; i < uBuffer.length; ++i) { u = Math.round(uBuffer[i]); if (u <= minU) { westIndices.push(i); u = 0; } else if (u >= maxU) { eastIndices.push(i); u = maxShort; } else { u = u * 2 + uOffset; } uBuffer[i] = u; v = Math.round(vBuffer[i]); if (v <= minV) { southIndices.push(i); v = 0; } else if (v >= maxV) { northIndices.push(i); v = maxShort; } else { v = v * 2 + vOffset; } vBuffer[i] = v; height = lerp(parentMinimumHeight, parentMaximumHeight, heightBuffer[i] / maxShort); if (height < minimumHeight) minimumHeight = height; if (height > maximumHeight) maximumHeight = height; heightBuffer[i] = height; cartographicScratch.longitude = lerp(west, east, u / maxShort); cartographicScratch.latitude = lerp(south, north, v / maxShort); cartographicScratch.height = height; ellipsoid.cartographicToCartesian(cartographicScratch, cartesian3Scratch); cartesianVertices.push(cartesian3Scratch.x); cartesianVertices.push(cartesian3Scratch.y); cartesianVertices.push(cartesian3Scratch.z); } const boundingSphere = BoundingSphere.fromVertices(cartesianVertices, Vector3.ZERO, 3); const orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid); const occluder = new EllipsoidalOccluder(ellipsoid); const horizonOcclusionPoint = occluder.computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid(boundingSphere.center, cartesianVertices, 3, boundingSphere.center, minimumHeight); const heightRange = maximumHeight - minimumHeight; const vertices = new Uint16Array(uBuffer.length + vBuffer.length + heightBuffer.length); for (i = 0; i < uBuffer.length; ++i) { vertices[i] = uBuffer[i]; } let start = uBuffer.length; for (i = 0; i < vBuffer.length; ++i) { vertices[start + i] = vBuffer[i]; } start += vBuffer.length; for (i = 0; i < heightBuffer.length; ++i) { vertices[start + i] = (maxShort * (heightBuffer[i] - minimumHeight)) / heightRange; } const indicesTypedArray = IndexDatatype.createTypedArray(uBuffer.length, indices); let encodedNormals; if (hasVertexNormals) { const normalArray = new Uint8Array(normalBuffer); encodedNormals = normalArray.buffer; } return { vertices: vertices.buffer, encodedNormals: encodedNormals, indices: indicesTypedArray.buffer, minimumHeight: minimumHeight, maximumHeight: maximumHeight, westIndices: westIndices, southIndices: southIndices, eastIndices: eastIndices, northIndices: northIndices, boundingSphere: boundingSphere, orientedBoundingBox: orientedBoundingBox, horizonOcclusionPoint: horizonOcclusionPoint, name: name, parameters: parameters } } function addClippedPolygon(uBuffer, vBuffer,heightBuffer, normalBuffer, indices, vertexMap, clipped, triangleVertices, hasVertexNormals) { if (clipped.length === 0) return; let numVertices = 0; let clippedIndex = 0; while (clippedIndex < clipped.length) { clippedIndex = polygonVertices[numVertices++].initializeFromClipResult(clipped, clippedIndex, triangleVertices); } for (let i = 0; i < numVertices; ++i) { const polygonVertex = polygonVertices[i]; if (!polygonVertex.isIndexed()) { const key = polygonVertex.getKey(); if (defined(vertexMap[key])) { polygonVertex.newIndex = vertexMap[key]; } else { const newIndex = uBuffer.length; uBuffer.push(polygonVertex.getU()); vBuffer.push(polygonVertex.getV()); heightBuffer.push(polygonVertex.getH()); if (hasVertexNormals) { normalBuffer.push(polygonVertex.getNormalX()); normalBuffer.push(polygonVertex.getNormalY()); } polygonVertex.newIndex = newIndex; vertexMap[key] = newIndex; } } else { polygonVertex.newIndex = vertexMap[polygonVertex.index]; polygonVertex.uBuffer = uBuffer; polygonVertex.vBuffer = vBuffer; polygonVertex.heightBuffer = heightBuffer; if (hasVertexNormals) { polygonVertex.normalBuffer = normalBuffer; } } } if (numVertices === 3) { // 三个点,组成一个三角形 indices.push(polygonVertices[0].newIndex); indices.push(polygonVertices[1].newIndex); indices.push(polygonVertices[2].newIndex); } else if (numVertices === 4) { // 四个点,组成两个三角形 indices.push(polygonVertices[0].newIndex); indices.push(polygonVertices[1].newIndex); indices.push(polygonVertices[2].newIndex); indices.push(polygonVertices[0].newIndex); indices.push(polygonVertices[2].newIndex); indices.push(polygonVertices[3].newIndex); } } function lerpOctEncodedNormal(vertex, result) { ++depth; let first = cartesianScratch1[depth]; let second = cartesianScratch2[depth]; first = AttributeCompression.octDecode(vertex.first.getNormalX(), vertex.first.getNormalY(), first); second = AttributeCompression.octDecode(vertex.second.getNormalX(), vertex.second.getNormalY(), second); cartesian3Scratch = lerp(first, second, vertex.ratio); cartesian3Scratch.normalize(); AttributeCompression.octDecode(cartesian3Scratch, result); --depth; return result; } 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( upsampleQuantizedTerrainMesh(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); }) }