智慧水务管理系统 - 精河县供水工程综合管理平台

BillboardCollection.js 67KB

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