import Cartesian2 from "./Cartesian2.js"; import Cartesian3 from "./Cartesian3.js"; import Cartesian4 from "./Cartesian4.js"; import Cartographic from "./Cartographic.js"; import CornerType from "./CornerType.js"; import EllipsoidTangentPlane from "./EllipsoidTangentPlane.js"; import CesiumMath from "./Math.js"; import Matrix3 from "./Matrix3.js"; import Matrix4 from "./Matrix4.js"; import PolylinePipeline from "./PolylinePipeline.js"; import Quaternion from "./Quaternion.js"; import Transforms from "./Transforms.js"; import oneTimeWarning from "../Core/oneTimeWarning.js"; const scratch2Array = [new Cartesian3(), new Cartesian3()]; const scratchCartesian1 = new Cartesian3(); const scratchCartesian2 = new Cartesian3(); const scratchCartesian3 = new Cartesian3(); const scratchCartesian4 = new Cartesian3(); const scratchCartesian5 = new Cartesian3(); const scratchCartesian6 = new Cartesian3(); const scratchCartesian7 = new Cartesian3(); const scratchCartesian8 = new Cartesian3(); const scratchCartesian9 = new Cartesian3(); const scratch1 = new Cartesian3(); const scratch2 = new Cartesian3(); /** * @private */ const PolylineVolumeGeometryLibrary = {}; let cartographic = new Cartographic(); function scaleToSurface(positions, ellipsoid) { const heights = new Array(positions.length); for (let i = 0; i < positions.length; i++) { const pos = positions[i]; cartographic = ellipsoid.cartesianToCartographic(pos, cartographic); heights[i] = cartographic.height; positions[i] = ellipsoid.scaleToGeodeticSurface(pos, pos); } return heights; } function subdivideHeights(points, h0, h1, granularity) { const p0 = points[0]; const p1 = points[1]; const angleBetween = Cartesian3.angleBetween(p0, p1); const numPoints = Math.ceil(angleBetween / granularity); const heights = new Array(numPoints); let i; if (h0 === h1) { for (i = 0; i < numPoints; i++) { heights[i] = h0; } heights.push(h1); return heights; } const dHeight = h1 - h0; const heightPerVertex = dHeight / numPoints; for (i = 1; i < numPoints; i++) { const h = h0 + i * heightPerVertex; heights[i] = h; } heights[0] = h0; heights.push(h1); return heights; } const nextScratch = new Cartesian3(); const prevScratch = new Cartesian3(); function computeRotationAngle(start, end, position, ellipsoid) { const tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); const next = tangentPlane.projectPointOntoPlane( Cartesian3.add(position, start, nextScratch), nextScratch ); const prev = tangentPlane.projectPointOntoPlane( Cartesian3.add(position, end, prevScratch), prevScratch ); const angle = Cartesian2.angleBetween(next, prev); return prev.x * next.y - prev.y * next.x >= 0.0 ? -angle : angle; } const negativeX = new Cartesian3(-1, 0, 0); let transform = new Matrix4(); const translation = new Matrix4(); let rotationZ = new Matrix3(); const scaleMatrix = Matrix3.IDENTITY.clone(); const westScratch = new Cartesian3(); const finalPosScratch = new Cartesian4(); const heightCartesian = new Cartesian3(); function addPosition( center, left, shape, finalPositions, ellipsoid, height, xScalar, repeat ) { let west = westScratch; let finalPosition = finalPosScratch; transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform); west = Matrix4.multiplyByPointAsVector(transform, negativeX, west); west = Cartesian3.normalize(west, west); const angle = computeRotationAngle(west, left, center, ellipsoid); rotationZ = Matrix3.fromRotationZ(angle, rotationZ); heightCartesian.z = height; transform = Matrix4.multiplyTransformation( transform, Matrix4.fromRotationTranslation(rotationZ, heightCartesian, translation), transform ); const scale = scaleMatrix; scale[0] = xScalar; for (let j = 0; j < repeat; j++) { for (let i = 0; i < shape.length; i += 3) { finalPosition = Cartesian3.fromArray(shape, i, finalPosition); finalPosition = Matrix3.multiplyByVector( scale, finalPosition, finalPosition ); finalPosition = Matrix4.multiplyByPoint( transform, finalPosition, finalPosition ); finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); } } return finalPositions; } const centerScratch = new Cartesian3(); function addPositions( centers, left, shape, finalPositions, ellipsoid, heights, xScalar ) { for (let i = 0; i < centers.length; i += 3) { const center = Cartesian3.fromArray(centers, i, centerScratch); finalPositions = addPosition( center, left, shape, finalPositions, ellipsoid, heights[i / 3], xScalar, 1 ); } return finalPositions; } function convertShapeTo3DDuplicate(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points const length = shape2D.length; const shape = new Array(length * 6); let index = 0; const xOffset = boundingRectangle.x + boundingRectangle.width / 2; const yOffset = boundingRectangle.y + boundingRectangle.height / 2; let point = shape2D[0]; shape[index++] = point.x - xOffset; shape[index++] = 0.0; shape[index++] = point.y - yOffset; for (let i = 1; i < length; i++) { point = shape2D[i]; const x = point.x - xOffset; const z = point.y - yOffset; shape[index++] = x; shape[index++] = 0.0; shape[index++] = z; shape[index++] = x; shape[index++] = 0.0; shape[index++] = z; } point = shape2D[0]; shape[index++] = point.x - xOffset; shape[index++] = 0.0; shape[index++] = point.y - yOffset; return shape; } function convertShapeTo3D(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) const length = shape2D.length; const shape = new Array(length * 3); let index = 0; const xOffset = boundingRectangle.x + boundingRectangle.width / 2; const yOffset = boundingRectangle.y + boundingRectangle.height / 2; for (let i = 0; i < length; i++) { shape[index++] = shape2D[i].x - xOffset; shape[index++] = 0; shape[index++] = shape2D[i].y - yOffset; } return shape; } const quaterion = new Quaternion(); const startPointScratch = new Cartesian3(); const rotMatrix = new Matrix3(); function computeRoundCorner( pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height, duplicatePoints ) { const angle = Cartesian3.angleBetween( Cartesian3.subtract(startPoint, pivot, scratch1), Cartesian3.subtract(endPoint, pivot, scratch2) ); const granularity = cornerType === CornerType.BEVELED ? 0 : Math.ceil(angle / CesiumMath.toRadians(5)); let m; if (leftIsOutside) { m = Matrix3.fromQuaternion( Quaternion.fromAxisAngle( Cartesian3.negate(pivot, scratch1), angle / (granularity + 1), quaterion ), rotMatrix ); } else { m = Matrix3.fromQuaternion( Quaternion.fromAxisAngle(pivot, angle / (granularity + 1), quaterion), rotMatrix ); } let left; let surfacePoint; startPoint = Cartesian3.clone(startPoint, startPointScratch); if (granularity > 0) { const repeat = duplicatePoints ? 2 : 1; for (let i = 0; i < granularity; i++) { startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint); left = Cartesian3.subtract(startPoint, pivot, scratch1); left = Cartesian3.normalize(left, left); if (!leftIsOutside) { left = Cartesian3.negate(left, left); } surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); finalPositions = addPosition( surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, repeat ); } } else { left = Cartesian3.subtract(startPoint, pivot, scratch1); left = Cartesian3.normalize(left, left); if (!leftIsOutside) { left = Cartesian3.negate(left, left); } surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); finalPositions = addPosition( surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1 ); endPoint = Cartesian3.clone(endPoint, startPointScratch); left = Cartesian3.subtract(endPoint, pivot, scratch1); left = Cartesian3.normalize(left, left); if (!leftIsOutside) { left = Cartesian3.negate(left, left); } surfacePoint = ellipsoid.scaleToGeodeticSurface(endPoint, scratch2); finalPositions = addPosition( surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1 ); } return finalPositions; } PolylineVolumeGeometryLibrary.removeDuplicatesFromShape = function ( shapePositions ) { const length = shapePositions.length; const cleanedPositions = []; for (let i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) { const v0 = shapePositions[i0]; const v1 = shapePositions[i1]; if (!Cartesian2.equals(v0, v1)) { cleanedPositions.push(v1); // Shallow copy! } } return cleanedPositions; }; PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function ( forward, backward, position, ellipsoid ) { const tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); const next = tangentPlane.projectPointOntoPlane( Cartesian3.add(position, forward, nextScratch), nextScratch ); const prev = tangentPlane.projectPointOntoPlane( Cartesian3.add(position, backward, prevScratch), prevScratch ); return prev.x * next.y - prev.y * next.x >= 0.0; }; const scratchForwardProjection = new Cartesian3(); const scratchBackwardProjection = new Cartesian3(); PolylineVolumeGeometryLibrary.computePositions = function ( positions, shape2D, boundingRectangle, geometry, duplicatePoints ) { const ellipsoid = geometry._ellipsoid; const heights = scaleToSurface(positions, ellipsoid); const granularity = geometry._granularity; const cornerType = geometry._cornerType; const shapeForSides = duplicatePoints ? convertShapeTo3DDuplicate(shape2D, boundingRectangle) : convertShapeTo3D(shape2D, boundingRectangle); const shapeForEnds = duplicatePoints ? convertShapeTo3D(shape2D, boundingRectangle) : undefined; const heightOffset = boundingRectangle.height / 2; const width = boundingRectangle.width / 2; let length = positions.length; let finalPositions = []; let ends = duplicatePoints ? [] : undefined; let forward = scratchCartesian1; let backward = scratchCartesian2; let cornerDirection = scratchCartesian3; let surfaceNormal = scratchCartesian4; let pivot = scratchCartesian5; let start = scratchCartesian6; let end = scratchCartesian7; let left = scratchCartesian8; let previousPosition = scratchCartesian9; let position = positions[0]; let nextPosition = positions[1]; surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); forward = Cartesian3.subtract(nextPosition, position, forward); forward = Cartesian3.normalize(forward, forward); left = Cartesian3.cross(surfaceNormal, forward, left); left = Cartesian3.normalize(left, left); let h0 = heights[0]; let h1 = heights[1]; if (duplicatePoints) { ends = addPosition( position, left, shapeForEnds, ends, ellipsoid, h0 + heightOffset, 1, 1 ); } previousPosition = Cartesian3.clone(position, previousPosition); position = nextPosition; backward = Cartesian3.negate(forward, backward); let subdividedHeights; let subdividedPositions; for (let i = 1; i < length - 1; i++) { const repeat = duplicatePoints ? 2 : 1; nextPosition = positions[i + 1]; if (position.equals(nextPosition)) { oneTimeWarning( "Positions are too close and are considered equivalent with rounding error." ); continue; } forward = Cartesian3.subtract(nextPosition, position, forward); forward = Cartesian3.normalize(forward, forward); cornerDirection = Cartesian3.add(forward, backward, cornerDirection); cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); const forwardProjection = Cartesian3.multiplyByScalar( surfaceNormal, Cartesian3.dot(forward, surfaceNormal), scratchForwardProjection ); Cartesian3.subtract(forward, forwardProjection, forwardProjection); Cartesian3.normalize(forwardProjection, forwardProjection); const backwardProjection = Cartesian3.multiplyByScalar( surfaceNormal, Cartesian3.dot(backward, surfaceNormal), scratchBackwardProjection ); Cartesian3.subtract(backward, backwardProjection, backwardProjection); Cartesian3.normalize(backwardProjection, backwardProjection); const doCorner = !CesiumMath.equalsEpsilon( Math.abs(Cartesian3.dot(forwardProjection, backwardProjection)), 1.0, CesiumMath.EPSILON7 ); if (doCorner) { cornerDirection = Cartesian3.cross( cornerDirection, surfaceNormal, cornerDirection ); cornerDirection = Cartesian3.cross( surfaceNormal, cornerDirection, cornerDirection ); cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); const scalar = 1 / Math.max( 0.25, Cartesian3.magnitude( Cartesian3.cross(cornerDirection, backward, scratch1) ) ); const leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi( forward, backward, position, ellipsoid ); if (leftIsOutside) { pivot = Cartesian3.add( position, Cartesian3.multiplyByScalar( cornerDirection, scalar * width, cornerDirection ), pivot ); start = Cartesian3.add( pivot, Cartesian3.multiplyByScalar(left, width, start), start ); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); subdividedHeights = subdivideHeights( scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity ); subdividedPositions = PolylinePipeline.generateArc({ positions: scratch2Array, granularity: granularity, ellipsoid: ellipsoid, }); finalPositions = addPositions( subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1 ); left = Cartesian3.cross(surfaceNormal, forward, left); left = Cartesian3.normalize(left, left); end = Cartesian3.add( pivot, Cartesian3.multiplyByScalar(left, width, end), end ); if ( cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED ) { computeRoundCorner( pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints ); } else { cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection); finalPositions = addPosition( position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat ); } previousPosition = Cartesian3.clone(end, previousPosition); } else { pivot = Cartesian3.add( position, Cartesian3.multiplyByScalar( cornerDirection, scalar * width, cornerDirection ), pivot ); start = Cartesian3.add( pivot, Cartesian3.multiplyByScalar(left, -width, start), start ); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); subdividedHeights = subdivideHeights( scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity ); subdividedPositions = PolylinePipeline.generateArc({ positions: scratch2Array, granularity: granularity, ellipsoid: ellipsoid, }); finalPositions = addPositions( subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1 ); left = Cartesian3.cross(surfaceNormal, forward, left); left = Cartesian3.normalize(left, left); end = Cartesian3.add( pivot, Cartesian3.multiplyByScalar(left, -width, end), end ); if ( cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED ) { computeRoundCorner( pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints ); } else { finalPositions = addPosition( position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat ); } previousPosition = Cartesian3.clone(end, previousPosition); } backward = Cartesian3.negate(forward, backward); } else { finalPositions = addPosition( previousPosition, left, shapeForSides, finalPositions, ellipsoid, h0 + heightOffset, 1, 1 ); previousPosition = position; } h0 = h1; h1 = heights[i + 1]; position = nextPosition; } scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]); subdividedHeights = subdivideHeights( scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity ); subdividedPositions = PolylinePipeline.generateArc({ positions: scratch2Array, granularity: granularity, ellipsoid: ellipsoid, }); finalPositions = addPositions( subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1 ); if (duplicatePoints) { ends = addPosition( position, left, shapeForEnds, ends, ellipsoid, h1 + heightOffset, 1, 1 ); } length = finalPositions.length; const posLength = duplicatePoints ? length + ends.length : length; const combinedPositions = new Float64Array(posLength); combinedPositions.set(finalPositions); if (duplicatePoints) { combinedPositions.set(ends, length); } return combinedPositions; }; export default PolylineVolumeGeometryLibrary;