Globe.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  1. import BoundingSphere from "../Core/BoundingSphere.js";
  2. import buildModuleUrl from "../Core/buildModuleUrl.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Cartographic from "../Core/Cartographic.js";
  5. import Color from "../Core/Color.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import destroyObject from "../Core/destroyObject.js";
  9. import DeveloperError from "../Core/DeveloperError.js";
  10. import Ellipsoid from "../Core/Ellipsoid.js";
  11. import EllipsoidTerrainProvider from "../Core/EllipsoidTerrainProvider.js";
  12. import Event from "../Core/Event.js";
  13. import IntersectionTests from "../Core/IntersectionTests.js";
  14. import NearFarScalar from "../Core/NearFarScalar.js";
  15. import Ray from "../Core/Ray.js";
  16. import Rectangle from "../Core/Rectangle.js";
  17. import Resource from "../Core/Resource.js";
  18. import ShaderSource from "../Renderer/ShaderSource.js";
  19. import Texture from "../Renderer/Texture.js";
  20. import GlobeFS from "../Shaders/GlobeFS.js";
  21. import GlobeVS from "../Shaders/GlobeVS.js";
  22. import AtmosphereCommon from "../Shaders/AtmosphereCommon.js";
  23. import GroundAtmosphere from "../Shaders/GroundAtmosphere.js";
  24. import GlobeSurfaceShaderSet from "./GlobeSurfaceShaderSet.js";
  25. import GlobeSurfaceTileProvider from "./GlobeSurfaceTileProvider.js";
  26. import GlobeTranslucency from "./GlobeTranslucency.js";
  27. import ImageryLayerCollection from "./ImageryLayerCollection.js";
  28. import QuadtreePrimitive from "./QuadtreePrimitive.js";
  29. import SceneMode from "./SceneMode.js";
  30. import ShadowMode from "./ShadowMode.js";
  31. /**
  32. * The globe rendered in the scene, including its terrain ({@link Globe#terrainProvider})
  33. * and imagery layers ({@link Globe#imageryLayers}). Access the globe using {@link Scene#globe}.
  34. *
  35. * @alias Globe
  36. * @constructor
  37. *
  38. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] Determines the size and shape of the
  39. * globe.
  40. */
  41. function Globe(ellipsoid) {
  42. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  43. const terrainProvider = new EllipsoidTerrainProvider({
  44. ellipsoid: ellipsoid,
  45. });
  46. const imageryLayerCollection = new ImageryLayerCollection();
  47. this._ellipsoid = ellipsoid;
  48. this._imageryLayerCollection = imageryLayerCollection;
  49. this._surfaceShaderSet = new GlobeSurfaceShaderSet();
  50. this._material = undefined;
  51. this._surface = new QuadtreePrimitive({
  52. tileProvider: new GlobeSurfaceTileProvider({
  53. terrainProvider: terrainProvider,
  54. imageryLayers: imageryLayerCollection,
  55. surfaceShaderSet: this._surfaceShaderSet,
  56. }),
  57. });
  58. this._terrainProvider = terrainProvider;
  59. this._terrainProviderChanged = new Event();
  60. this._undergroundColor = Color.clone(Color.BLACK);
  61. this._undergroundColorAlphaByDistance = new NearFarScalar(
  62. ellipsoid.maximumRadius / 1000.0,
  63. 0.0,
  64. ellipsoid.maximumRadius / 5.0,
  65. 1.0
  66. );
  67. this._translucency = new GlobeTranslucency();
  68. makeShadersDirty(this);
  69. /**
  70. * Determines if the globe will be shown.
  71. *
  72. * @type {boolean}
  73. * @default true
  74. */
  75. this.show = true;
  76. this._oceanNormalMapResourceDirty = true;
  77. this._oceanNormalMapResource = new Resource({
  78. url: buildModuleUrl("Assets/Textures/waterNormalsSmall.jpg"),
  79. });
  80. /**
  81. * The maximum screen-space error used to drive level-of-detail refinement. Higher
  82. * values will provide better performance but lower visual quality.
  83. *
  84. * @type {number}
  85. * @default 2
  86. */
  87. this.maximumScreenSpaceError = 2;
  88. /**
  89. * The size of the terrain tile cache, expressed as a number of tiles. Any additional
  90. * tiles beyond this number will be freed, as long as they aren't needed for rendering
  91. * this frame. A larger number will consume more memory but will show detail faster
  92. * when, for example, zooming out and then back in.
  93. *
  94. * @type {number}
  95. * @default 100
  96. */
  97. this.tileCacheSize = 100;
  98. /**
  99. * Gets or sets the number of loading descendant tiles that is considered "too many".
  100. * If a tile has too many loading descendants, that tile will be loaded and rendered before any of
  101. * its descendants are loaded and rendered. This means more feedback for the user that something
  102. * is happening at the cost of a longer overall load time. Setting this to 0 will cause each
  103. * tile level to be loaded successively, significantly increasing load time. Setting it to a large
  104. * number (e.g. 1000) will minimize the number of tiles that are loaded but tend to make
  105. * detail appear all at once after a long wait.
  106. * @type {number}
  107. * @default 20
  108. */
  109. this.loadingDescendantLimit = 20;
  110. /**
  111. * Gets or sets a value indicating whether the ancestors of rendered tiles should be preloaded.
  112. * Setting this to true optimizes the zoom-out experience and provides more detail in
  113. * newly-exposed areas when panning. The down side is that it requires loading more tiles.
  114. * @type {boolean}
  115. * @default true
  116. */
  117. this.preloadAncestors = true;
  118. /**
  119. * Gets or sets a value indicating whether the siblings of rendered tiles should be preloaded.
  120. * Setting this to true causes tiles with the same parent as a rendered tile to be loaded, even
  121. * if they are culled. Setting this to true may provide a better panning experience at the
  122. * cost of loading more tiles.
  123. * @type {boolean}
  124. * @default false
  125. */
  126. this.preloadSiblings = false;
  127. /**
  128. * The color to use to highlight terrain fill tiles. If undefined, fill tiles are not
  129. * highlighted at all. The alpha value is used to alpha blend with the tile's
  130. * actual color. Because terrain fill tiles do not represent the actual terrain surface,
  131. * it may be useful in some applications to indicate visually that they are not to be trusted.
  132. * @type {Color}
  133. * @default undefined
  134. */
  135. this.fillHighlightColor = undefined;
  136. /**
  137. * Enable lighting the globe with the scene's light source.
  138. *
  139. * @type {boolean}
  140. * @default false
  141. */
  142. this.enableLighting = false;
  143. /**
  144. * A multiplier to adjust terrain lambert lighting.
  145. * This number is multiplied by the result of <code>czm_getLambertDiffuse</code> in GlobeFS.glsl.
  146. * This only takes effect when <code>enableLighting</code> is <code>true</code>.
  147. *
  148. * @type {number}
  149. * @default 0.9
  150. */
  151. this.lambertDiffuseMultiplier = 0.9;
  152. /**
  153. * Enable dynamic lighting effects on atmosphere and fog. This only takes effect
  154. * when <code>enableLighting</code> is <code>true</code>.
  155. *
  156. * @type {boolean}
  157. * @default true
  158. */
  159. this.dynamicAtmosphereLighting = true;
  160. /**
  161. * Whether dynamic atmosphere lighting uses the sun direction instead of the scene's
  162. * light direction. This only takes effect when <code>enableLighting</code> and
  163. * <code>dynamicAtmosphereLighting</code> are <code>true</code>.
  164. *
  165. * @type {boolean}
  166. * @default false
  167. */
  168. this.dynamicAtmosphereLightingFromSun = false;
  169. /**
  170. * Enable the ground atmosphere, which is drawn over the globe when viewed from a distance between <code>lightingFadeInDistance</code> and <code>lightingFadeOutDistance</code>.
  171. *
  172. * @type {boolean}
  173. * @default true
  174. */
  175. this.showGroundAtmosphere = true;
  176. /**
  177. * The intensity of the light that is used for computing the ground atmosphere color.
  178. *
  179. * @type {number}
  180. * @default 10.0
  181. */
  182. this.atmosphereLightIntensity = 10.0;
  183. /**
  184. * The Rayleigh scattering coefficient used in the atmospheric scattering equations for the ground atmosphere.
  185. *
  186. * @type {Cartesian3}
  187. * @default Cartesian3(5.5e-6, 13.0e-6, 28.4e-6)
  188. */
  189. this.atmosphereRayleighCoefficient = new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6);
  190. /**
  191. * The Mie scattering coefficient used in the atmospheric scattering equations for the ground atmosphere.
  192. *
  193. * @type {Cartesian3}
  194. * @default Cartesian3(21e-6, 21e-6, 21e-6)
  195. */
  196. this.atmosphereMieCoefficient = new Cartesian3(21e-6, 21e-6, 21e-6);
  197. /**
  198. * The Rayleigh scale height used in the atmospheric scattering equations for the ground atmosphere, in meters.
  199. *
  200. * @type {number}
  201. * @default 10000.0
  202. */
  203. this.atmosphereRayleighScaleHeight = 10000.0;
  204. /**
  205. * The Mie scale height used in the atmospheric scattering equations for the ground atmosphere, in meters.
  206. *
  207. * @type {number}
  208. * @default 3200.0
  209. */
  210. this.atmosphereMieScaleHeight = 3200.0;
  211. /**
  212. * The anisotropy of the medium to consider for Mie scattering.
  213. * <p>
  214. * Valid values are between -1.0 and 1.0.
  215. * </p>
  216. * @type {number}
  217. * @default 0.9
  218. */
  219. this.atmosphereMieAnisotropy = 0.9;
  220. /**
  221. * The distance where everything becomes lit. This only takes effect
  222. * when <code>enableLighting</code> or <code>showGroundAtmosphere</code> is <code>true</code>.
  223. *
  224. * @type {number}
  225. * @default 10000000.0
  226. */
  227. this.lightingFadeOutDistance = 1.0e7;
  228. /**
  229. * The distance where lighting resumes. This only takes effect
  230. * when <code>enableLighting</code> or <code>showGroundAtmosphere</code> is <code>true</code>.
  231. *
  232. * @type {number}
  233. * @default 20000000.0
  234. */
  235. this.lightingFadeInDistance = 2.0e7;
  236. /**
  237. * The distance where the darkness of night from the ground atmosphere fades out to a lit ground atmosphere.
  238. * This only takes effect when <code>showGroundAtmosphere</code>, <code>enableLighting</code>, and
  239. * <code>dynamicAtmosphereLighting</code> are <code>true</code>.
  240. *
  241. * @type {number}
  242. * @default 10000000.0
  243. */
  244. this.nightFadeOutDistance = 1.0e7;
  245. /**
  246. * The distance where the darkness of night from the ground atmosphere fades in to an unlit ground atmosphere.
  247. * This only takes effect when <code>showGroundAtmosphere</code>, <code>enableLighting</code>, and
  248. * <code>dynamicAtmosphereLighting</code> are <code>true</code>.
  249. *
  250. * @type {number}
  251. * @default 50000000.0
  252. */
  253. this.nightFadeInDistance = 5.0e7;
  254. /**
  255. * True if an animated wave effect should be shown in areas of the globe
  256. * covered by water; otherwise, false. This property is ignored if the
  257. * <code>terrainProvider</code> does not provide a water mask.
  258. *
  259. * @type {boolean}
  260. * @default true
  261. */
  262. this.showWaterEffect = true;
  263. /**
  264. * True if primitives such as billboards, polylines, labels, etc. should be depth-tested
  265. * against the terrain surface, or false if such primitives should always be drawn on top
  266. * of terrain unless they're on the opposite side of the globe. The disadvantage of depth
  267. * testing primitives against terrain is that slight numerical noise or terrain level-of-detail
  268. * switched can sometimes make a primitive that should be on the surface disappear underneath it.
  269. *
  270. * @type {boolean}
  271. * @default false
  272. *
  273. */
  274. this.depthTestAgainstTerrain = false;
  275. /**
  276. * Determines whether the globe casts or receives shadows from light sources. Setting the globe
  277. * to cast shadows may impact performance since the terrain is rendered again from the light's perspective.
  278. * Currently only terrain that is in view casts shadows. By default the globe does not cast shadows.
  279. *
  280. * @type {ShadowMode}
  281. * @default ShadowMode.RECEIVE_ONLY
  282. */
  283. this.shadows = ShadowMode.RECEIVE_ONLY;
  284. /**
  285. * The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift).
  286. * A hue shift of 1.0 indicates a complete rotation of the hues available.
  287. * @type {number}
  288. * @default 0.0
  289. */
  290. this.atmosphereHueShift = 0.0;
  291. /**
  292. * The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift).
  293. * A saturation shift of -1.0 is monochrome.
  294. * @type {number}
  295. * @default 0.0
  296. */
  297. this.atmosphereSaturationShift = 0.0;
  298. /**
  299. * The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift).
  300. * A brightness shift of -1.0 is complete darkness, which will let space show through.
  301. * @type {number}
  302. * @default 0.0
  303. */
  304. this.atmosphereBrightnessShift = 0.0;
  305. /**
  306. * A scalar used to exaggerate the terrain. Defaults to <code>1.0</code> (no exaggeration).
  307. * A value of <code>2.0</code> scales the terrain by 2x.
  308. * A value of <code>0.0</code> makes the terrain completely flat.
  309. * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
  310. * @type {number}
  311. * @default 1.0
  312. */
  313. this.terrainExaggeration = 1.0;
  314. /**
  315. * The height from which terrain is exaggerated. Defaults to <code>0.0</code> (scaled relative to ellipsoid surface).
  316. * Terrain that is above this height will scale upwards and terrain that is below this height will scale downwards.
  317. * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
  318. * If {@link Globe#terrainExaggeration} is <code>1.0</code> this value will have no effect.
  319. * @type {number}
  320. * @default 0.0
  321. */
  322. this.terrainExaggerationRelativeHeight = 0.0;
  323. /**
  324. * Whether to show terrain skirts. Terrain skirts are geometry extending downwards from a tile's edges used to hide seams between neighboring tiles.
  325. * Skirts are always hidden when the camera is underground or translucency is enabled.
  326. *
  327. * @type {boolean}
  328. * @default true
  329. */
  330. this.showSkirts = true;
  331. /**
  332. * Whether to cull back-facing terrain. Back faces are not culled when the camera is underground or translucency is enabled.
  333. *
  334. * @type {boolean}
  335. * @default true
  336. */
  337. this.backFaceCulling = true;
  338. this._oceanNormalMap = undefined;
  339. this._zoomedOutOceanSpecularIntensity = undefined;
  340. /**
  341. * Determines the darkness of the vertex shadow.
  342. * This only takes effect when <code>enableLighting</code> is <code>true</code>.
  343. *
  344. * @type {number}
  345. * @default 0.3
  346. */
  347. this.vertexShadowDarkness = 0.3;
  348. }
  349. Object.defineProperties(Globe.prototype, {
  350. /**
  351. * Gets an ellipsoid describing the shape of this globe.
  352. * @memberof Globe.prototype
  353. * @type {Ellipsoid}
  354. */
  355. ellipsoid: {
  356. get: function () {
  357. return this._ellipsoid;
  358. },
  359. },
  360. /**
  361. * Gets the collection of image layers that will be rendered on this globe.
  362. * @memberof Globe.prototype
  363. * @type {ImageryLayerCollection}
  364. */
  365. imageryLayers: {
  366. get: function () {
  367. return this._imageryLayerCollection;
  368. },
  369. },
  370. /**
  371. * Gets an event that's raised when an imagery layer is added, shown, hidden, moved, or removed.
  372. *
  373. * @memberof Globe.prototype
  374. * @type {Event}
  375. * @readonly
  376. */
  377. imageryLayersUpdatedEvent: {
  378. get: function () {
  379. return this._surface.tileProvider.imageryLayersUpdatedEvent;
  380. },
  381. },
  382. /**
  383. * Returns <code>true</code> when the tile load queue is empty, <code>false</code> otherwise. When the load queue is empty,
  384. * all terrain and imagery for the current view have been loaded.
  385. * @memberof Globe.prototype
  386. * @type {boolean}
  387. * @readonly
  388. */
  389. tilesLoaded: {
  390. get: function () {
  391. if (!defined(this._surface)) {
  392. return true;
  393. }
  394. return (
  395. // ready is deprecated. This is here for backwards compatibility
  396. this._surface.tileProvider.ready &&
  397. this._surface._tileLoadQueueHigh.length === 0 &&
  398. this._surface._tileLoadQueueMedium.length === 0 &&
  399. this._surface._tileLoadQueueLow.length === 0
  400. );
  401. },
  402. },
  403. /**
  404. * Gets or sets the color of the globe when no imagery is available.
  405. * @memberof Globe.prototype
  406. * @type {Color}
  407. */
  408. baseColor: {
  409. get: function () {
  410. return this._surface.tileProvider.baseColor;
  411. },
  412. set: function (value) {
  413. this._surface.tileProvider.baseColor = value;
  414. },
  415. },
  416. /**
  417. * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane.
  418. *
  419. * @memberof Globe.prototype
  420. * @type {ClippingPlaneCollection}
  421. */
  422. clippingPlanes: {
  423. get: function () {
  424. return this._surface.tileProvider.clippingPlanes;
  425. },
  426. set: function (value) {
  427. this._surface.tileProvider.clippingPlanes = value;
  428. },
  429. },
  430. /**
  431. * A property specifying a {@link Rectangle} used to limit globe rendering to a cartographic area.
  432. * Defaults to the maximum extent of cartographic coordinates.
  433. *
  434. * @memberof Globe.prototype
  435. * @type {Rectangle}
  436. * @default {@link Rectangle.MAX_VALUE}
  437. */
  438. cartographicLimitRectangle: {
  439. get: function () {
  440. return this._surface.tileProvider.cartographicLimitRectangle;
  441. },
  442. set: function (value) {
  443. if (!defined(value)) {
  444. value = Rectangle.clone(Rectangle.MAX_VALUE);
  445. }
  446. this._surface.tileProvider.cartographicLimitRectangle = value;
  447. },
  448. },
  449. /**
  450. * The normal map to use for rendering waves in the ocean. Setting this property will
  451. * only have an effect if the configured terrain provider includes a water mask.
  452. * @memberof Globe.prototype
  453. * @type {string}
  454. * @default buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg')
  455. */
  456. oceanNormalMapUrl: {
  457. get: function () {
  458. return this._oceanNormalMapResource.url;
  459. },
  460. set: function (value) {
  461. this._oceanNormalMapResource.url = value;
  462. this._oceanNormalMapResourceDirty = true;
  463. },
  464. },
  465. /**
  466. * The terrain provider providing surface geometry for this globe.
  467. * @type {TerrainProvider}
  468. *
  469. * @memberof Globe.prototype
  470. * @type {TerrainProvider}
  471. *
  472. */
  473. terrainProvider: {
  474. get: function () {
  475. return this._terrainProvider;
  476. },
  477. set: function (value) {
  478. if (value !== this._terrainProvider) {
  479. this._terrainProvider = value;
  480. this._terrainProviderChanged.raiseEvent(value);
  481. if (defined(this._material)) {
  482. makeShadersDirty(this);
  483. }
  484. }
  485. },
  486. },
  487. /**
  488. * Gets an event that's raised when the terrain provider is changed
  489. *
  490. * @memberof Globe.prototype
  491. * @type {Event}
  492. * @readonly
  493. */
  494. terrainProviderChanged: {
  495. get: function () {
  496. return this._terrainProviderChanged;
  497. },
  498. },
  499. /**
  500. * Gets an event that's raised when the length of the tile load queue has changed since the last render frame. When the load queue is empty,
  501. * all terrain and imagery for the current view have been loaded. The event passes the new length of the tile load queue.
  502. *
  503. * @memberof Globe.prototype
  504. * @type {Event}
  505. */
  506. tileLoadProgressEvent: {
  507. get: function () {
  508. return this._surface.tileLoadProgressEvent;
  509. },
  510. },
  511. /**
  512. * Gets or sets the material appearance of the Globe. This can be one of several built-in {@link Material} objects or a custom material, scripted with
  513. * {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric}.
  514. * @memberof Globe.prototype
  515. * @type {Material | undefined}
  516. */
  517. material: {
  518. get: function () {
  519. return this._material;
  520. },
  521. set: function (material) {
  522. if (this._material !== material) {
  523. this._material = material;
  524. makeShadersDirty(this);
  525. }
  526. },
  527. },
  528. /**
  529. * The color to render the back side of the globe when the camera is underground or the globe is translucent,
  530. * blended with the globe color based on the camera's distance.
  531. * <br /><br />
  532. * To disable underground coloring, set <code>undergroundColor</code> to <code>undefined</code>.
  533. *
  534. * @memberof Globe.prototype
  535. * @type {Color}
  536. * @default {@link Color.BLACK}
  537. *
  538. * @see Globe#undergroundColorAlphaByDistance
  539. */
  540. undergroundColor: {
  541. get: function () {
  542. return this._undergroundColor;
  543. },
  544. set: function (value) {
  545. this._undergroundColor = Color.clone(value, this._undergroundColor);
  546. },
  547. },
  548. /**
  549. * Gets or sets the near and far distance for blending {@link Globe#undergroundColor} with the globe color.
  550. * The alpha will interpolate between the {@link NearFarScalar#nearValue} and
  551. * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds
  552. * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
  553. * Outside of these ranges the alpha remains clamped to the nearest bound. If undefined,
  554. * the underground color will not be blended with the globe color.
  555. * <br /> <br />
  556. * When the camera is above the ellipsoid the distance is computed from the nearest
  557. * point on the ellipsoid instead of the camera's position.
  558. *
  559. * @memberof Globe.prototype
  560. * @type {NearFarScalar}
  561. *
  562. * @see Globe#undergroundColor
  563. *
  564. */
  565. undergroundColorAlphaByDistance: {
  566. get: function () {
  567. return this._undergroundColorAlphaByDistance;
  568. },
  569. set: function (value) {
  570. //>>includeStart('debug', pragmas.debug);
  571. if (defined(value) && value.far < value.near) {
  572. throw new DeveloperError(
  573. "far distance must be greater than near distance."
  574. );
  575. }
  576. //>>includeEnd('debug');
  577. this._undergroundColorAlphaByDistance = NearFarScalar.clone(
  578. value,
  579. this._undergroundColorAlphaByDistance
  580. );
  581. },
  582. },
  583. /**
  584. * Properties for controlling globe translucency.
  585. *
  586. * @memberof Globe.prototype
  587. * @type {GlobeTranslucency}
  588. */
  589. translucency: {
  590. get: function () {
  591. return this._translucency;
  592. },
  593. },
  594. });
  595. function makeShadersDirty(globe) {
  596. const defines = [];
  597. const requireNormals =
  598. defined(globe._material) &&
  599. (globe._material.shaderSource.match(/slope/) ||
  600. globe._material.shaderSource.match("normalEC"));
  601. const fragmentSources = [AtmosphereCommon, GroundAtmosphere];
  602. if (
  603. defined(globe._material) &&
  604. (!requireNormals || globe._terrainProvider.requestVertexNormals)
  605. ) {
  606. fragmentSources.push(globe._material.shaderSource);
  607. defines.push("APPLY_MATERIAL");
  608. globe._surface._tileProvider.materialUniformMap = globe._material._uniforms;
  609. } else {
  610. globe._surface._tileProvider.materialUniformMap = undefined;
  611. }
  612. fragmentSources.push(GlobeFS);
  613. globe._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
  614. sources: [AtmosphereCommon, GroundAtmosphere, GlobeVS],
  615. defines: defines,
  616. });
  617. globe._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
  618. sources: fragmentSources,
  619. defines: defines,
  620. });
  621. globe._surfaceShaderSet.material = globe._material;
  622. }
  623. function createComparePickTileFunction(rayOrigin) {
  624. return function (a, b) {
  625. const aDist = BoundingSphere.distanceSquaredTo(
  626. a.pickBoundingSphere,
  627. rayOrigin
  628. );
  629. const bDist = BoundingSphere.distanceSquaredTo(
  630. b.pickBoundingSphere,
  631. rayOrigin
  632. );
  633. return aDist - bDist;
  634. };
  635. }
  636. const scratchArray = [];
  637. const scratchSphereIntersectionResult = {
  638. start: 0.0,
  639. stop: 0.0,
  640. };
  641. /**
  642. * Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates.
  643. *
  644. * @param {Ray} ray The ray to test for intersection.
  645. * @param {Scene} scene The scene.
  646. * @param {boolean} [cullBackFaces=true] Set to true to not pick back faces.
  647. * @param {Cartesian3} [result] The object onto which to store the result.
  648. * @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found. The returned position is in projected coordinates for 2D and Columbus View.
  649. *
  650. * @private
  651. */
  652. Globe.prototype.pickWorldCoordinates = function (
  653. ray,
  654. scene,
  655. cullBackFaces,
  656. result
  657. ) {
  658. //>>includeStart('debug', pragmas.debug);
  659. if (!defined(ray)) {
  660. throw new DeveloperError("ray is required");
  661. }
  662. if (!defined(scene)) {
  663. throw new DeveloperError("scene is required");
  664. }
  665. //>>includeEnd('debug');
  666. cullBackFaces = defaultValue(cullBackFaces, true);
  667. const mode = scene.mode;
  668. const projection = scene.mapProjection;
  669. const sphereIntersections = scratchArray;
  670. sphereIntersections.length = 0;
  671. const tilesToRender = this._surface._tilesToRender;
  672. let length = tilesToRender.length;
  673. let tile;
  674. let i;
  675. for (i = 0; i < length; ++i) {
  676. tile = tilesToRender[i];
  677. const surfaceTile = tile.data;
  678. if (!defined(surfaceTile)) {
  679. continue;
  680. }
  681. let boundingVolume = surfaceTile.pickBoundingSphere;
  682. if (mode !== SceneMode.SCENE3D) {
  683. surfaceTile.pickBoundingSphere = boundingVolume = BoundingSphere.fromRectangleWithHeights2D(
  684. tile.rectangle,
  685. projection,
  686. surfaceTile.tileBoundingRegion.minimumHeight,
  687. surfaceTile.tileBoundingRegion.maximumHeight,
  688. boundingVolume
  689. );
  690. Cartesian3.fromElements(
  691. boundingVolume.center.z,
  692. boundingVolume.center.x,
  693. boundingVolume.center.y,
  694. boundingVolume.center
  695. );
  696. } else if (defined(surfaceTile.renderedMesh)) {
  697. BoundingSphere.clone(
  698. surfaceTile.tileBoundingRegion.boundingSphere,
  699. boundingVolume
  700. );
  701. } else {
  702. // So wait how did we render this thing then? It shouldn't be possible to get here.
  703. continue;
  704. }
  705. const boundingSphereIntersection = IntersectionTests.raySphere(
  706. ray,
  707. boundingVolume,
  708. scratchSphereIntersectionResult
  709. );
  710. if (defined(boundingSphereIntersection)) {
  711. sphereIntersections.push(surfaceTile);
  712. }
  713. }
  714. sphereIntersections.sort(createComparePickTileFunction(ray.origin));
  715. let intersection;
  716. length = sphereIntersections.length;
  717. for (i = 0; i < length; ++i) {
  718. intersection = sphereIntersections[i].pick(
  719. ray,
  720. scene.mode,
  721. scene.mapProjection,
  722. cullBackFaces,
  723. result
  724. );
  725. if (defined(intersection)) {
  726. break;
  727. }
  728. }
  729. return intersection;
  730. };
  731. const cartoScratch = new Cartographic();
  732. /**
  733. * Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates.
  734. *
  735. * @param {Ray} ray The ray to test for intersection.
  736. * @param {Scene} scene The scene.
  737. * @param {Cartesian3} [result] The object onto which to store the result.
  738. * @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found.
  739. *
  740. * @example
  741. * // find intersection of ray through a pixel and the globe
  742. * const ray = viewer.camera.getPickRay(windowCoordinates);
  743. * const intersection = globe.pick(ray, scene);
  744. */
  745. Globe.prototype.pick = function (ray, scene, result) {
  746. result = this.pickWorldCoordinates(ray, scene, true, result);
  747. if (defined(result) && scene.mode !== SceneMode.SCENE3D) {
  748. result = Cartesian3.fromElements(result.y, result.z, result.x, result);
  749. const carto = scene.mapProjection.unproject(result, cartoScratch);
  750. result = scene.globe.ellipsoid.cartographicToCartesian(carto, result);
  751. }
  752. return result;
  753. };
  754. const scratchGetHeightCartesian = new Cartesian3();
  755. const scratchGetHeightIntersection = new Cartesian3();
  756. const scratchGetHeightCartographic = new Cartographic();
  757. const scratchGetHeightRay = new Ray();
  758. function tileIfContainsCartographic(tile, cartographic) {
  759. return defined(tile) && Rectangle.contains(tile.rectangle, cartographic)
  760. ? tile
  761. : undefined;
  762. }
  763. /**
  764. * Get the height of the surface at a given cartographic.
  765. *
  766. * @param {Cartographic} cartographic The cartographic for which to find the height.
  767. * @returns {number|undefined} The height of the cartographic or undefined if it could not be found.
  768. */
  769. Globe.prototype.getHeight = function (cartographic) {
  770. //>>includeStart('debug', pragmas.debug);
  771. if (!defined(cartographic)) {
  772. throw new DeveloperError("cartographic is required");
  773. }
  774. //>>includeEnd('debug');
  775. const levelZeroTiles = this._surface._levelZeroTiles;
  776. if (!defined(levelZeroTiles)) {
  777. return;
  778. }
  779. let tile;
  780. let i;
  781. const length = levelZeroTiles.length;
  782. for (i = 0; i < length; ++i) {
  783. tile = levelZeroTiles[i];
  784. if (Rectangle.contains(tile.rectangle, cartographic)) {
  785. break;
  786. }
  787. }
  788. if (i >= length) {
  789. return undefined;
  790. }
  791. let tileWithMesh = tile;
  792. while (defined(tile)) {
  793. tile =
  794. tileIfContainsCartographic(tile._southwestChild, cartographic) ||
  795. tileIfContainsCartographic(tile._southeastChild, cartographic) ||
  796. tileIfContainsCartographic(tile._northwestChild, cartographic) ||
  797. tile._northeastChild;
  798. if (
  799. defined(tile) &&
  800. defined(tile.data) &&
  801. defined(tile.data.renderedMesh)
  802. ) {
  803. tileWithMesh = tile;
  804. }
  805. }
  806. tile = tileWithMesh;
  807. // This tile was either rendered or culled.
  808. // It is sometimes useful to get a height from a culled tile,
  809. // e.g. when we're getting a height in order to place a billboard
  810. // on terrain, and the camera is looking at that same billboard.
  811. // The culled tile must have a valid mesh, though.
  812. if (
  813. !defined(tile) ||
  814. !defined(tile.data) ||
  815. !defined(tile.data.renderedMesh)
  816. ) {
  817. // Tile was not rendered (culled).
  818. return undefined;
  819. }
  820. const projection = this._surface._tileProvider.tilingScheme.projection;
  821. const ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid;
  822. //cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
  823. const cartesian = Cartesian3.fromRadians(
  824. cartographic.longitude,
  825. cartographic.latitude,
  826. 0.0,
  827. ellipsoid,
  828. scratchGetHeightCartesian
  829. );
  830. const ray = scratchGetHeightRay;
  831. const surfaceNormal = ellipsoid.geodeticSurfaceNormal(
  832. cartesian,
  833. ray.direction
  834. );
  835. // Try to find the intersection point between the surface normal and z-axis.
  836. // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
  837. const rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(
  838. cartesian,
  839. 11500.0,
  840. ray.origin
  841. );
  842. // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
  843. if (!defined(rayOrigin)) {
  844. // intersection point is outside the ellipsoid, try other value
  845. // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
  846. let minimumHeight;
  847. if (defined(tile.data.tileBoundingRegion)) {
  848. minimumHeight = tile.data.tileBoundingRegion.minimumHeight;
  849. }
  850. const magnitude = Math.min(defaultValue(minimumHeight, 0.0), -11500.0);
  851. // multiply by the *positive* value of the magnitude
  852. const vectorToMinimumPoint = Cartesian3.multiplyByScalar(
  853. surfaceNormal,
  854. Math.abs(magnitude) + 1,
  855. scratchGetHeightIntersection
  856. );
  857. Cartesian3.subtract(cartesian, vectorToMinimumPoint, ray.origin);
  858. }
  859. const intersection = tile.data.pick(
  860. ray,
  861. undefined,
  862. projection,
  863. false,
  864. scratchGetHeightIntersection
  865. );
  866. if (!defined(intersection)) {
  867. return undefined;
  868. }
  869. return ellipsoid.cartesianToCartographic(
  870. intersection,
  871. scratchGetHeightCartographic
  872. ).height;
  873. };
  874. /**
  875. * @private
  876. */
  877. Globe.prototype.update = function (frameState) {
  878. if (!this.show) {
  879. return;
  880. }
  881. if (frameState.passes.render) {
  882. this._surface.update(frameState);
  883. }
  884. };
  885. /**
  886. * @private
  887. */
  888. Globe.prototype.beginFrame = function (frameState) {
  889. const surface = this._surface;
  890. const tileProvider = surface.tileProvider;
  891. const terrainProvider = this.terrainProvider;
  892. const hasWaterMask =
  893. this.showWaterEffect &&
  894. defined(terrainProvider) &&
  895. terrainProvider.hasWaterMask &&
  896. // ready is deprecated; This is here for backwards compatibility
  897. terrainProvider._ready &&
  898. terrainProvider.hasWaterMask;
  899. if (hasWaterMask && this._oceanNormalMapResourceDirty) {
  900. // url changed, load new normal map asynchronously
  901. this._oceanNormalMapResourceDirty = false;
  902. const oceanNormalMapResource = this._oceanNormalMapResource;
  903. const oceanNormalMapUrl = oceanNormalMapResource.url;
  904. if (defined(oceanNormalMapUrl)) {
  905. const that = this;
  906. oceanNormalMapResource.fetchImage().then(function (image) {
  907. if (oceanNormalMapUrl !== that._oceanNormalMapResource.url) {
  908. // url changed while we were loading
  909. return;
  910. }
  911. that._oceanNormalMap =
  912. that._oceanNormalMap && that._oceanNormalMap.destroy();
  913. that._oceanNormalMap = new Texture({
  914. context: frameState.context,
  915. source: image,
  916. });
  917. });
  918. } else {
  919. this._oceanNormalMap =
  920. this._oceanNormalMap && this._oceanNormalMap.destroy();
  921. }
  922. }
  923. const pass = frameState.passes;
  924. const mode = frameState.mode;
  925. if (pass.render) {
  926. if (this.showGroundAtmosphere) {
  927. this._zoomedOutOceanSpecularIntensity = 0.4;
  928. } else {
  929. this._zoomedOutOceanSpecularIntensity = 0.5;
  930. }
  931. surface.maximumScreenSpaceError = this.maximumScreenSpaceError;
  932. surface.tileCacheSize = this.tileCacheSize;
  933. surface.loadingDescendantLimit = this.loadingDescendantLimit;
  934. surface.preloadAncestors = this.preloadAncestors;
  935. surface.preloadSiblings = this.preloadSiblings;
  936. tileProvider.terrainProvider = this.terrainProvider;
  937. tileProvider.lightingFadeOutDistance = this.lightingFadeOutDistance;
  938. tileProvider.lightingFadeInDistance = this.lightingFadeInDistance;
  939. tileProvider.nightFadeOutDistance = this.nightFadeOutDistance;
  940. tileProvider.nightFadeInDistance = this.nightFadeInDistance;
  941. tileProvider.zoomedOutOceanSpecularIntensity =
  942. mode === SceneMode.SCENE3D ? this._zoomedOutOceanSpecularIntensity : 0.0;
  943. tileProvider.hasWaterMask = hasWaterMask;
  944. tileProvider.oceanNormalMap = this._oceanNormalMap;
  945. tileProvider.enableLighting = this.enableLighting;
  946. tileProvider.dynamicAtmosphereLighting = this.dynamicAtmosphereLighting;
  947. tileProvider.dynamicAtmosphereLightingFromSun = this.dynamicAtmosphereLightingFromSun;
  948. tileProvider.showGroundAtmosphere = this.showGroundAtmosphere;
  949. tileProvider.atmosphereLightIntensity = this.atmosphereLightIntensity;
  950. tileProvider.atmosphereRayleighCoefficient = this.atmosphereRayleighCoefficient;
  951. tileProvider.atmosphereMieCoefficient = this.atmosphereMieCoefficient;
  952. tileProvider.atmosphereRayleighScaleHeight = this.atmosphereRayleighScaleHeight;
  953. tileProvider.atmosphereMieScaleHeight = this.atmosphereMieScaleHeight;
  954. tileProvider.atmosphereMieAnisotropy = this.atmosphereMieAnisotropy;
  955. tileProvider.shadows = this.shadows;
  956. tileProvider.hueShift = this.atmosphereHueShift;
  957. tileProvider.saturationShift = this.atmosphereSaturationShift;
  958. tileProvider.brightnessShift = this.atmosphereBrightnessShift;
  959. tileProvider.fillHighlightColor = this.fillHighlightColor;
  960. tileProvider.showSkirts = this.showSkirts;
  961. tileProvider.backFaceCulling = this.backFaceCulling;
  962. tileProvider.vertexShadowDarkness = this.vertexShadowDarkness;
  963. tileProvider.undergroundColor = this._undergroundColor;
  964. tileProvider.undergroundColorAlphaByDistance = this._undergroundColorAlphaByDistance;
  965. tileProvider.lambertDiffuseMultiplier = this.lambertDiffuseMultiplier;
  966. surface.beginFrame(frameState);
  967. }
  968. };
  969. /**
  970. * @private
  971. */
  972. Globe.prototype.render = function (frameState) {
  973. if (!this.show) {
  974. return;
  975. }
  976. if (defined(this._material)) {
  977. this._material.update(frameState.context);
  978. }
  979. this._surface.render(frameState);
  980. };
  981. /**
  982. * @private
  983. */
  984. Globe.prototype.endFrame = function (frameState) {
  985. if (!this.show) {
  986. return;
  987. }
  988. if (frameState.passes.render) {
  989. this._surface.endFrame(frameState);
  990. }
  991. };
  992. /**
  993. * Returns true if this object was destroyed; otherwise, false.
  994. * <br /><br />
  995. * If this object was destroyed, it should not be used; calling any function other than
  996. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  997. *
  998. * @returns {boolean} True if this object was destroyed; otherwise, false.
  999. *
  1000. * @see Globe#destroy
  1001. */
  1002. Globe.prototype.isDestroyed = function () {
  1003. return false;
  1004. };
  1005. /**
  1006. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  1007. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  1008. * <br /><br />
  1009. * Once an object is destroyed, it should not be used; calling any function other than
  1010. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  1011. * assign the return value (<code>undefined</code>) to the object as done in the example.
  1012. *
  1013. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  1014. *
  1015. *
  1016. * @example
  1017. * globe = globe && globe.destroy();
  1018. *
  1019. * @see Globe#isDestroyed
  1020. */
  1021. Globe.prototype.destroy = function () {
  1022. this._surfaceShaderSet =
  1023. this._surfaceShaderSet && this._surfaceShaderSet.destroy();
  1024. this._surface = this._surface && this._surface.destroy();
  1025. this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();
  1026. return destroyObject(this);
  1027. };
  1028. export default Globe;