createGroundPolylineGeometry.js 74 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134
  1. define(['./Transforms-bc45e707', './Matrix2-e1298525', './Matrix3-41c58dde', './Check-6ede7e26', './defaultValue-fe22d8c0', './Math-0a2ac845', './ArcType-2d9abbbc', './arrayRemoveDuplicates-d2061e85', './ComponentDatatype-cf1fa08e', './EllipsoidGeodesic-5b3623dc', './EllipsoidRhumbLine-ef872433', './EncodedCartesian3-57415c8a', './GeometryAttribute-a466e9c7', './IntersectionTests-88c49b2e', './Plane-4c3d403b', './WebMercatorProjection-13ed1a6e', './combine-d9581036', './RuntimeError-ef395448', './WebGLConstants-0b1ce7ba'], (function (Transforms, Matrix2, Matrix3, Check, defaultValue, Math$1, ArcType, arrayRemoveDuplicates, ComponentDatatype, EllipsoidGeodesic, EllipsoidRhumbLine, EncodedCartesian3, GeometryAttribute, IntersectionTests, Plane, WebMercatorProjection, combine, RuntimeError, WebGLConstants) { 'use strict';
  2. /**
  3. * A tiling scheme for geometry referenced to a simple {@link GeographicProjection} where
  4. * longitude and latitude are directly mapped to X and Y. This projection is commonly
  5. * known as geographic, equirectangular, equidistant cylindrical, or plate carrée.
  6. *
  7. * @alias GeographicTilingScheme
  8. * @constructor
  9. *
  10. * @param {object} [options] Object with the following properties:
  11. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
  12. * the WGS84 ellipsoid.
  13. * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the tiling scheme.
  14. * @param {number} [options.numberOfLevelZeroTilesX=2] The number of tiles in the X direction at level zero of
  15. * the tile tree.
  16. * @param {number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
  17. * the tile tree.
  18. */
  19. function GeographicTilingScheme(options) {
  20. options = defaultValue.defaultValue(options, defaultValue.defaultValue.EMPTY_OBJECT);
  21. this._ellipsoid = defaultValue.defaultValue(options.ellipsoid, Matrix3.Ellipsoid.WGS84);
  22. this._rectangle = defaultValue.defaultValue(options.rectangle, Matrix2.Rectangle.MAX_VALUE);
  23. this._projection = new Transforms.GeographicProjection(this._ellipsoid);
  24. this._numberOfLevelZeroTilesX = defaultValue.defaultValue(
  25. options.numberOfLevelZeroTilesX,
  26. 2
  27. );
  28. this._numberOfLevelZeroTilesY = defaultValue.defaultValue(
  29. options.numberOfLevelZeroTilesY,
  30. 1
  31. );
  32. }
  33. Object.defineProperties(GeographicTilingScheme.prototype, {
  34. /**
  35. * Gets the ellipsoid that is tiled by this tiling scheme.
  36. * @memberof GeographicTilingScheme.prototype
  37. * @type {Ellipsoid}
  38. */
  39. ellipsoid: {
  40. get: function () {
  41. return this._ellipsoid;
  42. },
  43. },
  44. /**
  45. * Gets the rectangle, in radians, covered by this tiling scheme.
  46. * @memberof GeographicTilingScheme.prototype
  47. * @type {Rectangle}
  48. */
  49. rectangle: {
  50. get: function () {
  51. return this._rectangle;
  52. },
  53. },
  54. /**
  55. * Gets the map projection used by this tiling scheme.
  56. * @memberof GeographicTilingScheme.prototype
  57. * @type {MapProjection}
  58. */
  59. projection: {
  60. get: function () {
  61. return this._projection;
  62. },
  63. },
  64. });
  65. /**
  66. * Gets the total number of tiles in the X direction at a specified level-of-detail.
  67. *
  68. * @param {number} level The level-of-detail.
  69. * @returns {number} The number of tiles in the X direction at the given level.
  70. */
  71. GeographicTilingScheme.prototype.getNumberOfXTilesAtLevel = function (level) {
  72. return this._numberOfLevelZeroTilesX << level;
  73. };
  74. /**
  75. * Gets the total number of tiles in the Y direction at a specified level-of-detail.
  76. *
  77. * @param {number} level The level-of-detail.
  78. * @returns {number} The number of tiles in the Y direction at the given level.
  79. */
  80. GeographicTilingScheme.prototype.getNumberOfYTilesAtLevel = function (level) {
  81. return this._numberOfLevelZeroTilesY << level;
  82. };
  83. /**
  84. * Transforms a rectangle specified in geodetic radians to the native coordinate system
  85. * of this tiling scheme.
  86. *
  87. * @param {Rectangle} rectangle The rectangle to transform.
  88. * @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
  89. * should be created.
  90. * @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
  91. * is undefined.
  92. */
  93. GeographicTilingScheme.prototype.rectangleToNativeRectangle = function (
  94. rectangle,
  95. result
  96. ) {
  97. //>>includeStart('debug', pragmas.debug);
  98. Check.Check.defined("rectangle", rectangle);
  99. //>>includeEnd('debug');
  100. const west = Math$1.CesiumMath.toDegrees(rectangle.west);
  101. const south = Math$1.CesiumMath.toDegrees(rectangle.south);
  102. const east = Math$1.CesiumMath.toDegrees(rectangle.east);
  103. const north = Math$1.CesiumMath.toDegrees(rectangle.north);
  104. if (!defaultValue.defined(result)) {
  105. return new Matrix2.Rectangle(west, south, east, north);
  106. }
  107. result.west = west;
  108. result.south = south;
  109. result.east = east;
  110. result.north = north;
  111. return result;
  112. };
  113. /**
  114. * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
  115. * of the tiling scheme.
  116. *
  117. * @param {number} x The integer x coordinate of the tile.
  118. * @param {number} y The integer y coordinate of the tile.
  119. * @param {number} level The tile level-of-detail. Zero is the least detailed.
  120. * @param {object} [result] The instance to which to copy the result, or undefined if a new instance
  121. * should be created.
  122. * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
  123. * if 'result' is undefined.
  124. */
  125. GeographicTilingScheme.prototype.tileXYToNativeRectangle = function (
  126. x,
  127. y,
  128. level,
  129. result
  130. ) {
  131. const rectangleRadians = this.tileXYToRectangle(x, y, level, result);
  132. rectangleRadians.west = Math$1.CesiumMath.toDegrees(rectangleRadians.west);
  133. rectangleRadians.south = Math$1.CesiumMath.toDegrees(rectangleRadians.south);
  134. rectangleRadians.east = Math$1.CesiumMath.toDegrees(rectangleRadians.east);
  135. rectangleRadians.north = Math$1.CesiumMath.toDegrees(rectangleRadians.north);
  136. return rectangleRadians;
  137. };
  138. /**
  139. * Converts tile x, y coordinates and level to a cartographic rectangle in radians.
  140. *
  141. * @param {number} x The integer x coordinate of the tile.
  142. * @param {number} y The integer y coordinate of the tile.
  143. * @param {number} level The tile level-of-detail. Zero is the least detailed.
  144. * @param {object} [result] The instance to which to copy the result, or undefined if a new instance
  145. * should be created.
  146. * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
  147. * if 'result' is undefined.
  148. */
  149. GeographicTilingScheme.prototype.tileXYToRectangle = function (
  150. x,
  151. y,
  152. level,
  153. result
  154. ) {
  155. const rectangle = this._rectangle;
  156. const xTiles = this.getNumberOfXTilesAtLevel(level);
  157. const yTiles = this.getNumberOfYTilesAtLevel(level);
  158. const xTileWidth = rectangle.width / xTiles;
  159. const west = x * xTileWidth + rectangle.west;
  160. const east = (x + 1) * xTileWidth + rectangle.west;
  161. const yTileHeight = rectangle.height / yTiles;
  162. const north = rectangle.north - y * yTileHeight;
  163. const south = rectangle.north - (y + 1) * yTileHeight;
  164. if (!defaultValue.defined(result)) {
  165. result = new Matrix2.Rectangle(west, south, east, north);
  166. }
  167. result.west = west;
  168. result.south = south;
  169. result.east = east;
  170. result.north = north;
  171. return result;
  172. };
  173. /**
  174. * Calculates the tile x, y coordinates of the tile containing
  175. * a given cartographic position.
  176. *
  177. * @param {Cartographic} position The position.
  178. * @param {number} level The tile level-of-detail. Zero is the least detailed.
  179. * @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
  180. * should be created.
  181. * @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
  182. * if 'result' is undefined.
  183. */
  184. GeographicTilingScheme.prototype.positionToTileXY = function (
  185. position,
  186. level,
  187. result
  188. ) {
  189. const rectangle = this._rectangle;
  190. if (!Matrix2.Rectangle.contains(rectangle, position)) {
  191. // outside the bounds of the tiling scheme
  192. return undefined;
  193. }
  194. const xTiles = this.getNumberOfXTilesAtLevel(level);
  195. const yTiles = this.getNumberOfYTilesAtLevel(level);
  196. const xTileWidth = rectangle.width / xTiles;
  197. const yTileHeight = rectangle.height / yTiles;
  198. let longitude = position.longitude;
  199. if (rectangle.east < rectangle.west) {
  200. longitude += Math$1.CesiumMath.TWO_PI;
  201. }
  202. let xTileCoordinate = ((longitude - rectangle.west) / xTileWidth) | 0;
  203. if (xTileCoordinate >= xTiles) {
  204. xTileCoordinate = xTiles - 1;
  205. }
  206. let yTileCoordinate =
  207. ((rectangle.north - position.latitude) / yTileHeight) | 0;
  208. if (yTileCoordinate >= yTiles) {
  209. yTileCoordinate = yTiles - 1;
  210. }
  211. if (!defaultValue.defined(result)) {
  212. return new Matrix2.Cartesian2(xTileCoordinate, yTileCoordinate);
  213. }
  214. result.x = xTileCoordinate;
  215. result.y = yTileCoordinate;
  216. return result;
  217. };
  218. const scratchDiagonalCartesianNE = new Matrix3.Cartesian3();
  219. const scratchDiagonalCartesianSW = new Matrix3.Cartesian3();
  220. const scratchDiagonalCartographic = new Matrix3.Cartographic();
  221. const scratchCenterCartesian = new Matrix3.Cartesian3();
  222. const scratchSurfaceCartesian = new Matrix3.Cartesian3();
  223. const scratchBoundingSphere = new Transforms.BoundingSphere();
  224. const tilingScheme = new GeographicTilingScheme();
  225. const scratchCorners = [
  226. new Matrix3.Cartographic(),
  227. new Matrix3.Cartographic(),
  228. new Matrix3.Cartographic(),
  229. new Matrix3.Cartographic(),
  230. ];
  231. const scratchTileXY = new Matrix2.Cartesian2();
  232. /**
  233. * A collection of functions for approximating terrain height
  234. * @private
  235. */
  236. const ApproximateTerrainHeights = {};
  237. /**
  238. * Initializes the minimum and maximum terrain heights
  239. * @return {Promise<void>}
  240. */
  241. ApproximateTerrainHeights.initialize = function () {
  242. let initPromise = ApproximateTerrainHeights._initPromise;
  243. if (defaultValue.defined(initPromise)) {
  244. return initPromise;
  245. }
  246. initPromise = Transforms.Resource.fetchJson(
  247. Transforms.buildModuleUrl("Assets/approximateTerrainHeights.json")
  248. ).then(function (json) {
  249. ApproximateTerrainHeights._terrainHeights = json;
  250. });
  251. ApproximateTerrainHeights._initPromise = initPromise;
  252. return initPromise;
  253. };
  254. /**
  255. * Computes the minimum and maximum terrain heights for a given rectangle
  256. * @param {Rectangle} rectangle The bounding rectangle
  257. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
  258. * @return {{minimumTerrainHeight: number, maximumTerrainHeight: number}}
  259. */
  260. ApproximateTerrainHeights.getMinimumMaximumHeights = function (
  261. rectangle,
  262. ellipsoid
  263. ) {
  264. //>>includeStart('debug', pragmas.debug);
  265. Check.Check.defined("rectangle", rectangle);
  266. if (!defaultValue.defined(ApproximateTerrainHeights._terrainHeights)) {
  267. throw new Check.DeveloperError(
  268. "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
  269. );
  270. }
  271. //>>includeEnd('debug');
  272. ellipsoid = defaultValue.defaultValue(ellipsoid, Matrix3.Ellipsoid.WGS84);
  273. const xyLevel = getTileXYLevel(rectangle);
  274. // Get the terrain min/max for that tile
  275. let minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
  276. let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  277. if (defaultValue.defined(xyLevel)) {
  278. const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;
  279. const heights = ApproximateTerrainHeights._terrainHeights[key];
  280. if (defaultValue.defined(heights)) {
  281. minTerrainHeight = heights[0];
  282. maxTerrainHeight = heights[1];
  283. }
  284. // Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
  285. ellipsoid.cartographicToCartesian(
  286. Matrix2.Rectangle.northeast(rectangle, scratchDiagonalCartographic),
  287. scratchDiagonalCartesianNE
  288. );
  289. ellipsoid.cartographicToCartesian(
  290. Matrix2.Rectangle.southwest(rectangle, scratchDiagonalCartographic),
  291. scratchDiagonalCartesianSW
  292. );
  293. Matrix3.Cartesian3.midpoint(
  294. scratchDiagonalCartesianSW,
  295. scratchDiagonalCartesianNE,
  296. scratchCenterCartesian
  297. );
  298. const surfacePosition = ellipsoid.scaleToGeodeticSurface(
  299. scratchCenterCartesian,
  300. scratchSurfaceCartesian
  301. );
  302. if (defaultValue.defined(surfacePosition)) {
  303. const distance = Matrix3.Cartesian3.distance(
  304. scratchCenterCartesian,
  305. surfacePosition
  306. );
  307. minTerrainHeight = Math.min(minTerrainHeight, -distance);
  308. } else {
  309. minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
  310. }
  311. }
  312. minTerrainHeight = Math.max(
  313. ApproximateTerrainHeights._defaultMinTerrainHeight,
  314. minTerrainHeight
  315. );
  316. return {
  317. minimumTerrainHeight: minTerrainHeight,
  318. maximumTerrainHeight: maxTerrainHeight,
  319. };
  320. };
  321. /**
  322. * Computes the bounding sphere based on the tile heights in the rectangle
  323. * @param {Rectangle} rectangle The bounding rectangle
  324. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
  325. * @return {BoundingSphere} The result bounding sphere
  326. */
  327. ApproximateTerrainHeights.getBoundingSphere = function (rectangle, ellipsoid) {
  328. //>>includeStart('debug', pragmas.debug);
  329. Check.Check.defined("rectangle", rectangle);
  330. if (!defaultValue.defined(ApproximateTerrainHeights._terrainHeights)) {
  331. throw new Check.DeveloperError(
  332. "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
  333. );
  334. }
  335. //>>includeEnd('debug');
  336. ellipsoid = defaultValue.defaultValue(ellipsoid, Matrix3.Ellipsoid.WGS84);
  337. const xyLevel = getTileXYLevel(rectangle);
  338. // Get the terrain max for that tile
  339. let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  340. if (defaultValue.defined(xyLevel)) {
  341. const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;
  342. const heights = ApproximateTerrainHeights._terrainHeights[key];
  343. if (defaultValue.defined(heights)) {
  344. maxTerrainHeight = heights[1];
  345. }
  346. }
  347. const result = Transforms.BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
  348. Transforms.BoundingSphere.fromRectangle3D(
  349. rectangle,
  350. ellipsoid,
  351. maxTerrainHeight,
  352. scratchBoundingSphere
  353. );
  354. return Transforms.BoundingSphere.union(result, scratchBoundingSphere, result);
  355. };
  356. function getTileXYLevel(rectangle) {
  357. Matrix3.Cartographic.fromRadians(
  358. rectangle.east,
  359. rectangle.north,
  360. 0.0,
  361. scratchCorners[0]
  362. );
  363. Matrix3.Cartographic.fromRadians(
  364. rectangle.west,
  365. rectangle.north,
  366. 0.0,
  367. scratchCorners[1]
  368. );
  369. Matrix3.Cartographic.fromRadians(
  370. rectangle.east,
  371. rectangle.south,
  372. 0.0,
  373. scratchCorners[2]
  374. );
  375. Matrix3.Cartographic.fromRadians(
  376. rectangle.west,
  377. rectangle.south,
  378. 0.0,
  379. scratchCorners[3]
  380. );
  381. // Determine which tile the bounding rectangle is in
  382. let lastLevelX = 0,
  383. lastLevelY = 0;
  384. let currentX = 0,
  385. currentY = 0;
  386. const maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel;
  387. let i;
  388. for (i = 0; i <= maxLevel; ++i) {
  389. let failed = false;
  390. for (let j = 0; j < 4; ++j) {
  391. const corner = scratchCorners[j];
  392. tilingScheme.positionToTileXY(corner, i, scratchTileXY);
  393. if (j === 0) {
  394. currentX = scratchTileXY.x;
  395. currentY = scratchTileXY.y;
  396. } else if (currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
  397. failed = true;
  398. break;
  399. }
  400. }
  401. if (failed) {
  402. break;
  403. }
  404. lastLevelX = currentX;
  405. lastLevelY = currentY;
  406. }
  407. if (i === 0) {
  408. return undefined;
  409. }
  410. return {
  411. x: lastLevelX,
  412. y: lastLevelY,
  413. level: i > maxLevel ? maxLevel : i - 1,
  414. };
  415. }
  416. ApproximateTerrainHeights._terrainHeightsMaxLevel = 6;
  417. ApproximateTerrainHeights._defaultMaxTerrainHeight = 9000.0;
  418. ApproximateTerrainHeights._defaultMinTerrainHeight = -100000.0;
  419. ApproximateTerrainHeights._terrainHeights = undefined;
  420. ApproximateTerrainHeights._initPromise = undefined;
  421. Object.defineProperties(ApproximateTerrainHeights, {
  422. /**
  423. * Determines if the terrain heights are initialized and ready to use. To initialize the terrain heights,
  424. * call {@link ApproximateTerrainHeights#initialize} and wait for the returned promise to resolve.
  425. * @type {boolean}
  426. * @readonly
  427. * @memberof ApproximateTerrainHeights
  428. */
  429. initialized: {
  430. get: function () {
  431. return defaultValue.defined(ApproximateTerrainHeights._terrainHeights);
  432. },
  433. },
  434. });
  435. var ApproximateTerrainHeights$1 = ApproximateTerrainHeights;
  436. const PROJECTIONS = [Transforms.GeographicProjection, WebMercatorProjection.WebMercatorProjection];
  437. const PROJECTION_COUNT = PROJECTIONS.length;
  438. const MITER_BREAK_SMALL = Math.cos(Math$1.CesiumMath.toRadians(30.0));
  439. const MITER_BREAK_LARGE = Math.cos(Math$1.CesiumMath.toRadians(150.0));
  440. // Initial heights for constructing the wall.
  441. // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps
  442. // prevent precision problems with planes in the shader.
  443. // Putting the start point of a plane at ApproximateTerrainHeights._defaultMinTerrainHeight,
  444. // which is a highly conservative bound, usually puts the plane origin several thousands
  445. // of meters away from the actual terrain, causing floating point problems when checking
  446. // fragments on terrain against the plane.
  447. // Ellipsoid height is generally much closer.
  448. // The initial max height is arbitrary.
  449. // Both heights are corrected using ApproximateTerrainHeights for computing the actual volume geometry.
  450. const WALL_INITIAL_MIN_HEIGHT = 0.0;
  451. const WALL_INITIAL_MAX_HEIGHT = 1000.0;
  452. /**
  453. * A description of a polyline on terrain or 3D Tiles. Only to be used with {@link GroundPolylinePrimitive}.
  454. *
  455. * @alias GroundPolylineGeometry
  456. * @constructor
  457. *
  458. * @param {object} options Options with the following properties:
  459. * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored.
  460. * @param {number} [options.width=1.0] The screen space width in pixels.
  461. * @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.
  462. * @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.
  463. * @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}.
  464. *
  465. * @exception {DeveloperError} At least two positions are required.
  466. *
  467. * @see GroundPolylinePrimitive
  468. *
  469. * @example
  470. * const positions = Cesium.Cartesian3.fromDegreesArray([
  471. * -112.1340164450331, 36.05494287836128,
  472. * -112.08821010582645, 36.097804071380715,
  473. * -112.13296079730024, 36.168769146801104
  474. * ]);
  475. *
  476. * const geometry = new Cesium.GroundPolylineGeometry({
  477. * positions : positions
  478. * });
  479. */
  480. function GroundPolylineGeometry(options) {
  481. options = defaultValue.defaultValue(options, defaultValue.defaultValue.EMPTY_OBJECT);
  482. const positions = options.positions;
  483. //>>includeStart('debug', pragmas.debug);
  484. if (!defaultValue.defined(positions) || positions.length < 2) {
  485. throw new Check.DeveloperError("At least two positions are required.");
  486. }
  487. if (
  488. defaultValue.defined(options.arcType) &&
  489. options.arcType !== ArcType.ArcType.GEODESIC &&
  490. options.arcType !== ArcType.ArcType.RHUMB
  491. ) {
  492. throw new Check.DeveloperError(
  493. "Valid options for arcType are ArcType.GEODESIC and ArcType.RHUMB."
  494. );
  495. }
  496. //>>includeEnd('debug');
  497. /**
  498. * The screen space width in pixels.
  499. * @type {number}
  500. */
  501. this.width = defaultValue.defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry.
  502. this._positions = positions;
  503. /**
  504. * The distance interval used for interpolating options.points. Zero indicates no interpolation.
  505. * Default of 9999.0 allows centimeter accuracy with 32 bit floating point.
  506. * @type {boolean}
  507. * @default 9999.0
  508. */
  509. this.granularity = defaultValue.defaultValue(options.granularity, 9999.0);
  510. /**
  511. * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
  512. * If the geometry has two positions this parameter will be ignored.
  513. * @type {boolean}
  514. * @default false
  515. */
  516. this.loop = defaultValue.defaultValue(options.loop, false);
  517. /**
  518. * The type of path the polyline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
  519. * @type {ArcType}
  520. * @default ArcType.GEODESIC
  521. */
  522. this.arcType = defaultValue.defaultValue(options.arcType, ArcType.ArcType.GEODESIC);
  523. this._ellipsoid = Matrix3.Ellipsoid.WGS84;
  524. // MapProjections can't be packed, so store the index to a known MapProjection.
  525. this._projectionIndex = 0;
  526. this._workerName = "createGroundPolylineGeometry";
  527. // Used by GroundPolylinePrimitive to signal worker that scenemode is 3D only.
  528. this._scene3DOnly = false;
  529. }
  530. Object.defineProperties(GroundPolylineGeometry.prototype, {
  531. /**
  532. * The number of elements used to pack the object into an array.
  533. * @memberof GroundPolylineGeometry.prototype
  534. * @type {number}
  535. * @readonly
  536. * @private
  537. */
  538. packedLength: {
  539. get: function () {
  540. return (
  541. 1.0 +
  542. this._positions.length * 3 +
  543. 1.0 +
  544. 1.0 +
  545. 1.0 +
  546. Matrix3.Ellipsoid.packedLength +
  547. 1.0 +
  548. 1.0
  549. );
  550. },
  551. },
  552. });
  553. /**
  554. * Set the GroundPolylineGeometry's projection and ellipsoid.
  555. * Used by GroundPolylinePrimitive to signal scene information to the geometry for generating 2D attributes.
  556. *
  557. * @param {GroundPolylineGeometry} groundPolylineGeometry GroundPolylinGeometry describing a polyline on terrain or 3D Tiles.
  558. * @param {Projection} mapProjection A MapProjection used for projecting cartographic coordinates to 2D.
  559. * @private
  560. */
  561. GroundPolylineGeometry.setProjectionAndEllipsoid = function (
  562. groundPolylineGeometry,
  563. mapProjection
  564. ) {
  565. let projectionIndex = 0;
  566. for (let i = 0; i < PROJECTION_COUNT; i++) {
  567. if (mapProjection instanceof PROJECTIONS[i]) {
  568. projectionIndex = i;
  569. break;
  570. }
  571. }
  572. groundPolylineGeometry._projectionIndex = projectionIndex;
  573. groundPolylineGeometry._ellipsoid = mapProjection.ellipsoid;
  574. };
  575. const cart3Scratch1 = new Matrix3.Cartesian3();
  576. const cart3Scratch2 = new Matrix3.Cartesian3();
  577. const cart3Scratch3 = new Matrix3.Cartesian3();
  578. function computeRightNormal(start, end, maxHeight, ellipsoid, result) {
  579. const startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1);
  580. const startTop = getPosition(ellipsoid, start, maxHeight, cart3Scratch2);
  581. const endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3);
  582. const up = direction(startTop, startBottom, cart3Scratch2);
  583. const forward = direction(endBottom, startBottom, cart3Scratch3);
  584. Matrix3.Cartesian3.cross(forward, up, result);
  585. return Matrix3.Cartesian3.normalize(result, result);
  586. }
  587. const interpolatedCartographicScratch = new Matrix3.Cartographic();
  588. const interpolatedBottomScratch = new Matrix3.Cartesian3();
  589. const interpolatedTopScratch = new Matrix3.Cartesian3();
  590. const interpolatedNormalScratch = new Matrix3.Cartesian3();
  591. function interpolateSegment(
  592. start,
  593. end,
  594. minHeight,
  595. maxHeight,
  596. granularity,
  597. arcType,
  598. ellipsoid,
  599. normalsArray,
  600. bottomPositionsArray,
  601. topPositionsArray,
  602. cartographicsArray
  603. ) {
  604. if (granularity === 0.0) {
  605. return;
  606. }
  607. let ellipsoidLine;
  608. if (arcType === ArcType.ArcType.GEODESIC) {
  609. ellipsoidLine = new EllipsoidGeodesic.EllipsoidGeodesic(start, end, ellipsoid);
  610. } else if (arcType === ArcType.ArcType.RHUMB) {
  611. ellipsoidLine = new EllipsoidRhumbLine.EllipsoidRhumbLine(start, end, ellipsoid);
  612. }
  613. const surfaceDistance = ellipsoidLine.surfaceDistance;
  614. if (surfaceDistance < granularity) {
  615. return;
  616. }
  617. // Compute rightwards normal applicable at all interpolated points
  618. const interpolatedNormal = computeRightNormal(
  619. start,
  620. end,
  621. maxHeight,
  622. ellipsoid,
  623. interpolatedNormalScratch
  624. );
  625. const segments = Math.ceil(surfaceDistance / granularity);
  626. const interpointDistance = surfaceDistance / segments;
  627. let distanceFromStart = interpointDistance;
  628. const pointsToAdd = segments - 1;
  629. let packIndex = normalsArray.length;
  630. for (let i = 0; i < pointsToAdd; i++) {
  631. const interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(
  632. distanceFromStart,
  633. interpolatedCartographicScratch
  634. );
  635. const interpolatedBottom = getPosition(
  636. ellipsoid,
  637. interpolatedCartographic,
  638. minHeight,
  639. interpolatedBottomScratch
  640. );
  641. const interpolatedTop = getPosition(
  642. ellipsoid,
  643. interpolatedCartographic,
  644. maxHeight,
  645. interpolatedTopScratch
  646. );
  647. Matrix3.Cartesian3.pack(interpolatedNormal, normalsArray, packIndex);
  648. Matrix3.Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex);
  649. Matrix3.Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex);
  650. cartographicsArray.push(interpolatedCartographic.latitude);
  651. cartographicsArray.push(interpolatedCartographic.longitude);
  652. packIndex += 3;
  653. distanceFromStart += interpointDistance;
  654. }
  655. }
  656. const heightlessCartographicScratch = new Matrix3.Cartographic();
  657. function getPosition(ellipsoid, cartographic, height, result) {
  658. Matrix3.Cartographic.clone(cartographic, heightlessCartographicScratch);
  659. heightlessCartographicScratch.height = height;
  660. return Matrix3.Cartographic.toCartesian(
  661. heightlessCartographicScratch,
  662. ellipsoid,
  663. result
  664. );
  665. }
  666. /**
  667. * Stores the provided instance into the provided array.
  668. *
  669. * @param {PolygonGeometry} value The value to pack.
  670. * @param {number[]} array The array to pack into.
  671. * @param {number} [startingIndex=0] The index into the array at which to start packing the elements.
  672. *
  673. * @returns {number[]} The array that was packed into
  674. */
  675. GroundPolylineGeometry.pack = function (value, array, startingIndex) {
  676. //>>includeStart('debug', pragmas.debug);
  677. Check.Check.typeOf.object("value", value);
  678. Check.Check.defined("array", array);
  679. //>>includeEnd('debug');
  680. let index = defaultValue.defaultValue(startingIndex, 0);
  681. const positions = value._positions;
  682. const positionsLength = positions.length;
  683. array[index++] = positionsLength;
  684. for (let i = 0; i < positionsLength; ++i) {
  685. const cartesian = positions[i];
  686. Matrix3.Cartesian3.pack(cartesian, array, index);
  687. index += 3;
  688. }
  689. array[index++] = value.granularity;
  690. array[index++] = value.loop ? 1.0 : 0.0;
  691. array[index++] = value.arcType;
  692. Matrix3.Ellipsoid.pack(value._ellipsoid, array, index);
  693. index += Matrix3.Ellipsoid.packedLength;
  694. array[index++] = value._projectionIndex;
  695. array[index++] = value._scene3DOnly ? 1.0 : 0.0;
  696. return array;
  697. };
  698. /**
  699. * Retrieves an instance from a packed array.
  700. *
  701. * @param {number[]} array The packed array.
  702. * @param {number} [startingIndex=0] The starting index of the element to be unpacked.
  703. * @param {PolygonGeometry} [result] The object into which to store the result.
  704. */
  705. GroundPolylineGeometry.unpack = function (array, startingIndex, result) {
  706. //>>includeStart('debug', pragmas.debug);
  707. Check.Check.defined("array", array);
  708. //>>includeEnd('debug');
  709. let index = defaultValue.defaultValue(startingIndex, 0);
  710. const positionsLength = array[index++];
  711. const positions = new Array(positionsLength);
  712. for (let i = 0; i < positionsLength; i++) {
  713. positions[i] = Matrix3.Cartesian3.unpack(array, index);
  714. index += 3;
  715. }
  716. const granularity = array[index++];
  717. const loop = array[index++] === 1.0;
  718. const arcType = array[index++];
  719. const ellipsoid = Matrix3.Ellipsoid.unpack(array, index);
  720. index += Matrix3.Ellipsoid.packedLength;
  721. const projectionIndex = array[index++];
  722. const scene3DOnly = array[index++] === 1.0;
  723. if (!defaultValue.defined(result)) {
  724. result = new GroundPolylineGeometry({
  725. positions: positions,
  726. });
  727. }
  728. result._positions = positions;
  729. result.granularity = granularity;
  730. result.loop = loop;
  731. result.arcType = arcType;
  732. result._ellipsoid = ellipsoid;
  733. result._projectionIndex = projectionIndex;
  734. result._scene3DOnly = scene3DOnly;
  735. return result;
  736. };
  737. function direction(target, origin, result) {
  738. Matrix3.Cartesian3.subtract(target, origin, result);
  739. Matrix3.Cartesian3.normalize(result, result);
  740. return result;
  741. }
  742. function tangentDirection(target, origin, up, result) {
  743. result = direction(target, origin, result);
  744. // orthogonalize
  745. result = Matrix3.Cartesian3.cross(result, up, result);
  746. result = Matrix3.Cartesian3.normalize(result, result);
  747. result = Matrix3.Cartesian3.cross(up, result, result);
  748. return result;
  749. }
  750. const toPreviousScratch = new Matrix3.Cartesian3();
  751. const toNextScratch = new Matrix3.Cartesian3();
  752. const forwardScratch = new Matrix3.Cartesian3();
  753. const vertexUpScratch = new Matrix3.Cartesian3();
  754. const cosine90 = 0.0;
  755. const cosine180 = -1.0;
  756. function computeVertexMiterNormal(
  757. previousBottom,
  758. vertexBottom,
  759. vertexTop,
  760. nextBottom,
  761. result
  762. ) {
  763. const up = direction(vertexTop, vertexBottom, vertexUpScratch);
  764. // Compute vectors pointing towards neighboring points but tangent to this point on the ellipsoid
  765. const toPrevious = tangentDirection(
  766. previousBottom,
  767. vertexBottom,
  768. up,
  769. toPreviousScratch
  770. );
  771. const toNext = tangentDirection(nextBottom, vertexBottom, up, toNextScratch);
  772. // Check if tangents are almost opposite - if so, no need to miter.
  773. if (
  774. Math$1.CesiumMath.equalsEpsilon(
  775. Matrix3.Cartesian3.dot(toPrevious, toNext),
  776. cosine180,
  777. Math$1.CesiumMath.EPSILON5
  778. )
  779. ) {
  780. result = Matrix3.Cartesian3.cross(up, toPrevious, result);
  781. result = Matrix3.Cartesian3.normalize(result, result);
  782. return result;
  783. }
  784. // Average directions to previous and to next in the plane of Up
  785. result = Matrix3.Cartesian3.add(toNext, toPrevious, result);
  786. result = Matrix3.Cartesian3.normalize(result, result);
  787. // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards")
  788. const forward = Matrix3.Cartesian3.cross(up, result, forwardScratch);
  789. if (Matrix3.Cartesian3.dot(toNext, forward) < cosine90) {
  790. result = Matrix3.Cartesian3.negate(result, result);
  791. }
  792. return result;
  793. }
  794. const XZ_PLANE = Plane.Plane.fromPointNormal(Matrix3.Cartesian3.ZERO, Matrix3.Cartesian3.UNIT_Y);
  795. const previousBottomScratch = new Matrix3.Cartesian3();
  796. const vertexBottomScratch = new Matrix3.Cartesian3();
  797. const vertexTopScratch = new Matrix3.Cartesian3();
  798. const nextBottomScratch = new Matrix3.Cartesian3();
  799. const vertexNormalScratch = new Matrix3.Cartesian3();
  800. const intersectionScratch = new Matrix3.Cartesian3();
  801. const cartographicScratch0 = new Matrix3.Cartographic();
  802. const cartographicScratch1 = new Matrix3.Cartographic();
  803. const cartographicIntersectionScratch = new Matrix3.Cartographic();
  804. /**
  805. * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere.
  806. * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain or 3D Tiles.
  807. * Should not be called independent of {@link GroundPolylinePrimitive}.
  808. *
  809. * @param {GroundPolylineGeometry} groundPolylineGeometry
  810. * @private
  811. */
  812. GroundPolylineGeometry.createGeometry = function (groundPolylineGeometry) {
  813. const compute2dAttributes = !groundPolylineGeometry._scene3DOnly;
  814. let loop = groundPolylineGeometry.loop;
  815. const ellipsoid = groundPolylineGeometry._ellipsoid;
  816. const granularity = groundPolylineGeometry.granularity;
  817. const arcType = groundPolylineGeometry.arcType;
  818. const projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](
  819. ellipsoid
  820. );
  821. const minHeight = WALL_INITIAL_MIN_HEIGHT;
  822. const maxHeight = WALL_INITIAL_MAX_HEIGHT;
  823. let index;
  824. let i;
  825. const positions = groundPolylineGeometry._positions;
  826. const positionsLength = positions.length;
  827. if (positionsLength === 2) {
  828. loop = false;
  829. }
  830. // Split positions across the IDL and the Prime Meridian as well.
  831. // Split across prime meridian because very large geometries crossing the Prime Meridian but not the IDL
  832. // may get split by the plane of IDL + Prime Meridian.
  833. let p0;
  834. let p1;
  835. let c0;
  836. let c1;
  837. const rhumbLine = new EllipsoidRhumbLine.EllipsoidRhumbLine(undefined, undefined, ellipsoid);
  838. let intersection;
  839. let intersectionCartographic;
  840. let intersectionLongitude;
  841. const splitPositions = [positions[0]];
  842. for (i = 0; i < positionsLength - 1; i++) {
  843. p0 = positions[i];
  844. p1 = positions[i + 1];
  845. intersection = IntersectionTests.IntersectionTests.lineSegmentPlane(
  846. p0,
  847. p1,
  848. XZ_PLANE,
  849. intersectionScratch
  850. );
  851. if (
  852. defaultValue.defined(intersection) &&
  853. !Matrix3.Cartesian3.equalsEpsilon(intersection, p0, Math$1.CesiumMath.EPSILON7) &&
  854. !Matrix3.Cartesian3.equalsEpsilon(intersection, p1, Math$1.CesiumMath.EPSILON7)
  855. ) {
  856. if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
  857. splitPositions.push(Matrix3.Cartesian3.clone(intersection));
  858. } else if (groundPolylineGeometry.arcType === ArcType.ArcType.RHUMB) {
  859. intersectionLongitude = ellipsoid.cartesianToCartographic(
  860. intersection,
  861. cartographicScratch0
  862. ).longitude;
  863. c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
  864. c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
  865. rhumbLine.setEndPoints(c0, c1);
  866. intersectionCartographic = rhumbLine.findIntersectionWithLongitude(
  867. intersectionLongitude,
  868. cartographicIntersectionScratch
  869. );
  870. intersection = ellipsoid.cartographicToCartesian(
  871. intersectionCartographic,
  872. intersectionScratch
  873. );
  874. if (
  875. defaultValue.defined(intersection) &&
  876. !Matrix3.Cartesian3.equalsEpsilon(intersection, p0, Math$1.CesiumMath.EPSILON7) &&
  877. !Matrix3.Cartesian3.equalsEpsilon(intersection, p1, Math$1.CesiumMath.EPSILON7)
  878. ) {
  879. splitPositions.push(Matrix3.Cartesian3.clone(intersection));
  880. }
  881. }
  882. }
  883. splitPositions.push(p1);
  884. }
  885. if (loop) {
  886. p0 = positions[positionsLength - 1];
  887. p1 = positions[0];
  888. intersection = IntersectionTests.IntersectionTests.lineSegmentPlane(
  889. p0,
  890. p1,
  891. XZ_PLANE,
  892. intersectionScratch
  893. );
  894. if (
  895. defaultValue.defined(intersection) &&
  896. !Matrix3.Cartesian3.equalsEpsilon(intersection, p0, Math$1.CesiumMath.EPSILON7) &&
  897. !Matrix3.Cartesian3.equalsEpsilon(intersection, p1, Math$1.CesiumMath.EPSILON7)
  898. ) {
  899. if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
  900. splitPositions.push(Matrix3.Cartesian3.clone(intersection));
  901. } else if (groundPolylineGeometry.arcType === ArcType.ArcType.RHUMB) {
  902. intersectionLongitude = ellipsoid.cartesianToCartographic(
  903. intersection,
  904. cartographicScratch0
  905. ).longitude;
  906. c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
  907. c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
  908. rhumbLine.setEndPoints(c0, c1);
  909. intersectionCartographic = rhumbLine.findIntersectionWithLongitude(
  910. intersectionLongitude,
  911. cartographicIntersectionScratch
  912. );
  913. intersection = ellipsoid.cartographicToCartesian(
  914. intersectionCartographic,
  915. intersectionScratch
  916. );
  917. if (
  918. defaultValue.defined(intersection) &&
  919. !Matrix3.Cartesian3.equalsEpsilon(intersection, p0, Math$1.CesiumMath.EPSILON7) &&
  920. !Matrix3.Cartesian3.equalsEpsilon(intersection, p1, Math$1.CesiumMath.EPSILON7)
  921. ) {
  922. splitPositions.push(Matrix3.Cartesian3.clone(intersection));
  923. }
  924. }
  925. }
  926. }
  927. let cartographicsLength = splitPositions.length;
  928. let cartographics = new Array(cartographicsLength);
  929. for (i = 0; i < cartographicsLength; i++) {
  930. const cartographic = Matrix3.Cartographic.fromCartesian(
  931. splitPositions[i],
  932. ellipsoid
  933. );
  934. cartographic.height = 0.0;
  935. cartographics[i] = cartographic;
  936. }
  937. cartographics = arrayRemoveDuplicates.arrayRemoveDuplicates(
  938. cartographics,
  939. Matrix3.Cartographic.equalsEpsilon
  940. );
  941. cartographicsLength = cartographics.length;
  942. if (cartographicsLength < 2) {
  943. return undefined;
  944. }
  945. /**** Build heap-side arrays for positions, interpolated cartographics, and normals from which to compute vertices ****/
  946. // We build a "wall" and then decompose it into separately connected component "volumes" because we need a lot
  947. // of information about the wall. Also, this simplifies interpolation.
  948. // Convention: "next" and "end" are locally forward to each segment of the wall,
  949. // and we are computing normals pointing towards the local right side of the vertices in each segment.
  950. const cartographicsArray = [];
  951. const normalsArray = [];
  952. const bottomPositionsArray = [];
  953. const topPositionsArray = [];
  954. let previousBottom = previousBottomScratch;
  955. let vertexBottom = vertexBottomScratch;
  956. let vertexTop = vertexTopScratch;
  957. let nextBottom = nextBottomScratch;
  958. let vertexNormal = vertexNormalScratch;
  959. // First point - either loop or attach a "perpendicular" normal
  960. const startCartographic = cartographics[0];
  961. const nextCartographic = cartographics[1];
  962. const prestartCartographic = cartographics[cartographicsLength - 1];
  963. previousBottom = getPosition(
  964. ellipsoid,
  965. prestartCartographic,
  966. minHeight,
  967. previousBottom
  968. );
  969. nextBottom = getPosition(ellipsoid, nextCartographic, minHeight, nextBottom);
  970. vertexBottom = getPosition(
  971. ellipsoid,
  972. startCartographic,
  973. minHeight,
  974. vertexBottom
  975. );
  976. vertexTop = getPosition(ellipsoid, startCartographic, maxHeight, vertexTop);
  977. if (loop) {
  978. vertexNormal = computeVertexMiterNormal(
  979. previousBottom,
  980. vertexBottom,
  981. vertexTop,
  982. nextBottom,
  983. vertexNormal
  984. );
  985. } else {
  986. vertexNormal = computeRightNormal(
  987. startCartographic,
  988. nextCartographic,
  989. maxHeight,
  990. ellipsoid,
  991. vertexNormal
  992. );
  993. }
  994. Matrix3.Cartesian3.pack(vertexNormal, normalsArray, 0);
  995. Matrix3.Cartesian3.pack(vertexBottom, bottomPositionsArray, 0);
  996. Matrix3.Cartesian3.pack(vertexTop, topPositionsArray, 0);
  997. cartographicsArray.push(startCartographic.latitude);
  998. cartographicsArray.push(startCartographic.longitude);
  999. interpolateSegment(
  1000. startCartographic,
  1001. nextCartographic,
  1002. minHeight,
  1003. maxHeight,
  1004. granularity,
  1005. arcType,
  1006. ellipsoid,
  1007. normalsArray,
  1008. bottomPositionsArray,
  1009. topPositionsArray,
  1010. cartographicsArray
  1011. );
  1012. // All inbetween points
  1013. for (i = 1; i < cartographicsLength - 1; ++i) {
  1014. previousBottom = Matrix3.Cartesian3.clone(vertexBottom, previousBottom);
  1015. vertexBottom = Matrix3.Cartesian3.clone(nextBottom, vertexBottom);
  1016. const vertexCartographic = cartographics[i];
  1017. getPosition(ellipsoid, vertexCartographic, maxHeight, vertexTop);
  1018. getPosition(ellipsoid, cartographics[i + 1], minHeight, nextBottom);
  1019. computeVertexMiterNormal(
  1020. previousBottom,
  1021. vertexBottom,
  1022. vertexTop,
  1023. nextBottom,
  1024. vertexNormal
  1025. );
  1026. index = normalsArray.length;
  1027. Matrix3.Cartesian3.pack(vertexNormal, normalsArray, index);
  1028. Matrix3.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  1029. Matrix3.Cartesian3.pack(vertexTop, topPositionsArray, index);
  1030. cartographicsArray.push(vertexCartographic.latitude);
  1031. cartographicsArray.push(vertexCartographic.longitude);
  1032. interpolateSegment(
  1033. cartographics[i],
  1034. cartographics[i + 1],
  1035. minHeight,
  1036. maxHeight,
  1037. granularity,
  1038. arcType,
  1039. ellipsoid,
  1040. normalsArray,
  1041. bottomPositionsArray,
  1042. topPositionsArray,
  1043. cartographicsArray
  1044. );
  1045. }
  1046. // Last point - either loop or attach a normal "perpendicular" to the wall.
  1047. const endCartographic = cartographics[cartographicsLength - 1];
  1048. const preEndCartographic = cartographics[cartographicsLength - 2];
  1049. vertexBottom = getPosition(
  1050. ellipsoid,
  1051. endCartographic,
  1052. minHeight,
  1053. vertexBottom
  1054. );
  1055. vertexTop = getPosition(ellipsoid, endCartographic, maxHeight, vertexTop);
  1056. if (loop) {
  1057. const postEndCartographic = cartographics[0];
  1058. previousBottom = getPosition(
  1059. ellipsoid,
  1060. preEndCartographic,
  1061. minHeight,
  1062. previousBottom
  1063. );
  1064. nextBottom = getPosition(
  1065. ellipsoid,
  1066. postEndCartographic,
  1067. minHeight,
  1068. nextBottom
  1069. );
  1070. vertexNormal = computeVertexMiterNormal(
  1071. previousBottom,
  1072. vertexBottom,
  1073. vertexTop,
  1074. nextBottom,
  1075. vertexNormal
  1076. );
  1077. } else {
  1078. vertexNormal = computeRightNormal(
  1079. preEndCartographic,
  1080. endCartographic,
  1081. maxHeight,
  1082. ellipsoid,
  1083. vertexNormal
  1084. );
  1085. }
  1086. index = normalsArray.length;
  1087. Matrix3.Cartesian3.pack(vertexNormal, normalsArray, index);
  1088. Matrix3.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  1089. Matrix3.Cartesian3.pack(vertexTop, topPositionsArray, index);
  1090. cartographicsArray.push(endCartographic.latitude);
  1091. cartographicsArray.push(endCartographic.longitude);
  1092. if (loop) {
  1093. interpolateSegment(
  1094. endCartographic,
  1095. startCartographic,
  1096. minHeight,
  1097. maxHeight,
  1098. granularity,
  1099. arcType,
  1100. ellipsoid,
  1101. normalsArray,
  1102. bottomPositionsArray,
  1103. topPositionsArray,
  1104. cartographicsArray
  1105. );
  1106. index = normalsArray.length;
  1107. for (i = 0; i < 3; ++i) {
  1108. normalsArray[index + i] = normalsArray[i];
  1109. bottomPositionsArray[index + i] = bottomPositionsArray[i];
  1110. topPositionsArray[index + i] = topPositionsArray[i];
  1111. }
  1112. cartographicsArray.push(startCartographic.latitude);
  1113. cartographicsArray.push(startCartographic.longitude);
  1114. }
  1115. return generateGeometryAttributes(
  1116. loop,
  1117. projection,
  1118. bottomPositionsArray,
  1119. topPositionsArray,
  1120. normalsArray,
  1121. cartographicsArray,
  1122. compute2dAttributes
  1123. );
  1124. };
  1125. // If the end normal angle is too steep compared to the direction of the line segment,
  1126. // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point
  1127. // For ultra precision we would want to project into a plane, but in practice this is sufficient.
  1128. const lineDirectionScratch = new Matrix3.Cartesian3();
  1129. const matrix3Scratch = new Matrix3.Matrix3();
  1130. const quaternionScratch = new Transforms.Quaternion();
  1131. function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) {
  1132. const lineDirection = direction(endBottom, startBottom, lineDirectionScratch);
  1133. const dot = Matrix3.Cartesian3.dot(lineDirection, endGeometryNormal);
  1134. if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) {
  1135. const vertexUp = direction(endTop, endBottom, vertexUpScratch);
  1136. const angle =
  1137. dot < MITER_BREAK_LARGE
  1138. ? Math$1.CesiumMath.PI_OVER_TWO
  1139. : -Math$1.CesiumMath.PI_OVER_TWO;
  1140. const quaternion = Transforms.Quaternion.fromAxisAngle(
  1141. vertexUp,
  1142. angle,
  1143. quaternionScratch
  1144. );
  1145. const rotationMatrix = Matrix3.Matrix3.fromQuaternion(quaternion, matrix3Scratch);
  1146. Matrix3.Matrix3.multiplyByVector(
  1147. rotationMatrix,
  1148. endGeometryNormal,
  1149. endGeometryNormal
  1150. );
  1151. return true;
  1152. }
  1153. return false;
  1154. }
  1155. const endPosCartographicScratch = new Matrix3.Cartographic();
  1156. const normalStartpointScratch = new Matrix3.Cartesian3();
  1157. const normalEndpointScratch = new Matrix3.Cartesian3();
  1158. function projectNormal(
  1159. projection,
  1160. cartographic,
  1161. normal,
  1162. projectedPosition,
  1163. result
  1164. ) {
  1165. const position = Matrix3.Cartographic.toCartesian(
  1166. cartographic,
  1167. projection._ellipsoid,
  1168. normalStartpointScratch
  1169. );
  1170. let normalEndpoint = Matrix3.Cartesian3.add(position, normal, normalEndpointScratch);
  1171. let flipNormal = false;
  1172. const ellipsoid = projection._ellipsoid;
  1173. let normalEndpointCartographic = ellipsoid.cartesianToCartographic(
  1174. normalEndpoint,
  1175. endPosCartographicScratch
  1176. );
  1177. // If normal crosses the IDL, go the other way and flip the result.
  1178. // In practice this almost never happens because the cartographic start
  1179. // and end points of each segment are "nudged" to be on the same side
  1180. // of the IDL and slightly away from the IDL.
  1181. if (
  1182. Math.abs(cartographic.longitude - normalEndpointCartographic.longitude) >
  1183. Math$1.CesiumMath.PI_OVER_TWO
  1184. ) {
  1185. flipNormal = true;
  1186. normalEndpoint = Matrix3.Cartesian3.subtract(
  1187. position,
  1188. normal,
  1189. normalEndpointScratch
  1190. );
  1191. normalEndpointCartographic = ellipsoid.cartesianToCartographic(
  1192. normalEndpoint,
  1193. endPosCartographicScratch
  1194. );
  1195. }
  1196. normalEndpointCartographic.height = 0.0;
  1197. const normalEndpointProjected = projection.project(
  1198. normalEndpointCartographic,
  1199. result
  1200. );
  1201. result = Matrix3.Cartesian3.subtract(
  1202. normalEndpointProjected,
  1203. projectedPosition,
  1204. result
  1205. );
  1206. result.z = 0.0;
  1207. result = Matrix3.Cartesian3.normalize(result, result);
  1208. if (flipNormal) {
  1209. Matrix3.Cartesian3.negate(result, result);
  1210. }
  1211. return result;
  1212. }
  1213. const adjustHeightNormalScratch = new Matrix3.Cartesian3();
  1214. const adjustHeightOffsetScratch = new Matrix3.Cartesian3();
  1215. function adjustHeights(
  1216. bottom,
  1217. top,
  1218. minHeight,
  1219. maxHeight,
  1220. adjustHeightBottom,
  1221. adjustHeightTop
  1222. ) {
  1223. // bottom and top should be at WALL_INITIAL_MIN_HEIGHT and WALL_INITIAL_MAX_HEIGHT, respectively
  1224. const adjustHeightNormal = Matrix3.Cartesian3.subtract(
  1225. top,
  1226. bottom,
  1227. adjustHeightNormalScratch
  1228. );
  1229. Matrix3.Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal);
  1230. const distanceForBottom = minHeight - WALL_INITIAL_MIN_HEIGHT;
  1231. let adjustHeightOffset = Matrix3.Cartesian3.multiplyByScalar(
  1232. adjustHeightNormal,
  1233. distanceForBottom,
  1234. adjustHeightOffsetScratch
  1235. );
  1236. Matrix3.Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom);
  1237. const distanceForTop = maxHeight - WALL_INITIAL_MAX_HEIGHT;
  1238. adjustHeightOffset = Matrix3.Cartesian3.multiplyByScalar(
  1239. adjustHeightNormal,
  1240. distanceForTop,
  1241. adjustHeightOffsetScratch
  1242. );
  1243. Matrix3.Cartesian3.add(top, adjustHeightOffset, adjustHeightTop);
  1244. }
  1245. const nudgeDirectionScratch = new Matrix3.Cartesian3();
  1246. function nudgeXZ(start, end) {
  1247. const startToXZdistance = Plane.Plane.getPointDistance(XZ_PLANE, start);
  1248. const endToXZdistance = Plane.Plane.getPointDistance(XZ_PLANE, end);
  1249. let offset = nudgeDirectionScratch;
  1250. // Larger epsilon than what's used in GeometryPipeline, a centimeter in world space
  1251. if (Math$1.CesiumMath.equalsEpsilon(startToXZdistance, 0.0, Math$1.CesiumMath.EPSILON2)) {
  1252. offset = direction(end, start, offset);
  1253. Matrix3.Cartesian3.multiplyByScalar(offset, Math$1.CesiumMath.EPSILON2, offset);
  1254. Matrix3.Cartesian3.add(start, offset, start);
  1255. } else if (
  1256. Math$1.CesiumMath.equalsEpsilon(endToXZdistance, 0.0, Math$1.CesiumMath.EPSILON2)
  1257. ) {
  1258. offset = direction(start, end, offset);
  1259. Matrix3.Cartesian3.multiplyByScalar(offset, Math$1.CesiumMath.EPSILON2, offset);
  1260. Matrix3.Cartesian3.add(end, offset, end);
  1261. }
  1262. }
  1263. // "Nudge" cartographic coordinates so start and end are on the same side of the IDL.
  1264. // Nudge amounts are tiny, basically just an IDL flip.
  1265. // Only used for 2D/CV.
  1266. function nudgeCartographic(start, end) {
  1267. const absStartLon = Math.abs(start.longitude);
  1268. const absEndLon = Math.abs(end.longitude);
  1269. if (
  1270. Math$1.CesiumMath.equalsEpsilon(absStartLon, Math$1.CesiumMath.PI, Math$1.CesiumMath.EPSILON11)
  1271. ) {
  1272. const endSign = Math$1.CesiumMath.sign(end.longitude);
  1273. start.longitude = endSign * (absStartLon - Math$1.CesiumMath.EPSILON11);
  1274. return 1;
  1275. } else if (
  1276. Math$1.CesiumMath.equalsEpsilon(absEndLon, Math$1.CesiumMath.PI, Math$1.CesiumMath.EPSILON11)
  1277. ) {
  1278. const startSign = Math$1.CesiumMath.sign(start.longitude);
  1279. end.longitude = startSign * (absEndLon - Math$1.CesiumMath.EPSILON11);
  1280. return 2;
  1281. }
  1282. return 0;
  1283. }
  1284. const startCartographicScratch = new Matrix3.Cartographic();
  1285. const endCartographicScratch = new Matrix3.Cartographic();
  1286. const segmentStartTopScratch = new Matrix3.Cartesian3();
  1287. const segmentEndTopScratch = new Matrix3.Cartesian3();
  1288. const segmentStartBottomScratch = new Matrix3.Cartesian3();
  1289. const segmentEndBottomScratch = new Matrix3.Cartesian3();
  1290. const segmentStartNormalScratch = new Matrix3.Cartesian3();
  1291. const segmentEndNormalScratch = new Matrix3.Cartesian3();
  1292. const getHeightCartographics = [
  1293. startCartographicScratch,
  1294. endCartographicScratch,
  1295. ];
  1296. const getHeightRectangleScratch = new Matrix2.Rectangle();
  1297. const adjustHeightStartTopScratch = new Matrix3.Cartesian3();
  1298. const adjustHeightEndTopScratch = new Matrix3.Cartesian3();
  1299. const adjustHeightStartBottomScratch = new Matrix3.Cartesian3();
  1300. const adjustHeightEndBottomScratch = new Matrix3.Cartesian3();
  1301. const segmentStart2DScratch = new Matrix3.Cartesian3();
  1302. const segmentEnd2DScratch = new Matrix3.Cartesian3();
  1303. const segmentStartNormal2DScratch = new Matrix3.Cartesian3();
  1304. const segmentEndNormal2DScratch = new Matrix3.Cartesian3();
  1305. const offsetScratch = new Matrix3.Cartesian3();
  1306. const startUpScratch = new Matrix3.Cartesian3();
  1307. const endUpScratch = new Matrix3.Cartesian3();
  1308. const rightScratch = new Matrix3.Cartesian3();
  1309. const startPlaneNormalScratch = new Matrix3.Cartesian3();
  1310. const endPlaneNormalScratch = new Matrix3.Cartesian3();
  1311. const encodeScratch = new EncodedCartesian3.EncodedCartesian3();
  1312. const encodeScratch2D = new EncodedCartesian3.EncodedCartesian3();
  1313. const forwardOffset2DScratch = new Matrix3.Cartesian3();
  1314. const right2DScratch = new Matrix3.Cartesian3();
  1315. const normalNudgeScratch = new Matrix3.Cartesian3();
  1316. const scratchBoundingSpheres = [new Transforms.BoundingSphere(), new Transforms.BoundingSphere()];
  1317. // Winding order is reversed so each segment's volume is inside-out
  1318. const REFERENCE_INDICES = [
  1319. 0,
  1320. 2,
  1321. 1,
  1322. 0,
  1323. 3,
  1324. 2, // right
  1325. 0,
  1326. 7,
  1327. 3,
  1328. 0,
  1329. 4,
  1330. 7, // start
  1331. 0,
  1332. 5,
  1333. 4,
  1334. 0,
  1335. 1,
  1336. 5, // bottom
  1337. 5,
  1338. 7,
  1339. 4,
  1340. 5,
  1341. 6,
  1342. 7, // left
  1343. 5,
  1344. 2,
  1345. 6,
  1346. 5,
  1347. 1,
  1348. 2, // end
  1349. 3,
  1350. 6,
  1351. 2,
  1352. 3,
  1353. 7,
  1354. 6, // top
  1355. ];
  1356. const REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length;
  1357. // Decompose the "wall" into a series of shadow volumes.
  1358. // Each shadow volume's vertices encode a description of the line it contains,
  1359. // including mitering planes at the end points, a plane along the line itself,
  1360. // and attributes for computing length-wise texture coordinates.
  1361. function generateGeometryAttributes(
  1362. loop,
  1363. projection,
  1364. bottomPositionsArray,
  1365. topPositionsArray,
  1366. normalsArray,
  1367. cartographicsArray,
  1368. compute2dAttributes
  1369. ) {
  1370. let i;
  1371. let index;
  1372. const ellipsoid = projection._ellipsoid;
  1373. // Each segment will have 8 vertices
  1374. const segmentCount = bottomPositionsArray.length / 3 - 1;
  1375. const vertexCount = segmentCount * 8;
  1376. const arraySizeVec4 = vertexCount * 4;
  1377. const indexCount = segmentCount * 36;
  1378. const indices =
  1379. vertexCount > 65535
  1380. ? new Uint32Array(indexCount)
  1381. : new Uint16Array(indexCount);
  1382. const positionsArray = new Float64Array(vertexCount * 3);
  1383. const startHiAndForwardOffsetX = new Float32Array(arraySizeVec4);
  1384. const startLoAndForwardOffsetY = new Float32Array(arraySizeVec4);
  1385. const startNormalAndForwardOffsetZ = new Float32Array(arraySizeVec4);
  1386. const endNormalAndTextureCoordinateNormalizationX = new Float32Array(
  1387. arraySizeVec4
  1388. );
  1389. const rightNormalAndTextureCoordinateNormalizationY = new Float32Array(
  1390. arraySizeVec4
  1391. );
  1392. let startHiLo2D;
  1393. let offsetAndRight2D;
  1394. let startEndNormals2D;
  1395. let texcoordNormalization2D;
  1396. if (compute2dAttributes) {
  1397. startHiLo2D = new Float32Array(arraySizeVec4);
  1398. offsetAndRight2D = new Float32Array(arraySizeVec4);
  1399. startEndNormals2D = new Float32Array(arraySizeVec4);
  1400. texcoordNormalization2D = new Float32Array(vertexCount * 2);
  1401. }
  1402. /*** Compute total lengths for texture coordinate normalization ***/
  1403. // 2D
  1404. const cartographicsLength = cartographicsArray.length / 2;
  1405. let length2D = 0.0;
  1406. const startCartographic = startCartographicScratch;
  1407. startCartographic.height = 0.0;
  1408. const endCartographic = endCartographicScratch;
  1409. endCartographic.height = 0.0;
  1410. let segmentStartCartesian = segmentStartTopScratch;
  1411. let segmentEndCartesian = segmentEndTopScratch;
  1412. if (compute2dAttributes) {
  1413. index = 0;
  1414. for (i = 1; i < cartographicsLength; i++) {
  1415. // Don't clone anything from previous segment b/c possible IDL touch
  1416. startCartographic.latitude = cartographicsArray[index];
  1417. startCartographic.longitude = cartographicsArray[index + 1];
  1418. endCartographic.latitude = cartographicsArray[index + 2];
  1419. endCartographic.longitude = cartographicsArray[index + 3];
  1420. segmentStartCartesian = projection.project(
  1421. startCartographic,
  1422. segmentStartCartesian
  1423. );
  1424. segmentEndCartesian = projection.project(
  1425. endCartographic,
  1426. segmentEndCartesian
  1427. );
  1428. length2D += Matrix3.Cartesian3.distance(
  1429. segmentStartCartesian,
  1430. segmentEndCartesian
  1431. );
  1432. index += 2;
  1433. }
  1434. }
  1435. // 3D
  1436. const positionsLength = topPositionsArray.length / 3;
  1437. segmentEndCartesian = Matrix3.Cartesian3.unpack(
  1438. topPositionsArray,
  1439. 0,
  1440. segmentEndCartesian
  1441. );
  1442. let length3D = 0.0;
  1443. index = 3;
  1444. for (i = 1; i < positionsLength; i++) {
  1445. segmentStartCartesian = Matrix3.Cartesian3.clone(
  1446. segmentEndCartesian,
  1447. segmentStartCartesian
  1448. );
  1449. segmentEndCartesian = Matrix3.Cartesian3.unpack(
  1450. topPositionsArray,
  1451. index,
  1452. segmentEndCartesian
  1453. );
  1454. length3D += Matrix3.Cartesian3.distance(segmentStartCartesian, segmentEndCartesian);
  1455. index += 3;
  1456. }
  1457. /*** Generate segments ***/
  1458. let j;
  1459. index = 3;
  1460. let cartographicsIndex = 0;
  1461. let vec2sWriteIndex = 0;
  1462. let vec3sWriteIndex = 0;
  1463. let vec4sWriteIndex = 0;
  1464. let miterBroken = false;
  1465. let endBottom = Matrix3.Cartesian3.unpack(
  1466. bottomPositionsArray,
  1467. 0,
  1468. segmentEndBottomScratch
  1469. );
  1470. let endTop = Matrix3.Cartesian3.unpack(topPositionsArray, 0, segmentEndTopScratch);
  1471. let endGeometryNormal = Matrix3.Cartesian3.unpack(
  1472. normalsArray,
  1473. 0,
  1474. segmentEndNormalScratch
  1475. );
  1476. if (loop) {
  1477. const preEndBottom = Matrix3.Cartesian3.unpack(
  1478. bottomPositionsArray,
  1479. bottomPositionsArray.length - 6,
  1480. segmentStartBottomScratch
  1481. );
  1482. if (breakMiter(endGeometryNormal, preEndBottom, endBottom, endTop)) {
  1483. // Miter broken as if for the last point in the loop, needs to be inverted for first point (clone of endBottom)
  1484. endGeometryNormal = Matrix3.Cartesian3.negate(
  1485. endGeometryNormal,
  1486. endGeometryNormal
  1487. );
  1488. }
  1489. }
  1490. let lengthSoFar3D = 0.0;
  1491. let lengthSoFar2D = 0.0;
  1492. // For translating bounding volume
  1493. let sumHeights = 0.0;
  1494. for (i = 0; i < segmentCount; i++) {
  1495. const startBottom = Matrix3.Cartesian3.clone(endBottom, segmentStartBottomScratch);
  1496. const startTop = Matrix3.Cartesian3.clone(endTop, segmentStartTopScratch);
  1497. let startGeometryNormal = Matrix3.Cartesian3.clone(
  1498. endGeometryNormal,
  1499. segmentStartNormalScratch
  1500. );
  1501. if (miterBroken) {
  1502. startGeometryNormal = Matrix3.Cartesian3.negate(
  1503. startGeometryNormal,
  1504. startGeometryNormal
  1505. );
  1506. }
  1507. endBottom = Matrix3.Cartesian3.unpack(
  1508. bottomPositionsArray,
  1509. index,
  1510. segmentEndBottomScratch
  1511. );
  1512. endTop = Matrix3.Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch);
  1513. endGeometryNormal = Matrix3.Cartesian3.unpack(
  1514. normalsArray,
  1515. index,
  1516. segmentEndNormalScratch
  1517. );
  1518. miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop);
  1519. // 2D - don't clone anything from previous segment b/c possible IDL touch
  1520. startCartographic.latitude = cartographicsArray[cartographicsIndex];
  1521. startCartographic.longitude = cartographicsArray[cartographicsIndex + 1];
  1522. endCartographic.latitude = cartographicsArray[cartographicsIndex + 2];
  1523. endCartographic.longitude = cartographicsArray[cartographicsIndex + 3];
  1524. let start2D;
  1525. let end2D;
  1526. let startGeometryNormal2D;
  1527. let endGeometryNormal2D;
  1528. if (compute2dAttributes) {
  1529. const nudgeResult = nudgeCartographic(startCartographic, endCartographic);
  1530. start2D = projection.project(startCartographic, segmentStart2DScratch);
  1531. end2D = projection.project(endCartographic, segmentEnd2DScratch);
  1532. const direction2D = direction(end2D, start2D, forwardOffset2DScratch);
  1533. direction2D.y = Math.abs(direction2D.y);
  1534. startGeometryNormal2D = segmentStartNormal2DScratch;
  1535. endGeometryNormal2D = segmentEndNormal2DScratch;
  1536. if (
  1537. nudgeResult === 0 ||
  1538. Matrix3.Cartesian3.dot(direction2D, Matrix3.Cartesian3.UNIT_Y) > MITER_BREAK_SMALL
  1539. ) {
  1540. // No nudge - project the original normal
  1541. // Or, if the line's angle relative to the IDL is very acute,
  1542. // in which case snapping will produce oddly shaped volumes.
  1543. startGeometryNormal2D = projectNormal(
  1544. projection,
  1545. startCartographic,
  1546. startGeometryNormal,
  1547. start2D,
  1548. segmentStartNormal2DScratch
  1549. );
  1550. endGeometryNormal2D = projectNormal(
  1551. projection,
  1552. endCartographic,
  1553. endGeometryNormal,
  1554. end2D,
  1555. segmentEndNormal2DScratch
  1556. );
  1557. } else if (nudgeResult === 1) {
  1558. // Start is close to IDL - snap start normal to align with IDL
  1559. endGeometryNormal2D = projectNormal(
  1560. projection,
  1561. endCartographic,
  1562. endGeometryNormal,
  1563. end2D,
  1564. segmentEndNormal2DScratch
  1565. );
  1566. startGeometryNormal2D.x = 0.0;
  1567. // If start longitude is negative and end longitude is less negative, relative right is unit -Y
  1568. // If start longitude is positive and end longitude is less positive, relative right is unit +Y
  1569. startGeometryNormal2D.y = Math$1.CesiumMath.sign(
  1570. startCartographic.longitude - Math.abs(endCartographic.longitude)
  1571. );
  1572. startGeometryNormal2D.z = 0.0;
  1573. } else {
  1574. // End is close to IDL - snap end normal to align with IDL
  1575. startGeometryNormal2D = projectNormal(
  1576. projection,
  1577. startCartographic,
  1578. startGeometryNormal,
  1579. start2D,
  1580. segmentStartNormal2DScratch
  1581. );
  1582. endGeometryNormal2D.x = 0.0;
  1583. // If end longitude is negative and start longitude is less negative, relative right is unit Y
  1584. // If end longitude is positive and start longitude is less positive, relative right is unit -Y
  1585. endGeometryNormal2D.y = Math$1.CesiumMath.sign(
  1586. startCartographic.longitude - endCartographic.longitude
  1587. );
  1588. endGeometryNormal2D.z = 0.0;
  1589. }
  1590. }
  1591. /****************************************
  1592. * Geometry descriptors of a "line on terrain,"
  1593. * as opposed to the "shadow volume used to draw
  1594. * the line on terrain":
  1595. * - position of start + offset to end
  1596. * - start, end, and right-facing planes
  1597. * - encoded texture coordinate offsets
  1598. ****************************************/
  1599. /* 3D */
  1600. const segmentLength3D = Matrix3.Cartesian3.distance(startTop, endTop);
  1601. const encodedStart = EncodedCartesian3.EncodedCartesian3.fromCartesian(
  1602. startBottom,
  1603. encodeScratch
  1604. );
  1605. const forwardOffset = Matrix3.Cartesian3.subtract(
  1606. endBottom,
  1607. startBottom,
  1608. offsetScratch
  1609. );
  1610. const forward = Matrix3.Cartesian3.normalize(forwardOffset, rightScratch);
  1611. let startUp = Matrix3.Cartesian3.subtract(startTop, startBottom, startUpScratch);
  1612. startUp = Matrix3.Cartesian3.normalize(startUp, startUp);
  1613. let rightNormal = Matrix3.Cartesian3.cross(forward, startUp, rightScratch);
  1614. rightNormal = Matrix3.Cartesian3.normalize(rightNormal, rightNormal);
  1615. let startPlaneNormal = Matrix3.Cartesian3.cross(
  1616. startUp,
  1617. startGeometryNormal,
  1618. startPlaneNormalScratch
  1619. );
  1620. startPlaneNormal = Matrix3.Cartesian3.normalize(startPlaneNormal, startPlaneNormal);
  1621. let endUp = Matrix3.Cartesian3.subtract(endTop, endBottom, endUpScratch);
  1622. endUp = Matrix3.Cartesian3.normalize(endUp, endUp);
  1623. let endPlaneNormal = Matrix3.Cartesian3.cross(
  1624. endGeometryNormal,
  1625. endUp,
  1626. endPlaneNormalScratch
  1627. );
  1628. endPlaneNormal = Matrix3.Cartesian3.normalize(endPlaneNormal, endPlaneNormal);
  1629. const texcoordNormalization3DX = segmentLength3D / length3D;
  1630. const texcoordNormalization3DY = lengthSoFar3D / length3D;
  1631. /* 2D */
  1632. let segmentLength2D = 0.0;
  1633. let encodedStart2D;
  1634. let forwardOffset2D;
  1635. let right2D;
  1636. let texcoordNormalization2DX = 0.0;
  1637. let texcoordNormalization2DY = 0.0;
  1638. if (compute2dAttributes) {
  1639. segmentLength2D = Matrix3.Cartesian3.distance(start2D, end2D);
  1640. encodedStart2D = EncodedCartesian3.EncodedCartesian3.fromCartesian(
  1641. start2D,
  1642. encodeScratch2D
  1643. );
  1644. forwardOffset2D = Matrix3.Cartesian3.subtract(
  1645. end2D,
  1646. start2D,
  1647. forwardOffset2DScratch
  1648. );
  1649. // Right direction is just forward direction rotated by -90 degrees around Z
  1650. // Similarly with plane normals
  1651. right2D = Matrix3.Cartesian3.normalize(forwardOffset2D, right2DScratch);
  1652. const swap = right2D.x;
  1653. right2D.x = right2D.y;
  1654. right2D.y = -swap;
  1655. texcoordNormalization2DX = segmentLength2D / length2D;
  1656. texcoordNormalization2DY = lengthSoFar2D / length2D;
  1657. }
  1658. /** Pack **/
  1659. for (j = 0; j < 8; j++) {
  1660. const vec4Index = vec4sWriteIndex + j * 4;
  1661. const vec2Index = vec2sWriteIndex + j * 2;
  1662. const wIndex = vec4Index + 3;
  1663. // Encode sidedness of vertex relative to right plane in texture coordinate normalization X,
  1664. // whether vertex is top or bottom of volume in sign/magnitude of normalization Y.
  1665. const rightPlaneSide = j < 4 ? 1.0 : -1.0;
  1666. const topBottomSide =
  1667. j === 2 || j === 3 || j === 6 || j === 7 ? 1.0 : -1.0;
  1668. // 3D
  1669. Matrix3.Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index);
  1670. startHiAndForwardOffsetX[wIndex] = forwardOffset.x;
  1671. Matrix3.Cartesian3.pack(encodedStart.low, startLoAndForwardOffsetY, vec4Index);
  1672. startLoAndForwardOffsetY[wIndex] = forwardOffset.y;
  1673. Matrix3.Cartesian3.pack(
  1674. startPlaneNormal,
  1675. startNormalAndForwardOffsetZ,
  1676. vec4Index
  1677. );
  1678. startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z;
  1679. Matrix3.Cartesian3.pack(
  1680. endPlaneNormal,
  1681. endNormalAndTextureCoordinateNormalizationX,
  1682. vec4Index
  1683. );
  1684. endNormalAndTextureCoordinateNormalizationX[wIndex] =
  1685. texcoordNormalization3DX * rightPlaneSide;
  1686. Matrix3.Cartesian3.pack(
  1687. rightNormal,
  1688. rightNormalAndTextureCoordinateNormalizationY,
  1689. vec4Index
  1690. );
  1691. let texcoordNormalization = texcoordNormalization3DY * topBottomSide;
  1692. if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
  1693. texcoordNormalization = 9.0; // some value greater than 1.0
  1694. }
  1695. rightNormalAndTextureCoordinateNormalizationY[
  1696. wIndex
  1697. ] = texcoordNormalization;
  1698. // 2D
  1699. if (compute2dAttributes) {
  1700. startHiLo2D[vec4Index] = encodedStart2D.high.x;
  1701. startHiLo2D[vec4Index + 1] = encodedStart2D.high.y;
  1702. startHiLo2D[vec4Index + 2] = encodedStart2D.low.x;
  1703. startHiLo2D[vec4Index + 3] = encodedStart2D.low.y;
  1704. startEndNormals2D[vec4Index] = -startGeometryNormal2D.y;
  1705. startEndNormals2D[vec4Index + 1] = startGeometryNormal2D.x;
  1706. startEndNormals2D[vec4Index + 2] = endGeometryNormal2D.y;
  1707. startEndNormals2D[vec4Index + 3] = -endGeometryNormal2D.x;
  1708. offsetAndRight2D[vec4Index] = forwardOffset2D.x;
  1709. offsetAndRight2D[vec4Index + 1] = forwardOffset2D.y;
  1710. offsetAndRight2D[vec4Index + 2] = right2D.x;
  1711. offsetAndRight2D[vec4Index + 3] = right2D.y;
  1712. texcoordNormalization2D[vec2Index] =
  1713. texcoordNormalization2DX * rightPlaneSide;
  1714. texcoordNormalization = texcoordNormalization2DY * topBottomSide;
  1715. if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
  1716. texcoordNormalization = 9.0; // some value greater than 1.0
  1717. }
  1718. texcoordNormalization2D[vec2Index + 1] = texcoordNormalization;
  1719. }
  1720. }
  1721. // Adjust height of volume in 3D
  1722. const adjustHeightStartBottom = adjustHeightStartBottomScratch;
  1723. const adjustHeightEndBottom = adjustHeightEndBottomScratch;
  1724. const adjustHeightStartTop = adjustHeightStartTopScratch;
  1725. const adjustHeightEndTop = adjustHeightEndTopScratch;
  1726. const getHeightsRectangle = Matrix2.Rectangle.fromCartographicArray(
  1727. getHeightCartographics,
  1728. getHeightRectangleScratch
  1729. );
  1730. const minMaxHeights = ApproximateTerrainHeights$1.getMinimumMaximumHeights(
  1731. getHeightsRectangle,
  1732. ellipsoid
  1733. );
  1734. const minHeight = minMaxHeights.minimumTerrainHeight;
  1735. const maxHeight = minMaxHeights.maximumTerrainHeight;
  1736. sumHeights += minHeight;
  1737. sumHeights += maxHeight;
  1738. adjustHeights(
  1739. startBottom,
  1740. startTop,
  1741. minHeight,
  1742. maxHeight,
  1743. adjustHeightStartBottom,
  1744. adjustHeightStartTop
  1745. );
  1746. adjustHeights(
  1747. endBottom,
  1748. endTop,
  1749. minHeight,
  1750. maxHeight,
  1751. adjustHeightEndBottom,
  1752. adjustHeightEndTop
  1753. );
  1754. // Nudge the positions away from the "polyline" a little bit to prevent errors in GeometryPipeline
  1755. let normalNudge = Matrix3.Cartesian3.multiplyByScalar(
  1756. rightNormal,
  1757. Math$1.CesiumMath.EPSILON5,
  1758. normalNudgeScratch
  1759. );
  1760. Matrix3.Cartesian3.add(
  1761. adjustHeightStartBottom,
  1762. normalNudge,
  1763. adjustHeightStartBottom
  1764. );
  1765. Matrix3.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  1766. Matrix3.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  1767. Matrix3.Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  1768. // If the segment is very close to the XZ plane, nudge the vertices slightly to avoid touching it.
  1769. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
  1770. nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  1771. Matrix3.Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex);
  1772. Matrix3.Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3);
  1773. Matrix3.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6);
  1774. Matrix3.Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9);
  1775. normalNudge = Matrix3.Cartesian3.multiplyByScalar(
  1776. rightNormal,
  1777. -2.0 * Math$1.CesiumMath.EPSILON5,
  1778. normalNudgeScratch
  1779. );
  1780. Matrix3.Cartesian3.add(
  1781. adjustHeightStartBottom,
  1782. normalNudge,
  1783. adjustHeightStartBottom
  1784. );
  1785. Matrix3.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  1786. Matrix3.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  1787. Matrix3.Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  1788. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
  1789. nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  1790. Matrix3.Cartesian3.pack(
  1791. adjustHeightStartBottom,
  1792. positionsArray,
  1793. vec3sWriteIndex + 12
  1794. );
  1795. Matrix3.Cartesian3.pack(
  1796. adjustHeightEndBottom,
  1797. positionsArray,
  1798. vec3sWriteIndex + 15
  1799. );
  1800. Matrix3.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18);
  1801. Matrix3.Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21);
  1802. cartographicsIndex += 2;
  1803. index += 3;
  1804. vec2sWriteIndex += 16;
  1805. vec3sWriteIndex += 24;
  1806. vec4sWriteIndex += 32;
  1807. lengthSoFar3D += segmentLength3D;
  1808. lengthSoFar2D += segmentLength2D;
  1809. }
  1810. index = 0;
  1811. let indexOffset = 0;
  1812. for (i = 0; i < segmentCount; i++) {
  1813. for (j = 0; j < REFERENCE_INDICES_LENGTH; j++) {
  1814. indices[index + j] = REFERENCE_INDICES[j] + indexOffset;
  1815. }
  1816. indexOffset += 8;
  1817. index += REFERENCE_INDICES_LENGTH;
  1818. }
  1819. const boundingSpheres = scratchBoundingSpheres;
  1820. Transforms.BoundingSphere.fromVertices(
  1821. bottomPositionsArray,
  1822. Matrix3.Cartesian3.ZERO,
  1823. 3,
  1824. boundingSpheres[0]
  1825. );
  1826. Transforms.BoundingSphere.fromVertices(
  1827. topPositionsArray,
  1828. Matrix3.Cartesian3.ZERO,
  1829. 3,
  1830. boundingSpheres[1]
  1831. );
  1832. const boundingSphere = Transforms.BoundingSphere.fromBoundingSpheres(boundingSpheres);
  1833. // Adjust bounding sphere height and radius to cover more of the volume
  1834. boundingSphere.radius += sumHeights / (segmentCount * 2.0);
  1835. const attributes = {
  1836. position: new GeometryAttribute.GeometryAttribute({
  1837. componentDatatype: ComponentDatatype.ComponentDatatype.DOUBLE,
  1838. componentsPerAttribute: 3,
  1839. normalize: false,
  1840. values: positionsArray,
  1841. }),
  1842. startHiAndForwardOffsetX: getVec4GeometryAttribute(
  1843. startHiAndForwardOffsetX
  1844. ),
  1845. startLoAndForwardOffsetY: getVec4GeometryAttribute(
  1846. startLoAndForwardOffsetY
  1847. ),
  1848. startNormalAndForwardOffsetZ: getVec4GeometryAttribute(
  1849. startNormalAndForwardOffsetZ
  1850. ),
  1851. endNormalAndTextureCoordinateNormalizationX: getVec4GeometryAttribute(
  1852. endNormalAndTextureCoordinateNormalizationX
  1853. ),
  1854. rightNormalAndTextureCoordinateNormalizationY: getVec4GeometryAttribute(
  1855. rightNormalAndTextureCoordinateNormalizationY
  1856. ),
  1857. };
  1858. if (compute2dAttributes) {
  1859. attributes.startHiLo2D = getVec4GeometryAttribute(startHiLo2D);
  1860. attributes.offsetAndRight2D = getVec4GeometryAttribute(offsetAndRight2D);
  1861. attributes.startEndNormals2D = getVec4GeometryAttribute(startEndNormals2D);
  1862. attributes.texcoordNormalization2D = new GeometryAttribute.GeometryAttribute({
  1863. componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
  1864. componentsPerAttribute: 2,
  1865. normalize: false,
  1866. values: texcoordNormalization2D,
  1867. });
  1868. }
  1869. return new GeometryAttribute.Geometry({
  1870. attributes: attributes,
  1871. indices: indices,
  1872. boundingSphere: boundingSphere,
  1873. });
  1874. }
  1875. function getVec4GeometryAttribute(typedArray) {
  1876. return new GeometryAttribute.GeometryAttribute({
  1877. componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
  1878. componentsPerAttribute: 4,
  1879. normalize: false,
  1880. values: typedArray,
  1881. });
  1882. }
  1883. /**
  1884. * Approximates an ellipsoid-tangent vector in 2D by projecting the end point into 2D.
  1885. * Exposed for testing.
  1886. *
  1887. * @param {MapProjection} projection Map Projection for projecting coordinates to 2D.
  1888. * @param {Cartographic} cartographic The cartographic origin point of the normal.
  1889. * Used to check if the normal crosses the IDL during projection.
  1890. * @param {Cartesian3} normal The normal in 3D.
  1891. * @param {Cartesian3} projectedPosition The projected origin point of the normal in 2D.
  1892. * @param {Cartesian3} result Result parameter on which to store the projected normal.
  1893. * @private
  1894. */
  1895. GroundPolylineGeometry._projectNormal = projectNormal;
  1896. function createGroundPolylineGeometry(groundPolylineGeometry, offset) {
  1897. return ApproximateTerrainHeights$1.initialize().then(function () {
  1898. if (defaultValue.defined(offset)) {
  1899. groundPolylineGeometry = GroundPolylineGeometry.unpack(
  1900. groundPolylineGeometry,
  1901. offset
  1902. );
  1903. }
  1904. return GroundPolylineGeometry.createGeometry(groundPolylineGeometry);
  1905. });
  1906. }
  1907. return createGroundPolylineGeometry;
  1908. }));