mirror of https://github.com/CesiumGS/cesium.git
1302 lines
34 KiB
JavaScript
1302 lines
34 KiB
JavaScript
import ArcType from "./ArcType.js";
|
|
import arrayRemoveDuplicates from "./arrayRemoveDuplicates.js";
|
|
import Cartesian2 from "./Cartesian2.js";
|
|
import Cartesian3 from "./Cartesian3.js";
|
|
import Cartographic from "./Cartographic.js";
|
|
import ComponentDatatype from "./ComponentDatatype.js";
|
|
import defined from "./defined.js";
|
|
import Ellipsoid from "./Ellipsoid.js";
|
|
import EllipsoidRhumbLine from "./EllipsoidRhumbLine.js";
|
|
import Geometry from "./Geometry.js";
|
|
import GeometryAttribute from "./GeometryAttribute.js";
|
|
import GeometryAttributes from "./GeometryAttributes.js";
|
|
import GeometryPipeline from "./GeometryPipeline.js";
|
|
import IndexDatatype from "./IndexDatatype.js";
|
|
import IntersectionTests from "./IntersectionTests.js";
|
|
import CesiumMath from "./Math.js";
|
|
import Matrix3 from "./Matrix3.js";
|
|
import Plane from "./Plane.js";
|
|
import PolygonHierarchy from "./PolygonHierarchy.js";
|
|
import PolygonPipeline from "./PolygonPipeline.js";
|
|
import PrimitiveType from "./PrimitiveType.js";
|
|
import Quaternion from "./Quaternion.js";
|
|
import Queue from "./Queue.js";
|
|
import WindingOrder from "./WindingOrder.js";
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
const PolygonGeometryLibrary = {};
|
|
|
|
PolygonGeometryLibrary.computeHierarchyPackedLength = function (
|
|
polygonHierarchy,
|
|
CartesianX,
|
|
) {
|
|
let numComponents = 0;
|
|
const stack = [polygonHierarchy];
|
|
while (stack.length > 0) {
|
|
const hierarchy = stack.pop();
|
|
if (!defined(hierarchy)) {
|
|
continue;
|
|
}
|
|
|
|
numComponents += 2;
|
|
|
|
const positions = hierarchy.positions;
|
|
const holes = hierarchy.holes;
|
|
|
|
if (defined(positions) && positions.length > 0) {
|
|
numComponents += positions.length * CartesianX.packedLength;
|
|
}
|
|
|
|
if (defined(holes)) {
|
|
const length = holes.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
stack.push(holes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return numComponents;
|
|
};
|
|
|
|
PolygonGeometryLibrary.packPolygonHierarchy = function (
|
|
polygonHierarchy,
|
|
array,
|
|
startingIndex,
|
|
CartesianX,
|
|
) {
|
|
const stack = [polygonHierarchy];
|
|
while (stack.length > 0) {
|
|
const hierarchy = stack.pop();
|
|
if (!defined(hierarchy)) {
|
|
continue;
|
|
}
|
|
|
|
const positions = hierarchy.positions;
|
|
const holes = hierarchy.holes;
|
|
|
|
array[startingIndex++] = defined(positions) ? positions.length : 0;
|
|
array[startingIndex++] = defined(holes) ? holes.length : 0;
|
|
|
|
if (defined(positions)) {
|
|
const positionsLength = positions.length;
|
|
for (
|
|
let i = 0;
|
|
i < positionsLength;
|
|
++i, startingIndex += CartesianX.packedLength
|
|
) {
|
|
CartesianX.pack(positions[i], array, startingIndex);
|
|
}
|
|
}
|
|
|
|
if (defined(holes)) {
|
|
const holesLength = holes.length;
|
|
for (let j = 0; j < holesLength; ++j) {
|
|
stack.push(holes[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return startingIndex;
|
|
};
|
|
|
|
PolygonGeometryLibrary.unpackPolygonHierarchy = function (
|
|
array,
|
|
startingIndex,
|
|
CartesianX,
|
|
) {
|
|
const positionsLength = array[startingIndex++];
|
|
const holesLength = array[startingIndex++];
|
|
|
|
const positions = new Array(positionsLength);
|
|
const holes = holesLength > 0 ? new Array(holesLength) : undefined;
|
|
|
|
for (
|
|
let i = 0;
|
|
i < positionsLength;
|
|
++i, startingIndex += CartesianX.packedLength
|
|
) {
|
|
positions[i] = CartesianX.unpack(array, startingIndex);
|
|
}
|
|
|
|
for (let j = 0; j < holesLength; ++j) {
|
|
holes[j] = PolygonGeometryLibrary.unpackPolygonHierarchy(
|
|
array,
|
|
startingIndex,
|
|
CartesianX,
|
|
);
|
|
startingIndex = holes[j].startingIndex;
|
|
delete holes[j].startingIndex;
|
|
}
|
|
|
|
return {
|
|
positions: positions,
|
|
holes: holes,
|
|
startingIndex: startingIndex,
|
|
};
|
|
};
|
|
|
|
const distance2DScratch = new Cartesian2();
|
|
function getPointAtDistance2D(p0, p1, distance, length) {
|
|
Cartesian2.subtract(p1, p0, distance2DScratch);
|
|
Cartesian2.multiplyByScalar(
|
|
distance2DScratch,
|
|
distance / length,
|
|
distance2DScratch,
|
|
);
|
|
Cartesian2.add(p0, distance2DScratch, distance2DScratch);
|
|
return [distance2DScratch.x, distance2DScratch.y];
|
|
}
|
|
|
|
const distanceScratch = new Cartesian3();
|
|
function getPointAtDistance(p0, p1, distance, length) {
|
|
Cartesian3.subtract(p1, p0, distanceScratch);
|
|
Cartesian3.multiplyByScalar(
|
|
distanceScratch,
|
|
distance / length,
|
|
distanceScratch,
|
|
);
|
|
Cartesian3.add(p0, distanceScratch, distanceScratch);
|
|
return [distanceScratch.x, distanceScratch.y, distanceScratch.z];
|
|
}
|
|
|
|
PolygonGeometryLibrary.subdivideLineCount = function (p0, p1, minDistance) {
|
|
const distance = Cartesian3.distance(p0, p1);
|
|
const n = distance / minDistance;
|
|
const countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n)));
|
|
return Math.pow(2, countDivide);
|
|
};
|
|
|
|
const scratchCartographic0 = new Cartographic();
|
|
const scratchCartographic1 = new Cartographic();
|
|
const scratchCartographic2 = new Cartographic();
|
|
const scratchCartesian0 = new Cartesian3();
|
|
const scratchRhumbLine = new EllipsoidRhumbLine();
|
|
PolygonGeometryLibrary.subdivideRhumbLineCount = function (
|
|
ellipsoid,
|
|
p0,
|
|
p1,
|
|
minDistance,
|
|
) {
|
|
const c0 = ellipsoid.cartesianToCartographic(p0, scratchCartographic0);
|
|
const c1 = ellipsoid.cartesianToCartographic(p1, scratchCartographic1);
|
|
const rhumb = new EllipsoidRhumbLine(c0, c1, ellipsoid);
|
|
const n = rhumb.surfaceDistance / minDistance;
|
|
const countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n)));
|
|
return Math.pow(2, countDivide);
|
|
};
|
|
|
|
/**
|
|
* Subdivides texture coordinates based on the subdivision of the associated world positions.
|
|
*
|
|
* @param {Cartesian2} t0 First texture coordinate.
|
|
* @param {Cartesian2} t1 Second texture coordinate.
|
|
* @param {Cartesian3} p0 First world position.
|
|
* @param {Cartesian3} p1 Second world position.
|
|
* @param {number} minDistance Minimum distance for a segment.
|
|
* @param {Cartesian2[]} result The subdivided texture coordinates.
|
|
*
|
|
* @private
|
|
*/
|
|
PolygonGeometryLibrary.subdivideTexcoordLine = function (
|
|
t0,
|
|
t1,
|
|
p0,
|
|
p1,
|
|
minDistance,
|
|
result,
|
|
) {
|
|
// Compute the number of subdivisions.
|
|
const subdivisions = PolygonGeometryLibrary.subdivideLineCount(
|
|
p0,
|
|
p1,
|
|
minDistance,
|
|
);
|
|
|
|
// Compute the distance between each subdivided point.
|
|
const length2D = Cartesian2.distance(t0, t1);
|
|
const distanceBetweenCoords = length2D / subdivisions;
|
|
|
|
// Resize the result array.
|
|
const texcoords = result;
|
|
texcoords.length = subdivisions * 2;
|
|
|
|
// Compute texture coordinates using linear interpolation.
|
|
let index = 0;
|
|
for (let i = 0; i < subdivisions; i++) {
|
|
const t = getPointAtDistance2D(t0, t1, i * distanceBetweenCoords, length2D);
|
|
texcoords[index++] = t[0];
|
|
texcoords[index++] = t[1];
|
|
}
|
|
|
|
return texcoords;
|
|
};
|
|
|
|
PolygonGeometryLibrary.subdivideLine = function (p0, p1, minDistance, result) {
|
|
const numVertices = PolygonGeometryLibrary.subdivideLineCount(
|
|
p0,
|
|
p1,
|
|
minDistance,
|
|
);
|
|
const length = Cartesian3.distance(p0, p1);
|
|
const distanceBetweenVertices = length / numVertices;
|
|
|
|
if (!defined(result)) {
|
|
result = [];
|
|
}
|
|
|
|
const positions = result;
|
|
positions.length = numVertices * 3;
|
|
|
|
let index = 0;
|
|
for (let i = 0; i < numVertices; i++) {
|
|
const p = getPointAtDistance(p0, p1, i * distanceBetweenVertices, length);
|
|
positions[index++] = p[0];
|
|
positions[index++] = p[1];
|
|
positions[index++] = p[2];
|
|
}
|
|
|
|
return positions;
|
|
};
|
|
|
|
/**
|
|
* Subdivides texture coordinates based on the subdivision of the associated world positions using a rhumb line.
|
|
*
|
|
* @param {Cartesian2} t0 First texture coordinate.
|
|
* @param {Cartesian2} t1 Second texture coordinate.
|
|
* @param {Ellipsoid} ellipsoid The ellipsoid.
|
|
* @param {Cartesian3} p0 First world position.
|
|
* @param {Cartesian3} p1 Second world position.
|
|
* @param {number} minDistance Minimum distance for a segment.
|
|
* @param {Cartesian2[]} result The subdivided texture coordinates.
|
|
*
|
|
* @private
|
|
*/
|
|
PolygonGeometryLibrary.subdivideTexcoordRhumbLine = function (
|
|
t0,
|
|
t1,
|
|
ellipsoid,
|
|
p0,
|
|
p1,
|
|
minDistance,
|
|
result,
|
|
) {
|
|
// Compute the surface distance.
|
|
const c0 = ellipsoid.cartesianToCartographic(p0, scratchCartographic0);
|
|
const c1 = ellipsoid.cartesianToCartographic(p1, scratchCartographic1);
|
|
scratchRhumbLine.setEndPoints(c0, c1);
|
|
const n = scratchRhumbLine.surfaceDistance / minDistance;
|
|
|
|
// Compute the number of subdivisions.
|
|
const countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n)));
|
|
const subdivisions = Math.pow(2, countDivide);
|
|
|
|
// Compute the distance between each subdivided point.
|
|
const length2D = Cartesian2.distance(t0, t1);
|
|
const distanceBetweenCoords = length2D / subdivisions;
|
|
|
|
// Resize the result array.
|
|
const texcoords = result;
|
|
texcoords.length = subdivisions * 2;
|
|
|
|
// Compute texture coordinates using linear interpolation.
|
|
let index = 0;
|
|
for (let i = 0; i < subdivisions; i++) {
|
|
const t = getPointAtDistance2D(t0, t1, i * distanceBetweenCoords, length2D);
|
|
texcoords[index++] = t[0];
|
|
texcoords[index++] = t[1];
|
|
}
|
|
|
|
return texcoords;
|
|
};
|
|
|
|
/**
|
|
* Subdivide the line between 2 points every minDistance length
|
|
* If the points are already closer than minDistance the first will be returned
|
|
*
|
|
* @private
|
|
* @param {Ellipsoid} ellipsoid
|
|
* @param {Cartesian3} p0 start point
|
|
* @param {Cartesian3} p1 end point
|
|
* @param {number} minDistance minimum distance between points in radians
|
|
* @param {number[]} [result] if provided positions will be packed into this array starting at index 0
|
|
* @returns {number[]} Cartesian3 positions packed into an array
|
|
*/
|
|
PolygonGeometryLibrary.subdivideRhumbLine = function (
|
|
ellipsoid,
|
|
p0,
|
|
p1,
|
|
minDistance,
|
|
result,
|
|
) {
|
|
const c0 = ellipsoid.cartesianToCartographic(p0, scratchCartographic0);
|
|
const c1 = ellipsoid.cartesianToCartographic(p1, scratchCartographic1);
|
|
const rhumb = new EllipsoidRhumbLine(c0, c1, ellipsoid);
|
|
|
|
if (!defined(result)) {
|
|
result = [];
|
|
}
|
|
|
|
if (rhumb.surfaceDistance <= minDistance) {
|
|
// no need to try and subdivide a line that's already shorter than the min distance
|
|
// this also inherently handles duplicated points which would have 0 distance
|
|
result.length = 3;
|
|
result[0] = p0.x;
|
|
result[1] = p0.y;
|
|
result[2] = p0.z;
|
|
return result;
|
|
}
|
|
|
|
const n = rhumb.surfaceDistance / minDistance;
|
|
const countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n)));
|
|
const numVertices = Math.pow(2, countDivide);
|
|
const distanceBetweenVertices = rhumb.surfaceDistance / numVertices;
|
|
|
|
const positions = result;
|
|
positions.length = numVertices * 3;
|
|
|
|
let index = 0;
|
|
for (let i = 0; i < numVertices; i++) {
|
|
const c = rhumb.interpolateUsingSurfaceDistance(
|
|
i * distanceBetweenVertices,
|
|
scratchCartographic2,
|
|
);
|
|
const p = ellipsoid.cartographicToCartesian(c, scratchCartesian0);
|
|
positions[index++] = p.x;
|
|
positions[index++] = p.y;
|
|
positions[index++] = p.z;
|
|
}
|
|
|
|
return positions;
|
|
};
|
|
|
|
const scaleToGeodeticHeightN1 = new Cartesian3();
|
|
const scaleToGeodeticHeightN2 = new Cartesian3();
|
|
const scaleToGeodeticHeightP1 = new Cartesian3();
|
|
const scaleToGeodeticHeightP2 = new Cartesian3();
|
|
|
|
PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function (
|
|
geometry,
|
|
maxHeight,
|
|
minHeight,
|
|
ellipsoid,
|
|
perPositionHeight,
|
|
) {
|
|
ellipsoid = ellipsoid ?? Ellipsoid.default;
|
|
|
|
const n1 = scaleToGeodeticHeightN1;
|
|
let n2 = scaleToGeodeticHeightN2;
|
|
const p = scaleToGeodeticHeightP1;
|
|
let p2 = scaleToGeodeticHeightP2;
|
|
|
|
if (
|
|
defined(geometry) &&
|
|
defined(geometry.attributes) &&
|
|
defined(geometry.attributes.position)
|
|
) {
|
|
const positions = geometry.attributes.position.values;
|
|
const length = positions.length / 2;
|
|
|
|
for (let i = 0; i < length; i += 3) {
|
|
Cartesian3.fromArray(positions, i, p);
|
|
|
|
ellipsoid.geodeticSurfaceNormal(p, n1);
|
|
p2 = ellipsoid.scaleToGeodeticSurface(p, p2);
|
|
n2 = Cartesian3.multiplyByScalar(n1, minHeight, n2);
|
|
n2 = Cartesian3.add(p2, n2, n2);
|
|
positions[i + length] = n2.x;
|
|
positions[i + 1 + length] = n2.y;
|
|
positions[i + 2 + length] = n2.z;
|
|
|
|
if (perPositionHeight) {
|
|
p2 = Cartesian3.clone(p, p2);
|
|
}
|
|
n2 = Cartesian3.multiplyByScalar(n1, maxHeight, n2);
|
|
n2 = Cartesian3.add(p2, n2, n2);
|
|
positions[i] = n2.x;
|
|
positions[i + 1] = n2.y;
|
|
positions[i + 2] = n2.z;
|
|
}
|
|
}
|
|
return geometry;
|
|
};
|
|
|
|
PolygonGeometryLibrary.polygonOutlinesFromHierarchy = function (
|
|
polygonHierarchy,
|
|
scaleToEllipsoidSurface,
|
|
ellipsoid,
|
|
) {
|
|
// create from a polygon hierarchy
|
|
// Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
|
|
const polygons = [];
|
|
const queue = new Queue();
|
|
queue.enqueue(polygonHierarchy);
|
|
let i;
|
|
let j;
|
|
let length;
|
|
while (queue.length !== 0) {
|
|
const outerNode = queue.dequeue();
|
|
let outerRing = outerNode.positions;
|
|
if (scaleToEllipsoidSurface) {
|
|
length = outerRing.length;
|
|
for (i = 0; i < length; i++) {
|
|
ellipsoid.scaleToGeodeticSurface(outerRing[i], outerRing[i]);
|
|
}
|
|
}
|
|
outerRing = arrayRemoveDuplicates(
|
|
outerRing,
|
|
Cartesian3.equalsEpsilon,
|
|
true,
|
|
);
|
|
if (outerRing.length < 3) {
|
|
continue;
|
|
}
|
|
|
|
const numChildren = outerNode.holes ? outerNode.holes.length : 0;
|
|
// The outer polygon contains inner polygons
|
|
for (i = 0; i < numChildren; i++) {
|
|
const hole = outerNode.holes[i];
|
|
let holePositions = hole.positions;
|
|
if (scaleToEllipsoidSurface) {
|
|
length = holePositions.length;
|
|
for (j = 0; j < length; ++j) {
|
|
ellipsoid.scaleToGeodeticSurface(holePositions[j], holePositions[j]);
|
|
}
|
|
}
|
|
holePositions = arrayRemoveDuplicates(
|
|
holePositions,
|
|
Cartesian3.equalsEpsilon,
|
|
true,
|
|
);
|
|
if (holePositions.length < 3) {
|
|
continue;
|
|
}
|
|
polygons.push(holePositions);
|
|
|
|
let numGrandchildren = 0;
|
|
if (defined(hole.holes)) {
|
|
numGrandchildren = hole.holes.length;
|
|
}
|
|
|
|
for (j = 0; j < numGrandchildren; j++) {
|
|
queue.enqueue(hole.holes[j]);
|
|
}
|
|
}
|
|
|
|
polygons.push(outerRing);
|
|
}
|
|
|
|
return polygons;
|
|
};
|
|
|
|
const scratchRhumbIntersection = new Cartographic();
|
|
function computeEquatorIntersectionRhumb(start, end, ellipsoid) {
|
|
const c0 = ellipsoid.cartesianToCartographic(start, scratchCartographic0);
|
|
const c1 = ellipsoid.cartesianToCartographic(end, scratchCartographic1);
|
|
|
|
if (Math.sign(c0.latitude) === Math.sign(c1.latitude)) {
|
|
return;
|
|
}
|
|
|
|
scratchRhumbLine.setEndPoints(c0, c1);
|
|
|
|
const intersection = scratchRhumbLine.findIntersectionWithLatitude(
|
|
0,
|
|
scratchRhumbIntersection,
|
|
);
|
|
|
|
if (!defined(intersection)) {
|
|
return;
|
|
}
|
|
|
|
let minLongitude = Math.min(c0.longitude, c1.longitude);
|
|
let maxLongitude = Math.max(c0.longitude, c1.longitude);
|
|
|
|
if (Math.abs(maxLongitude - minLongitude) > CesiumMath.PI) {
|
|
// Crosses IDL, flip min and max
|
|
const swap = minLongitude;
|
|
minLongitude = maxLongitude;
|
|
maxLongitude = swap;
|
|
}
|
|
|
|
if (
|
|
intersection.longitude < minLongitude ||
|
|
intersection.longitude > maxLongitude
|
|
) {
|
|
return;
|
|
}
|
|
|
|
return ellipsoid.cartographicToCartesian(intersection);
|
|
}
|
|
|
|
function computeEquatorIntersection(start, end, ellipsoid, arcType) {
|
|
if (arcType === ArcType.RHUMB) {
|
|
return computeEquatorIntersectionRhumb(start, end, ellipsoid);
|
|
}
|
|
|
|
const intersection = IntersectionTests.lineSegmentPlane(
|
|
start,
|
|
end,
|
|
Plane.ORIGIN_XY_PLANE,
|
|
);
|
|
|
|
if (!defined(intersection)) {
|
|
return;
|
|
}
|
|
|
|
return ellipsoid.scaleToGeodeticSurface(intersection, intersection);
|
|
}
|
|
|
|
const scratchCartographic = new Cartographic();
|
|
function computeEdgesOnPlane(positions, ellipsoid, arcType) {
|
|
const edgesOnPlane = [];
|
|
let startPoint,
|
|
endPoint,
|
|
type,
|
|
next,
|
|
intersection,
|
|
i = 0;
|
|
while (i < positions.length) {
|
|
startPoint = positions[i];
|
|
endPoint = positions[(i + 1) % positions.length];
|
|
|
|
type = CesiumMath.sign(startPoint.z);
|
|
next = CesiumMath.sign(endPoint.z);
|
|
|
|
const getLongitude = (position) => {
|
|
const cartographic = ellipsoid.cartesianToCartographic(
|
|
position,
|
|
scratchCartographic,
|
|
);
|
|
return cartographic.longitude;
|
|
};
|
|
|
|
if (type === 0) {
|
|
// The start position is on the split
|
|
edgesOnPlane.push({
|
|
position: i,
|
|
type: type,
|
|
visited: false,
|
|
next: next,
|
|
theta: getLongitude(startPoint),
|
|
});
|
|
} else if (next !== 0) {
|
|
intersection = computeEquatorIntersection(
|
|
startPoint,
|
|
endPoint,
|
|
ellipsoid,
|
|
arcType,
|
|
);
|
|
|
|
++i;
|
|
if (!defined(intersection)) {
|
|
// The line segment is entirely above or below
|
|
continue;
|
|
}
|
|
|
|
// The line segment passed through
|
|
positions.splice(i, 0, intersection);
|
|
edgesOnPlane.push({
|
|
position: i,
|
|
type: type,
|
|
visited: false,
|
|
next: next,
|
|
theta: getLongitude(intersection),
|
|
});
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
return edgesOnPlane;
|
|
}
|
|
|
|
function wirePolygon(
|
|
polygons,
|
|
polygonIndex,
|
|
positions,
|
|
edgesOnPlane,
|
|
toDelete,
|
|
startIndex,
|
|
abovePlane,
|
|
) {
|
|
const polygon = [];
|
|
let i = startIndex;
|
|
const getMatchingEdge = (i) => (edge) => edge.position === i;
|
|
const polygonsToWire = [];
|
|
do {
|
|
const position = positions[i];
|
|
polygon.push(position);
|
|
|
|
const edgeIndex = edgesOnPlane.findIndex(getMatchingEdge(i));
|
|
const edge = edgesOnPlane[edgeIndex];
|
|
if (!defined(edge)) {
|
|
// The current segment does not intersect
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
const { visited: hasBeenVisited, type, next } = edge;
|
|
edge.visited = true;
|
|
|
|
if (type === 0) {
|
|
if (next === 0) {
|
|
// Special case where we'll need to backtrack along the edge
|
|
const previousEdge = edgesOnPlane[edgeIndex - (abovePlane ? 1 : -1)];
|
|
if (previousEdge?.position === i + 1) {
|
|
previousEdge.visited = true;
|
|
} else {
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Special case where 3 polygons meet
|
|
if (
|
|
(!hasBeenVisited && abovePlane && next > 0) ||
|
|
(startIndex === i && !abovePlane && next < 0)
|
|
) {
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const followEdge = abovePlane ? type >= 0 : type <= 0;
|
|
if (!followEdge) {
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (!hasBeenVisited) {
|
|
// Wire another polygon starting at this position on the other side of the edge
|
|
polygonsToWire.push(i);
|
|
}
|
|
|
|
// Continue counter-clockwise to the next edge
|
|
const nextEdgeIndex = edgeIndex + (abovePlane ? 1 : -1);
|
|
const nextEdge = edgesOnPlane[nextEdgeIndex];
|
|
if (!defined(nextEdge)) {
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
i = nextEdge.position;
|
|
} while (
|
|
i < positions.length &&
|
|
i >= 0 &&
|
|
i !== startIndex &&
|
|
polygon.length < positions.length
|
|
);
|
|
|
|
polygons.splice(polygonIndex, toDelete, polygon);
|
|
|
|
for (const index of polygonsToWire) {
|
|
polygonIndex = wirePolygon(
|
|
polygons,
|
|
++polygonIndex,
|
|
positions,
|
|
edgesOnPlane,
|
|
0,
|
|
index,
|
|
!abovePlane,
|
|
);
|
|
}
|
|
|
|
return polygonIndex;
|
|
}
|
|
|
|
/**
|
|
* Splits an array of polygons, defined as a list of Cartesian3 positions in counter-clockwise winding order, along the equator.
|
|
*
|
|
* @param {Array<Cartesian3[]>} outerRings An array of polygons, defined as a list of Cartesian3 positions in counter-clockwise winding order.
|
|
* @param {Ellipsoid} ellipsoid The ellipsoid to be used as a reference.
|
|
* @param {ArcType} arcType The type of line the polygon edges must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
|
|
* @param {Array<Cartesian3[]>} [result] An array of split polygons.
|
|
*
|
|
* @returns {Array<Cartesian3[]>} An array of split polygons.
|
|
*/
|
|
PolygonGeometryLibrary.splitPolygonsOnEquator = function (
|
|
outerRings,
|
|
ellipsoid,
|
|
arcType,
|
|
result,
|
|
) {
|
|
if (!defined(result)) {
|
|
result = [];
|
|
}
|
|
|
|
result.splice(0, 0, ...outerRings);
|
|
result.length = outerRings.length;
|
|
|
|
let currentPolygon = 0;
|
|
while (currentPolygon < result.length) {
|
|
// Adapted from https://www.sciencedirect.com/science/article/abs/pii/B9780125434577500589#:~:text=If%20the%20plane%20intersects%20the,tree%20and%20polygon%20intersection%20libraries
|
|
const outerRing = result[currentPolygon];
|
|
const positions = outerRing.slice();
|
|
|
|
if (outerRing.length < 3) {
|
|
result[currentPolygon] = positions;
|
|
++currentPolygon;
|
|
continue;
|
|
}
|
|
|
|
// Step 1: Get all edges which intersect the split line, splciing any found intersections points into the list of positions
|
|
const edgesOnPlane = computeEdgesOnPlane(positions, ellipsoid, arcType);
|
|
// If nothing intersected (no point were added), or there is only a single point on the plane, use the original polygon
|
|
if (positions.length === outerRing.length || edgesOnPlane.length <= 1) {
|
|
result[currentPolygon] = positions;
|
|
++currentPolygon;
|
|
continue;
|
|
}
|
|
|
|
// Step 2: Sort the edges along the split line by the distance between their starting points and the starting point of the split line.
|
|
edgesOnPlane.sort((a, b) => {
|
|
return a.theta - b.theta;
|
|
});
|
|
|
|
// Step 3: Rewire polygons, splicing each polygon into the array of results
|
|
const north = positions[0].z >= 0.0;
|
|
currentPolygon = wirePolygon(
|
|
result,
|
|
currentPolygon,
|
|
positions,
|
|
edgesOnPlane,
|
|
1,
|
|
0,
|
|
north,
|
|
);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
PolygonGeometryLibrary.polygonsFromHierarchy = function (
|
|
polygonHierarchy,
|
|
keepDuplicates,
|
|
projectPointsTo2D,
|
|
scaleToEllipsoidSurface,
|
|
ellipsoid,
|
|
splitPolygons,
|
|
) {
|
|
// create from a polygon hierarchy
|
|
// Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
|
|
const hierarchy = [];
|
|
const polygons = [];
|
|
|
|
const queue = new Queue();
|
|
queue.enqueue(polygonHierarchy);
|
|
|
|
let split = defined(splitPolygons);
|
|
|
|
while (queue.length !== 0) {
|
|
const outerNode = queue.dequeue();
|
|
let outerRing = outerNode.positions;
|
|
const holes = outerNode.holes;
|
|
|
|
let i;
|
|
let length;
|
|
if (scaleToEllipsoidSurface) {
|
|
length = outerRing.length;
|
|
for (i = 0; i < length; i++) {
|
|
ellipsoid.scaleToGeodeticSurface(outerRing[i], outerRing[i]);
|
|
}
|
|
}
|
|
|
|
if (!keepDuplicates) {
|
|
outerRing = arrayRemoveDuplicates(
|
|
outerRing,
|
|
Cartesian3.equalsEpsilon,
|
|
true,
|
|
);
|
|
}
|
|
if (outerRing.length < 3) {
|
|
continue;
|
|
}
|
|
|
|
let positions2D = projectPointsTo2D(outerRing);
|
|
if (!defined(positions2D)) {
|
|
continue;
|
|
}
|
|
const holeIndices = [];
|
|
|
|
let originalWindingOrder =
|
|
PolygonPipeline.computeWindingOrder2D(positions2D);
|
|
if (originalWindingOrder === WindingOrder.CLOCKWISE) {
|
|
positions2D.reverse();
|
|
outerRing = outerRing.slice().reverse();
|
|
}
|
|
|
|
if (split) {
|
|
split = false;
|
|
let polygons = [outerRing];
|
|
polygons = splitPolygons(polygons, polygons);
|
|
|
|
if (polygons.length > 1) {
|
|
for (const positions of polygons) {
|
|
queue.enqueue(new PolygonHierarchy(positions, holes));
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let positions = outerRing.slice();
|
|
const numChildren = defined(holes) ? holes.length : 0;
|
|
const polygonHoles = [];
|
|
let j;
|
|
|
|
for (i = 0; i < numChildren; i++) {
|
|
const hole = holes[i];
|
|
let holePositions = hole.positions;
|
|
if (scaleToEllipsoidSurface) {
|
|
length = holePositions.length;
|
|
for (j = 0; j < length; ++j) {
|
|
ellipsoid.scaleToGeodeticSurface(holePositions[j], holePositions[j]);
|
|
}
|
|
}
|
|
|
|
if (!keepDuplicates) {
|
|
holePositions = arrayRemoveDuplicates(
|
|
holePositions,
|
|
Cartesian3.equalsEpsilon,
|
|
true,
|
|
);
|
|
}
|
|
if (holePositions.length < 3) {
|
|
continue;
|
|
}
|
|
|
|
const holePositions2D = projectPointsTo2D(holePositions);
|
|
if (!defined(holePositions2D)) {
|
|
continue;
|
|
}
|
|
|
|
originalWindingOrder =
|
|
PolygonPipeline.computeWindingOrder2D(holePositions2D);
|
|
if (originalWindingOrder === WindingOrder.CLOCKWISE) {
|
|
holePositions2D.reverse();
|
|
holePositions = holePositions.slice().reverse();
|
|
}
|
|
|
|
polygonHoles.push(holePositions);
|
|
holeIndices.push(positions.length);
|
|
positions = positions.concat(holePositions);
|
|
positions2D = positions2D.concat(holePositions2D);
|
|
|
|
let numGrandchildren = 0;
|
|
if (defined(hole.holes)) {
|
|
numGrandchildren = hole.holes.length;
|
|
}
|
|
|
|
for (j = 0; j < numGrandchildren; j++) {
|
|
queue.enqueue(hole.holes[j]);
|
|
}
|
|
}
|
|
|
|
hierarchy.push({
|
|
outerRing: outerRing,
|
|
holes: polygonHoles,
|
|
});
|
|
polygons.push({
|
|
positions: positions,
|
|
positions2D: positions2D,
|
|
holes: holeIndices,
|
|
});
|
|
}
|
|
|
|
return {
|
|
hierarchy: hierarchy,
|
|
polygons: polygons,
|
|
};
|
|
};
|
|
|
|
const computeBoundingRectangleCartesian2 = new Cartesian2();
|
|
const computeBoundingRectangleCartesian3 = new Cartesian3();
|
|
const computeBoundingRectangleQuaternion = new Quaternion();
|
|
const computeBoundingRectangleMatrix3 = new Matrix3();
|
|
PolygonGeometryLibrary.computeBoundingRectangle = function (
|
|
planeNormal,
|
|
projectPointTo2D,
|
|
positions,
|
|
angle,
|
|
result,
|
|
) {
|
|
const rotation = Quaternion.fromAxisAngle(
|
|
planeNormal,
|
|
angle,
|
|
computeBoundingRectangleQuaternion,
|
|
);
|
|
const textureMatrix = Matrix3.fromQuaternion(
|
|
rotation,
|
|
computeBoundingRectangleMatrix3,
|
|
);
|
|
|
|
let minX = Number.POSITIVE_INFINITY;
|
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
let minY = Number.POSITIVE_INFINITY;
|
|
let maxY = Number.NEGATIVE_INFINITY;
|
|
|
|
const length = positions.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
const p = Cartesian3.clone(
|
|
positions[i],
|
|
computeBoundingRectangleCartesian3,
|
|
);
|
|
Matrix3.multiplyByVector(textureMatrix, p, p);
|
|
const st = projectPointTo2D(p, computeBoundingRectangleCartesian2);
|
|
|
|
if (defined(st)) {
|
|
minX = Math.min(minX, st.x);
|
|
maxX = Math.max(maxX, st.x);
|
|
|
|
minY = Math.min(minY, st.y);
|
|
maxY = Math.max(maxY, st.y);
|
|
}
|
|
}
|
|
|
|
result.x = minX;
|
|
result.y = minY;
|
|
result.width = maxX - minX;
|
|
result.height = maxY - minY;
|
|
return result;
|
|
};
|
|
|
|
PolygonGeometryLibrary.createGeometryFromPositions = function (
|
|
ellipsoid,
|
|
polygon,
|
|
textureCoordinates,
|
|
granularity,
|
|
perPositionHeight,
|
|
vertexFormat,
|
|
arcType,
|
|
) {
|
|
let indices = PolygonPipeline.triangulate(polygon.positions2D, polygon.holes);
|
|
|
|
/* If polygon is completely unrenderable, just use the first three vertices */
|
|
if (indices.length < 3) {
|
|
indices = [0, 1, 2];
|
|
}
|
|
|
|
const positions = polygon.positions;
|
|
|
|
const hasTexcoords = defined(textureCoordinates);
|
|
const texcoords = hasTexcoords ? textureCoordinates.positions : undefined;
|
|
|
|
if (perPositionHeight) {
|
|
const length = positions.length;
|
|
const flattenedPositions = new Array(length * 3);
|
|
let index = 0;
|
|
for (let i = 0; i < length; i++) {
|
|
const p = positions[i];
|
|
flattenedPositions[index++] = p.x;
|
|
flattenedPositions[index++] = p.y;
|
|
flattenedPositions[index++] = p.z;
|
|
}
|
|
|
|
const geometryOptions = {
|
|
attributes: {
|
|
position: new GeometryAttribute({
|
|
componentDatatype: ComponentDatatype.DOUBLE,
|
|
componentsPerAttribute: 3,
|
|
values: flattenedPositions,
|
|
}),
|
|
},
|
|
indices: indices,
|
|
primitiveType: PrimitiveType.TRIANGLES,
|
|
};
|
|
|
|
if (hasTexcoords) {
|
|
geometryOptions.attributes.st = new GeometryAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 2,
|
|
values: Cartesian2.packArray(texcoords),
|
|
});
|
|
}
|
|
|
|
const geometry = new Geometry(geometryOptions);
|
|
|
|
if (vertexFormat.normal) {
|
|
return GeometryPipeline.computeNormal(geometry);
|
|
}
|
|
|
|
return geometry;
|
|
}
|
|
|
|
if (arcType === ArcType.GEODESIC) {
|
|
return PolygonPipeline.computeSubdivision(
|
|
ellipsoid,
|
|
positions,
|
|
indices,
|
|
texcoords,
|
|
granularity,
|
|
);
|
|
} else if (arcType === ArcType.RHUMB) {
|
|
return PolygonPipeline.computeRhumbLineSubdivision(
|
|
ellipsoid,
|
|
positions,
|
|
indices,
|
|
texcoords,
|
|
granularity,
|
|
);
|
|
}
|
|
};
|
|
|
|
const computeWallTexcoordsSubdivided = [];
|
|
const computeWallIndicesSubdivided = [];
|
|
const p1Scratch = new Cartesian3();
|
|
const p2Scratch = new Cartesian3();
|
|
|
|
PolygonGeometryLibrary.computeWallGeometry = function (
|
|
positions,
|
|
textureCoordinates,
|
|
ellipsoid,
|
|
granularity,
|
|
perPositionHeight,
|
|
arcType,
|
|
) {
|
|
let edgePositions;
|
|
let topEdgeLength;
|
|
let i;
|
|
let p1;
|
|
let p2;
|
|
let t1;
|
|
let t2;
|
|
let edgeTexcoords;
|
|
let topEdgeTexcoordLength;
|
|
|
|
let length = positions.length;
|
|
let index = 0;
|
|
let textureIndex = 0;
|
|
|
|
const hasTexcoords = defined(textureCoordinates);
|
|
const texcoords = hasTexcoords ? textureCoordinates.positions : undefined;
|
|
|
|
if (!perPositionHeight) {
|
|
const minDistance = CesiumMath.chordLength(
|
|
granularity,
|
|
ellipsoid.maximumRadius,
|
|
);
|
|
|
|
let numVertices = 0;
|
|
if (arcType === ArcType.GEODESIC) {
|
|
for (i = 0; i < length; i++) {
|
|
numVertices += PolygonGeometryLibrary.subdivideLineCount(
|
|
positions[i],
|
|
positions[(i + 1) % length],
|
|
minDistance,
|
|
);
|
|
}
|
|
} else if (arcType === ArcType.RHUMB) {
|
|
for (i = 0; i < length; i++) {
|
|
numVertices += PolygonGeometryLibrary.subdivideRhumbLineCount(
|
|
ellipsoid,
|
|
positions[i],
|
|
positions[(i + 1) % length],
|
|
minDistance,
|
|
);
|
|
}
|
|
}
|
|
|
|
topEdgeLength = (numVertices + length) * 3;
|
|
edgePositions = new Array(topEdgeLength * 2);
|
|
|
|
if (hasTexcoords) {
|
|
topEdgeTexcoordLength = (numVertices + length) * 2;
|
|
edgeTexcoords = new Array(topEdgeTexcoordLength * 2);
|
|
}
|
|
|
|
for (i = 0; i < length; i++) {
|
|
p1 = positions[i];
|
|
p2 = positions[(i + 1) % length];
|
|
|
|
let tempPositions;
|
|
let tempTexcoords;
|
|
|
|
if (hasTexcoords) {
|
|
t1 = texcoords[i];
|
|
t2 = texcoords[(i + 1) % length];
|
|
}
|
|
|
|
if (arcType === ArcType.GEODESIC) {
|
|
tempPositions = PolygonGeometryLibrary.subdivideLine(
|
|
p1,
|
|
p2,
|
|
minDistance,
|
|
computeWallIndicesSubdivided,
|
|
);
|
|
if (hasTexcoords) {
|
|
tempTexcoords = PolygonGeometryLibrary.subdivideTexcoordLine(
|
|
t1,
|
|
t2,
|
|
p1,
|
|
p2,
|
|
minDistance,
|
|
computeWallTexcoordsSubdivided,
|
|
);
|
|
}
|
|
} else if (arcType === ArcType.RHUMB) {
|
|
tempPositions = PolygonGeometryLibrary.subdivideRhumbLine(
|
|
ellipsoid,
|
|
p1,
|
|
p2,
|
|
minDistance,
|
|
computeWallIndicesSubdivided,
|
|
);
|
|
if (hasTexcoords) {
|
|
tempTexcoords = PolygonGeometryLibrary.subdivideTexcoordRhumbLine(
|
|
t1,
|
|
t2,
|
|
ellipsoid,
|
|
p1,
|
|
p2,
|
|
minDistance,
|
|
computeWallTexcoordsSubdivided,
|
|
);
|
|
}
|
|
}
|
|
const tempPositionsLength = tempPositions.length;
|
|
for (let j = 0; j < tempPositionsLength; ++j, ++index) {
|
|
edgePositions[index] = tempPositions[j];
|
|
edgePositions[index + topEdgeLength] = tempPositions[j];
|
|
}
|
|
|
|
edgePositions[index] = p2.x;
|
|
edgePositions[index + topEdgeLength] = p2.x;
|
|
++index;
|
|
|
|
edgePositions[index] = p2.y;
|
|
edgePositions[index + topEdgeLength] = p2.y;
|
|
++index;
|
|
|
|
edgePositions[index] = p2.z;
|
|
edgePositions[index + topEdgeLength] = p2.z;
|
|
++index;
|
|
|
|
if (hasTexcoords) {
|
|
const tempTexcoordsLength = tempTexcoords.length;
|
|
for (let k = 0; k < tempTexcoordsLength; ++k, ++textureIndex) {
|
|
edgeTexcoords[textureIndex] = tempTexcoords[k];
|
|
edgeTexcoords[textureIndex + topEdgeTexcoordLength] =
|
|
tempTexcoords[k];
|
|
}
|
|
|
|
edgeTexcoords[textureIndex] = t2.x;
|
|
edgeTexcoords[textureIndex + topEdgeTexcoordLength] = t2.x;
|
|
++textureIndex;
|
|
|
|
edgeTexcoords[textureIndex] = t2.y;
|
|
edgeTexcoords[textureIndex + topEdgeTexcoordLength] = t2.y;
|
|
++textureIndex;
|
|
}
|
|
}
|
|
} else {
|
|
topEdgeLength = length * 3 * 2;
|
|
edgePositions = new Array(topEdgeLength * 2);
|
|
|
|
if (hasTexcoords) {
|
|
topEdgeTexcoordLength = length * 2 * 2;
|
|
edgeTexcoords = new Array(topEdgeTexcoordLength * 2);
|
|
}
|
|
|
|
for (i = 0; i < length; i++) {
|
|
p1 = positions[i];
|
|
p2 = positions[(i + 1) % length];
|
|
edgePositions[index] = edgePositions[index + topEdgeLength] = p1.x;
|
|
++index;
|
|
edgePositions[index] = edgePositions[index + topEdgeLength] = p1.y;
|
|
++index;
|
|
edgePositions[index] = edgePositions[index + topEdgeLength] = p1.z;
|
|
++index;
|
|
edgePositions[index] = edgePositions[index + topEdgeLength] = p2.x;
|
|
++index;
|
|
edgePositions[index] = edgePositions[index + topEdgeLength] = p2.y;
|
|
++index;
|
|
edgePositions[index] = edgePositions[index + topEdgeLength] = p2.z;
|
|
++index;
|
|
|
|
if (hasTexcoords) {
|
|
t1 = texcoords[i];
|
|
t2 = texcoords[(i + 1) % length];
|
|
edgeTexcoords[textureIndex] = edgeTexcoords[
|
|
textureIndex + topEdgeTexcoordLength
|
|
] = t1.x;
|
|
++textureIndex;
|
|
edgeTexcoords[textureIndex] = edgeTexcoords[
|
|
textureIndex + topEdgeTexcoordLength
|
|
] = t1.y;
|
|
++textureIndex;
|
|
edgeTexcoords[textureIndex] = edgeTexcoords[
|
|
textureIndex + topEdgeTexcoordLength
|
|
] = t2.x;
|
|
++textureIndex;
|
|
edgeTexcoords[textureIndex] = edgeTexcoords[
|
|
textureIndex + topEdgeTexcoordLength
|
|
] = t2.y;
|
|
++textureIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
length = edgePositions.length;
|
|
const indices = IndexDatatype.createTypedArray(
|
|
length / 3,
|
|
length - positions.length * 6,
|
|
);
|
|
let edgeIndex = 0;
|
|
length /= 6;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
const UL = i;
|
|
const UR = UL + 1;
|
|
const LL = UL + length;
|
|
const LR = LL + 1;
|
|
|
|
p1 = Cartesian3.fromArray(edgePositions, UL * 3, p1Scratch);
|
|
p2 = Cartesian3.fromArray(edgePositions, UR * 3, p2Scratch);
|
|
if (
|
|
Cartesian3.equalsEpsilon(
|
|
p1,
|
|
p2,
|
|
CesiumMath.EPSILON10,
|
|
CesiumMath.EPSILON10,
|
|
)
|
|
) {
|
|
//skip corner
|
|
continue;
|
|
}
|
|
|
|
indices[edgeIndex++] = UL;
|
|
indices[edgeIndex++] = LL;
|
|
indices[edgeIndex++] = UR;
|
|
indices[edgeIndex++] = UR;
|
|
indices[edgeIndex++] = LL;
|
|
indices[edgeIndex++] = LR;
|
|
}
|
|
|
|
const geometryOptions = {
|
|
attributes: new GeometryAttributes({
|
|
position: new GeometryAttribute({
|
|
componentDatatype: ComponentDatatype.DOUBLE,
|
|
componentsPerAttribute: 3,
|
|
values: edgePositions,
|
|
}),
|
|
}),
|
|
indices: indices,
|
|
primitiveType: PrimitiveType.TRIANGLES,
|
|
};
|
|
|
|
if (hasTexcoords) {
|
|
geometryOptions.attributes.st = new GeometryAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 2,
|
|
values: edgeTexcoords,
|
|
});
|
|
}
|
|
|
|
const geometry = new Geometry(geometryOptions);
|
|
|
|
return geometry;
|
|
};
|
|
export default PolygonGeometryLibrary;
|