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

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