You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1308 lines
33 KiB
JavaScript
1308 lines
33 KiB
JavaScript
import ApproximateTerrainHeights from "../Core/ApproximateTerrainHeights.js";
|
|
import BoundingRectangle from "../Core/BoundingRectangle.js";
|
|
import Cartesian2 from "../Core/Cartesian2.js";
|
|
import Cartesian3 from "../Core/Cartesian3.js";
|
|
import Cartographic from "../Core/Cartographic.js";
|
|
import Check from "../Core/Check.js";
|
|
import Color from "../Core/Color.js";
|
|
import defaultValue from "../Core/defaultValue.js";
|
|
import defined from "../Core/defined.js";
|
|
import DeveloperError from "../Core/DeveloperError.js";
|
|
import Matrix4 from "../Core/Matrix4.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 Ray from "../Core/Ray.js";
|
|
import ShowGeometryInstanceAttribute from "../Core/ShowGeometryInstanceAttribute.js";
|
|
import when from "../ThirdParty/when.js";
|
|
import Camera from "./Camera.js";
|
|
import Cesium3DTileFeature from "./Cesium3DTileFeature.js";
|
|
import Cesium3DTilePass from "./Cesium3DTilePass.js";
|
|
import Cesium3DTilePassState from "./Cesium3DTilePassState.js";
|
|
import PickDepth from "./PickDepth.js";
|
|
import PrimitiveCollection from "./PrimitiveCollection.js";
|
|
import SceneMode from "./SceneMode.js";
|
|
import SceneTransforms from "./SceneTransforms.js";
|
|
import View from "./View.js";
|
|
|
|
var offscreenDefaultWidth = 0.1;
|
|
|
|
var mostDetailedPreloadTilesetPassState = new Cesium3DTilePassState({
|
|
pass: Cesium3DTilePass.MOST_DETAILED_PRELOAD,
|
|
});
|
|
|
|
var mostDetailedPickTilesetPassState = new Cesium3DTilePassState({
|
|
pass: Cesium3DTilePass.MOST_DETAILED_PICK,
|
|
});
|
|
|
|
var pickTilesetPassState = new Cesium3DTilePassState({
|
|
pass: Cesium3DTilePass.PICK,
|
|
});
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
function Picking(scene) {
|
|
this._mostDetailedRayPicks = [];
|
|
this.pickRenderStateCache = {};
|
|
this._pickPositionCache = {};
|
|
this._pickPositionCacheDirty = false;
|
|
|
|
var pickOffscreenViewport = new BoundingRectangle(0, 0, 1, 1);
|
|
var pickOffscreenCamera = new Camera(scene);
|
|
pickOffscreenCamera.frustum = new OrthographicFrustum({
|
|
width: offscreenDefaultWidth,
|
|
aspectRatio: 1.0,
|
|
near: 0.1,
|
|
});
|
|
|
|
this._pickOffscreenView = new View(
|
|
scene,
|
|
pickOffscreenCamera,
|
|
pickOffscreenViewport
|
|
);
|
|
}
|
|
|
|
Picking.prototype.update = function () {
|
|
this._pickPositionCacheDirty = true;
|
|
};
|
|
|
|
Picking.prototype.getPickDepth = function (scene, index) {
|
|
var pickDepths = scene.view.pickDepths;
|
|
var pickDepth = pickDepths[index];
|
|
if (!defined(pickDepth)) {
|
|
pickDepth = new PickDepth();
|
|
pickDepths[index] = pickDepth;
|
|
}
|
|
return pickDepth;
|
|
};
|
|
|
|
var scratchOrthoPickingFrustum = new OrthographicOffCenterFrustum();
|
|
var scratchOrthoOrigin = new Cartesian3();
|
|
var scratchOrthoDirection = new Cartesian3();
|
|
var scratchOrthoPixelSize = new Cartesian2();
|
|
var scratchOrthoPickVolumeMatrix4 = new Matrix4();
|
|
|
|
function getPickOrthographicCullingVolume(
|
|
scene,
|
|
drawingBufferPosition,
|
|
width,
|
|
height,
|
|
viewport
|
|
) {
|
|
var camera = scene.camera;
|
|
var frustum = camera.frustum;
|
|
if (defined(frustum._offCenterFrustum)) {
|
|
frustum = frustum._offCenterFrustum;
|
|
}
|
|
|
|
var x = (2.0 * (drawingBufferPosition.x - viewport.x)) / viewport.width - 1.0;
|
|
x *= (frustum.right - frustum.left) * 0.5;
|
|
var y =
|
|
(2.0 * (viewport.height - drawingBufferPosition.y - viewport.y)) /
|
|
viewport.height -
|
|
1.0;
|
|
y *= (frustum.top - frustum.bottom) * 0.5;
|
|
|
|
var transform = Matrix4.clone(
|
|
camera.transform,
|
|
scratchOrthoPickVolumeMatrix4
|
|
);
|
|
camera._setTransform(Matrix4.IDENTITY);
|
|
|
|
var origin = Cartesian3.clone(camera.position, scratchOrthoOrigin);
|
|
Cartesian3.multiplyByScalar(camera.right, x, scratchOrthoDirection);
|
|
Cartesian3.add(scratchOrthoDirection, origin, origin);
|
|
Cartesian3.multiplyByScalar(camera.up, y, scratchOrthoDirection);
|
|
Cartesian3.add(scratchOrthoDirection, origin, origin);
|
|
|
|
camera._setTransform(transform);
|
|
|
|
if (scene.mode === SceneMode.SCENE2D) {
|
|
Cartesian3.fromElements(origin.z, origin.x, origin.y, origin);
|
|
}
|
|
|
|
var pixelSize = frustum.getPixelDimensions(
|
|
viewport.width,
|
|
viewport.height,
|
|
1.0,
|
|
1.0,
|
|
scratchOrthoPixelSize
|
|
);
|
|
|
|
var ortho = scratchOrthoPickingFrustum;
|
|
ortho.right = pixelSize.x * 0.5;
|
|
ortho.left = -ortho.right;
|
|
ortho.top = pixelSize.y * 0.5;
|
|
ortho.bottom = -ortho.top;
|
|
ortho.near = frustum.near;
|
|
ortho.far = frustum.far;
|
|
|
|
return ortho.computeCullingVolume(origin, camera.directionWC, camera.upWC);
|
|
}
|
|
|
|
var scratchPerspPickingFrustum = new PerspectiveOffCenterFrustum();
|
|
var scratchPerspPixelSize = new Cartesian2();
|
|
|
|
function getPickPerspectiveCullingVolume(
|
|
scene,
|
|
drawingBufferPosition,
|
|
width,
|
|
height,
|
|
viewport
|
|
) {
|
|
var camera = scene.camera;
|
|
var frustum = camera.frustum;
|
|
var near = frustum.near;
|
|
|
|
var tanPhi = Math.tan(frustum.fovy * 0.5);
|
|
var tanTheta = frustum.aspectRatio * tanPhi;
|
|
|
|
var x = (2.0 * (drawingBufferPosition.x - viewport.x)) / viewport.width - 1.0;
|
|
var y =
|
|
(2.0 * (viewport.height - drawingBufferPosition.y - viewport.y)) /
|
|
viewport.height -
|
|
1.0;
|
|
|
|
var xDir = x * near * tanTheta;
|
|
var yDir = y * near * tanPhi;
|
|
|
|
var pixelSize = frustum.getPixelDimensions(
|
|
viewport.width,
|
|
viewport.height,
|
|
1.0,
|
|
1.0,
|
|
scratchPerspPixelSize
|
|
);
|
|
var pickWidth = pixelSize.x * width * 0.5;
|
|
var pickHeight = pixelSize.y * height * 0.5;
|
|
|
|
var offCenter = scratchPerspPickingFrustum;
|
|
offCenter.top = yDir + pickHeight;
|
|
offCenter.bottom = yDir - pickHeight;
|
|
offCenter.right = xDir + pickWidth;
|
|
offCenter.left = xDir - pickWidth;
|
|
offCenter.near = near;
|
|
offCenter.far = frustum.far;
|
|
|
|
return offCenter.computeCullingVolume(
|
|
camera.positionWC,
|
|
camera.directionWC,
|
|
camera.upWC
|
|
);
|
|
}
|
|
|
|
function getPickCullingVolume(
|
|
scene,
|
|
drawingBufferPosition,
|
|
width,
|
|
height,
|
|
viewport
|
|
) {
|
|
var frustum = scene.camera.frustum;
|
|
if (
|
|
frustum instanceof OrthographicFrustum ||
|
|
frustum instanceof OrthographicOffCenterFrustum
|
|
) {
|
|
return getPickOrthographicCullingVolume(
|
|
scene,
|
|
drawingBufferPosition,
|
|
width,
|
|
height,
|
|
viewport
|
|
);
|
|
}
|
|
|
|
return getPickPerspectiveCullingVolume(
|
|
scene,
|
|
drawingBufferPosition,
|
|
width,
|
|
height,
|
|
viewport
|
|
);
|
|
}
|
|
|
|
// pick rectangle width and height, assumed odd
|
|
var scratchRectangleWidth = 3.0;
|
|
var scratchRectangleHeight = 3.0;
|
|
var scratchRectangle = new BoundingRectangle(
|
|
0.0,
|
|
0.0,
|
|
scratchRectangleWidth,
|
|
scratchRectangleHeight
|
|
);
|
|
var scratchPosition = new Cartesian2();
|
|
var scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0);
|
|
|
|
Picking.prototype.pick = function (scene, windowPosition, width, height) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(windowPosition)) {
|
|
throw new DeveloperError("windowPosition is undefined.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
scratchRectangleWidth = defaultValue(width, 3.0);
|
|
scratchRectangleHeight = defaultValue(height, scratchRectangleWidth);
|
|
|
|
var context = scene.context;
|
|
var us = context.uniformState;
|
|
var frameState = scene.frameState;
|
|
|
|
var view = scene.defaultView;
|
|
scene.view = view;
|
|
|
|
var viewport = view.viewport;
|
|
viewport.x = 0;
|
|
viewport.y = 0;
|
|
viewport.width = context.drawingBufferWidth;
|
|
viewport.height = context.drawingBufferHeight;
|
|
|
|
var passState = view.passState;
|
|
passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
|
|
|
|
var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(
|
|
scene,
|
|
windowPosition,
|
|
scratchPosition
|
|
);
|
|
|
|
scene.jobScheduler.disableThisFrame();
|
|
|
|
scene.updateFrameState();
|
|
frameState.cullingVolume = getPickCullingVolume(
|
|
scene,
|
|
drawingBufferPosition,
|
|
scratchRectangleWidth,
|
|
scratchRectangleHeight,
|
|
viewport
|
|
);
|
|
frameState.invertClassification = false;
|
|
frameState.passes.pick = true;
|
|
frameState.tilesetPassState = pickTilesetPassState;
|
|
|
|
us.update(frameState);
|
|
|
|
scene.updateEnvironment();
|
|
|
|
scratchRectangle.x =
|
|
drawingBufferPosition.x - (scratchRectangleWidth - 1.0) * 0.5;
|
|
scratchRectangle.y =
|
|
scene.drawingBufferHeight -
|
|
drawingBufferPosition.y -
|
|
(scratchRectangleHeight - 1.0) * 0.5;
|
|
scratchRectangle.width = scratchRectangleWidth;
|
|
scratchRectangle.height = scratchRectangleHeight;
|
|
passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
|
|
|
|
scene.updateAndExecuteCommands(passState, scratchColorZero);
|
|
scene.resolveFramebuffers(passState);
|
|
|
|
var object = view.pickFramebuffer.end(scratchRectangle);
|
|
context.endFrame();
|
|
return object;
|
|
};
|
|
|
|
function renderTranslucentDepthForPick(scene, drawingBufferPosition) {
|
|
// PERFORMANCE_IDEA: render translucent only and merge with the previous frame
|
|
var context = scene.context;
|
|
var frameState = scene.frameState;
|
|
var environmentState = scene.environmentState;
|
|
|
|
var view = scene.defaultView;
|
|
scene.view = view;
|
|
|
|
var viewport = view.viewport;
|
|
viewport.x = 0;
|
|
viewport.y = 0;
|
|
viewport.width = context.drawingBufferWidth;
|
|
viewport.height = context.drawingBufferHeight;
|
|
|
|
var passState = view.passState;
|
|
passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
|
|
|
|
scene.clearPasses(frameState.passes);
|
|
frameState.passes.pick = true;
|
|
frameState.passes.depth = true;
|
|
frameState.cullingVolume = getPickCullingVolume(
|
|
scene,
|
|
drawingBufferPosition,
|
|
1,
|
|
1,
|
|
viewport
|
|
);
|
|
frameState.tilesetPassState = pickTilesetPassState;
|
|
|
|
scene.updateEnvironment();
|
|
environmentState.renderTranslucentDepthForPick = true;
|
|
passState = view.pickDepthFramebuffer.update(
|
|
context,
|
|
drawingBufferPosition,
|
|
viewport
|
|
);
|
|
|
|
scene.updateAndExecuteCommands(passState, scratchColorZero);
|
|
scene.resolveFramebuffers(passState);
|
|
|
|
context.endFrame();
|
|
}
|
|
|
|
var scratchPerspectiveFrustum = new PerspectiveFrustum();
|
|
var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
|
|
var scratchOrthographicFrustum = new OrthographicFrustum();
|
|
var scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum();
|
|
|
|
Picking.prototype.pickPositionWorldCoordinates = function (
|
|
scene,
|
|
windowPosition,
|
|
result
|
|
) {
|
|
if (!scene.useDepthPicking) {
|
|
return undefined;
|
|
}
|
|
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(windowPosition)) {
|
|
throw new DeveloperError("windowPosition is undefined.");
|
|
}
|
|
if (!scene.context.depthTexture) {
|
|
throw new DeveloperError(
|
|
"Picking from the depth buffer is not supported. Check pickPositionSupported."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var cacheKey = windowPosition.toString();
|
|
|
|
if (this._pickPositionCacheDirty) {
|
|
this._pickPositionCache = {};
|
|
this._pickPositionCacheDirty = false;
|
|
} else if (this._pickPositionCache.hasOwnProperty(cacheKey)) {
|
|
return Cartesian3.clone(this._pickPositionCache[cacheKey], result);
|
|
}
|
|
|
|
var frameState = scene.frameState;
|
|
var context = scene.context;
|
|
var uniformState = context.uniformState;
|
|
|
|
var view = scene.defaultView;
|
|
scene.view = view;
|
|
|
|
var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(
|
|
scene,
|
|
windowPosition,
|
|
scratchPosition
|
|
);
|
|
if (scene.pickTranslucentDepth) {
|
|
renderTranslucentDepthForPick(scene, drawingBufferPosition);
|
|
} else {
|
|
scene.updateFrameState();
|
|
uniformState.update(frameState);
|
|
scene.updateEnvironment();
|
|
}
|
|
drawingBufferPosition.y = scene.drawingBufferHeight - drawingBufferPosition.y;
|
|
|
|
var camera = scene.camera;
|
|
|
|
// Create a working frustum from the original camera frustum.
|
|
var 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);
|
|
}
|
|
|
|
var frustumCommandsList = view.frustumCommandsList;
|
|
var numFrustums = frustumCommandsList.length;
|
|
for (var i = 0; i < numFrustums; ++i) {
|
|
var pickDepth = this.getPickDepth(scene, i);
|
|
var depth = pickDepth.getDepth(
|
|
context,
|
|
drawingBufferPosition.x,
|
|
drawingBufferPosition.y
|
|
);
|
|
if (!defined(depth)) {
|
|
continue;
|
|
}
|
|
if (depth > 0.0 && depth < 1.0) {
|
|
var renderedFrustum = frustumCommandsList[i];
|
|
var height2D;
|
|
if (scene.mode === SceneMode.SCENE2D) {
|
|
height2D = camera.position.z;
|
|
camera.position.z = height2D - renderedFrustum.near + 1.0;
|
|
frustum.far = Math.max(1.0, renderedFrustum.far - renderedFrustum.near);
|
|
frustum.near = 1.0;
|
|
uniformState.update(frameState);
|
|
uniformState.updateFrustum(frustum);
|
|
} else {
|
|
frustum.near =
|
|
renderedFrustum.near *
|
|
(i !== 0 ? scene.opaqueFrustumNearOffset : 1.0);
|
|
frustum.far = renderedFrustum.far;
|
|
uniformState.updateFrustum(frustum);
|
|
}
|
|
|
|
result = SceneTransforms.drawingBufferToWgs84Coordinates(
|
|
scene,
|
|
drawingBufferPosition,
|
|
depth,
|
|
result
|
|
);
|
|
|
|
if (scene.mode === SceneMode.SCENE2D) {
|
|
camera.position.z = height2D;
|
|
uniformState.update(frameState);
|
|
}
|
|
|
|
this._pickPositionCache[cacheKey] = Cartesian3.clone(result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
this._pickPositionCache[cacheKey] = undefined;
|
|
return undefined;
|
|
};
|
|
|
|
var scratchPickPositionCartographic = new Cartographic();
|
|
|
|
Picking.prototype.pickPosition = function (scene, windowPosition, result) {
|
|
result = this.pickPositionWorldCoordinates(scene, windowPosition, result);
|
|
if (defined(result) && scene.mode !== SceneMode.SCENE3D) {
|
|
Cartesian3.fromElements(result.y, result.z, result.x, result);
|
|
|
|
var projection = scene.mapProjection;
|
|
var ellipsoid = projection.ellipsoid;
|
|
|
|
var cart = projection.unproject(result, scratchPickPositionCartographic);
|
|
ellipsoid.cartographicToCartesian(cart, result);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
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.
|
|
var i;
|
|
var attributes;
|
|
var result = [];
|
|
var pickedPrimitives = [];
|
|
var pickedAttributes = [];
|
|
var pickedFeatures = [];
|
|
if (!defined(limit)) {
|
|
limit = Number.MAX_VALUE;
|
|
}
|
|
|
|
var pickedResult = pickCallback();
|
|
while (defined(pickedResult)) {
|
|
var object = pickedResult.object;
|
|
var position = pickedResult.position;
|
|
var exclude = pickedResult.exclude;
|
|
|
|
if (defined(position) && !defined(object)) {
|
|
result.push(pickedResult);
|
|
break;
|
|
}
|
|
|
|
if (!defined(object) || !defined(object.primitive)) {
|
|
break;
|
|
}
|
|
|
|
if (!exclude) {
|
|
result.push(pickedResult);
|
|
if (0 >= --limit) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
var primitive = object.primitive;
|
|
var hasShowAttribute = false;
|
|
|
|
// If the picked object has a show attribute, use it.
|
|
if (typeof primitive.getGeometryInstanceAttributes === "function") {
|
|
if (defined(object.id)) {
|
|
attributes = primitive.getGeometryInstanceAttributes(object.id);
|
|
if (defined(attributes) && defined(attributes.show)) {
|
|
hasShowAttribute = true;
|
|
attributes.show = ShowGeometryInstanceAttribute.toValue(
|
|
false,
|
|
attributes.show
|
|
);
|
|
pickedAttributes.push(attributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (object instanceof Cesium3DTileFeature) {
|
|
hasShowAttribute = true;
|
|
object.show = false;
|
|
pickedFeatures.push(object);
|
|
}
|
|
|
|
// Otherwise, hide the entire primitive
|
|
if (!hasShowAttribute) {
|
|
primitive.show = false;
|
|
pickedPrimitives.push(primitive);
|
|
}
|
|
|
|
pickedResult = pickCallback();
|
|
}
|
|
|
|
// Unhide everything we hid while drill picking
|
|
for (i = 0; i < pickedPrimitives.length; ++i) {
|
|
pickedPrimitives[i].show = true;
|
|
}
|
|
|
|
for (i = 0; i < pickedAttributes.length; ++i) {
|
|
attributes = pickedAttributes[i];
|
|
attributes.show = ShowGeometryInstanceAttribute.toValue(
|
|
true,
|
|
attributes.show
|
|
);
|
|
}
|
|
|
|
for (i = 0; i < pickedFeatures.length; ++i) {
|
|
pickedFeatures[i].show = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Picking.prototype.drillPick = function (
|
|
scene,
|
|
windowPosition,
|
|
limit,
|
|
width,
|
|
height
|
|
) {
|
|
var that = this;
|
|
var pickCallback = function () {
|
|
var object = that.pick(scene, windowPosition, width, height);
|
|
if (defined(object)) {
|
|
return {
|
|
object: object,
|
|
position: undefined,
|
|
exclude: false,
|
|
};
|
|
}
|
|
};
|
|
var objects = drillPick(limit, pickCallback);
|
|
return objects.map(function (element) {
|
|
return element.object;
|
|
});
|
|
};
|
|
|
|
var scratchRight = new Cartesian3();
|
|
var scratchUp = new Cartesian3();
|
|
|
|
function MostDetailedRayPick(ray, width, tilesets) {
|
|
this.ray = ray;
|
|
this.width = width;
|
|
this.tilesets = tilesets;
|
|
this.ready = false;
|
|
this.deferred = when.defer();
|
|
this.promise = this.deferred.promise;
|
|
}
|
|
|
|
function updateOffscreenCameraFromRay(picking, ray, width, camera) {
|
|
var direction = ray.direction;
|
|
var orthogonalAxis = Cartesian3.mostOrthogonalAxis(direction, scratchRight);
|
|
var right = Cartesian3.cross(direction, orthogonalAxis, scratchRight);
|
|
var up = Cartesian3.cross(direction, right, scratchUp);
|
|
|
|
camera.position = ray.origin;
|
|
camera.direction = direction;
|
|
camera.up = up;
|
|
camera.right = right;
|
|
|
|
camera.frustum.width = defaultValue(width, offscreenDefaultWidth);
|
|
return camera.frustum.computeCullingVolume(
|
|
camera.positionWC,
|
|
camera.directionWC,
|
|
camera.upWC
|
|
);
|
|
}
|
|
|
|
function updateMostDetailedRayPick(picking, scene, rayPick) {
|
|
var frameState = scene.frameState;
|
|
|
|
var ray = rayPick.ray;
|
|
var width = rayPick.width;
|
|
var tilesets = rayPick.tilesets;
|
|
|
|
var camera = picking._pickOffscreenView.camera;
|
|
var cullingVolume = updateOffscreenCameraFromRay(picking, ray, width, camera);
|
|
|
|
var tilesetPassState = mostDetailedPreloadTilesetPassState;
|
|
tilesetPassState.camera = camera;
|
|
tilesetPassState.cullingVolume = cullingVolume;
|
|
|
|
var ready = true;
|
|
var tilesetsLength = tilesets.length;
|
|
for (var i = 0; i < tilesetsLength; ++i) {
|
|
var tileset = tilesets[i];
|
|
if (tileset.show && scene.primitives.contains(tileset)) {
|
|
// Only update tilesets that are still contained in the scene's primitive collection and are still visible
|
|
// Update tilesets continually until all tilesets are ready. This way tiles are never removed from the cache.
|
|
tileset.updateForPass(frameState, tilesetPassState);
|
|
ready = ready && tilesetPassState.ready;
|
|
}
|
|
}
|
|
|
|
if (ready) {
|
|
rayPick.deferred.resolve();
|
|
}
|
|
|
|
return ready;
|
|
}
|
|
|
|
Picking.prototype.updateMostDetailedRayPicks = function (scene) {
|
|
// Modifies array during iteration
|
|
var rayPicks = this._mostDetailedRayPicks;
|
|
for (var i = 0; i < rayPicks.length; ++i) {
|
|
if (updateMostDetailedRayPick(this, scene, rayPicks[i])) {
|
|
rayPicks.splice(i--, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
function getTilesets(primitives, objectsToExclude, tilesets) {
|
|
var length = primitives.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
var primitive = primitives.get(i);
|
|
if (primitive.show) {
|
|
if (defined(primitive.isCesium3DTileset)) {
|
|
if (
|
|
!defined(objectsToExclude) ||
|
|
objectsToExclude.indexOf(primitive) === -1
|
|
) {
|
|
tilesets.push(primitive);
|
|
}
|
|
} else if (primitive instanceof PrimitiveCollection) {
|
|
getTilesets(primitive, objectsToExclude, tilesets);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function launchMostDetailedRayPick(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
callback
|
|
) {
|
|
var tilesets = [];
|
|
getTilesets(scene.primitives, objectsToExclude, tilesets);
|
|
if (tilesets.length === 0) {
|
|
return when.resolve(callback());
|
|
}
|
|
|
|
var rayPick = new MostDetailedRayPick(ray, width, tilesets);
|
|
picking._mostDetailedRayPicks.push(rayPick);
|
|
return rayPick.promise.then(function () {
|
|
return callback();
|
|
});
|
|
}
|
|
|
|
function isExcluded(object, objectsToExclude) {
|
|
if (
|
|
!defined(object) ||
|
|
!defined(objectsToExclude) ||
|
|
objectsToExclude.length === 0
|
|
) {
|
|
return false;
|
|
}
|
|
return (
|
|
objectsToExclude.indexOf(object) > -1 ||
|
|
objectsToExclude.indexOf(object.primitive) > -1 ||
|
|
objectsToExclude.indexOf(object.id) > -1
|
|
);
|
|
}
|
|
|
|
function getRayIntersection(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
requirePosition,
|
|
mostDetailed
|
|
) {
|
|
var context = scene.context;
|
|
var uniformState = context.uniformState;
|
|
var frameState = scene.frameState;
|
|
|
|
var view = picking._pickOffscreenView;
|
|
scene.view = view;
|
|
|
|
updateOffscreenCameraFromRay(picking, ray, width, view.camera);
|
|
|
|
scratchRectangle = BoundingRectangle.clone(view.viewport, scratchRectangle);
|
|
|
|
var passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
|
|
|
|
scene.jobScheduler.disableThisFrame();
|
|
|
|
scene.updateFrameState();
|
|
frameState.invertClassification = false;
|
|
frameState.passes.pick = true;
|
|
frameState.passes.offscreen = true;
|
|
|
|
if (mostDetailed) {
|
|
frameState.tilesetPassState = mostDetailedPickTilesetPassState;
|
|
} else {
|
|
frameState.tilesetPassState = pickTilesetPassState;
|
|
}
|
|
|
|
uniformState.update(frameState);
|
|
|
|
scene.updateEnvironment();
|
|
scene.updateAndExecuteCommands(passState, scratchColorZero);
|
|
scene.resolveFramebuffers(passState);
|
|
|
|
var position;
|
|
var object = view.pickFramebuffer.end(scratchRectangle);
|
|
|
|
if (scene.context.depthTexture) {
|
|
var numFrustums = view.frustumCommandsList.length;
|
|
for (var i = 0; i < numFrustums; ++i) {
|
|
var pickDepth = picking.getPickDepth(scene, i);
|
|
var depth = pickDepth.getDepth(context, 0, 0);
|
|
if (!defined(depth)) {
|
|
continue;
|
|
}
|
|
if (depth > 0.0 && depth < 1.0) {
|
|
var renderedFrustum = view.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
scene.view = scene.defaultView;
|
|
context.endFrame();
|
|
|
|
if (defined(object) || defined(position)) {
|
|
return {
|
|
object: object,
|
|
position: position,
|
|
exclude:
|
|
(!defined(position) && requirePosition) ||
|
|
isExcluded(object, objectsToExclude),
|
|
};
|
|
}
|
|
}
|
|
|
|
function getRayIntersections(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
limit,
|
|
objectsToExclude,
|
|
width,
|
|
requirePosition,
|
|
mostDetailed
|
|
) {
|
|
var pickCallback = function () {
|
|
return getRayIntersection(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
requirePosition,
|
|
mostDetailed
|
|
);
|
|
};
|
|
return drillPick(limit, pickCallback);
|
|
}
|
|
|
|
function pickFromRay(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
requirePosition,
|
|
mostDetailed
|
|
) {
|
|
var results = getRayIntersections(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
1,
|
|
objectsToExclude,
|
|
width,
|
|
requirePosition,
|
|
mostDetailed
|
|
);
|
|
if (results.length > 0) {
|
|
return results[0];
|
|
}
|
|
}
|
|
|
|
function drillPickFromRay(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
limit,
|
|
objectsToExclude,
|
|
width,
|
|
requirePosition,
|
|
mostDetailed
|
|
) {
|
|
return getRayIntersections(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
limit,
|
|
objectsToExclude,
|
|
width,
|
|
requirePosition,
|
|
mostDetailed
|
|
);
|
|
}
|
|
|
|
function deferPromiseUntilPostRender(scene, promise) {
|
|
// Resolve promise after scene's postRender in case entities are created when the promise resolves.
|
|
// Entities can't be created between viewer._onTick and viewer._postRender.
|
|
var deferred = when.defer();
|
|
promise
|
|
.then(function (result) {
|
|
var removeCallback = scene.postRender.addEventListener(function () {
|
|
deferred.resolve(result);
|
|
removeCallback();
|
|
});
|
|
scene.requestRender();
|
|
})
|
|
.otherwise(function (error) {
|
|
deferred.reject(error);
|
|
});
|
|
return deferred.promise;
|
|
}
|
|
|
|
Picking.prototype.pickFromRay = function (scene, ray, objectsToExclude, width) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("ray", ray);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError(
|
|
"Ray intersections are only supported in 3D mode."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
return pickFromRay(this, scene, ray, objectsToExclude, width, false, false);
|
|
};
|
|
|
|
Picking.prototype.drillPickFromRay = function (
|
|
scene,
|
|
ray,
|
|
limit,
|
|
objectsToExclude,
|
|
width
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("ray", ray);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError(
|
|
"Ray intersections are only supported in 3D mode."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
return drillPickFromRay(
|
|
this,
|
|
scene,
|
|
ray,
|
|
limit,
|
|
objectsToExclude,
|
|
width,
|
|
false,
|
|
false
|
|
);
|
|
};
|
|
|
|
Picking.prototype.pickFromRayMostDetailed = function (
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("ray", ray);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError(
|
|
"Ray intersections are only supported in 3D mode."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var that = this;
|
|
ray = Ray.clone(ray);
|
|
objectsToExclude = defined(objectsToExclude)
|
|
? objectsToExclude.slice()
|
|
: objectsToExclude;
|
|
return deferPromiseUntilPostRender(
|
|
scene,
|
|
launchMostDetailedRayPick(
|
|
that,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
function () {
|
|
return pickFromRay(
|
|
that,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
false,
|
|
true
|
|
);
|
|
}
|
|
)
|
|
);
|
|
};
|
|
|
|
Picking.prototype.drillPickFromRayMostDetailed = function (
|
|
scene,
|
|
ray,
|
|
limit,
|
|
objectsToExclude,
|
|
width
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("ray", ray);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError(
|
|
"Ray intersections are only supported in 3D mode."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var that = this;
|
|
ray = Ray.clone(ray);
|
|
objectsToExclude = defined(objectsToExclude)
|
|
? objectsToExclude.slice()
|
|
: objectsToExclude;
|
|
return deferPromiseUntilPostRender(
|
|
scene,
|
|
launchMostDetailedRayPick(
|
|
that,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
function () {
|
|
return drillPickFromRay(
|
|
that,
|
|
scene,
|
|
ray,
|
|
limit,
|
|
objectsToExclude,
|
|
width,
|
|
false,
|
|
true
|
|
);
|
|
}
|
|
)
|
|
);
|
|
};
|
|
|
|
var scratchSurfacePosition = new Cartesian3();
|
|
var scratchSurfaceNormal = new Cartesian3();
|
|
var scratchSurfaceRay = new Ray();
|
|
var scratchCartographic = new Cartographic();
|
|
|
|
function getRayForSampleHeight(scene, cartographic) {
|
|
var globe = scene.globe;
|
|
var ellipsoid = defined(globe)
|
|
? globe.ellipsoid
|
|
: scene.mapProjection.ellipsoid;
|
|
var height = ApproximateTerrainHeights._defaultMaxTerrainHeight;
|
|
var surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(
|
|
cartographic,
|
|
scratchSurfaceNormal
|
|
);
|
|
var surfacePosition = Cartographic.toCartesian(
|
|
cartographic,
|
|
ellipsoid,
|
|
scratchSurfacePosition
|
|
);
|
|
var surfaceRay = scratchSurfaceRay;
|
|
surfaceRay.origin = surfacePosition;
|
|
surfaceRay.direction = surfaceNormal;
|
|
var ray = new Ray();
|
|
Ray.getPoint(surfaceRay, height, ray.origin);
|
|
Cartesian3.negate(surfaceNormal, ray.direction);
|
|
return ray;
|
|
}
|
|
|
|
function getRayForClampToHeight(scene, cartesian) {
|
|
var globe = scene.globe;
|
|
var ellipsoid = defined(globe)
|
|
? globe.ellipsoid
|
|
: scene.mapProjection.ellipsoid;
|
|
var cartographic = Cartographic.fromCartesian(
|
|
cartesian,
|
|
ellipsoid,
|
|
scratchCartographic
|
|
);
|
|
return getRayForSampleHeight(scene, cartographic);
|
|
}
|
|
|
|
function getHeightFromCartesian(scene, cartesian) {
|
|
var globe = scene.globe;
|
|
var ellipsoid = defined(globe)
|
|
? globe.ellipsoid
|
|
: scene.mapProjection.ellipsoid;
|
|
var cartographic = Cartographic.fromCartesian(
|
|
cartesian,
|
|
ellipsoid,
|
|
scratchCartographic
|
|
);
|
|
return cartographic.height;
|
|
}
|
|
|
|
function sampleHeightMostDetailed(
|
|
picking,
|
|
scene,
|
|
cartographic,
|
|
objectsToExclude,
|
|
width
|
|
) {
|
|
var ray = getRayForSampleHeight(scene, cartographic);
|
|
return launchMostDetailedRayPick(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
function () {
|
|
var pickResult = pickFromRay(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
true,
|
|
true
|
|
);
|
|
if (defined(pickResult)) {
|
|
return getHeightFromCartesian(scene, pickResult.position);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
function clampToHeightMostDetailed(
|
|
picking,
|
|
scene,
|
|
cartesian,
|
|
objectsToExclude,
|
|
width,
|
|
result
|
|
) {
|
|
var ray = getRayForClampToHeight(scene, cartesian);
|
|
return launchMostDetailedRayPick(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
function () {
|
|
var pickResult = pickFromRay(
|
|
picking,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
true,
|
|
true
|
|
);
|
|
if (defined(pickResult)) {
|
|
return Cartesian3.clone(pickResult.position, result);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
Picking.prototype.sampleHeight = function (
|
|
scene,
|
|
position,
|
|
objectsToExclude,
|
|
width
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("position", position);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError("sampleHeight is only supported in 3D mode.");
|
|
}
|
|
if (!scene.sampleHeightSupported) {
|
|
throw new DeveloperError(
|
|
"sampleHeight requires depth texture support. Check sampleHeightSupported."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var ray = getRayForSampleHeight(scene, position);
|
|
var pickResult = pickFromRay(
|
|
this,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
true,
|
|
false
|
|
);
|
|
if (defined(pickResult)) {
|
|
return getHeightFromCartesian(scene, pickResult.position);
|
|
}
|
|
};
|
|
|
|
Picking.prototype.clampToHeight = function (
|
|
scene,
|
|
cartesian,
|
|
objectsToExclude,
|
|
width,
|
|
result
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("cartesian", cartesian);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError("clampToHeight is only supported in 3D mode.");
|
|
}
|
|
if (!scene.clampToHeightSupported) {
|
|
throw new DeveloperError(
|
|
"clampToHeight requires depth texture support. Check clampToHeightSupported."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var ray = getRayForClampToHeight(scene, cartesian);
|
|
var pickResult = pickFromRay(
|
|
this,
|
|
scene,
|
|
ray,
|
|
objectsToExclude,
|
|
width,
|
|
true,
|
|
false
|
|
);
|
|
if (defined(pickResult)) {
|
|
return Cartesian3.clone(pickResult.position, result);
|
|
}
|
|
};
|
|
|
|
Picking.prototype.sampleHeightMostDetailed = function (
|
|
scene,
|
|
positions,
|
|
objectsToExclude,
|
|
width
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("positions", positions);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError(
|
|
"sampleHeightMostDetailed is only supported in 3D mode."
|
|
);
|
|
}
|
|
if (!scene.sampleHeightSupported) {
|
|
throw new DeveloperError(
|
|
"sampleHeightMostDetailed requires depth texture support. Check sampleHeightSupported."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
objectsToExclude = defined(objectsToExclude)
|
|
? objectsToExclude.slice()
|
|
: objectsToExclude;
|
|
var length = positions.length;
|
|
var promises = new Array(length);
|
|
for (var i = 0; i < length; ++i) {
|
|
promises[i] = sampleHeightMostDetailed(
|
|
this,
|
|
scene,
|
|
positions[i],
|
|
objectsToExclude,
|
|
width
|
|
);
|
|
}
|
|
return deferPromiseUntilPostRender(
|
|
scene,
|
|
when.all(promises).then(function (heights) {
|
|
var length = heights.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
positions[i].height = heights[i];
|
|
}
|
|
return positions;
|
|
})
|
|
);
|
|
};
|
|
|
|
Picking.prototype.clampToHeightMostDetailed = function (
|
|
scene,
|
|
cartesians,
|
|
objectsToExclude,
|
|
width
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("cartesians", cartesians);
|
|
if (scene.mode !== SceneMode.SCENE3D) {
|
|
throw new DeveloperError(
|
|
"clampToHeightMostDetailed is only supported in 3D mode."
|
|
);
|
|
}
|
|
if (!scene.clampToHeightSupported) {
|
|
throw new DeveloperError(
|
|
"clampToHeightMostDetailed requires depth texture support. Check clampToHeightSupported."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
objectsToExclude = defined(objectsToExclude)
|
|
? objectsToExclude.slice()
|
|
: objectsToExclude;
|
|
var length = cartesians.length;
|
|
var promises = new Array(length);
|
|
for (var i = 0; i < length; ++i) {
|
|
promises[i] = clampToHeightMostDetailed(
|
|
this,
|
|
scene,
|
|
cartesians[i],
|
|
objectsToExclude,
|
|
width,
|
|
cartesians[i]
|
|
);
|
|
}
|
|
return deferPromiseUntilPostRender(
|
|
scene,
|
|
when.all(promises).then(function (clampedCartesians) {
|
|
var length = clampedCartesians.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
cartesians[i] = clampedCartesians[i];
|
|
}
|
|
return cartesians;
|
|
})
|
|
);
|
|
};
|
|
|
|
Picking.prototype.destroy = function () {
|
|
this._pickOffscreenView =
|
|
this._pickOffscreenView && this._pickOffscreenView.destroy();
|
|
};
|
|
export default Picking;
|