LabelCollection.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. /*global define*/
  2. define([
  3. '../Core/Cartesian2',
  4. '../Core/defaultValue',
  5. '../Core/defined',
  6. '../Core/defineProperties',
  7. '../Core/destroyObject',
  8. '../Core/DeveloperError',
  9. '../Core/Matrix4',
  10. '../Core/writeTextToCanvas',
  11. './BillboardCollection',
  12. './HorizontalOrigin',
  13. './Label',
  14. './LabelStyle',
  15. './TextureAtlas',
  16. './VerticalOrigin'
  17. ], function(
  18. Cartesian2,
  19. defaultValue,
  20. defined,
  21. defineProperties,
  22. destroyObject,
  23. DeveloperError,
  24. Matrix4,
  25. writeTextToCanvas,
  26. BillboardCollection,
  27. HorizontalOrigin,
  28. Label,
  29. LabelStyle,
  30. TextureAtlas,
  31. VerticalOrigin) {
  32. "use strict";
  33. // A glyph represents a single character in a particular label. It may or may
  34. // not have a billboard, depending on whether the texture info has an index into
  35. // the the label collection's texture atlas. Invisible characters have no texture, and
  36. // no billboard. However, it always has a valid dimensions object.
  37. function Glyph() {
  38. this.textureInfo = undefined;
  39. this.dimensions = undefined;
  40. this.billboard = undefined;
  41. }
  42. // GlyphTextureInfo represents a single character, drawn in a particular style,
  43. // shared and reference counted across all labels. It may or may not have an
  44. // index into the label collection's texture atlas, depending on whether the character
  45. // has both width and height, but it always has a valid dimensions object.
  46. function GlyphTextureInfo(labelCollection, index, dimensions) {
  47. this.labelCollection = labelCollection;
  48. this.index = index;
  49. this.dimensions = dimensions;
  50. }
  51. // reusable object for calling writeTextToCanvas
  52. var writeTextToCanvasParameters = {};
  53. function createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin) {
  54. writeTextToCanvasParameters.font = font;
  55. writeTextToCanvasParameters.fillColor = fillColor;
  56. writeTextToCanvasParameters.strokeColor = outlineColor;
  57. writeTextToCanvasParameters.strokeWidth = outlineWidth;
  58. if (verticalOrigin === VerticalOrigin.BOTTOM) {
  59. writeTextToCanvasParameters.textBaseline = 'bottom';
  60. } else if (verticalOrigin === VerticalOrigin.TOP) {
  61. writeTextToCanvasParameters.textBaseline = 'top';
  62. } else {
  63. // VerticalOrigin.CENTER
  64. writeTextToCanvasParameters.textBaseline = 'middle';
  65. }
  66. writeTextToCanvasParameters.fill = style === LabelStyle.FILL || style === LabelStyle.FILL_AND_OUTLINE;
  67. writeTextToCanvasParameters.stroke = style === LabelStyle.OUTLINE || style === LabelStyle.FILL_AND_OUTLINE;
  68. return writeTextToCanvas(character, writeTextToCanvasParameters);
  69. }
  70. function unbindGlyph(labelCollection, glyph) {
  71. glyph.textureInfo = undefined;
  72. glyph.dimensions = undefined;
  73. var billboard = glyph.billboard;
  74. if (defined(billboard)) {
  75. billboard.show = false;
  76. billboard.image = undefined;
  77. labelCollection._spareBillboards.push(billboard);
  78. glyph.billboard = undefined;
  79. }
  80. }
  81. function addGlyphToTextureAtlas(textureAtlas, id, canvas, glyphTextureInfo) {
  82. textureAtlas.addImage(id, canvas).then(function(index, id) {
  83. glyphTextureInfo.index = index;
  84. });
  85. }
  86. function rebindAllGlyphs(labelCollection, label) {
  87. var text = label._text;
  88. var textLength = text.length;
  89. var glyphs = label._glyphs;
  90. var glyphsLength = glyphs.length;
  91. var glyph;
  92. var glyphIndex;
  93. var textIndex;
  94. // if we have more glyphs than needed, unbind the extras.
  95. if (textLength < glyphsLength) {
  96. for (glyphIndex = textLength; glyphIndex < glyphsLength; ++glyphIndex) {
  97. unbindGlyph(labelCollection, glyphs[glyphIndex]);
  98. }
  99. }
  100. // presize glyphs to match the new text length
  101. glyphs.length = textLength;
  102. var glyphTextureCache = labelCollection._glyphTextureCache;
  103. // walk the text looking for new characters (creating new glyphs for each)
  104. // or changed characters (rebinding existing glyphs)
  105. for (textIndex = 0; textIndex < textLength; ++textIndex) {
  106. var character = text.charAt(textIndex);
  107. var font = label._font;
  108. var fillColor = label._fillColor;
  109. var outlineColor = label._outlineColor;
  110. var outlineWidth = label._outlineWidth;
  111. var style = label._style;
  112. var verticalOrigin = label._verticalOrigin;
  113. // retrieve glyph dimensions and texture index (if the canvas has area)
  114. // from the glyph texture cache, or create and add if not present.
  115. var id = JSON.stringify([
  116. character,
  117. font,
  118. fillColor.toRgba(),
  119. outlineColor.toRgba(),
  120. outlineWidth,
  121. +style,
  122. +verticalOrigin
  123. ]);
  124. var glyphTextureInfo = glyphTextureCache[id];
  125. if (!defined(glyphTextureInfo)) {
  126. var canvas = createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin);
  127. glyphTextureInfo = new GlyphTextureInfo(labelCollection, -1, canvas.dimensions);
  128. glyphTextureCache[id] = glyphTextureInfo;
  129. if (canvas.width > 0 && canvas.height > 0) {
  130. addGlyphToTextureAtlas(labelCollection._textureAtlas, id, canvas, glyphTextureInfo);
  131. }
  132. }
  133. glyph = glyphs[textIndex];
  134. if (defined(glyph)) {
  135. // clean up leftover information from the previous glyph
  136. if (glyphTextureInfo.index === -1) {
  137. // no texture, and therefore no billboard, for this glyph.
  138. // so, completely unbind glyph.
  139. unbindGlyph(labelCollection, glyph);
  140. } else {
  141. // we have a texture and billboard. If we had one before, release
  142. // our reference to that texture info, but reuse the billboard.
  143. if (defined(glyph.textureInfo)) {
  144. glyph.textureInfo = undefined;
  145. }
  146. }
  147. } else {
  148. // create a glyph object
  149. glyph = new Glyph();
  150. glyphs[textIndex] = glyph;
  151. }
  152. glyph.textureInfo = glyphTextureInfo;
  153. glyph.dimensions = glyphTextureInfo.dimensions;
  154. // if we have a texture, configure the existing billboard, or obtain one
  155. if (glyphTextureInfo.index !== -1) {
  156. var billboard = glyph.billboard;
  157. if (!defined(billboard)) {
  158. if (labelCollection._spareBillboards.length > 0) {
  159. billboard = labelCollection._spareBillboards.pop();
  160. } else {
  161. billboard = labelCollection._billboardCollection.add({
  162. collection : labelCollection
  163. });
  164. }
  165. glyph.billboard = billboard;
  166. }
  167. billboard.show = label._show;
  168. billboard.position = label._position;
  169. billboard.eyeOffset = label._eyeOffset;
  170. billboard.pixelOffset = label._pixelOffset;
  171. billboard.horizontalOrigin = HorizontalOrigin.LEFT;
  172. billboard.verticalOrigin = label._verticalOrigin;
  173. billboard.scale = label._scale;
  174. billboard.pickPrimitive = label;
  175. billboard.id = label._id;
  176. billboard.image = id;
  177. billboard.translucencyByDistance = label._translucencyByDistance;
  178. billboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance;
  179. }
  180. }
  181. // changing glyphs will cause the position of the
  182. // glyphs to change, since different characters have different widths
  183. label._repositionAllGlyphs = true;
  184. }
  185. // reusable Cartesian2 instance
  186. var glyphPixelOffset = new Cartesian2();
  187. function repositionAllGlyphs(label, resolutionScale) {
  188. var glyphs = label._glyphs;
  189. var glyph;
  190. var dimensions;
  191. var totalWidth = 0;
  192. var maxHeight = 0;
  193. var glyphIndex = 0;
  194. var glyphLength = glyphs.length;
  195. for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
  196. glyph = glyphs[glyphIndex];
  197. dimensions = glyph.dimensions;
  198. totalWidth += dimensions.computedWidth;
  199. maxHeight = Math.max(maxHeight, dimensions.height);
  200. }
  201. var scale = label._scale;
  202. var horizontalOrigin = label._horizontalOrigin;
  203. var widthOffset = 0;
  204. if (horizontalOrigin === HorizontalOrigin.CENTER) {
  205. widthOffset -= totalWidth / 2 * scale;
  206. } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
  207. widthOffset -= totalWidth * scale;
  208. }
  209. glyphPixelOffset.x = widthOffset * resolutionScale;
  210. glyphPixelOffset.y = 0;
  211. var verticalOrigin = label._verticalOrigin;
  212. for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
  213. glyph = glyphs[glyphIndex];
  214. dimensions = glyph.dimensions;
  215. if (verticalOrigin === VerticalOrigin.BOTTOM || dimensions.height === maxHeight) {
  216. glyphPixelOffset.y = -dimensions.descent * scale;
  217. } else if (verticalOrigin === VerticalOrigin.TOP) {
  218. glyphPixelOffset.y = -(maxHeight - dimensions.height) * scale - dimensions.descent * scale;
  219. } else if (verticalOrigin === VerticalOrigin.CENTER) {
  220. glyphPixelOffset.y = -(maxHeight - dimensions.height) / 2 * scale - dimensions.descent * scale;
  221. }
  222. glyphPixelOffset.y *= resolutionScale;
  223. if (defined(glyph.billboard)) {
  224. glyph.billboard._setTranslate(glyphPixelOffset);
  225. }
  226. glyphPixelOffset.x += dimensions.computedWidth * scale * resolutionScale;
  227. }
  228. }
  229. function destroyLabel(labelCollection, label) {
  230. var glyphs = label._glyphs;
  231. for ( var i = 0, len = glyphs.length; i < len; ++i) {
  232. unbindGlyph(labelCollection, glyphs[i]);
  233. }
  234. label._labelCollection = undefined;
  235. destroyObject(label);
  236. }
  237. /**
  238. * A renderable collection of labels. Labels are viewport-aligned text positioned in the 3D scene.
  239. * Each label can have a different font, color, scale, etc.
  240. * <br /><br />
  241. * <div align='center'>
  242. * <img src='images/Label.png' width='400' height='300' /><br />
  243. * Example labels
  244. * </div>
  245. * <br /><br />
  246. * Labels are added and removed from the collection using {@link LabelCollection#add}
  247. * and {@link LabelCollection#remove}.
  248. *
  249. * @alias LabelCollection
  250. * @constructor
  251. *
  252. * @param {Object} [options] Object with the following properties:
  253. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each label from model to world coordinates.
  254. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
  255. *
  256. * @performance For best performance, prefer a few collections, each with many labels, to
  257. * many collections with only a few labels each. Avoid having collections where some
  258. * labels change every frame and others do not; instead, create one or more collections
  259. * for static labels, and one or more collections for dynamic labels.
  260. *
  261. * @see LabelCollection#add
  262. * @see LabelCollection#remove
  263. * @see Label
  264. * @see BillboardCollection
  265. *
  266. * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
  267. *
  268. * @example
  269. * // Create a label collection with two labels
  270. * var labels = new Cesium.LabelCollection();
  271. * labels.add({
  272. * position : { x : 1.0, y : 2.0, z : 3.0 },
  273. * text : 'A label'
  274. * });
  275. * labels.add({
  276. * position : { x : 4.0, y : 5.0, z : 6.0 },
  277. * text : 'Another label'
  278. * });
  279. */
  280. var LabelCollection = function(options) {
  281. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  282. this._textureAtlas = undefined;
  283. this._billboardCollection = new BillboardCollection();
  284. this._billboardCollection.destroyTextureAtlas = false;
  285. this._spareBillboards = [];
  286. this._glyphTextureCache = {};
  287. this._labels = [];
  288. this._labelsToUpdate = [];
  289. this._totalGlyphCount = 0;
  290. this._resolutionScale = undefined;
  291. /**
  292. * The 4x4 transformation matrix that transforms each label in this collection from model to world coordinates.
  293. * When this is the identity matrix, the labels are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
  294. * Local reference frames can be used by providing a different transformation matrix, like that returned
  295. * by {@link Transforms.eastNorthUpToFixedFrame}.
  296. *
  297. * @type Matrix4
  298. * @default {@link Matrix4.IDENTITY}
  299. *
  300. * @example
  301. * var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
  302. * labels.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
  303. * labels.add({
  304. * position : new Cesium.Cartesian3(0.0, 0.0, 0.0),
  305. * text : 'Center'
  306. * });
  307. * labels.add({
  308. * position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0),
  309. * text : 'East'
  310. * });
  311. * labels.add({
  312. * position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0),
  313. * text : 'North'
  314. * });
  315. * labels.add({
  316. * position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0),
  317. * text : 'Up'
  318. * });
  319. */
  320. this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
  321. /**
  322. * This property is for debugging only; it is not for production use nor is it optimized.
  323. * <p>
  324. * Draws the bounding sphere for each draw command in the primitive.
  325. * </p>
  326. *
  327. * @type {Boolean}
  328. *
  329. * @default false
  330. */
  331. this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
  332. };
  333. defineProperties(LabelCollection.prototype, {
  334. /**
  335. * Returns the number of labels in this collection. This is commonly used with
  336. * {@link LabelCollection#get} to iterate over all the labels
  337. * in the collection.
  338. * @memberof LabelCollection.prototype
  339. * @type {Number}
  340. */
  341. length : {
  342. get : function() {
  343. return this._labels.length;
  344. }
  345. }
  346. });
  347. /**
  348. * Creates and adds a label with the specified initial properties to the collection.
  349. * The added label is returned so it can be modified or removed from the collection later.
  350. *
  351. * @param {Object}[options] A template describing the label's properties as shown in Example 1.
  352. * @returns {Label} The label that was added to the collection.
  353. *
  354. * @performance Calling <code>add</code> is expected constant time. However, the collection's vertex buffer
  355. * is rewritten; this operations is <code>O(n)</code> and also incurs
  356. * CPU to GPU overhead. For best performance, add as many billboards as possible before
  357. * calling <code>update</code>.
  358. *
  359. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  360. *
  361. * @see LabelCollection#remove
  362. * @see LabelCollection#removeAll
  363. *
  364. * @example
  365. * // Example 1: Add a label, specifying all the default values.
  366. * var l = labels.add({
  367. * show : true,
  368. * position : Cesium.Cartesian3.ZERO,
  369. * text : '',
  370. * font : '30px sans-serif',
  371. * fillColor : Cesium.Color.WHITE,
  372. * outlineColor : Cesium.Color.BLACK,
  373. * style : Cesium.LabelStyle.FILL,
  374. * pixelOffset : Cesium.Cartesian2.ZERO,
  375. * eyeOffset : Cesium.Cartesian3.ZERO,
  376. * horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
  377. * verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
  378. * scale : 1.0
  379. * });
  380. *
  381. * @example
  382. * // Example 2: Specify only the label's cartographic position,
  383. * // text, and font.
  384. * var l = labels.add({
  385. * position : Cesium.Cartesian3.fromRadians(longitude, latitude, height),
  386. * text : 'Hello World',
  387. * font : '24px Helvetica',
  388. * });
  389. */
  390. LabelCollection.prototype.add = function(options) {
  391. var label = new Label(options, this);
  392. this._labels.push(label);
  393. this._labelsToUpdate.push(label);
  394. return label;
  395. };
  396. /**
  397. * Removes a label from the collection. Once removed, a label is no longer usable.
  398. *
  399. * @param {Label} label The label to remove.
  400. * @returns {Boolean} <code>true</code> if the label was removed; <code>false</code> if the label was not found in the collection.
  401. *
  402. * @performance Calling <code>remove</code> is expected constant time. However, the collection's vertex buffer
  403. * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
  404. * best performance, remove as many labels as possible before calling <code>update</code>.
  405. * If you intend to temporarily hide a label, it is usually more efficient to call
  406. * {@link Label#show} instead of removing and re-adding the label.
  407. *
  408. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  409. *
  410. * @see LabelCollection#add
  411. * @see LabelCollection#removeAll
  412. * @see Label#show
  413. *
  414. * @example
  415. * var l = labels.add(...);
  416. * labels.remove(l); // Returns true
  417. */
  418. LabelCollection.prototype.remove = function(label) {
  419. if (defined(label) && label._labelCollection === this) {
  420. var index = this._labels.indexOf(label);
  421. if (index !== -1) {
  422. this._labels.splice(index, 1);
  423. destroyLabel(this, label);
  424. return true;
  425. }
  426. }
  427. return false;
  428. };
  429. /**
  430. * Removes all labels from the collection.
  431. *
  432. * @performance <code>O(n)</code>. It is more efficient to remove all the labels
  433. * from a collection and then add new ones than to create a new collection entirely.
  434. *
  435. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  436. *
  437. * @see LabelCollection#add
  438. * @see LabelCollection#remove
  439. *
  440. * @example
  441. * labels.add(...);
  442. * labels.add(...);
  443. * labels.removeAll();
  444. */
  445. LabelCollection.prototype.removeAll = function() {
  446. var labels = this._labels;
  447. for ( var i = 0, len = labels.length; i < len; ++i) {
  448. destroyLabel(this, labels[i]);
  449. }
  450. labels.length = 0;
  451. };
  452. /**
  453. * Check whether this collection contains a given label.
  454. *
  455. * @param {Label} label The label to check for.
  456. * @returns {Boolean} true if this collection contains the label, false otherwise.
  457. *
  458. * @see LabelCollection#get
  459. */
  460. LabelCollection.prototype.contains = function(label) {
  461. return defined(label) && label._labelCollection === this;
  462. };
  463. /**
  464. * Returns the label in the collection at the specified index. Indices are zero-based
  465. * and increase as labels are added. Removing a label shifts all labels after
  466. * it to the left, changing their indices. This function is commonly used with
  467. * {@link LabelCollection#length} to iterate over all the labels
  468. * in the collection.
  469. *
  470. * @param {Number} index The zero-based index of the billboard.
  471. *
  472. * @returns {Label} The label at the specified index.
  473. *
  474. * @performance Expected constant time. If labels were removed from the collection and
  475. * {@link Scene#render} was not called, an implicit <code>O(n)</code>
  476. * operation is performed.
  477. *
  478. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  479. *
  480. * @see LabelCollection#length
  481. *
  482. * @example
  483. * // Toggle the show property of every label in the collection
  484. * var len = labels.length;
  485. * for (var i = 0; i < len; ++i) {
  486. * var l = billboards.get(i);
  487. * l.show = !l.show;
  488. * }
  489. */
  490. LabelCollection.prototype.get = function(index) {
  491. //>>includeStart('debug', pragmas.debug);
  492. if (!defined(index)) {
  493. throw new DeveloperError('index is required.');
  494. }
  495. //>>includeEnd('debug');
  496. return this._labels[index];
  497. };
  498. /**
  499. * @private
  500. */
  501. LabelCollection.prototype.update = function(context, frameState, commandList) {
  502. var billboardCollection = this._billboardCollection;
  503. billboardCollection.modelMatrix = this.modelMatrix;
  504. billboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;
  505. if (!defined(this._textureAtlas)) {
  506. this._textureAtlas = new TextureAtlas({
  507. context : context
  508. });
  509. billboardCollection.textureAtlas = this._textureAtlas;
  510. }
  511. var uniformState = context.uniformState;
  512. var resolutionScale = uniformState.resolutionScale;
  513. var resolutionChanged = this._resolutionScale !== resolutionScale;
  514. this._resolutionScale = resolutionScale;
  515. var labelsToUpdate;
  516. if (resolutionChanged) {
  517. labelsToUpdate = this._labels;
  518. } else {
  519. labelsToUpdate = this._labelsToUpdate;
  520. }
  521. for (var i = 0, len = labelsToUpdate.length; i < len; ++i) {
  522. var label = labelsToUpdate[i];
  523. if (label.isDestroyed()) {
  524. continue;
  525. }
  526. var preUpdateGlyphCount = label._glyphs.length;
  527. if (label._rebindAllGlyphs) {
  528. rebindAllGlyphs(this, label);
  529. label._rebindAllGlyphs = false;
  530. }
  531. if (resolutionChanged || label._repositionAllGlyphs) {
  532. repositionAllGlyphs(label, resolutionScale);
  533. label._repositionAllGlyphs = false;
  534. }
  535. var glyphCountDifference = label._glyphs.length - preUpdateGlyphCount;
  536. this._totalGlyphCount += glyphCountDifference;
  537. }
  538. this._labelsToUpdate.length = 0;
  539. billboardCollection.update(context, frameState, commandList);
  540. };
  541. /**
  542. * Returns true if this object was destroyed; otherwise, false.
  543. * <br /><br />
  544. * If this object was destroyed, it should not be used; calling any function other than
  545. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  546. *
  547. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  548. *
  549. * @see LabelCollection#destroy
  550. */
  551. LabelCollection.prototype.isDestroyed = function() {
  552. return false;
  553. };
  554. /**
  555. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  556. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  557. * <br /><br />
  558. * Once an object is destroyed, it should not be used; calling any function other than
  559. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  560. * assign the return value (<code>undefined</code>) to the object as done in the example.
  561. *
  562. * @returns {undefined}
  563. *
  564. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  565. *
  566. * @see LabelCollection#isDestroyed
  567. *
  568. * @example
  569. * labels = labels && labels.destroy();
  570. */
  571. LabelCollection.prototype.destroy = function() {
  572. this.removeAll();
  573. this._billboardCollection = this._billboardCollection.destroy();
  574. this._textureAtlas = this._textureAtlas && this._textureAtlas.destroy();
  575. return destroyObject(this);
  576. };
  577. return LabelCollection;
  578. });