Polyline.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. import arrayRemoveDuplicates from "../Core/arrayRemoveDuplicates.js";
  2. import BoundingSphere from "../Core/BoundingSphere.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Color from "../Core/Color.js";
  5. import defaultValue from "../Core/defaultValue.js";
  6. import defined from "../Core/defined.js";
  7. import DeveloperError from "../Core/DeveloperError.js";
  8. import DistanceDisplayCondition from "../Core/DistanceDisplayCondition.js";
  9. import Matrix4 from "../Core/Matrix4.js";
  10. import PolylinePipeline from "../Core/PolylinePipeline.js";
  11. import Material from "./Material.js";
  12. /**
  13. * A renderable polyline. Create this by calling {@link PolylineCollection#add}
  14. *
  15. * @alias Polyline
  16. * @internalConstructor
  17. * @class
  18. *
  19. * @param {Object} options Object with the following properties:
  20. * @param {Boolean} [options.show=true] <code>true</code> if this polyline will be shown; otherwise, <code>false</code>.
  21. * @param {Number} [options.width=1.0] The width of the polyline in pixels.
  22. * @param {Boolean} [options.loop=false] Whether a line segment will be added between the last and first line positions to make this line a loop.
  23. * @param {Material} [options.material=Material.ColorType] The material.
  24. * @param {Cartesian3[]} [options.positions] The positions.
  25. * @param {Object} [options.id] The user-defined object to be returned when this polyline is picked.
  26. * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this polyline will be displayed.
  27. * @param {PolylineCollection} polylineCollection The renderable polyline collection.
  28. *
  29. * @see PolylineCollection
  30. *
  31. */
  32. function Polyline(options, polylineCollection) {
  33. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  34. this._show = defaultValue(options.show, true);
  35. this._width = defaultValue(options.width, 1.0);
  36. this._loop = defaultValue(options.loop, false);
  37. this._distanceDisplayCondition = options.distanceDisplayCondition;
  38. this._material = options.material;
  39. if (!defined(this._material)) {
  40. this._material = Material.fromType(Material.ColorType, {
  41. color: new Color(1.0, 1.0, 1.0, 1.0),
  42. });
  43. }
  44. let positions = options.positions;
  45. if (!defined(positions)) {
  46. positions = [];
  47. }
  48. this._positions = positions;
  49. this._actualPositions = arrayRemoveDuplicates(
  50. positions,
  51. Cartesian3.equalsEpsilon
  52. );
  53. if (this._loop && this._actualPositions.length > 2) {
  54. if (this._actualPositions === this._positions) {
  55. this._actualPositions = positions.slice();
  56. }
  57. this._actualPositions.push(Cartesian3.clone(this._actualPositions[0]));
  58. }
  59. this._length = this._actualPositions.length;
  60. this._id = options.id;
  61. let modelMatrix;
  62. if (defined(polylineCollection)) {
  63. modelMatrix = Matrix4.clone(polylineCollection.modelMatrix);
  64. }
  65. this._modelMatrix = modelMatrix;
  66. this._segments = PolylinePipeline.wrapLongitude(
  67. this._actualPositions,
  68. modelMatrix
  69. );
  70. this._actualLength = undefined;
  71. // eslint-disable-next-line no-use-before-define
  72. this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
  73. this._polylineCollection = polylineCollection;
  74. this._dirty = false;
  75. this._pickId = undefined;
  76. this._boundingVolume = BoundingSphere.fromPoints(this._actualPositions);
  77. this._boundingVolumeWC = BoundingSphere.transform(
  78. this._boundingVolume,
  79. this._modelMatrix
  80. );
  81. this._boundingVolume2D = new BoundingSphere(); // modified in PolylineCollection
  82. }
  83. const POSITION_INDEX = (Polyline.POSITION_INDEX = 0);
  84. const SHOW_INDEX = (Polyline.SHOW_INDEX = 1);
  85. const WIDTH_INDEX = (Polyline.WIDTH_INDEX = 2);
  86. const MATERIAL_INDEX = (Polyline.MATERIAL_INDEX = 3);
  87. const POSITION_SIZE_INDEX = (Polyline.POSITION_SIZE_INDEX = 4);
  88. const DISTANCE_DISPLAY_CONDITION = (Polyline.DISTANCE_DISPLAY_CONDITION = 5);
  89. const NUMBER_OF_PROPERTIES = (Polyline.NUMBER_OF_PROPERTIES = 6);
  90. function makeDirty(polyline, propertyChanged) {
  91. ++polyline._propertiesChanged[propertyChanged];
  92. const polylineCollection = polyline._polylineCollection;
  93. if (defined(polylineCollection)) {
  94. polylineCollection._updatePolyline(polyline, propertyChanged);
  95. polyline._dirty = true;
  96. }
  97. }
  98. Object.defineProperties(Polyline.prototype, {
  99. /**
  100. * Determines if this polyline will be shown. Use this to hide or show a polyline, instead
  101. * of removing it and re-adding it to the collection.
  102. * @memberof Polyline.prototype
  103. * @type {Boolean}
  104. */
  105. show: {
  106. get: function () {
  107. return this._show;
  108. },
  109. set: function (value) {
  110. //>>includeStart('debug', pragmas.debug);
  111. if (!defined(value)) {
  112. throw new DeveloperError("value is required.");
  113. }
  114. //>>includeEnd('debug');
  115. if (value !== this._show) {
  116. this._show = value;
  117. makeDirty(this, SHOW_INDEX);
  118. }
  119. },
  120. },
  121. /**
  122. * Gets or sets the positions of the polyline.
  123. * @memberof Polyline.prototype
  124. * @type {Cartesian3[]}
  125. * @example
  126. * polyline.positions = Cesium.Cartesian3.fromDegreesArray([
  127. * 0.0, 0.0,
  128. * 10.0, 0.0,
  129. * 0.0, 20.0
  130. * ]);
  131. */
  132. positions: {
  133. get: function () {
  134. return this._positions;
  135. },
  136. set: function (value) {
  137. //>>includeStart('debug', pragmas.debug);
  138. if (!defined(value)) {
  139. throw new DeveloperError("value is required.");
  140. }
  141. //>>includeEnd('debug');
  142. let positions = arrayRemoveDuplicates(value, Cartesian3.equalsEpsilon);
  143. if (this._loop && positions.length > 2) {
  144. if (positions === value) {
  145. positions = value.slice();
  146. }
  147. positions.push(Cartesian3.clone(positions[0]));
  148. }
  149. if (
  150. this._actualPositions.length !== positions.length ||
  151. this._actualPositions.length !== this._length
  152. ) {
  153. makeDirty(this, POSITION_SIZE_INDEX);
  154. }
  155. this._positions = value;
  156. this._actualPositions = positions;
  157. this._length = positions.length;
  158. this._boundingVolume = BoundingSphere.fromPoints(
  159. this._actualPositions,
  160. this._boundingVolume
  161. );
  162. this._boundingVolumeWC = BoundingSphere.transform(
  163. this._boundingVolume,
  164. this._modelMatrix,
  165. this._boundingVolumeWC
  166. );
  167. makeDirty(this, POSITION_INDEX);
  168. this.update();
  169. },
  170. },
  171. /**
  172. * Gets or sets the surface appearance of the polyline. This can be one of several built-in {@link Material} objects or a custom material, scripted with
  173. * {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric}.
  174. * @memberof Polyline.prototype
  175. * @type {Material}
  176. */
  177. material: {
  178. get: function () {
  179. return this._material;
  180. },
  181. set: function (material) {
  182. //>>includeStart('debug', pragmas.debug);
  183. if (!defined(material)) {
  184. throw new DeveloperError("material is required.");
  185. }
  186. //>>includeEnd('debug');
  187. if (this._material !== material) {
  188. this._material = material;
  189. makeDirty(this, MATERIAL_INDEX);
  190. }
  191. },
  192. },
  193. /**
  194. * Gets or sets the width of the polyline.
  195. * @memberof Polyline.prototype
  196. * @type {Number}
  197. */
  198. width: {
  199. get: function () {
  200. return this._width;
  201. },
  202. set: function (value) {
  203. //>>includeStart('debug', pragmas.debug)
  204. if (!defined(value)) {
  205. throw new DeveloperError("value is required.");
  206. }
  207. //>>includeEnd('debug');
  208. const width = this._width;
  209. if (value !== width) {
  210. this._width = value;
  211. makeDirty(this, WIDTH_INDEX);
  212. }
  213. },
  214. },
  215. /**
  216. * Gets or sets whether a line segment will be added between the first and last polyline positions.
  217. * @memberof Polyline.prototype
  218. * @type {Boolean}
  219. */
  220. loop: {
  221. get: function () {
  222. return this._loop;
  223. },
  224. set: function (value) {
  225. //>>includeStart('debug', pragmas.debug)
  226. if (!defined(value)) {
  227. throw new DeveloperError("value is required.");
  228. }
  229. //>>includeEnd('debug');
  230. if (value !== this._loop) {
  231. let positions = this._actualPositions;
  232. if (value) {
  233. if (
  234. positions.length > 2 &&
  235. !Cartesian3.equals(positions[0], positions[positions.length - 1])
  236. ) {
  237. if (positions.length === this._positions.length) {
  238. this._actualPositions = positions = this._positions.slice();
  239. }
  240. positions.push(Cartesian3.clone(positions[0]));
  241. }
  242. } else if (
  243. positions.length > 2 &&
  244. Cartesian3.equals(positions[0], positions[positions.length - 1])
  245. ) {
  246. if (positions.length - 1 === this._positions.length) {
  247. this._actualPositions = this._positions;
  248. } else {
  249. positions.pop();
  250. }
  251. }
  252. this._loop = value;
  253. makeDirty(this, POSITION_SIZE_INDEX);
  254. }
  255. },
  256. },
  257. /**
  258. * Gets or sets the user-defined value returned when the polyline is picked.
  259. * @memberof Polyline.prototype
  260. * @type {*}
  261. */
  262. id: {
  263. get: function () {
  264. return this._id;
  265. },
  266. set: function (value) {
  267. this._id = value;
  268. if (defined(this._pickId)) {
  269. this._pickId.object.id = value;
  270. }
  271. },
  272. },
  273. /**
  274. * @private
  275. */
  276. pickId: {
  277. get: function () {
  278. return this._pickId;
  279. },
  280. },
  281. /**
  282. * Gets the destruction status of this polyline
  283. * @memberof Polyline.prototype
  284. * @type {Boolean}
  285. * @default false
  286. * @private
  287. */
  288. isDestroyed: {
  289. get: function () {
  290. return !defined(this._polylineCollection);
  291. },
  292. },
  293. /**
  294. * Gets or sets the condition specifying at what distance from the camera that this polyline will be displayed.
  295. * @memberof Polyline.prototype
  296. * @type {DistanceDisplayCondition}
  297. * @default undefined
  298. */
  299. distanceDisplayCondition: {
  300. get: function () {
  301. return this._distanceDisplayCondition;
  302. },
  303. set: function (value) {
  304. //>>includeStart('debug', pragmas.debug);
  305. if (defined(value) && value.far <= value.near) {
  306. throw new DeveloperError(
  307. "far distance must be greater than near distance."
  308. );
  309. }
  310. //>>includeEnd('debug');
  311. if (
  312. !DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)
  313. ) {
  314. this._distanceDisplayCondition = DistanceDisplayCondition.clone(
  315. value,
  316. this._distanceDisplayCondition
  317. );
  318. makeDirty(this, DISTANCE_DISPLAY_CONDITION);
  319. }
  320. },
  321. },
  322. });
  323. /**
  324. * @private
  325. */
  326. Polyline.prototype.update = function () {
  327. let modelMatrix = Matrix4.IDENTITY;
  328. if (defined(this._polylineCollection)) {
  329. modelMatrix = this._polylineCollection.modelMatrix;
  330. }
  331. const segmentPositionsLength = this._segments.positions.length;
  332. const segmentLengths = this._segments.lengths;
  333. const positionsChanged =
  334. this._propertiesChanged[POSITION_INDEX] > 0 ||
  335. this._propertiesChanged[POSITION_SIZE_INDEX] > 0;
  336. if (!Matrix4.equals(modelMatrix, this._modelMatrix) || positionsChanged) {
  337. this._segments = PolylinePipeline.wrapLongitude(
  338. this._actualPositions,
  339. modelMatrix
  340. );
  341. this._boundingVolumeWC = BoundingSphere.transform(
  342. this._boundingVolume,
  343. modelMatrix,
  344. this._boundingVolumeWC
  345. );
  346. }
  347. this._modelMatrix = Matrix4.clone(modelMatrix, this._modelMatrix);
  348. if (this._segments.positions.length !== segmentPositionsLength) {
  349. // number of positions changed
  350. makeDirty(this, POSITION_SIZE_INDEX);
  351. } else {
  352. const length = segmentLengths.length;
  353. for (let i = 0; i < length; ++i) {
  354. if (segmentLengths[i] !== this._segments.lengths[i]) {
  355. // indices changed
  356. makeDirty(this, POSITION_SIZE_INDEX);
  357. break;
  358. }
  359. }
  360. }
  361. };
  362. /**
  363. * @private
  364. */
  365. Polyline.prototype.getPickId = function (context) {
  366. if (!defined(this._pickId)) {
  367. this._pickId = context.createPickId({
  368. primitive: this,
  369. collection: this._polylineCollection,
  370. id: this._id,
  371. });
  372. }
  373. return this._pickId;
  374. };
  375. Polyline.prototype._clean = function () {
  376. this._dirty = false;
  377. const properties = this._propertiesChanged;
  378. for (let k = 0; k < NUMBER_OF_PROPERTIES - 1; ++k) {
  379. properties[k] = 0;
  380. }
  381. };
  382. Polyline.prototype._destroy = function () {
  383. this._pickId = this._pickId && this._pickId.destroy();
  384. this._material = this._material && this._material.destroy();
  385. this._polylineCollection = undefined;
  386. };
  387. export default Polyline;