createGroundPolylineGeometry.js 74 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135
  1. /* This file is automatically rebuilt by the Cesium build process. */
  2. define(['./Transforms-a076dbe6', './Matrix2-fc7e9822', './RuntimeError-c581ca93', './defaultValue-94c3e563', './ComponentDatatype-4a60b8d6', './ArcType-0cf52f8c', './arrayRemoveDuplicates-06991c15', './EllipsoidGeodesic-dc94f381', './EllipsoidRhumbLine-daebc75b', './EncodedCartesian3-d3e254ea', './GeometryAttribute-2ecf73f6', './IntersectionTests-5deed78b', './Plane-e20fba8c', './WebMercatorProjection-843df830', './_commonjsHelpers-3aae1032-f55dc0c4', './combine-761d9c3f', './WebGLConstants-7dccdc96'], (function (Transforms, Matrix2, RuntimeError, defaultValue, ComponentDatatype, ArcType, arrayRemoveDuplicates, EllipsoidGeodesic, EllipsoidRhumbLine, EncodedCartesian3, GeometryAttribute, IntersectionTests, Plane, WebMercatorProjection, _commonjsHelpers3aae1032, combine, WebGLConstants) { 'use strict';
  3. /**
  4. * A tiling scheme for geometry referenced to a simple {@link GeographicProjection} where
  5. * longitude and latitude are directly mapped to X and Y. This projection is commonly
  6. * known as geographic, equirectangular, equidistant cylindrical, or plate carrée.
  7. *
  8. * @alias GeographicTilingScheme
  9. * @constructor
  10. *
  11. * @param {Object} [options] Object with the following properties:
  12. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
  13. * the WGS84 ellipsoid.
  14. * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the tiling scheme.
  15. * @param {Number} [options.numberOfLevelZeroTilesX=2] The number of tiles in the X direction at level zero of
  16. * the tile tree.
  17. * @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
  18. * the tile tree.
  19. */
  20. function GeographicTilingScheme(options) {
  21. options = defaultValue.defaultValue(options, defaultValue.defaultValue.EMPTY_OBJECT);
  22. this._ellipsoid = defaultValue.defaultValue(options.ellipsoid, Matrix2.Ellipsoid.WGS84);
  23. this._rectangle = defaultValue.defaultValue(options.rectangle, Matrix2.Rectangle.MAX_VALUE);
  24. this._projection = new Transforms.GeographicProjection(this._ellipsoid);
  25. this._numberOfLevelZeroTilesX = defaultValue.defaultValue(
  26. options.numberOfLevelZeroTilesX,
  27. 2
  28. );
  29. this._numberOfLevelZeroTilesY = defaultValue.defaultValue(
  30. options.numberOfLevelZeroTilesY,
  31. 1
  32. );
  33. }
  34. Object.defineProperties(GeographicTilingScheme.prototype, {
  35. /**
  36. * Gets the ellipsoid that is tiled by this tiling scheme.
  37. * @memberof GeographicTilingScheme.prototype
  38. * @type {Ellipsoid}
  39. */
  40. ellipsoid: {
  41. get: function () {
  42. return this._ellipsoid;
  43. },
  44. },
  45. /**
  46. * Gets the rectangle, in radians, covered by this tiling scheme.
  47. * @memberof GeographicTilingScheme.prototype
  48. * @type {Rectangle}
  49. */
  50. rectangle: {
  51. get: function () {
  52. return this._rectangle;
  53. },
  54. },
  55. /**
  56. * Gets the map projection used by this tiling scheme.
  57. * @memberof GeographicTilingScheme.prototype
  58. * @type {MapProjection}
  59. */
  60. projection: {
  61. get: function () {
  62. return this._projection;
  63. },
  64. },
  65. });
  66. /**
  67. * Gets the total number of tiles in the X direction at a specified level-of-detail.
  68. *
  69. * @param {Number} level The level-of-detail.
  70. * @returns {Number} The number of tiles in the X direction at the given level.
  71. */
  72. GeographicTilingScheme.prototype.getNumberOfXTilesAtLevel = function (level) {
  73. return this._numberOfLevelZeroTilesX << level;
  74. };
  75. /**
  76. * Gets the total number of tiles in the Y direction at a specified level-of-detail.
  77. *
  78. * @param {Number} level The level-of-detail.
  79. * @returns {Number} The number of tiles in the Y direction at the given level.
  80. */
  81. GeographicTilingScheme.prototype.getNumberOfYTilesAtLevel = function (level) {
  82. return this._numberOfLevelZeroTilesY << level;
  83. };
  84. /**
  85. * Transforms a rectangle specified in geodetic radians to the native coordinate system
  86. * of this tiling scheme.
  87. *
  88. * @param {Rectangle} rectangle The rectangle to transform.
  89. * @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
  90. * should be created.
  91. * @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
  92. * is undefined.
  93. */
  94. GeographicTilingScheme.prototype.rectangleToNativeRectangle = function (
  95. rectangle,
  96. result
  97. ) {
  98. //>>includeStart('debug', pragmas.debug);
  99. RuntimeError.Check.defined("rectangle", rectangle);
  100. //>>includeEnd('debug');
  101. const west = ComponentDatatype.CesiumMath.toDegrees(rectangle.west);
  102. const south = ComponentDatatype.CesiumMath.toDegrees(rectangle.south);
  103. const east = ComponentDatatype.CesiumMath.toDegrees(rectangle.east);
  104. const north = ComponentDatatype.CesiumMath.toDegrees(rectangle.north);
  105. if (!defaultValue.defined(result)) {
  106. return new Matrix2.Rectangle(west, south, east, north);
  107. }
  108. result.west = west;
  109. result.south = south;
  110. result.east = east;
  111. result.north = north;
  112. return result;
  113. };
  114. /**
  115. * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
  116. * of the tiling scheme.
  117. *
  118. * @param {Number} x The integer x coordinate of the tile.
  119. * @param {Number} y The integer y coordinate of the tile.
  120. * @param {Number} level The tile level-of-detail. Zero is the least detailed.
  121. * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
  122. * should be created.
  123. * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
  124. * if 'result' is undefined.
  125. */
  126. GeographicTilingScheme.prototype.tileXYToNativeRectangle = function (
  127. x,
  128. y,
  129. level,
  130. result
  131. ) {
  132. const rectangleRadians = this.tileXYToRectangle(x, y, level, result);
  133. rectangleRadians.west = ComponentDatatype.CesiumMath.toDegrees(rectangleRadians.west);
  134. rectangleRadians.south = ComponentDatatype.CesiumMath.toDegrees(rectangleRadians.south);
  135. rectangleRadians.east = ComponentDatatype.CesiumMath.toDegrees(rectangleRadians.east);
  136. rectangleRadians.north = ComponentDatatype.CesiumMath.toDegrees(rectangleRadians.north);
  137. return rectangleRadians;
  138. };
  139. /**
  140. * Converts tile x, y coordinates and level to a cartographic rectangle in radians.
  141. *
  142. * @param {Number} x The integer x coordinate of the tile.
  143. * @param {Number} y The integer y coordinate of the tile.
  144. * @param {Number} level The tile level-of-detail. Zero is the least detailed.
  145. * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
  146. * should be created.
  147. * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
  148. * if 'result' is undefined.
  149. */
  150. GeographicTilingScheme.prototype.tileXYToRectangle = function (
  151. x,
  152. y,
  153. level,
  154. result
  155. ) {
  156. const rectangle = this._rectangle;
  157. const xTiles = this.getNumberOfXTilesAtLevel(level);
  158. const yTiles = this.getNumberOfYTilesAtLevel(level);
  159. const xTileWidth = rectangle.width / xTiles;
  160. const west = x * xTileWidth + rectangle.west;
  161. const east = (x + 1) * xTileWidth + rectangle.west;
  162. const yTileHeight = rectangle.height / yTiles;
  163. const north = rectangle.north - y * yTileHeight;
  164. const south = rectangle.north - (y + 1) * yTileHeight;
  165. if (!defaultValue.defined(result)) {
  166. result = new Matrix2.Rectangle(west, south, east, north);
  167. }
  168. result.west = west;
  169. result.south = south;
  170. result.east = east;
  171. result.north = north;
  172. return result;
  173. };
  174. /**
  175. * Calculates the tile x, y coordinates of the tile containing
  176. * a given cartographic position.
  177. *
  178. * @param {Cartographic} position The position.
  179. * @param {Number} level The tile level-of-detail. Zero is the least detailed.
  180. * @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
  181. * should be created.
  182. * @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
  183. * if 'result' is undefined.
  184. */
  185. GeographicTilingScheme.prototype.positionToTileXY = function (
  186. position,
  187. level,
  188. result
  189. ) {
  190. const rectangle = this._rectangle;
  191. if (!Matrix2.Rectangle.contains(rectangle, position)) {
  192. // outside the bounds of the tiling scheme
  193. return undefined;
  194. }
  195. const xTiles = this.getNumberOfXTilesAtLevel(level);
  196. const yTiles = this.getNumberOfYTilesAtLevel(level);
  197. const xTileWidth = rectangle.width / xTiles;
  198. const yTileHeight = rectangle.height / yTiles;
  199. let longitude = position.longitude;
  200. if (rectangle.east < rectangle.west) {
  201. longitude += ComponentDatatype.CesiumMath.TWO_PI;
  202. }
  203. let xTileCoordinate = ((longitude - rectangle.west) / xTileWidth) | 0;
  204. if (xTileCoordinate >= xTiles) {
  205. xTileCoordinate = xTiles - 1;
  206. }
  207. let yTileCoordinate =
  208. ((rectangle.north - position.latitude) / yTileHeight) | 0;
  209. if (yTileCoordinate >= yTiles) {
  210. yTileCoordinate = yTiles - 1;
  211. }
  212. if (!defaultValue.defined(result)) {
  213. return new Matrix2.Cartesian2(xTileCoordinate, yTileCoordinate);
  214. }
  215. result.x = xTileCoordinate;
  216. result.y = yTileCoordinate;
  217. return result;
  218. };
  219. const scratchDiagonalCartesianNE = new Matrix2.Cartesian3();
  220. const scratchDiagonalCartesianSW = new Matrix2.Cartesian3();
  221. const scratchDiagonalCartographic = new Matrix2.Cartographic();
  222. const scratchCenterCartesian = new Matrix2.Cartesian3();
  223. const scratchSurfaceCartesian = new Matrix2.Cartesian3();
  224. const scratchBoundingSphere = new Transforms.BoundingSphere();
  225. const tilingScheme = new GeographicTilingScheme();
  226. const scratchCorners = [
  227. new Matrix2.Cartographic(),
  228. new Matrix2.Cartographic(),
  229. new Matrix2.Cartographic(),
  230. new Matrix2.Cartographic(),
  231. ];
  232. const scratchTileXY = new Matrix2.Cartesian2();
  233. /**
  234. * A collection of functions for approximating terrain height
  235. * @private
  236. */
  237. const ApproximateTerrainHeights = {};
  238. /**
  239. * Initializes the minimum and maximum terrain heights
  240. * @return {Promise.<void>}
  241. */
  242. ApproximateTerrainHeights.initialize = function () {
  243. let initPromise = ApproximateTerrainHeights._initPromise;
  244. if (defaultValue.defined(initPromise)) {
  245. return initPromise;
  246. }
  247. initPromise = Transforms.Resource.fetchJson(
  248. Transforms.buildModuleUrl("Assets/approximateTerrainHeights.json")
  249. ).then(function (json) {
  250. ApproximateTerrainHeights._terrainHeights = json;
  251. });
  252. ApproximateTerrainHeights._initPromise = initPromise;
  253. return initPromise;
  254. };
  255. /**
  256. * Computes the minimum and maximum terrain heights for a given rectangle
  257. * @param {Rectangle} rectangle The bounding rectangle
  258. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
  259. * @return {{minimumTerrainHeight: Number, maximumTerrainHeight: Number}}
  260. */
  261. ApproximateTerrainHeights.getMinimumMaximumHeights = function (
  262. rectangle,
  263. ellipsoid
  264. ) {
  265. //>>includeStart('debug', pragmas.debug);
  266. RuntimeError.Check.defined("rectangle", rectangle);
  267. if (!defaultValue.defined(ApproximateTerrainHeights._terrainHeights)) {
  268. throw new RuntimeError.DeveloperError(
  269. "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
  270. );
  271. }
  272. //>>includeEnd('debug');
  273. ellipsoid = defaultValue.defaultValue(ellipsoid, Matrix2.Ellipsoid.WGS84);
  274. const xyLevel = getTileXYLevel(rectangle);
  275. // Get the terrain min/max for that tile
  276. let minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
  277. let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  278. if (defaultValue.defined(xyLevel)) {
  279. const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;
  280. const heights = ApproximateTerrainHeights._terrainHeights[key];
  281. if (defaultValue.defined(heights)) {
  282. minTerrainHeight = heights[0];
  283. maxTerrainHeight = heights[1];
  284. }
  285. // Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
  286. ellipsoid.cartographicToCartesian(
  287. Matrix2.Rectangle.northeast(rectangle, scratchDiagonalCartographic),
  288. scratchDiagonalCartesianNE
  289. );
  290. ellipsoid.cartographicToCartesian(
  291. Matrix2.Rectangle.southwest(rectangle, scratchDiagonalCartographic),
  292. scratchDiagonalCartesianSW
  293. );
  294. Matrix2.Cartesian3.midpoint(
  295. scratchDiagonalCartesianSW,
  296. scratchDiagonalCartesianNE,
  297. scratchCenterCartesian
  298. );
  299. const surfacePosition = ellipsoid.scaleToGeodeticSurface(
  300. scratchCenterCartesian,
  301. scratchSurfaceCartesian
  302. );
  303. if (defaultValue.defined(surfacePosition)) {
  304. const distance = Matrix2.Cartesian3.distance(
  305. scratchCenterCartesian,
  306. surfacePosition
  307. );
  308. minTerrainHeight = Math.min(minTerrainHeight, -distance);
  309. } else {
  310. minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
  311. }
  312. }
  313. minTerrainHeight = Math.max(
  314. ApproximateTerrainHeights._defaultMinTerrainHeight,
  315. minTerrainHeight
  316. );
  317. return {
  318. minimumTerrainHeight: minTerrainHeight,
  319. maximumTerrainHeight: maxTerrainHeight,
  320. };
  321. };
  322. /**
  323. * Computes the bounding sphere based on the tile heights in the rectangle
  324. * @param {Rectangle} rectangle The bounding rectangle
  325. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
  326. * @return {BoundingSphere} The result bounding sphere
  327. */
  328. ApproximateTerrainHeights.getBoundingSphere = function (rectangle, ellipsoid) {
  329. //>>includeStart('debug', pragmas.debug);
  330. RuntimeError.Check.defined("rectangle", rectangle);
  331. if (!defaultValue.defined(ApproximateTerrainHeights._terrainHeights)) {
  332. throw new RuntimeError.DeveloperError(
  333. "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
  334. );
  335. }
  336. //>>includeEnd('debug');
  337. ellipsoid = defaultValue.defaultValue(ellipsoid, Matrix2.Ellipsoid.WGS84);
  338. const xyLevel = getTileXYLevel(rectangle);
  339. // Get the terrain max for that tile
  340. let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  341. if (defaultValue.defined(xyLevel)) {
  342. const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;
  343. const heights = ApproximateTerrainHeights._terrainHeights[key];
  344. if (defaultValue.defined(heights)) {
  345. maxTerrainHeight = heights[1];
  346. }
  347. }
  348. const result = Transforms.BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
  349. Transforms.BoundingSphere.fromRectangle3D(
  350. rectangle,
  351. ellipsoid,
  352. maxTerrainHeight,
  353. scratchBoundingSphere
  354. );
  355. return Transforms.BoundingSphere.union(result, scratchBoundingSphere, result);
  356. };
  357. function getTileXYLevel(rectangle) {
  358. Matrix2.Cartographic.fromRadians(
  359. rectangle.east,
  360. rectangle.north,
  361. 0.0,
  362. scratchCorners[0]
  363. );
  364. Matrix2.Cartographic.fromRadians(
  365. rectangle.west,
  366. rectangle.north,
  367. 0.0,
  368. scratchCorners[1]
  369. );
  370. Matrix2.Cartographic.fromRadians(
  371. rectangle.east,
  372. rectangle.south,
  373. 0.0,
  374. scratchCorners[2]
  375. );
  376. Matrix2.Cartographic.fromRadians(
  377. rectangle.west,
  378. rectangle.south,
  379. 0.0,
  380. scratchCorners[3]
  381. );
  382. // Determine which tile the bounding rectangle is in
  383. let lastLevelX = 0,
  384. lastLevelY = 0;
  385. let currentX = 0,
  386. currentY = 0;
  387. const maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel;
  388. let i;
  389. for (i = 0; i <= maxLevel; ++i) {
  390. let failed = false;
  391. for (let j = 0; j < 4; ++j) {
  392. const corner = scratchCorners[j];
  393. tilingScheme.positionToTileXY(corner, i, scratchTileXY);
  394. if (j === 0) {
  395. currentX = scratchTileXY.x;
  396. currentY = scratchTileXY.y;
  397. } else if (currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
  398. failed = true;
  399. break;
  400. }
  401. }
  402. if (failed) {
  403. break;
  404. }
  405. lastLevelX = currentX;
  406. lastLevelY = currentY;
  407. }
  408. if (i === 0) {
  409. return undefined;
  410. }
  411. return {
  412. x: lastLevelX,
  413. y: lastLevelY,
  414. level: i > maxLevel ? maxLevel : i - 1,
  415. };
  416. }
  417. ApproximateTerrainHeights._terrainHeightsMaxLevel = 6;
  418. ApproximateTerrainHeights._defaultMaxTerrainHeight = 9000.0;
  419. ApproximateTerrainHeights._defaultMinTerrainHeight = -100000.0;
  420. ApproximateTerrainHeights._terrainHeights = undefined;
  421. ApproximateTerrainHeights._initPromise = undefined;
  422. Object.defineProperties(ApproximateTerrainHeights, {
  423. /**
  424. * Determines if the terrain heights are initialized and ready to use. To initialize the terrain heights,
  425. * call {@link ApproximateTerrainHeights#initialize} and wait for the returned promise to resolve.
  426. * @type {Boolean}
  427. * @readonly
  428. * @memberof ApproximateTerrainHeights
  429. */
  430. initialized: {
  431. get: function () {
  432. return defaultValue.defined(ApproximateTerrainHeights._terrainHeights);
  433. },
  434. },
  435. });
  436. const PROJECTIONS = [Transforms.GeographicProjection, WebMercatorProjection.WebMercatorProjection];
  437. const PROJECTION_COUNT = PROJECTIONS.length;
  438. const MITER_BREAK_SMALL = Math.cos(ComponentDatatype.CesiumMath.toRadians(30.0));
  439. const MITER_BREAK_LARGE = Math.cos(ComponentDatatype.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 RuntimeError.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 RuntimeError.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 = Matrix2.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. Matrix2.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 Matrix2.Cartesian3();
  576. const cart3Scratch2 = new Matrix2.Cartesian3();
  577. const cart3Scratch3 = new Matrix2.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. Matrix2.Cartesian3.cross(forward, up, result);
  585. return Matrix2.Cartesian3.normalize(result, result);
  586. }
  587. const interpolatedCartographicScratch = new Matrix2.Cartographic();
  588. const interpolatedBottomScratch = new Matrix2.Cartesian3();
  589. const interpolatedTopScratch = new Matrix2.Cartesian3();
  590. const interpolatedNormalScratch = new Matrix2.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. Matrix2.Cartesian3.pack(interpolatedNormal, normalsArray, packIndex);
  648. Matrix2.Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex);
  649. Matrix2.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 Matrix2.Cartographic();
  657. function getPosition(ellipsoid, cartographic, height, result) {
  658. Matrix2.Cartographic.clone(cartographic, heightlessCartographicScratch);
  659. heightlessCartographicScratch.height = height;
  660. return Matrix2.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. RuntimeError.Check.typeOf.object("value", value);
  678. RuntimeError.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. Matrix2.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. Matrix2.Ellipsoid.pack(value._ellipsoid, array, index);
  693. index += Matrix2.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. RuntimeError.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] = Matrix2.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 = Matrix2.Ellipsoid.unpack(array, index);
  720. index += Matrix2.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. Matrix2.Cartesian3.subtract(target, origin, result);
  739. Matrix2.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 = Matrix2.Cartesian3.cross(result, up, result);
  746. result = Matrix2.Cartesian3.normalize(result, result);
  747. result = Matrix2.Cartesian3.cross(up, result, result);
  748. return result;
  749. }
  750. const toPreviousScratch = new Matrix2.Cartesian3();
  751. const toNextScratch = new Matrix2.Cartesian3();
  752. const forwardScratch = new Matrix2.Cartesian3();
  753. const vertexUpScratch = new Matrix2.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. ComponentDatatype.CesiumMath.equalsEpsilon(
  775. Matrix2.Cartesian3.dot(toPrevious, toNext),
  776. cosine180,
  777. ComponentDatatype.CesiumMath.EPSILON5
  778. )
  779. ) {
  780. result = Matrix2.Cartesian3.cross(up, toPrevious, result);
  781. result = Matrix2.Cartesian3.normalize(result, result);
  782. return result;
  783. }
  784. // Average directions to previous and to next in the plane of Up
  785. result = Matrix2.Cartesian3.add(toNext, toPrevious, result);
  786. result = Matrix2.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 = Matrix2.Cartesian3.cross(up, result, forwardScratch);
  789. if (Matrix2.Cartesian3.dot(toNext, forward) < cosine90) {
  790. result = Matrix2.Cartesian3.negate(result, result);
  791. }
  792. return result;
  793. }
  794. const XZ_PLANE = Plane.Plane.fromPointNormal(Matrix2.Cartesian3.ZERO, Matrix2.Cartesian3.UNIT_Y);
  795. const previousBottomScratch = new Matrix2.Cartesian3();
  796. const vertexBottomScratch = new Matrix2.Cartesian3();
  797. const vertexTopScratch = new Matrix2.Cartesian3();
  798. const nextBottomScratch = new Matrix2.Cartesian3();
  799. const vertexNormalScratch = new Matrix2.Cartesian3();
  800. const intersectionScratch = new Matrix2.Cartesian3();
  801. const cartographicScratch0 = new Matrix2.Cartographic();
  802. const cartographicScratch1 = new Matrix2.Cartographic();
  803. const cartographicIntersectionScratch = new Matrix2.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. !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&
  854. !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)
  855. ) {
  856. if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
  857. splitPositions.push(Matrix2.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. !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&
  877. !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)
  878. ) {
  879. splitPositions.push(Matrix2.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. !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&
  897. !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)
  898. ) {
  899. if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
  900. splitPositions.push(Matrix2.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. !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&
  920. !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)
  921. ) {
  922. splitPositions.push(Matrix2.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 = Matrix2.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. Matrix2.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. Matrix2.Cartesian3.pack(vertexNormal, normalsArray, 0);
  995. Matrix2.Cartesian3.pack(vertexBottom, bottomPositionsArray, 0);
  996. Matrix2.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 = Matrix2.Cartesian3.clone(vertexBottom, previousBottom);
  1015. vertexBottom = Matrix2.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. Matrix2.Cartesian3.pack(vertexNormal, normalsArray, index);
  1028. Matrix2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  1029. Matrix2.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. Matrix2.Cartesian3.pack(vertexNormal, normalsArray, index);
  1088. Matrix2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  1089. Matrix2.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 Matrix2.Cartesian3();
  1129. const matrix3Scratch = new Matrix2.Matrix3();
  1130. const quaternionScratch = new Transforms.Quaternion();
  1131. function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) {
  1132. const lineDirection = direction(endBottom, startBottom, lineDirectionScratch);
  1133. const dot = Matrix2.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. ? ComponentDatatype.CesiumMath.PI_OVER_TWO
  1139. : -ComponentDatatype.CesiumMath.PI_OVER_TWO;
  1140. const quaternion = Transforms.Quaternion.fromAxisAngle(
  1141. vertexUp,
  1142. angle,
  1143. quaternionScratch
  1144. );
  1145. const rotationMatrix = Matrix2.Matrix3.fromQuaternion(quaternion, matrix3Scratch);
  1146. Matrix2.Matrix3.multiplyByVector(
  1147. rotationMatrix,
  1148. endGeometryNormal,
  1149. endGeometryNormal
  1150. );
  1151. return true;
  1152. }
  1153. return false;
  1154. }
  1155. const endPosCartographicScratch = new Matrix2.Cartographic();
  1156. const normalStartpointScratch = new Matrix2.Cartesian3();
  1157. const normalEndpointScratch = new Matrix2.Cartesian3();
  1158. function projectNormal(
  1159. projection,
  1160. cartographic,
  1161. normal,
  1162. projectedPosition,
  1163. result
  1164. ) {
  1165. const position = Matrix2.Cartographic.toCartesian(
  1166. cartographic,
  1167. projection._ellipsoid,
  1168. normalStartpointScratch
  1169. );
  1170. let normalEndpoint = Matrix2.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. ComponentDatatype.CesiumMath.PI_OVER_TWO
  1184. ) {
  1185. flipNormal = true;
  1186. normalEndpoint = Matrix2.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 = Matrix2.Cartesian3.subtract(
  1202. normalEndpointProjected,
  1203. projectedPosition,
  1204. result
  1205. );
  1206. result.z = 0.0;
  1207. result = Matrix2.Cartesian3.normalize(result, result);
  1208. if (flipNormal) {
  1209. Matrix2.Cartesian3.negate(result, result);
  1210. }
  1211. return result;
  1212. }
  1213. const adjustHeightNormalScratch = new Matrix2.Cartesian3();
  1214. const adjustHeightOffsetScratch = new Matrix2.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 = Matrix2.Cartesian3.subtract(
  1225. top,
  1226. bottom,
  1227. adjustHeightNormalScratch
  1228. );
  1229. Matrix2.Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal);
  1230. const distanceForBottom = minHeight - WALL_INITIAL_MIN_HEIGHT;
  1231. let adjustHeightOffset = Matrix2.Cartesian3.multiplyByScalar(
  1232. adjustHeightNormal,
  1233. distanceForBottom,
  1234. adjustHeightOffsetScratch
  1235. );
  1236. Matrix2.Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom);
  1237. const distanceForTop = maxHeight - WALL_INITIAL_MAX_HEIGHT;
  1238. adjustHeightOffset = Matrix2.Cartesian3.multiplyByScalar(
  1239. adjustHeightNormal,
  1240. distanceForTop,
  1241. adjustHeightOffsetScratch
  1242. );
  1243. Matrix2.Cartesian3.add(top, adjustHeightOffset, adjustHeightTop);
  1244. }
  1245. const nudgeDirectionScratch = new Matrix2.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 (ComponentDatatype.CesiumMath.equalsEpsilon(startToXZdistance, 0.0, ComponentDatatype.CesiumMath.EPSILON2)) {
  1252. offset = direction(end, start, offset);
  1253. Matrix2.Cartesian3.multiplyByScalar(offset, ComponentDatatype.CesiumMath.EPSILON2, offset);
  1254. Matrix2.Cartesian3.add(start, offset, start);
  1255. } else if (
  1256. ComponentDatatype.CesiumMath.equalsEpsilon(endToXZdistance, 0.0, ComponentDatatype.CesiumMath.EPSILON2)
  1257. ) {
  1258. offset = direction(start, end, offset);
  1259. Matrix2.Cartesian3.multiplyByScalar(offset, ComponentDatatype.CesiumMath.EPSILON2, offset);
  1260. Matrix2.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. ComponentDatatype.CesiumMath.equalsEpsilon(absStartLon, ComponentDatatype.CesiumMath.PI, ComponentDatatype.CesiumMath.EPSILON11)
  1271. ) {
  1272. const endSign = ComponentDatatype.CesiumMath.sign(end.longitude);
  1273. start.longitude = endSign * (absStartLon - ComponentDatatype.CesiumMath.EPSILON11);
  1274. return 1;
  1275. } else if (
  1276. ComponentDatatype.CesiumMath.equalsEpsilon(absEndLon, ComponentDatatype.CesiumMath.PI, ComponentDatatype.CesiumMath.EPSILON11)
  1277. ) {
  1278. const startSign = ComponentDatatype.CesiumMath.sign(start.longitude);
  1279. end.longitude = startSign * (absEndLon - ComponentDatatype.CesiumMath.EPSILON11);
  1280. return 2;
  1281. }
  1282. return 0;
  1283. }
  1284. const startCartographicScratch = new Matrix2.Cartographic();
  1285. const endCartographicScratch = new Matrix2.Cartographic();
  1286. const segmentStartTopScratch = new Matrix2.Cartesian3();
  1287. const segmentEndTopScratch = new Matrix2.Cartesian3();
  1288. const segmentStartBottomScratch = new Matrix2.Cartesian3();
  1289. const segmentEndBottomScratch = new Matrix2.Cartesian3();
  1290. const segmentStartNormalScratch = new Matrix2.Cartesian3();
  1291. const segmentEndNormalScratch = new Matrix2.Cartesian3();
  1292. const getHeightCartographics = [
  1293. startCartographicScratch,
  1294. endCartographicScratch,
  1295. ];
  1296. const getHeightRectangleScratch = new Matrix2.Rectangle();
  1297. const adjustHeightStartTopScratch = new Matrix2.Cartesian3();
  1298. const adjustHeightEndTopScratch = new Matrix2.Cartesian3();
  1299. const adjustHeightStartBottomScratch = new Matrix2.Cartesian3();
  1300. const adjustHeightEndBottomScratch = new Matrix2.Cartesian3();
  1301. const segmentStart2DScratch = new Matrix2.Cartesian3();
  1302. const segmentEnd2DScratch = new Matrix2.Cartesian3();
  1303. const segmentStartNormal2DScratch = new Matrix2.Cartesian3();
  1304. const segmentEndNormal2DScratch = new Matrix2.Cartesian3();
  1305. const offsetScratch = new Matrix2.Cartesian3();
  1306. const startUpScratch = new Matrix2.Cartesian3();
  1307. const endUpScratch = new Matrix2.Cartesian3();
  1308. const rightScratch = new Matrix2.Cartesian3();
  1309. const startPlaneNormalScratch = new Matrix2.Cartesian3();
  1310. const endPlaneNormalScratch = new Matrix2.Cartesian3();
  1311. const encodeScratch = new EncodedCartesian3.EncodedCartesian3();
  1312. const encodeScratch2D = new EncodedCartesian3.EncodedCartesian3();
  1313. const forwardOffset2DScratch = new Matrix2.Cartesian3();
  1314. const right2DScratch = new Matrix2.Cartesian3();
  1315. const normalNudgeScratch = new Matrix2.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 += Matrix2.Cartesian3.distance(
  1429. segmentStartCartesian,
  1430. segmentEndCartesian
  1431. );
  1432. index += 2;
  1433. }
  1434. }
  1435. // 3D
  1436. const positionsLength = topPositionsArray.length / 3;
  1437. segmentEndCartesian = Matrix2.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 = Matrix2.Cartesian3.clone(
  1446. segmentEndCartesian,
  1447. segmentStartCartesian
  1448. );
  1449. segmentEndCartesian = Matrix2.Cartesian3.unpack(
  1450. topPositionsArray,
  1451. index,
  1452. segmentEndCartesian
  1453. );
  1454. length3D += Matrix2.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 = Matrix2.Cartesian3.unpack(
  1466. bottomPositionsArray,
  1467. 0,
  1468. segmentEndBottomScratch
  1469. );
  1470. let endTop = Matrix2.Cartesian3.unpack(topPositionsArray, 0, segmentEndTopScratch);
  1471. let endGeometryNormal = Matrix2.Cartesian3.unpack(
  1472. normalsArray,
  1473. 0,
  1474. segmentEndNormalScratch
  1475. );
  1476. if (loop) {
  1477. const preEndBottom = Matrix2.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 = Matrix2.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 = Matrix2.Cartesian3.clone(endBottom, segmentStartBottomScratch);
  1496. const startTop = Matrix2.Cartesian3.clone(endTop, segmentStartTopScratch);
  1497. let startGeometryNormal = Matrix2.Cartesian3.clone(
  1498. endGeometryNormal,
  1499. segmentStartNormalScratch
  1500. );
  1501. if (miterBroken) {
  1502. startGeometryNormal = Matrix2.Cartesian3.negate(
  1503. startGeometryNormal,
  1504. startGeometryNormal
  1505. );
  1506. }
  1507. endBottom = Matrix2.Cartesian3.unpack(
  1508. bottomPositionsArray,
  1509. index,
  1510. segmentEndBottomScratch
  1511. );
  1512. endTop = Matrix2.Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch);
  1513. endGeometryNormal = Matrix2.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. Matrix2.Cartesian3.dot(direction2D, Matrix2.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 = ComponentDatatype.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 = ComponentDatatype.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 = Matrix2.Cartesian3.distance(startTop, endTop);
  1601. const encodedStart = EncodedCartesian3.EncodedCartesian3.fromCartesian(
  1602. startBottom,
  1603. encodeScratch
  1604. );
  1605. const forwardOffset = Matrix2.Cartesian3.subtract(
  1606. endBottom,
  1607. startBottom,
  1608. offsetScratch
  1609. );
  1610. const forward = Matrix2.Cartesian3.normalize(forwardOffset, rightScratch);
  1611. let startUp = Matrix2.Cartesian3.subtract(startTop, startBottom, startUpScratch);
  1612. startUp = Matrix2.Cartesian3.normalize(startUp, startUp);
  1613. let rightNormal = Matrix2.Cartesian3.cross(forward, startUp, rightScratch);
  1614. rightNormal = Matrix2.Cartesian3.normalize(rightNormal, rightNormal);
  1615. let startPlaneNormal = Matrix2.Cartesian3.cross(
  1616. startUp,
  1617. startGeometryNormal,
  1618. startPlaneNormalScratch
  1619. );
  1620. startPlaneNormal = Matrix2.Cartesian3.normalize(startPlaneNormal, startPlaneNormal);
  1621. let endUp = Matrix2.Cartesian3.subtract(endTop, endBottom, endUpScratch);
  1622. endUp = Matrix2.Cartesian3.normalize(endUp, endUp);
  1623. let endPlaneNormal = Matrix2.Cartesian3.cross(
  1624. endGeometryNormal,
  1625. endUp,
  1626. endPlaneNormalScratch
  1627. );
  1628. endPlaneNormal = Matrix2.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 = Matrix2.Cartesian3.distance(start2D, end2D);
  1640. encodedStart2D = EncodedCartesian3.EncodedCartesian3.fromCartesian(
  1641. start2D,
  1642. encodeScratch2D
  1643. );
  1644. forwardOffset2D = Matrix2.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 = Matrix2.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. Matrix2.Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index);
  1670. startHiAndForwardOffsetX[wIndex] = forwardOffset.x;
  1671. Matrix2.Cartesian3.pack(encodedStart.low, startLoAndForwardOffsetY, vec4Index);
  1672. startLoAndForwardOffsetY[wIndex] = forwardOffset.y;
  1673. Matrix2.Cartesian3.pack(
  1674. startPlaneNormal,
  1675. startNormalAndForwardOffsetZ,
  1676. vec4Index
  1677. );
  1678. startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z;
  1679. Matrix2.Cartesian3.pack(
  1680. endPlaneNormal,
  1681. endNormalAndTextureCoordinateNormalizationX,
  1682. vec4Index
  1683. );
  1684. endNormalAndTextureCoordinateNormalizationX[wIndex] =
  1685. texcoordNormalization3DX * rightPlaneSide;
  1686. Matrix2.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.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 = Matrix2.Cartesian3.multiplyByScalar(
  1756. rightNormal,
  1757. ComponentDatatype.CesiumMath.EPSILON5,
  1758. normalNudgeScratch
  1759. );
  1760. Matrix2.Cartesian3.add(
  1761. adjustHeightStartBottom,
  1762. normalNudge,
  1763. adjustHeightStartBottom
  1764. );
  1765. Matrix2.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  1766. Matrix2.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  1767. Matrix2.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. Matrix2.Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex);
  1772. Matrix2.Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3);
  1773. Matrix2.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6);
  1774. Matrix2.Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9);
  1775. normalNudge = Matrix2.Cartesian3.multiplyByScalar(
  1776. rightNormal,
  1777. -2.0 * ComponentDatatype.CesiumMath.EPSILON5,
  1778. normalNudgeScratch
  1779. );
  1780. Matrix2.Cartesian3.add(
  1781. adjustHeightStartBottom,
  1782. normalNudge,
  1783. adjustHeightStartBottom
  1784. );
  1785. Matrix2.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  1786. Matrix2.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  1787. Matrix2.Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  1788. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
  1789. nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  1790. Matrix2.Cartesian3.pack(
  1791. adjustHeightStartBottom,
  1792. positionsArray,
  1793. vec3sWriteIndex + 12
  1794. );
  1795. Matrix2.Cartesian3.pack(
  1796. adjustHeightEndBottom,
  1797. positionsArray,
  1798. vec3sWriteIndex + 15
  1799. );
  1800. Matrix2.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18);
  1801. Matrix2.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. Matrix2.Cartesian3.ZERO,
  1823. 3,
  1824. boundingSpheres[0]
  1825. );
  1826. Transforms.BoundingSphere.fromVertices(
  1827. topPositionsArray,
  1828. Matrix2.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.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. }));