GroundPolylineGeometry.js 52 KB

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