You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2762 lines
84 KiB
2762 lines
84 KiB
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); |
|
}) |
|
}
|
|
|