Polyline.js 12 KB

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