diff --git a/Source/Scene/FrameState.js b/Source/Scene/FrameState.js index 985172d6c80d..8f5d99495f65 100644 --- a/Source/Scene/FrameState.js +++ b/Source/Scene/FrameState.js @@ -159,7 +159,14 @@ define([ * @type {Boolean} * @default false */ - postProcess : false + postProcess : false, + + /** + * true if the primitive should update for an offscreen pass, false otherwise. + * @type {Boolean} + * @default false + */ + offscreen : false }; /** diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 8e75ee0c26b8..a6110d119f41 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -1,4 +1,5 @@ define([ + '../Core/ApproximateTerrainHeights', '../Core/BoundingRectangle', '../Core/BoundingSphere', '../Core/BoxGeometry', @@ -6,6 +7,7 @@ define([ '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Cartographic', + '../Core/Check', '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/createGuid', @@ -34,6 +36,7 @@ define([ '../Core/PerspectiveFrustum', '../Core/PerspectiveOffCenterFrustum', '../Core/PixelFormat', + '../Core/Ray', '../Core/RequestScheduler', '../Core/ShowGeometryInstanceAttribute', '../Core/TaskProcessor', @@ -83,6 +86,7 @@ define([ './SunPostProcess', './TweenCollection' ], function( + ApproximateTerrainHeights, BoundingRectangle, BoundingSphere, BoxGeometry, @@ -90,6 +94,7 @@ define([ Cartesian3, Cartesian4, Cartographic, + Check, Color, ColorGeometryInstanceAttribute, createGuid, @@ -118,6 +123,7 @@ define([ PerspectiveFrustum, PerspectiveOffCenterFrustum, PixelFormat, + Ray, RequestScheduler, ShowGeometryInstanceAttribute, TaskProcessor, @@ -852,8 +858,18 @@ define([ this._removeGlobeCallbacks = []; var viewport = new BoundingRectangle(0, 0, context.drawingBufferWidth, context.drawingBufferHeight); + var pickOffscreenViewport = new BoundingRectangle(0, 0, 1, 1); + + var pickOffscreenCamera = new Camera(this); + pickOffscreenCamera.frustum = new OrthographicFrustum({ + width: 0.01, + aspectRatio: 1.0, + near: 0.1, + far: 500000000.0 + }); this._view = new View(this, this._camera, viewport); + this._pickOffscreenView = new View(this, pickOffscreenCamera, pickOffscreenViewport); // initial guess at frustums. var near = camera.frustum.near; @@ -1658,6 +1674,7 @@ define([ passes.pick = false; passes.depth = false; passes.postProcess = false; + passes.offscreen = false; } function updateFrameState(scene, frameNumber, time) { @@ -2968,6 +2985,7 @@ define([ // Update celestial and terrestrial environment effects. var environmentState = scene._environmentState; var renderPass = frameState.passes.render; + var offscreenPass = frameState.passes.offscreen; var skyAtmosphere = scene.skyAtmosphere; var globe = scene.globe; @@ -3009,7 +3027,7 @@ define([ } environmentState.renderTranslucentDepthForPick = false; - environmentState.useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D; + environmentState.useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D && !offscreenPass; environmentState.frustumCommandsList = view.frustumCommandsList; environmentState.view = view; @@ -3822,6 +3840,8 @@ define([ * @returns {Cartesian3} The cartesian position. * * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported. + * + * @see Scene#pickPositionFromRay */ Scene.prototype.pickPosition = function(windowPosition, result) { result = this.pickPositionWorldCoordinates(windowPosition, result); @@ -3838,37 +3858,10 @@ define([ return result; }; - /** - * Returns a list of objects, each containing a `primitive` property, for all primitives at - * a particular window coordinate position. Other properties may also be set depending on the - * type of primitive. The primitives in the list are ordered by their visual order in the - * scene (front to back). - * - * @param {Cartesian2} windowPosition Window coordinates to perform picking on. - * @param {Number} [limit] If supplied, stop drilling after collecting this many picks. - * @param {Number} [width=3] Width of the pick rectangle. - * @param {Number} [height=3] Height of the pick rectangle. - * @returns {Object[]} Array of objects, each containing 1 picked primitives. - * - * @exception {DeveloperError} windowPosition is undefined. - * - * @example - * var pickedObjects = scene.drillPick(new Cesium.Cartesian2(100.0, 200.0)); - * - * @see Scene#pick - * - */ - Scene.prototype.drillPick = function(windowPosition, limit, width, height) { + function drillPick(limit, pickCallback) { // PERFORMANCE_IDEA: This function calls each primitive's update for each pass. Instead // we could update the primitive once, and then just execute their commands for each pass, // and cull commands for picked primitives. e.g., base on the command's owner. - - //>>includeStart('debug', pragmas.debug); - if (!defined(windowPosition)) { - throw new DeveloperError('windowPosition is undefined.'); - } - //>>includeEnd('debug'); - var i; var attributes; var result = []; @@ -3879,20 +3872,21 @@ define([ limit = Number.MAX_VALUE; } - var pickedResult = this.pick(windowPosition, width, height); - while (defined(pickedResult) && defined(pickedResult.primitive)) { + var pickedResult = pickCallback(); + while (defined(pickedResult) && defined(pickedResult.object) && defined(pickedResult.object.primitive)) { result.push(pickedResult); if (0 >= --limit) { break; } - var primitive = pickedResult.primitive; + var object = pickedResult.object; + var primitive = object.primitive; var hasShowAttribute = false; - //If the picked object has a show attribute, use it. + // If the picked object has a show attribute, use it. if (typeof primitive.getGeometryInstanceAttributes === 'function') { - if (defined(pickedResult.id)) { - attributes = primitive.getGeometryInstanceAttributes(pickedResult.id); + if (defined(object.id)) { + attributes = primitive.getGeometryInstanceAttributes(object.id); if (defined(attributes) && defined(attributes.show)) { hasShowAttribute = true; attributes.show = ShowGeometryInstanceAttribute.toValue(false, attributes.show); @@ -3901,10 +3895,10 @@ define([ } } - if (pickedResult instanceof Cesium3DTileFeature) { + if (object instanceof Cesium3DTileFeature) { hasShowAttribute = true; - pickedResult.show = false; - pickedFeatures.push(pickedResult); + object.show = false; + pickedFeatures.push(object); } // Otherwise, hide the entire primitive @@ -3913,7 +3907,7 @@ define([ pickedPrimitives.push(primitive); } - pickedResult = this.pick(windowPosition, width, height); + pickedResult = pickCallback(); } // Unhide everything we hid while drill picking @@ -3931,6 +3925,287 @@ define([ } return result; + } + + var scratchRight = new Cartesian3(); + var scratchUp = new Cartesian3(); + + function pickFromRay(scene, ray, pickPosition, pickObject) { + var context = scene._context; + var uniformState = context.uniformState; + var frameState = scene._frameState; + var environmentState = scene._environmentState; + var view = scene._pickOffscreenView; + + var direction = ray.direction; + var orthogonalAxis = Cartesian3.mostOrthogonalAxis(direction, scratchRight); + var right = Cartesian3.cross(direction, orthogonalAxis, scratchRight); + var up = Cartesian3.cross(direction, right, scratchUp); + + var pickOffscreenCamera = view.camera; + pickOffscreenCamera.position = ray.origin; + pickOffscreenCamera.direction = direction; + pickOffscreenCamera.up = up; + pickOffscreenCamera.right = right; + + scratchRectangle = BoundingRectangle.clone(view.viewport, scratchRectangle); + + // Switch out the scene's camera with the offscreen camera + var sceneCamera = scene._camera; + scene._camera = pickOffscreenCamera; + + scene._jobScheduler.disableThisFrame(); + + // Update with previous frame's number and time, assuming that render is called before picking. + updateFrameState(scene, frameState.frameNumber, frameState.time); + frameState.invertClassification = false; + frameState.passes.pick = true; + frameState.passes.offscreen = true; + + uniformState.update(frameState); + + updateEnvironment(scene, view); + var passState = environmentState.pickFramebuffer.begin(scratchRectangle, view.viewport); + + updateAndExecuteCommands(scene, passState, scratchColorZero); + resolveFramebuffers(scene, passState); + + var position; + var object; + + if (pickObject) { + object = environmentState.pickFramebuffer.end(context); + pickPosition = pickPosition && defined(object); + } + + if (pickPosition) { + var numFrustums = environmentState.frustumCommandsList.length; + for (var i = 0; i < numFrustums; ++i) { + var pickDepth = getPickDepth(scene, i); + var depth = pickDepth.getDepth(context, 0, 0); + if (depth > 0.0 && depth < 1.0) { + var renderedFrustum = environmentState.frustumCommandsList[i]; + var near = renderedFrustum.near * (i !== 0 ? scene.opaqueFrustumNearOffset : 1.0); + var far = renderedFrustum.far; + var distance = near + depth * (far - near); + position = Ray.getPoint(ray, distance); + break; + } + } + + if (defined(position) && (scene.mode !== SceneMode.SCENE3D)) { + Cartesian3.fromElements(position.y, position.z, position.x, position); + + var projection = scene.mapProjection; + var ellipsoid = projection.ellipsoid; + + var cartographic = projection.unproject(position, scratchPickPositionCartographic); + ellipsoid.cartographicToCartesian(cartographic, position); + } + } + + scene._camera = sceneCamera; + context.endFrame(); + callAfterRenderFunctions(scene); + + if (!defined(object) && !defined(position)) { + return; + } + + return { + object : object, + position : position + }; + } + + /** + * Returns an object with a `primitive` property that contains the first (top) primitive in the scene + * hit by the ray or undefined if nothing is hit. Other properties may potentially be set depending on the type of primitive. + *

+ * When a feature of a 3D Tiles tileset is picked, pick returns a {@link Cesium3DTileFeature} object. + *

+ * + * @private + * + * @param {Ray} ray The ray to pick from. + * @returns {Object} Object containing the picked primitive. + * + * @see Scene#pick + */ + Scene.prototype.pickFromRay = function(ray) { + //>>includeStart('debug', pragmas.debug); + Check.defined('ray', ray); + //>>includeEnd('debug'); + var pickResult = pickFromRay(this, ray, false, true); + if (defined(pickResult)) { + return pickResult.object; + } + }; + + /** + * Returns the cartesian position of the first intersection of the ray or undefined if nothing is hit. + * + * @private + * + * @param {Ray} ray The ray to pick from. + * @param {Cartesian3} [result] The object on which to restore the result. + * @returns {Cartesian3} The cartesian position. + * + * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported. + * + * @see Scene#pickPosition + */ + Scene.prototype.pickPositionFromRay = function(ray, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('ray', ray); + if (!this.pickPositionSupported) { + throw new DeveloperError('Picking from the depth buffer is not supported. Check pickPositionSupported.'); + } + //>>includeEnd('debug'); + var pickResult = pickFromRay(this, ray, true, false); + if (defined(pickResult)) { + return Cartesian3.clone(pickResult.position, result); + } + }; + + /** + * Returns a list of objects, each containing a `primitive` property, for all primitives hit + * by the ray. Other properties may also be set depending on the type of primitive. + * The primitives in the list are ordered by their visual order in the scene (front to back). + * + * @private + * + * @param {Ray} ray The ray to pick from. + * @param {Number} [limit] If supplied, stop drilling after collecting this many picks. + * @returns {Object[]} Array of objects, each containing 1 picked primitives. + * + * @see Scene#drillPick + */ + Scene.prototype.drillPickFromRay = function(ray, limit) { + //>>includeStart('debug', pragmas.debug); + Check.defined('ray', ray); + //>>includeEnd('debug'); + var that = this; + var pickCallback = function() { + return pickFromRay(that, ray, false, true); + }; + var objects = drillPick(limit, pickCallback); + return objects.map(function(element) { + return element.object; + }); + }; + + /** + * Returns a list of cartesian positions containing intersections of the ray in the scene. + * + * @private + * + * @param {Ray} ray The ray to pick from. + * @param {Number} [limit] If supplied, stop drilling after collecting this many picks. + * @returns {Cartesian3[]} Array of cartesian positions. + * + * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported. + * + * @see Scene#drillPick + * @see Scene#pickPosition + */ + Scene.prototype.drillPickPositionFromRay = function(ray, limit) { + //>>includeStart('debug', pragmas.debug); + Check.defined('ray', ray); + //>>includeEnd('debug'); + if (!this.pickPositionSupported) { + throw new DeveloperError('Picking from the depth buffer is not supported. Check pickPositionSupported.'); + } + var that = this; + var pickCallback = function() { + return pickFromRay(that, ray, true, true); + }; + var objects = drillPick(limit, pickCallback); + return objects.map(function(element) { + return element.position; + }); + }; + + /** + * Returns a list of objects, each containing a `primitive` property, for all primitives at + * a particular window coordinate position. Other properties may also be set depending on the + * type of primitive. The primitives in the list are ordered by their visual order in the + * scene (front to back). + * + * @param {Cartesian2} windowPosition Window coordinates to perform picking on. + * @param {Number} [limit] If supplied, stop drilling after collecting this many picks. + * @param {Number} [width=3] Width of the pick rectangle. + * @param {Number} [height=3] Height of the pick rectangle. + * @returns {Object[]} Array of objects, each containing 1 picked primitives. + * + * @exception {DeveloperError} windowPosition is undefined. + * + * @example + * var pickedObjects = scene.drillPick(new Cesium.Cartesian2(100.0, 200.0)); + * + * @see Scene#pick + * + */ + Scene.prototype.drillPick = function(windowPosition, limit, width, height) { + //>>includeStart('debug', pragmas.debug); + Check.defined('windowPosition', windowPosition); + //>>includeEnd('debug'); + var that = this; + var pickCallback = function() { + var object = that.pick(windowPosition, width, height); + if (defined(object)) { + return { + object : object + }; + } + }; + var objects = drillPick(limit, pickCallback); + return objects.map(function(element) { + return element.object; + }); + }; + + var scratchSurfacePosition = new Cartesian3(); + var scratchSurfaceNormal = new Cartesian3(); + var scratchSurfaceRay = new Ray(); + var scratchRay = new Ray(); + var scratchPickPosition = new Cartesian3(); + var scratchPickCartographic = new Cartographic(); + + /** + * Returns the height of scene geometry at the given cartographic position. May be used to clamp objects + * to the globe, 3D Tiles, or primitives in the scene. + *

+ * This function only samples height from globe tiles and 3D Tiles that are rendered in the current view. All other + * primitives are sampled regardless of visibility. + *

+ * + * @param {Cartographic} position The position to sample from. + * @returns {Number} The height. + * + * @see sampleTerrain + * @see sampleTerrainMostDetailed + */ + Scene.prototype.sampleHeight = function(position) { + //>>includeStart('debug', pragmas.debug); + Check.defined('position', position); + //>>includeEnd('debug'); + var globe = this.globe; + var ellipsoid = defined(globe) ? globe.ellipsoid : this.mapProjection.ellipsoid; + var height = ApproximateTerrainHeights._defaultMaxTerrainHeight; + var surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(position, scratchSurfaceNormal); + var surfacePosition = Cartographic.toCartesian(position, ellipsoid, scratchSurfacePosition); + var surfaceRay = scratchSurfaceRay; + surfaceRay.origin = surfacePosition; + surfaceRay.direction = surfaceNormal; + var ray = scratchRay; + Ray.getPoint(surfaceRay, height, ray.origin); + Cartesian3.negate(surfaceNormal, ray.direction); + var pickPosition = this.pickPositionFromRay(ray, scratchPickPosition); + if (defined(pickPosition)) { + var pickCartographic = Cartographic.fromCartesian(pickPosition, ellipsoid, scratchPickCartographic); + return pickCartographic.height; + } }; /** @@ -4059,6 +4334,7 @@ define([ this._brdfLutGenerator = this._brdfLutGenerator && this._brdfLutGenerator.destroy(); this._view = this._view && this._view.destroy(); + this._pickOffscreenView = this._pickOffscreenView && this._pickOffscreenView.destroy(); if (this._removeCreditContainer) { this._canvas.parentNode.removeChild(this._creditContainer);