2020-07-21 21:26:26 +08:00

875 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Angle from '../geom/Angle';
import ArgumentError from '../error/ArgumentError';
import Logger from '../util/Logger';
import Rectangle from '../geom/Rectangle';
import Vec3 from '../geom/Vec3';
/**
* Provides math constants and functions.
* @exports WWMath
*/
var WWMath = {
/**
* Returns a number within the range of a specified minimum and maximum.
* @param {Number} value The value to clamp.
* @param {Number} minimum The minimum value to return.
* @param {Number} maximum The maximum value to return.
* @returns {Number} The minimum value if the specified value is less than the minimum, the maximum value if
* the specified value is greater than the maximum, otherwise the value specified is returned.
*/
clamp: function (value, minimum, maximum) {
return value < minimum ? minimum : value > maximum ? maximum : value;
},
/**
* Computes a number between two numbers.
* @param amount {Number} The relative distance between the numbers at which to compute the new number. This
* should normally be a number between 0 and 1 but whatever number is specified is applied.
* @param {Number} value1 The first number.
* @param {Number} value2 The second number.
* @returns {Number} the computed value.
*/
interpolate: function (amount, value1, value2) {
return (1 - amount) * value1 + amount * value2;
},
/**
* Returns the cube root of a specified value.
* @param {Number} x The value whose cube root is computed.
* @returns {Number} The cube root of the specified number.
*/
cbrt: function (x) {
// Use the built-in version if it exists. cbrt() is defined in ECMA6.
if (typeof Math.cbrt === 'function') {
return Math.cbrt(x);
} else {
return Math.pow(x, 1 / 3);
}
},
/**
* Computes the Cartesian intersection point of a specified line with an ellipsoid.
* @param {Line} line The line for which to compute the intersection.
* @param {Number} equatorialRadius The ellipsoid's major radius.
* @param {Number} polarRadius The ellipsoid's minor radius.
* @param {Vec3} result A pre-allocated Vec3 instance in which to return the computed point.
* @returns {boolean} true if the line intersects the ellipsoid, otherwise false
* @throws {ArgumentError} If the specified line or result is null or undefined.
* @deprecated utilize the Globe.intersectsLine method attached implementation
*/
computeEllipsoidalGlobeIntersection: function (line, equatorialRadius, polarRadius, result) {
if (!line) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeEllipsoidalGlobeIntersection", "missingLine"));
}
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeEllipsoidalGlobeIntersection", "missingResult"));
}
// Taken from "Mathematics for 3D Game Programming and Computer Graphics, Second Edition", Section 5.2.3.
//
// Note that the parameter n from in equations 5.70 and 5.71 is omitted here. For an ellipsoidal globe this
// parameter is always 1, so its square and its product with any other value simplifies to the identity.
var vx = line.direction[0],
vy = line.direction[1],
vz = line.direction[2],
sx = line.origin[0],
sy = line.origin[1],
sz = line.origin[2],
m = equatorialRadius / polarRadius, // ratio of the x semi-axis length to the y semi-axis length
m2 = m * m,
r2 = equatorialRadius * equatorialRadius, // nominal radius squared
a = vx * vx + m2 * vy * vy + vz * vz,
b = 2 * (sx * vx + m2 * sy * vy + sz * vz),
c = sx * sx + m2 * sy * sy + sz * sz - r2,
d = b * b - 4 * a * c, // discriminant
t;
if (d < 0) {
return false;
}
else {
t = (-b - Math.sqrt(d)) / (2 * a);
result[0] = sx + vx * t;
result[1] = sy + vy * t;
result[2] = sz + vz * t;
return true;
}
},
/**
* Computes the Cartesian intersection point of a specified line with a triangle.
* @param {Line} line The line for which to compute the intersection.
* @param {Vec3} vertex0 The triangle's first vertex.
* @param {Vec3} vertex1 The triangle's second vertex.
* @param {Vec3} vertex2 The triangle's third vertex.
* @param {Vec3} result A pre-allocated Vec3 instance in which to return the computed point.
* @returns {boolean} true if the line intersects the triangle, otherwise false
* @throws {ArgumentError} If the specified line, vertex or result is null or undefined.
*/
computeTriangleIntersection: function (line, vertex0, vertex1, vertex2, result) {
if (!line) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeTriangleIntersection", "missingLine"));
}
if (!vertex0 || !vertex1 || !vertex2) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeTriangleIntersection", "missingVertex"));
}
if (!result) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeTriangleIntersection", "missingResult"));
}
// Taken from Moller and Trumbore
// https://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf
var vx = line.direction[0],
vy = line.direction[1],
vz = line.direction[2],
sx = line.origin[0],
sy = line.origin[1],
sz = line.origin[2],
EPSILON = 0.00001;
// find vectors for two edges sharing point a: vertex1 - vertex0 and vertex2 - vertex0
var edge1x = vertex1[0] - vertex0[0],
edge1y = vertex1[1] - vertex0[1],
edge1z = vertex1[2] - vertex0[2],
edge2x = vertex2[0] - vertex0[0],
edge2y = vertex2[1] - vertex0[1],
edge2z = vertex2[2] - vertex0[2];
// Compute cross product of line direction and edge2
var px = vy * edge2z - vz * edge2y,
py = vz * edge2x - vx * edge2z,
pz = vx * edge2y - vy * edge2x;
// Get determinant
var det = edge1x * px + edge1y * py + edge1z * pz; // edge1 dot p
if (det > -EPSILON && det < EPSILON) { // if det is near zero then ray lies in plane of triangle
return false;
}
var inv_det = 1.0 / det;
// Compute distance for vertex A to ray origin: origin - vertex0
var tx = sx - vertex0[0],
ty = sy - vertex0[1],
tz = sz - vertex0[2];
// Calculate u parameter and test bounds: 1/det * t dot p
var u = inv_det * (tx * px + ty * py + tz * pz);
if (u < -EPSILON || u > 1 + EPSILON) {
return false;
}
// Prepare to test v parameter: t cross edge1
var qx = ty * edge1z - tz * edge1y,
qy = tz * edge1x - tx * edge1z,
qz = tx * edge1y - ty * edge1x;
// Calculate v parameter and test bounds: 1/det * dir dot q
var v = inv_det * (vx * qx + vy * qy + vz * qz);
if (v < -EPSILON || u + v > 1 + EPSILON) {
return false;
}
// Calculate the point of intersection on the line: t = 1/det * edge2 dot q
var t = inv_det * (edge2x * qx + edge2y * qy + edge2z * qz);
if (t < 0) {
return false;
} else {
result[0] = sx + vx * t;
result[1] = sy + vy * t;
result[2] = sz + vz * t;
return true;
}
},
computeIndexedTrianglesIntersection: function (line, points, indices, results) {
if (!line) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeIndexedTrianglesIntersection", "missingLine"));
}
if (!points) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeIndexedTrianglesIntersection", "missingPoints"));
}
if (!indices) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeIndexedTrianglesIntersection", "missingIndices"));
}
if (!results) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeIndexedTrianglesIntersection", "missingResults"));
}
var v0 = new Vec3(0, 0, 0),
v1 = new Vec3(0, 0, 0),
v2 = new Vec3(0, 0, 0),
iPoint = new Vec3(0, 0, 0);
for (var i = 0, len = indices.length; i < len; i += 3) {
var i0 = 3 * indices[i],
i1 = 3 * indices[i + 1],
i2 = 3 * indices[i + 2];
v0[0] = points[i0];
v0[1] = points[i0 + 1];
v0[2] = points[i0 + 2];
v1[0] = points[i1];
v1[1] = points[i1 + 1];
v1[2] = points[i1 + 2];
v2[0] = points[i2];
v2[1] = points[i2 + 1];
v2[2] = points[i2 + 2];
if (WWMath.computeTriangleIntersection(line, v0, v1, v2, iPoint)) {
results.push(iPoint);
iPoint = new Vec3(0, 0, 0);
}
}
return results.length > 0;
},
/**
* Computes the Cartesian intersection points of a specified line with a triangle strip. The triangle strip
* is specified by a list of vertex points and a list of indices indicating the triangle strip tessellation
* of those vertices. The triangle strip indices are interpreted in the same manner as WebGL, where each
* index indicates a vertex position rather than an actual index into the points array (e.g. a triangle
* strip index of 1 indicates the XYZ tuple starting at array index 3). This is equivalent to calling
* computeTriangleIntersection for each individual triangle in the triangle strip, but is potentially much
* more efficient.
* @param {Line} line The line for which to compute the intersection.
* @param {Array} points The list of vertex points, organized as a list of tightly-packed XYZ tuples.
* @param {Array} indices The list of triangle strip indices, organized as a list of vertex positions.
* @param {Array} results A pre-allocated array instance in which to return the intersection points as
* {@link Vec3} instances.
* @throws {ArgumentError} If the specified line, points, indices or results is null or undefined.
*/
computeTriStripIntersections: function (line, points, indices, results) {
if (!line) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeTriStripIntersections", "missingLine"));
}
if (!points) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeTriStripIntersections", "missingPoints"));
}
if (!indices) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeTriStripIntersections", "missingIndices"));
}
if (!results) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"computeTriStripIntersections", "missingResults"));
}
// Taken from Moller and Trumbore
// https://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf
// Adapted from the original ray-triangle intersection algorithm to optimize for ray-triangle strip
// intersection. We optimize by reusing constant terms, replacing use of Vec3 with inline primitives,
// and exploiting the triangle strip organization to reuse computations common to adjacent triangles.
// These optimizations reduce worst-case terrain picking performance by approximately 50% in Chrome on a
// 2010 iMac and a Nexus 9.
var vx = line.direction[0],
vy = line.direction[1],
vz = line.direction[2],
sx = line.origin[0],
sy = line.origin[1],
sz = line.origin[2],
vert0x, vert0y, vert0z,
vert1x, vert1y, vert1z,
vert2x, vert2y, vert2z,
edge1x, edge1y, edge1z,
edge2x, edge2y, edge2z,
px, py, pz,
tx, ty, tz,
qx, qy, qz,
u, v, t,
det, inv_det,
index,
EPSILON = 0.00001;
// Get the triangle strip's first vertex.
index = 3 * indices[0];
vert1x = points[index++];
vert1y = points[index++];
vert1z = points[index];
// Get the triangle strip's second vertex.
index = 3 * indices[1];
vert2x = points[index++];
vert2y = points[index++];
vert2z = points[index];
// Compute the intersection of each triangle with the specified ray.
for (var i = 2, len = indices.length; i < len; i++) {
// Move the last two vertices into the first two vertices. This takes advantage of the triangle
// strip's structure and avoids redundant reads from points and indices. During the first
// iteration this places the triangle strip's first three vertices in vert0, vert1 and vert2,
// respectively.
vert0x = vert1x;
vert0y = vert1y;
vert0z = vert1z;
vert1x = vert2x;
vert1y = vert2y;
vert1z = vert2z;
// Get the triangle strip's next vertex.
index = 3 * indices[i];
vert2x = points[index++];
vert2y = points[index++];
vert2z = points[index];
// find vectors for two edges sharing point a: vert1 - vert0 and vert2 - vert0
edge1x = vert1x - vert0x;
edge1y = vert1y - vert0y;
edge1z = vert1z - vert0z;
edge2x = vert2x - vert0x;
edge2y = vert2y - vert0y;
edge2z = vert2z - vert0z;
// Compute cross product of line direction and edge2
px = vy * edge2z - vz * edge2y;
py = vz * edge2x - vx * edge2z;
pz = vx * edge2y - vy * edge2x;
// Get determinant
det = edge1x * px + edge1y * py + edge1z * pz; // edge1 dot p
if (det > -EPSILON && det < EPSILON) { // if det is near zero then ray lies in plane of triangle
continue;
}
inv_det = 1.0 / det;
// Compute distance for vertex A to ray origin: origin - vert0
tx = sx - vert0x;
ty = sy - vert0y;
tz = sz - vert0z;
// Calculate u parameter and test bounds: 1/det * t dot p
u = inv_det * (tx * px + ty * py + tz * pz);
if (u < -EPSILON || u > 1 + EPSILON) {
continue;
}
// Prepare to test v parameter: tvec cross edge1
qx = ty * edge1z - tz * edge1y;
qy = tz * edge1x - tx * edge1z;
qz = tx * edge1y - ty * edge1x;
// Calculate v parameter and test bounds: 1/det * dir dot q
v = inv_det * (vx * qx + vy * qy + vz * qz);
if (v < -EPSILON || u + v > 1 + EPSILON) {
continue;
}
// Calculate the point of intersection on the line: t = 1/det * edge2 dot q
t = inv_det * (edge2x * qx + edge2y * qy + edge2z * qz);
if (t >= 0) {
results.push(new Vec3(sx + vx * t, sy + vy * t, sz + vz * t));
}
}
},
/**
* Computes the absolute value of a specified value.
* @param {Number} a The value whose absolute value to compute.
* @returns {Number} The absolute value of the specified number.
*/
fabs: function (a) {
return a >= 0 ? a : -a;
},
/**
* Computes the floating-point modulus of a specified number.
* @param {Number} number The number whose modulus to compute.
* @param {Number} modulus The modulus.
* @returns {Number} The remainder after dividing the number by the modulus: number % modulus.
*/
fmod: function (number, modulus) {
return modulus === 0 ? 0 : number - Math.floor(number / modulus) * modulus;
},
/**
* Returns the fractional part of a specified number
* @param {Number} number The number whose fractional part to compute.
* @returns {Number} The fractional part of the specified number: number - floor(number).
*/
fract: function (number) {
return number - Math.floor(number);
},
/**
* Returns the integer modulus of a specified number. This differs from the % operator in that
* the result is always positive when the modulus is positive. For example -1 % 10 = -1,
* whereas mod(-1, 10) = 1.
* @param {Number} number The number whose modulus to compute.
* @param {Number} modulus The modulus.
* @returns {Number} The remainder after dividing the number by the modulus.
*/
mod: function (number, modulus) {
return (number % modulus + modulus) % modulus;
},
/**
* Returns the maximum of two specified numbers.
* @param {Number} value1 The first value to compare.
* @param {Number} value2 The second value to compare.
* @returns {Number} The maximum of the two specified values.
*/
max: function (value1, value2) {
return value1 > value2 ? value1 : value2;
},
/**
* Computes the axes of a local coordinate system on the specified globe, placing the resultant axes in the specified
* axis arguments.
*
* Upon return the specified axis arguments contain three orthogonal axes identifying the X, Y, and Z axes. Each
* axis has unit length.
*
* The local coordinate system is defined such that the Z axis maps to the globe's surface normal at the point, the
* Y axis maps to the north pointing tangent, and the X axis maps to the east pointing tangent.
*
* @param {Vec3} origin The local coordinate system origin, in model coordinates.
* @param {Globe} globe The globe the coordinate system is relative to.
* @param {Vec3} xAxisResult A pre-allocated Vec3 in which to return the computed X axis.
* @param {Vec3} yAxisResult A pre-allocated Vec3 in which to return the computed Y axis.
* @param {Vec3} zAxisResult A pre-allocated Vec3 in which to return the computed Z axis.
* @throws {ArgumentError} If any argument is null or undefined.
*/
localCoordinateAxesAtPoint: function (origin, globe, xAxisResult, yAxisResult, zAxisResult) {
if (!origin) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"localCoordinateAxesAtPoint", "missingVector"));
}
if (!globe) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"localCoordinateAxesAtPoint", "missingGlobe"));
}
if (!xAxisResult || !yAxisResult || !zAxisResult) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"localCoordinateAxesAtPoint", "missingResult"));
}
var x = origin[0],
y = origin[1],
z = origin[2];
// Compute the z axis from the surface normal in model coordinates. This axis is used to determine the other two
// axes, and is the only constant in the computations below.
globe.surfaceNormalAtPoint(x, y, z, zAxisResult);
// Compute the y axis from the north pointing tangent in model coordinates. This axis is known to be orthogonal to
// the z axis, and is therefore used to compute the x axis.
globe.northTangentAtPoint(x, y, z, yAxisResult);
// Compute the x axis as the cross product of the y and z axes. This ensures that the x and z axes are orthogonal.
xAxisResult.set(yAxisResult[0], yAxisResult[1], yAxisResult[2]);
xAxisResult.cross(zAxisResult);
xAxisResult.normalize();
// Re-compute the y axis as the cross product of the z and x axes. This ensures that all three axes are orthogonal.
// Though the initial y axis computed above is likely to be very nearly orthogonal, we re-compute it using cross
// products to reduce the effect of floating point rounding errors caused by working with Earth sized coordinates.
yAxisResult.set(zAxisResult[0], zAxisResult[1], zAxisResult[2]);
yAxisResult.cross(xAxisResult);
yAxisResult.normalize();
},
/**
* Computes the distance to a globe's horizon from a viewer at a given altitude.
*
* Only the globe's ellipsoid is considered; terrain height is not incorporated. This returns zero if the radius is zero
* or if the altitude is less than or equal to zero.
*
* @param {Number} radius The globe's radius, in meters.
* @param {Number} altitude The viewer's altitude above the globe, in meters.
* @returns {Number} The distance to the horizon, in model coordinates.
* @throws {ArgumentError} If the specified globe radius is negative.
*/
horizonDistanceForGlobeRadius: function (radius, altitude) {
if (radius < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
"horizontalDistanceForGlobeRadius", "The specified globe radius is negative."));
}
return radius > 0 && altitude > 0 ? Math.sqrt(altitude * (2 * radius + altitude)) : 0;
},
/**
* Computes the near clip distance that corresponds to a specified far clip distance and resolution at the far clip
* plane.
*
* This computes a near clip distance appropriate for use in [perspectiveFrustumRect]{@link WWMath#perspectiveFrustumRectangle}
* and [setToPerspectiveProjection]{@link Matrix#setToPerspectiveProjection}. This returns zero if either the distance or the
* resolution are zero.
*
* @param {Number} farDistance The far clip distance, in meters.
* @param {Number} farResolution The depth resolution at the far clip plane, in meters.
* @param {Number} depthBits The number of bit-planes in the depth buffer.
* @returns {Number} The near clip distance, in meters.
* @throws {ArgumentError} If either the distance or resolution is negative, or if the depth bits is less
* than one.
*/
perspectiveNearDistanceForFarDistance: function (farDistance, farResolution, depthBits) {
if (farDistance < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistanceForFarDistance",
"The specified distance is negative."));
}
if (farResolution < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistanceForFarDistance",
"The specified resolution is negative."));
}
if (depthBits < 1) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistanceForFarDistance",
"The specified depth bits is negative."));
}
var maxDepthValue = (1 << depthBits) - 1;
return farDistance / (maxDepthValue / (1 - farResolution / farDistance) - maxDepthValue + 1);
},
/**
* Computes the maximum near clip distance for a perspective projection that avoids clipping an object at a
* given distance from the eye point.
* <p/>
* This computes a near clip distance appropriate for use in perspectiveFrustumRect and
* Matrix.setToPerspectiveProjection. The given distance should specify the smallest distance between the
* eye and the object being viewed, but may be an approximation if an exact distance is not required.
*
* @param {Number} viewportWidth The viewport width, in screen coordinates.
* @param {Number} viewportHeight The viewport height, in screen coordinates.
* @param {Number} distanceToSurface The distance from the perspective eye point to the nearest object, in
* meters.
* @returns {Number} The maximum near clip distance, in meters.
* @throws {ArgumentError} If the specified width or height is less than or equal to zero, or if the
* specified distance is negative.
*/
perspectiveNearDistance: function (viewportWidth, viewportHeight, distanceToSurface) {
if (viewportWidth <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistance",
"invalidWidth"));
}
if (viewportHeight <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistance",
"invalidHeight"));
}
if (distanceToSurface < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveNearDistance",
"The specified distance is negative."));
}
// Compute the maximum near clip distance that avoids clipping an object at the specified distance from
// the eye. Since the furthest points on the near clip rectangle are the four corners, we compute a near
// distance that puts any one of these corners exactly at the given distance. The distance to one of the
// four corners can be expressed in terms of the near clip distance, given distance to a corner 'd',
// near distance 'n', and aspect ratio 'a':
//
// d*d = x*x + y*y + z*z
// d*d = (n*n/4 * a*a) + (n*n/4) + (n*n)
//
// Extracting 'n*n/4' from the right hand side gives:
//
// d*d = (n*n/4) * (a*a + 1 + 4)
// d*d = (n*n/4) * (a*a + 5)
//
// Finally, solving for 'n' gives:
//
// n*n = 4 * d*d / (a*a + 5)
// n = 2 * d / sqrt(a*a + 5)
// Assumes a 45 degree horizontal field of view.
var aspectRatio = viewportHeight / viewportWidth;
return 2 * distanceToSurface / Math.sqrt(aspectRatio * aspectRatio + 5);
},
/**
* Computes the coordinates of a rectangle carved out of a perspective projection's frustum at a given
* distance in model coordinates. This returns an empty rectangle if the specified distance is zero.
*
* @param {Number} viewportWidth The viewport width, in screen coordinates.
* @param {Number} viewportHeight The viewport height, in screen coordinates.
* @param {Number} distance The distance along the negative Z axis, in model coordinates.
* @returns {Rectangle} The frustum rectangle, in model coordinates.
* @throws {ArgumentError} If the specified width or height is less than or equal to zero, or if the
* specified distance is negative.
*/
perspectiveFrustumRectangle: function (viewportWidth, viewportHeight, distance) {
if (viewportWidth <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveFrustumRectangle",
"invalidWidth"));
}
if (viewportHeight <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveFrustumRectangle",
"invalidHeight"));
}
if (distance < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectiveFrustumRectangle",
"The specified distance is negative."));
}
// Assumes a 45 degree horizontal field of view.
var width = distance,
height = distance * viewportHeight / viewportWidth;
return new Rectangle(-width / 2, -height / 2, width, height);
},
/**
* Computes the vertical size of a pixel in model coordinates at a given distance from the eye point in a
* perspective projection. This returns zero if the specified distance is zero. The returned size is
* undefined if the distance is less than zero.
* <p/>
* This method assumes the model of a screen composed of rectangular pixels, where pixel coordinates denote
* infinitely thin space between pixels. The units of the returned size are in model coordinates per pixel
* (usually meters per pixel).
*
* @param {Number} viewportWidth The viewport width, in screen coordinates.
* @param {Number} viewportHeight The viewport height, in screen coordinates.
* @param {Number} distance The distance from the perspective eye point at which to determine pixel size, in
* model coordinates.
* @returns {Number} The pixel size at the specified distance from the eye point, in model coordinates per
* pixel.
* @throws {ArgumentError} If the specified width or height is less than or equal to zero, or if the
* specified distance is negative.
*/
perspectivePixelSize: function (viewportWidth, viewportHeight, distance) {
if (viewportWidth <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectivePixelSize",
"invalidWidth"));
}
if (viewportHeight <= 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectivePixelSize",
"invalidHeight"));
}
if (distance < 0) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "perspectivePixelSize",
"The specified distance is negative."));
}
var frustumHeight = WWMath.perspectiveFrustumRectangle(viewportWidth, viewportHeight, distance).height;
return frustumHeight / viewportHeight;
},
/**
* Computes the bounding rectangle for a unit quadrilateral after applying a transformation matrix to that
* quadrilateral.
* @param {Matrix} transformMatrix The matrix to apply to the unit quadrilateral.
* @returns {Rectangle} The computed bounding rectangle.
*/
boundingRectForUnitQuad: function (transformMatrix) {
if (!transformMatrix) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath", "boundingRectForUnitQuad",
"missingMatrix"));
}
var m = transformMatrix,
// transform of (0, 0)
x1 = m[3],
y1 = m[7],
// transform of (1, 0)
x2 = m[0] + m[3],
y2 = m[4] + m[7],
// transform of (0, 1)
x3 = m[1] + m[3],
y3 = m[5] + m[7],
// transform of (1, 1)
x4 = m[0] + m[1] + m[3],
y4 = m[4] + m[5] + m[7],
minX = Math.min(Math.min(x1, x2), Math.min(x3, x4)),
maxX = Math.max(Math.max(x1, x2), Math.max(x3, x4)),
minY = Math.min(Math.min(y1, y2), Math.min(y3, y4)),
maxY = Math.max(Math.max(y1, y2), Math.max(y3, y4));
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
},
/**
* Indicates whether a specified value is a power of two.
* @param {Number} value The value to test.
* @returns {boolean} <code>true</code> if the specified value is a power of two,
* otherwise <code>false</code>.
*/
isPowerOfTwo: function (value) {
return value != 0 && (value & value - 1) === 0;
},
/**
* Determine the sign of a number.
* @param {Number} value The value to determine the sign of.
* @returns {Number} 1, -1, or 0, depending on the sign of the value.
*/
signum: function (value) {
return value > 0 ? 1 : value < 0 ? -1 : 0;
},
/**
* Calculates the Gudermannian inverse used to unproject Mercator projections.
* @param {Number} latitude The latitude in degrees.
* @returns {Number} The Gudermannian inverse for the specified latitude.
*/
gudermannianInverse: function (latitude) {
return Math.log(Math.tan(Math.PI / 4 + latitude * Angle.DEGREES_TO_RADIANS / 2)) / Math.PI;
},
epsg3857ToEpsg4326: function (easting, northing) {
var r = 6.3781e6,
latRadians = Math.PI / 2 - 2 * Math.atan(Math.exp(-northing / r)),
lonRadians = easting / r;
return [
WWMath.clamp(latRadians * Angle.RADIANS_TO_DEGREES, -90, 90),
WWMath.clamp(lonRadians * Angle.RADIANS_TO_DEGREES, -180, 180)
];
},
/**
* Returns the value that is the nearest power of 2 less than or equal to the given value.
* @param {Number} value the reference value. The power of 2 returned is less than or equal to this value.
* @returns {Number} the value that is the nearest power of 2 less than or equal to the reference value
*/
powerOfTwoFloor: function (value) {
var power = Math.floor(Math.log(value) / Math.log(2));
return Math.pow(2, power);
},
/**
* Restricts an angle to the range [0, 360] degrees, wrapping angles outside the range.
* Wrapping takes place as though traversing the edge of a unit circle;
* angles less than 0 wrap back to 360, while angles greater than 360 wrap back to 0.
*
* @param {Number} degrees the angle to wrap in degrees
*
* @return {Number} the specified angle wrapped to [0, 360] degrees
*/
normalizeAngle360: function (degrees) {
var angle = degrees % 360;
return angle >= 0 ? angle : angle < 0 ? 360 + angle : 360 - angle;
},
/**
* 墨卡托投影(弧度)
* @param {Number} lat 纬度(弧度)
* @returns {Number} 投影纬度(弧度)
* @see https://github.com/d3/d3-geo/blob/master/src/projection/mercator.js
*/
_mercatorLat: function (lat) {
return Math.log(Math.tan((Math.PI / 2 + lat) / 2));
},
/**
* 墨卡托投影(角度)
* @param {Number} lat 纬度(角度)
* @returns {Number} 投影纬度(角度)
* @see https://github.com/d3/d3-geo/blob/master/src/projection/mercator.js
*/
mercatorLat: function (lat) {
return this._mercatorLat(lat * Angle.DEGREES_TO_RADIANS) * angle.RADIANS_TO_DEGREES;
},
/**
* 墨卡托投影反算(弧度)
* @param {Number} y 墨卡托投影Y坐标
* @returns {Number} 纬度(弧度)
* @see https://github.com/d3/d3-geo/blob/master/src/projection/mercator.js
*/
_mercatorLatInvert: function (y) {
return 2 * Math.atan(Math.exp(y)) - Math.PI / 2;
},
/**
* 墨卡托投影反算(角度)
* @param {Number} y 墨卡托投影Y坐标角度
* @returns {Number} 纬度(角度)
* @see https://github.com/d3/d3-geo/blob/master/src/projection/mercator.js
*/
mercatorLatInvert: function (y) {
return this._mercatorLatInvert(y * Angle.DEGREES_TO_RADIANS) * Angle.RADIANS_TO_DEGREES;
},
/**
* 计算两个经纬度之间距离(弧度)
* @param {Number} lon1 经度1(弧度)
* @param {Number} lat1 纬度1(弧度)
* @param {Number} lon2 经度2(弧度)
* @param {Number} lat2 纬度2(弧度)
* @returns {Number} 距离(米)
* @see https://www.xuebuyuan.com/2173606.html
*/
_getDistance: function (lon1, lat1, lon2, lat2) {
return 2 * 6378137 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
},
/**
* 计算两个经纬度之间距离(角度)
* @param {*} lon1 经度1(角度)
* @param {*} lat1 纬度1(角度)
* @param {*} lon2 经度2(角度)
* @param {*} lat2 纬度2(角度)
* @returns {Number} 距离(米)
* @see https://www.xuebuyuan.com/2173606.html
*/
getDistance: function (lon1, lat1, lon2, lat2) {
lon1 *= Angle.DEGREES_TO_RADIANS;
lat1 *= Angle.DEGREES_TO_RADIANS;
lon2 *= Angle.DEGREES_TO_RADIANS;
lat2 *= Angle.DEGREES_TO_RADIANS;
return this._getDistance(lon1, lat1, lon2, lat2);
},
// this.mercatorLatInvert(180)
MAX_LAT: 180
};
export default WWMath;