GroundPolylineGeometry.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149
  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 defineProperties from './defineProperties.js';
  12. import DeveloperError from './DeveloperError.js';
  13. import Ellipsoid from './Ellipsoid.js';
  14. import EllipsoidGeodesic from './EllipsoidGeodesic.js';
  15. import EllipsoidRhumbLine from './EllipsoidRhumbLine.js';
  16. import EncodedCartesian3 from './EncodedCartesian3.js';
  17. import GeographicProjection from './GeographicProjection.js';
  18. import Geometry from './Geometry.js';
  19. import GeometryAttribute from './GeometryAttribute.js';
  20. import IntersectionTests from './IntersectionTests.js';
  21. import CesiumMath from './Math.js';
  22. import Matrix3 from './Matrix3.js';
  23. import Plane from './Plane.js';
  24. import Quaternion from './Quaternion.js';
  25. import Rectangle from './Rectangle.js';
  26. import WebMercatorProjection from './WebMercatorProjection.js';
  27. var PROJECTIONS = [GeographicProjection, WebMercatorProjection];
  28. var PROJECTION_COUNT = PROJECTIONS.length;
  29. var MITER_BREAK_SMALL = Math.cos(CesiumMath.toRadians(30.0));
  30. var MITER_BREAK_LARGE = Math.cos(CesiumMath.toRadians(150.0));
  31. // Initial heights for constructing the wall.
  32. // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps
  33. // prevent precision problems with planes in the shader.
  34. // Putting the start point of a plane at ApproximateTerrainHeights._defaultMinTerrainHeight,
  35. // which is a highly conservative bound, usually puts the plane origin several thousands
  36. // of meters away from the actual terrain, causing floating point problems when checking
  37. // fragments on terrain against the plane.
  38. // Ellipsoid height is generally much closer.
  39. // The initial max height is arbitrary.
  40. // Both heights are corrected using ApproximateTerrainHeights for computing the actual volume geometry.
  41. var WALL_INITIAL_MIN_HEIGHT = 0.0;
  42. var WALL_INITIAL_MAX_HEIGHT = 1000.0;
  43. /**
  44. * A description of a polyline on terrain or 3D Tiles. Only to be used with {@link GroundPolylinePrimitive}.
  45. *
  46. * @alias GroundPolylineGeometry
  47. * @constructor
  48. *
  49. * @param {Object} options Options with the following properties:
  50. * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored.
  51. * @param {Number} [options.width=1.0] The screen space width in pixels.
  52. * @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.
  53. * @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.
  54. * @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}.
  55. *
  56. * @exception {DeveloperError} At least two positions are required.
  57. *
  58. * @see GroundPolylinePrimitive
  59. *
  60. * @example
  61. * var positions = Cesium.Cartesian3.fromDegreesArray([
  62. * -112.1340164450331, 36.05494287836128,
  63. * -112.08821010582645, 36.097804071380715,
  64. * -112.13296079730024, 36.168769146801104
  65. * ]);
  66. *
  67. * var geometry = new Cesium.GroundPolylineGeometry({
  68. * positions : positions
  69. * });
  70. */
  71. function GroundPolylineGeometry(options) {
  72. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  73. var positions = options.positions;
  74. //>>includeStart('debug', pragmas.debug);
  75. if ((!defined(positions)) || (positions.length < 2)) {
  76. throw new DeveloperError('At least two positions are required.');
  77. }
  78. if (defined(options.arcType) && options.arcType !== ArcType.GEODESIC && options.arcType !== ArcType.RHUMB) {
  79. throw new DeveloperError('Valid options for arcType are ArcType.GEODESIC and ArcType.RHUMB.');
  80. }
  81. //>>includeEnd('debug');
  82. /**
  83. * The screen space width in pixels.
  84. * @type {Number}
  85. */
  86. this.width = defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry.
  87. this._positions = positions;
  88. /**
  89. * The distance interval used for interpolating options.points. Zero indicates no interpolation.
  90. * Default of 9999.0 allows centimeter accuracy with 32 bit floating point.
  91. * @type {Boolean}
  92. * @default 9999.0
  93. */
  94. this.granularity = defaultValue(options.granularity, 9999.0);
  95. /**
  96. * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
  97. * If the geometry has two positions this parameter will be ignored.
  98. * @type {Boolean}
  99. * @default false
  100. */
  101. this.loop = defaultValue(options.loop, false);
  102. /**
  103. * The type of path the polyline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
  104. * @type {ArcType}
  105. * @default ArcType.GEODESIC
  106. */
  107. this.arcType = defaultValue(options.arcType, ArcType.GEODESIC);
  108. this._ellipsoid = Ellipsoid.WGS84;
  109. // MapProjections can't be packed, so store the index to a known MapProjection.
  110. this._projectionIndex = 0;
  111. this._workerName = 'createGroundPolylineGeometry';
  112. // Used by GroundPolylinePrimitive to signal worker that scenemode is 3D only.
  113. this._scene3DOnly = false;
  114. }
  115. defineProperties(GroundPolylineGeometry.prototype, {
  116. /**
  117. * The number of elements used to pack the object into an array.
  118. * @memberof GroundPolylineGeometry.prototype
  119. * @type {Number}
  120. * @readonly
  121. * @private
  122. */
  123. packedLength: {
  124. get: function() {
  125. return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0;
  126. }
  127. }
  128. });
  129. /**
  130. * Set the GroundPolylineGeometry's projection and ellipsoid.
  131. * Used by GroundPolylinePrimitive to signal scene information to the geometry for generating 2D attributes.
  132. *
  133. * @param {GroundPolylineGeometry} groundPolylineGeometry GroundPolylinGeometry describing a polyline on terrain or 3D Tiles.
  134. * @param {Projection} mapProjection A MapProjection used for projecting cartographic coordinates to 2D.
  135. * @private
  136. */
  137. GroundPolylineGeometry.setProjectionAndEllipsoid = function(groundPolylineGeometry, mapProjection) {
  138. var projectionIndex = 0;
  139. for (var i = 0; i < PROJECTION_COUNT; i++) {
  140. if (mapProjection instanceof PROJECTIONS[i]) {
  141. projectionIndex = i;
  142. break;
  143. }
  144. }
  145. groundPolylineGeometry._projectionIndex = projectionIndex;
  146. groundPolylineGeometry._ellipsoid = mapProjection.ellipsoid;
  147. };
  148. var cart3Scratch1 = new Cartesian3();
  149. var cart3Scratch2 = new Cartesian3();
  150. var cart3Scratch3 = new Cartesian3();
  151. function computeRightNormal(start, end, maxHeight, ellipsoid, result) {
  152. var startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1);
  153. var startTop = getPosition(ellipsoid, start, maxHeight, cart3Scratch2);
  154. var endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3);
  155. var up = direction(startTop, startBottom, cart3Scratch2);
  156. var forward = direction(endBottom, startBottom, cart3Scratch3);
  157. Cartesian3.cross(forward, up, result);
  158. return Cartesian3.normalize(result, result);
  159. }
  160. var interpolatedCartographicScratch = new Cartographic();
  161. var interpolatedBottomScratch = new Cartesian3();
  162. var interpolatedTopScratch = new Cartesian3();
  163. var interpolatedNormalScratch = new Cartesian3();
  164. function interpolateSegment(start, end, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray) {
  165. if (granularity === 0.0) {
  166. return;
  167. }
  168. var ellipsoidLine;
  169. if (arcType === ArcType.GEODESIC) {
  170. ellipsoidLine = new EllipsoidGeodesic(start, end, ellipsoid);
  171. } else if (arcType === ArcType.RHUMB) {
  172. ellipsoidLine = new EllipsoidRhumbLine(start, end, ellipsoid);
  173. }
  174. var surfaceDistance = ellipsoidLine.surfaceDistance;
  175. if (surfaceDistance < granularity) {
  176. return;
  177. }
  178. // Compute rightwards normal applicable at all interpolated points
  179. var interpolatedNormal = computeRightNormal(start, end, maxHeight, ellipsoid, interpolatedNormalScratch);
  180. var segments = Math.ceil(surfaceDistance / granularity);
  181. var interpointDistance = surfaceDistance / segments;
  182. var distanceFromStart = interpointDistance;
  183. var pointsToAdd = segments - 1;
  184. var packIndex = normalsArray.length;
  185. for (var i = 0; i < pointsToAdd; i++) {
  186. var interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch);
  187. var interpolatedBottom = getPosition(ellipsoid, interpolatedCartographic, minHeight, interpolatedBottomScratch);
  188. var interpolatedTop = getPosition(ellipsoid, interpolatedCartographic, maxHeight, interpolatedTopScratch);
  189. Cartesian3.pack(interpolatedNormal, normalsArray, packIndex);
  190. Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex);
  191. Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex);
  192. cartographicsArray.push(interpolatedCartographic.latitude);
  193. cartographicsArray.push(interpolatedCartographic.longitude);
  194. packIndex += 3;
  195. distanceFromStart += interpointDistance;
  196. }
  197. }
  198. var heightlessCartographicScratch = new Cartographic();
  199. function getPosition(ellipsoid, cartographic, height, result) {
  200. Cartographic.clone(cartographic, heightlessCartographicScratch);
  201. heightlessCartographicScratch.height = height;
  202. return Cartographic.toCartesian(heightlessCartographicScratch, ellipsoid, result);
  203. }
  204. /**
  205. * Stores the provided instance into the provided array.
  206. *
  207. * @param {PolygonGeometry} value The value to pack.
  208. * @param {Number[]} array The array to pack into.
  209. * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
  210. *
  211. * @returns {Number[]} The array that was packed into
  212. */
  213. GroundPolylineGeometry.pack = function(value, array, startingIndex) {
  214. //>>includeStart('debug', pragmas.debug);
  215. Check.typeOf.object('value', value);
  216. Check.defined('array', array);
  217. //>>includeEnd('debug');
  218. var index = defaultValue(startingIndex, 0);
  219. var positions = value._positions;
  220. var positionsLength = positions.length;
  221. array[index++] = positionsLength;
  222. for (var i = 0; i < positionsLength; ++i) {
  223. var cartesian = positions[i];
  224. Cartesian3.pack(cartesian, array, index);
  225. index += 3;
  226. }
  227. array[index++] = value.granularity;
  228. array[index++] = value.loop ? 1.0 : 0.0;
  229. array[index++] = value.arcType;
  230. Ellipsoid.pack(value._ellipsoid, array, index);
  231. index += Ellipsoid.packedLength;
  232. array[index++] = value._projectionIndex;
  233. array[index++] = value._scene3DOnly ? 1.0 : 0.0;
  234. return array;
  235. };
  236. /**
  237. * Retrieves an instance from a packed array.
  238. *
  239. * @param {Number[]} array The packed array.
  240. * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
  241. * @param {PolygonGeometry} [result] The object into which to store the result.
  242. */
  243. GroundPolylineGeometry.unpack = function(array, startingIndex, result) {
  244. //>>includeStart('debug', pragmas.debug);
  245. Check.defined('array', array);
  246. //>>includeEnd('debug');
  247. var index = defaultValue(startingIndex, 0);
  248. var positionsLength = array[index++];
  249. var positions = new Array(positionsLength);
  250. for (var i = 0; i < positionsLength; i++) {
  251. positions[i] = Cartesian3.unpack(array, index);
  252. index += 3;
  253. }
  254. var granularity = array[index++];
  255. var loop = array[index++] === 1.0;
  256. var arcType = array[index++];
  257. var ellipsoid = Ellipsoid.unpack(array, index);
  258. index += Ellipsoid.packedLength;
  259. var projectionIndex = array[index++];
  260. var scene3DOnly = (array[index++] === 1.0);
  261. if (!defined(result)) {
  262. result = new GroundPolylineGeometry({
  263. positions : positions
  264. });
  265. }
  266. result._positions = positions;
  267. result.granularity = granularity;
  268. result.loop = loop;
  269. result.arcType = arcType;
  270. result._ellipsoid = ellipsoid;
  271. result._projectionIndex = projectionIndex;
  272. result._scene3DOnly = scene3DOnly;
  273. return result;
  274. };
  275. function direction(target, origin, result) {
  276. Cartesian3.subtract(target, origin, result);
  277. Cartesian3.normalize(result, result);
  278. return result;
  279. }
  280. function tangentDirection(target, origin, up, result) {
  281. result = direction(target, origin, result);
  282. // orthogonalize
  283. result = Cartesian3.cross(result, up, result);
  284. result = Cartesian3.normalize(result, result);
  285. result = Cartesian3.cross(up, result, result);
  286. return result;
  287. }
  288. var toPreviousScratch = new Cartesian3();
  289. var toNextScratch = new Cartesian3();
  290. var forwardScratch = new Cartesian3();
  291. var vertexUpScratch = new Cartesian3();
  292. var cosine90 = 0.0;
  293. var cosine180 = -1.0;
  294. function computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, result) {
  295. var up = direction(vertexTop, vertexBottom, vertexUpScratch);
  296. // Compute vectors pointing towards neighboring points but tangent to this point on the ellipsoid
  297. var toPrevious = tangentDirection(previousBottom, vertexBottom, up, toPreviousScratch);
  298. var toNext = tangentDirection(nextBottom, vertexBottom, up, toNextScratch);
  299. // Check if tangents are almost opposite - if so, no need to miter.
  300. if (CesiumMath.equalsEpsilon(Cartesian3.dot(toPrevious, toNext), cosine180, CesiumMath.EPSILON5)) {
  301. result = Cartesian3.cross(up, toPrevious, result);
  302. result = Cartesian3.normalize(result, result);
  303. return result;
  304. }
  305. // Average directions to previous and to next in the plane of Up
  306. result = Cartesian3.add(toNext, toPrevious, result);
  307. result = Cartesian3.normalize(result, result);
  308. // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards")
  309. var forward = Cartesian3.cross(up, result, forwardScratch);
  310. if (Cartesian3.dot(toNext, forward) < cosine90) {
  311. result = Cartesian3.negate(result, result);
  312. }
  313. return result;
  314. }
  315. var XZ_PLANE = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Y);
  316. var previousBottomScratch = new Cartesian3();
  317. var vertexBottomScratch = new Cartesian3();
  318. var vertexTopScratch = new Cartesian3();
  319. var nextBottomScratch = new Cartesian3();
  320. var vertexNormalScratch = new Cartesian3();
  321. var intersectionScratch = new Cartesian3();
  322. var cartographicScratch0 = new Cartographic();
  323. var cartographicScratch1 = new Cartographic();
  324. var cartographicIntersectionScratch = new Cartographic();
  325. /**
  326. * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere.
  327. * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain or 3D Tiles.
  328. * Should not be called independent of {@link GroundPolylinePrimitive}.
  329. *
  330. * @param {GroundPolylineGeometry} groundPolylineGeometry
  331. * @private
  332. */
  333. GroundPolylineGeometry.createGeometry = function(groundPolylineGeometry) {
  334. var compute2dAttributes = !groundPolylineGeometry._scene3DOnly;
  335. var loop = groundPolylineGeometry.loop;
  336. var ellipsoid = groundPolylineGeometry._ellipsoid;
  337. var granularity = groundPolylineGeometry.granularity;
  338. var arcType = groundPolylineGeometry.arcType;
  339. var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](ellipsoid);
  340. var minHeight = WALL_INITIAL_MIN_HEIGHT;
  341. var maxHeight = WALL_INITIAL_MAX_HEIGHT;
  342. var index;
  343. var i;
  344. var positions = groundPolylineGeometry._positions;
  345. var positionsLength = positions.length;
  346. if (positionsLength === 2) {
  347. loop = false;
  348. }
  349. // Split positions across the IDL and the Prime Meridian as well.
  350. // Split across prime meridian because very large geometries crossing the Prime Meridian but not the IDL
  351. // may get split by the plane of IDL + Prime Meridian.
  352. var p0;
  353. var p1;
  354. var c0;
  355. var c1;
  356. var rhumbLine = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
  357. var intersection;
  358. var intersectionCartographic;
  359. var intersectionLongitude;
  360. var splitPositions = [positions[0]];
  361. for (i = 0; i < positionsLength - 1; i++) {
  362. p0 = positions[i];
  363. p1 = positions[i + 1];
  364. intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch);
  365. if (defined(intersection) &&
  366. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  367. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
  368. if (groundPolylineGeometry.arcType === ArcType.GEODESIC) {
  369. splitPositions.push(Cartesian3.clone(intersection));
  370. } else if (groundPolylineGeometry.arcType === ArcType.RHUMB) {
  371. intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude;
  372. c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
  373. c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
  374. rhumbLine.setEndPoints(c0, c1);
  375. intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch);
  376. intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch);
  377. if (defined(intersection) &&
  378. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  379. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
  380. splitPositions.push(Cartesian3.clone(intersection));
  381. }
  382. }
  383. }
  384. splitPositions.push(p1);
  385. }
  386. if (loop) {
  387. p0 = positions[positionsLength - 1];
  388. p1 = positions[0];
  389. intersection = IntersectionTests.lineSegmentPlane(p0, p1, XZ_PLANE, intersectionScratch);
  390. if (defined(intersection) &&
  391. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  392. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
  393. if (groundPolylineGeometry.arcType === ArcType.GEODESIC) {
  394. splitPositions.push(Cartesian3.clone(intersection));
  395. } else if (groundPolylineGeometry.arcType === ArcType.RHUMB) {
  396. intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude;
  397. c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
  398. c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
  399. rhumbLine.setEndPoints(c0, c1);
  400. intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch);
  401. intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch);
  402. if (defined(intersection) &&
  403. !Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
  404. !Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
  405. splitPositions.push(Cartesian3.clone(intersection));
  406. }
  407. }
  408. }
  409. }
  410. var cartographicsLength = splitPositions.length;
  411. var cartographics = new Array(cartographicsLength);
  412. for (i = 0; i < cartographicsLength; i++) {
  413. var cartographic = Cartographic.fromCartesian(splitPositions[i], ellipsoid);
  414. cartographic.height = 0.0;
  415. cartographics[i] = cartographic;
  416. }
  417. cartographics = arrayRemoveDuplicates(cartographics, Cartographic.equalsEpsilon);
  418. cartographicsLength = cartographics.length;
  419. if (cartographicsLength < 2) {
  420. return undefined;
  421. }
  422. /**** Build heap-side arrays for positions, interpolated cartographics, and normals from which to compute vertices ****/
  423. // We build a "wall" and then decompose it into separately connected component "volumes" because we need a lot
  424. // of information about the wall. Also, this simplifies interpolation.
  425. // Convention: "next" and "end" are locally forward to each segment of the wall,
  426. // and we are computing normals pointing towards the local right side of the vertices in each segment.
  427. var cartographicsArray = [];
  428. var normalsArray = [];
  429. var bottomPositionsArray = [];
  430. var topPositionsArray = [];
  431. var previousBottom = previousBottomScratch;
  432. var vertexBottom = vertexBottomScratch;
  433. var vertexTop = vertexTopScratch;
  434. var nextBottom = nextBottomScratch;
  435. var vertexNormal = vertexNormalScratch;
  436. // First point - either loop or attach a "perpendicular" normal
  437. var startCartographic = cartographics[0];
  438. var nextCartographic = cartographics[1];
  439. var prestartCartographic = cartographics[cartographicsLength - 1];
  440. previousBottom = getPosition(ellipsoid, prestartCartographic, minHeight, previousBottom);
  441. nextBottom = getPosition(ellipsoid, nextCartographic, minHeight, nextBottom);
  442. vertexBottom = getPosition(ellipsoid, startCartographic, minHeight, vertexBottom);
  443. vertexTop = getPosition(ellipsoid, startCartographic, maxHeight, vertexTop);
  444. if (loop) {
  445. vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal);
  446. } else {
  447. vertexNormal = computeRightNormal(startCartographic, nextCartographic, maxHeight, ellipsoid, vertexNormal);
  448. }
  449. Cartesian3.pack(vertexNormal, normalsArray, 0);
  450. Cartesian3.pack(vertexBottom, bottomPositionsArray, 0);
  451. Cartesian3.pack(vertexTop, topPositionsArray, 0);
  452. cartographicsArray.push(startCartographic.latitude);
  453. cartographicsArray.push(startCartographic.longitude);
  454. interpolateSegment(startCartographic, nextCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
  455. // All inbetween points
  456. for (i = 1; i < cartographicsLength - 1; ++i) {
  457. previousBottom = Cartesian3.clone(vertexBottom, previousBottom);
  458. vertexBottom = Cartesian3.clone(nextBottom, vertexBottom);
  459. var vertexCartographic = cartographics[i];
  460. getPosition(ellipsoid, vertexCartographic, maxHeight, vertexTop);
  461. getPosition(ellipsoid, cartographics[i + 1], minHeight, nextBottom);
  462. computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal);
  463. index = normalsArray.length;
  464. Cartesian3.pack(vertexNormal, normalsArray, index);
  465. Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  466. Cartesian3.pack(vertexTop, topPositionsArray, index);
  467. cartographicsArray.push(vertexCartographic.latitude);
  468. cartographicsArray.push(vertexCartographic.longitude);
  469. interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
  470. }
  471. // Last point - either loop or attach a normal "perpendicular" to the wall.
  472. var endCartographic = cartographics[cartographicsLength - 1];
  473. var preEndCartographic = cartographics[cartographicsLength - 2];
  474. vertexBottom = getPosition(ellipsoid, endCartographic, minHeight, vertexBottom);
  475. vertexTop = getPosition(ellipsoid, endCartographic, maxHeight, vertexTop);
  476. if (loop) {
  477. var postEndCartographic = cartographics[0];
  478. previousBottom = getPosition(ellipsoid, preEndCartographic, minHeight, previousBottom);
  479. nextBottom = getPosition(ellipsoid, postEndCartographic, minHeight, nextBottom);
  480. vertexNormal = computeVertexMiterNormal(previousBottom, vertexBottom, vertexTop, nextBottom, vertexNormal);
  481. } else {
  482. vertexNormal = computeRightNormal(preEndCartographic, endCartographic, maxHeight, ellipsoid, vertexNormal);
  483. }
  484. index = normalsArray.length;
  485. Cartesian3.pack(vertexNormal, normalsArray, index);
  486. Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
  487. Cartesian3.pack(vertexTop, topPositionsArray, index);
  488. cartographicsArray.push(endCartographic.latitude);
  489. cartographicsArray.push(endCartographic.longitude);
  490. if (loop) {
  491. interpolateSegment(endCartographic, startCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
  492. index = normalsArray.length;
  493. for (i = 0; i < 3; ++i) {
  494. normalsArray[index + i] = normalsArray[i];
  495. bottomPositionsArray[index + i] = bottomPositionsArray[i];
  496. topPositionsArray[index + i] = topPositionsArray[i];
  497. }
  498. cartographicsArray.push(startCartographic.latitude);
  499. cartographicsArray.push(startCartographic.longitude);
  500. }
  501. return generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, compute2dAttributes);
  502. };
  503. // If the end normal angle is too steep compared to the direction of the line segment,
  504. // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point
  505. // For ultra precision we would want to project into a plane, but in practice this is sufficient.
  506. var lineDirectionScratch = new Cartesian3();
  507. var matrix3Scratch = new Matrix3();
  508. var quaternionScratch = new Quaternion();
  509. function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) {
  510. var lineDirection = direction(endBottom, startBottom, lineDirectionScratch);
  511. var dot = Cartesian3.dot(lineDirection, endGeometryNormal);
  512. if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) {
  513. var vertexUp = direction(endTop, endBottom, vertexUpScratch);
  514. var angle = dot < MITER_BREAK_LARGE ? CesiumMath.PI_OVER_TWO : -CesiumMath.PI_OVER_TWO;
  515. var quaternion = Quaternion.fromAxisAngle(vertexUp, angle, quaternionScratch);
  516. var rotationMatrix = Matrix3.fromQuaternion(quaternion, matrix3Scratch);
  517. Matrix3.multiplyByVector(rotationMatrix, endGeometryNormal, endGeometryNormal);
  518. return true;
  519. }
  520. return false;
  521. }
  522. var endPosCartographicScratch = new Cartographic();
  523. var normalStartpointScratch = new Cartesian3();
  524. var normalEndpointScratch = new Cartesian3();
  525. function projectNormal(projection, cartographic, normal, projectedPosition, result) {
  526. var position = Cartographic.toCartesian(cartographic, projection._ellipsoid, normalStartpointScratch);
  527. var normalEndpoint = Cartesian3.add(position, normal, normalEndpointScratch);
  528. var flipNormal = false;
  529. var ellipsoid = projection._ellipsoid;
  530. var normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, endPosCartographicScratch);
  531. // If normal crosses the IDL, go the other way and flip the result.
  532. // In practice this almost never happens because the cartographic start
  533. // and end points of each segment are "nudged" to be on the same side
  534. // of the IDL and slightly away from the IDL.
  535. if (Math.abs(cartographic.longitude - normalEndpointCartographic.longitude) > CesiumMath.PI_OVER_TWO) {
  536. flipNormal = true;
  537. normalEndpoint = Cartesian3.subtract(position, normal, normalEndpointScratch);
  538. normalEndpointCartographic = ellipsoid.cartesianToCartographic(normalEndpoint, endPosCartographicScratch);
  539. }
  540. normalEndpointCartographic.height = 0.0;
  541. var normalEndpointProjected = projection.project(normalEndpointCartographic, result);
  542. result = Cartesian3.subtract(normalEndpointProjected, projectedPosition, result);
  543. result.z = 0.0;
  544. result = Cartesian3.normalize(result, result);
  545. if (flipNormal) {
  546. Cartesian3.negate(result, result);
  547. }
  548. return result;
  549. }
  550. var adjustHeightNormalScratch = new Cartesian3();
  551. var adjustHeightOffsetScratch = new Cartesian3();
  552. function adjustHeights(bottom, top, minHeight, maxHeight, adjustHeightBottom, adjustHeightTop) {
  553. // bottom and top should be at WALL_INITIAL_MIN_HEIGHT and WALL_INITIAL_MAX_HEIGHT, respectively
  554. var adjustHeightNormal = Cartesian3.subtract(top, bottom, adjustHeightNormalScratch);
  555. Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal);
  556. var distanceForBottom = minHeight - WALL_INITIAL_MIN_HEIGHT;
  557. var adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForBottom, adjustHeightOffsetScratch);
  558. Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom);
  559. var distanceForTop = maxHeight - WALL_INITIAL_MAX_HEIGHT;
  560. adjustHeightOffset = Cartesian3.multiplyByScalar(adjustHeightNormal, distanceForTop, adjustHeightOffsetScratch);
  561. Cartesian3.add(top, adjustHeightOffset, adjustHeightTop);
  562. }
  563. var nudgeDirectionScratch = new Cartesian3();
  564. function nudgeXZ(start, end) {
  565. var startToXZdistance = Plane.getPointDistance(XZ_PLANE, start);
  566. var endToXZdistance = Plane.getPointDistance(XZ_PLANE, end);
  567. var offset = nudgeDirectionScratch;
  568. // Larger epsilon than what's used in GeometryPipeline, a centimeter in world space
  569. if (CesiumMath.equalsEpsilon(startToXZdistance, 0.0, CesiumMath.EPSILON2)) {
  570. offset = direction(end, start, offset);
  571. Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON2, offset);
  572. Cartesian3.add(start, offset, start);
  573. } else if (CesiumMath.equalsEpsilon(endToXZdistance, 0.0, CesiumMath.EPSILON2)) {
  574. offset = direction(start, end, offset);
  575. Cartesian3.multiplyByScalar(offset, CesiumMath.EPSILON2, offset);
  576. Cartesian3.add(end, offset, end);
  577. }
  578. }
  579. // "Nudge" cartographic coordinates so start and end are on the same side of the IDL.
  580. // Nudge amounts are tiny, basically just an IDL flip.
  581. // Only used for 2D/CV.
  582. function nudgeCartographic(start, end) {
  583. var absStartLon = Math.abs(start.longitude);
  584. var absEndLon = Math.abs(end.longitude);
  585. if (CesiumMath.equalsEpsilon(absStartLon, CesiumMath.PI, CesiumMath.EPSILON11)) {
  586. var endSign = CesiumMath.sign(end.longitude);
  587. start.longitude = endSign * (absStartLon - CesiumMath.EPSILON11);
  588. return 1;
  589. } else if (CesiumMath.equalsEpsilon(absEndLon, CesiumMath.PI, CesiumMath.EPSILON11)) {
  590. var startSign = CesiumMath.sign(start.longitude);
  591. end.longitude = startSign * (absEndLon - CesiumMath.EPSILON11);
  592. return 2;
  593. }
  594. return 0;
  595. }
  596. var startCartographicScratch = new Cartographic();
  597. var endCartographicScratch = new Cartographic();
  598. var segmentStartTopScratch = new Cartesian3();
  599. var segmentEndTopScratch = new Cartesian3();
  600. var segmentStartBottomScratch = new Cartesian3();
  601. var segmentEndBottomScratch = new Cartesian3();
  602. var segmentStartNormalScratch = new Cartesian3();
  603. var segmentEndNormalScratch = new Cartesian3();
  604. var getHeightCartographics = [startCartographicScratch, endCartographicScratch];
  605. var getHeightRectangleScratch = new Rectangle();
  606. var adjustHeightStartTopScratch = new Cartesian3();
  607. var adjustHeightEndTopScratch = new Cartesian3();
  608. var adjustHeightStartBottomScratch = new Cartesian3();
  609. var adjustHeightEndBottomScratch = new Cartesian3();
  610. var segmentStart2DScratch = new Cartesian3();
  611. var segmentEnd2DScratch = new Cartesian3();
  612. var segmentStartNormal2DScratch = new Cartesian3();
  613. var segmentEndNormal2DScratch = new Cartesian3();
  614. var offsetScratch = new Cartesian3();
  615. var startUpScratch = new Cartesian3();
  616. var endUpScratch = new Cartesian3();
  617. var rightScratch = new Cartesian3();
  618. var startPlaneNormalScratch = new Cartesian3();
  619. var endPlaneNormalScratch = new Cartesian3();
  620. var encodeScratch = new EncodedCartesian3();
  621. var encodeScratch2D = new EncodedCartesian3();
  622. var forwardOffset2DScratch = new Cartesian3();
  623. var right2DScratch = new Cartesian3();
  624. var normalNudgeScratch = new Cartesian3();
  625. var scratchBoundingSpheres = [new BoundingSphere(), new BoundingSphere()];
  626. // Winding order is reversed so each segment's volume is inside-out
  627. var REFERENCE_INDICES = [
  628. 0, 2, 1, 0, 3, 2, // right
  629. 0, 7, 3, 0, 4, 7, // start
  630. 0, 5, 4, 0, 1, 5, // bottom
  631. 5, 7, 4, 5, 6, 7, // left
  632. 5, 2, 6, 5, 1, 2, // end
  633. 3, 6, 2, 3, 7, 6 // top
  634. ];
  635. var REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length;
  636. // Decompose the "wall" into a series of shadow volumes.
  637. // Each shadow volume's vertices encode a description of the line it contains,
  638. // including mitering planes at the end points, a plane along the line itself,
  639. // and attributes for computing length-wise texture coordinates.
  640. function generateGeometryAttributes(loop, projection, bottomPositionsArray, topPositionsArray, normalsArray, cartographicsArray, compute2dAttributes) {
  641. var i;
  642. var index;
  643. var ellipsoid = projection._ellipsoid;
  644. // Each segment will have 8 vertices
  645. var segmentCount = (bottomPositionsArray.length / 3) - 1;
  646. var vertexCount = segmentCount * 8;
  647. var arraySizeVec4 = vertexCount * 4;
  648. var indexCount = segmentCount * 36;
  649. var indices = vertexCount > 65535 ? new Uint32Array(indexCount) : new Uint16Array(indexCount);
  650. var positionsArray = new Float64Array(vertexCount * 3);
  651. var startHiAndForwardOffsetX = new Float32Array(arraySizeVec4);
  652. var startLoAndForwardOffsetY = new Float32Array(arraySizeVec4);
  653. var startNormalAndForwardOffsetZ = new Float32Array(arraySizeVec4);
  654. var endNormalAndTextureCoordinateNormalizationX = new Float32Array(arraySizeVec4);
  655. var rightNormalAndTextureCoordinateNormalizationY = new Float32Array(arraySizeVec4);
  656. var startHiLo2D;
  657. var offsetAndRight2D;
  658. var startEndNormals2D;
  659. var texcoordNormalization2D;
  660. if (compute2dAttributes) {
  661. startHiLo2D = new Float32Array(arraySizeVec4);
  662. offsetAndRight2D = new Float32Array(arraySizeVec4);
  663. startEndNormals2D = new Float32Array(arraySizeVec4);
  664. texcoordNormalization2D = new Float32Array(vertexCount * 2);
  665. }
  666. /*** Compute total lengths for texture coordinate normalization ***/
  667. // 2D
  668. var cartographicsLength = cartographicsArray.length / 2;
  669. var length2D = 0.0;
  670. var startCartographic = startCartographicScratch;
  671. startCartographic.height = 0.0;
  672. var endCartographic = endCartographicScratch;
  673. endCartographic.height = 0.0;
  674. var segmentStartCartesian = segmentStartTopScratch;
  675. var segmentEndCartesian = segmentEndTopScratch;
  676. if (compute2dAttributes) {
  677. index = 0;
  678. for (i = 1; i < cartographicsLength; i++) {
  679. // Don't clone anything from previous segment b/c possible IDL touch
  680. startCartographic.latitude = cartographicsArray[index];
  681. startCartographic.longitude = cartographicsArray[index + 1];
  682. endCartographic.latitude = cartographicsArray[index + 2];
  683. endCartographic.longitude = cartographicsArray[index + 3];
  684. segmentStartCartesian = projection.project(startCartographic, segmentStartCartesian);
  685. segmentEndCartesian = projection.project(endCartographic, segmentEndCartesian);
  686. length2D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian);
  687. index += 2;
  688. }
  689. }
  690. // 3D
  691. var positionsLength = topPositionsArray.length / 3;
  692. segmentEndCartesian = Cartesian3.unpack(topPositionsArray, 0, segmentEndCartesian);
  693. var length3D = 0.0;
  694. index = 3;
  695. for (i = 1; i < positionsLength; i++) {
  696. segmentStartCartesian = Cartesian3.clone(segmentEndCartesian, segmentStartCartesian);
  697. segmentEndCartesian = Cartesian3.unpack(topPositionsArray, index, segmentEndCartesian);
  698. length3D += Cartesian3.distance(segmentStartCartesian, segmentEndCartesian);
  699. index += 3;
  700. }
  701. /*** Generate segments ***/
  702. var j;
  703. index = 3;
  704. var cartographicsIndex = 0;
  705. var vec2sWriteIndex = 0;
  706. var vec3sWriteIndex = 0;
  707. var vec4sWriteIndex = 0;
  708. var miterBroken = false;
  709. var endBottom = Cartesian3.unpack(bottomPositionsArray, 0, segmentEndBottomScratch);
  710. var endTop = Cartesian3.unpack(topPositionsArray, 0, segmentEndTopScratch);
  711. var endGeometryNormal = Cartesian3.unpack(normalsArray, 0, segmentEndNormalScratch);
  712. if (loop) {
  713. var preEndBottom = Cartesian3.unpack(bottomPositionsArray, bottomPositionsArray.length - 6, segmentStartBottomScratch);
  714. if (breakMiter(endGeometryNormal, preEndBottom, endBottom, endTop)) {
  715. // Miter broken as if for the last point in the loop, needs to be inverted for first point (clone of endBottom)
  716. endGeometryNormal = Cartesian3.negate(endGeometryNormal, endGeometryNormal);
  717. }
  718. }
  719. var lengthSoFar3D = 0.0;
  720. var lengthSoFar2D = 0.0;
  721. // For translating bounding volume
  722. var sumHeights = 0.0;
  723. for (i = 0; i < segmentCount; i++) {
  724. var startBottom = Cartesian3.clone(endBottom, segmentStartBottomScratch);
  725. var startTop = Cartesian3.clone(endTop, segmentStartTopScratch);
  726. var startGeometryNormal = Cartesian3.clone(endGeometryNormal, segmentStartNormalScratch);
  727. if (miterBroken) {
  728. startGeometryNormal = Cartesian3.negate(startGeometryNormal, startGeometryNormal);
  729. }
  730. endBottom = Cartesian3.unpack(bottomPositionsArray, index, segmentEndBottomScratch);
  731. endTop = Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch);
  732. endGeometryNormal = Cartesian3.unpack(normalsArray, index, segmentEndNormalScratch);
  733. miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop);
  734. // 2D - don't clone anything from previous segment b/c possible IDL touch
  735. startCartographic.latitude = cartographicsArray[cartographicsIndex];
  736. startCartographic.longitude = cartographicsArray[cartographicsIndex + 1];
  737. endCartographic.latitude = cartographicsArray[cartographicsIndex + 2];
  738. endCartographic.longitude = cartographicsArray[cartographicsIndex + 3];
  739. var start2D;
  740. var end2D;
  741. var startGeometryNormal2D;
  742. var endGeometryNormal2D;
  743. if (compute2dAttributes) {
  744. var nudgeResult = nudgeCartographic(startCartographic, endCartographic);
  745. start2D = projection.project(startCartographic, segmentStart2DScratch);
  746. end2D = projection.project(endCartographic, segmentEnd2DScratch);
  747. var direction2D = direction(end2D, start2D, forwardOffset2DScratch);
  748. direction2D.y = Math.abs(direction2D.y);
  749. startGeometryNormal2D = segmentStartNormal2DScratch;
  750. endGeometryNormal2D = segmentEndNormal2DScratch;
  751. if (nudgeResult === 0 || Cartesian3.dot(direction2D, Cartesian3.UNIT_Y) > MITER_BREAK_SMALL) {
  752. // No nudge - project the original normal
  753. // Or, if the line's angle relative to the IDL is very acute,
  754. // in which case snapping will produce oddly shaped volumes.
  755. startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch);
  756. endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch);
  757. } else if (nudgeResult === 1) {
  758. // Start is close to IDL - snap start normal to align with IDL
  759. endGeometryNormal2D = projectNormal(projection, endCartographic, endGeometryNormal, end2D, segmentEndNormal2DScratch);
  760. startGeometryNormal2D.x = 0.0;
  761. // If start longitude is negative and end longitude is less negative, relative right is unit -Y
  762. // If start longitude is positive and end longitude is less positive, relative right is unit +Y
  763. startGeometryNormal2D.y = CesiumMath.sign(startCartographic.longitude - Math.abs(endCartographic.longitude));
  764. startGeometryNormal2D.z = 0.0;
  765. } else {
  766. // End is close to IDL - snap end normal to align with IDL
  767. startGeometryNormal2D = projectNormal(projection, startCartographic, startGeometryNormal, start2D, segmentStartNormal2DScratch);
  768. endGeometryNormal2D.x = 0.0;
  769. // If end longitude is negative and start longitude is less negative, relative right is unit Y
  770. // If end longitude is positive and start longitude is less positive, relative right is unit -Y
  771. endGeometryNormal2D.y = CesiumMath.sign(startCartographic.longitude - endCartographic.longitude);
  772. endGeometryNormal2D.z = 0.0;
  773. }
  774. }
  775. /****************************************
  776. * Geometry descriptors of a "line on terrain,"
  777. * as opposed to the "shadow volume used to draw
  778. * the line on terrain":
  779. * - position of start + offset to end
  780. * - start, end, and right-facing planes
  781. * - encoded texture coordinate offsets
  782. ****************************************/
  783. /** 3D **/
  784. var segmentLength3D = Cartesian3.distance(startTop, endTop);
  785. var encodedStart = EncodedCartesian3.fromCartesian(startBottom, encodeScratch);
  786. var forwardOffset = Cartesian3.subtract(endBottom, startBottom, offsetScratch);
  787. var forward = Cartesian3.normalize(forwardOffset, rightScratch);
  788. var startUp = Cartesian3.subtract(startTop, startBottom, startUpScratch);
  789. startUp = Cartesian3.normalize(startUp, startUp);
  790. var rightNormal = Cartesian3.cross(forward, startUp, rightScratch);
  791. rightNormal = Cartesian3.normalize(rightNormal, rightNormal);
  792. var startPlaneNormal = Cartesian3.cross(startUp, startGeometryNormal, startPlaneNormalScratch);
  793. startPlaneNormal = Cartesian3.normalize(startPlaneNormal, startPlaneNormal);
  794. var endUp = Cartesian3.subtract(endTop, endBottom, endUpScratch);
  795. endUp = Cartesian3.normalize(endUp, endUp);
  796. var endPlaneNormal = Cartesian3.cross(endGeometryNormal, endUp, endPlaneNormalScratch);
  797. endPlaneNormal = Cartesian3.normalize(endPlaneNormal, endPlaneNormal);
  798. var texcoordNormalization3DX = segmentLength3D / length3D;
  799. var texcoordNormalization3DY = lengthSoFar3D / length3D;
  800. /** 2D **/
  801. var segmentLength2D = 0.0;
  802. var encodedStart2D;
  803. var forwardOffset2D;
  804. var right2D;
  805. var texcoordNormalization2DX = 0.0;
  806. var texcoordNormalization2DY = 0.0;
  807. if (compute2dAttributes) {
  808. segmentLength2D = Cartesian3.distance(start2D, end2D);
  809. encodedStart2D = EncodedCartesian3.fromCartesian(start2D, encodeScratch2D);
  810. forwardOffset2D = Cartesian3.subtract(end2D, start2D, forwardOffset2DScratch);
  811. // Right direction is just forward direction rotated by -90 degrees around Z
  812. // Similarly with plane normals
  813. right2D = Cartesian3.normalize(forwardOffset2D, right2DScratch);
  814. var swap = right2D.x;
  815. right2D.x = right2D.y;
  816. right2D.y = -swap;
  817. texcoordNormalization2DX = segmentLength2D / length2D;
  818. texcoordNormalization2DY = lengthSoFar2D / length2D;
  819. }
  820. /** Pack **/
  821. for (j = 0; j < 8; j++) {
  822. var vec4Index = vec4sWriteIndex + j * 4;
  823. var vec2Index = vec2sWriteIndex + j * 2;
  824. var wIndex = vec4Index + 3;
  825. // Encode sidedness of vertex relative to right plane in texture coordinate normalization X,
  826. // whether vertex is top or bottom of volume in sign/magnitude of normalization Y.
  827. var rightPlaneSide = j < 4 ? 1.0 : -1.0;
  828. var topBottomSide = (j === 2 || j === 3 || j === 6 || j === 7) ? 1.0 : -1.0;
  829. // 3D
  830. Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index);
  831. startHiAndForwardOffsetX[wIndex] = forwardOffset.x;
  832. Cartesian3.pack(encodedStart.low, startLoAndForwardOffsetY, vec4Index);
  833. startLoAndForwardOffsetY[wIndex] = forwardOffset.y;
  834. Cartesian3.pack(startPlaneNormal, startNormalAndForwardOffsetZ, vec4Index);
  835. startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z;
  836. Cartesian3.pack(endPlaneNormal, endNormalAndTextureCoordinateNormalizationX, vec4Index);
  837. endNormalAndTextureCoordinateNormalizationX[wIndex] = texcoordNormalization3DX * rightPlaneSide;
  838. Cartesian3.pack(rightNormal, rightNormalAndTextureCoordinateNormalizationY, vec4Index);
  839. var texcoordNormalization = texcoordNormalization3DY * topBottomSide;
  840. if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
  841. texcoordNormalization = 9.0; // some value greater than 1.0
  842. }
  843. rightNormalAndTextureCoordinateNormalizationY[wIndex] = texcoordNormalization;
  844. // 2D
  845. if (compute2dAttributes) {
  846. startHiLo2D[vec4Index] = encodedStart2D.high.x;
  847. startHiLo2D[vec4Index + 1] = encodedStart2D.high.y;
  848. startHiLo2D[vec4Index + 2] = encodedStart2D.low.x;
  849. startHiLo2D[vec4Index + 3] = encodedStart2D.low.y;
  850. startEndNormals2D[vec4Index] = -startGeometryNormal2D.y;
  851. startEndNormals2D[vec4Index + 1] = startGeometryNormal2D.x;
  852. startEndNormals2D[vec4Index + 2] = endGeometryNormal2D.y;
  853. startEndNormals2D[vec4Index + 3] = -endGeometryNormal2D.x;
  854. offsetAndRight2D[vec4Index] = forwardOffset2D.x;
  855. offsetAndRight2D[vec4Index + 1] = forwardOffset2D.y;
  856. offsetAndRight2D[vec4Index + 2] = right2D.x;
  857. offsetAndRight2D[vec4Index + 3] = right2D.y;
  858. texcoordNormalization2D[vec2Index] = texcoordNormalization2DX * rightPlaneSide;
  859. texcoordNormalization = texcoordNormalization2DY * topBottomSide;
  860. if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
  861. texcoordNormalization = 9.0; // some value greater than 1.0
  862. }
  863. texcoordNormalization2D[vec2Index + 1] = texcoordNormalization;
  864. }
  865. }
  866. // Adjust height of volume in 3D
  867. var adjustHeightStartBottom = adjustHeightStartBottomScratch;
  868. var adjustHeightEndBottom = adjustHeightEndBottomScratch;
  869. var adjustHeightStartTop = adjustHeightStartTopScratch;
  870. var adjustHeightEndTop = adjustHeightEndTopScratch;
  871. var getHeightsRectangle = Rectangle.fromCartographicArray(getHeightCartographics, getHeightRectangleScratch);
  872. var minMaxHeights = ApproximateTerrainHeights.getMinimumMaximumHeights(getHeightsRectangle, ellipsoid);
  873. var minHeight = minMaxHeights.minimumTerrainHeight;
  874. var maxHeight = minMaxHeights.maximumTerrainHeight;
  875. sumHeights += minHeight;
  876. sumHeights += maxHeight;
  877. adjustHeights(startBottom, startTop, minHeight, maxHeight, adjustHeightStartBottom, adjustHeightStartTop);
  878. adjustHeights(endBottom, endTop, minHeight, maxHeight, adjustHeightEndBottom, adjustHeightEndTop);
  879. // Nudge the positions away from the "polyline" a little bit to prevent errors in GeometryPipeline
  880. var normalNudge = Cartesian3.multiplyByScalar(rightNormal, CesiumMath.EPSILON5, normalNudgeScratch);
  881. Cartesian3.add(adjustHeightStartBottom, normalNudge, adjustHeightStartBottom);
  882. Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  883. Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  884. Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  885. // If the segment is very close to the XZ plane, nudge the vertices slightly to avoid touching it.
  886. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
  887. nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  888. Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex);
  889. Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3);
  890. Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6);
  891. Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9);
  892. normalNudge = Cartesian3.multiplyByScalar(rightNormal, -2.0 * CesiumMath.EPSILON5, normalNudgeScratch);
  893. Cartesian3.add(adjustHeightStartBottom, normalNudge, adjustHeightStartBottom);
  894. Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
  895. Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
  896. Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  897. nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
  898. nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  899. Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex + 12);
  900. Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 15);
  901. Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18);
  902. Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21);
  903. cartographicsIndex += 2;
  904. index += 3;
  905. vec2sWriteIndex += 16;
  906. vec3sWriteIndex += 24;
  907. vec4sWriteIndex += 32;
  908. lengthSoFar3D += segmentLength3D;
  909. lengthSoFar2D += segmentLength2D;
  910. }
  911. index = 0;
  912. var indexOffset = 0;
  913. for (i = 0; i < segmentCount; i++) {
  914. for (j = 0; j < REFERENCE_INDICES_LENGTH; j++) {
  915. indices[index + j] = REFERENCE_INDICES[j] + indexOffset;
  916. }
  917. indexOffset += 8;
  918. index += REFERENCE_INDICES_LENGTH;
  919. }
  920. var boundingSpheres = scratchBoundingSpheres;
  921. BoundingSphere.fromVertices(bottomPositionsArray, Cartesian3.ZERO, 3, boundingSpheres[0]);
  922. BoundingSphere.fromVertices(topPositionsArray, Cartesian3.ZERO, 3, boundingSpheres[1]);
  923. var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
  924. // Adjust bounding sphere height and radius to cover more of the volume
  925. boundingSphere.radius += sumHeights / (segmentCount * 2.0);
  926. var attributes = {
  927. position : new GeometryAttribute({
  928. componentDatatype : ComponentDatatype.DOUBLE,
  929. componentsPerAttribute : 3,
  930. normalize : false,
  931. values : positionsArray
  932. }),
  933. startHiAndForwardOffsetX : getVec4GeometryAttribute(startHiAndForwardOffsetX),
  934. startLoAndForwardOffsetY : getVec4GeometryAttribute(startLoAndForwardOffsetY),
  935. startNormalAndForwardOffsetZ : getVec4GeometryAttribute(startNormalAndForwardOffsetZ),
  936. endNormalAndTextureCoordinateNormalizationX : getVec4GeometryAttribute(endNormalAndTextureCoordinateNormalizationX),
  937. rightNormalAndTextureCoordinateNormalizationY : getVec4GeometryAttribute(rightNormalAndTextureCoordinateNormalizationY)
  938. };
  939. if (compute2dAttributes) {
  940. attributes.startHiLo2D = getVec4GeometryAttribute(startHiLo2D);
  941. attributes.offsetAndRight2D = getVec4GeometryAttribute(offsetAndRight2D);
  942. attributes.startEndNormals2D = getVec4GeometryAttribute(startEndNormals2D);
  943. attributes.texcoordNormalization2D = new GeometryAttribute({
  944. componentDatatype : ComponentDatatype.FLOAT,
  945. componentsPerAttribute : 2,
  946. normalize : false,
  947. values : texcoordNormalization2D
  948. });
  949. }
  950. return new Geometry({
  951. attributes : attributes,
  952. indices : indices,
  953. boundingSphere : boundingSphere
  954. });
  955. }
  956. function getVec4GeometryAttribute(typedArray) {
  957. return new GeometryAttribute({
  958. componentDatatype : ComponentDatatype.FLOAT,
  959. componentsPerAttribute : 4,
  960. normalize : false,
  961. values : typedArray
  962. });
  963. }
  964. /**
  965. * Approximates an ellipsoid-tangent vector in 2D by projecting the end point into 2D.
  966. * Exposed for testing.
  967. *
  968. * @param {MapProjection} projection Map Projection for projecting coordinates to 2D.
  969. * @param {Cartographic} cartographic The cartographic origin point of the normal.
  970. * Used to check if the normal crosses the IDL during projection.
  971. * @param {Cartesian3} normal The normal in 3D.
  972. * @param {Cartesian3} projectedPosition The projected origin point of the normal in 2D.
  973. * @param {Cartesian3} result Result parameter on which to store the projected normal.
  974. * @private
  975. */
  976. GroundPolylineGeometry._projectNormal = projectNormal;
  977. export default GroundPolylineGeometry;