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.
2763 lines
84 KiB
2763 lines
84 KiB
|
1 year ago
|
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);
|
||
|
|
})
|
||
|
|
}
|