Billboard.js 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535
  1. import BoundingRectangle from "../Core/BoundingRectangle.js";
  2. import Cartesian2 from "../Core/Cartesian2.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Cartesian4 from "../Core/Cartesian4.js";
  5. import Cartographic from "../Core/Cartographic.js";
  6. import Color from "../Core/Color.js";
  7. import createGuid from "../Core/createGuid.js";
  8. import defaultValue from "../Core/defaultValue.js";
  9. import defined from "../Core/defined.js";
  10. import DeveloperError from "../Core/DeveloperError.js";
  11. import DistanceDisplayCondition from "../Core/DistanceDisplayCondition.js";
  12. import Matrix4 from "../Core/Matrix4.js";
  13. import NearFarScalar from "../Core/NearFarScalar.js";
  14. import Resource from "../Core/Resource.js";
  15. import HeightReference from "./HeightReference.js";
  16. import HorizontalOrigin from "./HorizontalOrigin.js";
  17. import SceneMode from "./SceneMode.js";
  18. import SceneTransforms from "./SceneTransforms.js";
  19. import VerticalOrigin from "./VerticalOrigin.js";
  20. /**
  21. * A viewport-aligned image positioned in the 3D scene, that is created
  22. * and rendered using a {@link BillboardCollection}. A billboard is created and its initial
  23. * properties are set by calling {@link BillboardCollection#add}.
  24. * <br /><br />
  25. * <div align='center'>
  26. * <img src='Images/Billboard.png' width='400' height='300' /><br />
  27. * Example billboards
  28. * </div>
  29. *
  30. * @alias Billboard
  31. *
  32. * @performance Reading a property, e.g., {@link Billboard#show}, is constant time.
  33. * Assigning to a property is constant time but results in
  34. * CPU to GPU traffic when {@link BillboardCollection#update} is called. The per-billboard traffic is
  35. * the same regardless of how many properties were updated. If most billboards in a collection need to be
  36. * updated, it may be more efficient to clear the collection with {@link BillboardCollection#removeAll}
  37. * and add new billboards instead of modifying each one.
  38. *
  39. * @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near
  40. * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
  41. * @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near
  42. * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
  43. *
  44. * @see BillboardCollection
  45. * @see BillboardCollection#add
  46. * @see Label
  47. *
  48. * @internalConstructor
  49. * @class
  50. *
  51. * @demo {@link https://sandcastle.cesium.com/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
  52. */
  53. function Billboard(options, billboardCollection) {
  54. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  55. //>>includeStart('debug', pragmas.debug);
  56. if (
  57. defined(options.disableDepthTestDistance) &&
  58. options.disableDepthTestDistance < 0.0
  59. ) {
  60. throw new DeveloperError(
  61. "disableDepthTestDistance must be greater than or equal to 0.0."
  62. );
  63. }
  64. //>>includeEnd('debug');
  65. var translucencyByDistance = options.translucencyByDistance;
  66. var pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance;
  67. var scaleByDistance = options.scaleByDistance;
  68. var distanceDisplayCondition = options.distanceDisplayCondition;
  69. if (defined(translucencyByDistance)) {
  70. //>>includeStart('debug', pragmas.debug);
  71. if (translucencyByDistance.far <= translucencyByDistance.near) {
  72. throw new DeveloperError(
  73. "translucencyByDistance.far must be greater than translucencyByDistance.near."
  74. );
  75. }
  76. //>>includeEnd('debug');
  77. translucencyByDistance = NearFarScalar.clone(translucencyByDistance);
  78. }
  79. if (defined(pixelOffsetScaleByDistance)) {
  80. //>>includeStart('debug', pragmas.debug);
  81. if (pixelOffsetScaleByDistance.far <= pixelOffsetScaleByDistance.near) {
  82. throw new DeveloperError(
  83. "pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near."
  84. );
  85. }
  86. //>>includeEnd('debug');
  87. pixelOffsetScaleByDistance = NearFarScalar.clone(
  88. pixelOffsetScaleByDistance
  89. );
  90. }
  91. if (defined(scaleByDistance)) {
  92. //>>includeStart('debug', pragmas.debug);
  93. if (scaleByDistance.far <= scaleByDistance.near) {
  94. throw new DeveloperError(
  95. "scaleByDistance.far must be greater than scaleByDistance.near."
  96. );
  97. }
  98. //>>includeEnd('debug');
  99. scaleByDistance = NearFarScalar.clone(scaleByDistance);
  100. }
  101. if (defined(distanceDisplayCondition)) {
  102. //>>includeStart('debug', pragmas.debug);
  103. if (distanceDisplayCondition.far <= distanceDisplayCondition.near) {
  104. throw new DeveloperError(
  105. "distanceDisplayCondition.far must be greater than distanceDisplayCondition.near."
  106. );
  107. }
  108. //>>includeEnd('debug');
  109. distanceDisplayCondition = DistanceDisplayCondition.clone(
  110. distanceDisplayCondition
  111. );
  112. }
  113. this._show = defaultValue(options.show, true);
  114. this._position = Cartesian3.clone(
  115. defaultValue(options.position, Cartesian3.ZERO)
  116. );
  117. this._actualPosition = Cartesian3.clone(this._position); // For columbus view and 2D
  118. this._pixelOffset = Cartesian2.clone(
  119. defaultValue(options.pixelOffset, Cartesian2.ZERO)
  120. );
  121. this._translate = new Cartesian2(0.0, 0.0); // used by labels for glyph vertex translation
  122. this._eyeOffset = Cartesian3.clone(
  123. defaultValue(options.eyeOffset, Cartesian3.ZERO)
  124. );
  125. this._heightReference = defaultValue(
  126. options.heightReference,
  127. HeightReference.NONE
  128. );
  129. this._verticalOrigin = defaultValue(
  130. options.verticalOrigin,
  131. VerticalOrigin.CENTER
  132. );
  133. this._horizontalOrigin = defaultValue(
  134. options.horizontalOrigin,
  135. HorizontalOrigin.CENTER
  136. );
  137. this._scale = defaultValue(options.scale, 1.0);
  138. this._color = Color.clone(defaultValue(options.color, Color.WHITE));
  139. this._rotation = defaultValue(options.rotation, 0.0);
  140. this._alignedAxis = Cartesian3.clone(
  141. defaultValue(options.alignedAxis, Cartesian3.ZERO)
  142. );
  143. this._width = options.width;
  144. this._height = options.height;
  145. this._scaleByDistance = scaleByDistance;
  146. this._translucencyByDistance = translucencyByDistance;
  147. this._pixelOffsetScaleByDistance = pixelOffsetScaleByDistance;
  148. this._sizeInMeters = defaultValue(options.sizeInMeters, false);
  149. this._distanceDisplayCondition = distanceDisplayCondition;
  150. this._disableDepthTestDistance = options.disableDepthTestDistance;
  151. this._id = options.id;
  152. this._collection = defaultValue(options.collection, billboardCollection);
  153. this._pickId = undefined;
  154. this._pickPrimitive = defaultValue(options._pickPrimitive, this);
  155. this._billboardCollection = billboardCollection;
  156. this._dirty = false;
  157. this._index = -1; //Used only by BillboardCollection
  158. this._batchIndex = undefined; // Used only by Vector3DTilePoints and BillboardCollection
  159. this._imageIndex = -1;
  160. this._imageIndexPromise = undefined;
  161. this._imageId = undefined;
  162. this._image = undefined;
  163. this._imageSubRegion = undefined;
  164. this._imageWidth = undefined;
  165. this._imageHeight = undefined;
  166. this._labelDimensions = undefined;
  167. this._labelHorizontalOrigin = undefined;
  168. this._labelTranslate = undefined;
  169. var image = options.image;
  170. var imageId = options.imageId;
  171. if (defined(image)) {
  172. if (!defined(imageId)) {
  173. if (typeof image === "string") {
  174. imageId = image;
  175. } else if (defined(image.src)) {
  176. imageId = image.src;
  177. } else {
  178. imageId = createGuid();
  179. }
  180. }
  181. this._imageId = imageId;
  182. this._image = image;
  183. }
  184. if (defined(options.imageSubRegion)) {
  185. this._imageId = imageId;
  186. this._imageSubRegion = options.imageSubRegion;
  187. }
  188. if (defined(this._billboardCollection._textureAtlas)) {
  189. this._loadImage();
  190. }
  191. this._actualClampedPosition = undefined;
  192. this._removeCallbackFunc = undefined;
  193. this._mode = SceneMode.SCENE3D;
  194. this._clusterShow = true;
  195. this._outlineColor = Color.clone(
  196. defaultValue(options.outlineColor, Color.BLACK)
  197. );
  198. this._outlineWidth = defaultValue(options.outlineWidth, 0.0);
  199. this._updateClamping();
  200. }
  201. var SHOW_INDEX = (Billboard.SHOW_INDEX = 0);
  202. var POSITION_INDEX = (Billboard.POSITION_INDEX = 1);
  203. var PIXEL_OFFSET_INDEX = (Billboard.PIXEL_OFFSET_INDEX = 2);
  204. var EYE_OFFSET_INDEX = (Billboard.EYE_OFFSET_INDEX = 3);
  205. var HORIZONTAL_ORIGIN_INDEX = (Billboard.HORIZONTAL_ORIGIN_INDEX = 4);
  206. var VERTICAL_ORIGIN_INDEX = (Billboard.VERTICAL_ORIGIN_INDEX = 5);
  207. var SCALE_INDEX = (Billboard.SCALE_INDEX = 6);
  208. var IMAGE_INDEX_INDEX = (Billboard.IMAGE_INDEX_INDEX = 7);
  209. var COLOR_INDEX = (Billboard.COLOR_INDEX = 8);
  210. var ROTATION_INDEX = (Billboard.ROTATION_INDEX = 9);
  211. var ALIGNED_AXIS_INDEX = (Billboard.ALIGNED_AXIS_INDEX = 10);
  212. var SCALE_BY_DISTANCE_INDEX = (Billboard.SCALE_BY_DISTANCE_INDEX = 11);
  213. var TRANSLUCENCY_BY_DISTANCE_INDEX = (Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX = 12);
  214. var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = (Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = 13);
  215. var DISTANCE_DISPLAY_CONDITION = (Billboard.DISTANCE_DISPLAY_CONDITION = 14);
  216. var DISABLE_DEPTH_DISTANCE = (Billboard.DISABLE_DEPTH_DISTANCE = 15);
  217. Billboard.TEXTURE_COORDINATE_BOUNDS = 16;
  218. var SDF_INDEX = (Billboard.SDF_INDEX = 17);
  219. Billboard.NUMBER_OF_PROPERTIES = 18;
  220. function makeDirty(billboard, propertyChanged) {
  221. var billboardCollection = billboard._billboardCollection;
  222. if (defined(billboardCollection)) {
  223. billboardCollection._updateBillboard(billboard, propertyChanged);
  224. billboard._dirty = true;
  225. }
  226. }
  227. Object.defineProperties(Billboard.prototype, {
  228. /**
  229. * Determines if this billboard will be shown. Use this to hide or show a billboard, instead
  230. * of removing it and re-adding it to the collection.
  231. * @memberof Billboard.prototype
  232. * @type {Boolean}
  233. * @default true
  234. */
  235. show: {
  236. get: function () {
  237. return this._show;
  238. },
  239. set: function (value) {
  240. //>>includeStart('debug', pragmas.debug);
  241. if (!defined(value)) {
  242. throw new DeveloperError("value is required.");
  243. }
  244. //>>includeEnd('debug');
  245. if (this._show !== value) {
  246. this._show = value;
  247. makeDirty(this, SHOW_INDEX);
  248. }
  249. },
  250. },
  251. /**
  252. * Gets or sets the Cartesian position of this billboard.
  253. * @memberof Billboard.prototype
  254. * @type {Cartesian3}
  255. */
  256. position: {
  257. get: function () {
  258. return this._position;
  259. },
  260. set: function (value) {
  261. //>>includeStart('debug', pragmas.debug)
  262. if (!defined(value)) {
  263. throw new DeveloperError("value is required.");
  264. }
  265. //>>includeEnd('debug');
  266. var position = this._position;
  267. if (!Cartesian3.equals(position, value)) {
  268. Cartesian3.clone(value, position);
  269. Cartesian3.clone(value, this._actualPosition);
  270. this._updateClamping();
  271. makeDirty(this, POSITION_INDEX);
  272. }
  273. },
  274. },
  275. /**
  276. * Gets or sets the height reference of this billboard.
  277. * @memberof Billboard.prototype
  278. * @type {HeightReference}
  279. * @default HeightReference.NONE
  280. */
  281. heightReference: {
  282. get: function () {
  283. return this._heightReference;
  284. },
  285. set: function (value) {
  286. //>>includeStart('debug', pragmas.debug)
  287. if (!defined(value)) {
  288. throw new DeveloperError("value is required.");
  289. }
  290. //>>includeEnd('debug');
  291. var heightReference = this._heightReference;
  292. if (value !== heightReference) {
  293. this._heightReference = value;
  294. this._updateClamping();
  295. makeDirty(this, POSITION_INDEX);
  296. }
  297. },
  298. },
  299. /**
  300. * Gets or sets the pixel offset in screen space from the origin of this billboard. This is commonly used
  301. * to align multiple billboards and labels at the same position, e.g., an image and text. The
  302. * screen space origin is the top, left corner of the canvas; <code>x</code> increases from
  303. * left to right, and <code>y</code> increases from top to bottom.
  304. * <br /><br />
  305. * <div align='center'>
  306. * <table border='0' cellpadding='5'><tr>
  307. * <td align='center'><code>default</code><br/><img src='Images/Billboard.setPixelOffset.default.png' width='250' height='188' /></td>
  308. * <td align='center'><code>b.pixeloffset = new Cartesian2(50, 25);</code><br/><img src='Images/Billboard.setPixelOffset.x50y-25.png' width='250' height='188' /></td>
  309. * </tr></table>
  310. * The billboard's origin is indicated by the yellow point.
  311. * </div>
  312. * @memberof Billboard.prototype
  313. * @type {Cartesian2}
  314. */
  315. pixelOffset: {
  316. get: function () {
  317. return this._pixelOffset;
  318. },
  319. set: function (value) {
  320. //>>includeStart('debug', pragmas.debug);
  321. if (!defined(value)) {
  322. throw new DeveloperError("value is required.");
  323. }
  324. //>>includeEnd('debug');
  325. var pixelOffset = this._pixelOffset;
  326. if (!Cartesian2.equals(pixelOffset, value)) {
  327. Cartesian2.clone(value, pixelOffset);
  328. makeDirty(this, PIXEL_OFFSET_INDEX);
  329. }
  330. },
  331. },
  332. /**
  333. * Gets or sets near and far scaling properties of a Billboard based on the billboard's distance from the camera.
  334. * A billboard's scale will interpolate between the {@link NearFarScalar#nearValue} and
  335. * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds
  336. * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
  337. * Outside of these ranges the billboard's scale remains clamped to the nearest bound. If undefined,
  338. * scaleByDistance will be disabled.
  339. * @memberof Billboard.prototype
  340. * @type {NearFarScalar}
  341. *
  342. * @example
  343. * // Example 1.
  344. * // Set a billboard's scaleByDistance to scale by 1.5 when the
  345. * // camera is 1500 meters from the billboard and disappear as
  346. * // the camera distance approaches 8.0e6 meters.
  347. * b.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0);
  348. *
  349. * @example
  350. * // Example 2.
  351. * // disable scaling by distance
  352. * b.scaleByDistance = undefined;
  353. */
  354. scaleByDistance: {
  355. get: function () {
  356. return this._scaleByDistance;
  357. },
  358. set: function (value) {
  359. //>>includeStart('debug', pragmas.debug);
  360. if (defined(value) && value.far <= value.near) {
  361. throw new DeveloperError(
  362. "far distance must be greater than near distance."
  363. );
  364. }
  365. //>>includeEnd('debug');
  366. var scaleByDistance = this._scaleByDistance;
  367. if (!NearFarScalar.equals(scaleByDistance, value)) {
  368. this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance);
  369. makeDirty(this, SCALE_BY_DISTANCE_INDEX);
  370. }
  371. },
  372. },
  373. /**
  374. * Gets or sets near and far translucency properties of a Billboard based on the billboard's distance from the camera.
  375. * A billboard's translucency will interpolate between the {@link NearFarScalar#nearValue} and
  376. * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds
  377. * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
  378. * Outside of these ranges the billboard's translucency remains clamped to the nearest bound. If undefined,
  379. * translucencyByDistance will be disabled.
  380. * @memberof Billboard.prototype
  381. * @type {NearFarScalar}
  382. *
  383. * @example
  384. * // Example 1.
  385. * // Set a billboard's translucency to 1.0 when the
  386. * // camera is 1500 meters from the billboard and disappear as
  387. * // the camera distance approaches 8.0e6 meters.
  388. * b.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
  389. *
  390. * @example
  391. * // Example 2.
  392. * // disable translucency by distance
  393. * b.translucencyByDistance = undefined;
  394. */
  395. translucencyByDistance: {
  396. get: function () {
  397. return this._translucencyByDistance;
  398. },
  399. set: function (value) {
  400. //>>includeStart('debug', pragmas.debug);
  401. if (defined(value) && value.far <= value.near) {
  402. throw new DeveloperError(
  403. "far distance must be greater than near distance."
  404. );
  405. }
  406. //>>includeEnd('debug');
  407. var translucencyByDistance = this._translucencyByDistance;
  408. if (!NearFarScalar.equals(translucencyByDistance, value)) {
  409. this._translucencyByDistance = NearFarScalar.clone(
  410. value,
  411. translucencyByDistance
  412. );
  413. makeDirty(this, TRANSLUCENCY_BY_DISTANCE_INDEX);
  414. }
  415. },
  416. },
  417. /**
  418. * Gets or sets near and far pixel offset scaling properties of a Billboard based on the billboard's distance from the camera.
  419. * A billboard's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and
  420. * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds
  421. * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
  422. * Outside of these ranges the billboard's pixel offset scale remains clamped to the nearest bound. If undefined,
  423. * pixelOffsetScaleByDistance will be disabled.
  424. * @memberof Billboard.prototype
  425. * @type {NearFarScalar}
  426. *
  427. * @example
  428. * // Example 1.
  429. * // Set a billboard's pixel offset scale to 0.0 when the
  430. * // camera is 1500 meters from the billboard and scale pixel offset to 10.0 pixels
  431. * // in the y direction the camera distance approaches 8.0e6 meters.
  432. * b.pixelOffset = new Cesium.Cartesian2(0.0, 1.0);
  433. * b.pixelOffsetScaleByDistance = new Cesium.NearFarScalar(1.5e2, 0.0, 8.0e6, 10.0);
  434. *
  435. * @example
  436. * // Example 2.
  437. * // disable pixel offset by distance
  438. * b.pixelOffsetScaleByDistance = undefined;
  439. */
  440. pixelOffsetScaleByDistance: {
  441. get: function () {
  442. return this._pixelOffsetScaleByDistance;
  443. },
  444. set: function (value) {
  445. //>>includeStart('debug', pragmas.debug);
  446. if (defined(value) && value.far <= value.near) {
  447. throw new DeveloperError(
  448. "far distance must be greater than near distance."
  449. );
  450. }
  451. //>>includeEnd('debug');
  452. var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
  453. if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) {
  454. this._pixelOffsetScaleByDistance = NearFarScalar.clone(
  455. value,
  456. pixelOffsetScaleByDistance
  457. );
  458. makeDirty(this, PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX);
  459. }
  460. },
  461. },
  462. /**
  463. * Gets or sets the 3D Cartesian offset applied to this billboard in eye coordinates. Eye coordinates is a left-handed
  464. * coordinate system, where <code>x</code> points towards the viewer's right, <code>y</code> points up, and
  465. * <code>z</code> points into the screen. Eye coordinates use the same scale as world and model coordinates,
  466. * which is typically meters.
  467. * <br /><br />
  468. * An eye offset is commonly used to arrange multiple billboards or objects at the same position, e.g., to
  469. * arrange a billboard above its corresponding 3D model.
  470. * <br /><br />
  471. * Below, the billboard is positioned at the center of the Earth but an eye offset makes it always
  472. * appear on top of the Earth regardless of the viewer's or Earth's orientation.
  473. * <br /><br />
  474. * <div align='center'>
  475. * <table border='0' cellpadding='5'><tr>
  476. * <td align='center'><img src='Images/Billboard.setEyeOffset.one.png' width='250' height='188' /></td>
  477. * <td align='center'><img src='Images/Billboard.setEyeOffset.two.png' width='250' height='188' /></td>
  478. * </tr></table>
  479. * <code>b.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);</code><br /><br />
  480. * </div>
  481. * @memberof Billboard.prototype
  482. * @type {Cartesian3}
  483. */
  484. eyeOffset: {
  485. get: function () {
  486. return this._eyeOffset;
  487. },
  488. set: function (value) {
  489. //>>includeStart('debug', pragmas.debug);
  490. if (!defined(value)) {
  491. throw new DeveloperError("value is required.");
  492. }
  493. //>>includeEnd('debug');
  494. var eyeOffset = this._eyeOffset;
  495. if (!Cartesian3.equals(eyeOffset, value)) {
  496. Cartesian3.clone(value, eyeOffset);
  497. makeDirty(this, EYE_OFFSET_INDEX);
  498. }
  499. },
  500. },
  501. /**
  502. * Gets or sets the horizontal origin of this billboard, which determines if the billboard is
  503. * to the left, center, or right of its anchor position.
  504. * <br /><br />
  505. * <div align='center'>
  506. * <img src='Images/Billboard.setHorizontalOrigin.png' width='648' height='196' /><br />
  507. * </div>
  508. * @memberof Billboard.prototype
  509. * @type {HorizontalOrigin}
  510. * @example
  511. * // Use a bottom, left origin
  512. * b.horizontalOrigin = Cesium.HorizontalOrigin.LEFT;
  513. * b.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
  514. */
  515. horizontalOrigin: {
  516. get: function () {
  517. return this._horizontalOrigin;
  518. },
  519. set: function (value) {
  520. //>>includeStart('debug', pragmas.debug);
  521. if (!defined(value)) {
  522. throw new DeveloperError("value is required.");
  523. }
  524. //>>includeEnd('debug');
  525. if (this._horizontalOrigin !== value) {
  526. this._horizontalOrigin = value;
  527. makeDirty(this, HORIZONTAL_ORIGIN_INDEX);
  528. }
  529. },
  530. },
  531. /**
  532. * Gets or sets the vertical origin of this billboard, which determines if the billboard is
  533. * to the above, below, or at the center of its anchor position.
  534. * <br /><br />
  535. * <div align='center'>
  536. * <img src='Images/Billboard.setVerticalOrigin.png' width='695' height='175' /><br />
  537. * </div>
  538. * @memberof Billboard.prototype
  539. * @type {VerticalOrigin}
  540. * @example
  541. * // Use a bottom, left origin
  542. * b.horizontalOrigin = Cesium.HorizontalOrigin.LEFT;
  543. * b.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
  544. */
  545. verticalOrigin: {
  546. get: function () {
  547. return this._verticalOrigin;
  548. },
  549. set: function (value) {
  550. //>>includeStart('debug', pragmas.debug);
  551. if (!defined(value)) {
  552. throw new DeveloperError("value is required.");
  553. }
  554. //>>includeEnd('debug');
  555. if (this._verticalOrigin !== value) {
  556. this._verticalOrigin = value;
  557. makeDirty(this, VERTICAL_ORIGIN_INDEX);
  558. }
  559. },
  560. },
  561. /**
  562. * Gets or sets the uniform scale that is multiplied with the billboard's image size in pixels.
  563. * A scale of <code>1.0</code> does not change the size of the billboard; a scale greater than
  564. * <code>1.0</code> enlarges the billboard; a positive scale less than <code>1.0</code> shrinks
  565. * the billboard.
  566. * <br /><br />
  567. * <div align='center'>
  568. * <img src='Images/Billboard.setScale.png' width='400' height='300' /><br/>
  569. * From left to right in the above image, the scales are <code>0.5</code>, <code>1.0</code>,
  570. * and <code>2.0</code>.
  571. * </div>
  572. * @memberof Billboard.prototype
  573. * @type {Number}
  574. */
  575. scale: {
  576. get: function () {
  577. return this._scale;
  578. },
  579. set: function (value) {
  580. //>>includeStart('debug', pragmas.debug);
  581. if (!defined(value)) {
  582. throw new DeveloperError("value is required.");
  583. }
  584. //>>includeEnd('debug');
  585. if (this._scale !== value) {
  586. this._scale = value;
  587. makeDirty(this, SCALE_INDEX);
  588. }
  589. },
  590. },
  591. /**
  592. * Gets or sets the color that is multiplied with the billboard's texture. This has two common use cases. First,
  593. * the same white texture may be used by many different billboards, each with a different color, to create
  594. * colored billboards. Second, the color's alpha component can be used to make the billboard translucent as shown below.
  595. * An alpha of <code>0.0</code> makes the billboard transparent, and <code>1.0</code> makes the billboard opaque.
  596. * <br /><br />
  597. * <div align='center'>
  598. * <table border='0' cellpadding='5'><tr>
  599. * <td align='center'><code>default</code><br/><img src='Images/Billboard.setColor.Alpha255.png' width='250' height='188' /></td>
  600. * <td align='center'><code>alpha : 0.5</code><br/><img src='Images/Billboard.setColor.Alpha127.png' width='250' height='188' /></td>
  601. * </tr></table>
  602. * </div>
  603. * <br />
  604. * The red, green, blue, and alpha values are indicated by <code>value</code>'s <code>red</code>, <code>green</code>,
  605. * <code>blue</code>, and <code>alpha</code> properties as shown in Example 1. These components range from <code>0.0</code>
  606. * (no intensity) to <code>1.0</code> (full intensity).
  607. * @memberof Billboard.prototype
  608. * @type {Color}
  609. *
  610. * @example
  611. * // Example 1. Assign yellow.
  612. * b.color = Cesium.Color.YELLOW;
  613. *
  614. * @example
  615. * // Example 2. Make a billboard 50% translucent.
  616. * b.color = new Cesium.Color(1.0, 1.0, 1.0, 0.5);
  617. */
  618. color: {
  619. get: function () {
  620. return this._color;
  621. },
  622. set: function (value) {
  623. //>>includeStart('debug', pragmas.debug);
  624. if (!defined(value)) {
  625. throw new DeveloperError("value is required.");
  626. }
  627. //>>includeEnd('debug');
  628. var color = this._color;
  629. if (!Color.equals(color, value)) {
  630. Color.clone(value, color);
  631. makeDirty(this, COLOR_INDEX);
  632. }
  633. },
  634. },
  635. /**
  636. * Gets or sets the rotation angle in radians.
  637. * @memberof Billboard.prototype
  638. * @type {Number}
  639. */
  640. rotation: {
  641. get: function () {
  642. return this._rotation;
  643. },
  644. set: function (value) {
  645. //>>includeStart('debug', pragmas.debug);
  646. if (!defined(value)) {
  647. throw new DeveloperError("value is required.");
  648. }
  649. //>>includeEnd('debug');
  650. if (this._rotation !== value) {
  651. this._rotation = value;
  652. makeDirty(this, ROTATION_INDEX);
  653. }
  654. },
  655. },
  656. /**
  657. * Gets or sets the aligned axis in world space. The aligned axis is the unit vector that the billboard up vector points towards.
  658. * The default is the zero vector, which means the billboard is aligned to the screen up vector.
  659. * @memberof Billboard.prototype
  660. * @type {Cartesian3}
  661. * @example
  662. * // Example 1.
  663. * // Have the billboard up vector point north
  664. * billboard.alignedAxis = Cesium.Cartesian3.UNIT_Z;
  665. *
  666. * @example
  667. * // Example 2.
  668. * // Have the billboard point east.
  669. * billboard.alignedAxis = Cesium.Cartesian3.UNIT_Z;
  670. * billboard.rotation = -Cesium.Math.PI_OVER_TWO;
  671. *
  672. * @example
  673. * // Example 3.
  674. * // Reset the aligned axis
  675. * billboard.alignedAxis = Cesium.Cartesian3.ZERO;
  676. */
  677. alignedAxis: {
  678. get: function () {
  679. return this._alignedAxis;
  680. },
  681. set: function (value) {
  682. //>>includeStart('debug', pragmas.debug);
  683. if (!defined(value)) {
  684. throw new DeveloperError("value is required.");
  685. }
  686. //>>includeEnd('debug');
  687. var alignedAxis = this._alignedAxis;
  688. if (!Cartesian3.equals(alignedAxis, value)) {
  689. Cartesian3.clone(value, alignedAxis);
  690. makeDirty(this, ALIGNED_AXIS_INDEX);
  691. }
  692. },
  693. },
  694. /**
  695. * Gets or sets a width for the billboard. If undefined, the image width will be used.
  696. * @memberof Billboard.prototype
  697. * @type {Number}
  698. */
  699. width: {
  700. get: function () {
  701. return defaultValue(this._width, this._imageWidth);
  702. },
  703. set: function (value) {
  704. if (this._width !== value) {
  705. this._width = value;
  706. makeDirty(this, IMAGE_INDEX_INDEX);
  707. }
  708. },
  709. },
  710. /**
  711. * Gets or sets a height for the billboard. If undefined, the image height will be used.
  712. * @memberof Billboard.prototype
  713. * @type {Number}
  714. */
  715. height: {
  716. get: function () {
  717. return defaultValue(this._height, this._imageHeight);
  718. },
  719. set: function (value) {
  720. if (this._height !== value) {
  721. this._height = value;
  722. makeDirty(this, IMAGE_INDEX_INDEX);
  723. }
  724. },
  725. },
  726. /**
  727. * Gets or sets if the billboard size is in meters or pixels. <code>true</code> to size the billboard in meters;
  728. * otherwise, the size is in pixels.
  729. * @memberof Billboard.prototype
  730. * @type {Boolean}
  731. * @default false
  732. */
  733. sizeInMeters: {
  734. get: function () {
  735. return this._sizeInMeters;
  736. },
  737. set: function (value) {
  738. if (this._sizeInMeters !== value) {
  739. this._sizeInMeters = value;
  740. makeDirty(this, COLOR_INDEX);
  741. }
  742. },
  743. },
  744. /**
  745. * Gets or sets the condition specifying at what distance from the camera that this billboard will be displayed.
  746. * @memberof Billboard.prototype
  747. * @type {DistanceDisplayCondition}
  748. * @default undefined
  749. */
  750. distanceDisplayCondition: {
  751. get: function () {
  752. return this._distanceDisplayCondition;
  753. },
  754. set: function (value) {
  755. if (
  756. !DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)
  757. ) {
  758. //>>includeStart('debug', pragmas.debug);
  759. if (defined(value) && value.far <= value.near) {
  760. throw new DeveloperError(
  761. "far distance must be greater than near distance."
  762. );
  763. }
  764. //>>includeEnd('debug');
  765. this._distanceDisplayCondition = DistanceDisplayCondition.clone(
  766. value,
  767. this._distanceDisplayCondition
  768. );
  769. makeDirty(this, DISTANCE_DISPLAY_CONDITION);
  770. }
  771. },
  772. },
  773. /**
  774. * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain.
  775. * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied.
  776. * @memberof Billboard.prototype
  777. * @type {Number}
  778. */
  779. disableDepthTestDistance: {
  780. get: function () {
  781. return this._disableDepthTestDistance;
  782. },
  783. set: function (value) {
  784. if (this._disableDepthTestDistance !== value) {
  785. //>>includeStart('debug', pragmas.debug);
  786. if (defined(value) && value < 0.0) {
  787. throw new DeveloperError(
  788. "disableDepthTestDistance must be greater than or equal to 0.0."
  789. );
  790. }
  791. //>>includeEnd('debug');
  792. this._disableDepthTestDistance = value;
  793. makeDirty(this, DISABLE_DEPTH_DISTANCE);
  794. }
  795. },
  796. },
  797. /**
  798. * Gets or sets the user-defined object returned when the billboard is picked.
  799. * @memberof Billboard.prototype
  800. * @type {Object}
  801. */
  802. id: {
  803. get: function () {
  804. return this._id;
  805. },
  806. set: function (value) {
  807. this._id = value;
  808. if (defined(this._pickId)) {
  809. this._pickId.object.id = value;
  810. }
  811. },
  812. },
  813. /**
  814. * The primitive to return when picking this billboard.
  815. * @memberof Billboard.prototype
  816. * @private
  817. */
  818. pickPrimitive: {
  819. get: function () {
  820. return this._pickPrimitive;
  821. },
  822. set: function (value) {
  823. this._pickPrimitive = value;
  824. if (defined(this._pickId)) {
  825. this._pickId.object.primitive = value;
  826. }
  827. },
  828. },
  829. /**
  830. * @private
  831. */
  832. pickId: {
  833. get: function () {
  834. return this._pickId;
  835. },
  836. },
  837. /**
  838. * <p>
  839. * Gets or sets the image to be used for this billboard. If a texture has already been created for the
  840. * given image, the existing texture is used.
  841. * </p>
  842. * <p>
  843. * This property can be set to a loaded Image, a URL which will be loaded as an Image automatically,
  844. * a canvas, or another billboard's image property (from the same billboard collection).
  845. * </p>
  846. *
  847. * @memberof Billboard.prototype
  848. * @type {String}
  849. * @example
  850. * // load an image from a URL
  851. * b.image = 'some/image/url.png';
  852. *
  853. * // assuming b1 and b2 are billboards in the same billboard collection,
  854. * // use the same image for both billboards.
  855. * b2.image = b1.image;
  856. */
  857. image: {
  858. get: function () {
  859. return this._imageId;
  860. },
  861. set: function (value) {
  862. if (!defined(value)) {
  863. this._imageIndex = -1;
  864. this._imageSubRegion = undefined;
  865. this._imageId = undefined;
  866. this._image = undefined;
  867. this._imageIndexPromise = undefined;
  868. makeDirty(this, IMAGE_INDEX_INDEX);
  869. } else if (typeof value === "string") {
  870. this.setImage(value, value);
  871. } else if (value instanceof Resource) {
  872. this.setImage(value.url, value);
  873. } else if (defined(value.src)) {
  874. this.setImage(value.src, value);
  875. } else {
  876. this.setImage(createGuid(), value);
  877. }
  878. },
  879. },
  880. /**
  881. * When <code>true</code>, this billboard is ready to render, i.e., the image
  882. * has been downloaded and the WebGL resources are created.
  883. *
  884. * @memberof Billboard.prototype
  885. *
  886. * @type {Boolean}
  887. * @readonly
  888. *
  889. * @default false
  890. */
  891. ready: {
  892. get: function () {
  893. return this._imageIndex !== -1;
  894. },
  895. },
  896. /**
  897. * Keeps track of the position of the billboard based on the height reference.
  898. * @memberof Billboard.prototype
  899. * @type {Cartesian3}
  900. * @private
  901. */
  902. _clampedPosition: {
  903. get: function () {
  904. return this._actualClampedPosition;
  905. },
  906. set: function (value) {
  907. this._actualClampedPosition = Cartesian3.clone(
  908. value,
  909. this._actualClampedPosition
  910. );
  911. makeDirty(this, POSITION_INDEX);
  912. },
  913. },
  914. /**
  915. * Determines whether or not this billboard will be shown or hidden because it was clustered.
  916. * @memberof Billboard.prototype
  917. * @type {Boolean}
  918. * @private
  919. */
  920. clusterShow: {
  921. get: function () {
  922. return this._clusterShow;
  923. },
  924. set: function (value) {
  925. if (this._clusterShow !== value) {
  926. this._clusterShow = value;
  927. makeDirty(this, SHOW_INDEX);
  928. }
  929. },
  930. },
  931. /**
  932. * The outline color of this Billboard. Effective only for SDF billboards like Label glyphs.
  933. * @memberof Billboard.prototype
  934. * @type {Color}
  935. * @private
  936. */
  937. outlineColor: {
  938. get: function () {
  939. return this._outlineColor;
  940. },
  941. set: function (value) {
  942. //>>includeStart('debug', pragmas.debug);
  943. if (!defined(value)) {
  944. throw new DeveloperError("value is required.");
  945. }
  946. //>>includeEnd('debug');
  947. var outlineColor = this._outlineColor;
  948. if (!Color.equals(outlineColor, value)) {
  949. Color.clone(value, outlineColor);
  950. makeDirty(this, SDF_INDEX);
  951. }
  952. },
  953. },
  954. /**
  955. * The outline width of this Billboard in pixels. Effective only for SDF billboards like Label glyphs.
  956. * @memberof Billboard.prototype
  957. * @type {Number}
  958. * @private
  959. */
  960. outlineWidth: {
  961. get: function () {
  962. return this._outlineWidth;
  963. },
  964. set: function (value) {
  965. if (this._outlineWidth !== value) {
  966. this._outlineWidth = value;
  967. makeDirty(this, SDF_INDEX);
  968. }
  969. },
  970. },
  971. });
  972. Billboard.prototype.getPickId = function (context) {
  973. if (!defined(this._pickId)) {
  974. this._pickId = context.createPickId({
  975. primitive: this._pickPrimitive,
  976. collection: this._collection,
  977. id: this._id,
  978. });
  979. }
  980. return this._pickId;
  981. };
  982. Billboard.prototype._updateClamping = function () {
  983. Billboard._updateClamping(this._billboardCollection, this);
  984. };
  985. var scratchCartographic = new Cartographic();
  986. var scratchPosition = new Cartesian3();
  987. Billboard._updateClamping = function (collection, owner) {
  988. var scene = collection._scene;
  989. if (!defined(scene) || !defined(scene.globe)) {
  990. //>>includeStart('debug', pragmas.debug);
  991. if (owner._heightReference !== HeightReference.NONE) {
  992. throw new DeveloperError(
  993. "Height reference is not supported without a scene and globe."
  994. );
  995. }
  996. //>>includeEnd('debug');
  997. return;
  998. }
  999. var globe = scene.globe;
  1000. var ellipsoid = globe.ellipsoid;
  1001. var surface = globe._surface;
  1002. var mode = scene.frameState.mode;
  1003. var modeChanged = mode !== owner._mode;
  1004. owner._mode = mode;
  1005. if (
  1006. (owner._heightReference === HeightReference.NONE || modeChanged) &&
  1007. defined(owner._removeCallbackFunc)
  1008. ) {
  1009. owner._removeCallbackFunc();
  1010. owner._removeCallbackFunc = undefined;
  1011. owner._clampedPosition = undefined;
  1012. }
  1013. if (
  1014. owner._heightReference === HeightReference.NONE ||
  1015. !defined(owner._position)
  1016. ) {
  1017. return;
  1018. }
  1019. var position = ellipsoid.cartesianToCartographic(owner._position);
  1020. if (!defined(position)) {
  1021. owner._actualClampedPosition = undefined;
  1022. return;
  1023. }
  1024. if (defined(owner._removeCallbackFunc)) {
  1025. owner._removeCallbackFunc();
  1026. }
  1027. function updateFunction(clampedPosition) {
  1028. if (owner._heightReference === HeightReference.RELATIVE_TO_GROUND) {
  1029. if (owner._mode === SceneMode.SCENE3D) {
  1030. var clampedCart = ellipsoid.cartesianToCartographic(
  1031. clampedPosition,
  1032. scratchCartographic
  1033. );
  1034. clampedCart.height += position.height;
  1035. ellipsoid.cartographicToCartesian(clampedCart, clampedPosition);
  1036. } else {
  1037. clampedPosition.x += position.height;
  1038. }
  1039. }
  1040. owner._clampedPosition = Cartesian3.clone(
  1041. clampedPosition,
  1042. owner._clampedPosition
  1043. );
  1044. }
  1045. owner._removeCallbackFunc = surface.updateHeight(position, updateFunction);
  1046. Cartographic.clone(position, scratchCartographic);
  1047. var height = globe.getHeight(position);
  1048. if (defined(height)) {
  1049. scratchCartographic.height = height;
  1050. }
  1051. ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition);
  1052. updateFunction(scratchPosition);
  1053. };
  1054. Billboard.prototype._loadImage = function () {
  1055. var atlas = this._billboardCollection._textureAtlas;
  1056. var imageId = this._imageId;
  1057. var image = this._image;
  1058. var imageSubRegion = this._imageSubRegion;
  1059. var imageIndexPromise;
  1060. if (defined(image)) {
  1061. imageIndexPromise = atlas.addImage(imageId, image);
  1062. }
  1063. if (defined(imageSubRegion)) {
  1064. imageIndexPromise = atlas.addSubRegion(imageId, imageSubRegion);
  1065. }
  1066. this._imageIndexPromise = imageIndexPromise;
  1067. if (!defined(imageIndexPromise)) {
  1068. return;
  1069. }
  1070. var that = this;
  1071. imageIndexPromise
  1072. .then(function (index) {
  1073. if (
  1074. that._imageId !== imageId ||
  1075. that._image !== image ||
  1076. !BoundingRectangle.equals(that._imageSubRegion, imageSubRegion)
  1077. ) {
  1078. // another load occurred before this one finished, ignore the index
  1079. return;
  1080. }
  1081. // fill in imageWidth and imageHeight
  1082. var textureCoordinates = atlas.textureCoordinates[index];
  1083. that._imageWidth = atlas.texture.width * textureCoordinates.width;
  1084. that._imageHeight = atlas.texture.height * textureCoordinates.height;
  1085. that._imageIndex = index;
  1086. that._ready = true;
  1087. that._image = undefined;
  1088. that._imageIndexPromise = undefined;
  1089. makeDirty(that, IMAGE_INDEX_INDEX);
  1090. })
  1091. .otherwise(function (error) {
  1092. console.error("Error loading image for billboard: " + error);
  1093. that._imageIndexPromise = undefined;
  1094. });
  1095. };
  1096. /**
  1097. * <p>
  1098. * Sets the image to be used for this billboard. If a texture has already been created for the
  1099. * given id, the existing texture is used.
  1100. * </p>
  1101. * <p>
  1102. * This function is useful for dynamically creating textures that are shared across many billboards.
  1103. * Only the first billboard will actually call the function and create the texture, while subsequent
  1104. * billboards created with the same id will simply re-use the existing texture.
  1105. * </p>
  1106. * <p>
  1107. * To load an image from a URL, setting the {@link Billboard#image} property is more convenient.
  1108. * </p>
  1109. *
  1110. * @param {String} id The id of the image. This can be any string that uniquely identifies the image.
  1111. * @param {HTMLImageElement|HTMLCanvasElement|String|Resource|Billboard.CreateImageCallback} image The image to load. This parameter
  1112. * can either be a loaded Image or Canvas, a URL which will be loaded as an Image automatically,
  1113. * or a function which will be called to create the image if it hasn't been loaded already.
  1114. * @example
  1115. * // create a billboard image dynamically
  1116. * function drawImage(id) {
  1117. * // create and draw an image using a canvas
  1118. * var canvas = document.createElement('canvas');
  1119. * var context2D = canvas.getContext('2d');
  1120. * // ... draw image
  1121. * return canvas;
  1122. * }
  1123. * // drawImage will be called to create the texture
  1124. * b.setImage('myImage', drawImage);
  1125. *
  1126. * // subsequent billboards created in the same collection using the same id will use the existing
  1127. * // texture, without the need to create the canvas or draw the image
  1128. * b2.setImage('myImage', drawImage);
  1129. */
  1130. Billboard.prototype.setImage = function (id, image) {
  1131. //>>includeStart('debug', pragmas.debug);
  1132. if (!defined(id)) {
  1133. throw new DeveloperError("id is required.");
  1134. }
  1135. if (!defined(image)) {
  1136. throw new DeveloperError("image is required.");
  1137. }
  1138. //>>includeEnd('debug');
  1139. if (this._imageId === id) {
  1140. return;
  1141. }
  1142. this._imageIndex = -1;
  1143. this._imageSubRegion = undefined;
  1144. this._imageId = id;
  1145. this._image = image;
  1146. if (defined(this._billboardCollection._textureAtlas)) {
  1147. this._loadImage();
  1148. }
  1149. };
  1150. /**
  1151. * Uses a sub-region of the image with the given id as the image for this billboard,
  1152. * measured in pixels from the bottom-left.
  1153. *
  1154. * @param {String} id The id of the image to use.
  1155. * @param {BoundingRectangle} subRegion The sub-region of the image.
  1156. *
  1157. * @exception {RuntimeError} image with id must be in the atlas
  1158. */
  1159. Billboard.prototype.setImageSubRegion = function (id, subRegion) {
  1160. //>>includeStart('debug', pragmas.debug);
  1161. if (!defined(id)) {
  1162. throw new DeveloperError("id is required.");
  1163. }
  1164. if (!defined(subRegion)) {
  1165. throw new DeveloperError("subRegion is required.");
  1166. }
  1167. //>>includeEnd('debug');
  1168. if (
  1169. this._imageId === id &&
  1170. BoundingRectangle.equals(this._imageSubRegion, subRegion)
  1171. ) {
  1172. return;
  1173. }
  1174. this._imageIndex = -1;
  1175. this._imageId = id;
  1176. this._imageSubRegion = BoundingRectangle.clone(subRegion);
  1177. if (defined(this._billboardCollection._textureAtlas)) {
  1178. this._loadImage();
  1179. }
  1180. };
  1181. Billboard.prototype._setTranslate = function (value) {
  1182. //>>includeStart('debug', pragmas.debug);
  1183. if (!defined(value)) {
  1184. throw new DeveloperError("value is required.");
  1185. }
  1186. //>>includeEnd('debug');
  1187. var translate = this._translate;
  1188. if (!Cartesian2.equals(translate, value)) {
  1189. Cartesian2.clone(value, translate);
  1190. makeDirty(this, PIXEL_OFFSET_INDEX);
  1191. }
  1192. };
  1193. Billboard.prototype._getActualPosition = function () {
  1194. return defined(this._clampedPosition)
  1195. ? this._clampedPosition
  1196. : this._actualPosition;
  1197. };
  1198. Billboard.prototype._setActualPosition = function (value) {
  1199. if (!defined(this._clampedPosition)) {
  1200. Cartesian3.clone(value, this._actualPosition);
  1201. }
  1202. makeDirty(this, POSITION_INDEX);
  1203. };
  1204. var tempCartesian3 = new Cartesian4();
  1205. Billboard._computeActualPosition = function (
  1206. billboard,
  1207. position,
  1208. frameState,
  1209. modelMatrix
  1210. ) {
  1211. if (defined(billboard._clampedPosition)) {
  1212. if (frameState.mode !== billboard._mode) {
  1213. billboard._updateClamping();
  1214. }
  1215. return billboard._clampedPosition;
  1216. } else if (frameState.mode === SceneMode.SCENE3D) {
  1217. return position;
  1218. }
  1219. Matrix4.multiplyByPoint(modelMatrix, position, tempCartesian3);
  1220. return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3);
  1221. };
  1222. var scratchCartesian3 = new Cartesian3();
  1223. // This function is basically a stripped-down JavaScript version of BillboardCollectionVS.glsl
  1224. Billboard._computeScreenSpacePosition = function (
  1225. modelMatrix,
  1226. position,
  1227. eyeOffset,
  1228. pixelOffset,
  1229. scene,
  1230. result
  1231. ) {
  1232. // Model to world coordinates
  1233. var positionWorld = Matrix4.multiplyByPoint(
  1234. modelMatrix,
  1235. position,
  1236. scratchCartesian3
  1237. );
  1238. // World to window coordinates
  1239. var positionWC = SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(
  1240. scene,
  1241. positionWorld,
  1242. eyeOffset,
  1243. result
  1244. );
  1245. if (!defined(positionWC)) {
  1246. return undefined;
  1247. }
  1248. // Apply pixel offset
  1249. Cartesian2.add(positionWC, pixelOffset, positionWC);
  1250. return positionWC;
  1251. };
  1252. var scratchPixelOffset = new Cartesian2(0.0, 0.0);
  1253. /**
  1254. * Computes the screen-space position of the billboard's origin, taking into account eye and pixel offsets.
  1255. * The screen space origin is the top, left corner of the canvas; <code>x</code> increases from
  1256. * left to right, and <code>y</code> increases from top to bottom.
  1257. *
  1258. * @param {Scene} scene The scene.
  1259. * @param {Cartesian2} [result] The object onto which to store the result.
  1260. * @returns {Cartesian2} The screen-space position of the billboard.
  1261. *
  1262. * @exception {DeveloperError} Billboard must be in a collection.
  1263. *
  1264. * @example
  1265. * console.log(b.computeScreenSpacePosition(scene).toString());
  1266. *
  1267. * @see Billboard#eyeOffset
  1268. * @see Billboard#pixelOffset
  1269. */
  1270. Billboard.prototype.computeScreenSpacePosition = function (scene, result) {
  1271. var billboardCollection = this._billboardCollection;
  1272. if (!defined(result)) {
  1273. result = new Cartesian2();
  1274. }
  1275. //>>includeStart('debug', pragmas.debug);
  1276. if (!defined(billboardCollection)) {
  1277. throw new DeveloperError(
  1278. "Billboard must be in a collection. Was it removed?"
  1279. );
  1280. }
  1281. if (!defined(scene)) {
  1282. throw new DeveloperError("scene is required.");
  1283. }
  1284. //>>includeEnd('debug');
  1285. // pixel offset for screen space computation is the pixelOffset + screen space translate
  1286. Cartesian2.clone(this._pixelOffset, scratchPixelOffset);
  1287. Cartesian2.add(scratchPixelOffset, this._translate, scratchPixelOffset);
  1288. var modelMatrix = billboardCollection.modelMatrix;
  1289. var position = this._position;
  1290. if (defined(this._clampedPosition)) {
  1291. position = this._clampedPosition;
  1292. if (scene.mode !== SceneMode.SCENE3D) {
  1293. // position needs to be in world coordinates
  1294. var projection = scene.mapProjection;
  1295. var ellipsoid = projection.ellipsoid;
  1296. var cart = projection.unproject(position, scratchCartographic);
  1297. position = ellipsoid.cartographicToCartesian(cart, scratchCartesian3);
  1298. modelMatrix = Matrix4.IDENTITY;
  1299. }
  1300. }
  1301. var windowCoordinates = Billboard._computeScreenSpacePosition(
  1302. modelMatrix,
  1303. position,
  1304. this._eyeOffset,
  1305. scratchPixelOffset,
  1306. scene,
  1307. result
  1308. );
  1309. return windowCoordinates;
  1310. };
  1311. /**
  1312. * Gets a billboard's screen space bounding box centered around screenSpacePosition.
  1313. * @param {Billboard} billboard The billboard to get the screen space bounding box for.
  1314. * @param {Cartesian2} screenSpacePosition The screen space center of the label.
  1315. * @param {BoundingRectangle} [result] The object onto which to store the result.
  1316. * @returns {BoundingRectangle} The screen space bounding box.
  1317. *
  1318. * @private
  1319. */
  1320. Billboard.getScreenSpaceBoundingBox = function (
  1321. billboard,
  1322. screenSpacePosition,
  1323. result
  1324. ) {
  1325. var width = billboard.width;
  1326. var height = billboard.height;
  1327. var scale = billboard.scale;
  1328. width *= scale;
  1329. height *= scale;
  1330. var x = screenSpacePosition.x;
  1331. if (billboard.horizontalOrigin === HorizontalOrigin.RIGHT) {
  1332. x -= width;
  1333. } else if (billboard.horizontalOrigin === HorizontalOrigin.CENTER) {
  1334. x -= width * 0.5;
  1335. }
  1336. var y = screenSpacePosition.y;
  1337. if (
  1338. billboard.verticalOrigin === VerticalOrigin.BOTTOM ||
  1339. billboard.verticalOrigin === VerticalOrigin.BASELINE
  1340. ) {
  1341. y -= height;
  1342. } else if (billboard.verticalOrigin === VerticalOrigin.CENTER) {
  1343. y -= height * 0.5;
  1344. }
  1345. if (!defined(result)) {
  1346. result = new BoundingRectangle();
  1347. }
  1348. result.x = x;
  1349. result.y = y;
  1350. result.width = width;
  1351. result.height = height;
  1352. return result;
  1353. };
  1354. /**
  1355. * Determines if this billboard equals another billboard. Billboards are equal if all their properties
  1356. * are equal. Billboards in different collections can be equal.
  1357. *
  1358. * @param {Billboard} other The billboard to compare for equality.
  1359. * @returns {Boolean} <code>true</code> if the billboards are equal; otherwise, <code>false</code>.
  1360. */
  1361. Billboard.prototype.equals = function (other) {
  1362. return (
  1363. this === other ||
  1364. (defined(other) &&
  1365. this._id === other._id &&
  1366. Cartesian3.equals(this._position, other._position) &&
  1367. this._imageId === other._imageId &&
  1368. this._show === other._show &&
  1369. this._scale === other._scale &&
  1370. this._verticalOrigin === other._verticalOrigin &&
  1371. this._horizontalOrigin === other._horizontalOrigin &&
  1372. this._heightReference === other._heightReference &&
  1373. BoundingRectangle.equals(this._imageSubRegion, other._imageSubRegion) &&
  1374. Color.equals(this._color, other._color) &&
  1375. Cartesian2.equals(this._pixelOffset, other._pixelOffset) &&
  1376. Cartesian2.equals(this._translate, other._translate) &&
  1377. Cartesian3.equals(this._eyeOffset, other._eyeOffset) &&
  1378. NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) &&
  1379. NearFarScalar.equals(
  1380. this._translucencyByDistance,
  1381. other._translucencyByDistance
  1382. ) &&
  1383. NearFarScalar.equals(
  1384. this._pixelOffsetScaleByDistance,
  1385. other._pixelOffsetScaleByDistance
  1386. ) &&
  1387. DistanceDisplayCondition.equals(
  1388. this._distanceDisplayCondition,
  1389. other._distanceDisplayCondition
  1390. ) &&
  1391. this._disableDepthTestDistance === other._disableDepthTestDistance)
  1392. );
  1393. };
  1394. Billboard.prototype._destroy = function () {
  1395. if (defined(this._customData)) {
  1396. this._billboardCollection._scene.globe._surface.removeTileCustomData(
  1397. this._customData
  1398. );
  1399. this._customData = undefined;
  1400. }
  1401. if (defined(this._removeCallbackFunc)) {
  1402. this._removeCallbackFunc();
  1403. this._removeCallbackFunc = undefined;
  1404. }
  1405. this.image = undefined;
  1406. this._pickId = this._pickId && this._pickId.destroy();
  1407. this._billboardCollection = undefined;
  1408. };
  1409. /**
  1410. * A function that creates an image.
  1411. * @callback Billboard.CreateImageCallback
  1412. * @param {String} id The identifier of the image to load.
  1413. * @returns {HTMLImageElement|HTMLCanvasElement|Promise<HTMLImageElement|HTMLCanvasElement>} The image, or a promise that will resolve to an image.
  1414. */
  1415. export default Billboard;