BillboardCollection.js 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417
  1. import AttributeCompression from "../Core/AttributeCompression.js";
  2. import BoundingSphere from "../Core/BoundingSphere.js";
  3. import Cartesian2 from "../Core/Cartesian2.js";
  4. import Cartesian3 from "../Core/Cartesian3.js";
  5. import Check from "../Core/Check.js";
  6. import Color from "../Core/Color.js";
  7. import ComponentDatatype from "../Core/ComponentDatatype.js";
  8. import defaultValue from "../Core/defaultValue.js";
  9. import defined from "../Core/defined.js";
  10. import destroyObject from "../Core/destroyObject.js";
  11. import DeveloperError from "../Core/DeveloperError.js";
  12. import EncodedCartesian3 from "../Core/EncodedCartesian3.js";
  13. import IndexDatatype from "../Core/IndexDatatype.js";
  14. import CesiumMath from "../Core/Math.js";
  15. import Matrix4 from "../Core/Matrix4.js";
  16. import WebGLConstants from "../Core/WebGLConstants.js";
  17. import Buffer from "../Renderer/Buffer.js";
  18. import BufferUsage from "../Renderer/BufferUsage.js";
  19. import ContextLimits from "../Renderer/ContextLimits.js";
  20. import DrawCommand from "../Renderer/DrawCommand.js";
  21. import Pass from "../Renderer/Pass.js";
  22. import RenderState from "../Renderer/RenderState.js";
  23. import ShaderProgram from "../Renderer/ShaderProgram.js";
  24. import ShaderSource from "../Renderer/ShaderSource.js";
  25. import VertexArrayFacade from "../Renderer/VertexArrayFacade.js";
  26. import BillboardCollectionFS from "../Shaders/BillboardCollectionFS.js";
  27. import BillboardCollectionVS from "../Shaders/BillboardCollectionVS.js";
  28. import Billboard from "./Billboard.js";
  29. import BlendingState from "./BlendingState.js";
  30. import BlendOption from "./BlendOption.js";
  31. import HeightReference from "./HeightReference.js";
  32. import HorizontalOrigin from "./HorizontalOrigin.js";
  33. import SceneMode from "./SceneMode.js";
  34. import SDFSettings from "./SDFSettings.js";
  35. import TextureAtlas from "./TextureAtlas.js";
  36. import VerticalOrigin from "./VerticalOrigin.js";
  37. const SHOW_INDEX = Billboard.SHOW_INDEX;
  38. const POSITION_INDEX = Billboard.POSITION_INDEX;
  39. const PIXEL_OFFSET_INDEX = Billboard.PIXEL_OFFSET_INDEX;
  40. const EYE_OFFSET_INDEX = Billboard.EYE_OFFSET_INDEX;
  41. const HORIZONTAL_ORIGIN_INDEX = Billboard.HORIZONTAL_ORIGIN_INDEX;
  42. const VERTICAL_ORIGIN_INDEX = Billboard.VERTICAL_ORIGIN_INDEX;
  43. const SCALE_INDEX = Billboard.SCALE_INDEX;
  44. const IMAGE_INDEX_INDEX = Billboard.IMAGE_INDEX_INDEX;
  45. const COLOR_INDEX = Billboard.COLOR_INDEX;
  46. const ROTATION_INDEX = Billboard.ROTATION_INDEX;
  47. const ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX;
  48. const SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX;
  49. const TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX;
  50. const PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX =
  51. Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX;
  52. const DISTANCE_DISPLAY_CONDITION_INDEX = Billboard.DISTANCE_DISPLAY_CONDITION;
  53. const DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE;
  54. const TEXTURE_COORDINATE_BOUNDS = Billboard.TEXTURE_COORDINATE_BOUNDS;
  55. const SDF_INDEX = Billboard.SDF_INDEX;
  56. const NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;
  57. let attributeLocations;
  58. const attributeLocationsBatched = {
  59. positionHighAndScale: 0,
  60. positionLowAndRotation: 1,
  61. compressedAttribute0: 2, // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates
  62. compressedAttribute1: 3, // aligned axis, translucency by distance, image width
  63. compressedAttribute2: 4, // image height, color, pick color, size in meters, valid aligned axis, 13 bits free
  64. eyeOffset: 5, // 4 bytes free
  65. scaleByDistance: 6,
  66. pixelOffsetScaleByDistance: 7,
  67. compressedAttribute3: 8,
  68. textureCoordinateBoundsOrLabelTranslate: 9,
  69. a_batchId: 10,
  70. sdf: 11,
  71. };
  72. const attributeLocationsInstanced = {
  73. direction: 0,
  74. positionHighAndScale: 1,
  75. positionLowAndRotation: 2, // texture offset in w
  76. compressedAttribute0: 3,
  77. compressedAttribute1: 4,
  78. compressedAttribute2: 5,
  79. eyeOffset: 6, // texture range in w
  80. scaleByDistance: 7,
  81. pixelOffsetScaleByDistance: 8,
  82. compressedAttribute3: 9,
  83. textureCoordinateBoundsOrLabelTranslate: 10,
  84. a_batchId: 11,
  85. sdf: 12,
  86. };
  87. /**
  88. * A renderable collection of billboards. Billboards are viewport-aligned
  89. * images positioned in the 3D scene.
  90. * <br /><br />
  91. * <div align='center'>
  92. * <img src='Images/Billboard.png' width='400' height='300' /><br />
  93. * Example billboards
  94. * </div>
  95. * <br /><br />
  96. * Billboards are added and removed from the collection using {@link BillboardCollection#add}
  97. * and {@link BillboardCollection#remove}. Billboards in a collection automatically share textures
  98. * for images with the same identifier.
  99. *
  100. * @alias BillboardCollection
  101. * @constructor
  102. *
  103. * @param {object} [options] Object with the following properties:
  104. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each billboard from model to world coordinates.
  105. * @param {boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
  106. * @param {Scene} [options.scene] Must be passed in for billboards that use the height reference property or will be depth tested against the globe.
  107. * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The billboard blending option. The default
  108. * is used for rendering both opaque and translucent billboards. However, if either all of the billboards are completely opaque or all are completely translucent,
  109. * setting the technique to BlendOption.OPAQUE or BlendOption.TRANSLUCENT can improve performance by up to 2x.
  110. * @param {boolean} [options.show=true] Determines if the billboards in the collection will be shown.
  111. *
  112. * @performance For best performance, prefer a few collections, each with many billboards, to
  113. * many collections with only a few billboards each. Organize collections so that billboards
  114. * with the same update frequency are in the same collection, i.e., billboards that do not
  115. * change should be in one collection; billboards that change every frame should be in another
  116. * collection; and so on.
  117. *
  118. * @see BillboardCollection#add
  119. * @see BillboardCollection#remove
  120. * @see Billboard
  121. * @see LabelCollection
  122. *
  123. * @demo {@link https://sandcastle.cesium.com/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
  124. *
  125. * @example
  126. * // Create a billboard collection with two billboards
  127. * const billboards = scene.primitives.add(new Cesium.BillboardCollection());
  128. * billboards.add({
  129. * position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
  130. * image : 'url/to/image'
  131. * });
  132. * billboards.add({
  133. * position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
  134. * image : 'url/to/another/image'
  135. * });
  136. */
  137. function BillboardCollection(options) {
  138. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  139. this._scene = options.scene;
  140. this._batchTable = options.batchTable;
  141. this._textureAtlas = undefined;
  142. this._textureAtlasGUID = undefined;
  143. this._destroyTextureAtlas = true;
  144. this._sp = undefined;
  145. this._spTranslucent = undefined;
  146. this._rsOpaque = undefined;
  147. this._rsTranslucent = undefined;
  148. this._vaf = undefined;
  149. this._billboards = [];
  150. this._billboardsToUpdate = [];
  151. this._billboardsToUpdateIndex = 0;
  152. this._billboardsRemoved = false;
  153. this._createVertexArray = false;
  154. this._shaderRotation = false;
  155. this._compiledShaderRotation = false;
  156. this._shaderAlignedAxis = false;
  157. this._compiledShaderAlignedAxis = false;
  158. this._shaderScaleByDistance = false;
  159. this._compiledShaderScaleByDistance = false;
  160. this._shaderTranslucencyByDistance = false;
  161. this._compiledShaderTranslucencyByDistance = false;
  162. this._shaderPixelOffsetScaleByDistance = false;
  163. this._compiledShaderPixelOffsetScaleByDistance = false;
  164. this._shaderDistanceDisplayCondition = false;
  165. this._compiledShaderDistanceDisplayCondition = false;
  166. this._shaderDisableDepthDistance = false;
  167. this._compiledShaderDisableDepthDistance = false;
  168. this._shaderClampToGround = false;
  169. this._compiledShaderClampToGround = false;
  170. this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
  171. this._maxSize = 0.0;
  172. this._maxEyeOffset = 0.0;
  173. this._maxScale = 1.0;
  174. this._maxPixelOffset = 0.0;
  175. this._allHorizontalCenter = true;
  176. this._allVerticalCenter = true;
  177. this._allSizedInMeters = true;
  178. this._baseVolume = new BoundingSphere();
  179. this._baseVolumeWC = new BoundingSphere();
  180. this._baseVolume2D = new BoundingSphere();
  181. this._boundingVolume = new BoundingSphere();
  182. this._boundingVolumeDirty = false;
  183. this._colorCommands = [];
  184. /**
  185. * Determines if billboards in this collection will be shown.
  186. *
  187. * @type {boolean}
  188. * @default true
  189. */
  190. this.show = defaultValue(options.show, true);
  191. /**
  192. * The 4x4 transformation matrix that transforms each billboard in this collection from model to world coordinates.
  193. * When this is the identity matrix, the billboards are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
  194. * Local reference frames can be used by providing a different transformation matrix, like that returned
  195. * by {@link Transforms.eastNorthUpToFixedFrame}.
  196. *
  197. * @type {Matrix4}
  198. * @default {@link Matrix4.IDENTITY}
  199. *
  200. *
  201. * @example
  202. * const center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
  203. * billboards.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
  204. * billboards.add({
  205. * image : 'url/to/image',
  206. * position : new Cesium.Cartesian3(0.0, 0.0, 0.0) // center
  207. * });
  208. * billboards.add({
  209. * image : 'url/to/image',
  210. * position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0) // east
  211. * });
  212. * billboards.add({
  213. * image : 'url/to/image',
  214. * position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0) // north
  215. * });
  216. * billboards.add({
  217. * image : 'url/to/image',
  218. * position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0) // up
  219. * });
  220. *
  221. * @see Transforms.eastNorthUpToFixedFrame
  222. */
  223. this.modelMatrix = Matrix4.clone(
  224. defaultValue(options.modelMatrix, Matrix4.IDENTITY)
  225. );
  226. this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
  227. /**
  228. * This property is for debugging only; it is not for production use nor is it optimized.
  229. * <p>
  230. * Draws the bounding sphere for each draw command in the primitive.
  231. * </p>
  232. *
  233. * @type {boolean}
  234. *
  235. * @default false
  236. */
  237. this.debugShowBoundingVolume = defaultValue(
  238. options.debugShowBoundingVolume,
  239. false
  240. );
  241. /**
  242. * This property is for debugging only; it is not for production use nor is it optimized.
  243. * <p>
  244. * Draws the texture atlas for this BillboardCollection as a fullscreen quad.
  245. * </p>
  246. *
  247. * @type {boolean}
  248. *
  249. * @default false
  250. */
  251. this.debugShowTextureAtlas = defaultValue(
  252. options.debugShowTextureAtlas,
  253. false
  254. );
  255. /**
  256. * The billboard blending option. The default is used for rendering both opaque and translucent billboards.
  257. * However, if either all of the billboards are completely opaque or all are completely translucent,
  258. * setting the technique to BlendOption.OPAQUE or BlendOption.TRANSLUCENT can improve
  259. * performance by up to 2x.
  260. * @type {BlendOption}
  261. * @default BlendOption.OPAQUE_AND_TRANSLUCENT
  262. */
  263. this.blendOption = defaultValue(
  264. options.blendOption,
  265. BlendOption.OPAQUE_AND_TRANSLUCENT
  266. );
  267. this._blendOption = undefined;
  268. this._mode = SceneMode.SCENE3D;
  269. // The buffer usage for each attribute is determined based on the usage of the attribute over time.
  270. this._buffersUsage = [
  271. BufferUsage.STATIC_DRAW, // SHOW_INDEX
  272. BufferUsage.STATIC_DRAW, // POSITION_INDEX
  273. BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_INDEX
  274. BufferUsage.STATIC_DRAW, // EYE_OFFSET_INDEX
  275. BufferUsage.STATIC_DRAW, // HORIZONTAL_ORIGIN_INDEX
  276. BufferUsage.STATIC_DRAW, // VERTICAL_ORIGIN_INDEX
  277. BufferUsage.STATIC_DRAW, // SCALE_INDEX
  278. BufferUsage.STATIC_DRAW, // IMAGE_INDEX_INDEX
  279. BufferUsage.STATIC_DRAW, // COLOR_INDEX
  280. BufferUsage.STATIC_DRAW, // ROTATION_INDEX
  281. BufferUsage.STATIC_DRAW, // ALIGNED_AXIS_INDEX
  282. BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX
  283. BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX
  284. BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX
  285. BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX
  286. BufferUsage.STATIC_DRAW, // TEXTURE_COORDINATE_BOUNDS
  287. ];
  288. this._highlightColor = Color.clone(Color.WHITE); // Only used by Vector3DTilePoints
  289. const that = this;
  290. this._uniforms = {
  291. u_atlas: function () {
  292. return that._textureAtlas.texture;
  293. },
  294. u_highlightColor: function () {
  295. return that._highlightColor;
  296. },
  297. };
  298. const scene = this._scene;
  299. if (defined(scene) && defined(scene.terrainProviderChanged)) {
  300. this._removeCallbackFunc = scene.terrainProviderChanged.addEventListener(
  301. function () {
  302. const billboards = this._billboards;
  303. const length = billboards.length;
  304. for (let i = 0; i < length; ++i) {
  305. if (defined(billboards[i])) {
  306. billboards[i]._updateClamping();
  307. }
  308. }
  309. },
  310. this
  311. );
  312. }
  313. }
  314. Object.defineProperties(BillboardCollection.prototype, {
  315. /**
  316. * Returns the number of billboards in this collection. This is commonly used with
  317. * {@link BillboardCollection#get} to iterate over all the billboards
  318. * in the collection.
  319. * @memberof BillboardCollection.prototype
  320. * @type {number}
  321. */
  322. length: {
  323. get: function () {
  324. removeBillboards(this);
  325. return this._billboards.length;
  326. },
  327. },
  328. /**
  329. * Gets or sets the textureAtlas.
  330. * @memberof BillboardCollection.prototype
  331. * @type {TextureAtlas}
  332. * @private
  333. */
  334. textureAtlas: {
  335. get: function () {
  336. return this._textureAtlas;
  337. },
  338. set: function (value) {
  339. if (this._textureAtlas !== value) {
  340. this._textureAtlas =
  341. this._destroyTextureAtlas &&
  342. this._textureAtlas &&
  343. this._textureAtlas.destroy();
  344. this._textureAtlas = value;
  345. this._createVertexArray = true; // New per-billboard texture coordinates
  346. }
  347. },
  348. },
  349. /**
  350. * Gets or sets a value which determines if the texture atlas is
  351. * destroyed when the collection is destroyed.
  352. *
  353. * If the texture atlas is used by more than one collection, set this to <code>false</code>,
  354. * and explicitly destroy the atlas to avoid attempting to destroy it multiple times.
  355. *
  356. * @memberof BillboardCollection.prototype
  357. * @type {boolean}
  358. * @private
  359. *
  360. * @example
  361. * // Set destroyTextureAtlas
  362. * // Destroy a billboard collection but not its texture atlas.
  363. *
  364. * const atlas = new TextureAtlas({
  365. * scene : scene,
  366. * images : images
  367. * });
  368. * billboards.textureAtlas = atlas;
  369. * billboards.destroyTextureAtlas = false;
  370. * billboards = billboards.destroy();
  371. * console.log(atlas.isDestroyed()); // False
  372. */
  373. destroyTextureAtlas: {
  374. get: function () {
  375. return this._destroyTextureAtlas;
  376. },
  377. set: function (value) {
  378. this._destroyTextureAtlas = value;
  379. },
  380. },
  381. });
  382. function destroyBillboards(billboards) {
  383. const length = billboards.length;
  384. for (let i = 0; i < length; ++i) {
  385. if (billboards[i]) {
  386. billboards[i]._destroy();
  387. }
  388. }
  389. }
  390. /**
  391. * Creates and adds a billboard with the specified initial properties to the collection.
  392. * The added billboard is returned so it can be modified or removed from the collection later.
  393. *
  394. * @param {object}[options] A template describing the billboard's properties as shown in Example 1.
  395. * @returns {Billboard} The billboard that was added to the collection.
  396. *
  397. * @performance Calling <code>add</code> is expected constant time. However, the collection's vertex buffer
  398. * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
  399. * best performance, add as many billboards as possible before calling <code>update</code>.
  400. *
  401. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  402. *
  403. *
  404. * @example
  405. * // Example 1: Add a billboard, specifying all the default values.
  406. * const b = billboards.add({
  407. * show : true,
  408. * position : Cesium.Cartesian3.ZERO,
  409. * pixelOffset : Cesium.Cartesian2.ZERO,
  410. * eyeOffset : Cesium.Cartesian3.ZERO,
  411. * heightReference : Cesium.HeightReference.NONE,
  412. * horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
  413. * verticalOrigin : Cesium.VerticalOrigin.CENTER,
  414. * scale : 1.0,
  415. * image : 'url/to/image',
  416. * imageSubRegion : undefined,
  417. * color : Cesium.Color.WHITE,
  418. * id : undefined,
  419. * rotation : 0.0,
  420. * alignedAxis : Cesium.Cartesian3.ZERO,
  421. * width : undefined,
  422. * height : undefined,
  423. * scaleByDistance : undefined,
  424. * translucencyByDistance : undefined,
  425. * pixelOffsetScaleByDistance : undefined,
  426. * sizeInMeters : false,
  427. * distanceDisplayCondition : undefined
  428. * });
  429. *
  430. * @example
  431. * // Example 2: Specify only the billboard's cartographic position.
  432. * const b = billboards.add({
  433. * position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
  434. * });
  435. *
  436. * @see BillboardCollection#remove
  437. * @see BillboardCollection#removeAll
  438. */
  439. BillboardCollection.prototype.add = function (options) {
  440. const billboard = new Billboard(options, this);
  441. billboard._index = this._billboards.length;
  442. this._billboards.push(billboard);
  443. this._createVertexArray = true;
  444. return billboard;
  445. };
  446. /**
  447. * Removes a billboard from the collection.
  448. *
  449. * @param {Billboard} billboard The billboard to remove.
  450. * @returns {boolean} <code>true</code> if the billboard was removed; <code>false</code> if the billboard was not found in the collection.
  451. *
  452. * @performance Calling <code>remove</code> is expected constant time. However, the collection's vertex buffer
  453. * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
  454. * best performance, remove as many billboards as possible before calling <code>update</code>.
  455. * If you intend to temporarily hide a billboard, it is usually more efficient to call
  456. * {@link Billboard#show} instead of removing and re-adding the billboard.
  457. *
  458. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  459. *
  460. *
  461. * @example
  462. * const b = billboards.add(...);
  463. * billboards.remove(b); // Returns true
  464. *
  465. * @see BillboardCollection#add
  466. * @see BillboardCollection#removeAll
  467. * @see Billboard#show
  468. */
  469. BillboardCollection.prototype.remove = function (billboard) {
  470. if (this.contains(billboard)) {
  471. this._billboards[billboard._index] = undefined; // Removed later
  472. this._billboardsRemoved = true;
  473. this._createVertexArray = true;
  474. billboard._destroy();
  475. return true;
  476. }
  477. return false;
  478. };
  479. /**
  480. * Removes all billboards from the collection.
  481. *
  482. * @performance <code>O(n)</code>. It is more efficient to remove all the billboards
  483. * from a collection and then add new ones than to create a new collection entirely.
  484. *
  485. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  486. *
  487. *
  488. * @example
  489. * billboards.add(...);
  490. * billboards.add(...);
  491. * billboards.removeAll();
  492. *
  493. * @see BillboardCollection#add
  494. * @see BillboardCollection#remove
  495. */
  496. BillboardCollection.prototype.removeAll = function () {
  497. destroyBillboards(this._billboards);
  498. this._billboards = [];
  499. this._billboardsToUpdate = [];
  500. this._billboardsToUpdateIndex = 0;
  501. this._billboardsRemoved = false;
  502. this._createVertexArray = true;
  503. };
  504. function removeBillboards(billboardCollection) {
  505. if (billboardCollection._billboardsRemoved) {
  506. billboardCollection._billboardsRemoved = false;
  507. const newBillboards = [];
  508. const billboards = billboardCollection._billboards;
  509. const length = billboards.length;
  510. for (let i = 0, j = 0; i < length; ++i) {
  511. const billboard = billboards[i];
  512. if (defined(billboard)) {
  513. billboard._index = j++;
  514. newBillboards.push(billboard);
  515. }
  516. }
  517. billboardCollection._billboards = newBillboards;
  518. }
  519. }
  520. BillboardCollection.prototype._updateBillboard = function (
  521. billboard,
  522. propertyChanged
  523. ) {
  524. if (!billboard._dirty) {
  525. this._billboardsToUpdate[this._billboardsToUpdateIndex++] = billboard;
  526. }
  527. ++this._propertiesChanged[propertyChanged];
  528. };
  529. /**
  530. * Check whether this collection contains a given billboard.
  531. *
  532. * @param {Billboard} [billboard] The billboard to check for.
  533. * @returns {boolean} true if this collection contains the billboard, false otherwise.
  534. *
  535. * @see BillboardCollection#get
  536. */
  537. BillboardCollection.prototype.contains = function (billboard) {
  538. return defined(billboard) && billboard._billboardCollection === this;
  539. };
  540. /**
  541. * Returns the billboard in the collection at the specified index. Indices are zero-based
  542. * and increase as billboards are added. Removing a billboard shifts all billboards after
  543. * it to the left, changing their indices. This function is commonly used with
  544. * {@link BillboardCollection#length} to iterate over all the billboards
  545. * in the collection.
  546. *
  547. * @param {number} index The zero-based index of the billboard.
  548. * @returns {Billboard} The billboard at the specified index.
  549. *
  550. * @performance Expected constant time. If billboards were removed from the collection and
  551. * {@link BillboardCollection#update} was not called, an implicit <code>O(n)</code>
  552. * operation is performed.
  553. *
  554. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  555. *
  556. *
  557. * @example
  558. * // Toggle the show property of every billboard in the collection
  559. * const len = billboards.length;
  560. * for (let i = 0; i < len; ++i) {
  561. * const b = billboards.get(i);
  562. * b.show = !b.show;
  563. * }
  564. *
  565. * @see BillboardCollection#length
  566. */
  567. BillboardCollection.prototype.get = function (index) {
  568. //>>includeStart('debug', pragmas.debug);
  569. Check.typeOf.number("index", index);
  570. //>>includeEnd('debug');
  571. removeBillboards(this);
  572. return this._billboards[index];
  573. };
  574. let getIndexBuffer;
  575. function getIndexBufferBatched(context) {
  576. const sixteenK = 16 * 1024;
  577. let indexBuffer = context.cache.billboardCollection_indexBufferBatched;
  578. if (defined(indexBuffer)) {
  579. return indexBuffer;
  580. }
  581. // Subtract 6 because the last index is reserverd for primitive restart.
  582. // https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.18
  583. const length = sixteenK * 6 - 6;
  584. const indices = new Uint16Array(length);
  585. for (let i = 0, j = 0; i < length; i += 6, j += 4) {
  586. indices[i] = j;
  587. indices[i + 1] = j + 1;
  588. indices[i + 2] = j + 2;
  589. indices[i + 3] = j + 0;
  590. indices[i + 4] = j + 2;
  591. indices[i + 5] = j + 3;
  592. }
  593. // PERFORMANCE_IDEA: Should we reference count billboard collections, and eventually delete this?
  594. // Is this too much memory to allocate up front? Should we dynamically grow it?
  595. indexBuffer = Buffer.createIndexBuffer({
  596. context: context,
  597. typedArray: indices,
  598. usage: BufferUsage.STATIC_DRAW,
  599. indexDatatype: IndexDatatype.UNSIGNED_SHORT,
  600. });
  601. indexBuffer.vertexArrayDestroyable = false;
  602. context.cache.billboardCollection_indexBufferBatched = indexBuffer;
  603. return indexBuffer;
  604. }
  605. function getIndexBufferInstanced(context) {
  606. let indexBuffer = context.cache.billboardCollection_indexBufferInstanced;
  607. if (defined(indexBuffer)) {
  608. return indexBuffer;
  609. }
  610. indexBuffer = Buffer.createIndexBuffer({
  611. context: context,
  612. typedArray: new Uint16Array([0, 1, 2, 0, 2, 3]),
  613. usage: BufferUsage.STATIC_DRAW,
  614. indexDatatype: IndexDatatype.UNSIGNED_SHORT,
  615. });
  616. indexBuffer.vertexArrayDestroyable = false;
  617. context.cache.billboardCollection_indexBufferInstanced = indexBuffer;
  618. return indexBuffer;
  619. }
  620. function getVertexBufferInstanced(context) {
  621. let vertexBuffer = context.cache.billboardCollection_vertexBufferInstanced;
  622. if (defined(vertexBuffer)) {
  623. return vertexBuffer;
  624. }
  625. vertexBuffer = Buffer.createVertexBuffer({
  626. context: context,
  627. typedArray: new Float32Array([0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]),
  628. usage: BufferUsage.STATIC_DRAW,
  629. });
  630. vertexBuffer.vertexArrayDestroyable = false;
  631. context.cache.billboardCollection_vertexBufferInstanced = vertexBuffer;
  632. return vertexBuffer;
  633. }
  634. BillboardCollection.prototype.computeNewBuffersUsage = function () {
  635. const buffersUsage = this._buffersUsage;
  636. let usageChanged = false;
  637. const properties = this._propertiesChanged;
  638. for (let k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
  639. const newUsage =
  640. properties[k] === 0 ? BufferUsage.STATIC_DRAW : BufferUsage.STREAM_DRAW;
  641. usageChanged = usageChanged || buffersUsage[k] !== newUsage;
  642. buffersUsage[k] = newUsage;
  643. }
  644. return usageChanged;
  645. };
  646. function createVAF(
  647. context,
  648. numberOfBillboards,
  649. buffersUsage,
  650. instanced,
  651. batchTable,
  652. sdf
  653. ) {
  654. const attributes = [
  655. {
  656. index: attributeLocations.positionHighAndScale,
  657. componentsPerAttribute: 4,
  658. componentDatatype: ComponentDatatype.FLOAT,
  659. usage: buffersUsage[POSITION_INDEX],
  660. },
  661. {
  662. index: attributeLocations.positionLowAndRotation,
  663. componentsPerAttribute: 4,
  664. componentDatatype: ComponentDatatype.FLOAT,
  665. usage: buffersUsage[POSITION_INDEX],
  666. },
  667. {
  668. index: attributeLocations.compressedAttribute0,
  669. componentsPerAttribute: 4,
  670. componentDatatype: ComponentDatatype.FLOAT,
  671. usage: buffersUsage[PIXEL_OFFSET_INDEX],
  672. },
  673. {
  674. index: attributeLocations.compressedAttribute1,
  675. componentsPerAttribute: 4,
  676. componentDatatype: ComponentDatatype.FLOAT,
  677. usage: buffersUsage[TRANSLUCENCY_BY_DISTANCE_INDEX],
  678. },
  679. {
  680. index: attributeLocations.compressedAttribute2,
  681. componentsPerAttribute: 4,
  682. componentDatatype: ComponentDatatype.FLOAT,
  683. usage: buffersUsage[COLOR_INDEX],
  684. },
  685. {
  686. index: attributeLocations.eyeOffset,
  687. componentsPerAttribute: 4,
  688. componentDatatype: ComponentDatatype.FLOAT,
  689. usage: buffersUsage[EYE_OFFSET_INDEX],
  690. },
  691. {
  692. index: attributeLocations.scaleByDistance,
  693. componentsPerAttribute: 4,
  694. componentDatatype: ComponentDatatype.FLOAT,
  695. usage: buffersUsage[SCALE_BY_DISTANCE_INDEX],
  696. },
  697. {
  698. index: attributeLocations.pixelOffsetScaleByDistance,
  699. componentsPerAttribute: 4,
  700. componentDatatype: ComponentDatatype.FLOAT,
  701. usage: buffersUsage[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX],
  702. },
  703. {
  704. index: attributeLocations.compressedAttribute3,
  705. componentsPerAttribute: 4,
  706. componentDatatype: ComponentDatatype.FLOAT,
  707. usage: buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX],
  708. },
  709. {
  710. index: attributeLocations.textureCoordinateBoundsOrLabelTranslate,
  711. componentsPerAttribute: 4,
  712. componentDatatype: ComponentDatatype.FLOAT,
  713. usage: buffersUsage[TEXTURE_COORDINATE_BOUNDS],
  714. },
  715. ];
  716. // Instancing requires one non-instanced attribute.
  717. if (instanced) {
  718. attributes.push({
  719. index: attributeLocations.direction,
  720. componentsPerAttribute: 2,
  721. componentDatatype: ComponentDatatype.FLOAT,
  722. vertexBuffer: getVertexBufferInstanced(context),
  723. });
  724. }
  725. if (defined(batchTable)) {
  726. attributes.push({
  727. index: attributeLocations.a_batchId,
  728. componentsPerAttribute: 1,
  729. componentDatatype: ComponentDatatype.FLOAT,
  730. bufferUsage: BufferUsage.STATIC_DRAW,
  731. });
  732. }
  733. if (sdf) {
  734. attributes.push({
  735. index: attributeLocations.sdf,
  736. componentsPerAttribute: 2,
  737. componentDatatype: ComponentDatatype.FLOAT,
  738. usage: buffersUsage[SDF_INDEX],
  739. });
  740. }
  741. // When instancing is enabled, only one vertex is needed for each billboard.
  742. const sizeInVertices = instanced
  743. ? numberOfBillboards
  744. : 4 * numberOfBillboards;
  745. return new VertexArrayFacade(context, attributes, sizeInVertices, instanced);
  746. }
  747. ///////////////////////////////////////////////////////////////////////////
  748. // Four vertices per billboard. Each has the same position, etc., but a different screen-space direction vector.
  749. // PERFORMANCE_IDEA: Save memory if a property is the same for all billboards, use a latched attribute state,
  750. // instead of storing it in a vertex buffer.
  751. const writePositionScratch = new EncodedCartesian3();
  752. function writePositionScaleAndRotation(
  753. billboardCollection,
  754. frameState,
  755. textureAtlasCoordinates,
  756. vafWriters,
  757. billboard
  758. ) {
  759. let i;
  760. const positionHighWriter =
  761. vafWriters[attributeLocations.positionHighAndScale];
  762. const positionLowWriter =
  763. vafWriters[attributeLocations.positionLowAndRotation];
  764. const position = billboard._getActualPosition();
  765. if (billboardCollection._mode === SceneMode.SCENE3D) {
  766. BoundingSphere.expand(
  767. billboardCollection._baseVolume,
  768. position,
  769. billboardCollection._baseVolume
  770. );
  771. billboardCollection._boundingVolumeDirty = true;
  772. }
  773. EncodedCartesian3.fromCartesian(position, writePositionScratch);
  774. const scale = billboard.scale;
  775. const rotation = billboard.rotation;
  776. if (rotation !== 0.0) {
  777. billboardCollection._shaderRotation = true;
  778. }
  779. billboardCollection._maxScale = Math.max(
  780. billboardCollection._maxScale,
  781. scale
  782. );
  783. const high = writePositionScratch.high;
  784. const low = writePositionScratch.low;
  785. if (billboardCollection._instanced) {
  786. i = billboard._index;
  787. positionHighWriter(i, high.x, high.y, high.z, scale);
  788. positionLowWriter(i, low.x, low.y, low.z, rotation);
  789. } else {
  790. i = billboard._index * 4;
  791. positionHighWriter(i + 0, high.x, high.y, high.z, scale);
  792. positionHighWriter(i + 1, high.x, high.y, high.z, scale);
  793. positionHighWriter(i + 2, high.x, high.y, high.z, scale);
  794. positionHighWriter(i + 3, high.x, high.y, high.z, scale);
  795. positionLowWriter(i + 0, low.x, low.y, low.z, rotation);
  796. positionLowWriter(i + 1, low.x, low.y, low.z, rotation);
  797. positionLowWriter(i + 2, low.x, low.y, low.z, rotation);
  798. positionLowWriter(i + 3, low.x, low.y, low.z, rotation);
  799. }
  800. }
  801. const scratchCartesian2 = new Cartesian2();
  802. const UPPER_BOUND = 32768.0; // 2^15
  803. const LEFT_SHIFT16 = 65536.0; // 2^16
  804. const LEFT_SHIFT12 = 4096.0; // 2^12
  805. const LEFT_SHIFT8 = 256.0; // 2^8
  806. const LEFT_SHIFT7 = 128.0;
  807. const LEFT_SHIFT5 = 32.0;
  808. const LEFT_SHIFT3 = 8.0;
  809. const LEFT_SHIFT2 = 4.0;
  810. const RIGHT_SHIFT8 = 1.0 / 256.0;
  811. const LOWER_LEFT = 0.0;
  812. const LOWER_RIGHT = 2.0;
  813. const UPPER_RIGHT = 3.0;
  814. const UPPER_LEFT = 1.0;
  815. function writeCompressedAttrib0(
  816. billboardCollection,
  817. frameState,
  818. textureAtlasCoordinates,
  819. vafWriters,
  820. billboard
  821. ) {
  822. let i;
  823. const writer = vafWriters[attributeLocations.compressedAttribute0];
  824. const pixelOffset = billboard.pixelOffset;
  825. const pixelOffsetX = pixelOffset.x;
  826. const pixelOffsetY = pixelOffset.y;
  827. const translate = billboard._translate;
  828. const translateX = translate.x;
  829. const translateY = translate.y;
  830. billboardCollection._maxPixelOffset = Math.max(
  831. billboardCollection._maxPixelOffset,
  832. Math.abs(pixelOffsetX + translateX),
  833. Math.abs(-pixelOffsetY + translateY)
  834. );
  835. const horizontalOrigin = billboard.horizontalOrigin;
  836. let verticalOrigin = billboard._verticalOrigin;
  837. let show = billboard.show && billboard.clusterShow;
  838. // If the color alpha is zero, do not show this billboard. This lets us avoid providing
  839. // color during the pick pass and also eliminates a discard in the fragment shader.
  840. if (billboard.color.alpha === 0.0) {
  841. show = false;
  842. }
  843. // Raw billboards don't distinguish between BASELINE and BOTTOM, only LabelCollection does that.
  844. if (verticalOrigin === VerticalOrigin.BASELINE) {
  845. verticalOrigin = VerticalOrigin.BOTTOM;
  846. }
  847. billboardCollection._allHorizontalCenter =
  848. billboardCollection._allHorizontalCenter &&
  849. horizontalOrigin === HorizontalOrigin.CENTER;
  850. billboardCollection._allVerticalCenter =
  851. billboardCollection._allVerticalCenter &&
  852. verticalOrigin === VerticalOrigin.CENTER;
  853. let bottomLeftX = 0;
  854. let bottomLeftY = 0;
  855. let width = 0;
  856. let height = 0;
  857. const index = billboard._imageIndex;
  858. if (index !== -1) {
  859. const imageRectangle = textureAtlasCoordinates[index];
  860. //>>includeStart('debug', pragmas.debug);
  861. if (!defined(imageRectangle)) {
  862. throw new DeveloperError(`Invalid billboard image index: ${index}`);
  863. }
  864. //>>includeEnd('debug');
  865. bottomLeftX = imageRectangle.x;
  866. bottomLeftY = imageRectangle.y;
  867. width = imageRectangle.width;
  868. height = imageRectangle.height;
  869. }
  870. const topRightX = bottomLeftX + width;
  871. const topRightY = bottomLeftY + height;
  872. let compressed0 =
  873. Math.floor(
  874. CesiumMath.clamp(pixelOffsetX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND
  875. ) * LEFT_SHIFT7;
  876. compressed0 += (horizontalOrigin + 1.0) * LEFT_SHIFT5;
  877. compressed0 += (verticalOrigin + 1.0) * LEFT_SHIFT3;
  878. compressed0 += (show ? 1.0 : 0.0) * LEFT_SHIFT2;
  879. let compressed1 =
  880. Math.floor(
  881. CesiumMath.clamp(pixelOffsetY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND
  882. ) * LEFT_SHIFT8;
  883. let compressed2 =
  884. Math.floor(
  885. CesiumMath.clamp(translateX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND
  886. ) * LEFT_SHIFT8;
  887. const tempTanslateY =
  888. (CesiumMath.clamp(translateY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) *
  889. RIGHT_SHIFT8;
  890. const upperTranslateY = Math.floor(tempTanslateY);
  891. const lowerTranslateY = Math.floor(
  892. (tempTanslateY - upperTranslateY) * LEFT_SHIFT8
  893. );
  894. compressed1 += upperTranslateY;
  895. compressed2 += lowerTranslateY;
  896. scratchCartesian2.x = bottomLeftX;
  897. scratchCartesian2.y = bottomLeftY;
  898. const compressedTexCoordsLL = AttributeCompression.compressTextureCoordinates(
  899. scratchCartesian2
  900. );
  901. scratchCartesian2.x = topRightX;
  902. const compressedTexCoordsLR = AttributeCompression.compressTextureCoordinates(
  903. scratchCartesian2
  904. );
  905. scratchCartesian2.y = topRightY;
  906. const compressedTexCoordsUR = AttributeCompression.compressTextureCoordinates(
  907. scratchCartesian2
  908. );
  909. scratchCartesian2.x = bottomLeftX;
  910. const compressedTexCoordsUL = AttributeCompression.compressTextureCoordinates(
  911. scratchCartesian2
  912. );
  913. if (billboardCollection._instanced) {
  914. i = billboard._index;
  915. writer(i, compressed0, compressed1, compressed2, compressedTexCoordsLL);
  916. } else {
  917. i = billboard._index * 4;
  918. writer(
  919. i + 0,
  920. compressed0 + LOWER_LEFT,
  921. compressed1,
  922. compressed2,
  923. compressedTexCoordsLL
  924. );
  925. writer(
  926. i + 1,
  927. compressed0 + LOWER_RIGHT,
  928. compressed1,
  929. compressed2,
  930. compressedTexCoordsLR
  931. );
  932. writer(
  933. i + 2,
  934. compressed0 + UPPER_RIGHT,
  935. compressed1,
  936. compressed2,
  937. compressedTexCoordsUR
  938. );
  939. writer(
  940. i + 3,
  941. compressed0 + UPPER_LEFT,
  942. compressed1,
  943. compressed2,
  944. compressedTexCoordsUL
  945. );
  946. }
  947. }
  948. function writeCompressedAttrib1(
  949. billboardCollection,
  950. frameState,
  951. textureAtlasCoordinates,
  952. vafWriters,
  953. billboard
  954. ) {
  955. let i;
  956. const writer = vafWriters[attributeLocations.compressedAttribute1];
  957. const alignedAxis = billboard.alignedAxis;
  958. if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) {
  959. billboardCollection._shaderAlignedAxis = true;
  960. }
  961. let near = 0.0;
  962. let nearValue = 1.0;
  963. let far = 1.0;
  964. let farValue = 1.0;
  965. const translucency = billboard.translucencyByDistance;
  966. if (defined(translucency)) {
  967. near = translucency.near;
  968. nearValue = translucency.nearValue;
  969. far = translucency.far;
  970. farValue = translucency.farValue;
  971. if (nearValue !== 1.0 || farValue !== 1.0) {
  972. // translucency by distance calculation in shader need not be enabled
  973. // until a billboard with near and far !== 1.0 is found
  974. billboardCollection._shaderTranslucencyByDistance = true;
  975. }
  976. }
  977. let width = 0;
  978. const index = billboard._imageIndex;
  979. if (index !== -1) {
  980. const imageRectangle = textureAtlasCoordinates[index];
  981. //>>includeStart('debug', pragmas.debug);
  982. if (!defined(imageRectangle)) {
  983. throw new DeveloperError(`Invalid billboard image index: ${index}`);
  984. }
  985. //>>includeEnd('debug');
  986. width = imageRectangle.width;
  987. }
  988. const textureWidth = billboardCollection._textureAtlas.texture.width;
  989. const imageWidth = Math.round(
  990. defaultValue(billboard.width, textureWidth * width)
  991. );
  992. billboardCollection._maxSize = Math.max(
  993. billboardCollection._maxSize,
  994. imageWidth
  995. );
  996. let compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16);
  997. let compressed1 = 0.0;
  998. if (
  999. Math.abs(Cartesian3.magnitudeSquared(alignedAxis) - 1.0) <
  1000. CesiumMath.EPSILON6
  1001. ) {
  1002. compressed1 = AttributeCompression.octEncodeFloat(alignedAxis);
  1003. }
  1004. nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0);
  1005. nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0;
  1006. compressed0 = compressed0 * LEFT_SHIFT8 + nearValue;
  1007. farValue = CesiumMath.clamp(farValue, 0.0, 1.0);
  1008. farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
  1009. compressed1 = compressed1 * LEFT_SHIFT8 + farValue;
  1010. if (billboardCollection._instanced) {
  1011. i = billboard._index;
  1012. writer(i, compressed0, compressed1, near, far);
  1013. } else {
  1014. i = billboard._index * 4;
  1015. writer(i + 0, compressed0, compressed1, near, far);
  1016. writer(i + 1, compressed0, compressed1, near, far);
  1017. writer(i + 2, compressed0, compressed1, near, far);
  1018. writer(i + 3, compressed0, compressed1, near, far);
  1019. }
  1020. }
  1021. function writeCompressedAttrib2(
  1022. billboardCollection,
  1023. frameState,
  1024. textureAtlasCoordinates,
  1025. vafWriters,
  1026. billboard
  1027. ) {
  1028. let i;
  1029. const writer = vafWriters[attributeLocations.compressedAttribute2];
  1030. const color = billboard.color;
  1031. const pickColor = !defined(billboardCollection._batchTable)
  1032. ? billboard.getPickId(frameState.context).color
  1033. : Color.WHITE;
  1034. const sizeInMeters = billboard.sizeInMeters ? 1.0 : 0.0;
  1035. const validAlignedAxis =
  1036. Math.abs(Cartesian3.magnitudeSquared(billboard.alignedAxis) - 1.0) <
  1037. CesiumMath.EPSILON6
  1038. ? 1.0
  1039. : 0.0;
  1040. billboardCollection._allSizedInMeters =
  1041. billboardCollection._allSizedInMeters && sizeInMeters === 1.0;
  1042. let height = 0;
  1043. const index = billboard._imageIndex;
  1044. if (index !== -1) {
  1045. const imageRectangle = textureAtlasCoordinates[index];
  1046. //>>includeStart('debug', pragmas.debug);
  1047. if (!defined(imageRectangle)) {
  1048. throw new DeveloperError(`Invalid billboard image index: ${index}`);
  1049. }
  1050. //>>includeEnd('debug');
  1051. height = imageRectangle.height;
  1052. }
  1053. const dimensions = billboardCollection._textureAtlas.texture.dimensions;
  1054. const imageHeight = Math.round(
  1055. defaultValue(billboard.height, dimensions.y * height)
  1056. );
  1057. billboardCollection._maxSize = Math.max(
  1058. billboardCollection._maxSize,
  1059. imageHeight
  1060. );
  1061. let labelHorizontalOrigin = defaultValue(
  1062. billboard._labelHorizontalOrigin,
  1063. -2
  1064. );
  1065. labelHorizontalOrigin += 2;
  1066. const compressed3 = imageHeight * LEFT_SHIFT2 + labelHorizontalOrigin;
  1067. let red = Color.floatToByte(color.red);
  1068. let green = Color.floatToByte(color.green);
  1069. let blue = Color.floatToByte(color.blue);
  1070. const compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
  1071. red = Color.floatToByte(pickColor.red);
  1072. green = Color.floatToByte(pickColor.green);
  1073. blue = Color.floatToByte(pickColor.blue);
  1074. const compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
  1075. let compressed2 =
  1076. Color.floatToByte(color.alpha) * LEFT_SHIFT16 +
  1077. Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8;
  1078. compressed2 += sizeInMeters * 2.0 + validAlignedAxis;
  1079. if (billboardCollection._instanced) {
  1080. i = billboard._index;
  1081. writer(i, compressed0, compressed1, compressed2, compressed3);
  1082. } else {
  1083. i = billboard._index * 4;
  1084. writer(i + 0, compressed0, compressed1, compressed2, compressed3);
  1085. writer(i + 1, compressed0, compressed1, compressed2, compressed3);
  1086. writer(i + 2, compressed0, compressed1, compressed2, compressed3);
  1087. writer(i + 3, compressed0, compressed1, compressed2, compressed3);
  1088. }
  1089. }
  1090. function writeEyeOffset(
  1091. billboardCollection,
  1092. frameState,
  1093. textureAtlasCoordinates,
  1094. vafWriters,
  1095. billboard
  1096. ) {
  1097. let i;
  1098. const writer = vafWriters[attributeLocations.eyeOffset];
  1099. const eyeOffset = billboard.eyeOffset;
  1100. // For billboards that are clamped to ground, move it slightly closer to the camera
  1101. let eyeOffsetZ = eyeOffset.z;
  1102. if (billboard._heightReference !== HeightReference.NONE) {
  1103. eyeOffsetZ *= 1.005;
  1104. }
  1105. billboardCollection._maxEyeOffset = Math.max(
  1106. billboardCollection._maxEyeOffset,
  1107. Math.abs(eyeOffset.x),
  1108. Math.abs(eyeOffset.y),
  1109. Math.abs(eyeOffsetZ)
  1110. );
  1111. if (billboardCollection._instanced) {
  1112. let width = 0;
  1113. let height = 0;
  1114. const index = billboard._imageIndex;
  1115. if (index !== -1) {
  1116. const imageRectangle = textureAtlasCoordinates[index];
  1117. //>>includeStart('debug', pragmas.debug);
  1118. if (!defined(imageRectangle)) {
  1119. throw new DeveloperError(`Invalid billboard image index: ${index}`);
  1120. }
  1121. //>>includeEnd('debug');
  1122. width = imageRectangle.width;
  1123. height = imageRectangle.height;
  1124. }
  1125. scratchCartesian2.x = width;
  1126. scratchCartesian2.y = height;
  1127. const compressedTexCoordsRange = AttributeCompression.compressTextureCoordinates(
  1128. scratchCartesian2
  1129. );
  1130. i = billboard._index;
  1131. writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, compressedTexCoordsRange);
  1132. } else {
  1133. i = billboard._index * 4;
  1134. writer(i + 0, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
  1135. writer(i + 1, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
  1136. writer(i + 2, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
  1137. writer(i + 3, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
  1138. }
  1139. }
  1140. function writeScaleByDistance(
  1141. billboardCollection,
  1142. frameState,
  1143. textureAtlasCoordinates,
  1144. vafWriters,
  1145. billboard
  1146. ) {
  1147. let i;
  1148. const writer = vafWriters[attributeLocations.scaleByDistance];
  1149. let near = 0.0;
  1150. let nearValue = 1.0;
  1151. let far = 1.0;
  1152. let farValue = 1.0;
  1153. const scale = billboard.scaleByDistance;
  1154. if (defined(scale)) {
  1155. near = scale.near;
  1156. nearValue = scale.nearValue;
  1157. far = scale.far;
  1158. farValue = scale.farValue;
  1159. if (nearValue !== 1.0 || farValue !== 1.0) {
  1160. // scale by distance calculation in shader need not be enabled
  1161. // until a billboard with near and far !== 1.0 is found
  1162. billboardCollection._shaderScaleByDistance = true;
  1163. }
  1164. }
  1165. if (billboardCollection._instanced) {
  1166. i = billboard._index;
  1167. writer(i, near, nearValue, far, farValue);
  1168. } else {
  1169. i = billboard._index * 4;
  1170. writer(i + 0, near, nearValue, far, farValue);
  1171. writer(i + 1, near, nearValue, far, farValue);
  1172. writer(i + 2, near, nearValue, far, farValue);
  1173. writer(i + 3, near, nearValue, far, farValue);
  1174. }
  1175. }
  1176. function writePixelOffsetScaleByDistance(
  1177. billboardCollection,
  1178. frameState,
  1179. textureAtlasCoordinates,
  1180. vafWriters,
  1181. billboard
  1182. ) {
  1183. let i;
  1184. const writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance];
  1185. let near = 0.0;
  1186. let nearValue = 1.0;
  1187. let far = 1.0;
  1188. let farValue = 1.0;
  1189. const pixelOffsetScale = billboard.pixelOffsetScaleByDistance;
  1190. if (defined(pixelOffsetScale)) {
  1191. near = pixelOffsetScale.near;
  1192. nearValue = pixelOffsetScale.nearValue;
  1193. far = pixelOffsetScale.far;
  1194. farValue = pixelOffsetScale.farValue;
  1195. if (nearValue !== 1.0 || farValue !== 1.0) {
  1196. // pixelOffsetScale by distance calculation in shader need not be enabled
  1197. // until a billboard with near and far !== 1.0 is found
  1198. billboardCollection._shaderPixelOffsetScaleByDistance = true;
  1199. }
  1200. }
  1201. if (billboardCollection._instanced) {
  1202. i = billboard._index;
  1203. writer(i, near, nearValue, far, farValue);
  1204. } else {
  1205. i = billboard._index * 4;
  1206. writer(i + 0, near, nearValue, far, farValue);
  1207. writer(i + 1, near, nearValue, far, farValue);
  1208. writer(i + 2, near, nearValue, far, farValue);
  1209. writer(i + 3, near, nearValue, far, farValue);
  1210. }
  1211. }
  1212. function writeCompressedAttribute3(
  1213. billboardCollection,
  1214. frameState,
  1215. textureAtlasCoordinates,
  1216. vafWriters,
  1217. billboard
  1218. ) {
  1219. let i;
  1220. const writer = vafWriters[attributeLocations.compressedAttribute3];
  1221. let near = 0.0;
  1222. let far = Number.MAX_VALUE;
  1223. const distanceDisplayCondition = billboard.distanceDisplayCondition;
  1224. if (defined(distanceDisplayCondition)) {
  1225. near = distanceDisplayCondition.near;
  1226. far = distanceDisplayCondition.far;
  1227. near *= near;
  1228. far *= far;
  1229. billboardCollection._shaderDistanceDisplayCondition = true;
  1230. }
  1231. let disableDepthTestDistance = billboard.disableDepthTestDistance;
  1232. const clampToGround =
  1233. billboard.heightReference === HeightReference.CLAMP_TO_GROUND &&
  1234. frameState.context.depthTexture;
  1235. if (!defined(disableDepthTestDistance)) {
  1236. disableDepthTestDistance = clampToGround ? 5000.0 : 0.0;
  1237. }
  1238. disableDepthTestDistance *= disableDepthTestDistance;
  1239. if (clampToGround || disableDepthTestDistance > 0.0) {
  1240. billboardCollection._shaderDisableDepthDistance = true;
  1241. if (disableDepthTestDistance === Number.POSITIVE_INFINITY) {
  1242. disableDepthTestDistance = -1.0;
  1243. }
  1244. }
  1245. let imageHeight;
  1246. let imageWidth;
  1247. if (!defined(billboard._labelDimensions)) {
  1248. let height = 0;
  1249. let width = 0;
  1250. const index = billboard._imageIndex;
  1251. if (index !== -1) {
  1252. const imageRectangle = textureAtlasCoordinates[index];
  1253. //>>includeStart('debug', pragmas.debug);
  1254. if (!defined(imageRectangle)) {
  1255. throw new DeveloperError(`Invalid billboard image index: ${index}`);
  1256. }
  1257. //>>includeEnd('debug');
  1258. height = imageRectangle.height;
  1259. width = imageRectangle.width;
  1260. }
  1261. imageHeight = Math.round(
  1262. defaultValue(
  1263. billboard.height,
  1264. billboardCollection._textureAtlas.texture.dimensions.y * height
  1265. )
  1266. );
  1267. const textureWidth = billboardCollection._textureAtlas.texture.width;
  1268. imageWidth = Math.round(
  1269. defaultValue(billboard.width, textureWidth * width)
  1270. );
  1271. } else {
  1272. imageWidth = billboard._labelDimensions.x;
  1273. imageHeight = billboard._labelDimensions.y;
  1274. }
  1275. const w = Math.floor(CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT12));
  1276. const h = Math.floor(CesiumMath.clamp(imageHeight, 0.0, LEFT_SHIFT12));
  1277. const dimensions = w * LEFT_SHIFT12 + h;
  1278. if (billboardCollection._instanced) {
  1279. i = billboard._index;
  1280. writer(i, near, far, disableDepthTestDistance, dimensions);
  1281. } else {
  1282. i = billboard._index * 4;
  1283. writer(i + 0, near, far, disableDepthTestDistance, dimensions);
  1284. writer(i + 1, near, far, disableDepthTestDistance, dimensions);
  1285. writer(i + 2, near, far, disableDepthTestDistance, dimensions);
  1286. writer(i + 3, near, far, disableDepthTestDistance, dimensions);
  1287. }
  1288. }
  1289. function writeTextureCoordinateBoundsOrLabelTranslate(
  1290. billboardCollection,
  1291. frameState,
  1292. textureAtlasCoordinates,
  1293. vafWriters,
  1294. billboard
  1295. ) {
  1296. if (billboard.heightReference === HeightReference.CLAMP_TO_GROUND) {
  1297. const scene = billboardCollection._scene;
  1298. const context = frameState.context;
  1299. const globeTranslucent = frameState.globeTranslucencyState.translucent;
  1300. const depthTestAgainstTerrain =
  1301. defined(scene.globe) && scene.globe.depthTestAgainstTerrain;
  1302. // Only do manual depth test if the globe is opaque and writes depth
  1303. billboardCollection._shaderClampToGround =
  1304. context.depthTexture && !globeTranslucent && depthTestAgainstTerrain;
  1305. }
  1306. let i;
  1307. const writer =
  1308. vafWriters[attributeLocations.textureCoordinateBoundsOrLabelTranslate];
  1309. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  1310. //write _labelTranslate, used by depth testing in the vertex shader
  1311. let translateX = 0;
  1312. let translateY = 0;
  1313. if (defined(billboard._labelTranslate)) {
  1314. translateX = billboard._labelTranslate.x;
  1315. translateY = billboard._labelTranslate.y;
  1316. }
  1317. if (billboardCollection._instanced) {
  1318. i = billboard._index;
  1319. writer(i, translateX, translateY, 0.0, 0.0);
  1320. } else {
  1321. i = billboard._index * 4;
  1322. writer(i + 0, translateX, translateY, 0.0, 0.0);
  1323. writer(i + 1, translateX, translateY, 0.0, 0.0);
  1324. writer(i + 2, translateX, translateY, 0.0, 0.0);
  1325. writer(i + 3, translateX, translateY, 0.0, 0.0);
  1326. }
  1327. return;
  1328. }
  1329. //write texture coordinate bounds, used by depth testing in fragment shader
  1330. let minX = 0;
  1331. let minY = 0;
  1332. let width = 0;
  1333. let height = 0;
  1334. const index = billboard._imageIndex;
  1335. if (index !== -1) {
  1336. const imageRectangle = textureAtlasCoordinates[index];
  1337. //>>includeStart('debug', pragmas.debug);
  1338. if (!defined(imageRectangle)) {
  1339. throw new DeveloperError(`Invalid billboard image index: ${index}`);
  1340. }
  1341. //>>includeEnd('debug');
  1342. minX = imageRectangle.x;
  1343. minY = imageRectangle.y;
  1344. width = imageRectangle.width;
  1345. height = imageRectangle.height;
  1346. }
  1347. const maxX = minX + width;
  1348. const maxY = minY + height;
  1349. if (billboardCollection._instanced) {
  1350. i = billboard._index;
  1351. writer(i, minX, minY, maxX, maxY);
  1352. } else {
  1353. i = billboard._index * 4;
  1354. writer(i + 0, minX, minY, maxX, maxY);
  1355. writer(i + 1, minX, minY, maxX, maxY);
  1356. writer(i + 2, minX, minY, maxX, maxY);
  1357. writer(i + 3, minX, minY, maxX, maxY);
  1358. }
  1359. }
  1360. function writeBatchId(
  1361. billboardCollection,
  1362. frameState,
  1363. textureAtlasCoordinates,
  1364. vafWriters,
  1365. billboard
  1366. ) {
  1367. if (!defined(billboardCollection._batchTable)) {
  1368. return;
  1369. }
  1370. const writer = vafWriters[attributeLocations.a_batchId];
  1371. const id = billboard._batchIndex;
  1372. let i;
  1373. if (billboardCollection._instanced) {
  1374. i = billboard._index;
  1375. writer(i, id);
  1376. } else {
  1377. i = billboard._index * 4;
  1378. writer(i + 0, id);
  1379. writer(i + 1, id);
  1380. writer(i + 2, id);
  1381. writer(i + 3, id);
  1382. }
  1383. }
  1384. function writeSDF(
  1385. billboardCollection,
  1386. frameState,
  1387. textureAtlasCoordinates,
  1388. vafWriters,
  1389. billboard
  1390. ) {
  1391. if (!billboardCollection._sdf) {
  1392. return;
  1393. }
  1394. let i;
  1395. const writer = vafWriters[attributeLocations.sdf];
  1396. const outlineColor = billboard.outlineColor;
  1397. const outlineWidth = billboard.outlineWidth;
  1398. const red = Color.floatToByte(outlineColor.red);
  1399. const green = Color.floatToByte(outlineColor.green);
  1400. const blue = Color.floatToByte(outlineColor.blue);
  1401. const compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
  1402. // Compute the relative outline distance
  1403. const outlineDistance = outlineWidth / SDFSettings.RADIUS;
  1404. const compressed1 =
  1405. Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT16 +
  1406. Color.floatToByte(outlineDistance) * LEFT_SHIFT8;
  1407. if (billboardCollection._instanced) {
  1408. i = billboard._index;
  1409. writer(i, compressed0, compressed1);
  1410. } else {
  1411. i = billboard._index * 4;
  1412. writer(i + 0, compressed0 + LOWER_LEFT, compressed1);
  1413. writer(i + 1, compressed0 + LOWER_RIGHT, compressed1);
  1414. writer(i + 2, compressed0 + UPPER_RIGHT, compressed1);
  1415. writer(i + 3, compressed0 + UPPER_LEFT, compressed1);
  1416. }
  1417. }
  1418. function writeBillboard(
  1419. billboardCollection,
  1420. frameState,
  1421. textureAtlasCoordinates,
  1422. vafWriters,
  1423. billboard
  1424. ) {
  1425. writePositionScaleAndRotation(
  1426. billboardCollection,
  1427. frameState,
  1428. textureAtlasCoordinates,
  1429. vafWriters,
  1430. billboard
  1431. );
  1432. writeCompressedAttrib0(
  1433. billboardCollection,
  1434. frameState,
  1435. textureAtlasCoordinates,
  1436. vafWriters,
  1437. billboard
  1438. );
  1439. writeCompressedAttrib1(
  1440. billboardCollection,
  1441. frameState,
  1442. textureAtlasCoordinates,
  1443. vafWriters,
  1444. billboard
  1445. );
  1446. writeCompressedAttrib2(
  1447. billboardCollection,
  1448. frameState,
  1449. textureAtlasCoordinates,
  1450. vafWriters,
  1451. billboard
  1452. );
  1453. writeEyeOffset(
  1454. billboardCollection,
  1455. frameState,
  1456. textureAtlasCoordinates,
  1457. vafWriters,
  1458. billboard
  1459. );
  1460. writeScaleByDistance(
  1461. billboardCollection,
  1462. frameState,
  1463. textureAtlasCoordinates,
  1464. vafWriters,
  1465. billboard
  1466. );
  1467. writePixelOffsetScaleByDistance(
  1468. billboardCollection,
  1469. frameState,
  1470. textureAtlasCoordinates,
  1471. vafWriters,
  1472. billboard
  1473. );
  1474. writeCompressedAttribute3(
  1475. billboardCollection,
  1476. frameState,
  1477. textureAtlasCoordinates,
  1478. vafWriters,
  1479. billboard
  1480. );
  1481. writeTextureCoordinateBoundsOrLabelTranslate(
  1482. billboardCollection,
  1483. frameState,
  1484. textureAtlasCoordinates,
  1485. vafWriters,
  1486. billboard
  1487. );
  1488. writeBatchId(
  1489. billboardCollection,
  1490. frameState,
  1491. textureAtlasCoordinates,
  1492. vafWriters,
  1493. billboard
  1494. );
  1495. writeSDF(
  1496. billboardCollection,
  1497. frameState,
  1498. textureAtlasCoordinates,
  1499. vafWriters,
  1500. billboard
  1501. );
  1502. }
  1503. function recomputeActualPositions(
  1504. billboardCollection,
  1505. billboards,
  1506. length,
  1507. frameState,
  1508. modelMatrix,
  1509. recomputeBoundingVolume
  1510. ) {
  1511. let boundingVolume;
  1512. if (frameState.mode === SceneMode.SCENE3D) {
  1513. boundingVolume = billboardCollection._baseVolume;
  1514. billboardCollection._boundingVolumeDirty = true;
  1515. } else {
  1516. boundingVolume = billboardCollection._baseVolume2D;
  1517. }
  1518. const positions = [];
  1519. for (let i = 0; i < length; ++i) {
  1520. const billboard = billboards[i];
  1521. const position = billboard.position;
  1522. const actualPosition = Billboard._computeActualPosition(
  1523. billboard,
  1524. position,
  1525. frameState,
  1526. modelMatrix
  1527. );
  1528. if (defined(actualPosition)) {
  1529. billboard._setActualPosition(actualPosition);
  1530. if (recomputeBoundingVolume) {
  1531. positions.push(actualPosition);
  1532. } else {
  1533. BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume);
  1534. }
  1535. }
  1536. }
  1537. if (recomputeBoundingVolume) {
  1538. BoundingSphere.fromPoints(positions, boundingVolume);
  1539. }
  1540. }
  1541. function updateMode(billboardCollection, frameState) {
  1542. const mode = frameState.mode;
  1543. const billboards = billboardCollection._billboards;
  1544. const billboardsToUpdate = billboardCollection._billboardsToUpdate;
  1545. const modelMatrix = billboardCollection._modelMatrix;
  1546. if (
  1547. billboardCollection._createVertexArray ||
  1548. billboardCollection._mode !== mode ||
  1549. (mode !== SceneMode.SCENE3D &&
  1550. !Matrix4.equals(modelMatrix, billboardCollection.modelMatrix))
  1551. ) {
  1552. billboardCollection._mode = mode;
  1553. Matrix4.clone(billboardCollection.modelMatrix, modelMatrix);
  1554. billboardCollection._createVertexArray = true;
  1555. if (
  1556. mode === SceneMode.SCENE3D ||
  1557. mode === SceneMode.SCENE2D ||
  1558. mode === SceneMode.COLUMBUS_VIEW
  1559. ) {
  1560. recomputeActualPositions(
  1561. billboardCollection,
  1562. billboards,
  1563. billboards.length,
  1564. frameState,
  1565. modelMatrix,
  1566. true
  1567. );
  1568. }
  1569. } else if (mode === SceneMode.MORPHING) {
  1570. recomputeActualPositions(
  1571. billboardCollection,
  1572. billboards,
  1573. billboards.length,
  1574. frameState,
  1575. modelMatrix,
  1576. true
  1577. );
  1578. } else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
  1579. recomputeActualPositions(
  1580. billboardCollection,
  1581. billboardsToUpdate,
  1582. billboardCollection._billboardsToUpdateIndex,
  1583. frameState,
  1584. modelMatrix,
  1585. false
  1586. );
  1587. }
  1588. }
  1589. function updateBoundingVolume(collection, frameState, boundingVolume) {
  1590. let pixelScale = 1.0;
  1591. if (!collection._allSizedInMeters || collection._maxPixelOffset !== 0.0) {
  1592. pixelScale = frameState.camera.getPixelSize(
  1593. boundingVolume,
  1594. frameState.context.drawingBufferWidth,
  1595. frameState.context.drawingBufferHeight
  1596. );
  1597. }
  1598. let size = pixelScale * collection._maxScale * collection._maxSize * 2.0;
  1599. if (collection._allHorizontalCenter && collection._allVerticalCenter) {
  1600. size *= 0.5;
  1601. }
  1602. const offset =
  1603. pixelScale * collection._maxPixelOffset + collection._maxEyeOffset;
  1604. boundingVolume.radius += size + offset;
  1605. }
  1606. function createDebugCommand(billboardCollection, context) {
  1607. const fs =
  1608. "uniform sampler2D billboard_texture; \n" +
  1609. "in vec2 v_textureCoordinates; \n" +
  1610. "void main() \n" +
  1611. "{ \n" +
  1612. " out_FragColor = texture(billboard_texture, v_textureCoordinates); \n" +
  1613. "} \n";
  1614. const drawCommand = context.createViewportQuadCommand(fs, {
  1615. uniformMap: {
  1616. billboard_texture: function () {
  1617. return billboardCollection._textureAtlas.texture;
  1618. },
  1619. },
  1620. });
  1621. drawCommand.pass = Pass.OVERLAY;
  1622. return drawCommand;
  1623. }
  1624. const scratchWriterArray = [];
  1625. /**
  1626. * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
  1627. * get the draw commands needed to render this primitive.
  1628. * <p>
  1629. * Do not call this function directly. This is documented just to
  1630. * list the exceptions that may be propagated when the scene is rendered:
  1631. * </p>
  1632. *
  1633. * @exception {RuntimeError} image with id must be in the atlas.
  1634. */
  1635. BillboardCollection.prototype.update = function (frameState) {
  1636. removeBillboards(this);
  1637. if (!this.show) {
  1638. return;
  1639. }
  1640. let billboards = this._billboards;
  1641. let billboardsLength = billboards.length;
  1642. const context = frameState.context;
  1643. this._instanced = context.instancedArrays;
  1644. attributeLocations = this._instanced
  1645. ? attributeLocationsInstanced
  1646. : attributeLocationsBatched;
  1647. getIndexBuffer = this._instanced
  1648. ? getIndexBufferInstanced
  1649. : getIndexBufferBatched;
  1650. let textureAtlas = this._textureAtlas;
  1651. if (!defined(textureAtlas)) {
  1652. textureAtlas = this._textureAtlas = new TextureAtlas({
  1653. context: context,
  1654. });
  1655. for (let ii = 0; ii < billboardsLength; ++ii) {
  1656. billboards[ii]._loadImage();
  1657. }
  1658. }
  1659. const textureAtlasCoordinates = textureAtlas.textureCoordinates;
  1660. if (textureAtlasCoordinates.length === 0) {
  1661. // Can't write billboard vertices until we have texture coordinates
  1662. // provided by a texture atlas
  1663. return;
  1664. }
  1665. updateMode(this, frameState);
  1666. billboards = this._billboards;
  1667. billboardsLength = billboards.length;
  1668. const billboardsToUpdate = this._billboardsToUpdate;
  1669. const billboardsToUpdateLength = this._billboardsToUpdateIndex;
  1670. const properties = this._propertiesChanged;
  1671. const textureAtlasGUID = textureAtlas.guid;
  1672. const createVertexArray =
  1673. this._createVertexArray || this._textureAtlasGUID !== textureAtlasGUID;
  1674. this._textureAtlasGUID = textureAtlasGUID;
  1675. let vafWriters;
  1676. const pass = frameState.passes;
  1677. const picking = pass.pick;
  1678. // PERFORMANCE_IDEA: Round robin multiple buffers.
  1679. if (createVertexArray || (!picking && this.computeNewBuffersUsage())) {
  1680. this._createVertexArray = false;
  1681. for (let k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
  1682. properties[k] = 0;
  1683. }
  1684. this._vaf = this._vaf && this._vaf.destroy();
  1685. if (billboardsLength > 0) {
  1686. // PERFORMANCE_IDEA: Instead of creating a new one, resize like std::vector.
  1687. this._vaf = createVAF(
  1688. context,
  1689. billboardsLength,
  1690. this._buffersUsage,
  1691. this._instanced,
  1692. this._batchTable,
  1693. this._sdf
  1694. );
  1695. vafWriters = this._vaf.writers;
  1696. // Rewrite entire buffer if billboards were added or removed.
  1697. for (let i = 0; i < billboardsLength; ++i) {
  1698. const billboard = this._billboards[i];
  1699. billboard._dirty = false; // In case it needed an update.
  1700. writeBillboard(
  1701. this,
  1702. frameState,
  1703. textureAtlasCoordinates,
  1704. vafWriters,
  1705. billboard
  1706. );
  1707. }
  1708. // Different billboard collections share the same index buffer.
  1709. this._vaf.commit(getIndexBuffer(context));
  1710. }
  1711. this._billboardsToUpdateIndex = 0;
  1712. } else if (billboardsToUpdateLength > 0) {
  1713. // Billboards were modified, but none were added or removed.
  1714. const writers = scratchWriterArray;
  1715. writers.length = 0;
  1716. if (
  1717. properties[POSITION_INDEX] ||
  1718. properties[ROTATION_INDEX] ||
  1719. properties[SCALE_INDEX]
  1720. ) {
  1721. writers.push(writePositionScaleAndRotation);
  1722. }
  1723. if (
  1724. properties[IMAGE_INDEX_INDEX] ||
  1725. properties[PIXEL_OFFSET_INDEX] ||
  1726. properties[HORIZONTAL_ORIGIN_INDEX] ||
  1727. properties[VERTICAL_ORIGIN_INDEX] ||
  1728. properties[SHOW_INDEX]
  1729. ) {
  1730. writers.push(writeCompressedAttrib0);
  1731. if (this._instanced) {
  1732. writers.push(writeEyeOffset);
  1733. }
  1734. }
  1735. if (
  1736. properties[IMAGE_INDEX_INDEX] ||
  1737. properties[ALIGNED_AXIS_INDEX] ||
  1738. properties[TRANSLUCENCY_BY_DISTANCE_INDEX]
  1739. ) {
  1740. writers.push(writeCompressedAttrib1);
  1741. writers.push(writeCompressedAttrib2);
  1742. }
  1743. if (properties[IMAGE_INDEX_INDEX] || properties[COLOR_INDEX]) {
  1744. writers.push(writeCompressedAttrib2);
  1745. }
  1746. if (properties[EYE_OFFSET_INDEX]) {
  1747. writers.push(writeEyeOffset);
  1748. }
  1749. if (properties[SCALE_BY_DISTANCE_INDEX]) {
  1750. writers.push(writeScaleByDistance);
  1751. }
  1752. if (properties[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]) {
  1753. writers.push(writePixelOffsetScaleByDistance);
  1754. }
  1755. if (
  1756. properties[DISTANCE_DISPLAY_CONDITION_INDEX] ||
  1757. properties[DISABLE_DEPTH_DISTANCE] ||
  1758. properties[IMAGE_INDEX_INDEX] ||
  1759. properties[POSITION_INDEX]
  1760. ) {
  1761. writers.push(writeCompressedAttribute3);
  1762. }
  1763. if (properties[IMAGE_INDEX_INDEX] || properties[POSITION_INDEX]) {
  1764. writers.push(writeTextureCoordinateBoundsOrLabelTranslate);
  1765. }
  1766. if (properties[SDF_INDEX]) {
  1767. writers.push(writeSDF);
  1768. }
  1769. const numWriters = writers.length;
  1770. vafWriters = this._vaf.writers;
  1771. if (billboardsToUpdateLength / billboardsLength > 0.1) {
  1772. // If more than 10% of billboard change, rewrite the entire buffer.
  1773. // PERFORMANCE_IDEA: I totally made up 10% :).
  1774. for (let m = 0; m < billboardsToUpdateLength; ++m) {
  1775. const b = billboardsToUpdate[m];
  1776. b._dirty = false;
  1777. for (let n = 0; n < numWriters; ++n) {
  1778. writers[n](this, frameState, textureAtlasCoordinates, vafWriters, b);
  1779. }
  1780. }
  1781. this._vaf.commit(getIndexBuffer(context));
  1782. } else {
  1783. for (let h = 0; h < billboardsToUpdateLength; ++h) {
  1784. const bb = billboardsToUpdate[h];
  1785. bb._dirty = false;
  1786. for (let o = 0; o < numWriters; ++o) {
  1787. writers[o](this, frameState, textureAtlasCoordinates, vafWriters, bb);
  1788. }
  1789. if (this._instanced) {
  1790. this._vaf.subCommit(bb._index, 1);
  1791. } else {
  1792. this._vaf.subCommit(bb._index * 4, 4);
  1793. }
  1794. }
  1795. this._vaf.endSubCommits();
  1796. }
  1797. this._billboardsToUpdateIndex = 0;
  1798. }
  1799. // If the number of total billboards ever shrinks considerably
  1800. // Truncate billboardsToUpdate so that we free memory that we're
  1801. // not going to be using.
  1802. if (billboardsToUpdateLength > billboardsLength * 1.5) {
  1803. billboardsToUpdate.length = billboardsLength;
  1804. }
  1805. if (!defined(this._vaf) || !defined(this._vaf.va)) {
  1806. return;
  1807. }
  1808. if (this._boundingVolumeDirty) {
  1809. this._boundingVolumeDirty = false;
  1810. BoundingSphere.transform(
  1811. this._baseVolume,
  1812. this.modelMatrix,
  1813. this._baseVolumeWC
  1814. );
  1815. }
  1816. let boundingVolume;
  1817. let modelMatrix = Matrix4.IDENTITY;
  1818. if (frameState.mode === SceneMode.SCENE3D) {
  1819. modelMatrix = this.modelMatrix;
  1820. boundingVolume = BoundingSphere.clone(
  1821. this._baseVolumeWC,
  1822. this._boundingVolume
  1823. );
  1824. } else {
  1825. boundingVolume = BoundingSphere.clone(
  1826. this._baseVolume2D,
  1827. this._boundingVolume
  1828. );
  1829. }
  1830. updateBoundingVolume(this, frameState, boundingVolume);
  1831. const blendOptionChanged = this._blendOption !== this.blendOption;
  1832. this._blendOption = this.blendOption;
  1833. if (blendOptionChanged) {
  1834. if (
  1835. this._blendOption === BlendOption.OPAQUE ||
  1836. this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT
  1837. ) {
  1838. this._rsOpaque = RenderState.fromCache({
  1839. depthTest: {
  1840. enabled: true,
  1841. func: WebGLConstants.LESS,
  1842. },
  1843. depthMask: true,
  1844. });
  1845. } else {
  1846. this._rsOpaque = undefined;
  1847. }
  1848. // If OPAQUE_AND_TRANSLUCENT is in use, only the opaque pass gets the benefit of the depth buffer,
  1849. // not the translucent pass. Otherwise, if the TRANSLUCENT pass is on its own, it turns on
  1850. // a depthMask in lieu of full depth sorting (because it has opaque-ish fragments that look bad in OIT).
  1851. // When the TRANSLUCENT depth mask is in use, label backgrounds require the depth func to be LEQUAL.
  1852. const useTranslucentDepthMask =
  1853. this._blendOption === BlendOption.TRANSLUCENT;
  1854. if (
  1855. this._blendOption === BlendOption.TRANSLUCENT ||
  1856. this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT
  1857. ) {
  1858. this._rsTranslucent = RenderState.fromCache({
  1859. depthTest: {
  1860. enabled: true,
  1861. func: useTranslucentDepthMask
  1862. ? WebGLConstants.LEQUAL
  1863. : WebGLConstants.LESS,
  1864. },
  1865. depthMask: useTranslucentDepthMask,
  1866. blending: BlendingState.ALPHA_BLEND,
  1867. });
  1868. } else {
  1869. this._rsTranslucent = undefined;
  1870. }
  1871. }
  1872. this._shaderDisableDepthDistance =
  1873. this._shaderDisableDepthDistance ||
  1874. frameState.minimumDisableDepthTestDistance !== 0.0;
  1875. let vsSource;
  1876. let fsSource;
  1877. let vs;
  1878. let fs;
  1879. let vertDefines;
  1880. const supportVSTextureReads =
  1881. ContextLimits.maximumVertexTextureImageUnits > 0;
  1882. if (
  1883. blendOptionChanged ||
  1884. this._shaderRotation !== this._compiledShaderRotation ||
  1885. this._shaderAlignedAxis !== this._compiledShaderAlignedAxis ||
  1886. this._shaderScaleByDistance !== this._compiledShaderScaleByDistance ||
  1887. this._shaderTranslucencyByDistance !==
  1888. this._compiledShaderTranslucencyByDistance ||
  1889. this._shaderPixelOffsetScaleByDistance !==
  1890. this._compiledShaderPixelOffsetScaleByDistance ||
  1891. this._shaderDistanceDisplayCondition !==
  1892. this._compiledShaderDistanceDisplayCondition ||
  1893. this._shaderDisableDepthDistance !==
  1894. this._compiledShaderDisableDepthDistance ||
  1895. this._shaderClampToGround !== this._compiledShaderClampToGround ||
  1896. this._sdf !== this._compiledSDF
  1897. ) {
  1898. vsSource = BillboardCollectionVS;
  1899. fsSource = BillboardCollectionFS;
  1900. vertDefines = [];
  1901. if (defined(this._batchTable)) {
  1902. vertDefines.push("VECTOR_TILE");
  1903. vsSource = this._batchTable.getVertexShaderCallback(
  1904. false,
  1905. "a_batchId",
  1906. undefined
  1907. )(vsSource);
  1908. fsSource = this._batchTable.getFragmentShaderCallback(
  1909. false,
  1910. undefined
  1911. )(fsSource);
  1912. }
  1913. vs = new ShaderSource({
  1914. defines: vertDefines,
  1915. sources: [vsSource],
  1916. });
  1917. if (this._instanced) {
  1918. vs.defines.push("INSTANCED");
  1919. }
  1920. if (this._shaderRotation) {
  1921. vs.defines.push("ROTATION");
  1922. }
  1923. if (this._shaderAlignedAxis) {
  1924. vs.defines.push("ALIGNED_AXIS");
  1925. }
  1926. if (this._shaderScaleByDistance) {
  1927. vs.defines.push("EYE_DISTANCE_SCALING");
  1928. }
  1929. if (this._shaderTranslucencyByDistance) {
  1930. vs.defines.push("EYE_DISTANCE_TRANSLUCENCY");
  1931. }
  1932. if (this._shaderPixelOffsetScaleByDistance) {
  1933. vs.defines.push("EYE_DISTANCE_PIXEL_OFFSET");
  1934. }
  1935. if (this._shaderDistanceDisplayCondition) {
  1936. vs.defines.push("DISTANCE_DISPLAY_CONDITION");
  1937. }
  1938. if (this._shaderDisableDepthDistance) {
  1939. vs.defines.push("DISABLE_DEPTH_DISTANCE");
  1940. }
  1941. if (this._shaderClampToGround) {
  1942. if (supportVSTextureReads) {
  1943. vs.defines.push("VERTEX_DEPTH_CHECK");
  1944. } else {
  1945. vs.defines.push("FRAGMENT_DEPTH_CHECK");
  1946. }
  1947. }
  1948. const sdfEdge = 1.0 - SDFSettings.CUTOFF;
  1949. if (this._sdf) {
  1950. vs.defines.push("SDF");
  1951. }
  1952. const vectorFragDefine = defined(this._batchTable) ? "VECTOR_TILE" : "";
  1953. if (this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) {
  1954. fs = new ShaderSource({
  1955. defines: ["OPAQUE", vectorFragDefine],
  1956. sources: [fsSource],
  1957. });
  1958. if (this._shaderClampToGround) {
  1959. if (supportVSTextureReads) {
  1960. fs.defines.push("VERTEX_DEPTH_CHECK");
  1961. } else {
  1962. fs.defines.push("FRAGMENT_DEPTH_CHECK");
  1963. }
  1964. }
  1965. if (this._sdf) {
  1966. fs.defines.push("SDF");
  1967. fs.defines.push(`SDF_EDGE ${sdfEdge}`);
  1968. }
  1969. this._sp = ShaderProgram.replaceCache({
  1970. context: context,
  1971. shaderProgram: this._sp,
  1972. vertexShaderSource: vs,
  1973. fragmentShaderSource: fs,
  1974. attributeLocations: attributeLocations,
  1975. });
  1976. fs = new ShaderSource({
  1977. defines: ["TRANSLUCENT", vectorFragDefine],
  1978. sources: [fsSource],
  1979. });
  1980. if (this._shaderClampToGround) {
  1981. if (supportVSTextureReads) {
  1982. fs.defines.push("VERTEX_DEPTH_CHECK");
  1983. } else {
  1984. fs.defines.push("FRAGMENT_DEPTH_CHECK");
  1985. }
  1986. }
  1987. if (this._sdf) {
  1988. fs.defines.push("SDF");
  1989. fs.defines.push(`SDF_EDGE ${sdfEdge}`);
  1990. }
  1991. this._spTranslucent = ShaderProgram.replaceCache({
  1992. context: context,
  1993. shaderProgram: this._spTranslucent,
  1994. vertexShaderSource: vs,
  1995. fragmentShaderSource: fs,
  1996. attributeLocations: attributeLocations,
  1997. });
  1998. }
  1999. if (this._blendOption === BlendOption.OPAQUE) {
  2000. fs = new ShaderSource({
  2001. defines: [vectorFragDefine],
  2002. sources: [fsSource],
  2003. });
  2004. if (this._shaderClampToGround) {
  2005. if (supportVSTextureReads) {
  2006. fs.defines.push("VERTEX_DEPTH_CHECK");
  2007. } else {
  2008. fs.defines.push("FRAGMENT_DEPTH_CHECK");
  2009. }
  2010. }
  2011. if (this._sdf) {
  2012. fs.defines.push("SDF");
  2013. fs.defines.push(`SDF_EDGE ${sdfEdge}`);
  2014. }
  2015. this._sp = ShaderProgram.replaceCache({
  2016. context: context,
  2017. shaderProgram: this._sp,
  2018. vertexShaderSource: vs,
  2019. fragmentShaderSource: fs,
  2020. attributeLocations: attributeLocations,
  2021. });
  2022. }
  2023. if (this._blendOption === BlendOption.TRANSLUCENT) {
  2024. fs = new ShaderSource({
  2025. defines: [vectorFragDefine],
  2026. sources: [fsSource],
  2027. });
  2028. if (this._shaderClampToGround) {
  2029. if (supportVSTextureReads) {
  2030. fs.defines.push("VERTEX_DEPTH_CHECK");
  2031. } else {
  2032. fs.defines.push("FRAGMENT_DEPTH_CHECK");
  2033. }
  2034. }
  2035. if (this._sdf) {
  2036. fs.defines.push("SDF");
  2037. fs.defines.push(`SDF_EDGE ${sdfEdge}`);
  2038. }
  2039. this._spTranslucent = ShaderProgram.replaceCache({
  2040. context: context,
  2041. shaderProgram: this._spTranslucent,
  2042. vertexShaderSource: vs,
  2043. fragmentShaderSource: fs,
  2044. attributeLocations: attributeLocations,
  2045. });
  2046. }
  2047. this._compiledShaderRotation = this._shaderRotation;
  2048. this._compiledShaderAlignedAxis = this._shaderAlignedAxis;
  2049. this._compiledShaderScaleByDistance = this._shaderScaleByDistance;
  2050. this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance;
  2051. this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance;
  2052. this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition;
  2053. this._compiledShaderDisableDepthDistance = this._shaderDisableDepthDistance;
  2054. this._compiledShaderClampToGround = this._shaderClampToGround;
  2055. this._compiledSDF = this._sdf;
  2056. }
  2057. const commandList = frameState.commandList;
  2058. if (pass.render || pass.pick) {
  2059. const colorList = this._colorCommands;
  2060. const opaque = this._blendOption === BlendOption.OPAQUE;
  2061. const opaqueAndTranslucent =
  2062. this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT;
  2063. const va = this._vaf.va;
  2064. const vaLength = va.length;
  2065. let uniforms = this._uniforms;
  2066. let pickId;
  2067. if (defined(this._batchTable)) {
  2068. uniforms = this._batchTable.getUniformMapCallback()(uniforms);
  2069. pickId = this._batchTable.getPickId();
  2070. } else {
  2071. pickId = "v_pickColor";
  2072. }
  2073. colorList.length = vaLength;
  2074. const totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength;
  2075. for (let j = 0; j < totalLength; ++j) {
  2076. let command = colorList[j];
  2077. if (!defined(command)) {
  2078. command = colorList[j] = new DrawCommand();
  2079. }
  2080. const opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0);
  2081. command.pass =
  2082. opaqueCommand || !opaqueAndTranslucent ? Pass.OPAQUE : Pass.TRANSLUCENT;
  2083. command.owner = this;
  2084. const index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j;
  2085. command.boundingVolume = boundingVolume;
  2086. command.modelMatrix = modelMatrix;
  2087. command.count = va[index].indicesCount;
  2088. command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent;
  2089. command.uniformMap = uniforms;
  2090. command.vertexArray = va[index].va;
  2091. command.renderState = opaqueCommand
  2092. ? this._rsOpaque
  2093. : this._rsTranslucent;
  2094. command.debugShowBoundingVolume = this.debugShowBoundingVolume;
  2095. command.pickId = pickId;
  2096. if (this._instanced) {
  2097. command.count = 6;
  2098. command.instanceCount = billboardsLength;
  2099. }
  2100. commandList.push(command);
  2101. }
  2102. if (this.debugShowTextureAtlas) {
  2103. if (!defined(this.debugCommand)) {
  2104. this.debugCommand = createDebugCommand(this, frameState.context);
  2105. }
  2106. commandList.push(this.debugCommand);
  2107. }
  2108. }
  2109. };
  2110. /**
  2111. * Returns true if this object was destroyed; otherwise, false.
  2112. * <br /><br />
  2113. * If this object was destroyed, it should not be used; calling any function other than
  2114. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  2115. *
  2116. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  2117. *
  2118. * @see BillboardCollection#destroy
  2119. */
  2120. BillboardCollection.prototype.isDestroyed = function () {
  2121. return false;
  2122. };
  2123. /**
  2124. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  2125. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  2126. * <br /><br />
  2127. * Once an object is destroyed, it should not be used; calling any function other than
  2128. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  2129. * assign the return value (<code>undefined</code>) to the object as done in the example.
  2130. *
  2131. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  2132. *
  2133. *
  2134. * @example
  2135. * billboards = billboards && billboards.destroy();
  2136. *
  2137. * @see BillboardCollection#isDestroyed
  2138. */
  2139. BillboardCollection.prototype.destroy = function () {
  2140. if (defined(this._removeCallbackFunc)) {
  2141. this._removeCallbackFunc();
  2142. this._removeCallbackFunc = undefined;
  2143. }
  2144. this._textureAtlas =
  2145. this._destroyTextureAtlas &&
  2146. this._textureAtlas &&
  2147. this._textureAtlas.destroy();
  2148. this._sp = this._sp && this._sp.destroy();
  2149. this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy();
  2150. this._vaf = this._vaf && this._vaf.destroy();
  2151. destroyBillboards(this._billboards);
  2152. return destroyObject(this);
  2153. };
  2154. export default BillboardCollection;