GroundPolylineGeometry.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651
  1. import ApproximateTerrainHeights from "./ApproximateTerrainHeights.js";
  2. import ArcType from "./ArcType.js";
  3. import arrayRemoveDuplicates from "./arrayRemoveDuplicates.js";
  4. import BoundingSphere from "./BoundingSphere.js";
  5. import Cartesian3 from "./Cartesian3.js";
  6. import Cartographic from "./Cartographic.js";
  7. import Check from "./Check.js";
  8. import ComponentDatatype from "./ComponentDatatype.js";
  9. import defaultValue from "./defaultValue.js";
  10. import defined from "./defined.js";
  11. import DeveloperError from "./DeveloperError.js";
  12. import Ellipsoid from "./Ellipsoid.js";
  13. import EllipsoidGeodesic from "./EllipsoidGeodesic.js";
  14. import EllipsoidRhumbLine from "./EllipsoidRhumbLine.js";
  15. import EncodedCartesian3 from "./EncodedCartesian3.js";
  16. import GeographicProjection from "./GeographicProjection.js";
  17. import Geometry from "./Geometry.js";
  18. import GeometryAttribute from "./GeometryAttribute.js";
  19. import IntersectionTests from "./IntersectionTests.js";
  20. import CesiumMath from "./Math.js";
  21. import Matrix3 from "./Matrix3.js";
  22. import Plane from "./Plane.js";
  23. import Quaternion from "./Quaternion.js";
  24. import Rectangle from "./Rectangle.js";
  25. import WebMercatorProjection from "./WebMercatorProjection.js";
  26. var PROJECTIONS = [GeographicProjection, WebMercatorProjection];
  27. var PROJECTION_COUNT = PROJECTIONS.length;
  28. var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30.0));
  29. var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150.0));
  30. // Initial heights for constructing the wall.
  31. // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps
  32. // prevent precision problems with planes in the shader.
  33. // Putting the start point of a plane at ApproximateTerrainHeights._defaultMinTerrainHeight,
  34. // which is a highly conservative bound, usually puts the plane origin several thousands
  35. // of meters away from the actual terrain, causing floating point problems when checking
  36. // fragments on terrain against the plane.
  37. // Ellipsoid height is generally much closer.
  38. // The initial max height is arbitrary.
  39. // Both heights are corrected using ApproximateTerrainHeights for computing the actual volume geometry.
  40. var WALL_INITIAL_MIN_HEIGHT = 0.0;
  41. var WALL_INITIAL_MAX_HEIGHT = 1000.0;
  42. /**
  43. * A description of a polyline on terrain or 3D Tiles. Only to be used with {@link GroundPolylinePrimitive}.
  44. *
  45. * @alias GroundPolylineGeometry
  46. * @constructor
  47. *
  48. * @param {Object} options Options with the following properties:
  49. * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored.
  50. * @param {Number} [options.width=1.0] The screen space width in pixels.
  51. * @param {Number} [options.granularity=9999.0] The distance interval in meters used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation.
  52. * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
  53. * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
  54. *
  55. * @exception {DeveloperError} At least two positions are required.
  56. *
  57. * @see GroundPolylinePrimitive
  58. *
  59. * @example
  60. * var positions = Cesium.Cartesian3.fromDegreesArray([
  61. * -112.1340164450331, 36.05494287836128,
  62. * -112.08821010582645, 36.097804071380715,
  63. * -112.13296079730024, 36.168769146801104
  64. * ]);
  65. *
  66. * var geometry = new Cesium.GroundPolylineGeometry({
  67. * positions : positions
  68. * });
  69. */
  70. function GroundPolylineGeometry(options) {
  71. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  72. var positions = options.positions;
  73. //>>includeStart('debug', pragmas.debug);
  74. if (!defined(positions) || positions.length < 2) {
  75. throw new DeveloperError("At least two positions are required.");
  76. }
  77. if (
  78. defined(options.arcType) &&
  79. options.arcType !== ArcType.GEODESIC &&
  80. options.arcType !== ArcType.RHUMB
  81. ) {
  82. throw new DeveloperError(
  83. "Valid options for arcType are ArcType.GEODESIC and ArcType.RHUMB."
  84. );
  85. }
  86. //>>includeEnd('debug');
  87. /**
  88. * The screen space width in pixels.
  89. * @type {Number}
  90. */
  91. this.width = defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry.
  92. this._positions = positions;
  93. /**
  94. * The distance interval used for interpolating options.points. Zero indicates no interpolation.
  95. * Default of 9999.0 allows centimeter accuracy with 32 bit floating point.
  96. * @type {Boolean}
  97. * @default 9999.0
  98. */
  99. this.granularity = defaultValue(options.granularity, 9999.0);
  100. /**
  101. * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
  102. * If the geometry has two positions this parameter will be ignored.
  103. * @type {Boolean}
  104. * @default false
  105. */
  106. this.loop = defaultValue(options.loop, false);
  107. /**
  108. * The type of path the polyline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
  109. * @type {ArcType}
  110. * @default ArcType.GEODESIC
  111. */
  112. this.arcType = defaultValue(options.arcType, ArcType.GEODESIC);
  113. this._ellipsoid = Ellipsoid.WGS84;
  114. // MapProjections can't be packed, so store the index to a known MapProjection.
  115. this._projectionIndex = 0;
  116. this._workerName = "createGroundPolylineGeometry";
  117. // Used by GroundPolylinePrimitive to signal worker that scenemode is 3D only.
  118. this._scene3DOnly = false;
  119. }
  120. Object.defineProperties(GroundPolylineGeometry.prototype, {
  121. /**
  122. * The number of elements used to pack the object into an array.
  123. * @memberof GroundPolylineGeometry.prototype
  124. * @type {Number}
  125. * @readonly
  126. * @private
  127. */
  128. packedLength: {
  129. get: function () {
  130. return (
  131. 1.0 +
  132. this._positions.length * 3 +
  133. 1.0 +
  134. 1.0 +
  135. 1.0 +
  136. Ellipsoid.packedLength +
  137. 1.0 +
  138. 1.0
  139. );
  140. },
  141. },
  142. });
  143. /**
  144. * Set the GroundPolylineGeometry's projection and ellipsoid.
  145. * Used by GroundPolylinePrimitive to signal scene information to the geometry for generating 2D attributes.
  146. *
  147. * @param {GroundPolylineGeometry} groundPolylineGeometry GroundPolylinGeometry describing a polyline on terrain or 3D Tiles.
  148. * @param {Projection} mapProjection A MapProjection used for projecting cartographic coordinates to 2D.
  149. * @private
  150. */
  151. GroundPolylineGeometry.setProjectionAndEllipsoid = function (
  152. groundPolylineGeometry,
  153. mapProjection
  154. ) {
  155. var projectionIndex = 0;
  156. for (var i = 0; i < PROJECTION_COUNT; i++) {
  157. if (mapProjection instanceof PROJECTIONS[i]) {
  158. projectionIndex = i;
  159. break;
  160. }
  161. }
  162. groundPolylineGeometry._projectionIndex = projectionIndex;
  163. groundPolylineGeometry._ellipsoid = mapProjection.ellipsoid;
  164. };
  165. var cart3Scratch1 = new Cartesian3();
  166. var cart3Scratch2 = new Cartesian3();
  167. var cart3Scratch3 = new Cartesian3();
  168. function computeRightNormal(start, end, maxHeight, ellipsoid, result) {
  169. var startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1);
  170. var startTop = getPosition(ellipsoid, start, maxHeight, cart3Scratch2);
  171. var endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3);
  172. var up = direction(startTop, startBottom, cart3Scratch2);
  173. var forward = direction(endBottom, startBottom, cart3Scratch3);
  174. Cartesian3.cross(forward, up, result);
  175. return Cartesian3.normalize(result, result);
  176. }
  177. var interpolatedCartographicScratch = new Cartographic();
  178. var interpolatedBottomScratch = new Cartesian3();
  179. var interpolatedTopScratch = new Cartesian3();
  180. var interpolatedNormalScratch = new Cartesian3();
  181. function interpolateSegment(
  182. start,
  183. end,
  184. minHeight,
  185. maxHeight,
  186. granularity,
  187. arcType,
  188. ellipsoid,
  189. normalsArray,
  190. bottomPositionsArray,
  191. topPositionsArray,
  192. cartographicsArray
  193. ) {
  194. if (granularity === 0.0) {
  195. return;
  196. }
  197. var ellipsoidLine;
  198. if (arcType === ArcType.GEODESIC) {
  199. ellipsoidLine = new EllipsoidGeodesic(start, end, ellipsoid);
  200. } else if (arcType === ArcType.RHUMB) {
  201. ellipsoidLine = new EllipsoidRhumbLine(start, end, ellipsoid);
  202. }
  203. var surfaceDistance = ellipsoidLine.surfaceDistance;
  204. if (surfaceDistance < granularity) {
  205. return;
  206. }
  207. // Compute rightwards normal applicable at all interpolated points
  208. var interpolatedNormal = computeRightNormal(
  209. start,
  210. end,
  211. maxHeight,
  212. ellipsoid,
  213. interpolatedNormalScratch
  214. );
  215. var segments = Math.ceil(surfaceDistance / granularity);
  216. var interpointDistance = surfaceDistance / segments;
  217. var distanceFromStart = interpointDistance;
  218. var pointsToAdd = segments - 1;
  219. var packIndex = normalsArray.length;
  220. for (var i = 0; i < pointsToAdd; i++) {
  221. var interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(
  222. distanceFromStart,
  223. interpolatedCartographicScratch
  224. );
  225. var interpolatedBottom = getPosition(
  226. ellipsoid,
  227. interpolatedCartographic,
  228. minHeight,
  229. interpolatedBottomScratch
  230. );
  231. var interpolatedTop = getPosition(
  232. ellipsoid,
  233. interpolatedCartographic,
  234. maxHeight,
  235. interpolatedTopScratch
  236. );
  237. Cartesian3.pack(interpolatedNormal, normalsArray, packIndex);
  238. Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex);
  239. Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex);
  240. cartographicsArray.push(interpolatedCartographic.latitude);
  241. cartographicsArray.push(interpolatedCartographic.longitude);
  242. packIndex += 3;
  243. distanceFromStart += interpointDistance;
  244. }
  245. }
  246. var heightlessCartographicScratch = new Cartographic();
  247. function getPosition(ellipsoid, cartographic, height, result) {
  248. Cartographic.clone(cartographic, heightlessCartographicScratch);
  249. heightlessCartographicScratch.height = height;
  250. return Cartographic.toCartesian(
  251. heightlessCartographicScratch,
  252. ellipsoid,
  253. result
  254. );
  255. }
  256. /**
  257. * Stores the provided instance into the provided array.
  258. *
  259. * @param {PolygonGeometry} value The value to pack.
  260. * @param {Number[]} array The array to pack into.
  261. * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
  262. *
  263. * @returns {Number[]} The array that was packed into
  264. */
  265. GroundPolylineGeometry.pack = function (value, array, startingIndex) {
  266. //>>includeStart('debug', pragmas.debug);
  267. Check.typeOf.object("value", value);
  268. Check.defined("array", array);
  269. //>>includeEnd('debug');
  270. var index = defaultValue(startingIndex, 0);
  271. var positions = value._positions;
  272. var positionsLength = positions.length;
  273. array[index++] = positionsLength;
  274. for (var i = 0; i < positionsLength; ++i) {
  275. var cartesian = positions[i];
  276. Cartesian3.pack(cartesian, array, index);
  277. index += 3;
  278. }
  279. array[index++] = value.granularity;
  280. array[index++] = value.loop ? 1.0 : 0.0;
  281. array[index++] = value.arcType;
  282. Ellipsoid.pack(value._ellipsoid, array, index);
  283. index += Ellipsoid.packedLength;
  284. array[index++] = value._projectionIndex;
  285. array[index++] = value._scene3DOnly ? 1.0 : 0.0;
  286. return array;
  287. };
  288. /**
  289. * Retrieves an instance from a packed array.
  290. *
  291. * @param {Number[]} array The packed array.
  292. * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
  293. * @param {PolygonGeometry} [result] The object into which to store the result.
  294. */
  295. GroundPolylineGeometry.unpack = function (array, startingIndex, result) {
  296. //>>includeStart('debug', pragmas.debug);
  297. Check.defined("array", array);
  298. //>>includeEnd('debug');
  299. var index = defaultValue(startingIndex, 0);
  300. var positionsLength = array[index++];
  301. var positions = new Array(positionsLength);
  302. for (var i = 0; i < positionsLength; i++) {
  303. positions[i] = Cartesian3.unpack(array, index);
  304. index += 3;
  305. }
  306. var granularity = array[index++];
  307. var loop = array[index++] === 1.0;
  308. var arcType = array[index++];
  309. var ellipsoid = Ellipsoid.unpack(array, index);
  310. index += Ellipsoid.packedLength;
  311. var projectionIndex = array[index++];
  312. var scene3DOnly = array[index++] === 1.0;
  313. if (!defined(result)) {
  314. result = new GroundPolylineGeometry({
  315. positions: positions,
  316. });
  317. }
  318. result._positions = positions;
  319. result.granularity = granularity;
  320. result.loop = loop;
  321. result.arcType = arcType;
  322. result._ellipsoid = ellipsoid;
  323. result._projectionIndex = projectionIndex;
  324. result._scene3DOnly = scene3DOnly;
  325. return result;
  326. };
  327. function direction(target, origin, result) {
  328. Cartesian3.subtract(target, origin, result);
  329. Cartesian3.normalize(result, result);
  330. return result;
  331. }
  332. function tangentDirection(target, origin, up, result) {
  333. result = direction(target, origin, result);
  334. // orthogonalize
  335. result = Cartesian3.cross(result, up, result);
  336. result = Cartesian3.normalize(result, result);
  337. result = Cartesian3.cross(up, result, result);
  338. return result;
  339. }
  340. var toPreviousScratch = new Cartesian3();
  341. var toNextScratch = new Cartesian3();
  342. var forwardScratch = new Cartesian3();
  343. var vertexUpScratch = new Cartesian3();
  344. var cosine90 = 0.0;
  345. var cosine180 = -1.0;
  346. function computeVertexMiterNormal(
  347. previousBottom,
  348. vertexBottom,
  349. vertexTop,
  350. nextBottom,
  351. result
  352. ) {
  353. var up = direction(vertexTop, vertexBottom, vertexUpScratch);
  354. // Compute vectors pointing towards neighboring points but tangent to this point on the ellipsoid
  355. var toPrevious = tangentDirection(
  356. previousBottom,
  357. vertexBottom,
  358. up,
  359. toPreviousScratch
  360. );
  361. var toNext = tangentDirection(nextBottom, vertexBottom, up, toNextScratch);
  362. // Check if tangents are almost opposite - if so, no need to miter.
  363. if (
  364. CesiumMath.equalsEpsilon(
  365. Cartesian3.dot(toPrevious, toNext),
  366. cosine180,
  367. CesiumMath.EPSILON5
  368. )
  369. ) {
  370. result = Cartesian3.cross(up, toPrevious, result);
  371. result = Cartesian3.normalize(result, result);
  372. return result;
  373. }
  374. // Average directions to previous and to next in the plane of Up
  375. result = Cartesian3.add(toNext, toPrevious, result);
  376. result = Cartesian3.normalize(result, result);
  377. // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards")
  378. var forward = Cartesian3.cross(up, result, forwardScratch);
  379. if (Cartesian3.dot(toNext, forward) < cosine90) {
  380. result = Cartesian3.negate(result, result);
  381. }
  382. return result;
  383. }
  384. var XZ_PLANE = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Y);
  385. var previousBottomScratch = new Cartesian3();
  386. var vertexBottomScratch = new Cartesian3();
  387. var vertexTopScratch = new Cartesian3();
  388. var nextBottomScratch = new Cartesian3();
  389. var vertexNormalScratch = new Cartesian3();
  390. var intersectionScratch = new Cartesian3();
  391. var cartographicScratch0 = new Cartographic();
  392. var cartographicScratch1 = new Cartographic();
  393. var cartographicIntersectionScratch = new Cartographic();
  394. /**
  395. * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere.
  396. * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain or 3D Tiles.
  397. * Should not be called independent of {@link GroundPolylinePrimitive}.
  398. *
  399. * @param {GroundPolylineGeometry} groundPolylineGeometry
  400. * @private
  401. */
  402. GroundPolylineGeometry.createGeometry = function (groundPolylineGeometry) {
  403. var compute2dAttributes = !groundPolylineGeometry._scene3DOnly;
  404. var loop = groundPolylineGeometry.loop;
  405. var ellipsoid = groundPolylineGeometry._ellipsoid;
  406. var granularity = groundPolylineGeometry.granularity;
  407. var arcType = groundPolylineGeometry.arcType;
  408. var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](
  409. ellipsoid
  410. );
  411. var minHeight = WALL_INITIAL_MIN_HEIGHT;
  412. var maxHeight = WALL_INITIAL_MAX_HEIGHT;
  413. var index;
  414. var i;
  415. var positions = groundPolylineGeometry._positions;
  416. var positionsLength = positions.length;
  417. if (positionsLength === 2) {
  418. loop = false;
  419. }
  420. // Split positions across the IDL and the Prime Meridian as well.
  421. // Split across prime meridian because very large geometries crossing the Prime Meridian but not the IDL
  422. // may get split by the plane of IDL + Prime Meridian.
  423. var p0;
  424. var p1;
  425. var c0;
  426. var c1;
  427. var rhumbLine = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
  428. var intersection;
  429. var intersectionCartographic;
  430. var intersectionLongitude;
  431. var splitPositions = [positions[0]];
  432. for (i = 0; i < positionsLength - 1; i++) {
  433. p0 = positions[i];
  434. p1 = positions[i + 1];
  435. intersection = IntersectionTests.lineSegmentPlane(
  436. p0,
  437. p1,
  438. XZ_PLANE,
  439. intersectionScratch
  440. );
  441. if (
  442. defined(intersection) &&
  443. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  444. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)
  445. ) {
  446. if (groundPolylineGeometry.arcType === ArcType.GEODESIC) {
  447. splitPositions.push(Cartesian3.clone(intersection));
  448. } else if (groundPolylineGeometry.arcType === ArcType.RHUMB) {
  449. intersectionLongitude = ellipsoid.cartesianToCartographic(
  450. intersection,
  451. cartographicScratch0
  452. ).longitude;
  453. c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
  454. c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
  455. rhumbLine.setEndPoints(c0, c1);
  456. intersectionCartographic = rhumbLine.findIntersectionWithLongitude(
  457. intersectionLongitude,
  458. cartographicIntersectionScratch
  459. );
  460. intersection = ellipsoid.cartographicToCartesian(
  461. intersectionCartographic,
  462. intersectionScratch
  463. );
  464. if (
  465. defined(intersection) &&
  466. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  467. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)
  468. ) {
  469. splitPositions.push(Cartesian3.clone(intersection));
  470. }
  471. }
  472. }
  473. splitPositions.push(p1);
  474. }
  475. if (loop) {
  476. p0 = positions[positionsLength - 1];
  477. p1 = positions[0];
  478. intersection = IntersectionTests.lineSegmentPlane(
  479. p0,
  480. p1,
  481. XZ_PLANE,
  482. intersectionScratch
  483. );
  484. if (
  485. defined(intersection) &&
  486. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  487. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)
  488. ) {
  489. if (groundPolylineGeometry.arcType === ArcType.GEODESIC) {
  490. splitPositions.push(Cartesian3.clone(intersection));
  491. } else if (groundPolylineGeometry.arcType === ArcType.RHUMB) {
  492. intersectionLongitude = ellipsoid.cartesianToCartographic(
  493. intersection,
  494. cartographicScratch0
  495. ).longitude;
  496. c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
  497. c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
  498. rhumbLine.setEndPoints(c0, c1);
  499. intersectionCartographic = rhumbLine.findIntersectionWithLongitude(
  500. intersectionLongitude,
  501. cartographicIntersectionScratch
  502. );
  503. intersection = ellipsoid.cartographicToCartesian(
  504. intersectionCartographic,
  505. intersectionScratch
  506. );
  507. if (
  508. defined(intersection) &&
  509. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  510. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)
  511. ) {
  512. splitPositions.push(Cartesian3.clone(intersection));
  513. }
  514. }
  515. }
  516. }
  517. var cartographicsLength = splitPositions.length;
  518. var cartographics = new Array(cartographicsLength);
  519. for (i = 0; i < cartographicsLength; i++) {
  520. var cartographic = Cartographic.fromCartesian(splitPositions[i], ellipsoid);
  521. cartographic.height = 0.0;
  522. cartographics[i] = cartographic;
  523. }
  524. cartographics = arrayRemoveDuplicates(
  525. cartographics,
  526. Cartographic.equalsEpsilon
  527. );
  528. cartographicsLength = cartographics.length;
  529. if (cartographicsLength < 2) {
  530. return undefined;
  531. }
  532. /**** Build heap-side arrays for positions, interpolated cartographics, and normals from which to compute vertices ****/
  533. // We build a "wall" and then decompose it into separately connected component "volumes" because we need a lot
  534. // of information about the wall. Also, this simplifies interpolation.
  535. // Convention: "next" and "end" are locally forward to each segment of the wall,
  536. // and we are computing normals pointing towards the local right side of the vertices in each segment.
  537. var cartographicsArray = [];
  538. var normalsArray = [];
  539. var bottomPositionsArray = [];
  540. var topPositionsArray = [];
  541. var previousBottom = previousBottomScratch;
  542. var vertexBottom = vertexBottomScratch;
  543. var vertexTop = vertexTopScratch;
  544. var nextBottom = nextBottomScratch;
  545. var vertexNormal = vertexNormalScratch;
  546. // First point - either loop or attach a "perpendicular" normal
  547. var startCartographic = cartographics[0];
  548. var nextCartographic = cartographics[1];
  549. var prestartCartographic = cartographics[cartographicsLength - 1];
  550. previousBottom = getPosition(
  551. ellipsoid,
  552. prestartCartographic,
  553. minHeight,
  554. previousBottom
  555. );
  556. nextBottom = getPosition(ellipsoid, nextCartographic, minHeight, nextBottom);
  557. vertexBottom = getPosition(
  558. ellipsoid,
  559. startCartographic,
  560. minHeight,
  561. vertexBottom
  562. );
  563. vertexTop = getPosition(ellipsoid, startCartographic, maxHeight, vertexTop);
  564. if (loop) {
  565. vertexNormal = computeVertexMiterNormal(
  566. previousBottom,
  567. vertexBottom,
  568. vertexTop,
  569. nextBottom,
  570. vertexNormal
  571. );
  572. } else {
  573. vertexNormal = computeRightNormal(
  574. startCartographic,
  575. nextCartographic,
  576. maxHeight,
  577. ellipsoid,
  578. vertexNormal
  579. );
  580. }
  581. Cartesian3.pack(vertexNormal, normalsArray, 0);
  582. Cartesian3.pack(vertexBottom, bottomPositionsArray, 0);
  583. Cartesian3.pack(vertexTop, topPositionsArray, 0);
  584. cartographicsArray.push(startCartographic.latitude);
  585. cartographicsArray.push(startCartographic.longitude);
  586. interpolateSegment(
  587. startCartographic,
  588. nextCartographic,
  589. minHeight,
  590. maxHeight,
  591. granularity,
  592. arcType,
  593. ellipsoid,
  594. normalsArray,
  595. bottomPositionsArray,
  596. topPositionsArray,
  597. cartographicsArray
  598. );
  599. // All inbetween points
  600. for (i = 1; i < cartographicsLength - 1; ++i) {
  601. previousBottom = Cartesian3.clone(vertexBottom, previousBottom);
  602. vertexBottom = Cartesian3.clone(nextBottom, vertexBottom);
  603. var vertexCartographic = cartographics[i];
  604. getPosition(ellipsoid, vertexCartographic, maxHeight, vertexTop);
  605. getPosition(ellipsoid, cartographics[i + 1], minHeight, nextBottom);
  606. computeVertexMiterNormal(
  607. previousBottom,
  608. vertexBottom,
  609. vertexTop,
  610. nextBottom,
  611. vertexNormal
  612. );
  613. index = normalsArray.length;
  614. Cartesian3.pack(vertexNormal, normalsArray, index);
  615. Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  616. Cartesian3.pack(vertexTop, topPositionsArray, index);
  617. cartographicsArray.push(vertexCartographic.latitude);
  618. cartographicsArray.push(vertexCartographic.longitude);
  619. interpolateSegment(
  620. cartographics[i],
  621. cartographics[i + 1],
  622. minHeight,
  623. maxHeight,
  624. granularity,
  625. arcType,
  626. ellipsoid,
  627. normalsArray,
  628. bottomPositionsArray,
  629. topPositionsArray,
  630. cartographicsArray
  631. );
  632. }
  633. // Last point - either loop or attach a normal "perpendicular" to the wall.
  634. var endCartographic = cartographics[cartographicsLength - 1];
  635. var preEndCartographic = cartographics[cartographicsLength - 2];
  636. vertexBottom = getPosition(
  637. ellipsoid,
  638. endCartographic,
  639. minHeight,
  640. vertexBottom
  641. );
  642. vertexTop = getPosition(ellipsoid, endCartographic, maxHeight, vertexTop);
  643. if (loop) {
  644. var postEndCartographic = cartographics[0];
  645. previousBottom = getPosition(
  646. ellipsoid,
  647. preEndCartographic,
  648. minHeight,
  649. previousBottom
  650. );
  651. nextBottom = getPosition(
  652. ellipsoid,
  653. postEndCartographic,
  654. minHeight,
  655. nextBottom
  656. );
  657. vertexNormal = computeVertexMiterNormal(
  658. previousBottom,
  659. vertexBottom,
  660. vertexTop,
  661. nextBottom,
  662. vertexNormal
  663. );
  664. } else {
  665. vertexNormal = computeRightNormal(
  666. preEndCartographic,
  667. endCartographic,
  668. maxHeight,
  669. ellipsoid,
  670. vertexNormal
  671. );
  672. }
  673. index = normalsArray.length;
  674. Cartesian3.pack(vertexNormal, normalsArray, index);
  675. Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  676. Cartesian3.pack(vertexTop, topPositionsArray, index);
  677. cartographicsArray.push(endCartographic.latitude);
  678. cartographicsArray.push(endCartographic.longitude);
  679. if (loop) {
  680. interpolateSegment(
  681. endCartographic,
  682. startCartographic,
  683. minHeight,
  684. maxHeight,
  685. granularity,
  686. arcType,
  687. ellipsoid,
  688. normalsArray,
  689. bottomPositionsArray,
  690. topPositionsArray,
  691. cartographicsArray
  692. );
  693. index = normalsArray.length;
  694. for (i = 0; i < 3; ++i) {
  695. normalsArray[index + i] = normalsArray[i];
  696. bottomPositionsArray[index + i] = bottomPositionsArray[i];
  697. topPositionsArray[index + i] = topPositionsArray[i];
  698. }
  699. cartographicsArray.push(startCartographic.latitude);
  700. cartographicsArray.push(startCartographic.longitude);
  701. }
  702. return generateGeometryAttributes(
  703. loop,
  704. projection,
  705. bottomPositionsArray,
  706. topPositionsArray,
  707. normalsArray,
  708. cartographicsArray,
  709. compute2dAttributes
  710. );
  711. };
  712. // If the end normal angle is too steep compared to the direction of the line segment,
  713. // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point
  714. // For ultra precision we would want to project into a plane, but in practice this is sufficient.
  715. var lineDirectionScratch = new Cartesian3();
  716. var matrix3Scratch = new Matrix3();
  717. var quaternionScratch = new Quaternion();
  718. function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) {
  719. var lineDirection = direction(endBottom, startBottom, lineDirectionScratch);
  720. var dot = Cartesian3.dot(lineDirection, endGeometryNormal);
  721. if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) {
  722. var vertexUp = direction(endTop, endBottom, vertexUpScratch);
  723. var angle =
  724. dot < MITER_BREAK_LARGE
  725. ? CesiumMath.PI_OVER_TWO
  726. : -CesiumMath.PI_OVER_TWO;
  727. var quaternion = Quaternion.fromAxisAngle(
  728. vertexUp,
  729. angle,
  730. quaternionScratch
  731. );
  732. var rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch);
  733. Matrix3.multiplyByVector(
  734. rotationMatrix,
  735. endGeometryNormal,
  736. endGeometryNormal
  737. );
  738. return true;
  739. }
  740. return false;
  741. }
  742. var endPosCartographicScratch = new Cartographic();
  743. var normalStartpointScratch = new Cartesian3();
  744. var normalEndpointScratch = new Cartesian3();
  745. function projectNormal(
  746. projection,
  747. cartographic,
  748. normal,
  749. projectedPosition,
  750. result
  751. ) {
  752. var position = Cartographic.toCartesian(
  753. cartographic,
  754. projection._ellipsoid,
  755. normalStartpointScratch
  756. );
  757. var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch);
  758. var flipNormal = false;
  759. var ellipsoid = projection._ellipsoid;
  760. var normalEndpointCartographic = ellipsoid.cartesianToCartographic(
  761. normalEndpoint,
  762. endPosCartographicScratch
  763. );
  764. // If normal crosses the IDL, go the other way and flip the result.
  765. // In practice this almost never happens because the cartographic start
  766. // and end points of each segment are "nudged" to be on the same side
  767. // of the IDL and slightly away from the IDL.
  768. if (
  769. Math.abs(cartographic.longitude - normalEndpointCartographic.longitude) >
  770. CesiumMath.PI_OVER_TWO
  771. ) {
  772. flipNormal = true;
  773. normalEndpoint = Cartesian3.subtract(
  774. position,
  775. normal,
  776. normalEndpointScratch
  777. );
  778. normalEndpointCartographic = ellipsoid.cartesianToCartographic(
  779. normalEndpoint,
  780. endPosCartographicScratch
  781. );
  782. }
  783. normalEndpointCartographic.height = 0.0;
  784. var normalEndpointProjected = projection.project(
  785. normalEndpointCartographic,
  786. result
  787. );
  788. result = Cartesian3.subtract(
  789. normalEndpointProjected,
  790. projectedPosition,
  791. result
  792. );
  793. result.z = 0.0;
  794. result = Cartesian3.normalize(result, result);
  795. if (flipNormal) {
  796. Cartesian3.negate(result, result);
  797. }
  798. return result;
  799. }
  800. var adjustHeightNormalScratch = new Cartesian3();
  801. var adjustHeightOffsetScratch = new Cartesian3();
  802. function adjustHeights(
  803. bottom,
  804. top,
  805. minHeight,
  806. maxHeight,
  807. adjustHeightBottom,
  808. adjustHeightTop
  809. ) {
  810. // bottom and top should be at WALL_INITIAL_MIN_HEIGHT and WALL_INITIAL_MAX_HEIGHT, respectively
  811. var adjustHeightNormal = Cartesian3.subtract(
  812. top,
  813. bottom,
  814. adjustHeightNormalScratch
  815. );
  816. Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal);
  817. var distanceForBottom = minHeight - WALL_INITIAL_MIN_HEIGHT;
  818. var adjustHeightOffset = Cartesian3.multiplyByScalar(
  819. adjustHeightNormal,
  820. distanceForBottom,
  821. adjustHeightOffsetScratch
  822. );
  823. Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom);
  824. var distanceForTop = maxHeight - WALL_INITIAL_MAX_HEIGHT;
  825. adjustHeightOffset = Cartesian3.multiplyByScalar(
  826. adjustHeightNormal,
  827. distanceForTop,
  828. adjustHeightOffsetScratch
  829. );
  830. Cartesian3.add(top, adjustHeightOffset, adjustHeightTop);
  831. }
  832. var nudgeDirectionScratch = new Cartesian3();
  833. function nudgeXZ(start, end) {
  834. var startToXZdistance = Plane.getPointDistance(XZ_PLANE, start);
  835. var endToXZdistance = Plane.getPointDistance(XZ_PLANE, end);
  836. var offset = nudgeDirectionScratch;
  837. // Larger epsilon than what's used in GeometryPipeline, a centimeter in world space
  838. if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON2)) {
  839. offset = direction(end, start, offset);
  840. Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON2, offset);
  841. Cartesian3.add(start, offset, start);
  842. } else if (
  843. CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON2)
  844. ) {
  845. offset = direction(start, end, offset);
  846. Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON2, offset);
  847. Cartesian3.add(end, offset, end);
  848. }
  849. }
  850. // "Nudge" cartographic coordinates so start and end are on the same side of the IDL.
  851. // Nudge amounts are tiny, basically just an IDL flip.
  852. // Only used for 2D/CV.
  853. function nudgeCartographic(start, end) {
  854. var absStartLon = Math.abs(start.longitude);
  855. var absEndLon = Math.abs(end.longitude);
  856. if (
  857. CesiumMath.equalsEpsilon(absStartLon, CesiumMath.PI, CesiumMath.EPSILON11)
  858. ) {
  859. var endSign = CesiumMath.sign(end.longitude);
  860. start.longitude = endSign * (absStartLon - CesiumMath.EPSILON11);
  861. return 1;
  862. } else if (
  863. CesiumMath.equalsEpsilon(absEndLon, CesiumMath.PI, CesiumMath.EPSILON11)
  864. ) {
  865. var startSign = CesiumMath.sign(start.longitude);
  866. end.longitude = startSign * (absEndLon - CesiumMath.EPSILON11);
  867. return 2;
  868. }
  869. return 0;
  870. }
  871. var startCartographicScratch = new Cartographic();
  872. var endCartographicScratch = new Cartographic();
  873. var segmentStartTopScratch = new Cartesian3();
  874. var segmentEndTopScratch = new Cartesian3();
  875. var segmentStartBottomScratch = new Cartesian3();
  876. var segmentEndBottomScratch = new Cartesian3();
  877. var segmentStartNormalScratch = new Cartesian3();
  878. var segmentEndNormalScratch = new Cartesian3();
  879. var getHeightCartographics = [startCartographicScratch, endCartographicScratch];
  880. var getHeightRectangleScratch = new Rectangle();
  881. var adjustHeightStartTopScratch = new Cartesian3();
  882. var adjustHeightEndTopScratch = new Cartesian3();
  883. var adjustHeightStartBottomScratch = new Cartesian3();
  884. var adjustHeightEndBottomScratch = new Cartesian3();
  885. var segmentStart2DScratch = new Cartesian3();
  886. var segmentEnd2DScratch = new Cartesian3();
  887. var segmentStartNormal2DScratch = new Cartesian3();
  888. var segmentEndNormal2DScratch = new Cartesian3();
  889. var offsetScratch = new Cartesian3();
  890. var startUpScratch = new Cartesian3();
  891. var endUpScratch = new Cartesian3();
  892. var rightScratch = new Cartesian3();
  893. var startPlaneNormalScratch = new Cartesian3();
  894. var endPlaneNormalScratch = new Cartesian3();
  895. var encodeScratch = new EncodedCartesian3();
  896. var encodeScratch2D = new EncodedCartesian3();
  897. var forwardOffset2DScratch = new Cartesian3();
  898. var right2DScratch = new Cartesian3();
  899. var normalNudgeScratch = new Cartesian3();
  900. var scratchBoundingSpheres = [new BoundingSphere(), new BoundingSphere()];
  901. // Winding order is reversed so each segment's volume is inside-out
  902. var REFERENCE_INDICES = [
  903. 0,
  904. 2,
  905. 1,
  906. 0,
  907. 3,
  908. 2, // right
  909. 0,
  910. 7,
  911. 3,
  912. 0,
  913. 4,
  914. 7, // start
  915. 0,
  916. 5,
  917. 4,
  918. 0,
  919. 1,
  920. 5, // bottom
  921. 5,
  922. 7,
  923. 4,
  924. 5,
  925. 6,
  926. 7, // left
  927. 5,
  928. 2,
  929. 6,
  930. 5,
  931. 1,
  932. 2, // end
  933. 3,
  934. 6,
  935. 2,
  936. 3,
  937. 7,
  938. 6, // top
  939. ];
  940. var REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length;
  941. // Decompose the "wall" into a series of shadow volumes.
  942. // Each shadow volume's vertices encode a description of the line it contains,
  943. // including mitering planes at the end points, a plane along the line itself,
  944. // and attributes for computing length-wise texture coordinates.
  945. function generateGeometryAttributes(
  946. loop,
  947. projection,
  948. bottomPositionsArray,
  949. topPositionsArray,
  950. normalsArray,
  951. cartographicsArray,
  952. compute2dAttributes
  953. ) {
  954. var i;
  955. var index;
  956. var ellipsoid = projection._ellipsoid;
  957. // Each segment will have 8 vertices
  958. var segmentCount = bottomPositionsArray.length / 3 - 1;
  959. var vertexCount = segmentCount * 8;
  960. var arraySizeVec4 = vertexCount * 4;
  961. var indexCount = segmentCount * 36;
  962. var indices =
  963. vertexCount > 65535
  964. ? new Uint32Array(indexCount)
  965. : new Uint16Array(indexCount);
  966. var positionsArray = new Float64Array(vertexCount * 3);
  967. var startHiAndForwardOffsetX = new Float32Array(arraySizeVec4);
  968. var startLoAndForwardOffsetY = new Float32Array(arraySizeVec4);
  969. var startNormalAndForwardOffsetZ = new Float32Array(arraySizeVec4);
  970. var endNormalAndTextureCoordinateNormalizationX = new Float32Array(
  971. arraySizeVec4
  972. );
  973. var rightNormalAndTextureCoordinateNormalizationY = new Float32Array(
  974. arraySizeVec4
  975. );
  976. var startHiLo2D;
  977. var offsetAndRight2D;
  978. var startEndNormals2D;
  979. var texcoordNormalization2D;
  980. if (compute2dAttributes) {
  981. startHiLo2D = new Float32Array(arraySizeVec4);
  982. offsetAndRight2D = new Float32Array(arraySizeVec4);
  983. startEndNormals2D = new Float32Array(arraySizeVec4);
  984. texcoordNormalization2D = new Float32Array(vertexCount * 2);
  985. }
  986. /*** Compute total lengths for texture coordinate normalization ***/
  987. // 2D
  988. var cartographicsLength = cartographicsArray.length / 2;
  989. var length2D = 0.0;
  990. var startCartographic = startCartographicScratch;
  991. startCartographic.height = 0.0;
  992. var endCartographic = endCartographicScratch;
  993. endCartographic.height = 0.0;
  994. var segmentStartCartesian = segmentStartTopScratch;
  995. var segmentEndCartesian = segmentEndTopScratch;
  996. if (compute2dAttributes) {
  997. index = 0;
  998. for (i = 1; i < cartographicsLength; i++) {
  999. // Don't clone anything from previous segment b/c possible IDL touch
  1000. startCartographic.latitude = cartographicsArray[index];
  1001. startCartographic.longitude = cartographicsArray[index + 1];
  1002. endCartographic.latitude = cartographicsArray[index + 2];
  1003. endCartographic.longitude = cartographicsArray[index + 3];
  1004. segmentStartCartesian = projection.project(
  1005. startCartographic,
  1006. segmentStartCartesian
  1007. );
  1008. segmentEndCartesian = projection.project(
  1009. endCartographic,
  1010. segmentEndCartesian
  1011. );
  1012. length2D += Cartesian3.distance(
  1013. segmentStartCartesian,
  1014. segmentEndCartesian
  1015. );
  1016. index += 2;
  1017. }
  1018. }
  1019. // 3D
  1020. var positionsLength = topPositionsArray.length / 3;
  1021. segmentEndCartesian = Cartesian3.unpack(
  1022. topPositionsArray,
  1023. 0,
  1024. segmentEndCartesian
  1025. );
  1026. var length3D = 0.0;
  1027. index = 3;
  1028. for (i = 1; i < positionsLength; i++) {
  1029. segmentStartCartesian = Cartesian3.clone(
  1030. segmentEndCartesian,
  1031. segmentStartCartesian
  1032. );
  1033. segmentEndCartesian = Cartesian3.unpack(
  1034. topPositionsArray,
  1035. index,
  1036. segmentEndCartesian
  1037. );
  1038. length3D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian);
  1039. index += 3;
  1040. }
  1041. /*** Generate segments ***/
  1042. var j;
  1043. index = 3;
  1044. var cartographicsIndex = 0;
  1045. var vec2sWriteIndex = 0;
  1046. var vec3sWriteIndex = 0;
  1047. var vec4sWriteIndex = 0;
  1048. var miterBroken = false;
  1049. var endBottom = Cartesian3.unpack(
  1050. bottomPositionsArray,
  1051. 0,
  1052. segmentEndBottomScratch
  1053. );
  1054. var endTop = Cartesian3.unpack(topPositionsArray, 0, segmentEndTopScratch);
  1055. var endGeometryNormal = Cartesian3.unpack(
  1056. normalsArray,
  1057. 0,
  1058. segmentEndNormalScratch
  1059. );
  1060. if (loop) {
  1061. var preEndBottom = Cartesian3.unpack(
  1062. bottomPositionsArray,
  1063. bottomPositionsArray.length - 6,
  1064. segmentStartBottomScratch
  1065. );
  1066. if (breakMiter(endGeometryNormal, preEndBottom, endBottom, endTop)) {
  1067. // Miter broken as if for the last point in the loop, needs to be inverted for first point (clone of endBottom)
  1068. endGeometryNormal = Cartesian3.negate(
  1069. endGeometryNormal,
  1070. endGeometryNormal
  1071. );
  1072. }
  1073. }
  1074. var lengthSoFar3D = 0.0;
  1075. var lengthSoFar2D = 0.0;
  1076. // For translating bounding volume
  1077. var sumHeights = 0.0;
  1078. for (i = 0; i < segmentCount; i++) {
  1079. var startBottom = Cartesian3.clone(endBottom, segmentStartBottomScratch);
  1080. var startTop = Cartesian3.clone(endTop, segmentStartTopScratch);
  1081. var startGeometryNormal = Cartesian3.clone(
  1082. endGeometryNormal,
  1083. segmentStartNormalScratch
  1084. );
  1085. if (miterBroken) {
  1086. startGeometryNormal = Cartesian3.negate(
  1087. startGeometryNormal,
  1088. startGeometryNormal
  1089. );
  1090. }
  1091. endBottom = Cartesian3.unpack(
  1092. bottomPositionsArray,
  1093. index,
  1094. segmentEndBottomScratch
  1095. );
  1096. endTop = Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch);
  1097. endGeometryNormal = Cartesian3.unpack(
  1098. normalsArray,
  1099. index,
  1100. segmentEndNormalScratch
  1101. );
  1102. miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop);
  1103. // 2D - don't clone anything from previous segment b/c possible IDL touch
  1104. startCartographic.latitude = cartographicsArray[cartographicsIndex];
  1105. startCartographic.longitude = cartographicsArray[cartographicsIndex + 1];
  1106. endCartographic.latitude = cartographicsArray[cartographicsIndex + 2];
  1107. endCartographic.longitude = cartographicsArray[cartographicsIndex + 3];
  1108. var start2D;
  1109. var end2D;
  1110. var startGeometryNormal2D;
  1111. var endGeometryNormal2D;
  1112. if (compute2dAttributes) {
  1113. var nudgeResult = nudgeCartographic(startCartographic, endCartographic);
  1114. start2D = projection.project(startCartographic, segmentStart2DScratch);
  1115. end2D = projection.project(endCartographic, segmentEnd2DScratch);
  1116. var direction2D = direction(end2D, start2D, forwardOffset2DScratch);
  1117. direction2D.y = Math.abs(direction2D.y);
  1118. startGeometryNormal2D = segmentStartNormal2DScratch;
  1119. endGeometryNormal2D = segmentEndNormal2DScratch;
  1120. if (
  1121. nudgeResult === 0 ||
  1122. Cartesian3.dot(direction2D, Cartesian3.UNIT_Y) > MITER_BREAK_SMALL
  1123. ) {
  1124. // No nudge - project the original normal
  1125. // Or, if the line's angle relative to the IDL is very acute,
  1126. // in which case snapping will produce oddly shaped volumes.
  1127. startGeometryNormal2D = projectNormal(
  1128. projection,
  1129. startCartographic,
  1130. startGeometryNormal,
  1131. start2D,
  1132. segmentStartNormal2DScratch
  1133. );
  1134. endGeometryNormal2D = projectNormal(
  1135. projection,
  1136. endCartographic,
  1137. endGeometryNormal,
  1138. end2D,
  1139. segmentEndNormal2DScratch
  1140. );
  1141. } else if (nudgeResult === 1) {
  1142. // Start is close to IDL - snap start normal to align with IDL
  1143. endGeometryNormal2D = projectNormal(
  1144. projection,
  1145. endCartographic,
  1146. endGeometryNormal,
  1147. end2D,
  1148. segmentEndNormal2DScratch
  1149. );
  1150. startGeometryNormal2D.x = 0.0;
  1151. // If start longitude is negative and end longitude is less negative, relative right is unit -Y
  1152. // If start longitude is positive and end longitude is less positive, relative right is unit +Y
  1153. startGeometryNormal2D.y = CesiumMath.sign(
  1154. startCartographic.longitude - Math.abs(endCartographic.longitude)
  1155. );
  1156. startGeometryNormal2D.z = 0.0;
  1157. } else {
  1158. // End is close to IDL - snap end normal to align with IDL
  1159. startGeometryNormal2D = projectNormal(
  1160. projection,
  1161. startCartographic,
  1162. startGeometryNormal,
  1163. start2D,
  1164. segmentStartNormal2DScratch
  1165. );
  1166. endGeometryNormal2D.x = 0.0;
  1167. // If end longitude is negative and start longitude is less negative, relative right is unit Y
  1168. // If end longitude is positive and start longitude is less positive, relative right is unit -Y
  1169. endGeometryNormal2D.y = CesiumMath.sign(
  1170. startCartographic.longitude - endCartographic.longitude
  1171. );
  1172. endGeometryNormal2D.z = 0.0;
  1173. }
  1174. }
  1175. /****************************************
  1176. * Geometry descriptors of a "line on terrain,"
  1177. * as opposed to the "shadow volume used to draw
  1178. * the line on terrain":
  1179. * - position of start + offset to end
  1180. * - start, end, and right-facing planes
  1181. * - encoded texture coordinate offsets
  1182. ****************************************/
  1183. /* 3D */
  1184. var segmentLength3D = Cartesian3.distance(startTop, endTop);
  1185. var encodedStart = EncodedCartesian3.fromCartesian(
  1186. startBottom,
  1187. encodeScratch
  1188. );
  1189. var forwardOffset = Cartesian3.subtract(
  1190. endBottom,
  1191. startBottom,
  1192. offsetScratch
  1193. );
  1194. var forward = Cartesian3.normalize(forwardOffset, rightScratch);
  1195. var startUp = Cartesian3.subtract(startTop, startBottom, startUpScratch);
  1196. startUp = Cartesian3.normalize(startUp, startUp);
  1197. var rightNormal = Cartesian3.cross(forward, startUp, rightScratch);
  1198. rightNormal = Cartesian3.normalize(rightNormal, rightNormal);
  1199. var startPlaneNormal = Cartesian3.cross(
  1200. startUp,
  1201. startGeometryNormal,
  1202. startPlaneNormalScratch
  1203. );
  1204. startPlaneNormal = Cartesian3.normalize(startPlaneNormal, startPlaneNormal);
  1205. var endUp = Cartesian3.subtract(endTop, endBottom, endUpScratch);
  1206. endUp = Cartesian3.normalize(endUp, endUp);
  1207. var endPlaneNormal = Cartesian3.cross(
  1208. endGeometryNormal,
  1209. endUp,
  1210. endPlaneNormalScratch
  1211. );
  1212. endPlaneNormal = Cartesian3.normalize(endPlaneNormal, endPlaneNormal);
  1213. var texcoordNormalization3DX = segmentLength3D / length3D;
  1214. var texcoordNormalization3DY = lengthSoFar3D / length3D;
  1215. /* 2D */
  1216. var segmentLength2D = 0.0;
  1217. var encodedStart2D;
  1218. var forwardOffset2D;
  1219. var right2D;
  1220. var texcoordNormalization2DX = 0.0;
  1221. var texcoordNormalization2DY = 0.0;
  1222. if (compute2dAttributes) {
  1223. segmentLength2D = Cartesian3.distance(start2D, end2D);
  1224. encodedStart2D = EncodedCartesian3.fromCartesian(
  1225. start2D,
  1226. encodeScratch2D
  1227. );
  1228. forwardOffset2D = Cartesian3.subtract(
  1229. end2D,
  1230. start2D,
  1231. forwardOffset2DScratch
  1232. );
  1233. // Right direction is just forward direction rotated by -90 degrees around Z
  1234. // Similarly with plane normals
  1235. right2D = Cartesian3.normalize(forwardOffset2D, right2DScratch);
  1236. var swap = right2D.x;
  1237. right2D.x = right2D.y;
  1238. right2D.y = -swap;
  1239. texcoordNormalization2DX = segmentLength2D / length2D;
  1240. texcoordNormalization2DY = lengthSoFar2D / length2D;
  1241. }
  1242. /** Pack **/
  1243. for (j = 0; j < 8; j++) {
  1244. var vec4Index = vec4sWriteIndex + j * 4;
  1245. var vec2Index = vec2sWriteIndex + j * 2;
  1246. var wIndex = vec4Index + 3;
  1247. // Encode sidedness of vertex relative to right plane in texture coordinate normalization X,
  1248. // whether vertex is top or bottom of volume in sign/magnitude of normalization Y.
  1249. var rightPlaneSide = j < 4 ? 1.0 : -1.0;
  1250. var topBottomSide = j === 2 || j === 3 || j === 6 || j === 7 ? 1.0 : -1.0;
  1251. // 3D
  1252. Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index);
  1253. startHiAndForwardOffsetX[wIndex] = forwardOffset.x;
  1254. Cartesian3.pack(encodedStart.low, startLoAndForwardOffsetY, vec4Index);
  1255. startLoAndForwardOffsetY[wIndex] = forwardOffset.y;
  1256. Cartesian3.pack(
  1257. startPlaneNormal,
  1258. startNormalAndForwardOffsetZ,
  1259. vec4Index
  1260. );
  1261. startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z;
  1262. Cartesian3.pack(
  1263. endPlaneNormal,
  1264. endNormalAndTextureCoordinateNormalizationX,
  1265. vec4Index
  1266. );
  1267. endNormalAndTextureCoordinateNormalizationX[wIndex] =
  1268. texcoordNormalization3DX * rightPlaneSide;
  1269. Cartesian3.pack(
  1270. rightNormal,
  1271. rightNormalAndTextureCoordinateNormalizationY,
  1272. vec4Index
  1273. );
  1274. var texcoordNormalization = texcoordNormalization3DY * topBottomSide;
  1275. if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
  1276. texcoordNormalization = 9.0; // some value greater than 1.0
  1277. }
  1278. rightNormalAndTextureCoordinateNormalizationY[
  1279. wIndex
  1280. ] = texcoordNormalization;
  1281. // 2D
  1282. if (compute2dAttributes) {
  1283. startHiLo2D[vec4Index] = encodedStart2D.high.x;
  1284. startHiLo2D[vec4Index + 1] = encodedStart2D.high.y;
  1285. startHiLo2D[vec4Index + 2] = encodedStart2D.low.x;
  1286. startHiLo2D[vec4Index + 3] = encodedStart2D.low.y;
  1287. startEndNormals2D[vec4Index] = -startGeometryNormal2D.y;
  1288. startEndNormals2D[vec4Index + 1] = startGeometryNormal2D.x;
  1289. startEndNormals2D[vec4Index + 2] = endGeometryNormal2D.y;
  1290. startEndNormals2D[vec4Index + 3] = -endGeometryNormal2D.x;
  1291. offsetAndRight2D[vec4Index] = forwardOffset2D.x;
  1292. offsetAndRight2D[vec4Index + 1] = forwardOffset2D.y;
  1293. offsetAndRight2D[vec4Index + 2] = right2D.x;
  1294. offsetAndRight2D[vec4Index + 3] = right2D.y;
  1295. texcoordNormalization2D[vec2Index] =
  1296. texcoordNormalization2DX * rightPlaneSide;
  1297. texcoordNormalization = texcoordNormalization2DY * topBottomSide;
  1298. if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
  1299. texcoordNormalization = 9.0; // some value greater than 1.0
  1300. }
  1301. texcoordNormalization2D[vec2Index + 1] = texcoordNormalization;
  1302. }
  1303. }
  1304. // Adjust height of volume in 3D
  1305. var adjustHeightStartBottom = adjustHeightStartBottomScratch;
  1306. var adjustHeightEndBottom = adjustHeightEndBottomScratch;
  1307. var adjustHeightStartTop = adjustHeightStartTopScratch;
  1308. var adjustHeightEndTop = adjustHeightEndTopScratch;
  1309. var getHeightsRectangle = Rectangle.fromCartographicArray(
  1310. getHeightCartographics,
  1311. getHeightRectangleScratch
  1312. );
  1313. var minMaxHeights = ApproximateTerrainHeights.getMinimumMaximumHeights(
  1314. getHeightsRectangle,
  1315. ellipsoid
  1316. );
  1317. var minHeight = minMaxHeights.minimumTerrainHeight;
  1318. var maxHeight = minMaxHeights.maximumTerrainHeight;
  1319. sumHeights += minHeight;
  1320. sumHeights += maxHeight;
  1321. adjustHeights(
  1322. startBottom,
  1323. startTop,
  1324. minHeight,
  1325. maxHeight,
  1326. adjustHeightStartBottom,
  1327. adjustHeightStartTop
  1328. );
  1329. adjustHeights(
  1330. endBottom,
  1331. endTop,
  1332. minHeight,
  1333. maxHeight,
  1334. adjustHeightEndBottom,
  1335. adjustHeightEndTop
  1336. );
  1337. // Nudge the positions away from the "polyline" a little bit to prevent errors in GeometryPipeline
  1338. var normalNudge = Cartesian3.multiplyByScalar(
  1339. rightNormal,
  1340. CesiumMath.EPSILON5,
  1341. normalNudgeScratch
  1342. );
  1343. Cartesian3.add(
  1344. adjustHeightStartBottom,
  1345. normalNudge,
  1346. adjustHeightStartBottom
  1347. );
  1348. Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  1349. Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  1350. Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  1351. // If the segment is very close to the XZ plane, nudge the vertices slightly to avoid touching it.
  1352. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
  1353. nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  1354. Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex);
  1355. Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3);
  1356. Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6);
  1357. Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9);
  1358. normalNudge = Cartesian3.multiplyByScalar(
  1359. rightNormal,
  1360. -2.0 * CesiumMath.EPSILON5,
  1361. normalNudgeScratch
  1362. );
  1363. Cartesian3.add(
  1364. adjustHeightStartBottom,
  1365. normalNudge,
  1366. adjustHeightStartBottom
  1367. );
  1368. Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  1369. Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  1370. Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  1371. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
  1372. nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  1373. Cartesian3.pack(
  1374. adjustHeightStartBottom,
  1375. positionsArray,
  1376. vec3sWriteIndex + 12
  1377. );
  1378. Cartesian3.pack(
  1379. adjustHeightEndBottom,
  1380. positionsArray,
  1381. vec3sWriteIndex + 15
  1382. );
  1383. Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18);
  1384. Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21);
  1385. cartographicsIndex += 2;
  1386. index += 3;
  1387. vec2sWriteIndex += 16;
  1388. vec3sWriteIndex += 24;
  1389. vec4sWriteIndex += 32;
  1390. lengthSoFar3D += segmentLength3D;
  1391. lengthSoFar2D += segmentLength2D;
  1392. }
  1393. index = 0;
  1394. var indexOffset = 0;
  1395. for (i = 0; i < segmentCount; i++) {
  1396. for (j = 0; j < REFERENCE_INDICES_LENGTH; j++) {
  1397. indices[index + j] = REFERENCE_INDICES[j] + indexOffset;
  1398. }
  1399. indexOffset += 8;
  1400. index += REFERENCE_INDICES_LENGTH;
  1401. }
  1402. var boundingSpheres = scratchBoundingSpheres;
  1403. BoundingSphere.fromVertices(
  1404. bottomPositionsArray,
  1405. Cartesian3.ZERO,
  1406. 3,
  1407. boundingSpheres[0]
  1408. );
  1409. BoundingSphere.fromVertices(
  1410. topPositionsArray,
  1411. Cartesian3.ZERO,
  1412. 3,
  1413. boundingSpheres[1]
  1414. );
  1415. var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
  1416. // Adjust bounding sphere height and radius to cover more of the volume
  1417. boundingSphere.radius += sumHeights / (segmentCount * 2.0);
  1418. var attributes = {
  1419. position: new GeometryAttribute({
  1420. componentDatatype: ComponentDatatype.DOUBLE,
  1421. componentsPerAttribute: 3,
  1422. normalize: false,
  1423. values: positionsArray,
  1424. }),
  1425. startHiAndForwardOffsetX: getVec4GeometryAttribute(
  1426. startHiAndForwardOffsetX
  1427. ),
  1428. startLoAndForwardOffsetY: getVec4GeometryAttribute(
  1429. startLoAndForwardOffsetY
  1430. ),
  1431. startNormalAndForwardOffsetZ: getVec4GeometryAttribute(
  1432. startNormalAndForwardOffsetZ
  1433. ),
  1434. endNormalAndTextureCoordinateNormalizationX: getVec4GeometryAttribute(
  1435. endNormalAndTextureCoordinateNormalizationX
  1436. ),
  1437. rightNormalAndTextureCoordinateNormalizationY: getVec4GeometryAttribute(
  1438. rightNormalAndTextureCoordinateNormalizationY
  1439. ),
  1440. };
  1441. if (compute2dAttributes) {
  1442. attributes.startHiLo2D = getVec4GeometryAttribute(startHiLo2D);
  1443. attributes.offsetAndRight2D = getVec4GeometryAttribute(offsetAndRight2D);
  1444. attributes.startEndNormals2D = getVec4GeometryAttribute(startEndNormals2D);
  1445. attributes.texcoordNormalization2D = new GeometryAttribute({
  1446. componentDatatype: ComponentDatatype.FLOAT,
  1447. componentsPerAttribute: 2,
  1448. normalize: false,
  1449. values: texcoordNormalization2D,
  1450. });
  1451. }
  1452. return new Geometry({
  1453. attributes: attributes,
  1454. indices: indices,
  1455. boundingSphere: boundingSphere,
  1456. });
  1457. }
  1458. function getVec4GeometryAttribute(typedArray) {
  1459. return new GeometryAttribute({
  1460. componentDatatype: ComponentDatatype.FLOAT,
  1461. componentsPerAttribute: 4,
  1462. normalize: false,
  1463. values: typedArray,
  1464. });
  1465. }
  1466. /**
  1467. * Approximates an ellipsoid-tangent vector in 2D by projecting the end point into 2D.
  1468. * Exposed for testing.
  1469. *
  1470. * @param {MapProjection} projection Map Projection for projecting coordinates to 2D.
  1471. * @param {Cartographic} cartographic The cartographic origin point of the normal.
  1472. * Used to check if the normal crosses the IDL during projection.
  1473. * @param {Cartesian3} normal The normal in 3D.
  1474. * @param {Cartesian3} projectedPosition The projected origin point of the normal in 2D.
  1475. * @param {Cartesian3} result Result parameter on which to store the projected normal.
  1476. * @private
  1477. */
  1478. GroundPolylineGeometry._projectNormal = projectNormal;
  1479. export default GroundPolylineGeometry;