import buildModuleUrl from '../../Core/buildModuleUrl.js';
import Cartesian3 from '../../Core/Cartesian3.js';
import Clock from '../../Core/Clock.js';
import defaultValue from '../../Core/defaultValue.js';
import defined from '../../Core/defined.js';
import defineProperties from '../../Core/defineProperties.js';
import destroyObject from '../../Core/destroyObject.js';
import DeveloperError from '../../Core/DeveloperError.js';
import Ellipsoid from '../../Core/Ellipsoid.js';
import FeatureDetection from '../../Core/FeatureDetection.js';
import formatError from '../../Core/formatError.js';
import requestAnimationFrame from '../../Core/requestAnimationFrame.js';
import ScreenSpaceEventHandler from '../../Core/ScreenSpaceEventHandler.js';
import createWorldImagery from '../../Scene/createWorldImagery.js';
import Globe from '../../Scene/Globe.js';
import Moon from '../../Scene/Moon.js';
import Scene from '../../Scene/Scene.js';
import SceneMode from '../../Scene/SceneMode.js';
import ShadowMode from '../../Scene/ShadowMode.js';
import SkyAtmosphere from '../../Scene/SkyAtmosphere.js';
import SkyBox from '../../Scene/SkyBox.js';
import Sun from '../../Scene/Sun.js';
import getElement from '../getElement.js';
function getDefaultSkyBoxUrl(suffix) {
return buildModuleUrl('Assets/Textures/SkyBox/tycho2t3_80_' + suffix + '.jpg');
}
function startRenderLoop(widget) {
widget._renderLoopRunning = true;
var lastFrameTime = 0;
function render(frameTime) {
if (widget.isDestroyed()) {
return;
}
if (widget._useDefaultRenderLoop) {
try {
var targetFrameRate = widget._targetFrameRate;
if (!defined(targetFrameRate)) {
widget.resize();
widget.render();
requestAnimationFrame(render);
} else {
var interval = 1000.0 / targetFrameRate;
var delta = frameTime - lastFrameTime;
if (delta > interval) {
widget.resize();
widget.render();
lastFrameTime = frameTime - (delta % interval);
}
requestAnimationFrame(render);
}
} catch (error) {
widget._useDefaultRenderLoop = false;
widget._renderLoopRunning = false;
if (widget._showRenderLoopErrors) {
var title = 'An error occurred while rendering. Rendering has stopped.';
widget.showErrorPanel(title, undefined, error);
}
}
} else {
widget._renderLoopRunning = false;
}
}
requestAnimationFrame(render);
}
function configurePixelRatio(widget) {
var pixelRatio = widget._useBrowserRecommendedResolution ? 1.0 : window.devicePixelRatio;
pixelRatio *= widget._resolutionScale;
if (defined(widget._scene)) {
widget._scene.pixelRatio = pixelRatio;
}
return pixelRatio;
}
function configureCanvasSize(widget) {
var canvas = widget._canvas;
var width = canvas.clientWidth;
var height = canvas.clientHeight;
var pixelRatio = configurePixelRatio(widget);
widget._canvasClientWidth = width;
widget._canvasClientHeight = height;
width *= pixelRatio;
height *= pixelRatio;
canvas.width = width;
canvas.height = height;
widget._canRender = width !== 0 && height !== 0;
widget._lastDevicePixelRatio = window.devicePixelRatio;
}
function configureCameraFrustum(widget) {
var canvas = widget._canvas;
var width = canvas.width;
var height = canvas.height;
if (width !== 0 && height !== 0) {
var frustum = widget._scene.camera.frustum;
if (defined(frustum.aspectRatio)) {
frustum.aspectRatio = width / height;
} else {
frustum.top = frustum.right * (height / width);
frustum.bottom = -frustum.top;
}
}
}
/**
* A widget containing a Cesium scene.
*
* @alias CesiumWidget
* @constructor
*
* @param {Element|String} container The DOM element or ID that will contain the widget.
* @param {Object} [options] Object with the following properties:
* @param {Clock} [options.clock=new Clock()] The clock to use to control current time.
* @param {ImageryProvider} [options.imageryProvider=createWorldImagery()] The imagery provider to serve as the base layer. If set to false
, no imagery provider will be added.
* @param {TerrainProvider} [options.terrainProvider=new EllipsoidTerrainProvider] The terrain provider.
* @param {SkyBox} [options.skyBox] The skybox used to render the stars. When undefined
, the default stars are used. If set to false
, no skyBox, Sun, or Moon will be added.
* @param {SkyAtmosphere} [options.skyAtmosphere] Blue sky, and the glow around the Earth's limb. Set to false
to turn it off.
* @param {SceneMode} [options.sceneMode=SceneMode.SCENE3D] The initial scene mode.
* @param {Boolean} [options.scene3DOnly=false] When true
, each geometry instance will only be rendered in 3D to save GPU memory.
* @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
* @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
* @param {Globe} [options.globe=new Globe(mapProjection.ellipsoid)] The globe to use in the scene. If set to false
, no globe will be added.
* @param {Boolean} [options.useDefaultRenderLoop=true] True if this widget should control the render loop, false otherwise.
* @param {Boolean} [options.useBrowserRecommendedResolution=false] If true, render at the browser's recommended resolution and ignore window.devicePixelRatio
.
* @param {Number} [options.targetFrameRate] The target frame rate when using the default render loop.
* @param {Boolean} [options.showRenderLoopErrors=true] If true, this widget will automatically display an HTML panel to the user containing the error, if a render loop error occurs.
* @param {Object} [options.contextOptions] Context and WebGL creation properties corresponding to options
passed to {@link Scene}.
* @param {Element|String} [options.creditContainer] The DOM element or ID that will contain the {@link CreditDisplay}. If not specified, the credits are added
* to the bottom of the widget itself.
* @param {Element|String} [options.creditViewport] The DOM element or ID that will contain the credit pop up created by the {@link CreditDisplay}. If not specified, it will appear over the widget itself.
* @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
* @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
* @param {ShadowMode} [options.terrainShadows=ShadowMode.RECEIVE_ONLY] Determines if the terrain casts or receives shadows from the sun.
* @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
* @param {Boolean} [options.requestRenderMode=false] If true, 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}.
* @param {Number} [options.maximumRenderTimeChange=0.0] If requestRenderMode is true, this value defines the maximum change in simulation time allowed before a render is requested. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
*
* @exception {DeveloperError} Element with id "container" does not exist in the document.
*
* @demo {@link https://sandcastle.cesium.com/index.html?src=Cesium%20Widget.html|Cesium Sandcastle Cesium Widget Demo}
*
* @example
* // For each example, include a link to CesiumWidget.css stylesheet in HTML head,
* // and in the body, include:
useDefaultRenderLoop
* is true. If undefined, the browser's {@link requestAnimationFrame} implementation
* determines the frame rate. If defined, this value must be greater than 0. A value higher
* than the underlying requestAnimationFrame implementation will have no effect.
* @memberof CesiumWidget.prototype
*
* @type {Number}
*/
targetFrameRate : {
get : function() {
return this._targetFrameRate;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
if (value <= 0) {
throw new DeveloperError('targetFrameRate must be greater than 0, or undefined.');
}
//>>includeEnd('debug');
this._targetFrameRate = value;
}
},
/**
* Gets or sets whether or not this widget should control the render loop.
* If set to true the widget will use {@link requestAnimationFrame} to
* perform rendering and resizing of the widget, as well as drive the
* simulation clock. If set to false, you must manually call the
* resize
, render
methods as part of a custom
* render loop. If an error occurs during rendering, {@link Scene}'s
* renderError
event will be raised and this property
* will be set to false. It must be set back to true to continue rendering
* after the error.
* @memberof CesiumWidget.prototype
*
* @type {Boolean}
*/
useDefaultRenderLoop : {
get : function() {
return this._useDefaultRenderLoop;
},
set : function(value) {
if (this._useDefaultRenderLoop !== value) {
this._useDefaultRenderLoop = value;
if (value && !this._renderLoopRunning) {
startRenderLoop(this);
}
}
}
},
/**
* Gets or sets a scaling factor for rendering resolution. Values less than 1.0 can improve
* performance on less powerful devices while values greater than 1.0 will render at a higher
* resolution and then scale down, resulting in improved visual fidelity.
* For example, if the widget is laid out at a size of 640x480, setting this value to 0.5
* will cause the scene to be rendered at 320x240 and then scaled up while setting
* it to 2.0 will cause the scene to be rendered at 1280x960 and then scaled down.
* @memberof CesiumWidget.prototype
*
* @type {Number}
* @default 1.0
*/
resolutionScale : {
get : function() {
return this._resolutionScale;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
if (value <= 0) {
throw new DeveloperError('resolutionScale must be greater than 0.');
}
//>>includeEnd('debug');
if (this._resolutionScale !== value) {
this._resolutionScale = value;
this._forceResize = true;
}
}
},
/**
* Boolean flag indicating if the browser's recommended resolution is used.
* If true, the browser's device pixel ratio is ignored and 1.0 is used instead,
* effectively rendering based on CSS pixels instead of device pixels. This can improve
* performance on less powerful devices that have high pixel density. When false, rendering
* will be in device pixels. {@link CesiumWidget#resolutionScale} will still take effect whether
* this flag is true or false.
* @memberof CesiumWidget.prototype
*
* @type {Boolean}
* @default false
*/
useBrowserRecommendedResolution : {
get : function() {
return this._useBrowserRecommendedResolution;
},
set : function(value) {
if (this._useBrowserRecommendedResolution !== value) {
this._useBrowserRecommendedResolution = value;
this._forceResize = true;
}
}
}
});
/**
* Show an error panel to the user containing a title and a longer error message,
* which can be dismissed using an OK button. This panel is displayed automatically
* when a render loop error occurs, if showRenderLoopErrors was not false when the
* widget was constructed.
*
* @param {String} title The title to be displayed on the error panel. This string is interpreted as text.
* @param {String} message A helpful, user-facing message to display prior to the detailed error information. This string is interpreted as HTML.
* @param {String} [error] The error to be displayed on the error panel. This string is formatted using {@link formatError} and then displayed as text.
*/
CesiumWidget.prototype.showErrorPanel = function(title, message, error) {
var element = this._element;
var overlay = document.createElement('div');
overlay.className = 'cesium-widget-errorPanel';
var content = document.createElement('div');
content.className = 'cesium-widget-errorPanel-content';
overlay.appendChild(content);
var errorHeader = document.createElement('div');
errorHeader.className = 'cesium-widget-errorPanel-header';
errorHeader.appendChild(document.createTextNode(title));
content.appendChild(errorHeader);
var errorPanelScroller = document.createElement('div');
errorPanelScroller.className = 'cesium-widget-errorPanel-scroll';
content.appendChild(errorPanelScroller);
function resizeCallback() {
errorPanelScroller.style.maxHeight = Math.max(Math.round(element.clientHeight * 0.9 - 100), 30) + 'px';
}
resizeCallback();
if (defined(window.addEventListener)) {
window.addEventListener('resize', resizeCallback, false);
}
if (defined(message)) {
var errorMessage = document.createElement('div');
errorMessage.className = 'cesium-widget-errorPanel-message';
errorMessage.innerHTML = '' + message + '
'; errorPanelScroller.appendChild(errorMessage); } var errorDetails = '(no error details available)'; if (defined(error)) { errorDetails = formatError(error); } var errorMessageDetails = document.createElement('div'); errorMessageDetails.className = 'cesium-widget-errorPanel-message'; errorMessageDetails.appendChild(document.createTextNode(errorDetails)); errorPanelScroller.appendChild(errorMessageDetails); var buttonPanel = document.createElement('div'); buttonPanel.className = 'cesium-widget-errorPanel-buttonPanel'; content.appendChild(buttonPanel); var okButton = document.createElement('button'); okButton.setAttribute('type', 'button'); okButton.className = 'cesium-button'; okButton.appendChild(document.createTextNode('OK')); okButton.onclick = function() { if (defined(resizeCallback) && defined(window.removeEventListener)) { window.removeEventListener('resize', resizeCallback, false); } element.removeChild(overlay); }; buttonPanel.appendChild(okButton); element.appendChild(overlay); //IE8 does not have a console object unless the dev tools are open. if (typeof console !== 'undefined') { console.error(title + '\n' + message + '\n' + errorDetails); } }; /** * @returns {Boolean} true if the object has been destroyed, false otherwise. */ CesiumWidget.prototype.isDestroyed = function() { return false; }; /** * Destroys the widget. Should be called if permanently * removing the widget from layout. */ CesiumWidget.prototype.destroy = function() { this._scene = this._scene && this._scene.destroy(); this._container.removeChild(this._element); this._creditContainer.removeChild(this._innerCreditContainer); destroyObject(this); }; /** * Updates the canvas size, camera aspect ratio, and viewport size. * This function is called automatically as needed unless *useDefaultRenderLoop
is set to false.
*/
CesiumWidget.prototype.resize = function() {
var canvas = this._canvas;
if (!this._forceResize && this._canvasClientWidth === canvas.clientWidth && this._canvasClientHeight === canvas.clientHeight && this._lastDevicePixelRatio === window.devicePixelRatio) {
return;
}
this._forceResize = false;
configureCanvasSize(this);
configureCameraFrustum(this);
this._scene.requestRender();
};
/**
* Renders the scene. This function is called automatically
* unless useDefaultRenderLoop
is set to false;
*/
CesiumWidget.prototype.render = function() {
if (this._canRender) {
this._scene.initializeFrame();
var currentTime = this._clock.tick();
this._scene.render(currentTime);
} else {
this._clock.tick();
}
};
export default CesiumWidget;