import BoundingRectangle from "../Core/BoundingRectangle.js"; import BoundingSphere from "../Core/BoundingSphere.js"; import BoxGeometry from "../Core/BoxGeometry.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartographic from "../Core/Cartographic.js"; import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js"; import createGuid from "../Core/createGuid.js"; import CullingVolume from "../Core/CullingVolume.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import EllipsoidGeometry from "../Core/EllipsoidGeometry.js"; import Event from "../Core/Event.js"; import FeatureDetection from "../Core/FeatureDetection.js"; import GeographicProjection from "../Core/GeographicProjection.js"; import GeometryInstance from "../Core/GeometryInstance.js"; import GeometryPipeline from "../Core/GeometryPipeline.js"; import Intersect from "../Core/Intersect.js"; import JulianDate from "../Core/JulianDate.js"; import CesiumMath from "../Core/Math.js"; import Matrix4 from "../Core/Matrix4.js"; import mergeSort from "../Core/mergeSort.js"; import Occluder from "../Core/Occluder.js"; import OrthographicFrustum from "../Core/OrthographicFrustum.js"; import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js"; import PerspectiveFrustum from "../Core/PerspectiveFrustum.js"; import PerspectiveOffCenterFrustum from "../Core/PerspectiveOffCenterFrustum.js"; import RequestScheduler from "../Core/RequestScheduler.js"; import TaskProcessor from "../Core/TaskProcessor.js"; import Transforms from "../Core/Transforms.js"; import ClearCommand from "../Renderer/ClearCommand.js"; import ComputeEngine from "../Renderer/ComputeEngine.js"; import Context from "../Renderer/Context.js"; import ContextLimits from "../Renderer/ContextLimits.js"; import Pass from "../Renderer/Pass.js"; import RenderState from "../Renderer/RenderState.js"; import BrdfLutGenerator from "./BrdfLutGenerator.js"; import Camera from "./Camera.js"; import Cesium3DTilePass from "./Cesium3DTilePass.js"; import Cesium3DTilePassState from "./Cesium3DTilePassState.js"; import CreditDisplay from "./CreditDisplay.js"; import DebugCameraPrimitive from "./DebugCameraPrimitive.js"; import DepthPlane from "./DepthPlane.js"; import DerivedCommand from "./DerivedCommand.js"; import DeviceOrientationCameraController from "./DeviceOrientationCameraController.js"; import Fog from "./Fog.js"; import FrameState from "./FrameState.js"; import GlobeTranslucencyState from "./GlobeTranslucencyState.js"; import InvertClassification from "./InvertClassification.js"; import JobScheduler from "./JobScheduler.js"; import MapMode2D from "./MapMode2D.js"; import OctahedralProjectedCubeMap from "./OctahedralProjectedCubeMap.js"; import PerformanceDisplay from "./PerformanceDisplay.js"; import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js"; import Picking from "./Picking.js"; import PostProcessStageCollection from "./PostProcessStageCollection.js"; import Primitive from "./Primitive.js"; import PrimitiveCollection from "./PrimitiveCollection.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; import SceneTransitioner from "./SceneTransitioner.js"; import ScreenSpaceCameraController from "./ScreenSpaceCameraController.js"; import ShadowMap from "./ShadowMap.js"; import StencilConstants from "./StencilConstants.js"; import SunLight from "./SunLight.js"; import SunPostProcess from "./SunPostProcess.js"; import TweenCollection from "./TweenCollection.js"; import View from "./View.js"; import DebugInspector from "./DebugInspector.js"; const requestRenderAfterFrame = function (scene) { return function () { scene.frameState.afterRender.push(function () { scene.requestRender(); }); }; }; /** * The container for all 3D graphical objects and state in a Cesium virtual scene. Generally, * a scene is not created directly; instead, it is implicitly created by {@link CesiumWidget}. *
* contextOptions
parameter details:
*
* The default values are:
*
* {
* webgl : {
* alpha : false,
* depth : true,
* stencil : false,
* antialias : true,
* powerPreference: 'high-performance',
* premultipliedAlpha : true,
* preserveDrawingBuffer : false,
* failIfMajorPerformanceCaveat : false
* },
* allowTextureFilterAnisotropic : true
* }
*
*
* The webgl
property corresponds to the {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
* object used to create the WebGL context.
*
* webgl.alpha
defaults to false, which can improve performance compared to the standard WebGL default
* of true. If an application needs to composite Cesium above other HTML elements using alpha-blending, set
* webgl.alpha
to true.
*
* The other webgl
properties match the WebGL defaults for {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}.
*
* allowTextureFilterAnisotropic
defaults to true, which enables anisotropic texture filtering when the
* WebGL extension is supported. Setting this to false will improve performance, but hurt visual quality, especially for horizon views.
*
render
are always caught in order to raise the
* renderError
event. If this property is true, the error is rethrown
* after the event is raised. If this property is false, the render
function
* returns normally after raising the event.
*
* @type {Boolean}
* @default false
*/
this.rethrowRenderErrors = false;
/**
* Determines whether or not to instantly complete the
* scene transition animation on user input.
*
* @type {Boolean}
* @default true
*/
this.completeMorphOnUserInput = true;
/**
* The event fired at the beginning of a scene transition.
* @type {Event}
* @default Event()
*/
this.morphStart = new Event();
/**
* The event fired at the completion of a scene transition.
* @type {Event}
* @default Event()
*/
this.morphComplete = new Event();
/**
* The {@link SkyBox} used to draw the stars.
*
* @type {SkyBox}
* @default undefined
*
* @see Scene#backgroundColor
*/
this.skyBox = undefined;
/**
* The sky atmosphere drawn around the globe.
*
* @type {SkyAtmosphere}
* @default undefined
*/
this.skyAtmosphere = undefined;
/**
* The {@link Sun}.
*
* @type {Sun}
* @default undefined
*/
this.sun = undefined;
/**
* Uses a bloom filter on the sun when enabled.
*
* @type {Boolean}
* @default true
*/
this.sunBloom = true;
this._sunBloom = undefined;
/**
* The {@link Moon}
*
* @type Moon
* @default undefined
*/
this.moon = undefined;
/**
* The background color, which is only visible if there is no sky box, i.e., {@link Scene#skyBox} is undefined.
*
* @type {Color}
* @default {@link Color.BLACK}
*
* @see Scene#skyBox
*/
this.backgroundColor = Color.clone(Color.BLACK);
this._mode = SceneMode.SCENE3D;
this._mapProjection = defined(options.mapProjection)
? options.mapProjection
: new GeographicProjection();
/**
* The current morph transition time between 2D/Columbus View and 3D,
* with 0.0 being 2D or Columbus View and 1.0 being 3D.
*
* @type {Number}
* @default 1.0
*/
this.morphTime = 1.0;
/**
* The far-to-near ratio of the multi-frustum when using a normal depth buffer.
*
* This value is used to create the near and far values for each frustum of the multi-frustum. It is only used
* when {@link Scene#logarithmicDepthBuffer} is false
. When logarithmicDepthBuffer
is
* true
, use {@link Scene#logarithmicDepthFarToNearRatio}.
*
* This value is used to create the near and far values for each frustum of the multi-frustum. It is only used
* when {@link Scene#logarithmicDepthBuffer} is true
. When logarithmicDepthBuffer
is
* false
, use {@link Scene#farToNearRatio}.
*
* A function that determines what commands are executed. As shown in the examples below,
* the function receives the command's owner
as an argument, and returns a boolean indicating if the
* command should be executed.
*
* The default is undefined
, indicating that all commands are executed.
*
* When true
, commands are randomly shaded. This is useful
* for performance analysis to see what parts of a scene or model are
* command-dense and could benefit from batching.
*
* When true
, commands are shaded based on the frustums they
* overlap. Commands in the closest frustum are tinted red, commands in
* the next closest are green, and commands in the farthest frustum are
* blue. If a command overlaps more than one frustum, the color components
* are combined, e.g., a command overlapping the first two frustums is tinted
* yellow.
*
* Displays frames per second and time between frames. *
* * @type Boolean * * @default false */ this.debugShowFramesPerSecond = false; /** * This property is for debugging only; it is not for production use. ** Indicates which frustum will have depth information displayed. *
* * @type Number * * @default 1 */ this.debugShowDepthFrustum = 1; /** * This property is for debugging only; it is not for production use. *
* When true
, draws outlines to show the boundaries of the camera frustums
*
true
, enables picking using the depth buffer.
*
* @type Boolean
* @default true
*/
this.useDepthPicking = true;
/**
* When true
, enables picking translucent geometry using the depth buffer. Note that {@link Scene#useDepthPicking} must also be true for enabling this to work.
*
* * There is a decrease in performance when enabled. There are extra draw calls to write depth for * translucent geometry. *
* * @example * // picking the position of a translucent primitive * viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) { * const pickedFeature = viewer.scene.pick(movement.position); * if (!Cesium.defined(pickedFeature)) { * // nothing picked * return; * } * const worldPosition = viewer.scene.pickPosition(movement.position); * }, Cesium.ScreenSpaceEventType.LEFT_CLICK); * * @type {Boolean} * @default false */ this.pickTranslucentDepth = false; /** * The time in milliseconds to wait before checking if the camera has not moved and fire the cameraMoveEnd event. * @type {Number} * @default 500.0 * @private */ this.cameraEventWaitTime = 500.0; /** * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional * performance improvements by rendering less geometry and dispatching less terrain requests. * @type {Fog} */ this.fog = new Fog(); this._shadowMapCamera = new Camera(this); /** * The shadow map for the scene's light source. When enabled, models, primitives, and the globe may cast and receive shadows. * @type {ShadowMap} */ this.shadowMap = new ShadowMap({ context: context, lightCamera: this._shadowMapCamera, enabled: defaultValue(options.shadows, false), }); /** * Whenfalse
, 3D Tiles will render normally. When true
, classified 3D Tile geometry will render normally and
* unclassified 3D Tile geometry will render with the color multiplied by {@link Scene#invertClassificationColor}.
* @type {Boolean}
* @default false
*/
this.invertClassification = false;
/**
* The highlight color of unclassified 3D Tile geometry when {@link Scene#invertClassification} is true
.
* When the color's alpha is less than 1.0, the unclassified portions of the 3D Tiles will not blend correctly with the classified positions of the 3D Tiles.
*Also, when the color's alpha is less than 1.0, the WEBGL_depth_texture and EXT_frag_depth WebGL extensions must be supported.
* @type {Color} * @default Color.WHITE */ this.invertClassificationColor = Color.clone(Color.WHITE); this._actualInvertClassificationColor = Color.clone( this._invertClassificationColor ); this._invertClassification = new InvertClassification(); /** * The focal length for use when with cardboard or WebVR. * @type {Number} */ this.focalLength = undefined; /** * The eye separation distance in meters for use with cardboard or WebVR. * @type {Number} */ this.eyeSeparation = undefined; /** * Post processing effects applied to the final render. * @type {PostProcessStageCollection} */ this.postProcessStages = new PostProcessStageCollection(); this._brdfLutGenerator = new BrdfLutGenerator(); this._performanceDisplay = undefined; this._debugVolume = undefined; this._screenSpaceCameraController = new ScreenSpaceCameraController(this); this._cameraUnderground = false; this._mapMode2D = defaultValue(options.mapMode2D, MapMode2D.INFINITE_SCROLL); // Keeps track of the state of a frame. FrameState is the state across // the primitives of the scene. This state is for internally keeping track // of celestial and environment effects that need to be updated/rendered in // a certain order as well as updating/tracking framebuffer usage. this._environmentState = { skyBoxCommand: undefined, skyAtmosphereCommand: undefined, sunDrawCommand: undefined, sunComputeCommand: undefined, moonCommand: undefined, isSunVisible: false, isMoonVisible: false, isReadyForAtmosphere: false, isSkyAtmosphereVisible: false, clearGlobeDepth: false, useDepthPlane: false, renderTranslucentDepthForPick: false, originalFramebuffer: undefined, useGlobeDepthFramebuffer: false, useOIT: false, useInvertClassification: false, usePostProcess: false, usePostProcessSelected: false, useWebVR: false, }; this._useWebVR = false; this._cameraVR = undefined; this._aspectRatioVR = undefined; /** * Whentrue
, rendering a frame will only occur when needed as determined by changes within the scene.
* Enabling improves performance of the application, but requires using {@link Scene#requestRender}
* to render a new frame explicitly in this mode. This will be necessary in many cases after making changes
* to the scene in other parts of the API.
*
* @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
* @see Scene#maximumRenderTimeChange
* @see Scene#requestRender
*
* @type {Boolean}
* @default false
*/
this.requestRenderMode = defaultValue(options.requestRenderMode, false);
this._renderRequested = true;
/**
* If {@link Scene#requestRenderMode} is true
, this value defines the maximum change in
* simulation time allowed before a render is requested. Lower values increase the number of frames rendered
* and higher values decrease the number of frames rendered. If undefined
, changes to
* the simulation time will never request a render.
* This value impacts the rate of rendering for changes in the scene like lighting, entity property updates,
* and animations.
*
* @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
* @see Scene#requestRenderMode
*
* @type {Number}
* @default 0.0
*/
this.maximumRenderTimeChange = defaultValue(
options.maximumRenderTimeChange,
0.0
);
this._lastRenderTime = undefined;
this._frameRateMonitor = undefined;
this._removeRequestListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener(
requestRenderAfterFrame(this)
);
this._removeTaskProcessorListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener(
requestRenderAfterFrame(this)
);
this._removeGlobeCallbacks = [];
const viewport = new BoundingRectangle(
0,
0,
context.drawingBufferWidth,
context.drawingBufferHeight
);
const camera = new Camera(this);
if (this._logDepthBuffer) {
camera.frustum.near = 0.1;
camera.frustum.far = 10000000000.0;
}
/**
* The camera view for the scene camera flight destination. Used for preloading flight destination tiles.
* @type {Camera}
* @private
*/
this.preloadFlightCamera = new Camera(this);
/**
* The culling volume for the scene camera flight destination. Used for preloading flight destination tiles.
* @type {CullingVolume}
* @private
*/
this.preloadFlightCullingVolume = undefined;
this._picking = new Picking(this);
this._defaultView = new View(this, camera, viewport);
this._view = this._defaultView;
this._hdr = undefined;
this._hdrDirty = undefined;
this.highDynamicRange = false;
this.gamma = 2.2;
/**
* The spherical harmonic coefficients for image-based lighting of PBR models.
* @type {Cartesian3[]}
*/
this.sphericalHarmonicCoefficients = undefined;
/**
* The url to the KTX2 file containing the specular environment map and convoluted mipmaps for image-based lighting of PBR models.
* @type {String}
*/
this.specularEnvironmentMaps = undefined;
this._specularEnvironmentMapAtlas = undefined;
/**
* The light source for shading. Defaults to a directional light from the Sun.
* @type {Light}
*/
this.light = new SunLight();
// Give frameState, camera, and screen space camera controller initial state before rendering
updateFrameNumber(this, 0.0, JulianDate.now());
this.updateFrameState();
this.initializeFrame();
}
function updateGlobeListeners(scene, globe) {
for (let i = 0; i < scene._removeGlobeCallbacks.length; ++i) {
scene._removeGlobeCallbacks[i]();
}
scene._removeGlobeCallbacks.length = 0;
const removeGlobeCallbacks = [];
if (defined(globe)) {
removeGlobeCallbacks.push(
globe.imageryLayersUpdatedEvent.addEventListener(
requestRenderAfterFrame(scene)
)
);
removeGlobeCallbacks.push(
globe.terrainProviderChanged.addEventListener(
requestRenderAfterFrame(scene)
)
);
}
scene._removeGlobeCallbacks = removeGlobeCallbacks;
}
Object.defineProperties(Scene.prototype, {
/**
* Gets the canvas element to which this scene is bound.
* @memberof Scene.prototype
*
* @type {HTMLCanvasElement}
* @readonly
*/
canvas: {
get: function () {
return this._canvas;
},
},
/**
* The drawingBufferHeight of the underlying GL context.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
*/
drawingBufferHeight: {
get: function () {
return this._context.drawingBufferHeight;
},
},
/**
* The drawingBufferHeight of the underlying GL context.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
*/
drawingBufferWidth: {
get: function () {
return this._context.drawingBufferWidth;
},
},
/**
* The maximum aliased line width, in pixels, supported by this WebGL implementation. It will be at least one.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_LINE_WIDTH_RANGE
.
*/
maximumAliasedLineWidth: {
get: function () {
return ContextLimits.maximumAliasedLineWidth;
},
},
/**
* The maximum length in pixels of one edge of a cube map, supported by this WebGL implementation. It will be at least 16.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with GL_MAX_CUBE_MAP_TEXTURE_SIZE
.
*/
maximumCubeMapSize: {
get: function () {
return ContextLimits.maximumCubeMapSize;
},
},
/**
* Returns true
if the {@link Scene#pickPosition} function is supported.
* @memberof Scene.prototype
*
* @type {Boolean}
* @readonly
*
* @see Scene#pickPosition
*/
pickPositionSupported: {
get: function () {
return this._context.depthTexture;
},
},
/**
* Returns true
if the {@link Scene#sampleHeight} and {@link Scene#sampleHeightMostDetailed} functions are supported.
* @memberof Scene.prototype
*
* @type {Boolean}
* @readonly
*
* @see Scene#sampleHeight
* @see Scene#sampleHeightMostDetailed
*/
sampleHeightSupported: {
get: function () {
return this._context.depthTexture;
},
},
/**
* Returns true
if the {@link Scene#clampToHeight} and {@link Scene#clampToHeightMostDetailed} functions are supported.
* @memberof Scene.prototype
*
* @type {Boolean}
* @readonly
*
* @see Scene#clampToHeight
* @see Scene#clampToHeightMostDetailed
*/
clampToHeightSupported: {
get: function () {
return this._context.depthTexture;
},
},
/**
* Returns true
if the {@link Scene#invertClassification} is supported.
* @memberof Scene.prototype
*
* @type {Boolean}
* @readonly
*
* @see Scene#invertClassification
*/
invertClassificationSupported: {
get: function () {
return this._context.depthTexture;
},
},
/**
* Returns true
if specular environment maps are supported.
* @memberof Scene.prototype
*
* @type {Boolean}
* @readonly
*
* @see Scene#specularEnvironmentMaps
*/
specularEnvironmentMapsSupported: {
get: function () {
return OctahedralProjectedCubeMap.isSupported(this._context);
},
},
/**
* Gets or sets the depth-test ellipsoid.
* @memberof Scene.prototype
*
* @type {Globe}
*/
globe: {
get: function () {
return this._globe;
},
set: function (globe) {
this._globe = this._globe && this._globe.destroy();
this._globe = globe;
updateGlobeListeners(this, globe);
},
},
/**
* Gets the collection of primitives.
* @memberof Scene.prototype
*
* @type {PrimitiveCollection}
* @readonly
*/
primitives: {
get: function () {
return this._primitives;
},
},
/**
* Gets the collection of ground primitives.
* @memberof Scene.prototype
*
* @type {PrimitiveCollection}
* @readonly
*/
groundPrimitives: {
get: function () {
return this._groundPrimitives;
},
},
/**
* Gets or sets the camera.
* @memberof Scene.prototype
*
* @type {Camera}
* @readonly
*/
camera: {
get: function () {
return this._view.camera;
},
set: function (camera) {
// For internal use only. Documentation is still @readonly.
this._view.camera = camera;
},
},
/**
* Gets or sets the view.
* @memberof Scene.prototype
*
* @type {View}
* @readonly
*
* @private
*/
view: {
get: function () {
return this._view;
},
set: function (view) {
// For internal use only. Documentation is still @readonly.
this._view = view;
},
},
/**
* Gets the default view.
* @memberof Scene.prototype
*
* @type {View}
* @readonly
*
* @private
*/
defaultView: {
get: function () {
return this._defaultView;
},
},
/**
* Gets picking functions and state
* @memberof Scene.prototype
*
* @type {Picking}
* @readonly
*
* @private
*/
picking: {
get: function () {
return this._picking;
},
},
/**
* Gets the controller for camera input handling.
* @memberof Scene.prototype
*
* @type {ScreenSpaceCameraController}
* @readonly
*/
screenSpaceCameraController: {
get: function () {
return this._screenSpaceCameraController;
},
},
/**
* Get the map projection to use in 2D and Columbus View modes.
* @memberof Scene.prototype
*
* @type {MapProjection}
* @readonly
*
* @default new GeographicProjection()
*/
mapProjection: {
get: function () {
return this._mapProjection;
},
},
/**
* Gets the job scheduler
* @memberof Scene.prototype
* @type {JobScheduler}
* @readonly
*
* @private
*/
jobScheduler: {
get: function () {
return this._jobScheduler;
},
},
/**
* Gets state information about the current scene. If called outside of a primitive's update
* function, the previous frame's state is returned.
* @memberof Scene.prototype
*
* @type {FrameState}
* @readonly
*
* @private
*/
frameState: {
get: function () {
return this._frameState;
},
},
/**
* Gets the environment state.
* @memberof Scene.prototype
*
* @type {EnvironmentState}
* @readonly
*
* @private
*/
environmentState: {
get: function () {
return this._environmentState;
},
},
/**
* Gets the collection of tweens taking place in the scene.
* @memberof Scene.prototype
*
* @type {TweenCollection}
* @readonly
*
* @private
*/
tweens: {
get: function () {
return this._tweens;
},
},
/**
* Gets the collection of image layers that will be rendered on the globe.
* @memberof Scene.prototype
*
* @type {ImageryLayerCollection}
* @readonly
*/
imageryLayers: {
get: function () {
if (!defined(this.globe)) {
return undefined;
}
return this.globe.imageryLayers;
},
},
/**
* The terrain provider providing surface geometry for the globe.
* @memberof Scene.prototype
*
* @type {TerrainProvider}
*/
terrainProvider: {
get: function () {
if (!defined(this.globe)) {
return undefined;
}
return this.globe.terrainProvider;
},
set: function (terrainProvider) {
if (defined(this.globe)) {
this.globe.terrainProvider = terrainProvider;
}
},
},
/**
* Gets an event that's raised when the terrain provider is changed
* @memberof Scene.prototype
*
* @type {Event}
* @readonly
*/
terrainProviderChanged: {
get: function () {
if (!defined(this.globe)) {
return undefined;
}
return this.globe.terrainProviderChanged;
},
},
/**
* Gets the event that will be raised before the scene is updated or rendered. Subscribers to the event
* receive the Scene instance as the first parameter and the current time as the second parameter.
* @memberof Scene.prototype
*
* @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
* @see Scene#postUpdate
* @see Scene#preRender
* @see Scene#postRender
*
* @type {Event}
* @readonly
*/
preUpdate: {
get: function () {
return this._preUpdate;
},
},
/**
* Gets the event that will be raised immediately after the scene is updated and before the scene is rendered.
* Subscribers to the event receive the Scene instance as the first parameter and the current time as the second
* parameter.
* @memberof Scene.prototype
*
* @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
* @see Scene#preUpdate
* @see Scene#preRender
* @see Scene#postRender
*
* @type {Event}
* @readonly
*/
postUpdate: {
get: function () {
return this._postUpdate;
},
},
/**
* Gets the event that will be raised when an error is thrown inside the render
function.
* The Scene instance and the thrown error are the only two parameters passed to the event handler.
* By default, errors are not rethrown after this event is raised, but that can be changed by setting
* the rethrowRenderErrors
property.
* @memberof Scene.prototype
*
* @type {Event}
* @readonly
*/
renderError: {
get: function () {
return this._renderError;
},
},
/**
* Gets the event that will be raised after the scene is updated and immediately before the scene is rendered.
* Subscribers to the event receive the Scene instance as the first parameter and the current time as the second
* parameter.
* @memberof Scene.prototype
*
* @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
* @see Scene#preUpdate
* @see Scene#postUpdate
* @see Scene#postRender
*
* @type {Event}
* @readonly
*/
preRender: {
get: function () {
return this._preRender;
},
},
/**
* Gets the event that will be raised immediately after the scene is rendered. Subscribers to the event
* receive the Scene instance as the first parameter and the current time as the second parameter.
* @memberof Scene.prototype
*
* @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
* @see Scene#preUpdate
* @see Scene#postUpdate
* @see Scene#postRender
*
* @type {Event}
* @readonly
*/
postRender: {
get: function () {
return this._postRender;
},
},
/**
* Gets the simulation time when the scene was last rendered. Returns undefined if the scene has not yet been
* rendered.
* @memberof Scene.prototype
*
* @type {JulianDate}
* @readonly
*/
lastRenderTime: {
get: function () {
return this._lastRenderTime;
},
},
/**
* @memberof Scene.prototype
* @private
* @readonly
*/
context: {
get: function () {
return this._context;
},
},
/**
* This property is for debugging only; it is not for production use.
*
* When {@link Scene.debugShowFrustums} is true
, this contains
* properties with statistics about the number of command execute per frustum.
* totalCommands
is the total number of commands executed, ignoring
* overlap. commandsInFrustums
is an array with the number of times
* commands are executed redundantly, e.g., how many commands overlap two or
* three frustums.
*
true
, splits the scene into two viewports with steroscopic views for the left and right eyes.
* Used for cardboard and WebVR.
* @memberof Scene.prototype
* @type {Boolean}
* @default false
*/
useWebVR: {
get: function () {
return this._useWebVR;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
if (this.camera.frustum instanceof OrthographicFrustum) {
throw new DeveloperError(
"VR is unsupported with an orthographic projection."
);
}
//>>includeEnd('debug');
this._useWebVR = value;
if (this._useWebVR) {
this._frameState.creditDisplay.container.style.visibility = "hidden";
this._cameraVR = new Camera(this);
if (!defined(this._deviceOrientationCameraController)) {
this._deviceOrientationCameraController = new DeviceOrientationCameraController(
this
);
}
this._aspectRatioVR = this.camera.frustum.aspectRatio;
} else {
this._frameState.creditDisplay.container.style.visibility = "visible";
this._cameraVR = undefined;
this._deviceOrientationCameraController =
this._deviceOrientationCameraController &&
!this._deviceOrientationCameraController.isDestroyed() &&
this._deviceOrientationCameraController.destroy();
this.camera.frustum.aspectRatio = this._aspectRatioVR;
this.camera.frustum.xOffset = 0.0;
}
},
},
/**
* Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
* @memberof Scene.prototype
* @type {MapMode2D}
* @readonly
*/
mapMode2D: {
get: function () {
return this._mapMode2D;
},
},
/**
* Gets or sets the position of the splitter within the viewport. Valid values are between 0.0 and 1.0.
* @memberof Scene.prototype
*
* @type {Number}
*/
splitPosition: {
get: function () {
return this._frameState.splitPosition;
},
set: function (value) {
this._frameState.splitPosition = value;
},
},
/**
* Gets or sets the position of the Imagery splitter within the viewport. Valid values are between 0.0 and 1.0.
* @memberof Scene.prototype
*
* @deprecated Use splitPosition instead.
* @type {Number}
*/
imagerySplitPosition: {
get: function () {
deprecationWarning(
"Scene.imagerySplitPosition",
"Scene.imagerySplitPosition has been deprecated in Cesium 1.92. It will be removed in Cesium 1.94. Use splitPosition instead."
);
return this._frameState.splitPosition;
},
set: function (value) {
deprecationWarning(
"Scene.imagerySplitPosition",
"Scene.imagerySplitPosition has been deprecated in Cesium 1.92. It will be removed in Cesium 1.94. Use splitPosition instead."
);
this._frameState.splitPosition = value;
},
},
/**
* The distance from the camera at which to disable the depth test of billboards, labels and points
* to, for example, prevent clipping against terrain. When set to zero, the depth test should always
* be applied. When less than zero, the depth test should never be applied. Setting the disableDepthTestDistance
* property of a billboard, label or point will override this value.
* @memberof Scene.prototype
* @type {Number}
* @default 0.0
*/
minimumDisableDepthTestDistance: {
get: function () {
return this._minimumDisableDepthTestDistance;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
if (!defined(value) || value < 0.0) {
throw new DeveloperError(
"minimumDisableDepthTestDistance must be greater than or equal to 0.0."
);
}
//>>includeEnd('debug');
this._minimumDisableDepthTestDistance = value;
},
},
/**
* Whether or not to use a logarithmic depth buffer. Enabling this option will allow for less frustums in the multi-frustum,
* increasing performance. This property relies on fragmentDepth being supported.
* @memberof Scene.prototype
* @type {Boolean}
*/
logarithmicDepthBuffer: {
get: function () {
return this._logDepthBuffer;
},
set: function (value) {
value = this._context.fragmentDepth && value;
if (this._logDepthBuffer !== value) {
this._logDepthBuffer = value;
this._logDepthBufferDirty = true;
}
},
},
/**
* The value used for gamma correction. This is only used when rendering with high dynamic range.
* @memberof Scene.prototype
* @type {Number}
* @default 2.2
*/
gamma: {
get: function () {
return this._context.uniformState.gamma;
},
set: function (value) {
this._context.uniformState.gamma = value;
},
},
/**
* Whether or not to use high dynamic range rendering.
* @memberof Scene.prototype
* @type {Boolean}
* @default true
*/
highDynamicRange: {
get: function () {
return this._hdr;
},
set: function (value) {
const context = this._context;
const hdr =
value &&
context.depthTexture &&
(context.colorBufferFloat || context.colorBufferHalfFloat);
this._hdrDirty = hdr !== this._hdr;
this._hdr = hdr;
},
},
/**
* Whether or not high dynamic range rendering is supported.
* @memberof Scene.prototype
* @type {Boolean}
* @readonly
* @default true
*/
highDynamicRangeSupported: {
get: function () {
const context = this._context;
return (
context.depthTexture &&
(context.colorBufferFloat || context.colorBufferHalfFloat)
);
},
},
/**
* Whether or not the camera is underneath the globe.
* @memberof Scene.prototype
* @type {Boolean}
* @readonly
* @default false
*/
cameraUnderground: {
get: function () {
return this._cameraUnderground;
},
},
/**
* The sample rate of multisample antialiasing (values greater than 1 enable MSAA).
* @memberof Scene.prototype
* @type {Number}
* @default 1
*/
msaaSamples: {
get: function () {
return this._msaaSamples;
},
set: function (value) {
value = Math.min(value, ContextLimits.maximumSamples);
this._msaaSamples = value;
},
},
/**
* Returns true
if the Scene's context supports MSAA.
* @memberof Scene.prototype
* @type {Boolean}
* @readonly
*/
msaaSupported: {
get: function () {
return this._context.msaa;
},
},
/**
* Ratio between a pixel and a density-independent pixel. Provides a standard unit of
* measure for real pixel measurements appropriate to a particular device.
*
* @memberof Scene.prototype
* @type {Number}
* @default 1.0
* @private
*/
pixelRatio: {
get: function () {
return this._frameState.pixelRatio;
},
set: function (value) {
this._frameState.pixelRatio = value;
},
},
/**
* @private
*/
opaqueFrustumNearOffset: {
get: function () {
return 0.9999;
},
},
/**
* @private
*/
globeHeight: {
get: function () {
return this._globeHeight;
},
},
});
/**
* Determines if a compressed texture format is supported.
* @param {String} format The texture format. May be the name of the format or the WebGL extension name, e.g. s3tc or WEBGL_compressed_texture_s3tc.
* @return {boolean} Whether or not the format is supported.
*/
Scene.prototype.getCompressedTextureFormatSupported = function (format) {
const context = this.context;
return (
((format === "WEBGL_compressed_texture_s3tc" || format === "s3tc") &&
context.s3tc) ||
((format === "WEBGL_compressed_texture_pvrtc" || format === "pvrtc") &&
context.pvrtc) ||
((format === "WEBGL_compressed_texture_etc" || format === "etc") &&
context.etc) ||
((format === "WEBGL_compressed_texture_etc1" || format === "etc1") &&
context.etc1) ||
((format === "WEBGL_compressed_texture_astc" || format === "astc") &&
context.astc) ||
((format === "EXT_texture_compression_bptc" || format === "bc7") &&
context.bc7)
);
};
function updateDerivedCommands(scene, command, shadowsDirty) {
const frameState = scene._frameState;
const context = scene._context;
const oit = scene._view.oit;
const lightShadowMaps = frameState.shadowState.lightShadowMaps;
const lightShadowsEnabled = frameState.shadowState.lightShadowsEnabled;
let derivedCommands = command.derivedCommands;
if (defined(command.pickId)) {
derivedCommands.picking = DerivedCommand.createPickDerivedCommand(
scene,
command,
context,
derivedCommands.picking
);
}
if (!command.pickOnly) {
derivedCommands.depth = DerivedCommand.createDepthOnlyDerivedCommand(
scene,
command,
context,
derivedCommands.depth
);
}
derivedCommands.originalCommand = command;
if (scene._hdr) {
derivedCommands.hdr = DerivedCommand.createHdrCommand(
command,
context,
derivedCommands.hdr
);
command = derivedCommands.hdr.command;
derivedCommands = command.derivedCommands;
}
if (lightShadowsEnabled && command.receiveShadows) {
derivedCommands.shadows = ShadowMap.createReceiveDerivedCommand(
lightShadowMaps,
command,
shadowsDirty,
context,
derivedCommands.shadows
);
}
if (command.pass === Pass.TRANSLUCENT && defined(oit) && oit.isSupported()) {
if (lightShadowsEnabled && command.receiveShadows) {
derivedCommands.oit = defined(derivedCommands.oit)
? derivedCommands.oit
: {};
derivedCommands.oit.shadows = oit.createDerivedCommands(
derivedCommands.shadows.receiveCommand,
context,
derivedCommands.oit.shadows
);
} else {
derivedCommands.oit = oit.createDerivedCommands(
command,
context,
derivedCommands.oit
);
}
}
}
/**
* @private
*/
Scene.prototype.updateDerivedCommands = function (command) {
if (!defined(command.derivedCommands)) {
// Is not a DrawCommand
return;
}
const frameState = this._frameState;
const context = this._context;
// Update derived commands when any shadow maps become dirty
let shadowsDirty = false;
const lastDirtyTime = frameState.shadowState.lastDirtyTime;
if (command.lastDirtyTime !== lastDirtyTime) {
command.lastDirtyTime = lastDirtyTime;
command.dirty = true;
shadowsDirty = true;
}
const useLogDepth = frameState.useLogDepth;
const useHdr = this._hdr;
const derivedCommands = command.derivedCommands;
const hasLogDepthDerivedCommands = defined(derivedCommands.logDepth);
const hasHdrCommands = defined(derivedCommands.hdr);
const hasDerivedCommands = defined(derivedCommands.originalCommand);
const needsLogDepthDerivedCommands =
useLogDepth && !hasLogDepthDerivedCommands;
const needsHdrCommands = useHdr && !hasHdrCommands;
const needsDerivedCommands = (!useLogDepth || !useHdr) && !hasDerivedCommands;
command.dirty =
command.dirty ||
needsLogDepthDerivedCommands ||
needsHdrCommands ||
needsDerivedCommands;
if (command.dirty) {
command.dirty = false;
const shadowMaps = frameState.shadowState.shadowMaps;
const shadowsEnabled = frameState.shadowState.shadowsEnabled;
if (shadowsEnabled && command.castShadows) {
derivedCommands.shadows = ShadowMap.createCastDerivedCommand(
shadowMaps,
command,
shadowsDirty,
context,
derivedCommands.shadows
);
}
if (hasLogDepthDerivedCommands || needsLogDepthDerivedCommands) {
derivedCommands.logDepth = DerivedCommand.createLogDepthCommand(
command,
context,
derivedCommands.logDepth
);
updateDerivedCommands(
this,
derivedCommands.logDepth.command,
shadowsDirty
);
}
if (hasDerivedCommands || needsDerivedCommands) {
updateDerivedCommands(this, command, shadowsDirty);
}
}
};
const renderTilesetPassState = new Cesium3DTilePassState({
pass: Cesium3DTilePass.RENDER,
});
const preloadTilesetPassState = new Cesium3DTilePassState({
pass: Cesium3DTilePass.PRELOAD,
});
const preloadFlightTilesetPassState = new Cesium3DTilePassState({
pass: Cesium3DTilePass.PRELOAD_FLIGHT,
});
const requestRenderModeDeferCheckPassState = new Cesium3DTilePassState({
pass: Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK,
});
const scratchOccluderBoundingSphere = new BoundingSphere();
let scratchOccluder;
function getOccluder(scene) {
// TODO: The occluder is the top-level globe. When we add
// support for multiple central bodies, this should be the closest one.
const globe = scene.globe;
if (
scene._mode === SceneMode.SCENE3D &&
defined(globe) &&
globe.show &&
!scene._cameraUnderground &&
!scene._globeTranslucencyState.translucent
) {
const ellipsoid = globe.ellipsoid;
const minimumTerrainHeight = scene.frameState.minimumTerrainHeight;
scratchOccluderBoundingSphere.radius =
ellipsoid.minimumRadius + minimumTerrainHeight;
scratchOccluder = Occluder.fromBoundingSphere(
scratchOccluderBoundingSphere,
scene.camera.positionWC,
scratchOccluder
);
return scratchOccluder;
}
return undefined;
}
/**
* @private
*/
Scene.prototype.clearPasses = function (passes) {
passes.render = false;
passes.pick = false;
passes.depth = false;
passes.postProcess = false;
passes.offscreen = false;
};
function updateFrameNumber(scene, frameNumber, time) {
const frameState = scene._frameState;
frameState.frameNumber = frameNumber;
frameState.time = JulianDate.clone(time, frameState.time);
}
/**
* @private
*/
Scene.prototype.updateFrameState = function () {
const camera = this.camera;
const frameState = this._frameState;
frameState.commandList.length = 0;
frameState.shadowMaps.length = 0;
frameState.brdfLutGenerator = this._brdfLutGenerator;
frameState.environmentMap = this.skyBox && this.skyBox._cubeMap;
frameState.mode = this._mode;
frameState.morphTime = this.morphTime;
frameState.mapProjection = this.mapProjection;
frameState.camera = camera;
frameState.cullingVolume = camera.frustum.computeCullingVolume(
camera.positionWC,
camera.directionWC,
camera.upWC
);
frameState.occluder = getOccluder(this);
frameState.minimumTerrainHeight = 0.0;
frameState.minimumDisableDepthTestDistance = this._minimumDisableDepthTestDistance;
frameState.invertClassification = this.invertClassification;
frameState.useLogDepth =
this._logDepthBuffer &&
!(
this.camera.frustum instanceof OrthographicFrustum ||
this.camera.frustum instanceof OrthographicOffCenterFrustum
);
frameState.light = this.light;
frameState.cameraUnderground = this._cameraUnderground;
frameState.globeTranslucencyState = this._globeTranslucencyState;
if (defined(this.globe)) {
frameState.terrainExaggeration = this.globe.terrainExaggeration;
frameState.terrainExaggerationRelativeHeight = this.globe.terrainExaggerationRelativeHeight;
}
if (
defined(this._specularEnvironmentMapAtlas) &&
this._specularEnvironmentMapAtlas.ready
) {
frameState.specularEnvironmentMaps = this._specularEnvironmentMapAtlas.texture;
frameState.specularEnvironmentMapsMaximumLOD = this._specularEnvironmentMapAtlas.maximumMipmapLevel;
} else {
frameState.specularEnvironmentMaps = undefined;
frameState.specularEnvironmentMapsMaximumLOD = undefined;
}
frameState.sphericalHarmonicCoefficients = this.sphericalHarmonicCoefficients;
this._actualInvertClassificationColor = Color.clone(
this.invertClassificationColor,
this._actualInvertClassificationColor
);
if (!InvertClassification.isTranslucencySupported(this._context)) {
this._actualInvertClassificationColor.alpha = 1.0;
}
frameState.invertClassificationColor = this._actualInvertClassificationColor;
if (defined(this.globe)) {
frameState.maximumScreenSpaceError = this.globe.maximumScreenSpaceError;
} else {
frameState.maximumScreenSpaceError = 2;
}
this.clearPasses(frameState.passes);
frameState.tilesetPassState = undefined;
};
/**
* @private
*/
Scene.prototype.isVisible = function (command, cullingVolume, occluder) {
return (
defined(command) &&
(!defined(command.boundingVolume) ||
!command.cull ||
(cullingVolume.computeVisibility(command.boundingVolume) !==
Intersect.OUTSIDE &&
(!defined(occluder) ||
!command.occlude ||
!command.boundingVolume.isOccluded(occluder))))
);
};
let transformFrom2D = new Matrix4(
0.0,
0.0,
1.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
);
transformFrom2D = Matrix4.inverseTransformation(
transformFrom2D,
transformFrom2D
);
function debugShowBoundingVolume(command, scene, passState, debugFramebuffer) {
// Debug code to draw bounding volume for command. Not optimized!
// Assumes bounding volume is a bounding sphere or box
const frameState = scene._frameState;
const context = frameState.context;
const boundingVolume = command.boundingVolume;
if (defined(scene._debugVolume)) {
scene._debugVolume.destroy();
}
let geometry;
let center = Cartesian3.clone(boundingVolume.center);
if (frameState.mode !== SceneMode.SCENE3D) {
center = Matrix4.multiplyByPoint(transformFrom2D, center, center);
const projection = frameState.mapProjection;
const centerCartographic = projection.unproject(center);
center = projection.ellipsoid.cartographicToCartesian(centerCartographic);
}
if (defined(boundingVolume.radius)) {
const radius = boundingVolume.radius;
geometry = GeometryPipeline.toWireframe(
EllipsoidGeometry.createGeometry(
new EllipsoidGeometry({
radii: new Cartesian3(radius, radius, radius),
vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT,
})
)
);
scene._debugVolume = new Primitive({
geometryInstances: new GeometryInstance({
geometry: geometry,
modelMatrix: Matrix4.fromTranslation(center),
attributes: {
color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0),
},
}),
appearance: new PerInstanceColorAppearance({
flat: true,
translucent: false,
}),
asynchronous: false,
});
} else {
const halfAxes = boundingVolume.halfAxes;
geometry = GeometryPipeline.toWireframe(
BoxGeometry.createGeometry(
BoxGeometry.fromDimensions({
dimensions: new Cartesian3(2.0, 2.0, 2.0),
vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT,
})
)
);
scene._debugVolume = new Primitive({
geometryInstances: new GeometryInstance({
geometry: geometry,
modelMatrix: Matrix4.fromRotationTranslation(
halfAxes,
center,
new Matrix4()
),
attributes: {
color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0),
},
}),
appearance: new PerInstanceColorAppearance({
flat: true,
translucent: false,
}),
asynchronous: false,
});
}
const savedCommandList = frameState.commandList;
const commandList = (frameState.commandList = []);
scene._debugVolume.update(frameState);
command = commandList[0];
if (frameState.useLogDepth) {
const logDepth = DerivedCommand.createLogDepthCommand(command, context);
command = logDepth.command;
}
let framebuffer;
if (defined(debugFramebuffer)) {
framebuffer = passState.framebuffer;
passState.framebuffer = debugFramebuffer;
}
command.execute(context, passState);
if (defined(framebuffer)) {
passState.framebuffer = framebuffer;
}
frameState.commandList = savedCommandList;
}
function executeCommand(command, scene, context, passState, debugFramebuffer) {
const frameState = scene._frameState;
if (defined(scene.debugCommandFilter) && !scene.debugCommandFilter(command)) {
return;
}
if (command instanceof ClearCommand) {
command.execute(context, passState);
return;
}
if (command.debugShowBoundingVolume && defined(command.boundingVolume)) {
debugShowBoundingVolume(command, scene, passState, debugFramebuffer);
}
if (frameState.useLogDepth && defined(command.derivedCommands.logDepth)) {
command = command.derivedCommands.logDepth.command;
}
const passes = frameState.passes;
if (
!passes.pick &&
!passes.depth &&
scene._hdr &&
defined(command.derivedCommands) &&
defined(command.derivedCommands.hdr)
) {
command = command.derivedCommands.hdr.command;
}
if (passes.pick || passes.depth) {
if (
passes.pick &&
!passes.depth &&
defined(command.derivedCommands.picking)
) {
command = command.derivedCommands.picking.pickCommand;
command.execute(context, passState);
return;
} else if (defined(command.derivedCommands.depth)) {
command = command.derivedCommands.depth.depthOnlyCommand;
command.execute(context, passState);
return;
}
}
if (scene.debugShowCommands || scene.debugShowFrustums) {
scene._debugInspector.executeDebugShowFrustumsCommand(
scene,
command,
passState
);
return;
}
if (
frameState.shadowState.lightShadowsEnabled &&
command.receiveShadows &&
defined(command.derivedCommands.shadows)
) {
// If the command receives shadows, execute the derived shadows command.
// Some commands, such as OIT derived commands, do not have derived shadow commands themselves
// and instead shadowing is built-in. In this case execute the command regularly below.
command.derivedCommands.shadows.receiveCommand.execute(context, passState);
} else {
command.execute(context, passState);
}
}
function executeIdCommand(command, scene, context, passState) {
const frameState = scene._frameState;
let derivedCommands = command.derivedCommands;
if (!defined(derivedCommands)) {
return;
}
if (frameState.useLogDepth && defined(derivedCommands.logDepth)) {
command = derivedCommands.logDepth.command;
}
derivedCommands = command.derivedCommands;
if (defined(derivedCommands.picking)) {
command = derivedCommands.picking.pickCommand;
command.execute(context, passState);
} else if (defined(derivedCommands.depth)) {
command = derivedCommands.depth.depthOnlyCommand;
command.execute(context, passState);
}
}
function backToFront(a, b, position) {
return (
b.boundingVolume.distanceSquaredTo(position) -
a.boundingVolume.distanceSquaredTo(position)
);
}
function frontToBack(a, b, position) {
// When distances are equal equal favor sorting b before a. This gives render priority to commands later in the list.
return (
a.boundingVolume.distanceSquaredTo(position) -
b.boundingVolume.distanceSquaredTo(position) +
CesiumMath.EPSILON12
);
}
function executeTranslucentCommandsBackToFront(
scene,
executeFunction,
passState,
commands,
invertClassification
) {
const context = scene.context;
mergeSort(commands, backToFront, scene.camera.positionWC);
if (defined(invertClassification)) {
executeFunction(
invertClassification.unclassifiedCommand,
scene,
context,
passState
);
}
const length = commands.length;
for (let i = 0; i < length; ++i) {
executeFunction(commands[i], scene, context, passState);
}
}
function executeTranslucentCommandsFrontToBack(
scene,
executeFunction,
passState,
commands,
invertClassification
) {
const context = scene.context;
mergeSort(commands, frontToBack, scene.camera.positionWC);
if (defined(invertClassification)) {
executeFunction(
invertClassification.unclassifiedCommand,
scene,
context,
passState
);
}
const length = commands.length;
for (let i = 0; i < length; ++i) {
executeFunction(commands[i], scene, context, passState);
}
}
const scratchPerspectiveFrustum = new PerspectiveFrustum();
const scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
const scratchOrthographicFrustum = new OrthographicFrustum();
const scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum();
function executeCommands(scene, passState) {
const camera = scene.camera;
const context = scene.context;
const frameState = scene.frameState;
const us = context.uniformState;
us.updateCamera(camera);
// Create a working frustum from the original camera frustum.
let frustum;
if (defined(camera.frustum.fov)) {
frustum = camera.frustum.clone(scratchPerspectiveFrustum);
} else if (defined(camera.frustum.infiniteProjectionMatrix)) {
frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
} else if (defined(camera.frustum.width)) {
frustum = camera.frustum.clone(scratchOrthographicFrustum);
} else {
frustum = camera.frustum.clone(scratchOrthographicOffCenterFrustum);
}
// Ideally, we would render the sky box and atmosphere last for
// early-z, but we would have to draw it in each frustum
frustum.near = camera.frustum.near;
frustum.far = camera.frustum.far;
us.updateFrustum(frustum);
us.updatePass(Pass.ENVIRONMENT);
const passes = frameState.passes;
const picking = passes.pick;
const environmentState = scene._environmentState;
const view = scene._view;
const renderTranslucentDepthForPick =
environmentState.renderTranslucentDepthForPick;
const useWebVR = environmentState.useWebVR;
// Do not render environment primitives during a pick pass since they do not generate picking commands.
if (!picking) {
const skyBoxCommand = environmentState.skyBoxCommand;
if (defined(skyBoxCommand)) {
executeCommand(skyBoxCommand, scene, context, passState);
}
if (environmentState.isSkyAtmosphereVisible) {
executeCommand(
environmentState.skyAtmosphereCommand,
scene,
context,
passState
);
}
if (environmentState.isSunVisible) {
environmentState.sunDrawCommand.execute(context, passState);
if (scene.sunBloom && !useWebVR) {
let framebuffer;
if (environmentState.useGlobeDepthFramebuffer) {
framebuffer = view.globeDepth.framebuffer;
} else if (environmentState.usePostProcess) {
framebuffer = view.sceneFramebuffer.framebuffer;
} else {
framebuffer = environmentState.originalFramebuffer;
}
scene._sunPostProcess.execute(context);
scene._sunPostProcess.copy(context, framebuffer);
passState.framebuffer = framebuffer;
}
}
// Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere.
if (environmentState.isMoonVisible) {
environmentState.moonCommand.execute(context, passState);
}
}
// Determine how translucent surfaces will be handled.
let executeTranslucentCommands;
if (environmentState.useOIT) {
if (!defined(scene._executeOITFunction)) {
scene._executeOITFunction = function (
scene,
executeFunction,
passState,
commands,
invertClassification
) {
view.globeDepth.prepareColorTextures(context);
view.oit.executeCommands(
scene,
executeFunction,
passState,
commands,
invertClassification
);
};
}
executeTranslucentCommands = scene._executeOITFunction;
} else if (passes.render) {
executeTranslucentCommands = executeTranslucentCommandsBackToFront;
} else {
executeTranslucentCommands = executeTranslucentCommandsFrontToBack;
}
const frustumCommandsList = view.frustumCommandsList;
const numFrustums = frustumCommandsList.length;
const clearGlobeDepth = environmentState.clearGlobeDepth;
const useDepthPlane = environmentState.useDepthPlane;
const globeTranslucencyState = scene._globeTranslucencyState;
const globeTranslucent = globeTranslucencyState.translucent;
const globeTranslucencyFramebuffer = scene._view.globeTranslucencyFramebuffer;
const clearDepth = scene._depthClearCommand;
const clearStencil = scene._stencilClearCommand;
const clearClassificationStencil = scene._classificationStencilClearCommand;
const depthPlane = scene._depthPlane;
const usePostProcessSelected = environmentState.usePostProcessSelected;
const height2D = camera.position.z;
// Execute commands in each frustum in back to front order
let j;
for (let i = 0; i < numFrustums; ++i) {
const index = numFrustums - i - 1;
const frustumCommands = frustumCommandsList[index];
if (scene.mode === SceneMode.SCENE2D) {
// To avoid z-fighting in 2D, move the camera to just before the frustum
// and scale the frustum depth to be in [1.0, nearToFarDistance2D].
camera.position.z = height2D - frustumCommands.near + 1.0;
frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near);
frustum.near = 1.0;
us.update(frameState);
us.updateFrustum(frustum);
} else {
// Avoid tearing artifacts between adjacent frustums in the opaque passes
frustum.near =
index !== 0
? frustumCommands.near * scene.opaqueFrustumNearOffset
: frustumCommands.near;
frustum.far = frustumCommands.far;
us.updateFrustum(frustum);
}
clearDepth.execute(context, passState);
if (context.stencilBuffer) {
clearStencil.execute(context, passState);
}
us.updatePass(Pass.GLOBE);
let commands = frustumCommands.commands[Pass.GLOBE];
let length = frustumCommands.indices[Pass.GLOBE];
if (globeTranslucent) {
globeTranslucencyState.executeGlobeCommands(
frustumCommands,
executeCommand,
globeTranslucencyFramebuffer,
scene,
passState
);
} else {
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
}
const globeDepth = view.globeDepth;
if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
globeDepth.executeCopyDepth(context, passState);
}
// Draw terrain classification
if (!environmentState.renderTranslucentDepthForPick) {
us.updatePass(Pass.TERRAIN_CLASSIFICATION);
commands = frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION];
length = frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION];
if (globeTranslucent) {
globeTranslucencyState.executeGlobeClassificationCommands(
frustumCommands,
executeCommand,
globeTranslucencyFramebuffer,
scene,
passState
);
} else {
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
}
}
if (clearGlobeDepth) {
clearDepth.execute(context, passState);
if (useDepthPlane) {
depthPlane.execute(context, passState);
}
}
if (
!environmentState.useInvertClassification ||
picking ||
environmentState.renderTranslucentDepthForPick
) {
// Common/fastest path. Draw 3D Tiles and classification normally.
// Draw 3D Tiles
us.updatePass(Pass.CESIUM_3D_TILE);
commands = frustumCommands.commands[Pass.CESIUM_3D_TILE];
length = frustumCommands.indices[Pass.CESIUM_3D_TILE];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
if (length > 0) {
if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
// When clearGlobeDepth is true, executeUpdateDepth needs
// a globe depth texture with resolved stencil bits.
globeDepth.prepareColorTextures(context, clearGlobeDepth);
globeDepth.executeUpdateDepth(
context,
passState,
clearGlobeDepth,
globeDepth.depthStencilTexture
);
}
// Draw classifications. Modifies 3D Tiles color.
if (!environmentState.renderTranslucentDepthForPick) {
us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION);
commands =
frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION];
length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
}
}
} else {
// When the invert classification color is opaque:
// Main FBO (FBO1): Main_Color + Main_DepthStencil
// Invert classification FBO (FBO2) : Invert_Color + Main_DepthStencil
//
// 1. Clear FBO2 color to vec4(0.0) for each frustum
// 2. Draw 3D Tiles to FBO2
// 3. Draw classification to FBO2
// 4. Fullscreen pass to FBO1, draw Invert_Color when:
// * Main_DepthStencil has the stencil bit set > 0 (classified)
// 5. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when:
// * Main_DepthStencil has stencil bit set to 0 (unclassified) and
// * Invert_Color !== vec4(0.0)
//
// When the invert classification color is translucent:
// Main FBO (FBO1): Main_Color + Main_DepthStencil
// Invert classification FBO (FBO2): Invert_Color + Invert_DepthStencil
// IsClassified FBO (FBO3): IsClassified_Color + Invert_DepthStencil
//
// 1. Clear FBO2 and FBO3 color to vec4(0.0), stencil to 0, and depth to 1.0
// 2. Draw 3D Tiles to FBO2
// 3. Draw classification to FBO2
// 4. Fullscreen pass to FBO3, draw any color when
// * Invert_DepthStencil has the stencil bit set > 0 (classified)
// 5. Fullscreen pass to FBO1, draw Invert_Color when:
// * Invert_Color !== vec4(0.0) and
// * IsClassified_Color !== vec4(0.0)
// 6. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when:
// * Invert_Color !== vec4(0.0) and
// * IsClassified_Color === vec4(0.0)
//
// NOTE: Step six when translucent invert color occurs after the TRANSLUCENT pass
//
scene._invertClassification.clear(context, passState);
const opaqueClassificationFramebuffer = passState.framebuffer;
passState.framebuffer = scene._invertClassification._fbo.framebuffer;
// Draw normally
us.updatePass(Pass.CESIUM_3D_TILE);
commands = frustumCommands.commands[Pass.CESIUM_3D_TILE];
length = frustumCommands.indices[Pass.CESIUM_3D_TILE];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
scene._invertClassification.prepareTextures(context);
globeDepth.executeUpdateDepth(
context,
passState,
clearGlobeDepth,
scene._invertClassification._fbo.getDepthStencilTexture()
);
}
// Set stencil
us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW);
commands =
frustumCommands.commands[
Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW
];
length =
frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
passState.framebuffer = opaqueClassificationFramebuffer;
// Fullscreen pass to copy classified fragments
scene._invertClassification.executeClassified(context, passState);
if (frameState.invertClassificationColor.alpha === 1.0) {
// Fullscreen pass to copy unclassified fragments when alpha == 1.0
scene._invertClassification.executeUnclassified(context, passState);
}
// Clear stencil set by the classification for the next classification pass
if (length > 0 && context.stencilBuffer) {
clearClassificationStencil.execute(context, passState);
}
// Draw style over classification.
us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION);
commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION];
length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
}
if (length > 0 && context.stencilBuffer) {
clearStencil.execute(context, passState);
}
us.updatePass(Pass.OPAQUE);
commands = frustumCommands.commands[Pass.OPAQUE];
length = frustumCommands.indices[Pass.OPAQUE];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
if (index !== 0 && scene.mode !== SceneMode.SCENE2D) {
// Do not overlap frustums in the translucent pass to avoid blending artifacts
frustum.near = frustumCommands.near;
us.updateFrustum(frustum);
}
let invertClassification;
if (
!picking &&
environmentState.useInvertClassification &&
frameState.invertClassificationColor.alpha < 1.0
) {
// Fullscreen pass to copy unclassified fragments when alpha < 1.0.
// Not executed when undefined.
invertClassification = scene._invertClassification;
}
us.updatePass(Pass.TRANSLUCENT);
commands = frustumCommands.commands[Pass.TRANSLUCENT];
commands.length = frustumCommands.indices[Pass.TRANSLUCENT];
executeTranslucentCommands(
scene,
executeCommand,
passState,
commands,
invertClassification
);
// Classification for translucent 3D Tiles
const has3DTilesClassificationCommands =
frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] > 0;
if (
has3DTilesClassificationCommands &&
view.translucentTileClassification.isSupported()
) {
view.translucentTileClassification.executeTranslucentCommands(
scene,
executeCommand,
passState,
commands,
globeDepth.depthStencilTexture
);
view.translucentTileClassification.executeClassificationCommands(
scene,
executeCommand,
passState,
frustumCommands
);
}
if (
context.depthTexture &&
scene.useDepthPicking &&
(environmentState.useGlobeDepthFramebuffer ||
renderTranslucentDepthForPick)
) {
// PERFORMANCE_IDEA: Use MRT to avoid the extra copy.
const depthStencilTexture = globeDepth.depthStencilTexture;
const pickDepth = scene._picking.getPickDepth(scene, index);
pickDepth.update(context, depthStencilTexture);
pickDepth.executeCopyDepth(context, passState);
}
if (picking || !usePostProcessSelected) {
continue;
}
const originalFramebuffer = passState.framebuffer;
passState.framebuffer = view.sceneFramebuffer.getIdFramebuffer();
// reset frustum
frustum.near =
index !== 0
? frustumCommands.near * scene.opaqueFrustumNearOffset
: frustumCommands.near;
frustum.far = frustumCommands.far;
us.updateFrustum(frustum);
us.updatePass(Pass.GLOBE);
commands = frustumCommands.commands[Pass.GLOBE];
length = frustumCommands.indices[Pass.GLOBE];
if (globeTranslucent) {
globeTranslucencyState.executeGlobeCommands(
frustumCommands,
executeIdCommand,
globeTranslucencyFramebuffer,
scene,
passState
);
} else {
for (j = 0; j < length; ++j) {
executeIdCommand(commands[j], scene, context, passState);
}
}
if (clearGlobeDepth) {
clearDepth.framebuffer = passState.framebuffer;
clearDepth.execute(context, passState);
clearDepth.framebuffer = undefined;
}
if (clearGlobeDepth && useDepthPlane) {
depthPlane.execute(context, passState);
}
us.updatePass(Pass.CESIUM_3D_TILE);
commands = frustumCommands.commands[Pass.CESIUM_3D_TILE];
length = frustumCommands.indices[Pass.CESIUM_3D_TILE];
for (j = 0; j < length; ++j) {
executeIdCommand(commands[j], scene, context, passState);
}
us.updatePass(Pass.OPAQUE);
commands = frustumCommands.commands[Pass.OPAQUE];
length = frustumCommands.indices[Pass.OPAQUE];
for (j = 0; j < length; ++j) {
executeIdCommand(commands[j], scene, context, passState);
}
us.updatePass(Pass.TRANSLUCENT);
commands = frustumCommands.commands[Pass.TRANSLUCENT];
length = frustumCommands.indices[Pass.TRANSLUCENT];
for (j = 0; j < length; ++j) {
executeIdCommand(commands[j], scene, context, passState);
}
passState.framebuffer = originalFramebuffer;
}
}
function executeComputeCommands(scene) {
const us = scene.context.uniformState;
us.updatePass(Pass.COMPUTE);
const sunComputeCommand = scene._environmentState.sunComputeCommand;
if (defined(sunComputeCommand)) {
sunComputeCommand.execute(scene._computeEngine);
}
const commandList = scene._computeCommandList;
const length = commandList.length;
for (let i = 0; i < length; ++i) {
commandList[i].execute(scene._computeEngine);
}
}
function executeOverlayCommands(scene, passState) {
const us = scene.context.uniformState;
us.updatePass(Pass.OVERLAY);
const context = scene.context;
const commandList = scene._overlayCommandList;
const length = commandList.length;
for (let i = 0; i < length; ++i) {
commandList[i].execute(context, passState);
}
}
function insertShadowCastCommands(scene, commandList, shadowMap) {
const shadowVolume = shadowMap.shadowMapCullingVolume;
const isPointLight = shadowMap.isPointLight;
const passes = shadowMap.passes;
const numberOfPasses = passes.length;
const length = commandList.length;
for (let i = 0; i < length; ++i) {
const command = commandList[i];
scene.updateDerivedCommands(command);
if (
command.castShadows &&
(command.pass === Pass.GLOBE ||
command.pass === Pass.CESIUM_3D_TILE ||
command.pass === Pass.OPAQUE ||
command.pass === Pass.TRANSLUCENT)
) {
if (scene.isVisible(command, shadowVolume)) {
if (isPointLight) {
for (let k = 0; k < numberOfPasses; ++k) {
passes[k].commandList.push(command);
}
} else if (numberOfPasses === 1) {
passes[0].commandList.push(command);
} else {
let wasVisible = false;
// Loop over cascades from largest to smallest
for (let j = numberOfPasses - 1; j >= 0; --j) {
const cascadeVolume = passes[j].cullingVolume;
if (scene.isVisible(command, cascadeVolume)) {
passes[j].commandList.push(command);
wasVisible = true;
} else if (wasVisible) {
// If it was visible in the previous cascade but now isn't
// then there is no need to check any more cascades
break;
}
}
}
}
}
}
}
function executeShadowMapCastCommands(scene) {
const frameState = scene.frameState;
const shadowMaps = frameState.shadowState.shadowMaps;
const shadowMapLength = shadowMaps.length;
if (!frameState.shadowState.shadowsEnabled) {
return;
}
const context = scene.context;
const uniformState = context.uniformState;
for (let i = 0; i < shadowMapLength; ++i) {
const shadowMap = shadowMaps[i];
if (shadowMap.outOfView) {
continue;
}
// Reset the command lists
const passes = shadowMap.passes;
const numberOfPasses = passes.length;
for (let j = 0; j < numberOfPasses; ++j) {
passes[j].commandList.length = 0;
}
// Insert the primitive/model commands into the command lists
const sceneCommands = scene.frameState.commandList;
insertShadowCastCommands(scene, sceneCommands, shadowMap);
for (let j = 0; j < numberOfPasses; ++j) {
const pass = shadowMap.passes[j];
uniformState.updateCamera(pass.camera);
shadowMap.updatePass(context, j);
const numberOfCommands = pass.commandList.length;
for (let k = 0; k < numberOfCommands; ++k) {
const command = pass.commandList[k];
// Set the correct pass before rendering into the shadow map because some shaders
// conditionally render based on whether the pass is translucent or opaque.
uniformState.updatePass(command.pass);
executeCommand(
command.derivedCommands.shadows.castCommands[i],
scene,
context,
pass.passState
);
}
}
}
}
const scratchEyeTranslation = new Cartesian3();
/**
* @private
*/
Scene.prototype.updateAndExecuteCommands = function (
passState,
backgroundColor
) {
const frameState = this._frameState;
const mode = frameState.mode;
const useWebVR = this._environmentState.useWebVR;
if (useWebVR) {
executeWebVRCommands(this, passState, backgroundColor);
} else if (
mode !== SceneMode.SCENE2D ||
this._mapMode2D === MapMode2D.ROTATE
) {
executeCommandsInViewport(true, this, passState, backgroundColor);
} else {
updateAndClearFramebuffers(this, passState, backgroundColor);
execute2DViewportCommands(this, passState);
}
};
function executeWebVRCommands(scene, passState, backgroundColor) {
const view = scene._view;
const camera = view.camera;
const environmentState = scene._environmentState;
const renderTranslucentDepthForPick =
environmentState.renderTranslucentDepthForPick;
updateAndClearFramebuffers(scene, passState, backgroundColor);
updateAndRenderPrimitives(scene);
view.createPotentiallyVisibleSet(scene);
executeComputeCommands(scene);
if (!renderTranslucentDepthForPick) {
executeShadowMapCastCommands(scene);
}
// Based on Calculating Stereo pairs by Paul Bourke
// http://paulbourke.net/stereographics/stereorender/
const viewport = passState.viewport;
viewport.x = 0;
viewport.y = 0;
viewport.width = viewport.width * 0.5;
const savedCamera = Camera.clone(camera, scene._cameraVR);
savedCamera.frustum = camera.frustum;
const near = camera.frustum.near;
const fo = near * defaultValue(scene.focalLength, 5.0);
const eyeSeparation = defaultValue(scene.eyeSeparation, fo / 30.0);
const eyeTranslation = Cartesian3.multiplyByScalar(
savedCamera.right,
eyeSeparation * 0.5,
scratchEyeTranslation
);
camera.frustum.aspectRatio = viewport.width / viewport.height;
const offset = (0.5 * eyeSeparation * near) / fo;
Cartesian3.add(savedCamera.position, eyeTranslation, camera.position);
camera.frustum.xOffset = offset;
executeCommands(scene, passState);
viewport.x = viewport.width;
Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position);
camera.frustum.xOffset = -offset;
executeCommands(scene, passState);
Camera.clone(savedCamera, camera);
}
const scratch2DViewportCartographic = new Cartographic(
Math.PI,
CesiumMath.PI_OVER_TWO
);
const scratch2DViewportMaxCoord = new Cartesian3();
const scratch2DViewportSavedPosition = new Cartesian3();
const scratch2DViewportTransform = new Matrix4();
const scratch2DViewportCameraTransform = new Matrix4();
const scratch2DViewportEyePoint = new Cartesian3();
const scratch2DViewportWindowCoords = new Cartesian3();
const scratch2DViewport = new BoundingRectangle();
function execute2DViewportCommands(scene, passState) {
const context = scene.context;
const frameState = scene.frameState;
const camera = scene.camera;
const originalViewport = passState.viewport;
const viewport = BoundingRectangle.clone(originalViewport, scratch2DViewport);
passState.viewport = viewport;
const maxCartographic = scratch2DViewportCartographic;
const maxCoord = scratch2DViewportMaxCoord;
const projection = scene.mapProjection;
projection.project(maxCartographic, maxCoord);
const position = Cartesian3.clone(
camera.position,
scratch2DViewportSavedPosition
);
const transform = Matrix4.clone(
camera.transform,
scratch2DViewportCameraTransform
);
const frustum = camera.frustum.clone();
camera._setTransform(Matrix4.IDENTITY);
const viewportTransformation = Matrix4.computeViewportTransformation(
viewport,
0.0,
1.0,
scratch2DViewportTransform
);
const projectionMatrix = camera.frustum.projectionMatrix;
const x = camera.positionWC.y;
const eyePoint = Cartesian3.fromElements(
CesiumMath.sign(x) * maxCoord.x - x,
0.0,
-camera.positionWC.x,
scratch2DViewportEyePoint
);
const windowCoordinates = Transforms.pointToGLWindowCoordinates(
projectionMatrix,
viewportTransformation,
eyePoint,
scratch2DViewportWindowCoords
);
windowCoordinates.x = Math.floor(windowCoordinates.x);
const viewportX = viewport.x;
const viewportWidth = viewport.width;
if (
x === 0.0 ||
windowCoordinates.x <= viewportX ||
windowCoordinates.x >= viewportX + viewportWidth
) {
executeCommandsInViewport(true, scene, passState);
} else if (
Math.abs(viewportX + viewportWidth * 0.5 - windowCoordinates.x) < 1.0
) {
viewport.width = windowCoordinates.x - viewport.x;
camera.position.x *= CesiumMath.sign(camera.position.x);
camera.frustum.right = 0.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(
camera.positionWC,
camera.directionWC,
camera.upWC
);
context.uniformState.update(frameState);
executeCommandsInViewport(true, scene, passState);
viewport.x = windowCoordinates.x;
camera.position.x = -camera.position.x;
camera.frustum.right = -camera.frustum.left;
camera.frustum.left = 0.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(
camera.positionWC,
camera.directionWC,
camera.upWC
);
context.uniformState.update(frameState);
executeCommandsInViewport(false, scene, passState);
} else if (windowCoordinates.x > viewportX + viewportWidth * 0.5) {
viewport.width = windowCoordinates.x - viewportX;
const right = camera.frustum.right;
camera.frustum.right = maxCoord.x - x;
frameState.cullingVolume = camera.frustum.computeCullingVolume(
camera.positionWC,
camera.directionWC,
camera.upWC
);
context.uniformState.update(frameState);
executeCommandsInViewport(true, scene, passState);
viewport.x = windowCoordinates.x;
viewport.width = viewportX + viewportWidth - windowCoordinates.x;
camera.position.x = -camera.position.x;
camera.frustum.left = -camera.frustum.right;
camera.frustum.right = right - camera.frustum.right * 2.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(
camera.positionWC,
camera.directionWC,
camera.upWC
);
context.uniformState.update(frameState);
executeCommandsInViewport(false, scene, passState);
} else {
viewport.x = windowCoordinates.x;
viewport.width = viewportX + viewportWidth - windowCoordinates.x;
const left = camera.frustum.left;
camera.frustum.left = -maxCoord.x - x;
frameState.cullingVolume = camera.frustum.computeCullingVolume(
camera.positionWC,
camera.directionWC,
camera.upWC
);
context.uniformState.update(frameState);
executeCommandsInViewport(true, scene, passState);
viewport.x = viewportX;
viewport.width = windowCoordinates.x - viewportX;
camera.position.x = -camera.position.x;
camera.frustum.right = -camera.frustum.left;
camera.frustum.left = left - camera.frustum.left * 2.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(
camera.positionWC,
camera.directionWC,
camera.upWC
);
context.uniformState.update(frameState);
executeCommandsInViewport(false, scene, passState);
}
camera._setTransform(transform);
Cartesian3.clone(position, camera.position);
camera.frustum = frustum.clone();
passState.viewport = originalViewport;
}
function executeCommandsInViewport(
firstViewport,
scene,
passState,
backgroundColor
) {
const environmentState = scene._environmentState;
const view = scene._view;
const renderTranslucentDepthForPick =
environmentState.renderTranslucentDepthForPick;
if (!firstViewport) {
scene.frameState.commandList.length = 0;
}
updateAndRenderPrimitives(scene);
view.createPotentiallyVisibleSet(scene);
if (firstViewport) {
if (defined(backgroundColor)) {
updateAndClearFramebuffers(scene, passState, backgroundColor);
}
executeComputeCommands(scene);
if (!renderTranslucentDepthForPick) {
executeShadowMapCastCommands(scene);
}
}
executeCommands(scene, passState);
}
const scratchCullingVolume = new CullingVolume();
/**
* @private
*/
Scene.prototype.updateEnvironment = function () {
const frameState = this._frameState;
const view = this._view;
// Update celestial and terrestrial environment effects.
const environmentState = this._environmentState;
const renderPass = frameState.passes.render;
const offscreenPass = frameState.passes.offscreen;
const skyAtmosphere = this.skyAtmosphere;
const globe = this.globe;
const globeTranslucencyState = this._globeTranslucencyState;
if (
!renderPass ||
(this._mode !== SceneMode.SCENE2D &&
view.camera.frustum instanceof OrthographicFrustum) ||
!globeTranslucencyState.environmentVisible
) {
environmentState.skyAtmosphereCommand = undefined;
environmentState.skyBoxCommand = undefined;
environmentState.sunDrawCommand = undefined;
environmentState.sunComputeCommand = undefined;
environmentState.moonCommand = undefined;
} else {
if (defined(skyAtmosphere)) {
if (defined(globe)) {
skyAtmosphere.setDynamicAtmosphereColor(
globe.enableLighting && globe.dynamicAtmosphereLighting,
globe.dynamicAtmosphereLightingFromSun
);
environmentState.isReadyForAtmosphere =
environmentState.isReadyForAtmosphere ||
globe._surface._tilesToRender.length > 0;
}
environmentState.skyAtmosphereCommand = skyAtmosphere.update(
frameState,
globe
);
if (defined(environmentState.skyAtmosphereCommand)) {
this.updateDerivedCommands(environmentState.skyAtmosphereCommand);
}
} else {
environmentState.skyAtmosphereCommand = undefined;
}
environmentState.skyBoxCommand = defined(this.skyBox)
? this.skyBox.update(frameState, this._hdr)
: undefined;
const sunCommands = defined(this.sun)
? this.sun.update(frameState, view.passState, this._hdr)
: undefined;
environmentState.sunDrawCommand = defined(sunCommands)
? sunCommands.drawCommand
: undefined;
environmentState.sunComputeCommand = defined(sunCommands)
? sunCommands.computeCommand
: undefined;
environmentState.moonCommand = defined(this.moon)
? this.moon.update(frameState)
: undefined;
}
const clearGlobeDepth = (environmentState.clearGlobeDepth =
defined(globe) &&
globe.show &&
(!globe.depthTestAgainstTerrain || this.mode === SceneMode.SCENE2D));
const useDepthPlane = (environmentState.useDepthPlane =
clearGlobeDepth &&
this.mode === SceneMode.SCENE3D &&
globeTranslucencyState.useDepthPlane);
if (useDepthPlane) {
// Update the depth plane that is rendered in 3D when the primitives are
// not depth tested against terrain so primitives on the backface
// of the globe are not picked.
this._depthPlane.update(frameState);
}
environmentState.renderTranslucentDepthForPick = false;
environmentState.useWebVR =
this._useWebVR && this.mode !== SceneMode.SCENE2D && !offscreenPass;
const occluder =
frameState.mode === SceneMode.SCENE3D &&
!globeTranslucencyState.sunVisibleThroughGlobe
? frameState.occluder
: undefined;
let cullingVolume = frameState.cullingVolume;
// get user culling volume minus the far plane.
const planes = scratchCullingVolume.planes;
for (let k = 0; k < 5; ++k) {
planes[k] = cullingVolume.planes[k];
}
cullingVolume = scratchCullingVolume;
// Determine visibility of celestial and terrestrial environment effects.
environmentState.isSkyAtmosphereVisible =
defined(environmentState.skyAtmosphereCommand) &&
environmentState.isReadyForAtmosphere;
environmentState.isSunVisible = this.isVisible(
environmentState.sunDrawCommand,
cullingVolume,
occluder
);
environmentState.isMoonVisible = this.isVisible(
environmentState.moonCommand,
cullingVolume,
occluder
);
const envMaps = this.specularEnvironmentMaps;
let envMapAtlas = this._specularEnvironmentMapAtlas;
if (
defined(envMaps) &&
(!defined(envMapAtlas) || envMapAtlas.url !== envMaps)
) {
envMapAtlas = envMapAtlas && envMapAtlas.destroy();
this._specularEnvironmentMapAtlas = new OctahedralProjectedCubeMap(envMaps);
} else if (!defined(envMaps) && defined(envMapAtlas)) {
envMapAtlas.destroy();
this._specularEnvironmentMapAtlas = undefined;
}
if (defined(this._specularEnvironmentMapAtlas)) {
this._specularEnvironmentMapAtlas.update(frameState);
}
};
function updateDebugFrustumPlanes(scene) {
const frameState = scene._frameState;
if (scene.debugShowFrustumPlanes !== scene._debugShowFrustumPlanes) {
if (scene.debugShowFrustumPlanes) {
scene._debugFrustumPlanes = new DebugCameraPrimitive({
camera: scene.camera,
updateOnChange: false,
frustumSplits: frameState.frustumSplits,
});
} else {
scene._debugFrustumPlanes =
scene._debugFrustumPlanes && scene._debugFrustumPlanes.destroy();
}
scene._debugShowFrustumPlanes = scene.debugShowFrustumPlanes;
}
if (defined(scene._debugFrustumPlanes)) {
scene._debugFrustumPlanes.update(frameState);
}
}
function updateShadowMaps(scene) {
const frameState = scene._frameState;
const shadowMaps = frameState.shadowMaps;
const length = shadowMaps.length;
const shadowsEnabled =
length > 0 && !frameState.passes.pick && scene.mode === SceneMode.SCENE3D;
if (shadowsEnabled !== frameState.shadowState.shadowsEnabled) {
// Update derived commands when shadowsEnabled changes
++frameState.shadowState.lastDirtyTime;
frameState.shadowState.shadowsEnabled = shadowsEnabled;
}
frameState.shadowState.lightShadowsEnabled = false;
if (!shadowsEnabled) {
return;
}
// Check if the shadow maps are different than the shadow maps last frame.
// If so, the derived commands need to be updated.
for (let j = 0; j < length; ++j) {
if (shadowMaps[j] !== frameState.shadowState.shadowMaps[j]) {
++frameState.shadowState.lastDirtyTime;
break;
}
}
frameState.shadowState.shadowMaps.length = 0;
frameState.shadowState.lightShadowMaps.length = 0;
for (let i = 0; i < length; ++i) {
const shadowMap = shadowMaps[i];
shadowMap.update(frameState);
frameState.shadowState.shadowMaps.push(shadowMap);
if (shadowMap.fromLightSource) {
frameState.shadowState.lightShadowMaps.push(shadowMap);
frameState.shadowState.lightShadowsEnabled = true;
}
if (shadowMap.dirty) {
++frameState.shadowState.lastDirtyTime;
shadowMap.dirty = false;
}
}
}
function updateAndRenderPrimitives(scene) {
const frameState = scene._frameState;
scene._groundPrimitives.update(frameState);
scene._primitives.update(frameState);
updateDebugFrustumPlanes(scene);
updateShadowMaps(scene);
if (scene._globe) {
scene._globe.render(frameState);
}
}
function updateAndClearFramebuffers(scene, passState, clearColor) {
const context = scene._context;
const frameState = scene._frameState;
const environmentState = scene._environmentState;
const view = scene._view;
const passes = scene._frameState.passes;
const picking = passes.pick;
if (defined(view.globeDepth)) {
view.globeDepth.picking = picking;
}
const useWebVR = environmentState.useWebVR;
// Preserve the reference to the original framebuffer.
environmentState.originalFramebuffer = passState.framebuffer;
// Manage sun bloom post-processing effect.
if (defined(scene.sun) && scene.sunBloom !== scene._sunBloom) {
if (scene.sunBloom && !useWebVR) {
scene._sunPostProcess = new SunPostProcess();
} else if (defined(scene._sunPostProcess)) {
scene._sunPostProcess = scene._sunPostProcess.destroy();
}
scene._sunBloom = scene.sunBloom;
} else if (!defined(scene.sun) && defined(scene._sunPostProcess)) {
scene._sunPostProcess = scene._sunPostProcess.destroy();
scene._sunBloom = false;
}
// Clear the pass state framebuffer.
const clear = scene._clearColorCommand;
Color.clone(clearColor, clear.color);
clear.execute(context, passState);
// Update globe depth rendering based on the current context and clear the globe depth framebuffer.
// Globe depth is copied for the pick pass to support picking batched geometries in GroundPrimitives.
const useGlobeDepthFramebuffer = (environmentState.useGlobeDepthFramebuffer = defined(
view.globeDepth
));
if (useGlobeDepthFramebuffer) {
view.globeDepth.update(
context,
passState,
view.viewport,
scene.msaaSamples,
scene._hdr,
environmentState.clearGlobeDepth
);
view.globeDepth.clear(context, passState, clearColor);
}
// If supported, configure OIT to use the globe depth framebuffer and clear the OIT framebuffer.
const oit = view.oit;
const useOIT = (environmentState.useOIT =
!picking && defined(oit) && oit.isSupported());
if (useOIT) {
oit.update(
context,
passState,
view.globeDepth.colorFramebufferManager,
scene._hdr,
scene.msaaSamples
);
oit.clear(context, passState, clearColor);
environmentState.useOIT = oit.isSupported();
}
const postProcess = scene.postProcessStages;
let usePostProcess = (environmentState.usePostProcess =
!picking &&
(scene._hdr ||
postProcess.length > 0 ||
postProcess.ambientOcclusion.enabled ||
postProcess.fxaa.enabled ||
postProcess.bloom.enabled));
environmentState.usePostProcessSelected = false;
if (usePostProcess) {
view.sceneFramebuffer.update(
context,
view.viewport,
scene._hdr,
scene.msaaSamples
);
view.sceneFramebuffer.clear(context, passState, clearColor);
postProcess.update(context, frameState.useLogDepth, scene._hdr);
postProcess.clear(context);
usePostProcess = environmentState.usePostProcess = postProcess.ready;
environmentState.usePostProcessSelected =
usePostProcess && postProcess.hasSelected;
}
if (environmentState.isSunVisible && scene.sunBloom && !useWebVR) {
passState.framebuffer = scene._sunPostProcess.update(passState);
scene._sunPostProcess.clear(context, passState, clearColor);
} else if (useGlobeDepthFramebuffer) {
passState.framebuffer = view.globeDepth.framebuffer;
} else if (usePostProcess) {
passState.framebuffer = view.sceneFramebuffer.framebuffer;
}
if (defined(passState.framebuffer)) {
clear.execute(context, passState);
}
const useInvertClassification = (environmentState.useInvertClassification =
!picking && defined(passState.framebuffer) && scene.invertClassification);
if (useInvertClassification) {
let depthFramebuffer;
if (scene.frameState.invertClassificationColor.alpha === 1.0) {
if (environmentState.useGlobeDepthFramebuffer) {
depthFramebuffer = view.globeDepth.framebuffer;
}
}
if (defined(depthFramebuffer) || context.depthTexture) {
scene._invertClassification.previousFramebuffer = depthFramebuffer;
scene._invertClassification.update(
context,
scene.msaaSamples,
view.globeDepth.colorFramebufferManager
);
scene._invertClassification.clear(context, passState);
if (scene.frameState.invertClassificationColor.alpha < 1.0 && useOIT) {
const command = scene._invertClassification.unclassifiedCommand;
const derivedCommands = command.derivedCommands;
derivedCommands.oit = oit.createDerivedCommands(
command,
context,
derivedCommands.oit
);
}
} else {
environmentState.useInvertClassification = false;
}
}
if (scene._globeTranslucencyState.translucent) {
view.globeTranslucencyFramebuffer.updateAndClear(
scene._hdr,
view.viewport,
context,
passState
);
}
}
/**
* @private
*/
Scene.prototype.resolveFramebuffers = function (passState) {
const context = this._context;
const environmentState = this._environmentState;
const view = this._view;
const globeDepth = view.globeDepth;
if (defined(globeDepth)) {
globeDepth.prepareColorTextures(context);
}
const useOIT = environmentState.useOIT;
const useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer;
const usePostProcess = environmentState.usePostProcess;
const defaultFramebuffer = environmentState.originalFramebuffer;
const globeFramebuffer = useGlobeDepthFramebuffer
? globeDepth.colorFramebufferManager
: undefined;
const sceneFramebuffer = view.sceneFramebuffer._colorFramebuffer;
const idFramebuffer = view.sceneFramebuffer.idFramebuffer;
if (useOIT) {
passState.framebuffer = usePostProcess
? sceneFramebuffer.framebuffer
: defaultFramebuffer;
view.oit.execute(context, passState);
}
const translucentTileClassification = view.translucentTileClassification;
if (
translucentTileClassification.hasTranslucentDepth &&
translucentTileClassification.isSupported()
) {
translucentTileClassification.execute(this, passState);
}
if (usePostProcess) {
view.sceneFramebuffer.prepareColorTextures(context);
let inputFramebuffer = sceneFramebuffer;
if (useGlobeDepthFramebuffer && !useOIT) {
inputFramebuffer = globeFramebuffer;
}
const postProcess = this.postProcessStages;
const colorTexture = inputFramebuffer.getColorTexture(0);
const idTexture = idFramebuffer.getColorTexture(0);
const depthTexture = defaultValue(
globeFramebuffer,
sceneFramebuffer
).getDepthStencilTexture();
postProcess.execute(context, colorTexture, depthTexture, idTexture);
postProcess.copy(context, defaultFramebuffer);
}
if (!useOIT && !usePostProcess && useGlobeDepthFramebuffer) {
passState.framebuffer = defaultFramebuffer;
globeDepth.executeCopyColor(context, passState);
}
};
function callAfterRenderFunctions(scene) {
// Functions are queued up during primitive update and executed here in case
// the function modifies scene state that should remain constant over the frame.
const functions = scene._frameState.afterRender;
for (let i = 0, length = functions.length; i < length; ++i) {
functions[i]();
scene.requestRender();
}
functions.length = 0;
}
function getGlobeHeight(scene) {
const globe = scene._globe;
const camera = scene.camera;
const cartographic = camera.positionCartographic;
if (defined(globe) && globe.show && defined(cartographic)) {
return globe.getHeight(cartographic);
}
return undefined;
}
function isCameraUnderground(scene) {
const camera = scene.camera;
const mode = scene._mode;
const globe = scene.globe;
const cameraController = scene._screenSpaceCameraController;
const cartographic = camera.positionCartographic;
if (!defined(cartographic)) {
return false;
}
if (!cameraController.onMap() && cartographic.height < 0.0) {
// The camera can go off the map while in Columbus View.
// Make a best guess as to whether it's underground by checking if its height is less than zero.
return true;
}
if (
!defined(globe) ||
!globe.show ||
mode === SceneMode.SCENE2D ||
mode === SceneMode.MORPHING
) {
return false;
}
const globeHeight = scene._globeHeight;
return defined(globeHeight) && cartographic.height < globeHeight;
}
/**
* @private
*/
Scene.prototype.initializeFrame = function () {
// Destroy released shaders and textures once every 120 frames to avoid thrashing the cache
if (this._shaderFrameCount++ === 120) {
this._shaderFrameCount = 0;
this._context.shaderCache.destroyReleasedShaderPrograms();
this._context.textureCache.destroyReleasedTextures();
}
this._tweens.update();
this._globeHeight = getGlobeHeight(this);
this._cameraUnderground = isCameraUnderground(this);
this._globeTranslucencyState.update(this);
this._screenSpaceCameraController.update();
if (defined(this._deviceOrientationCameraController)) {
this._deviceOrientationCameraController.update();
}
this.camera.update(this._mode);
this.camera._updateCameraChanged();
};
function updateDebugShowFramesPerSecond(scene, renderedThisFrame) {
if (scene.debugShowFramesPerSecond) {
if (!defined(scene._performanceDisplay)) {
const performanceContainer = document.createElement("div");
performanceContainer.className =
"cesium-performanceDisplay-defaultContainer";
const container = scene._canvas.parentNode;
container.appendChild(performanceContainer);
const performanceDisplay = new PerformanceDisplay({
container: performanceContainer,
});
scene._performanceDisplay = performanceDisplay;
scene._performanceContainer = performanceContainer;
}
scene._performanceDisplay.throttled = scene.requestRenderMode;
scene._performanceDisplay.update(renderedThisFrame);
} else if (defined(scene._performanceDisplay)) {
scene._performanceDisplay =
scene._performanceDisplay && scene._performanceDisplay.destroy();
scene._performanceContainer.parentNode.removeChild(
scene._performanceContainer
);
}
}
function prePassesUpdate(scene) {
scene._jobScheduler.resetBudgets();
const frameState = scene._frameState;
const primitives = scene.primitives;
primitives.prePassesUpdate(frameState);
if (defined(scene.globe)) {
scene.globe.update(frameState);
}
scene._picking.update();
frameState.creditDisplay.update();
}
function postPassesUpdate(scene) {
const frameState = scene._frameState;
const primitives = scene.primitives;
primitives.postPassesUpdate(frameState);
RequestScheduler.update();
}
const scratchBackgroundColor = new Color();
function render(scene) {
const frameState = scene._frameState;
const context = scene.context;
const us = context.uniformState;
const view = scene._defaultView;
scene._view = view;
scene.updateFrameState();
frameState.passes.render = true;
frameState.passes.postProcess = scene.postProcessStages.hasSelected;
frameState.tilesetPassState = renderTilesetPassState;
let backgroundColor = defaultValue(scene.backgroundColor, Color.BLACK);
if (scene._hdr) {
backgroundColor = Color.clone(backgroundColor, scratchBackgroundColor);
backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma);
backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma);
backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma);
}
frameState.backgroundColor = backgroundColor;
scene.fog.update(frameState);
us.update(frameState);
const shadowMap = scene.shadowMap;
if (defined(shadowMap) && shadowMap.enabled) {
if (!defined(scene.light) || scene.light instanceof SunLight) {
// Negate the sun direction so that it is from the Sun, not to the Sun
Cartesian3.negate(us.sunDirectionWC, scene._shadowMapCamera.direction);
} else {
Cartesian3.clone(scene.light.direction, scene._shadowMapCamera.direction);
}
frameState.shadowMaps.push(shadowMap);
}
scene._computeCommandList.length = 0;
scene._overlayCommandList.length = 0;
const viewport = view.viewport;
viewport.x = 0;
viewport.y = 0;
viewport.width = context.drawingBufferWidth;
viewport.height = context.drawingBufferHeight;
const passState = view.passState;
passState.framebuffer = undefined;
passState.blendingEnabled = undefined;
passState.scissorTest = undefined;
passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
if (defined(scene.globe)) {
scene.globe.beginFrame(frameState);
}
scene.updateEnvironment();
scene.updateAndExecuteCommands(passState, backgroundColor);
scene.resolveFramebuffers(passState);
passState.framebuffer = undefined;
executeOverlayCommands(scene, passState);
if (defined(scene.globe)) {
scene.globe.endFrame(frameState);
if (!scene.globe.tilesLoaded) {
scene._renderRequested = true;
}
}
context.endFrame();
}
function tryAndCatchError(scene, functionToExecute) {
try {
functionToExecute(scene);
} catch (error) {
scene._renderError.raiseEvent(scene, error);
if (scene.rethrowRenderErrors) {
throw error;
}
}
}
function updateMostDetailedRayPicks(scene) {
return scene._picking.updateMostDetailedRayPicks(scene);
}
/**
* Update and render the scene. It is usually not necessary to call this function
* directly because {@link CesiumWidget} or {@link Viewer} do it automatically.
* @param {JulianDate} [time] The simulation time at which to render.
*/
Scene.prototype.render = function (time) {
/**
*
* Pre passes update. Execute any pass invariant code that should run before the passes here.
*
*/
this._preUpdate.raiseEvent(this, time);
const frameState = this._frameState;
frameState.newFrame = false;
if (!defined(time)) {
time = JulianDate.now();
}
// Determine if shouldRender
const cameraChanged = this._view.checkForCameraUpdates(this);
let shouldRender =
!this.requestRenderMode ||
this._renderRequested ||
cameraChanged ||
this._logDepthBufferDirty ||
this._hdrDirty ||
this.mode === SceneMode.MORPHING;
if (
!shouldRender &&
defined(this.maximumRenderTimeChange) &&
defined(this._lastRenderTime)
) {
const difference = Math.abs(
JulianDate.secondsDifference(this._lastRenderTime, time)
);
shouldRender = shouldRender || difference > this.maximumRenderTimeChange;
}
if (shouldRender) {
this._lastRenderTime = JulianDate.clone(time, this._lastRenderTime);
this._renderRequested = false;
this._logDepthBufferDirty = false;
this._hdrDirty = false;
const frameNumber = CesiumMath.incrementWrap(
frameState.frameNumber,
15000000.0,
1.0
);
updateFrameNumber(this, frameNumber, time);
frameState.newFrame = true;
}
tryAndCatchError(this, prePassesUpdate);
/**
*
* Passes update. Add any passes here
*
*/
if (this.primitives.show) {
tryAndCatchError(this, updateMostDetailedRayPicks);
tryAndCatchError(this, updatePreloadPass);
tryAndCatchError(this, updatePreloadFlightPass);
if (!shouldRender) {
tryAndCatchError(this, updateRequestRenderModeDeferCheckPass);
}
}
this._postUpdate.raiseEvent(this, time);
if (shouldRender) {
this._preRender.raiseEvent(this, time);
frameState.creditDisplay.beginFrame();
tryAndCatchError(this, render);
}
/**
*
* Post passes update. Execute any pass invariant code that should run after the passes here.
*
*/
updateDebugShowFramesPerSecond(this, shouldRender);
tryAndCatchError(this, postPassesUpdate);
// Often used to trigger events (so don't want in trycatch) that the user might be subscribed to. Things like the tile load events, ready promises, etc.
// We don't want those events to resolve during the render loop because the events might add new primitives
callAfterRenderFunctions(this);
if (shouldRender) {
this._postRender.raiseEvent(this, time);
frameState.creditDisplay.endFrame();
}
};
/**
* Update and render the scene. Always forces a new render frame regardless of whether a render was
* previously requested.
* @param {JulianDate} [time] The simulation time at which to render.
*
* @private
*/
Scene.prototype.forceRender = function (time) {
this._renderRequested = true;
this.render(time);
};
/**
* Requests a new rendered frame when {@link Scene#requestRenderMode} is set to true
.
* The render rate will not exceed the {@link CesiumWidget#targetFrameRate}.
*
* @see Scene#requestRenderMode
*/
Scene.prototype.requestRender = function () {
this._renderRequested = true;
};
/**
* @private
*/
Scene.prototype.clampLineWidth = function (width) {
return Math.max(
ContextLimits.minimumAliasedLineWidth,
Math.min(width, ContextLimits.maximumAliasedLineWidth)
);
};
/**
* Returns an object with a `primitive` property that contains the first (top) primitive in the scene
* at a particular window coordinate or undefined if nothing is at the location. Other properties may
* potentially be set depending on the type of primitive and may be used to further identify the picked object.
*
* When a feature of a 3D Tiles tileset is picked, pick
returns a {@link Cesium3DTileFeature} object.
*
* Set {@link Scene#pickTranslucentDepth} to true
to include the depth of
* translucent primitives; otherwise, this essentially picks through translucent primitives.
*
* The position reconstructed from the depth buffer in 2D may be slightly different from those * reconstructed in 3D and Columbus view. This is caused by the difference in the distribution * of depth values of perspective and orthographic projection. *
*
* Set {@link Scene#pickTranslucentDepth} to true
to include the depth of
* translucent primitives; otherwise, this essentially picks through translucent primitives.
*
undefined
if there were no intersections. The intersected object has a primitive
* property that contains the intersected primitive. Other properties may be set depending on the type of primitive
* and may be used to further identify the picked object. The ray must be given in world coordinates.
* * This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other * primitives regardless of their visibility. *
* * @private * * @param {Ray} ray The ray. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Object} An object containing the object and position of the first intersection. * * @exception {DeveloperError} Ray intersections are only supported in 3D mode. */ Scene.prototype.pickFromRay = function (ray, objectsToExclude, width) { return this._picking.pickFromRay(this, ray, objectsToExclude, width); }; /** * Returns a list of objects, each containing the object intersected by the ray and the position of intersection. * The intersected object has aprimitive
property that contains the intersected primitive. Other
* properties may also be set depending on the type of primitive and may be used to further identify the picked object.
* The primitives in the list are ordered by first intersection to last intersection. The ray must be given in
* world coordinates.
* * This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other * primitives regardless of their visibility. *
* * @private * * @param {Ray} ray The ray. * @param {Number} [limit=Number.MAX_VALUE] If supplied, stop finding intersections after this many intersections. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Object[]} List of objects containing the object and position of each intersection. * * @exception {DeveloperError} Ray intersections are only supported in 3D mode. */ Scene.prototype.drillPickFromRay = function ( ray, limit, objectsToExclude, width ) { return this._picking.drillPickFromRay( this, ray, limit, objectsToExclude, width ); }; /** * Initiates an asynchronous {@link Scene#pickFromRay} request using the maximum level of detail for 3D Tilesets * regardless of visibility. * * @private * * @param {Ray} ray The ray. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Promise.