import BoundingRectangle from '../Core/BoundingRectangle.js'; import Cartesian3 from '../Core/Cartesian3.js'; import CullingVolume from '../Core/CullingVolume.js'; import defined from '../Core/defined.js'; import getTimestamp from '../Core/getTimestamp.js'; import Interval from '../Core/Interval.js'; import CesiumMath from '../Core/Math.js'; import Matrix4 from '../Core/Matrix4.js'; import ClearCommand from '../Renderer/ClearCommand.js'; import Pass from '../Renderer/Pass.js'; import PassState from '../Renderer/PassState.js'; import Camera from './Camera.js'; import FrustumCommands from './FrustumCommands.js'; import GlobeDepth from './GlobeDepth.js'; import OIT from './OIT.js'; import PickDepthFramebuffer from './PickDepthFramebuffer.js'; import PickFramebuffer from './PickFramebuffer.js'; import SceneFramebuffer from './SceneFramebuffer.js'; import SceneMode from './SceneMode.js'; import ShadowMap from './ShadowMap.js'; /** * @private */ function View(scene, camera, viewport) { var context = scene.context; var frustumCommandsList = []; // Initial guess at frustums. var near = camera.frustum.near; var far = camera.frustum.far; var farToNearRatio = scene.logarithmicDepthBuffer ? scene.logarithmicDepthFarToNearRatio : scene.farToNearRatio; var numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio)); updateFrustums(near, far, farToNearRatio, numFrustums, scene.logarithmicDepthBuffer, frustumCommandsList, false, undefined); var globeDepth; if (context.depthTexture) { globeDepth = new GlobeDepth(); } var oit; if (scene._useOIT && context.depthTexture) { oit = new OIT(context); } var passState = new PassState(context); passState.viewport = BoundingRectangle.clone(viewport); this.camera = camera; this._cameraClone = Camera.clone(camera); this._cameraStartFired = false; this._cameraMovedTime = undefined; this.viewport = viewport; this.passState = passState; this.pickFramebuffer = new PickFramebuffer(context); this.pickDepthFramebuffer = new PickDepthFramebuffer(); this.sceneFramebuffer = new SceneFramebuffer(); this.globeDepth = globeDepth; this.oit = oit; this.pickDepths = []; this.debugGlobeDepths = []; this.frustumCommandsList = frustumCommandsList; this.debugFrustumStatistics = undefined; this.updateFrustums = false; } var scratchPosition0 = new Cartesian3(); var scratchPosition1 = new Cartesian3(); function maxComponent(a, b) { var x = Math.max(Math.abs(a.x), Math.abs(b.x)); var y = Math.max(Math.abs(a.y), Math.abs(b.y)); var z = Math.max(Math.abs(a.z), Math.abs(b.z)); return Math.max(Math.max(x, y), z); } function cameraEqual(camera0, camera1, epsilon) { var scalar = 1 / Math.max(1, maxComponent(camera0.position, camera1.position)); Cartesian3.multiplyByScalar(camera0.position, scalar, scratchPosition0); Cartesian3.multiplyByScalar(camera1.position, scalar, scratchPosition1); return Cartesian3.equalsEpsilon(scratchPosition0, scratchPosition1, epsilon) && Cartesian3.equalsEpsilon(camera0.direction, camera1.direction, epsilon) && Cartesian3.equalsEpsilon(camera0.up, camera1.up, epsilon) && Cartesian3.equalsEpsilon(camera0.right, camera1.right, epsilon) && Matrix4.equalsEpsilon(camera0.transform, camera1.transform, epsilon) && camera0.frustum.equalsEpsilon(camera1.frustum, epsilon); } View.prototype.checkForCameraUpdates = function(scene) { var camera = this.camera; var cameraClone = this._cameraClone; if (!cameraEqual(camera, cameraClone, CesiumMath.EPSILON15)) { if (!this._cameraStartFired) { camera.moveStart.raiseEvent(); this._cameraStartFired = true; } this._cameraMovedTime = getTimestamp(); Camera.clone(camera, cameraClone); return true; } if (this._cameraStartFired && getTimestamp() - this._cameraMovedTime > scene.cameraEventWaitTime) { camera.moveEnd.raiseEvent(); this._cameraStartFired = false; } return false; }; function updateFrustums(near, far, farToNearRatio, numFrustums, logDepth, frustumCommandsList, is2D, nearToFarDistance2D) { frustumCommandsList.length = numFrustums; for (var m = 0; m < numFrustums; ++m) { var curNear; var curFar; if (is2D) { curNear = Math.min(far - nearToFarDistance2D, near + m * nearToFarDistance2D); curFar = Math.min(far, curNear + nearToFarDistance2D); } else { curNear = Math.max(near, Math.pow(farToNearRatio, m) * near); curFar = farToNearRatio * curNear; if (!logDepth) { curFar = Math.min(far, curFar); } } var frustumCommands = frustumCommandsList[m]; if (!defined(frustumCommands)) { frustumCommands = frustumCommandsList[m] = new FrustumCommands(curNear, curFar); } else { frustumCommands.near = curNear; frustumCommands.far = curFar; } } } function insertIntoBin(scene, view, command, distance) { if (scene.debugShowFrustums) { command.debugOverlappingFrustums = 0; } var frustumCommandsList = view.frustumCommandsList; var length = frustumCommandsList.length; for (var i = 0; i < length; ++i) { var frustumCommands = frustumCommandsList[i]; var curNear = frustumCommands.near; var curFar = frustumCommands.far; if (distance.start > curFar) { continue; } if (distance.stop < curNear) { break; } var pass = command.pass; var index = frustumCommands.indices[pass]++; frustumCommands.commands[pass][index] = command; if (scene.debugShowFrustums) { command.debugOverlappingFrustums |= (1 << i); } if (command.executeInClosestFrustum) { break; } } if (scene.debugShowFrustums) { var cf = view.debugFrustumStatistics.commandsInFrustums; cf[command.debugOverlappingFrustums] = defined(cf[command.debugOverlappingFrustums]) ? cf[command.debugOverlappingFrustums] + 1 : 1; ++view.debugFrustumStatistics.totalCommands; } scene.updateDerivedCommands(command); } var scratchCullingVolume = new CullingVolume(); var distances = new Interval(); View.prototype.createPotentiallyVisibleSet = function(scene) { var frameState = scene.frameState; var camera = frameState.camera; var direction = camera.directionWC; var position = camera.positionWC; var computeList = scene._computeCommandList; var overlayList = scene._overlayCommandList; var commandList = frameState.commandList; if (scene.debugShowFrustums) { this.debugFrustumStatistics = { totalCommands : 0, commandsInFrustums : {} }; } var frustumCommandsList = this.frustumCommandsList; var numberOfFrustums = frustumCommandsList.length; var numberOfPasses = Pass.NUMBER_OF_PASSES; for (var n = 0; n < numberOfFrustums; ++n) { for (var p = 0; p < numberOfPasses; ++p) { frustumCommandsList[n].indices[p] = 0; } } computeList.length = 0; overlayList.length = 0; var near = Number.MAX_VALUE; var far = -Number.MAX_VALUE; var undefBV = false; var shadowsEnabled = frameState.shadowState.shadowsEnabled; var shadowNear = Number.MAX_VALUE; var shadowFar = -Number.MAX_VALUE; var shadowClosestObjectSize = Number.MAX_VALUE; var occluder = (frameState.mode === SceneMode.SCENE3D) ? frameState.occluder: undefined; var cullingVolume = frameState.cullingVolume; // get user culling volume minus the far plane. var planes = scratchCullingVolume.planes; for (var k = 0; k < 5; ++k) { planes[k] = cullingVolume.planes[k]; } cullingVolume = scratchCullingVolume; var length = commandList.length; for (var i = 0; i < length; ++i) { var command = commandList[i]; var pass = command.pass; if (pass === Pass.COMPUTE) { computeList.push(command); } else if (pass === Pass.OVERLAY) { overlayList.push(command); } else { var boundingVolume = command.boundingVolume; if (defined(boundingVolume)) { if (!scene.isVisible(command, cullingVolume, occluder)) { continue; } distances = boundingVolume.computePlaneDistances(position, direction, distances); near = Math.min(near, distances.start); far = Math.max(far, distances.stop); // Compute a tight near and far plane for commands that receive shadows. This helps compute // good splits for cascaded shadow maps. Ignore commands that exceed the maximum distance. // When moving the camera low LOD globe tiles begin to load, whose bounding volumes // throw off the near/far fitting for the shadow map. Only update for globe tiles that the // camera isn't inside. if (shadowsEnabled && command.receiveShadows && (distances.start < ShadowMap.MAXIMUM_DISTANCE) && !((pass === Pass.GLOBE) && (distances.start < -100.0) && (distances.stop > 100.0))) { // Get the smallest bounding volume the camera is near. This is used to place more shadow detail near the object. var size = distances.stop - distances.start; if ((pass !== Pass.GLOBE) && (distances.start < 100.0)) { shadowClosestObjectSize = Math.min(shadowClosestObjectSize, size); } shadowNear = Math.min(shadowNear, distances.start); shadowFar = Math.max(shadowFar, distances.stop); } } else { // Clear commands don't need a bounding volume - just add the clear to all frustums. // If another command has no bounding volume, though, we need to use the camera's // worst-case near and far planes to avoid clipping something important. distances.start = camera.frustum.near; distances.stop = camera.frustum.far; undefBV = !(command instanceof ClearCommand); } insertIntoBin(scene, this, command, distances); } } if (undefBV) { near = camera.frustum.near; far = camera.frustum.far; } else { // The computed near plane must be between the user defined near and far planes. // The computed far plane must between the user defined far and computed near. // This will handle the case where the computed near plane is further than the user defined far plane. near = Math.min(Math.max(near, camera.frustum.near), camera.frustum.far); far = Math.max(Math.min(far, camera.frustum.far), near); if (shadowsEnabled) { shadowNear = Math.min(Math.max(shadowNear, camera.frustum.near), camera.frustum.far); shadowFar = Math.max(Math.min(shadowFar, camera.frustum.far), shadowNear); } } // Use the computed near and far for shadows if (shadowsEnabled) { frameState.shadowState.nearPlane = shadowNear; frameState.shadowState.farPlane = shadowFar; frameState.shadowState.closestObjectSize = shadowClosestObjectSize; } // Exploit temporal coherence. If the frustums haven't changed much, use the frustums computed // last frame, else compute the new frustums and sort them by frustum again. var is2D = scene.mode === SceneMode.SCENE2D; var logDepth = frameState.useLogDepth; var farToNearRatio = logDepth ? scene.logarithmicDepthFarToNearRatio : scene.farToNearRatio; var numFrustums; if (is2D) { // The multifrustum for 2D is uniformly distributed. To avoid z-fighting in 2D, // the camera is moved to just before the frustum and the frustum depth is scaled // to be in [1.0, nearToFarDistance2D]. far = Math.min(far, camera.position.z + scene.nearToFarDistance2D); near = Math.min(near, far); numFrustums = Math.ceil(Math.max(1.0, far - near) / scene.nearToFarDistance2D); } else { // The multifrustum for 3D/CV is non-uniformly distributed. numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio)); } if (this.updateFrustums || (near !== Number.MAX_VALUE && (numFrustums !== numberOfFrustums || (frustumCommandsList.length !== 0 && (near < frustumCommandsList[0].near || (far > frustumCommandsList[numberOfFrustums - 1].far && (logDepth || !CesiumMath.equalsEpsilon(far, frustumCommandsList[numberOfFrustums - 1].far, CesiumMath.EPSILON8)))))))) { this.updateFrustums = false; updateFrustums(near, far, farToNearRatio, numFrustums, logDepth, frustumCommandsList, is2D, scene.nearToFarDistance2D); this.createPotentiallyVisibleSet(scene); } var frustumSplits = frameState.frustumSplits; frustumSplits.length = numFrustums + 1; for (var j = 0; j < numFrustums; ++j) { frustumSplits[j] = frustumCommandsList[j].near; if (j === numFrustums - 1) { frustumSplits[j + 1] = frustumCommandsList[j].far; } } }; View.prototype.destroy = function() { this.pickFramebuffer = this.pickFramebuffer && this.pickFramebuffer.destroy(); this.pickDepthFramebuffer = this.pickDepthFramebuffer && this.pickDepthFramebuffer.destroy(); this.sceneFramebuffer = this.sceneFramebuffer && this.sceneFramebuffer.destroy(); this.globeDepth = this.globeDepth && this.globeDepth.destroy(); this.oit = this.oit && this.oit.destroy(); var i; var length; var pickDepths = this.pickDepths; var debugGlobeDepths = this.debugGlobeDepths; length = pickDepths.length; for (i = 0; i < length; ++i) { pickDepths[i].destroy(); } length = debugGlobeDepths.length; for (i = 0; i < length; ++i) { debugGlobeDepths[i].destroy(); } }; export default View;