| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135 | import BoundingSphere from "../Core/BoundingSphere.js";import buildModuleUrl from "../Core/buildModuleUrl.js";import Cartesian3 from "../Core/Cartesian3.js";import Cartographic from "../Core/Cartographic.js";import Color from "../Core/Color.js";import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import DeveloperError from "../Core/DeveloperError.js";import Ellipsoid from "../Core/Ellipsoid.js";import EllipsoidTerrainProvider from "../Core/EllipsoidTerrainProvider.js";import Event from "../Core/Event.js";import IntersectionTests from "../Core/IntersectionTests.js";import NearFarScalar from "../Core/NearFarScalar.js";import Ray from "../Core/Ray.js";import Rectangle from "../Core/Rectangle.js";import Resource from "../Core/Resource.js";import ShaderSource from "../Renderer/ShaderSource.js";import Texture from "../Renderer/Texture.js";import GlobeFS from "../Shaders/GlobeFS.js";import GlobeVS from "../Shaders/GlobeVS.js";import AtmosphereCommon from "../Shaders/AtmosphereCommon.js";import GroundAtmosphere from "../Shaders/GroundAtmosphere.js";import GlobeSurfaceShaderSet from "./GlobeSurfaceShaderSet.js";import GlobeSurfaceTileProvider from "./GlobeSurfaceTileProvider.js";import GlobeTranslucency from "./GlobeTranslucency.js";import ImageryLayerCollection from "./ImageryLayerCollection.js";import QuadtreePrimitive from "./QuadtreePrimitive.js";import SceneMode from "./SceneMode.js";import ShadowMode from "./ShadowMode.js";/** * The globe rendered in the scene, including its terrain ({@link Globe#terrainProvider}) * and imagery layers ({@link Globe#imageryLayers}).  Access the globe using {@link Scene#globe}. * * @alias Globe * @constructor * * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] Determines the size and shape of the * globe. */function Globe(ellipsoid) {  ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);  const terrainProvider = new EllipsoidTerrainProvider({    ellipsoid: ellipsoid,  });  const imageryLayerCollection = new ImageryLayerCollection();  this._ellipsoid = ellipsoid;  this._imageryLayerCollection = imageryLayerCollection;  this._surfaceShaderSet = new GlobeSurfaceShaderSet();  this._material = undefined;  this._surface = new QuadtreePrimitive({    tileProvider: new GlobeSurfaceTileProvider({      terrainProvider: terrainProvider,      imageryLayers: imageryLayerCollection,      surfaceShaderSet: this._surfaceShaderSet,    }),  });  this._terrainProvider = terrainProvider;  this._terrainProviderChanged = new Event();  this._undergroundColor = Color.clone(Color.BLACK);  this._undergroundColorAlphaByDistance = new NearFarScalar(    ellipsoid.maximumRadius / 1000.0,    0.0,    ellipsoid.maximumRadius / 5.0,    1.0  );  this._translucency = new GlobeTranslucency();  makeShadersDirty(this);  /**   * Determines if the globe will be shown.   *   * @type {boolean}   * @default true   */  this.show = true;  this._oceanNormalMapResourceDirty = true;  this._oceanNormalMapResource = new Resource({    url: buildModuleUrl("Assets/Textures/waterNormalsSmall.jpg"),  });  /**   * The maximum screen-space error used to drive level-of-detail refinement.  Higher   * values will provide better performance but lower visual quality.   *   * @type {number}   * @default 2   */  this.maximumScreenSpaceError = 2;  /**   * The size of the terrain tile cache, expressed as a number of tiles.  Any additional   * tiles beyond this number will be freed, as long as they aren't needed for rendering   * this frame.  A larger number will consume more memory but will show detail faster   * when, for example, zooming out and then back in.   *   * @type {number}   * @default 100   */  this.tileCacheSize = 100;  /**   * Gets or sets the number of loading descendant tiles that is considered "too many".   * If a tile has too many loading descendants, that tile will be loaded and rendered before any of   * its descendants are loaded and rendered. This means more feedback for the user that something   * is happening at the cost of a longer overall load time. Setting this to 0 will cause each   * tile level to be loaded successively, significantly increasing load time. Setting it to a large   * number (e.g. 1000) will minimize the number of tiles that are loaded but tend to make   * detail appear all at once after a long wait.   * @type {number}   * @default 20   */  this.loadingDescendantLimit = 20;  /**   * Gets or sets a value indicating whether the ancestors of rendered tiles should be preloaded.   * Setting this to true optimizes the zoom-out experience and provides more detail in   * newly-exposed areas when panning. The down side is that it requires loading more tiles.   * @type {boolean}   * @default true   */  this.preloadAncestors = true;  /**   * Gets or sets a value indicating whether the siblings of rendered tiles should be preloaded.   * Setting this to true causes tiles with the same parent as a rendered tile to be loaded, even   * if they are culled. Setting this to true may provide a better panning experience at the   * cost of loading more tiles.   * @type {boolean}   * @default false   */  this.preloadSiblings = false;  /**   * The color to use to highlight terrain fill tiles. If undefined, fill tiles are not   * highlighted at all. The alpha value is used to alpha blend with the tile's   * actual color. Because terrain fill tiles do not represent the actual terrain surface,   * it may be useful in some applications to indicate visually that they are not to be trusted.   * @type {Color}   * @default undefined   */  this.fillHighlightColor = undefined;  /**   * Enable lighting the globe with the scene's light source.   *   * @type {boolean}   * @default false   */  this.enableLighting = false;  /**   * A multiplier to adjust terrain lambert lighting.   * This number is multiplied by the result of <code>czm_getLambertDiffuse</code> in GlobeFS.glsl.   * This only takes effect when <code>enableLighting</code> is <code>true</code>.   *   * @type {number}   * @default 0.9   */  this.lambertDiffuseMultiplier = 0.9;  /**   * Enable dynamic lighting effects on atmosphere and fog. This only takes effect   * when <code>enableLighting</code> is <code>true</code>.   *   * @type {boolean}   * @default true   */  this.dynamicAtmosphereLighting = true;  /**   * Whether dynamic atmosphere lighting uses the sun direction instead of the scene's   * light direction. This only takes effect when <code>enableLighting</code> and   * <code>dynamicAtmosphereLighting</code> are <code>true</code>.   *   * @type {boolean}   * @default false   */  this.dynamicAtmosphereLightingFromSun = false;  /**   * Enable the ground atmosphere, which is drawn over the globe when viewed from a distance between <code>lightingFadeInDistance</code> and <code>lightingFadeOutDistance</code>.   *   * @type {boolean}   * @default true   */  this.showGroundAtmosphere = true;  /**   * The intensity of the light that is used for computing the ground atmosphere color.   *   * @type {number}   * @default 10.0   */  this.atmosphereLightIntensity = 10.0;  /**   * The Rayleigh scattering coefficient used in the atmospheric scattering equations for the ground atmosphere.   *   * @type {Cartesian3}   * @default Cartesian3(5.5e-6, 13.0e-6, 28.4e-6)   */  this.atmosphereRayleighCoefficient = new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6);  /**   * The Mie scattering coefficient used in the atmospheric scattering equations for the ground atmosphere.   *   * @type {Cartesian3}   * @default Cartesian3(21e-6, 21e-6, 21e-6)   */  this.atmosphereMieCoefficient = new Cartesian3(21e-6, 21e-6, 21e-6);  /**   * The Rayleigh scale height used in the atmospheric scattering equations for the ground atmosphere, in meters.   *   * @type {number}   * @default 10000.0   */  this.atmosphereRayleighScaleHeight = 10000.0;  /**   * The Mie scale height used in the atmospheric scattering equations for the ground atmosphere, in meters.   *   * @type {number}   * @default 3200.0   */  this.atmosphereMieScaleHeight = 3200.0;  /**   * The anisotropy of the medium to consider for Mie scattering.   * <p>   * Valid values are between -1.0 and 1.0.   * </p>   * @type {number}   * @default 0.9   */  this.atmosphereMieAnisotropy = 0.9;  /**   * The distance where everything becomes lit. This only takes effect   * when <code>enableLighting</code> or <code>showGroundAtmosphere</code> is <code>true</code>.   *   * @type {number}   * @default 10000000.0   */  this.lightingFadeOutDistance = 1.0e7;  /**   * The distance where lighting resumes. This only takes effect   * when <code>enableLighting</code> or <code>showGroundAtmosphere</code> is <code>true</code>.   *   * @type {number}   * @default 20000000.0   */  this.lightingFadeInDistance = 2.0e7;  /**   * The distance where the darkness of night from the ground atmosphere fades out to a lit ground atmosphere.   * This only takes effect when <code>showGroundAtmosphere</code>, <code>enableLighting</code>, and   * <code>dynamicAtmosphereLighting</code> are <code>true</code>.   *   * @type {number}   * @default 10000000.0   */  this.nightFadeOutDistance = 1.0e7;  /**   * The distance where the darkness of night from the ground atmosphere fades in to an unlit ground atmosphere.   * This only takes effect when <code>showGroundAtmosphere</code>, <code>enableLighting</code>, and   * <code>dynamicAtmosphereLighting</code> are <code>true</code>.   *   * @type {number}   * @default 50000000.0   */  this.nightFadeInDistance = 5.0e7;  /**   * True if an animated wave effect should be shown in areas of the globe   * covered by water; otherwise, false.  This property is ignored if the   * <code>terrainProvider</code> does not provide a water mask.   *   * @type {boolean}   * @default true   */  this.showWaterEffect = true;  /**   * True if primitives such as billboards, polylines, labels, etc. should be depth-tested   * against the terrain surface, or false if such primitives should always be drawn on top   * of terrain unless they're on the opposite side of the globe.  The disadvantage of depth   * testing primitives against terrain is that slight numerical noise or terrain level-of-detail   * switched can sometimes make a primitive that should be on the surface disappear underneath it.   *   * @type {boolean}   * @default false   *   */  this.depthTestAgainstTerrain = false;  /**   * Determines whether the globe casts or receives shadows from light sources. Setting the globe   * to cast shadows may impact performance since the terrain is rendered again from the light's perspective.   * Currently only terrain that is in view casts shadows. By default the globe does not cast shadows.   *   * @type {ShadowMode}   * @default ShadowMode.RECEIVE_ONLY   */  this.shadows = ShadowMode.RECEIVE_ONLY;  /**   * The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift).   * A hue shift of 1.0 indicates a complete rotation of the hues available.   * @type {number}   * @default 0.0   */  this.atmosphereHueShift = 0.0;  /**   * The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift).   * A saturation shift of -1.0 is monochrome.   * @type {number}   * @default 0.0   */  this.atmosphereSaturationShift = 0.0;  /**   * The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift).   * A brightness shift of -1.0 is complete darkness, which will let space show through.   * @type {number}   * @default 0.0   */  this.atmosphereBrightnessShift = 0.0;  /**   * A scalar used to exaggerate the terrain. Defaults to <code>1.0</code> (no exaggeration).   * A value of <code>2.0</code> scales the terrain by 2x.   * A value of <code>0.0</code> makes the terrain completely flat.   * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.   * @type {number}   * @default 1.0   */  this.terrainExaggeration = 1.0;  /**   * The height from which terrain is exaggerated. Defaults to <code>0.0</code> (scaled relative to ellipsoid surface).   * Terrain that is above this height will scale upwards and terrain that is below this height will scale downwards.   * Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.   * If {@link Globe#terrainExaggeration} is <code>1.0</code> this value will have no effect.   * @type {number}   * @default 0.0   */  this.terrainExaggerationRelativeHeight = 0.0;  /**   * Whether to show terrain skirts. Terrain skirts are geometry extending downwards from a tile's edges used to hide seams between neighboring tiles.   * Skirts are always hidden when the camera is underground or translucency is enabled.   *   * @type {boolean}   * @default true   */  this.showSkirts = true;  /**   * Whether to cull back-facing terrain. Back faces are not culled when the camera is underground or translucency is enabled.   *   * @type {boolean}   * @default true   */  this.backFaceCulling = true;  this._oceanNormalMap = undefined;  this._zoomedOutOceanSpecularIntensity = undefined;  /**   * Determines the darkness of the vertex shadow.   * This only takes effect when <code>enableLighting</code> is <code>true</code>.   *   * @type {number}   * @default 0.3   */  this.vertexShadowDarkness = 0.3;}Object.defineProperties(Globe.prototype, {  /**   * Gets an ellipsoid describing the shape of this globe.   * @memberof Globe.prototype   * @type {Ellipsoid}   */  ellipsoid: {    get: function () {      return this._ellipsoid;    },  },  /**   * Gets the collection of image layers that will be rendered on this globe.   * @memberof Globe.prototype   * @type {ImageryLayerCollection}   */  imageryLayers: {    get: function () {      return this._imageryLayerCollection;    },  },  /**   * Gets an event that's raised when an imagery layer is added, shown, hidden, moved, or removed.   *   * @memberof Globe.prototype   * @type {Event}   * @readonly   */  imageryLayersUpdatedEvent: {    get: function () {      return this._surface.tileProvider.imageryLayersUpdatedEvent;    },  },  /**   * Returns <code>true</code> when the tile load queue is empty, <code>false</code> otherwise.  When the load queue is empty,   * all terrain and imagery for the current view have been loaded.   * @memberof Globe.prototype   * @type {boolean}   * @readonly   */  tilesLoaded: {    get: function () {      if (!defined(this._surface)) {        return true;      }      return (        // ready is deprecated. This is here for backwards compatibility        this._surface.tileProvider.ready &&        this._surface._tileLoadQueueHigh.length === 0 &&        this._surface._tileLoadQueueMedium.length === 0 &&        this._surface._tileLoadQueueLow.length === 0      );    },  },  /**   * Gets or sets the color of the globe when no imagery is available.   * @memberof Globe.prototype   * @type {Color}   */  baseColor: {    get: function () {      return this._surface.tileProvider.baseColor;    },    set: function (value) {      this._surface.tileProvider.baseColor = value;    },  },  /**   * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane.   *   * @memberof Globe.prototype   * @type {ClippingPlaneCollection}   */  clippingPlanes: {    get: function () {      return this._surface.tileProvider.clippingPlanes;    },    set: function (value) {      this._surface.tileProvider.clippingPlanes = value;    },  },  /**   * A property specifying a {@link Rectangle} used to limit globe rendering to a cartographic area.   * Defaults to the maximum extent of cartographic coordinates.   *   * @memberof Globe.prototype   * @type {Rectangle}   * @default {@link Rectangle.MAX_VALUE}   */  cartographicLimitRectangle: {    get: function () {      return this._surface.tileProvider.cartographicLimitRectangle;    },    set: function (value) {      if (!defined(value)) {        value = Rectangle.clone(Rectangle.MAX_VALUE);      }      this._surface.tileProvider.cartographicLimitRectangle = value;    },  },  /**   * The normal map to use for rendering waves in the ocean.  Setting this property will   * only have an effect if the configured terrain provider includes a water mask.   * @memberof Globe.prototype   * @type {string}   * @default buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg')   */  oceanNormalMapUrl: {    get: function () {      return this._oceanNormalMapResource.url;    },    set: function (value) {      this._oceanNormalMapResource.url = value;      this._oceanNormalMapResourceDirty = true;    },  },  /**   * The terrain provider providing surface geometry for this globe.   * @type {TerrainProvider}   *   * @memberof Globe.prototype   * @type {TerrainProvider}   *   */  terrainProvider: {    get: function () {      return this._terrainProvider;    },    set: function (value) {      if (value !== this._terrainProvider) {        this._terrainProvider = value;        this._terrainProviderChanged.raiseEvent(value);        if (defined(this._material)) {          makeShadersDirty(this);        }      }    },  },  /**   * Gets an event that's raised when the terrain provider is changed   *   * @memberof Globe.prototype   * @type {Event}   * @readonly   */  terrainProviderChanged: {    get: function () {      return this._terrainProviderChanged;    },  },  /**   * 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,   * all terrain and imagery for the current view have been loaded.  The event passes the new length of the tile load queue.   *   * @memberof Globe.prototype   * @type {Event}   */  tileLoadProgressEvent: {    get: function () {      return this._surface.tileLoadProgressEvent;    },  },  /**   * 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   * {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric}.   * @memberof Globe.prototype   * @type {Material | undefined}   */  material: {    get: function () {      return this._material;    },    set: function (material) {      if (this._material !== material) {        this._material = material;        makeShadersDirty(this);      }    },  },  /**   * The color to render the back side of the globe when the camera is underground or the globe is translucent,   * blended with the globe color based on the camera's distance.   * <br /><br />   * To disable underground coloring, set <code>undergroundColor</code> to <code>undefined</code>.   *   * @memberof Globe.prototype   * @type {Color}   * @default {@link Color.BLACK}   *   * @see Globe#undergroundColorAlphaByDistance   */  undergroundColor: {    get: function () {      return this._undergroundColor;    },    set: function (value) {      this._undergroundColor = Color.clone(value, this._undergroundColor);    },  },  /**   * Gets or sets the near and far distance for blending {@link Globe#undergroundColor} with the globe color.   * The alpha will interpolate between the {@link NearFarScalar#nearValue} and   * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds   * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.   * Outside of these ranges the alpha remains clamped to the nearest bound. If undefined,   * the underground color will not be blended with the globe color.   * <br /> <br />   * When the camera is above the ellipsoid the distance is computed from the nearest   * point on the ellipsoid instead of the camera's position.   *   * @memberof Globe.prototype   * @type {NearFarScalar}   *   * @see Globe#undergroundColor   *   */  undergroundColorAlphaByDistance: {    get: function () {      return this._undergroundColorAlphaByDistance;    },    set: function (value) {      //>>includeStart('debug', pragmas.debug);      if (defined(value) && value.far < value.near) {        throw new DeveloperError(          "far distance must be greater than near distance."        );      }      //>>includeEnd('debug');      this._undergroundColorAlphaByDistance = NearFarScalar.clone(        value,        this._undergroundColorAlphaByDistance      );    },  },  /**   * Properties for controlling globe translucency.   *   * @memberof Globe.prototype   * @type {GlobeTranslucency}   */  translucency: {    get: function () {      return this._translucency;    },  },});function makeShadersDirty(globe) {  const defines = [];  const requireNormals =    defined(globe._material) &&    (globe._material.shaderSource.match(/slope/) ||      globe._material.shaderSource.match("normalEC"));  const fragmentSources = [AtmosphereCommon, GroundAtmosphere];  if (    defined(globe._material) &&    (!requireNormals || globe._terrainProvider.requestVertexNormals)  ) {    fragmentSources.push(globe._material.shaderSource);    defines.push("APPLY_MATERIAL");    globe._surface._tileProvider.materialUniformMap = globe._material._uniforms;  } else {    globe._surface._tileProvider.materialUniformMap = undefined;  }  fragmentSources.push(GlobeFS);  globe._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({    sources: [AtmosphereCommon, GroundAtmosphere, GlobeVS],    defines: defines,  });  globe._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({    sources: fragmentSources,    defines: defines,  });  globe._surfaceShaderSet.material = globe._material;}function createComparePickTileFunction(rayOrigin) {  return function (a, b) {    const aDist = BoundingSphere.distanceSquaredTo(      a.pickBoundingSphere,      rayOrigin    );    const bDist = BoundingSphere.distanceSquaredTo(      b.pickBoundingSphere,      rayOrigin    );    return aDist - bDist;  };}const scratchArray = [];const scratchSphereIntersectionResult = {  start: 0.0,  stop: 0.0,};/** * Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {Scene} scene The scene. * @param {boolean} [cullBackFaces=true] Set to true to not pick back faces. * @param {Cartesian3} [result] The object onto which to store the result. * @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. * * @private */Globe.prototype.pickWorldCoordinates = function (  ray,  scene,  cullBackFaces,  result) {  //>>includeStart('debug', pragmas.debug);  if (!defined(ray)) {    throw new DeveloperError("ray is required");  }  if (!defined(scene)) {    throw new DeveloperError("scene is required");  }  //>>includeEnd('debug');  cullBackFaces = defaultValue(cullBackFaces, true);  const mode = scene.mode;  const projection = scene.mapProjection;  const sphereIntersections = scratchArray;  sphereIntersections.length = 0;  const tilesToRender = this._surface._tilesToRender;  let length = tilesToRender.length;  let tile;  let i;  for (i = 0; i < length; ++i) {    tile = tilesToRender[i];    const surfaceTile = tile.data;    if (!defined(surfaceTile)) {      continue;    }    let boundingVolume = surfaceTile.pickBoundingSphere;    if (mode !== SceneMode.SCENE3D) {      surfaceTile.pickBoundingSphere = boundingVolume = BoundingSphere.fromRectangleWithHeights2D(        tile.rectangle,        projection,        surfaceTile.tileBoundingRegion.minimumHeight,        surfaceTile.tileBoundingRegion.maximumHeight,        boundingVolume      );      Cartesian3.fromElements(        boundingVolume.center.z,        boundingVolume.center.x,        boundingVolume.center.y,        boundingVolume.center      );    } else if (defined(surfaceTile.renderedMesh)) {      BoundingSphere.clone(        surfaceTile.tileBoundingRegion.boundingSphere,        boundingVolume      );    } else {      // So wait how did we render this thing then? It shouldn't be possible to get here.      continue;    }    const boundingSphereIntersection = IntersectionTests.raySphere(      ray,      boundingVolume,      scratchSphereIntersectionResult    );    if (defined(boundingSphereIntersection)) {      sphereIntersections.push(surfaceTile);    }  }  sphereIntersections.sort(createComparePickTileFunction(ray.origin));  let intersection;  length = sphereIntersections.length;  for (i = 0; i < length; ++i) {    intersection = sphereIntersections[i].pick(      ray,      scene.mode,      scene.mapProjection,      cullBackFaces,      result    );    if (defined(intersection)) {      break;    }  }  return intersection;};const cartoScratch = new Cartographic();/** * Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {Scene} scene The scene. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found. * * @example * // find intersection of ray through a pixel and the globe * const ray = viewer.camera.getPickRay(windowCoordinates); * const intersection = globe.pick(ray, scene); */Globe.prototype.pick = function (ray, scene, result) {  result = this.pickWorldCoordinates(ray, scene, true, result);  if (defined(result) && scene.mode !== SceneMode.SCENE3D) {    result = Cartesian3.fromElements(result.y, result.z, result.x, result);    const carto = scene.mapProjection.unproject(result, cartoScratch);    result = scene.globe.ellipsoid.cartographicToCartesian(carto, result);  }  return result;};const scratchGetHeightCartesian = new Cartesian3();const scratchGetHeightIntersection = new Cartesian3();const scratchGetHeightCartographic = new Cartographic();const scratchGetHeightRay = new Ray();function tileIfContainsCartographic(tile, cartographic) {  return defined(tile) && Rectangle.contains(tile.rectangle, cartographic)    ? tile    : undefined;}/** * Get the height of the surface at a given cartographic. * * @param {Cartographic} cartographic The cartographic for which to find the height. * @returns {number|undefined} The height of the cartographic or undefined if it could not be found. */Globe.prototype.getHeight = function (cartographic) {  //>>includeStart('debug', pragmas.debug);  if (!defined(cartographic)) {    throw new DeveloperError("cartographic is required");  }  //>>includeEnd('debug');  const levelZeroTiles = this._surface._levelZeroTiles;  if (!defined(levelZeroTiles)) {    return;  }  let tile;  let i;  const length = levelZeroTiles.length;  for (i = 0; i < length; ++i) {    tile = levelZeroTiles[i];    if (Rectangle.contains(tile.rectangle, cartographic)) {      break;    }  }  if (i >= length) {    return undefined;  }  let tileWithMesh = tile;  while (defined(tile)) {    tile =      tileIfContainsCartographic(tile._southwestChild, cartographic) ||      tileIfContainsCartographic(tile._southeastChild, cartographic) ||      tileIfContainsCartographic(tile._northwestChild, cartographic) ||      tile._northeastChild;    if (      defined(tile) &&      defined(tile.data) &&      defined(tile.data.renderedMesh)    ) {      tileWithMesh = tile;    }  }  tile = tileWithMesh;  // This tile was either rendered or culled.  // It is sometimes useful to get a height from a culled tile,  // e.g. when we're getting a height in order to place a billboard  // on terrain, and the camera is looking at that same billboard.  // The culled tile must have a valid mesh, though.  if (    !defined(tile) ||    !defined(tile.data) ||    !defined(tile.data.renderedMesh)  ) {    // Tile was not rendered (culled).    return undefined;  }  const projection = this._surface._tileProvider.tilingScheme.projection;  const ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid;  //cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`  const cartesian = Cartesian3.fromRadians(    cartographic.longitude,    cartographic.latitude,    0.0,    ellipsoid,    scratchGetHeightCartesian  );  const ray = scratchGetHeightRay;  const surfaceNormal = ellipsoid.geodeticSurfaceNormal(    cartesian,    ray.direction  );  // Try to find the intersection point between the surface normal and z-axis.  // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider  const rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(    cartesian,    11500.0,    ray.origin  );  // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid  if (!defined(rayOrigin)) {    // intersection point is outside the ellipsoid, try other value    // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider    let minimumHeight;    if (defined(tile.data.tileBoundingRegion)) {      minimumHeight = tile.data.tileBoundingRegion.minimumHeight;    }    const magnitude = Math.min(defaultValue(minimumHeight, 0.0), -11500.0);    // multiply by the *positive* value of the magnitude    const vectorToMinimumPoint = Cartesian3.multiplyByScalar(      surfaceNormal,      Math.abs(magnitude) + 1,      scratchGetHeightIntersection    );    Cartesian3.subtract(cartesian, vectorToMinimumPoint, ray.origin);  }  const intersection = tile.data.pick(    ray,    undefined,    projection,    false,    scratchGetHeightIntersection  );  if (!defined(intersection)) {    return undefined;  }  return ellipsoid.cartesianToCartographic(    intersection,    scratchGetHeightCartographic  ).height;};/** * @private */Globe.prototype.update = function (frameState) {  if (!this.show) {    return;  }  if (frameState.passes.render) {    this._surface.update(frameState);  }};/** * @private */Globe.prototype.beginFrame = function (frameState) {  const surface = this._surface;  const tileProvider = surface.tileProvider;  const terrainProvider = this.terrainProvider;  const hasWaterMask =    this.showWaterEffect &&    defined(terrainProvider) &&    terrainProvider.hasWaterMask &&    // ready is deprecated; This is here for backwards compatibility    terrainProvider._ready &&    terrainProvider.hasWaterMask;  if (hasWaterMask && this._oceanNormalMapResourceDirty) {    // url changed, load new normal map asynchronously    this._oceanNormalMapResourceDirty = false;    const oceanNormalMapResource = this._oceanNormalMapResource;    const oceanNormalMapUrl = oceanNormalMapResource.url;    if (defined(oceanNormalMapUrl)) {      const that = this;      oceanNormalMapResource.fetchImage().then(function (image) {        if (oceanNormalMapUrl !== that._oceanNormalMapResource.url) {          // url changed while we were loading          return;        }        that._oceanNormalMap =          that._oceanNormalMap && that._oceanNormalMap.destroy();        that._oceanNormalMap = new Texture({          context: frameState.context,          source: image,        });      });    } else {      this._oceanNormalMap =        this._oceanNormalMap && this._oceanNormalMap.destroy();    }  }  const pass = frameState.passes;  const mode = frameState.mode;  if (pass.render) {    if (this.showGroundAtmosphere) {      this._zoomedOutOceanSpecularIntensity = 0.4;    } else {      this._zoomedOutOceanSpecularIntensity = 0.5;    }    surface.maximumScreenSpaceError = this.maximumScreenSpaceError;    surface.tileCacheSize = this.tileCacheSize;    surface.loadingDescendantLimit = this.loadingDescendantLimit;    surface.preloadAncestors = this.preloadAncestors;    surface.preloadSiblings = this.preloadSiblings;    tileProvider.terrainProvider = this.terrainProvider;    tileProvider.lightingFadeOutDistance = this.lightingFadeOutDistance;    tileProvider.lightingFadeInDistance = this.lightingFadeInDistance;    tileProvider.nightFadeOutDistance = this.nightFadeOutDistance;    tileProvider.nightFadeInDistance = this.nightFadeInDistance;    tileProvider.zoomedOutOceanSpecularIntensity =      mode === SceneMode.SCENE3D ? this._zoomedOutOceanSpecularIntensity : 0.0;    tileProvider.hasWaterMask = hasWaterMask;    tileProvider.oceanNormalMap = this._oceanNormalMap;    tileProvider.enableLighting = this.enableLighting;    tileProvider.dynamicAtmosphereLighting = this.dynamicAtmosphereLighting;    tileProvider.dynamicAtmosphereLightingFromSun = this.dynamicAtmosphereLightingFromSun;    tileProvider.showGroundAtmosphere = this.showGroundAtmosphere;    tileProvider.atmosphereLightIntensity = this.atmosphereLightIntensity;    tileProvider.atmosphereRayleighCoefficient = this.atmosphereRayleighCoefficient;    tileProvider.atmosphereMieCoefficient = this.atmosphereMieCoefficient;    tileProvider.atmosphereRayleighScaleHeight = this.atmosphereRayleighScaleHeight;    tileProvider.atmosphereMieScaleHeight = this.atmosphereMieScaleHeight;    tileProvider.atmosphereMieAnisotropy = this.atmosphereMieAnisotropy;    tileProvider.shadows = this.shadows;    tileProvider.hueShift = this.atmosphereHueShift;    tileProvider.saturationShift = this.atmosphereSaturationShift;    tileProvider.brightnessShift = this.atmosphereBrightnessShift;    tileProvider.fillHighlightColor = this.fillHighlightColor;    tileProvider.showSkirts = this.showSkirts;    tileProvider.backFaceCulling = this.backFaceCulling;    tileProvider.vertexShadowDarkness = this.vertexShadowDarkness;    tileProvider.undergroundColor = this._undergroundColor;    tileProvider.undergroundColorAlphaByDistance = this._undergroundColorAlphaByDistance;    tileProvider.lambertDiffuseMultiplier = this.lambertDiffuseMultiplier;    surface.beginFrame(frameState);  }};/** * @private */Globe.prototype.render = function (frameState) {  if (!this.show) {    return;  }  if (defined(this._material)) {    this._material.update(frameState.context);  }  this._surface.render(frameState);};/** * @private */Globe.prototype.endFrame = function (frameState) {  if (!this.show) {    return;  }  if (frameState.passes.render) {    this._surface.endFrame(frameState);  }};/** * Returns true if this object was destroyed; otherwise, false. * <br /><br /> * If this object was destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. * * @returns {boolean} True if this object was destroyed; otherwise, false. * * @see Globe#destroy */Globe.prototype.isDestroyed = function () {  return false;};/** * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. * <br /><br /> * Once an object is destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore, * assign the return value (<code>undefined</code>) to the object as done in the example. * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * * @example * globe = globe && globe.destroy(); * * @see Globe#isDestroyed */Globe.prototype.destroy = function () {  this._surfaceShaderSet =    this._surfaceShaderSet && this._surfaceShaderSet.destroy();  this._surface = this._surface && this._surface.destroy();  this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();  return destroyObject(this);};export default Globe;
 |