| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- 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 EdgeFramebuffer from "./EdgeFramebuffer.js";
- import FrustumCommands from "./FrustumCommands.js";
- import GlobeDepth from "./GlobeDepth.js";
- import GlobeTranslucencyFramebuffer from "./GlobeTranslucencyFramebuffer.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";
- import TranslucentTileClassification from "./TranslucentTileClassification.js";
-
- function CommandExtent() {
- this.command = undefined;
- this.near = undefined;
- this.far = undefined;
- }
-
- /**
- * @alias View
- * @constructor
- *
- * @param {Scene} scene
- * @param {Camera} camera
- * @param {BoundingRectangle} viewport
- *
- * @private
- */
- function View(scene, camera, viewport) {
- const context = scene.context;
-
- let globeDepth;
- if (context.depthTexture) {
- globeDepth = new GlobeDepth();
- }
-
- let oit;
- if (scene._useOIT && context.depthTexture) {
- oit = new OIT(context);
- }
-
- const 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.edgeFramebuffer = new EdgeFramebuffer();
- this.globeDepth = globeDepth;
- this.globeTranslucencyFramebuffer = new GlobeTranslucencyFramebuffer();
- this.oit = oit;
- this.translucentTileClassification = new TranslucentTileClassification(
- context,
- );
- /**
- * @type {PickDepth[]}
- */
- this.pickDepths = [];
- this.frustumCommandsList = [];
- this.debugFrustumStatistics = undefined;
-
- // Array of all commands that get rendered into frustums along with their near / far values.
- // Acts similar to a ManagedArray.
- this._commandExtents = [];
- }
-
- const scratchPosition0 = new Cartesian3();
- const scratchPosition1 = new Cartesian3();
- /**
- * Check if two cameras have the same view.
- *
- * @param {Camera} camera0 The first camera for comparison.
- * @param {Camera} camera1 The second camera for comparison.
- * @param {number} epsilon The epsilon tolerance to use for equality testing.
- * @returns {boolean} <code>true</code> if the cameras are equal.
- *
- * @private
- */
- function cameraEqual(camera0, camera1, epsilon) {
- const maximumPositionComponent = Math.max(
- Cartesian3.maximumComponent(
- Cartesian3.abs(camera0.position, scratchPosition0),
- ),
- Cartesian3.maximumComponent(
- Cartesian3.abs(camera1.position, scratchPosition1),
- ),
- );
- const scalar = 1 / Math.max(1, maximumPositionComponent);
- 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)
- );
- }
-
- /**
- * Check if the camera position or direction has changed.
- *
- * @param {Scene} scene
- * @returns {boolean} <code>true</code> if the camera has been updated
- *
- * @private
- */
- View.prototype.checkForCameraUpdates = function (scene) {
- const camera = this.camera;
- const 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;
- };
-
- /**
- * Split the depth range of the scene into multiple frustums, and initialize
- * a list of {@link FrustumCommands} with the distances to the near and far
- * planes for each frustum.
- *
- * @param {View} view The view to which the frustum commands list is attached.
- * @param {Scene} scene The scene to be rendered.
- * @param {number} near The distance to the nearest object in the scene.
- * @param {number} far The distance to the farthest object in the scene.
- *
- * @private
- */
- function updateFrustums(view, scene, near, far) {
- const { frameState } = scene;
- const { camera, useLogDepth } = frameState;
- const farToNearRatio = useLogDepth
- ? scene.logarithmicDepthFarToNearRatio
- : scene.farToNearRatio;
- const is2D = scene.mode === SceneMode.SCENE2D;
- const nearToFarDistance2D = scene.nearToFarDistance2D;
-
- // Extend the far plane slightly further to prevent geometry clipping against the far plane.
- far *= 1.0 + CesiumMath.EPSILON2;
-
- // 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);
-
- let 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));
- }
-
- const { frustumCommandsList } = view;
- frustumCommandsList.length = numFrustums;
- for (let m = 0; m < numFrustums; ++m) {
- let curNear;
- let 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 = Math.min(far, farToNearRatio * curNear);
- }
- let frustumCommands = frustumCommandsList[m];
- if (!defined(frustumCommands)) {
- frustumCommands = frustumCommandsList[m] = new FrustumCommands(
- curNear,
- curFar,
- );
- } else {
- frustumCommands.near = curNear;
- frustumCommands.far = curFar;
- }
- }
- }
-
- /**
- * Insert a command into the appropriate {@link FrustumCommands} based on the
- * range of depths covered by its bounding volume.
- *
- * @param {View} view
- * @param {Scene} scene
- * @param {CommandExtent} commandExtent
- *
- * @private
- */
- function insertIntoBin(view, scene, commandExtent) {
- const { command, near, far } = commandExtent;
-
- if (scene.debugShowFrustums) {
- command.debugOverlappingFrustums = 0;
- }
-
- const { frustumCommandsList } = view;
-
- for (let i = 0; i < frustumCommandsList.length; ++i) {
- const frustumCommands = frustumCommandsList[i];
-
- if (near > frustumCommands.far) {
- continue;
- }
-
- if (far < frustumCommands.near) {
- break;
- }
-
- const pass = command.pass;
- const index = frustumCommands.indices[pass]++;
- frustumCommands.commands[pass][index] = command;
-
- if (scene.debugShowFrustums) {
- command.debugOverlappingFrustums |= 1 << i;
- }
-
- if (command.executeInClosestFrustum) {
- break;
- }
- }
-
- if (scene.debugShowFrustums) {
- const { debugFrustumStatistics } = view;
- const { debugOverlappingFrustums } = command;
- const cf = debugFrustumStatistics.commandsInFrustums;
- cf[debugOverlappingFrustums] = defined(cf[debugOverlappingFrustums])
- ? cf[debugOverlappingFrustums] + 1
- : 1;
- ++debugFrustumStatistics.totalCommands;
- }
-
- scene.updateDerivedCommands(command);
- }
-
- const scratchCullingVolume = new CullingVolume();
- const scratchNearFarInterval = new Interval();
-
- View.prototype.createPotentiallyVisibleSet = function (scene) {
- const { frameState } = scene;
- const { camera, commandList, shadowState } = frameState;
- const { positionWC, directionWC, frustum } = camera;
-
- const computeList = scene._computeCommandList;
- const overlayList = scene._overlayCommandList;
-
- if (scene.debugShowFrustums) {
- this.debugFrustumStatistics = {
- totalCommands: 0,
- commandsInFrustums: {},
- };
- }
-
- const frustumCommandsList = this.frustumCommandsList;
- for (let n = 0; n < frustumCommandsList.length; ++n) {
- for (let p = 0; p < Pass.NUMBER_OF_PASSES; ++p) {
- frustumCommandsList[n].indices[p] = 0;
- }
- }
-
- computeList.length = 0;
- overlayList.length = 0;
-
- const commandExtents = this._commandExtents;
- const commandExtentCapacity = commandExtents.length;
- let commandExtentCount = 0;
-
- let near = +Number.MAX_VALUE;
- let far = -Number.MAX_VALUE;
-
- const { shadowsEnabled } = shadowState;
- let shadowNear = +Number.MAX_VALUE;
- let shadowFar = -Number.MAX_VALUE;
- let shadowClosestObjectSize = Number.MAX_VALUE;
-
- const occluder =
- frameState.mode === SceneMode.SCENE3D ? frameState.occluder : undefined;
-
- // get user culling volume minus the far plane.
- let { cullingVolume } = frameState;
- const planes = scratchCullingVolume.planes;
- for (let k = 0; k < 5; ++k) {
- planes[k] = cullingVolume.planes[k];
- }
- cullingVolume = scratchCullingVolume;
-
- for (let i = 0; i < commandList.length; ++i) {
- const command = commandList[i];
- const { pass, boundingVolume } = command;
-
- if (pass === Pass.COMPUTE) {
- computeList.push(command);
- } else if (pass === Pass.OVERLAY) {
- overlayList.push(command);
- } else {
- let commandNear;
- let commandFar;
-
- if (defined(boundingVolume)) {
- if (!scene.isVisible(cullingVolume, command, occluder)) {
- continue;
- }
-
- const nearFarInterval = boundingVolume.computePlaneDistances(
- positionWC,
- directionWC,
- scratchNearFarInterval,
- );
- commandNear = nearFarInterval.start;
- commandFar = nearFarInterval.stop;
- near = Math.min(near, commandNear);
- far = Math.max(far, commandFar);
-
- // 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 &&
- commandNear < ShadowMap.MAXIMUM_DISTANCE &&
- !(pass === Pass.GLOBE && commandNear < -100.0 && commandFar > 100.0)
- ) {
- // Get the smallest bounding volume the camera is near. This is used to place more shadow detail near the object.
- const size = commandFar - commandNear;
- if (pass !== Pass.GLOBE && commandNear < 100.0) {
- shadowClosestObjectSize = Math.min(shadowClosestObjectSize, size);
- }
- shadowNear = Math.min(shadowNear, commandNear);
- shadowFar = Math.max(shadowFar, commandFar);
- }
- } else if (command instanceof ClearCommand) {
- // Clear commands don't need a bounding volume - just add the clear to all frustums.
- commandNear = frustum.near;
- commandFar = frustum.far;
- } else {
- // If command has no bounding volume we need to use the camera's
- // worst-case near and far planes to avoid clipping something important.
- commandNear = frustum.near;
- commandFar = frustum.far;
- near = Math.min(near, commandNear);
- far = Math.max(far, commandFar);
- }
-
- let extent = commandExtents[commandExtentCount];
- if (!defined(extent)) {
- extent = commandExtents[commandExtentCount] = new CommandExtent();
- }
- extent.command = command;
- extent.near = commandNear;
- extent.far = commandFar;
- commandExtentCount++;
- }
- }
-
- if (shadowsEnabled) {
- shadowNear = Math.min(Math.max(shadowNear, frustum.near), frustum.far);
- shadowFar = Math.max(Math.min(shadowFar, frustum.far), shadowNear);
- // Use the computed near and far for shadows
- shadowState.nearPlane = shadowNear;
- shadowState.farPlane = shadowFar;
- shadowState.closestObjectSize = shadowClosestObjectSize;
- }
-
- updateFrustums(this, scene, near, far);
-
- for (let c = 0; c < commandExtentCount; c++) {
- insertIntoBin(this, scene, commandExtents[c]);
- }
-
- // Dereference old commands
- if (commandExtentCount < commandExtentCapacity) {
- for (let c = commandExtentCount; c < commandExtentCapacity; c++) {
- const commandExtent = commandExtents[c];
- if (!defined(commandExtent.command)) {
- // If the command is undefined, it's assumed that all
- // subsequent commmands were set to undefined as well,
- // so no need to loop over them all
- break;
- }
- commandExtent.command = undefined;
- }
- }
-
- const numFrustums = frustumCommandsList.length;
- const { frustumSplits } = frameState;
- frustumSplits.length = numFrustums + 1;
- for (let 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.edgeFramebuffer = this.edgeFramebuffer && this.edgeFramebuffer.destroy();
- this.globeDepth = this.globeDepth && this.globeDepth.destroy();
- this.oit = this.oit && this.oit.destroy();
- this.translucentTileClassification =
- this.translucentTileClassification &&
- this.translucentTileClassification.destroy();
- this.globeTranslucencyFramebuffer =
- this.globeTranslucencyFramebuffer &&
- this.globeTranslucencyFramebuffer.destroy();
-
- const pickDepths = this.pickDepths;
- for (let i = 0; i < pickDepths.length; ++i) {
- pickDepths[i].destroy();
- }
- };
- export default View;
|