BillboardCollection.js 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377
  1. /*global define*/
  2. define([
  3. '../Core/AttributeCompression',
  4. '../Core/BoundingSphere',
  5. '../Core/Cartesian2',
  6. '../Core/Cartesian3',
  7. '../Core/Color',
  8. '../Core/ComponentDatatype',
  9. '../Core/defaultValue',
  10. '../Core/defined',
  11. '../Core/defineProperties',
  12. '../Core/destroyObject',
  13. '../Core/DeveloperError',
  14. '../Core/EncodedCartesian3',
  15. '../Core/IndexDatatype',
  16. '../Core/Math',
  17. '../Core/Matrix4',
  18. '../Renderer/BufferUsage',
  19. '../Renderer/DrawCommand',
  20. '../Renderer/ShaderSource',
  21. '../Renderer/VertexArrayFacade',
  22. '../Shaders/BillboardCollectionFS',
  23. '../Shaders/BillboardCollectionVS',
  24. './Billboard',
  25. './BlendingState',
  26. './HorizontalOrigin',
  27. './Pass',
  28. './SceneMode',
  29. './TextureAtlas'
  30. ], function(
  31. AttributeCompression,
  32. BoundingSphere,
  33. Cartesian2,
  34. Cartesian3,
  35. Color,
  36. ComponentDatatype,
  37. defaultValue,
  38. defined,
  39. defineProperties,
  40. destroyObject,
  41. DeveloperError,
  42. EncodedCartesian3,
  43. IndexDatatype,
  44. CesiumMath,
  45. Matrix4,
  46. BufferUsage,
  47. DrawCommand,
  48. ShaderSource,
  49. VertexArrayFacade,
  50. BillboardCollectionFS,
  51. BillboardCollectionVS,
  52. Billboard,
  53. BlendingState,
  54. HorizontalOrigin,
  55. Pass,
  56. SceneMode,
  57. TextureAtlas) {
  58. "use strict";
  59. var SHOW_INDEX = Billboard.SHOW_INDEX;
  60. var POSITION_INDEX = Billboard.POSITION_INDEX;
  61. var PIXEL_OFFSET_INDEX = Billboard.PIXEL_OFFSET_INDEX;
  62. var EYE_OFFSET_INDEX = Billboard.EYE_OFFSET_INDEX;
  63. var HORIZONTAL_ORIGIN_INDEX = Billboard.HORIZONTAL_ORIGIN_INDEX;
  64. var VERTICAL_ORIGIN_INDEX = Billboard.VERTICAL_ORIGIN_INDEX;
  65. var SCALE_INDEX = Billboard.SCALE_INDEX;
  66. var IMAGE_INDEX_INDEX = Billboard.IMAGE_INDEX_INDEX;
  67. var COLOR_INDEX = Billboard.COLOR_INDEX;
  68. var ROTATION_INDEX = Billboard.ROTATION_INDEX;
  69. var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX;
  70. var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX;
  71. var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX;
  72. var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX;
  73. var NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;
  74. var attributeLocations = {
  75. positionHighAndScale : 0,
  76. positionLowAndRotation : 1,
  77. compressedAttribute0 : 2, // pixel offset, translate, horizontal origin, vertical origin, show, texture coordinates, direction
  78. compressedAttribute1 : 3, // aligned axis, translucency by distance, image width
  79. compressedAttribute2 : 4, // image height, color, pick color, 2 bytes free
  80. eyeOffset : 5,
  81. scaleByDistance : 6,
  82. pixelOffsetScaleByDistance : 7
  83. };
  84. /**
  85. * A renderable collection of billboards. Billboards are viewport-aligned
  86. * images positioned in the 3D scene.
  87. * <br /><br />
  88. * <div align='center'>
  89. * <img src='images/Billboard.png' width='400' height='300' /><br />
  90. * Example billboards
  91. * </div>
  92. * <br /><br />
  93. * Billboards are added and removed from the collection using {@link BillboardCollection#add}
  94. * and {@link BillboardCollection#remove}. Billboards in a collection automatically share textures
  95. * for images with the same identifier.
  96. *
  97. * @alias BillboardCollection
  98. * @constructor
  99. *
  100. * @param {Object} [options] Object with the following properties:
  101. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each billboard from model to world coordinates.
  102. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
  103. *
  104. * @performance For best performance, prefer a few collections, each with many billboards, to
  105. * many collections with only a few billboards each. Organize collections so that billboards
  106. * with the same update frequency are in the same collection, i.e., billboards that do not
  107. * change should be in one collection; billboards that change every frame should be in another
  108. * collection; and so on.
  109. *
  110. * @see BillboardCollection#add
  111. * @see BillboardCollection#remove
  112. * @see Billboard
  113. * @see LabelCollection
  114. *
  115. * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
  116. *
  117. * @example
  118. * // Create a billboard collection with two billboards
  119. * var billboards = new Cesium.BillboardCollection();
  120. * billboards.add({
  121. * position : { x : 1.0, y : 2.0, z : 3.0 },
  122. * image : 'url/to/image'
  123. * });
  124. * billboards.add({
  125. * position : { x : 4.0, y : 5.0, z : 6.0 },
  126. * image : 'url/to/another/image'
  127. * });
  128. */
  129. var BillboardCollection = function(options) {
  130. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  131. this._textureAtlas = undefined;
  132. this._textureAtlasGUID = undefined;
  133. this._destroyTextureAtlas = true;
  134. this._sp = undefined;
  135. this._rs = undefined;
  136. this._vaf = undefined;
  137. this._spPick = undefined;
  138. this._billboards = [];
  139. this._billboardsToUpdate = [];
  140. this._billboardsToUpdateIndex = 0;
  141. this._billboardsRemoved = false;
  142. this._createVertexArray = false;
  143. this._shaderRotation = false;
  144. this._compiledShaderRotation = false;
  145. this._compiledShaderRotationPick = false;
  146. this._shaderAlignedAxis = false;
  147. this._compiledShaderAlignedAxis = false;
  148. this._compiledShaderAlignedAxisPick = false;
  149. this._shaderScaleByDistance = false;
  150. this._compiledShaderScaleByDistance = false;
  151. this._compiledShaderScaleByDistancePick = false;
  152. this._shaderTranslucencyByDistance = false;
  153. this._compiledShaderTranslucencyByDistance = false;
  154. this._compiledShaderTranslucencyByDistancePick = false;
  155. this._shaderPixelOffsetScaleByDistance = false;
  156. this._compiledShaderPixelOffsetScaleByDistance = false;
  157. this._compiledShaderPixelOffsetScaleByDistancePick = false;
  158. this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
  159. this._maxSize = 0.0;
  160. this._maxEyeOffset = 0.0;
  161. this._maxScale = 1.0;
  162. this._maxPixelOffset = 0.0;
  163. this._allHorizontalCenter = true;
  164. this._baseVolume = new BoundingSphere();
  165. this._baseVolumeWC = new BoundingSphere();
  166. this._baseVolume2D = new BoundingSphere();
  167. this._boundingVolume = new BoundingSphere();
  168. this._boundingVolumeDirty = false;
  169. this._colorCommands = [];
  170. this._pickCommands = [];
  171. /**
  172. * The 4x4 transformation matrix that transforms each billboard in this collection from model to world coordinates.
  173. * When this is the identity matrix, the billboards are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
  174. * Local reference frames can be used by providing a different transformation matrix, like that returned
  175. * by {@link Transforms.eastNorthUpToFixedFrame}.
  176. *
  177. * @type {Matrix4}
  178. * @default {@link Matrix4.IDENTITY}
  179. *
  180. * @see Transforms.eastNorthUpToFixedFrame
  181. *
  182. * @example
  183. * var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
  184. * billboards.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
  185. * billboards.add({
  186. * image : 'url/to/image',
  187. * position : new Cesium.Cartesian3(0.0, 0.0, 0.0) // center
  188. * });
  189. * billboards.add({
  190. * image : 'url/to/image',
  191. * position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0) // east
  192. * });
  193. * billboards.add({
  194. * image : 'url/to/image',
  195. * position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0) // north
  196. * });
  197. * billboards.add({
  198. * image : 'url/to/image',
  199. * position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0) // up
  200. * });
  201. */
  202. this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
  203. this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
  204. /**
  205. * This property is for debugging only; it is not for production use nor is it optimized.
  206. * <p>
  207. * Draws the bounding sphere for each draw command in the primitive.
  208. * </p>
  209. *
  210. * @type {Boolean}
  211. *
  212. * @default false
  213. */
  214. this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
  215. this._mode = SceneMode.SCENE3D;
  216. // The buffer usage for each attribute is determined based on the usage of the attribute over time.
  217. this._buffersUsage = [
  218. BufferUsage.STATIC_DRAW, // SHOW_INDEX
  219. BufferUsage.STATIC_DRAW, // POSITION_INDEX
  220. BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_INDEX
  221. BufferUsage.STATIC_DRAW, // EYE_OFFSET_INDEX
  222. BufferUsage.STATIC_DRAW, // HORIZONTAL_ORIGIN_INDEX
  223. BufferUsage.STATIC_DRAW, // VERTICAL_ORIGIN_INDEX
  224. BufferUsage.STATIC_DRAW, // SCALE_INDEX
  225. BufferUsage.STATIC_DRAW, // IMAGE_INDEX_INDEX
  226. BufferUsage.STATIC_DRAW, // COLOR_INDEX
  227. BufferUsage.STATIC_DRAW, // ROTATION_INDEX
  228. BufferUsage.STATIC_DRAW, // ALIGNED_AXIS_INDEX
  229. BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX
  230. BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX
  231. BufferUsage.STATIC_DRAW // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX
  232. ];
  233. var that = this;
  234. this._uniforms = {
  235. u_atlas : function() {
  236. return that._textureAtlas.texture;
  237. }
  238. };
  239. };
  240. defineProperties(BillboardCollection.prototype, {
  241. /**
  242. * Returns the number of billboards in this collection. This is commonly used with
  243. * {@link BillboardCollection#get} to iterate over all the billboards
  244. * in the collection.
  245. * @memberof BillboardCollection.prototype
  246. * @type {Number}
  247. */
  248. length : {
  249. get : function() {
  250. removeBillboards(this);
  251. return this._billboards.length;
  252. }
  253. },
  254. /**
  255. * Gets or sets the textureAtlas.
  256. * @memberof BillboardCollection.prototype
  257. * @type {TextureAtlas}
  258. * @private
  259. */
  260. textureAtlas : {
  261. get : function() {
  262. return this._textureAtlas;
  263. },
  264. set : function(value) {
  265. if (this._textureAtlas !== value) {
  266. this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy();
  267. this._textureAtlas = value;
  268. this._createVertexArray = true; // New per-billboard texture coordinates
  269. }
  270. }
  271. },
  272. /**
  273. * Gets or sets a value which determines if the texture atlas is
  274. * destroyed when the collection is destroyed.
  275. *
  276. * If the texture atlas is used by more than one collection, set this to <code>false</code>,
  277. * and explicitly destroy the atlas to avoid attempting to destroy it multiple times.
  278. *
  279. * @memberof BillboardCollection.prototype
  280. * @type {Boolean}
  281. * @private
  282. *
  283. * @example
  284. * // Set destroyTextureAtlas
  285. * // Destroy a billboard collection but not its texture atlas.
  286. *
  287. * var atlas = new TextureAtlas({
  288. * scene : scene,
  289. * images : images
  290. * });
  291. * billboards.textureAtlas = atlas;
  292. * billboards.destroyTextureAtlas = false;
  293. * billboards = billboards.destroy();
  294. * console.log(atlas.isDestroyed()); // False
  295. */
  296. destroyTextureAtlas : {
  297. get : function() {
  298. return this._destroyTextureAtlas;
  299. },
  300. set : function(value) {
  301. this._destroyTextureAtlas = value;
  302. }
  303. }
  304. });
  305. /**
  306. * Creates and adds a billboard with the specified initial properties to the collection.
  307. * The added billboard is returned so it can be modified or removed from the collection later.
  308. *
  309. * @param {Object}[billboard] A template describing the billboard's properties as shown in Example 1.
  310. * @returns {Billboard} The billboard that was added to the collection.
  311. *
  312. * @performance Calling <code>add</code> is expected constant time. However, the collection's vertex buffer
  313. * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
  314. * best performance, add as many billboards as possible before calling <code>update</code>.
  315. *
  316. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  317. *
  318. * @see BillboardCollection#remove
  319. * @see BillboardCollection#removeAll
  320. *
  321. * @example
  322. * // Example 1: Add a billboard, specifying all the default values.
  323. * var b = billboards.add({
  324. * show : true,
  325. * position : Cesium.Cartesian3.ZERO,
  326. * pixelOffset : Cesium.Cartesian2.ZERO,
  327. * eyeOffset : Cesium.Cartesian3.ZERO,
  328. * horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
  329. * verticalOrigin : Cesium.VerticalOrigin.CENTER,
  330. * scale : 1.0,
  331. * image : 'url/to/image',
  332. * color : Cesium.Color.WHITE,
  333. * id : undefined
  334. * });
  335. *
  336. * @example
  337. * // Example 2: Specify only the billboard's cartographic position.
  338. * var b = billboards.add({
  339. * position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
  340. * });
  341. */
  342. BillboardCollection.prototype.add = function(billboard) {
  343. var b = new Billboard(billboard, this);
  344. b._index = this._billboards.length;
  345. this._billboards.push(b);
  346. this._createVertexArray = true;
  347. return b;
  348. };
  349. /**
  350. * Removes a billboard from the collection.
  351. *
  352. * @param {Billboard} billboard The billboard to remove.
  353. * @returns {Boolean} <code>true</code> if the billboard was removed; <code>false</code> if the billboard was not found in the collection.
  354. *
  355. * @performance Calling <code>remove</code> is expected constant time. However, the collection's vertex buffer
  356. * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
  357. * best performance, remove as many billboards as possible before calling <code>update</code>.
  358. * If you intend to temporarily hide a billboard, it is usually more efficient to call
  359. * {@link Billboard#show} instead of removing and re-adding the billboard.
  360. *
  361. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  362. *
  363. * @see BillboardCollection#add
  364. * @see BillboardCollection#removeAll
  365. * @see Billboard#show
  366. *
  367. * @example
  368. * var b = billboards.add(...);
  369. * billboards.remove(b); // Returns true
  370. */
  371. BillboardCollection.prototype.remove = function(billboard) {
  372. if (this.contains(billboard)) {
  373. this._billboards[billboard._index] = null; // Removed later
  374. this._billboardsRemoved = true;
  375. this._createVertexArray = true;
  376. billboard._destroy();
  377. return true;
  378. }
  379. return false;
  380. };
  381. /**
  382. * Removes all billboards from the collection.
  383. *
  384. * @performance <code>O(n)</code>. It is more efficient to remove all the billboards
  385. * from a collection and then add new ones than to create a new collection entirely.
  386. *
  387. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  388. *
  389. * @see BillboardCollection#add
  390. * @see BillboardCollection#remove
  391. *
  392. * @example
  393. * billboards.add(...);
  394. * billboards.add(...);
  395. * billboards.removeAll();
  396. */
  397. BillboardCollection.prototype.removeAll = function() {
  398. this._destroyBillboards();
  399. this._billboards = [];
  400. this._billboardsToUpdate = [];
  401. this._billboardsToUpdateIndex = 0;
  402. this._billboardsRemoved = false;
  403. this._createVertexArray = true;
  404. };
  405. function removeBillboards(billboardCollection) {
  406. if (billboardCollection._billboardsRemoved) {
  407. billboardCollection._billboardsRemoved = false;
  408. var newBillboards = [];
  409. var billboards = billboardCollection._billboards;
  410. var length = billboards.length;
  411. for (var i = 0, j = 0; i < length; ++i) {
  412. var billboard = billboards[i];
  413. if (billboard) {
  414. billboard._index = j++;
  415. newBillboards.push(billboard);
  416. }
  417. }
  418. billboardCollection._billboards = newBillboards;
  419. }
  420. }
  421. BillboardCollection.prototype._updateBillboard = function(billboard, propertyChanged) {
  422. if (!billboard._dirty) {
  423. this._billboardsToUpdate[this._billboardsToUpdateIndex++] = billboard;
  424. }
  425. ++this._propertiesChanged[propertyChanged];
  426. };
  427. /**
  428. * Check whether this collection contains a given billboard.
  429. *
  430. * @param {Billboard} [billboard] The billboard to check for.
  431. * @returns {Boolean} true if this collection contains the billboard, false otherwise.
  432. *
  433. * @see BillboardCollection#get
  434. */
  435. BillboardCollection.prototype.contains = function(billboard) {
  436. return defined(billboard) && billboard._billboardCollection === this;
  437. };
  438. /**
  439. * Returns the billboard in the collection at the specified index. Indices are zero-based
  440. * and increase as billboards are added. Removing a billboard shifts all billboards after
  441. * it to the left, changing their indices. This function is commonly used with
  442. * {@link BillboardCollection#length} to iterate over all the billboards
  443. * in the collection.
  444. *
  445. * @param {Number} index The zero-based index of the billboard.
  446. * @returns {Billboard} The billboard at the specified index.
  447. *
  448. * @performance Expected constant time. If billboards were removed from the collection and
  449. * {@link BillboardCollection#update} was not called, an implicit <code>O(n)</code>
  450. * operation is performed.
  451. *
  452. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  453. *
  454. * @see BillboardCollection#length
  455. *
  456. * @example
  457. * // Toggle the show property of every billboard in the collection
  458. * var len = billboards.length;
  459. * for (var i = 0; i < len; ++i) {
  460. * var b = billboards.get(i);
  461. * b.show = !b.show;
  462. * }
  463. */
  464. BillboardCollection.prototype.get = function(index) {
  465. //>>includeStart('debug', pragmas.debug);
  466. if (!defined(index)) {
  467. throw new DeveloperError('index is required.');
  468. }
  469. //>>includeEnd('debug');
  470. removeBillboards(this);
  471. return this._billboards[index];
  472. };
  473. function getIndexBuffer(context) {
  474. var sixteenK = 16 * 1024;
  475. var indexBuffer = context.cache.billboardCollection_indexBuffer;
  476. if (defined(indexBuffer)) {
  477. return indexBuffer;
  478. }
  479. var length = sixteenK * 6;
  480. var indices = new Uint16Array(length);
  481. for (var i = 0, j = 0; i < length; i += 6, j += 4) {
  482. indices[i] = j;
  483. indices[i + 1] = j + 1;
  484. indices[i + 2] = j + 2;
  485. indices[i + 3] = j + 0;
  486. indices[i + 4] = j + 2;
  487. indices[i + 5] = j + 3;
  488. }
  489. // PERFORMANCE_IDEA: Should we reference count billboard collections, and eventually delete this?
  490. // Is this too much memory to allocate up front? Should we dynamically grow it?
  491. indexBuffer = context.createIndexBuffer(indices, BufferUsage.STATIC_DRAW, IndexDatatype.UNSIGNED_SHORT);
  492. indexBuffer.vertexArrayDestroyable = false;
  493. context.cache.billboardCollection_indexBuffer = indexBuffer;
  494. return indexBuffer;
  495. }
  496. BillboardCollection.prototype.computeNewBuffersUsage = function() {
  497. var buffersUsage = this._buffersUsage;
  498. var usageChanged = false;
  499. var properties = this._propertiesChanged;
  500. for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
  501. var newUsage = (properties[k] === 0) ? BufferUsage.STATIC_DRAW : BufferUsage.STREAM_DRAW;
  502. usageChanged = usageChanged || (buffersUsage[k] !== newUsage);
  503. buffersUsage[k] = newUsage;
  504. }
  505. return usageChanged;
  506. };
  507. function createVAF(context, numberOfBillboards, buffersUsage) {
  508. return new VertexArrayFacade(context, [{
  509. index : attributeLocations.positionHighAndScale,
  510. componentsPerAttribute : 4,
  511. componentDatatype : ComponentDatatype.FLOAT,
  512. usage : buffersUsage[POSITION_INDEX]
  513. }, {
  514. index : attributeLocations.positionLowAndRotation,
  515. componentsPerAttribute : 4,
  516. componentDatatype : ComponentDatatype.FLOAT,
  517. usage : buffersUsage[POSITION_INDEX]
  518. }, {
  519. index : attributeLocations.compressedAttribute0,
  520. componentsPerAttribute : 4,
  521. componentDatatype : ComponentDatatype.FLOAT,
  522. usage : buffersUsage[PIXEL_OFFSET_INDEX]
  523. }, {
  524. index : attributeLocations.compressedAttribute1,
  525. componentsPerAttribute : 4,
  526. componentDatatype : ComponentDatatype.FLOAT,
  527. usage : buffersUsage[TRANSLUCENCY_BY_DISTANCE_INDEX]
  528. }, {
  529. index : attributeLocations.compressedAttribute2,
  530. componentsPerAttribute : 4,
  531. componentDatatype : ComponentDatatype.FLOAT,
  532. usage : buffersUsage[COLOR_INDEX]
  533. }, {
  534. index : attributeLocations.eyeOffset,
  535. componentsPerAttribute : 3,
  536. componentDatatype : ComponentDatatype.FLOAT,
  537. usage : buffersUsage[EYE_OFFSET_INDEX]
  538. }, {
  539. index : attributeLocations.scaleByDistance,
  540. componentsPerAttribute : 4,
  541. componentDatatype : ComponentDatatype.FLOAT,
  542. usage : buffersUsage[SCALE_BY_DISTANCE_INDEX]
  543. }, {
  544. index : attributeLocations.pixelOffsetScaleByDistance,
  545. componentsPerAttribute : 4,
  546. componentDatatype : ComponentDatatype.FLOAT,
  547. usage : buffersUsage[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]
  548. }], 4 * numberOfBillboards); // 4 vertices per billboard
  549. }
  550. ///////////////////////////////////////////////////////////////////////////
  551. // Four vertices per billboard. Each has the same position, etc., but a different screen-space direction vector.
  552. // PERFORMANCE_IDEA: Save memory if a property is the same for all billboards, use a latched attribute state,
  553. // instead of storing it in a vertex buffer.
  554. var writePositionScratch = new EncodedCartesian3();
  555. function writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  556. var i = billboard._index * 4;
  557. var position = billboard._getActualPosition();
  558. if (billboardCollection._mode === SceneMode.SCENE3D) {
  559. BoundingSphere.expand(billboardCollection._baseVolume, position, billboardCollection._baseVolume);
  560. billboardCollection._boundingVolumeDirty = true;
  561. }
  562. EncodedCartesian3.fromCartesian(position, writePositionScratch);
  563. var scale = billboard.scale;
  564. var rotation = billboard.rotation;
  565. if (rotation !== 0.0) {
  566. billboardCollection._shaderRotation = true;
  567. }
  568. billboardCollection._maxScale = Math.max(billboardCollection._maxScale, scale);
  569. var positionHighWriter = vafWriters[attributeLocations.positionHighAndScale];
  570. var high = writePositionScratch.high;
  571. positionHighWriter(i + 0, high.x, high.y, high.z, scale);
  572. positionHighWriter(i + 1, high.x, high.y, high.z, scale);
  573. positionHighWriter(i + 2, high.x, high.y, high.z, scale);
  574. positionHighWriter(i + 3, high.x, high.y, high.z, scale);
  575. var positionLowWriter = vafWriters[attributeLocations.positionLowAndRotation];
  576. var low = writePositionScratch.low;
  577. positionLowWriter(i + 0, low.x, low.y, low.z, rotation);
  578. positionLowWriter(i + 1, low.x, low.y, low.z, rotation);
  579. positionLowWriter(i + 2, low.x, low.y, low.z, rotation);
  580. positionLowWriter(i + 3, low.x, low.y, low.z, rotation);
  581. }
  582. var scratchCartesian2 = new Cartesian2();
  583. var UPPER_BOUND = 32768.0; // 2^15
  584. var LEFT_SHIFT16 = 65536.0; // 2^16
  585. var LEFT_SHIFT8 = 256.0; // 2^8
  586. var LEFT_SHIFT7 = 128.0;
  587. var LEFT_SHIFT5 = 32.0;
  588. var LEFT_SHIFT3 = 8.0;
  589. var LEFT_SHIFT2 = 4.0;
  590. var RIGHT_SHIFT8 = 1.0 / 256.0;
  591. var LOWER_LEFT = 0.0;
  592. var LOWER_RIGHT = 2.0;
  593. var UPPER_RIGHT = 3.0;
  594. var UPPER_LEFT = 1.0;
  595. function writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  596. var i = billboard._index * 4;
  597. var pixelOffset = billboard.pixelOffset;
  598. var pixelOffsetX = pixelOffset.x;
  599. var pixelOffsetY = pixelOffset.y;
  600. var translate = billboard._translate;
  601. var translateX = translate.x;
  602. var translateY = translate.y;
  603. billboardCollection._maxPixelOffset = Math.max(billboardCollection._maxPixelOffset, Math.abs(pixelOffsetX + translateX), Math.abs(-pixelOffsetY + translateY));
  604. var horizontalOrigin = billboard.horizontalOrigin;
  605. var verticalOrigin = billboard.verticalOrigin;
  606. var show = billboard.show;
  607. // If the color alpha is zero, do not show this billboard. This lets us avoid providing
  608. // color during the pick pass and also eliminates a discard in the fragment shader.
  609. if (billboard.color.alpha === 0.0) {
  610. show = false;
  611. }
  612. billboardCollection._allHorizontalCenter = billboardCollection._allHorizontalCenter && horizontalOrigin === HorizontalOrigin.CENTER;
  613. var bottomLeftX = 0;
  614. var bottomLeftY = 0;
  615. var width = 0;
  616. var height = 0;
  617. var index = billboard._imageIndex;
  618. if (index !== -1) {
  619. var imageRectangle = textureAtlasCoordinates[index];
  620. //>>includeStart('debug', pragmas.debug);
  621. if (!defined(imageRectangle)) {
  622. throw new DeveloperError('Invalid billboard image index: ' + index);
  623. }
  624. //>>includeEnd('debug');
  625. bottomLeftX = imageRectangle.x;
  626. bottomLeftY = imageRectangle.y;
  627. width = imageRectangle.width;
  628. height = imageRectangle.height;
  629. }
  630. var topRightX = bottomLeftX + width;
  631. var topRightY = bottomLeftY + height;
  632. var compressed0 = Math.floor(CesiumMath.clamp(pixelOffsetX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT7;
  633. compressed0 += (horizontalOrigin + 1.0) * LEFT_SHIFT5;
  634. compressed0 += (verticalOrigin + 1.0) * LEFT_SHIFT3;
  635. compressed0 += (show ? 1.0 : 0.0) * LEFT_SHIFT2;
  636. var compressed1 = Math.floor(CesiumMath.clamp(pixelOffsetY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;
  637. var compressed2 = Math.floor(CesiumMath.clamp(translateX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;
  638. var tempTanslateY = (CesiumMath.clamp(translateY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * RIGHT_SHIFT8;
  639. var upperTranslateY = Math.floor(tempTanslateY);
  640. var lowerTranslateY = Math.floor((tempTanslateY - upperTranslateY) * LEFT_SHIFT8);
  641. compressed1 += upperTranslateY;
  642. compressed2 += lowerTranslateY;
  643. scratchCartesian2.x = bottomLeftX;
  644. scratchCartesian2.y = bottomLeftY;
  645. var compressedTexCoordsLL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
  646. scratchCartesian2.x = topRightX;
  647. var compressedTexCoordsLR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
  648. scratchCartesian2.y = topRightY;
  649. var compressedTexCoordsUR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
  650. scratchCartesian2.x = bottomLeftX;
  651. var compressedTexCoordsUL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
  652. var writer = vafWriters[attributeLocations.compressedAttribute0];
  653. writer(i + 0, compressed0 + LOWER_LEFT, compressed1, compressed2, compressedTexCoordsLL);
  654. writer(i + 1, compressed0 + LOWER_RIGHT, compressed1, compressed2, compressedTexCoordsLR);
  655. writer(i + 2, compressed0 + UPPER_RIGHT, compressed1, compressed2, compressedTexCoordsUR);
  656. writer(i + 3, compressed0 + UPPER_LEFT, compressed1, compressed2, compressedTexCoordsUL);
  657. }
  658. function writeCompressedAttrib1(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  659. var i = billboard._index * 4;
  660. var alignedAxis = billboard.alignedAxis;
  661. if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) {
  662. billboardCollection._shaderAlignedAxis = true;
  663. }
  664. var near = 0.0;
  665. var nearValue = 1.0;
  666. var far = 1.0;
  667. var farValue = 1.0;
  668. var translucency = billboard.translucencyByDistance;
  669. if (defined(translucency)) {
  670. near = translucency.near;
  671. nearValue = translucency.nearValue;
  672. far = translucency.far;
  673. farValue = translucency.farValue;
  674. if (nearValue !== 1.0 || farValue !== 1.0) {
  675. // translucency by distance calculation in shader need not be enabled
  676. // until a billboard with near and far !== 1.0 is found
  677. billboardCollection._shaderTranslucencyByDistance = true;
  678. }
  679. }
  680. var width = 0;
  681. var index = billboard._imageIndex;
  682. if (index !== -1) {
  683. var imageRectangle = textureAtlasCoordinates[index];
  684. //>>includeStart('debug', pragmas.debug);
  685. if (!defined(imageRectangle)) {
  686. throw new DeveloperError('Invalid billboard image index: ' + index);
  687. }
  688. //>>includeEnd('debug');
  689. width = imageRectangle.width;
  690. }
  691. var textureWidth = billboardCollection._textureAtlas.texture.width;
  692. var imageWidth = Math.ceil(defaultValue(billboard.width, textureWidth * width) * 0.5);
  693. billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageWidth);
  694. var compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16);
  695. var compressed1 = 0.0;
  696. if (Math.abs(Cartesian3.magnitudeSquared(alignedAxis) - 1.0) < CesiumMath.EPSILON6) {
  697. compressed1 = AttributeCompression.octEncodeFloat(alignedAxis);
  698. }
  699. nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0);
  700. nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0;
  701. compressed0 = compressed0 * LEFT_SHIFT8 + nearValue;
  702. farValue = CesiumMath.clamp(farValue, 0.0, 1.0);
  703. farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
  704. compressed1 = compressed1 * LEFT_SHIFT8 + farValue;
  705. var writer = vafWriters[attributeLocations.compressedAttribute1];
  706. writer(i + 0, compressed0, compressed1, near, far);
  707. writer(i + 1, compressed0, compressed1, near, far);
  708. writer(i + 2, compressed0, compressed1, near, far);
  709. writer(i + 3, compressed0, compressed1, near, far);
  710. }
  711. function writeCompressedAttrib2(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  712. var i = billboard._index * 4;
  713. var color = billboard.color;
  714. var pickColor = billboard.getPickId(context).color;
  715. var height = 0;
  716. var index = billboard._imageIndex;
  717. if (index !== -1) {
  718. var imageRectangle = textureAtlasCoordinates[index];
  719. //>>includeStart('debug', pragmas.debug);
  720. if (!defined(imageRectangle)) {
  721. throw new DeveloperError('Invalid billboard image index: ' + index);
  722. }
  723. //>>includeEnd('debug');
  724. height = imageRectangle.height;
  725. }
  726. var dimensions = billboardCollection._textureAtlas.texture.dimensions;
  727. var imageHeight = Math.ceil(defaultValue(billboard.height, dimensions.y * height) * 0.5);
  728. billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageHeight);
  729. var red = Color.floatToByte(color.red);
  730. var green = Color.floatToByte(color.green);
  731. var blue = Color.floatToByte(color.blue);
  732. var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
  733. red = Color.floatToByte(pickColor.red);
  734. green = Color.floatToByte(pickColor.green);
  735. blue = Color.floatToByte(pickColor.blue);
  736. var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
  737. var compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT8 + Color.floatToByte(pickColor.alpha);
  738. var writer = vafWriters[attributeLocations.compressedAttribute2];
  739. writer(i + 0, compressed0, compressed1, compressed2, imageHeight);
  740. writer(i + 1, compressed0, compressed1, compressed2, imageHeight);
  741. writer(i + 2, compressed0, compressed1, compressed2, imageHeight);
  742. writer(i + 3, compressed0, compressed1, compressed2, imageHeight);
  743. }
  744. function writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  745. var i = billboard._index * 4;
  746. var eyeOffset = billboard.eyeOffset;
  747. billboardCollection._maxEyeOffset = Math.max(billboardCollection._maxEyeOffset, Math.abs(eyeOffset.x), Math.abs(eyeOffset.y), Math.abs(eyeOffset.z));
  748. var writer = vafWriters[attributeLocations.eyeOffset];
  749. writer(i + 0, eyeOffset.x, eyeOffset.y, eyeOffset.z);
  750. writer(i + 1, eyeOffset.x, eyeOffset.y, eyeOffset.z);
  751. writer(i + 2, eyeOffset.x, eyeOffset.y, eyeOffset.z);
  752. writer(i + 3, eyeOffset.x, eyeOffset.y, eyeOffset.z);
  753. }
  754. function writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  755. var i = billboard._index * 4;
  756. var writer = vafWriters[attributeLocations.scaleByDistance];
  757. var near = 0.0;
  758. var nearValue = 1.0;
  759. var far = 1.0;
  760. var farValue = 1.0;
  761. var scale = billboard.scaleByDistance;
  762. if (defined(scale)) {
  763. near = scale.near;
  764. nearValue = scale.nearValue;
  765. far = scale.far;
  766. farValue = scale.farValue;
  767. if (nearValue !== 1.0 || farValue !== 1.0) {
  768. // scale by distance calculation in shader need not be enabled
  769. // until a billboard with near and far !== 1.0 is found
  770. billboardCollection._shaderScaleByDistance = true;
  771. }
  772. }
  773. writer(i + 0, near, nearValue, far, farValue);
  774. writer(i + 1, near, nearValue, far, farValue);
  775. writer(i + 2, near, nearValue, far, farValue);
  776. writer(i + 3, near, nearValue, far, farValue);
  777. }
  778. function writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  779. var i = billboard._index * 4;
  780. var writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance];
  781. var near = 0.0;
  782. var nearValue = 1.0;
  783. var far = 1.0;
  784. var farValue = 1.0;
  785. var pixelOffsetScale = billboard.pixelOffsetScaleByDistance;
  786. if (defined(pixelOffsetScale)) {
  787. near = pixelOffsetScale.near;
  788. nearValue = pixelOffsetScale.nearValue;
  789. far = pixelOffsetScale.far;
  790. farValue = pixelOffsetScale.farValue;
  791. if (nearValue !== 1.0 || farValue !== 1.0) {
  792. // pixelOffsetScale by distance calculation in shader need not be enabled
  793. // until a billboard with near and far !== 1.0 is found
  794. billboardCollection._shaderPixelOffsetScaleByDistance = true;
  795. }
  796. }
  797. writer(i + 0, near, nearValue, far, farValue);
  798. writer(i + 1, near, nearValue, far, farValue);
  799. writer(i + 2, near, nearValue, far, farValue);
  800. writer(i + 3, near, nearValue, far, farValue);
  801. }
  802. function writeBillboard(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
  803. writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
  804. writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
  805. writeCompressedAttrib1(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
  806. writeCompressedAttrib2(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
  807. writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
  808. writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
  809. writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
  810. }
  811. function recomputeActualPositions(billboardCollection, billboards, length, frameState, modelMatrix, recomputeBoundingVolume) {
  812. var boundingVolume;
  813. if (frameState.mode === SceneMode.SCENE3D) {
  814. boundingVolume = billboardCollection._baseVolume;
  815. billboardCollection._boundingVolumeDirty = true;
  816. } else {
  817. boundingVolume = billboardCollection._baseVolume2D;
  818. }
  819. var positions = [];
  820. for ( var i = 0; i < length; ++i) {
  821. var billboard = billboards[i];
  822. var position = billboard.position;
  823. var actualPosition = Billboard._computeActualPosition(position, frameState, modelMatrix);
  824. if (defined(actualPosition)) {
  825. billboard._setActualPosition(actualPosition);
  826. if (recomputeBoundingVolume) {
  827. positions.push(actualPosition);
  828. } else {
  829. BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume);
  830. }
  831. }
  832. }
  833. if (recomputeBoundingVolume) {
  834. BoundingSphere.fromPoints(positions, boundingVolume);
  835. }
  836. }
  837. function updateMode(billboardCollection, frameState) {
  838. var mode = frameState.mode;
  839. var billboards = billboardCollection._billboards;
  840. var billboardsToUpdate = billboardCollection._billboardsToUpdate;
  841. var modelMatrix = billboardCollection._modelMatrix;
  842. if (billboardCollection._createVertexArray ||
  843. billboardCollection._mode !== mode ||
  844. mode !== SceneMode.SCENE3D &&
  845. !Matrix4.equals(modelMatrix, billboardCollection.modelMatrix)) {
  846. billboardCollection._mode = mode;
  847. Matrix4.clone(billboardCollection.modelMatrix, modelMatrix);
  848. billboardCollection._createVertexArray = true;
  849. if (mode === SceneMode.SCENE3D || mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
  850. recomputeActualPositions(billboardCollection, billboards, billboards.length, frameState, modelMatrix, true);
  851. }
  852. } else if (mode === SceneMode.MORPHING) {
  853. recomputeActualPositions(billboardCollection, billboards, billboards.length, frameState, modelMatrix, true);
  854. } else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
  855. recomputeActualPositions(billboardCollection, billboardsToUpdate, billboardCollection._billboardsToUpdateIndex, frameState, modelMatrix, false);
  856. }
  857. }
  858. var scratchDrawingBufferDimensions = new Cartesian2();
  859. var scratchToCenter = new Cartesian3();
  860. var scratchProj = new Cartesian3();
  861. function updateBoundingVolume(collection, context, frameState, boundingVolume) {
  862. var camera = frameState.camera;
  863. var frustum = camera.frustum;
  864. var toCenter = Cartesian3.subtract(camera.positionWC, boundingVolume.center, scratchToCenter);
  865. var proj = Cartesian3.multiplyByScalar(camera.directionWC, Cartesian3.dot(toCenter, camera.directionWC), scratchProj);
  866. var distance = Math.max(0.0, Cartesian3.magnitude(proj) - boundingVolume.radius);
  867. scratchDrawingBufferDimensions.x = context.drawingBufferWidth;
  868. scratchDrawingBufferDimensions.y = context.drawingBufferHeight;
  869. var pixelSize = frustum.getPixelSize(scratchDrawingBufferDimensions, distance);
  870. var pixelScale = Math.max(pixelSize.x, pixelSize.y);
  871. var size = pixelScale * collection._maxScale * collection._maxSize * 2.0;
  872. if (collection._allHorizontalCenter) {
  873. size *= 0.5;
  874. }
  875. var offset = pixelScale * collection._maxPixelOffset + collection._maxEyeOffset;
  876. boundingVolume.radius += size + offset;
  877. }
  878. /**
  879. * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
  880. * get the draw commands needed to render this primitive.
  881. * <p>
  882. * Do not call this function directly. This is documented just to
  883. * list the exceptions that may be propagated when the scene is rendered:
  884. * </p>
  885. *
  886. * @exception {RuntimeError} image with id must be in the atlas.
  887. */
  888. BillboardCollection.prototype.update = function(context, frameState, commandList) {
  889. var billboards = this._billboards;
  890. var billboardsLength = billboards.length;
  891. var textureAtlas = this._textureAtlas;
  892. if (!defined(textureAtlas)) {
  893. textureAtlas = this._textureAtlas = new TextureAtlas({
  894. context : context
  895. });
  896. for (var ii = 0; ii < billboardsLength; ++ii) {
  897. billboards[ii]._loadImage();
  898. }
  899. }
  900. var textureAtlasCoordinates = textureAtlas.textureCoordinates;
  901. if (textureAtlasCoordinates.length === 0) {
  902. // Can't write billboard vertices until we have texture coordinates
  903. // provided by a texture atlas
  904. return;
  905. }
  906. removeBillboards(this);
  907. updateMode(this, frameState);
  908. billboards = this._billboards;
  909. billboardsLength = billboards.length;
  910. var billboardsToUpdate = this._billboardsToUpdate;
  911. var billboardsToUpdateLength = this._billboardsToUpdateIndex;
  912. var properties = this._propertiesChanged;
  913. var textureAtlasGUID = textureAtlas.guid;
  914. var createVertexArray = this._createVertexArray || this._textureAtlasGUID !== textureAtlasGUID;
  915. this._textureAtlasGUID = textureAtlasGUID;
  916. var vafWriters;
  917. var pass = frameState.passes;
  918. var picking = pass.pick;
  919. // PERFORMANCE_IDEA: Round robin multiple buffers.
  920. if (createVertexArray || (!picking && this.computeNewBuffersUsage())) {
  921. this._createVertexArray = false;
  922. for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
  923. properties[k] = 0;
  924. }
  925. this._vaf = this._vaf && this._vaf.destroy();
  926. if (billboardsLength > 0) {
  927. // PERFORMANCE_IDEA: Instead of creating a new one, resize like std::vector.
  928. this._vaf = createVAF(context, billboardsLength, this._buffersUsage);
  929. vafWriters = this._vaf.writers;
  930. // Rewrite entire buffer if billboards were added or removed.
  931. for (var i = 0; i < billboardsLength; ++i) {
  932. var billboard = this._billboards[i];
  933. billboard._dirty = false; // In case it needed an update.
  934. writeBillboard(this, context, textureAtlasCoordinates, vafWriters, billboard);
  935. }
  936. // Different billboard collections share the same index buffer.
  937. this._vaf.commit(getIndexBuffer(context));
  938. }
  939. this._billboardsToUpdateIndex = 0;
  940. } else {
  941. // Billboards were modified, but none were added or removed.
  942. if (billboardsToUpdateLength > 0) {
  943. var writers = [];
  944. if (properties[POSITION_INDEX] || properties[ROTATION_INDEX] || properties[SCALE_INDEX]) {
  945. writers.push(writePositionScaleAndRotation);
  946. }
  947. if (properties[IMAGE_INDEX_INDEX] || properties[PIXEL_OFFSET_INDEX] || properties[HORIZONTAL_ORIGIN_INDEX] || properties[VERTICAL_ORIGIN_INDEX] || properties[SHOW_INDEX]) {
  948. writers.push(writeCompressedAttrib0);
  949. }
  950. if (properties[IMAGE_INDEX_INDEX] || properties[ALIGNED_AXIS_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) {
  951. writers.push(writeCompressedAttrib1);
  952. }
  953. if (properties[IMAGE_INDEX_INDEX] || properties[COLOR_INDEX]) {
  954. writers.push(writeCompressedAttrib2);
  955. }
  956. if (properties[EYE_OFFSET_INDEX]) {
  957. writers.push(writeEyeOffset);
  958. }
  959. if (properties[SCALE_BY_DISTANCE_INDEX]) {
  960. writers.push(writeScaleByDistance);
  961. }
  962. if (properties[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]) {
  963. writers.push(writePixelOffsetScaleByDistance);
  964. }
  965. vafWriters = this._vaf.writers;
  966. if ((billboardsToUpdateLength / billboardsLength) > 0.1) {
  967. // If more than 10% of billboard change, rewrite the entire buffer.
  968. // PERFORMANCE_IDEA: I totally made up 10% :).
  969. for (var m = 0; m < billboardsToUpdateLength; ++m) {
  970. var b = billboardsToUpdate[m];
  971. b._dirty = false;
  972. for ( var n = 0; n < writers.length; ++n) {
  973. writers[n](this, context, textureAtlasCoordinates, vafWriters, b);
  974. }
  975. }
  976. this._vaf.commit(getIndexBuffer(context));
  977. } else {
  978. for (var h = 0; h < billboardsToUpdateLength; ++h) {
  979. var bb = billboardsToUpdate[h];
  980. bb._dirty = false;
  981. for ( var o = 0; o < writers.length; ++o) {
  982. writers[o](this, context, textureAtlasCoordinates, vafWriters, bb);
  983. }
  984. this._vaf.subCommit(bb._index * 4, 4);
  985. }
  986. this._vaf.endSubCommits();
  987. }
  988. this._billboardsToUpdateIndex = 0;
  989. }
  990. }
  991. // If the number of total billboards ever shrinks considerably
  992. // Truncate billboardsToUpdate so that we free memory that we're
  993. // not going to be using.
  994. if (billboardsToUpdateLength > billboardsLength * 1.5) {
  995. billboardsToUpdate.length = billboardsLength;
  996. }
  997. if (!defined(this._vaf) || !defined(this._vaf.va)) {
  998. return;
  999. }
  1000. if (this._boundingVolumeDirty) {
  1001. this._boundingVolumeDirty = false;
  1002. BoundingSphere.transform(this._baseVolume, this.modelMatrix, this._baseVolumeWC);
  1003. }
  1004. var boundingVolume;
  1005. var modelMatrix = Matrix4.IDENTITY;
  1006. if (frameState.mode === SceneMode.SCENE3D) {
  1007. modelMatrix = this.modelMatrix;
  1008. boundingVolume = BoundingSphere.clone(this._baseVolumeWC, this._boundingVolume);
  1009. } else {
  1010. boundingVolume = BoundingSphere.clone(this._baseVolume2D, this._boundingVolume);
  1011. }
  1012. updateBoundingVolume(this, context, frameState, boundingVolume);
  1013. var va;
  1014. var vaLength;
  1015. var command;
  1016. var j;
  1017. var vs;
  1018. var fs;
  1019. if (pass.render) {
  1020. var colorList = this._colorCommands;
  1021. if (!defined(this._rs)) {
  1022. this._rs = context.createRenderState({
  1023. depthTest : {
  1024. enabled : true
  1025. },
  1026. blending : BlendingState.ALPHA_BLEND
  1027. });
  1028. }
  1029. if (!defined(this._sp) ||
  1030. (this._shaderRotation && !this._compiledShaderRotation) ||
  1031. (this._shaderAlignedAxis && !this._compiledShaderAlignedAxis) ||
  1032. (this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) ||
  1033. (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) ||
  1034. (this._shaderPixelOffsetScaleByDistance && !this._compiledShaderPixelOffsetScaleByDistance)) {
  1035. vs = new ShaderSource({
  1036. sources : [BillboardCollectionVS]
  1037. });
  1038. if (this._shaderRotation) {
  1039. vs.defines.push('ROTATION');
  1040. }
  1041. if (this._shaderAlignedAxis) {
  1042. vs.defines.push('ALIGNED_AXIS');
  1043. }
  1044. if (this._shaderScaleByDistance) {
  1045. vs.defines.push('EYE_DISTANCE_SCALING');
  1046. }
  1047. if (this._shaderTranslucencyByDistance) {
  1048. vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
  1049. }
  1050. if (this._shaderPixelOffsetScaleByDistance) {
  1051. vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET');
  1052. }
  1053. this._sp = context.replaceShaderProgram(this._sp, vs, BillboardCollectionFS, attributeLocations);
  1054. this._compiledShaderRotation = this._shaderRotation;
  1055. this._compiledShaderAlignedAxis = this._shaderAlignedAxis;
  1056. this._compiledShaderScaleByDistance = this._shaderScaleByDistance;
  1057. this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance;
  1058. this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance;
  1059. }
  1060. va = this._vaf.va;
  1061. vaLength = va.length;
  1062. colorList.length = vaLength;
  1063. for (j = 0; j < vaLength; ++j) {
  1064. command = colorList[j];
  1065. if (!defined(command)) {
  1066. command = colorList[j] = new DrawCommand({
  1067. pass : Pass.OPAQUE,
  1068. owner : this
  1069. });
  1070. }
  1071. command.boundingVolume = boundingVolume;
  1072. command.modelMatrix = modelMatrix;
  1073. command.count = va[j].indicesCount;
  1074. command.shaderProgram = this._sp;
  1075. command.uniformMap = this._uniforms;
  1076. command.vertexArray = va[j].va;
  1077. command.renderState = this._rs;
  1078. command.debugShowBoundingVolume = this.debugShowBoundingVolume;
  1079. commandList.push(command);
  1080. }
  1081. }
  1082. if (picking) {
  1083. var pickList = this._pickCommands;
  1084. if (!defined(this._spPick) ||
  1085. (this._shaderRotation && !this._compiledShaderRotationPick) ||
  1086. (this._shaderAlignedAxis && !this._compiledShaderAlignedAxisPick) ||
  1087. (this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) ||
  1088. (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) ||
  1089. (this._shaderPixelOffsetScaleByDistance && !this._compiledShaderPixelOffsetScaleByDistancePick)) {
  1090. vs = new ShaderSource({
  1091. defines : ['RENDER_FOR_PICK'],
  1092. sources : [BillboardCollectionVS]
  1093. });
  1094. if (this._shaderRotation) {
  1095. vs.defines.push('ROTATION');
  1096. }
  1097. if (this._shaderAlignedAxis) {
  1098. vs.defines.push('ALIGNED_AXIS');
  1099. }
  1100. if (this._shaderScaleByDistance) {
  1101. vs.defines.push('EYE_DISTANCE_SCALING');
  1102. }
  1103. if (this._shaderTranslucencyByDistance) {
  1104. vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
  1105. }
  1106. if (this._shaderPixelOffsetScaleByDistance) {
  1107. vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET');
  1108. }
  1109. fs = new ShaderSource({
  1110. defines : ['RENDER_FOR_PICK'],
  1111. sources : [BillboardCollectionFS]
  1112. });
  1113. this._spPick = context.replaceShaderProgram(this._spPick, vs, fs, attributeLocations);
  1114. this._compiledShaderRotationPick = this._shaderRotation;
  1115. this._compiledShaderAlignedAxisPick = this._shaderAlignedAxis;
  1116. this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance;
  1117. this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance;
  1118. this._compiledShaderPixelOffsetScaleByDistancePick = this._shaderPixelOffsetScaleByDistance;
  1119. }
  1120. va = this._vaf.va;
  1121. vaLength = va.length;
  1122. pickList.length = vaLength;
  1123. for (j = 0; j < vaLength; ++j) {
  1124. command = pickList[j];
  1125. if (!defined(command)) {
  1126. command = pickList[j] = new DrawCommand({
  1127. pass : Pass.OPAQUE,
  1128. owner : this
  1129. });
  1130. }
  1131. command.boundingVolume = boundingVolume;
  1132. command.modelMatrix = modelMatrix;
  1133. command.count = va[j].indicesCount;
  1134. command.shaderProgram = this._spPick;
  1135. command.uniformMap = this._uniforms;
  1136. command.vertexArray = va[j].va;
  1137. command.renderState = this._rs;
  1138. commandList.push(command);
  1139. }
  1140. }
  1141. };
  1142. /**
  1143. * Returns true if this object was destroyed; otherwise, false.
  1144. * <br /><br />
  1145. * If this object was destroyed, it should not be used; calling any function other than
  1146. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  1147. *
  1148. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  1149. *
  1150. * @see BillboardCollection#destroy
  1151. */
  1152. BillboardCollection.prototype.isDestroyed = function() {
  1153. return false;
  1154. };
  1155. /**
  1156. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  1157. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  1158. * <br /><br />
  1159. * Once an object is destroyed, it should not be used; calling any function other than
  1160. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  1161. * assign the return value (<code>undefined</code>) to the object as done in the example.
  1162. *
  1163. * @returns {undefined}
  1164. *
  1165. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  1166. *
  1167. * @see BillboardCollection#isDestroyed
  1168. *
  1169. * @example
  1170. * billboards = billboards && billboards.destroy();
  1171. */
  1172. BillboardCollection.prototype.destroy = function() {
  1173. this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy();
  1174. this._sp = this._sp && this._sp.destroy();
  1175. this._spPick = this._spPick && this._spPick.destroy();
  1176. this._vaf = this._vaf && this._vaf.destroy();
  1177. this._destroyBillboards();
  1178. return destroyObject(this);
  1179. };
  1180. BillboardCollection.prototype._destroyBillboards = function() {
  1181. var billboards = this._billboards;
  1182. var length = billboards.length;
  1183. for (var i = 0; i < length; ++i) {
  1184. if (billboards[i]) {
  1185. billboards[i]._destroy();
  1186. }
  1187. }
  1188. };
  1189. return BillboardCollection;
  1190. });