Picking.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. import ApproximateTerrainHeights from '../Core/ApproximateTerrainHeights.js';
  2. import BoundingRectangle from '../Core/BoundingRectangle.js';
  3. import Cartesian2 from '../Core/Cartesian2.js';
  4. import Cartesian3 from '../Core/Cartesian3.js';
  5. import Cartographic from '../Core/Cartographic.js';
  6. import Check from '../Core/Check.js';
  7. import Color from '../Core/Color.js';
  8. import defaultValue from '../Core/defaultValue.js';
  9. import defined from '../Core/defined.js';
  10. import DeveloperError from '../Core/DeveloperError.js';
  11. import Matrix4 from '../Core/Matrix4.js';
  12. import OrthographicFrustum from '../Core/OrthographicFrustum.js';
  13. import OrthographicOffCenterFrustum from '../Core/OrthographicOffCenterFrustum.js';
  14. import PerspectiveFrustum from '../Core/PerspectiveFrustum.js';
  15. import PerspectiveOffCenterFrustum from '../Core/PerspectiveOffCenterFrustum.js';
  16. import Ray from '../Core/Ray.js';
  17. import ShowGeometryInstanceAttribute from '../Core/ShowGeometryInstanceAttribute.js';
  18. import when from '../ThirdParty/when.js';
  19. import Camera from './Camera.js';
  20. import Cesium3DTileFeature from './Cesium3DTileFeature.js';
  21. import Cesium3DTilePass from './Cesium3DTilePass.js';
  22. import Cesium3DTilePassState from './Cesium3DTilePassState.js';
  23. import Cesium3DTileset from './Cesium3DTileset.js';
  24. import PickDepth from './PickDepth.js';
  25. import PrimitiveCollection from './PrimitiveCollection.js';
  26. import SceneMode from './SceneMode.js';
  27. import SceneTransforms from './SceneTransforms.js';
  28. import View from './View.js';
  29. var offscreenDefaultWidth = 0.1;
  30. var mostDetailedPreloadTilesetPassState = new Cesium3DTilePassState({
  31. pass : Cesium3DTilePass.MOST_DETAILED_PRELOAD
  32. });
  33. var mostDetailedPickTilesetPassState = new Cesium3DTilePassState({
  34. pass : Cesium3DTilePass.MOST_DETAILED_PICK
  35. });
  36. var pickTilesetPassState = new Cesium3DTilePassState({
  37. pass : Cesium3DTilePass.PICK
  38. });
  39. /**
  40. * @private
  41. */
  42. function Picking(scene) {
  43. this._mostDetailedRayPicks = [];
  44. this.pickRenderStateCache = {};
  45. this._pickPositionCache = {};
  46. this._pickPositionCacheDirty = false;
  47. var pickOffscreenViewport = new BoundingRectangle(0, 0, 1, 1);
  48. var pickOffscreenCamera = new Camera(scene);
  49. pickOffscreenCamera.frustum = new OrthographicFrustum({
  50. width: offscreenDefaultWidth,
  51. aspectRatio: 1.0,
  52. near: 0.1
  53. });
  54. this._pickOffscreenView = new View(scene, pickOffscreenCamera, pickOffscreenViewport);
  55. }
  56. Picking.prototype.update = function() {
  57. this._pickPositionCacheDirty = true;
  58. };
  59. Picking.prototype.getPickDepth = function(scene, index) {
  60. var pickDepths = scene.view.pickDepths;
  61. var pickDepth = pickDepths[index];
  62. if (!defined(pickDepth)) {
  63. pickDepth = new PickDepth();
  64. pickDepths[index] = pickDepth;
  65. }
  66. return pickDepth;
  67. };
  68. var scratchOrthoPickingFrustum = new OrthographicOffCenterFrustum();
  69. var scratchOrthoOrigin = new Cartesian3();
  70. var scratchOrthoDirection = new Cartesian3();
  71. var scratchOrthoPixelSize = new Cartesian2();
  72. var scratchOrthoPickVolumeMatrix4 = new Matrix4();
  73. function getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height, viewport) {
  74. var camera = scene.camera;
  75. var frustum = camera.frustum;
  76. if (defined(frustum._offCenterFrustum)) {
  77. frustum = frustum._offCenterFrustum;
  78. }
  79. var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
  80. x *= (frustum.right - frustum.left) * 0.5;
  81. var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;
  82. y *= (frustum.top - frustum.bottom) * 0.5;
  83. var transform = Matrix4.clone(camera.transform, scratchOrthoPickVolumeMatrix4);
  84. camera._setTransform(Matrix4.IDENTITY);
  85. var origin = Cartesian3.clone(camera.position, scratchOrthoOrigin);
  86. Cartesian3.multiplyByScalar(camera.right, x, scratchOrthoDirection);
  87. Cartesian3.add(scratchOrthoDirection, origin, origin);
  88. Cartesian3.multiplyByScalar(camera.up, y, scratchOrthoDirection);
  89. Cartesian3.add(scratchOrthoDirection, origin, origin);
  90. camera._setTransform(transform);
  91. if (scene.mode === SceneMode.SCENE2D) {
  92. Cartesian3.fromElements(origin.z, origin.x, origin.y, origin);
  93. }
  94. var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, 1.0, scratchOrthoPixelSize);
  95. var ortho = scratchOrthoPickingFrustum;
  96. ortho.right = pixelSize.x * 0.5;
  97. ortho.left = -ortho.right;
  98. ortho.top = pixelSize.y * 0.5;
  99. ortho.bottom = -ortho.top;
  100. ortho.near = frustum.near;
  101. ortho.far = frustum.far;
  102. return ortho.computeCullingVolume(origin, camera.directionWC, camera.upWC);
  103. }
  104. var scratchPerspPickingFrustum = new PerspectiveOffCenterFrustum();
  105. var scratchPerspPixelSize = new Cartesian2();
  106. function getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height, viewport) {
  107. var camera = scene.camera;
  108. var frustum = camera.frustum;
  109. var near = frustum.near;
  110. var tanPhi = Math.tan(frustum.fovy * 0.5);
  111. var tanTheta = frustum.aspectRatio * tanPhi;
  112. var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
  113. var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;
  114. var xDir = x * near * tanTheta;
  115. var yDir = y * near * tanPhi;
  116. var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, 1.0, scratchPerspPixelSize);
  117. var pickWidth = pixelSize.x * width * 0.5;
  118. var pickHeight = pixelSize.y * height * 0.5;
  119. var offCenter = scratchPerspPickingFrustum;
  120. offCenter.top = yDir + pickHeight;
  121. offCenter.bottom = yDir - pickHeight;
  122. offCenter.right = xDir + pickWidth;
  123. offCenter.left = xDir - pickWidth;
  124. offCenter.near = near;
  125. offCenter.far = frustum.far;
  126. return offCenter.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
  127. }
  128. function getPickCullingVolume(scene, drawingBufferPosition, width, height, viewport) {
  129. var frustum = scene.camera.frustum;
  130. if (frustum instanceof OrthographicFrustum || frustum instanceof OrthographicOffCenterFrustum) {
  131. return getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height, viewport);
  132. }
  133. return getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height, viewport);
  134. }
  135. // pick rectangle width and height, assumed odd
  136. var scratchRectangleWidth = 3.0;
  137. var scratchRectangleHeight = 3.0;
  138. var scratchRectangle = new BoundingRectangle(0.0, 0.0, scratchRectangleWidth, scratchRectangleHeight);
  139. var scratchPosition = new Cartesian2();
  140. var scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0);
  141. Picking.prototype.pick = function(scene, windowPosition, width, height) {
  142. //>>includeStart('debug', pragmas.debug);
  143. if (!defined(windowPosition)) {
  144. throw new DeveloperError('windowPosition is undefined.');
  145. }
  146. //>>includeEnd('debug');
  147. scratchRectangleWidth = defaultValue(width, 3.0);
  148. scratchRectangleHeight = defaultValue(height, scratchRectangleWidth);
  149. var context = scene.context;
  150. var us = context.uniformState;
  151. var frameState = scene.frameState;
  152. var view = scene.defaultView;
  153. scene.view = view;
  154. var viewport = view.viewport;
  155. viewport.x = 0;
  156. viewport.y = 0;
  157. viewport.width = context.drawingBufferWidth;
  158. viewport.height = context.drawingBufferHeight;
  159. var passState = view.passState;
  160. passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
  161. var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(scene, windowPosition, scratchPosition);
  162. scene.jobScheduler.disableThisFrame();
  163. scene.updateFrameState();
  164. frameState.cullingVolume = getPickCullingVolume(scene, drawingBufferPosition, scratchRectangleWidth, scratchRectangleHeight, viewport);
  165. frameState.invertClassification = false;
  166. frameState.passes.pick = true;
  167. frameState.tilesetPassState = pickTilesetPassState;
  168. us.update(frameState);
  169. scene.updateEnvironment();
  170. scratchRectangle.x = drawingBufferPosition.x - ((scratchRectangleWidth - 1.0) * 0.5);
  171. scratchRectangle.y = (scene.drawingBufferHeight - drawingBufferPosition.y) - ((scratchRectangleHeight - 1.0) * 0.5);
  172. scratchRectangle.width = scratchRectangleWidth;
  173. scratchRectangle.height = scratchRectangleHeight;
  174. passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
  175. scene.updateAndExecuteCommands(passState, scratchColorZero);
  176. scene.resolveFramebuffers(passState);
  177. var object = view.pickFramebuffer.end(scratchRectangle);
  178. context.endFrame();
  179. return object;
  180. };
  181. function renderTranslucentDepthForPick(scene, drawingBufferPosition) {
  182. // PERFORMANCE_IDEA: render translucent only and merge with the previous frame
  183. var context = scene.context;
  184. var frameState = scene.frameState;
  185. var environmentState = scene.environmentState;
  186. var view = scene.defaultView;
  187. scene.view = view;
  188. var viewport = view.viewport;
  189. viewport.x = 0;
  190. viewport.y = 0;
  191. viewport.width = context.drawingBufferWidth;
  192. viewport.height = context.drawingBufferHeight;
  193. var passState = view.passState;
  194. passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
  195. scene.clearPasses(frameState.passes);
  196. frameState.passes.pick = true;
  197. frameState.passes.depth = true;
  198. frameState.cullingVolume = getPickCullingVolume(scene, drawingBufferPosition, 1, 1, viewport);
  199. frameState.tilesetPassState = pickTilesetPassState;
  200. scene.updateEnvironment();
  201. environmentState.renderTranslucentDepthForPick = true;
  202. passState = view.pickDepthFramebuffer.update(context, drawingBufferPosition, viewport);
  203. scene.updateAndExecuteCommands(passState, scratchColorZero);
  204. scene.resolveFramebuffers(passState);
  205. context.endFrame();
  206. }
  207. var scratchPerspectiveFrustum = new PerspectiveFrustum();
  208. var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
  209. var scratchOrthographicFrustum = new OrthographicFrustum();
  210. var scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum();
  211. Picking.prototype.pickPositionWorldCoordinates = function(scene, windowPosition, result) {
  212. if (!scene.useDepthPicking) {
  213. return undefined;
  214. }
  215. //>>includeStart('debug', pragmas.debug);
  216. if (!defined(windowPosition)) {
  217. throw new DeveloperError('windowPosition is undefined.');
  218. }
  219. if (!scene.context.depthTexture) {
  220. throw new DeveloperError('Picking from the depth buffer is not supported. Check pickPositionSupported.');
  221. }
  222. //>>includeEnd('debug');
  223. var cacheKey = windowPosition.toString();
  224. if (this._pickPositionCacheDirty){
  225. this._pickPositionCache = {};
  226. this._pickPositionCacheDirty = false;
  227. } else if (this._pickPositionCache.hasOwnProperty(cacheKey)){
  228. return Cartesian3.clone(this._pickPositionCache[cacheKey], result);
  229. }
  230. var frameState = scene.frameState;
  231. var context = scene.context;
  232. var uniformState = context.uniformState;
  233. var view = scene.defaultView;
  234. scene.view = view;
  235. var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(scene, windowPosition, scratchPosition);
  236. if (scene.pickTranslucentDepth) {
  237. renderTranslucentDepthForPick(scene, drawingBufferPosition);
  238. } else {
  239. scene.updateFrameState();
  240. uniformState.update(frameState);
  241. scene.updateEnvironment();
  242. }
  243. drawingBufferPosition.y = scene.drawingBufferHeight - drawingBufferPosition.y;
  244. var camera = scene.camera;
  245. // Create a working frustum from the original camera frustum.
  246. var frustum;
  247. if (defined(camera.frustum.fov)) {
  248. frustum = camera.frustum.clone(scratchPerspectiveFrustum);
  249. } else if (defined(camera.frustum.infiniteProjectionMatrix)){
  250. frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
  251. } else if (defined(camera.frustum.width)) {
  252. frustum = camera.frustum.clone(scratchOrthographicFrustum);
  253. } else {
  254. frustum = camera.frustum.clone(scratchOrthographicOffCenterFrustum);
  255. }
  256. var frustumCommandsList = view.frustumCommandsList;
  257. var numFrustums = frustumCommandsList.length;
  258. for (var i = 0; i < numFrustums; ++i) {
  259. var pickDepth = this.getPickDepth(scene, i);
  260. var depth = pickDepth.getDepth(context, drawingBufferPosition.x, drawingBufferPosition.y);
  261. if (depth > 0.0 && depth < 1.0) {
  262. var renderedFrustum = frustumCommandsList[i];
  263. var height2D;
  264. if (scene.mode === SceneMode.SCENE2D) {
  265. height2D = camera.position.z;
  266. camera.position.z = height2D - renderedFrustum.near + 1.0;
  267. frustum.far = Math.max(1.0, renderedFrustum.far - renderedFrustum.near);
  268. frustum.near = 1.0;
  269. uniformState.update(frameState);
  270. uniformState.updateFrustum(frustum);
  271. } else {
  272. frustum.near = renderedFrustum.near * (i !== 0 ? scene.opaqueFrustumNearOffset : 1.0);
  273. frustum.far = renderedFrustum.far;
  274. uniformState.updateFrustum(frustum);
  275. }
  276. result = SceneTransforms.drawingBufferToWgs84Coordinates(scene, drawingBufferPosition, depth, result);
  277. if (scene.mode === SceneMode.SCENE2D) {
  278. camera.position.z = height2D;
  279. uniformState.update(frameState);
  280. }
  281. this._pickPositionCache[cacheKey] = Cartesian3.clone(result);
  282. return result;
  283. }
  284. }
  285. this._pickPositionCache[cacheKey] = undefined;
  286. return undefined;
  287. };
  288. var scratchPickPositionCartographic = new Cartographic();
  289. Picking.prototype.pickPosition = function(scene, windowPosition, result) {
  290. result = this.pickPositionWorldCoordinates(scene, windowPosition, result);
  291. if (defined(result) && scene.mode !== SceneMode.SCENE3D) {
  292. Cartesian3.fromElements(result.y, result.z, result.x, result);
  293. var projection = scene.mapProjection;
  294. var ellipsoid = projection.ellipsoid;
  295. var cart = projection.unproject(result, scratchPickPositionCartographic);
  296. ellipsoid.cartographicToCartesian(cart, result);
  297. }
  298. return result;
  299. };
  300. function drillPick(limit, pickCallback) {
  301. // PERFORMANCE_IDEA: This function calls each primitive's update for each pass. Instead
  302. // we could update the primitive once, and then just execute their commands for each pass,
  303. // and cull commands for picked primitives. e.g., base on the command's owner.
  304. var i;
  305. var attributes;
  306. var result = [];
  307. var pickedPrimitives = [];
  308. var pickedAttributes = [];
  309. var pickedFeatures = [];
  310. if (!defined(limit)) {
  311. limit = Number.MAX_VALUE;
  312. }
  313. var pickedResult = pickCallback();
  314. while (defined(pickedResult)) {
  315. var object = pickedResult.object;
  316. var position = pickedResult.position;
  317. var exclude = pickedResult.exclude;
  318. if (defined(position) && !defined(object)) {
  319. result.push(pickedResult);
  320. break;
  321. }
  322. if (!defined(object) || !defined(object.primitive)) {
  323. break;
  324. }
  325. if (!exclude) {
  326. result.push(pickedResult);
  327. if (0 >= --limit) {
  328. break;
  329. }
  330. }
  331. var primitive = object.primitive;
  332. var hasShowAttribute = false;
  333. // If the picked object has a show attribute, use it.
  334. if (typeof primitive.getGeometryInstanceAttributes === 'function') {
  335. if (defined(object.id)) {
  336. attributes = primitive.getGeometryInstanceAttributes(object.id);
  337. if (defined(attributes) && defined(attributes.show)) {
  338. hasShowAttribute = true;
  339. attributes.show = ShowGeometryInstanceAttribute.toValue(false, attributes.show);
  340. pickedAttributes.push(attributes);
  341. }
  342. }
  343. }
  344. if (object instanceof Cesium3DTileFeature) {
  345. hasShowAttribute = true;
  346. object.show = false;
  347. pickedFeatures.push(object);
  348. }
  349. // Otherwise, hide the entire primitive
  350. if (!hasShowAttribute) {
  351. primitive.show = false;
  352. pickedPrimitives.push(primitive);
  353. }
  354. pickedResult = pickCallback();
  355. }
  356. // Unhide everything we hid while drill picking
  357. for (i = 0; i < pickedPrimitives.length; ++i) {
  358. pickedPrimitives[i].show = true;
  359. }
  360. for (i = 0; i < pickedAttributes.length; ++i) {
  361. attributes = pickedAttributes[i];
  362. attributes.show = ShowGeometryInstanceAttribute.toValue(true, attributes.show);
  363. }
  364. for (i = 0; i < pickedFeatures.length; ++i) {
  365. pickedFeatures[i].show = true;
  366. }
  367. return result;
  368. }
  369. Picking.prototype.drillPick = function(scene, windowPosition, limit, width, height) {
  370. var that = this;
  371. var pickCallback = function() {
  372. var object = that.pick(scene, windowPosition, width, height);
  373. if (defined(object)) {
  374. return {
  375. object : object,
  376. position : undefined,
  377. exclude : false
  378. };
  379. }
  380. };
  381. var objects = drillPick(limit, pickCallback);
  382. return objects.map(function(element) {
  383. return element.object;
  384. });
  385. };
  386. var scratchRight = new Cartesian3();
  387. var scratchUp = new Cartesian3();
  388. function MostDetailedRayPick(ray, width, tilesets) {
  389. this.ray = ray;
  390. this.width = width;
  391. this.tilesets = tilesets;
  392. this.ready = false;
  393. this.deferred = when.defer();
  394. this.promise = this.deferred.promise;
  395. }
  396. function updateOffscreenCameraFromRay(picking, ray, width, camera) {
  397. var direction = ray.direction;
  398. var orthogonalAxis = Cartesian3.mostOrthogonalAxis(direction, scratchRight);
  399. var right = Cartesian3.cross(direction, orthogonalAxis, scratchRight);
  400. var up = Cartesian3.cross(direction, right, scratchUp);
  401. camera.position = ray.origin;
  402. camera.direction = direction;
  403. camera.up = up;
  404. camera.right = right;
  405. camera.frustum.width = defaultValue(width, offscreenDefaultWidth);
  406. return camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
  407. }
  408. function updateMostDetailedRayPick(picking, scene, rayPick) {
  409. var frameState = scene.frameState;
  410. var ray = rayPick.ray;
  411. var width = rayPick.width;
  412. var tilesets = rayPick.tilesets;
  413. var camera = picking._pickOffscreenView.camera;
  414. var cullingVolume = updateOffscreenCameraFromRay(picking, ray, width, camera);
  415. var tilesetPassState = mostDetailedPreloadTilesetPassState;
  416. tilesetPassState.camera = camera;
  417. tilesetPassState.cullingVolume = cullingVolume;
  418. var ready = true;
  419. var tilesetsLength = tilesets.length;
  420. for (var i = 0; i < tilesetsLength; ++i) {
  421. var tileset = tilesets[i];
  422. if (tileset.show && scene.primitives.contains(tileset)) {
  423. // Only update tilesets that are still contained in the scene's primitive collection and are still visible
  424. // Update tilesets continually until all tilesets are ready. This way tiles are never removed from the cache.
  425. tileset.updateForPass(frameState, tilesetPassState);
  426. ready = (ready && tilesetPassState.ready);
  427. }
  428. }
  429. if (ready) {
  430. rayPick.deferred.resolve();
  431. }
  432. return ready;
  433. }
  434. Picking.prototype.updateMostDetailedRayPicks = function(scene) {
  435. // Modifies array during iteration
  436. var rayPicks = this._mostDetailedRayPicks;
  437. for (var i = 0; i < rayPicks.length; ++i) {
  438. if (updateMostDetailedRayPick(this, scene, rayPicks[i])) {
  439. rayPicks.splice(i--, 1);
  440. }
  441. }
  442. };
  443. function getTilesets(primitives, objectsToExclude, tilesets) {
  444. var length = primitives.length;
  445. for (var i = 0; i < length; ++i) {
  446. var primitive = primitives.get(i);
  447. if (primitive.show) {
  448. if ((primitive instanceof Cesium3DTileset)) {
  449. if (!defined(objectsToExclude) || objectsToExclude.indexOf(primitive) === -1) {
  450. tilesets.push(primitive);
  451. }
  452. } else if (primitive instanceof PrimitiveCollection) {
  453. getTilesets(primitive, objectsToExclude, tilesets);
  454. }
  455. }
  456. }
  457. }
  458. function launchMostDetailedRayPick(picking, scene, ray, objectsToExclude, width, callback) {
  459. var tilesets = [];
  460. getTilesets(scene.primitives, objectsToExclude, tilesets);
  461. if (tilesets.length === 0) {
  462. return when.resolve(callback());
  463. }
  464. var rayPick = new MostDetailedRayPick(ray, width, tilesets);
  465. picking._mostDetailedRayPicks.push(rayPick);
  466. return rayPick.promise.then(function() {
  467. return callback();
  468. });
  469. }
  470. function isExcluded(object, objectsToExclude) {
  471. if (!defined(object) || !defined(objectsToExclude) || objectsToExclude.length === 0) {
  472. return false;
  473. }
  474. return (objectsToExclude.indexOf(object) > -1) ||
  475. (objectsToExclude.indexOf(object.primitive) > -1) ||
  476. (objectsToExclude.indexOf(object.id) > -1);
  477. }
  478. function getRayIntersection(picking, scene, ray, objectsToExclude, width, requirePosition, mostDetailed) {
  479. var context = scene.context;
  480. var uniformState = context.uniformState;
  481. var frameState = scene.frameState;
  482. var view = picking._pickOffscreenView;
  483. scene.view = view;
  484. updateOffscreenCameraFromRay(picking, ray, width, view.camera);
  485. scratchRectangle = BoundingRectangle.clone(view.viewport, scratchRectangle);
  486. var passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
  487. scene.jobScheduler.disableThisFrame();
  488. scene.updateFrameState();
  489. frameState.invertClassification = false;
  490. frameState.passes.pick = true;
  491. frameState.passes.offscreen = true;
  492. if (mostDetailed) {
  493. frameState.tilesetPassState = mostDetailedPickTilesetPassState;
  494. } else {
  495. frameState.tilesetPassState = pickTilesetPassState;
  496. }
  497. uniformState.update(frameState);
  498. scene.updateEnvironment();
  499. scene.updateAndExecuteCommands(passState, scratchColorZero);
  500. scene.resolveFramebuffers(passState);
  501. var position;
  502. var object = view.pickFramebuffer.end(context);
  503. if (scene.context.depthTexture) {
  504. var numFrustums = view.frustumCommandsList.length;
  505. for (var i = 0; i < numFrustums; ++i) {
  506. var pickDepth = picking.getPickDepth(scene, i);
  507. var depth = pickDepth.getDepth(context, 0, 0);
  508. if (depth > 0.0 && depth < 1.0) {
  509. var renderedFrustum = view.frustumCommandsList[i];
  510. var near = renderedFrustum.near * (i !== 0 ? scene.opaqueFrustumNearOffset : 1.0);
  511. var far = renderedFrustum.far;
  512. var distance = near + depth * (far - near);
  513. position = Ray.getPoint(ray, distance);
  514. break;
  515. }
  516. }
  517. }
  518. scene.view = scene.defaultView;
  519. context.endFrame();
  520. if (defined(object) || defined(position)) {
  521. return {
  522. object : object,
  523. position : position,
  524. exclude : (!defined(position) && requirePosition) || isExcluded(object, objectsToExclude)
  525. };
  526. }
  527. }
  528. function getRayIntersections(picking, scene, ray, limit, objectsToExclude, width, requirePosition, mostDetailed) {
  529. var pickCallback = function() {
  530. return getRayIntersection(picking, scene, ray, objectsToExclude, width, requirePosition, mostDetailed);
  531. };
  532. return drillPick(limit, pickCallback);
  533. }
  534. function pickFromRay(picking, scene, ray, objectsToExclude, width, requirePosition, mostDetailed) {
  535. var results = getRayIntersections(picking, scene, ray, 1, objectsToExclude, width, requirePosition, mostDetailed);
  536. if (results.length > 0) {
  537. return results[0];
  538. }
  539. }
  540. function drillPickFromRay(picking, scene, ray, limit, objectsToExclude, width, requirePosition, mostDetailed) {
  541. return getRayIntersections(picking, scene, ray, limit, objectsToExclude, width, requirePosition, mostDetailed);
  542. }
  543. function deferPromiseUntilPostRender(scene, promise) {
  544. // Resolve promise after scene's postRender in case entities are created when the promise resolves.
  545. // Entities can't be created between viewer._onTick and viewer._postRender.
  546. var deferred = when.defer();
  547. promise.then(function(result) {
  548. var removeCallback = scene.postRender.addEventListener(function() {
  549. deferred.resolve(result);
  550. removeCallback();
  551. });
  552. scene.requestRender();
  553. }).otherwise(function(error) {
  554. deferred.reject(error);
  555. });
  556. return deferred.promise;
  557. }
  558. Picking.prototype.pickFromRay = function(scene, ray, objectsToExclude, width) {
  559. //>>includeStart('debug', pragmas.debug);
  560. Check.defined('ray', ray);
  561. if (scene.mode !== SceneMode.SCENE3D) {
  562. throw new DeveloperError('Ray intersections are only supported in 3D mode.');
  563. }
  564. //>>includeEnd('debug');
  565. return pickFromRay(this, scene, ray, objectsToExclude, width, false, false);
  566. };
  567. Picking.prototype.drillPickFromRay = function(scene, ray, limit, objectsToExclude, width) {
  568. //>>includeStart('debug', pragmas.debug);
  569. Check.defined('ray', ray);
  570. if (scene.mode !== SceneMode.SCENE3D) {
  571. throw new DeveloperError('Ray intersections are only supported in 3D mode.');
  572. }
  573. //>>includeEnd('debug');
  574. return drillPickFromRay(this, scene, ray, limit, objectsToExclude, width,false, false);
  575. };
  576. Picking.prototype.pickFromRayMostDetailed = function(scene, ray, objectsToExclude, width) {
  577. //>>includeStart('debug', pragmas.debug);
  578. Check.defined('ray', ray);
  579. if (scene.mode !== SceneMode.SCENE3D) {
  580. throw new DeveloperError('Ray intersections are only supported in 3D mode.');
  581. }
  582. //>>includeEnd('debug');
  583. var that = this;
  584. ray = Ray.clone(ray);
  585. objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
  586. return deferPromiseUntilPostRender(scene, launchMostDetailedRayPick(that, scene, ray, objectsToExclude, width, function() {
  587. return pickFromRay(that, scene, ray, objectsToExclude, width, false, true);
  588. }));
  589. };
  590. Picking.prototype.drillPickFromRayMostDetailed = function(scene, ray, limit, objectsToExclude, width) {
  591. //>>includeStart('debug', pragmas.debug);
  592. Check.defined('ray', ray);
  593. if (scene.mode !== SceneMode.SCENE3D) {
  594. throw new DeveloperError('Ray intersections are only supported in 3D mode.');
  595. }
  596. //>>includeEnd('debug');
  597. var that = this;
  598. ray = Ray.clone(ray);
  599. objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
  600. return deferPromiseUntilPostRender(scene, launchMostDetailedRayPick(that, scene, ray, objectsToExclude, width, function() {
  601. return drillPickFromRay(that, scene, ray, limit, objectsToExclude, width, false, true);
  602. }));
  603. };
  604. var scratchSurfacePosition = new Cartesian3();
  605. var scratchSurfaceNormal = new Cartesian3();
  606. var scratchSurfaceRay = new Ray();
  607. var scratchCartographic = new Cartographic();
  608. function getRayForSampleHeight(scene, cartographic) {
  609. var globe = scene.globe;
  610. var ellipsoid = defined(globe) ? globe.ellipsoid : scene.mapProjection.ellipsoid;
  611. var height = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  612. var surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(cartographic, scratchSurfaceNormal);
  613. var surfacePosition = Cartographic.toCartesian(cartographic, ellipsoid, scratchSurfacePosition);
  614. var surfaceRay = scratchSurfaceRay;
  615. surfaceRay.origin = surfacePosition;
  616. surfaceRay.direction = surfaceNormal;
  617. var ray = new Ray();
  618. Ray.getPoint(surfaceRay, height, ray.origin);
  619. Cartesian3.negate(surfaceNormal, ray.direction);
  620. return ray;
  621. }
  622. function getRayForClampToHeight(scene, cartesian) {
  623. var globe = scene.globe;
  624. var ellipsoid = defined(globe) ? globe.ellipsoid : scene.mapProjection.ellipsoid;
  625. var cartographic = Cartographic.fromCartesian(cartesian, ellipsoid, scratchCartographic);
  626. return getRayForSampleHeight(scene, cartographic);
  627. }
  628. function getHeightFromCartesian(scene, cartesian) {
  629. var globe = scene.globe;
  630. var ellipsoid = defined(globe) ? globe.ellipsoid : scene.mapProjection.ellipsoid;
  631. var cartographic = Cartographic.fromCartesian(cartesian, ellipsoid, scratchCartographic);
  632. return cartographic.height;
  633. }
  634. function sampleHeightMostDetailed(picking, scene, cartographic, objectsToExclude, width) {
  635. var ray = getRayForSampleHeight(scene, cartographic);
  636. return launchMostDetailedRayPick(picking, scene, ray, objectsToExclude, width, function() {
  637. var pickResult = pickFromRay(picking, scene, ray, objectsToExclude, width, true, true);
  638. if (defined(pickResult)) {
  639. return getHeightFromCartesian(scene, pickResult.position);
  640. }
  641. });
  642. }
  643. function clampToHeightMostDetailed(picking, scene, cartesian, objectsToExclude, width, result) {
  644. var ray = getRayForClampToHeight(scene, cartesian);
  645. return launchMostDetailedRayPick(picking, scene, ray, objectsToExclude, width, function() {
  646. var pickResult = pickFromRay(picking, scene, ray, objectsToExclude, width, true, true);
  647. if (defined(pickResult)) {
  648. return Cartesian3.clone(pickResult.position, result);
  649. }
  650. });
  651. }
  652. Picking.prototype.sampleHeight = function(scene, position, objectsToExclude, width) {
  653. //>>includeStart('debug', pragmas.debug);
  654. Check.defined('position', position);
  655. if (scene.mode !== SceneMode.SCENE3D) {
  656. throw new DeveloperError('sampleHeight is only supported in 3D mode.');
  657. }
  658. if (!scene.sampleHeightSupported) {
  659. throw new DeveloperError('sampleHeight requires depth texture support. Check sampleHeightSupported.');
  660. }
  661. //>>includeEnd('debug');
  662. var ray = getRayForSampleHeight(scene, position);
  663. var pickResult = pickFromRay(this, scene, ray, objectsToExclude, width, true, false);
  664. if (defined(pickResult)) {
  665. return getHeightFromCartesian(scene, pickResult.position);
  666. }
  667. };
  668. Picking.prototype.clampToHeight = function(scene, cartesian, objectsToExclude, width, result) {
  669. //>>includeStart('debug', pragmas.debug);
  670. Check.defined('cartesian', cartesian);
  671. if (scene.mode !== SceneMode.SCENE3D) {
  672. throw new DeveloperError('clampToHeight is only supported in 3D mode.');
  673. }
  674. if (!scene.clampToHeightSupported) {
  675. throw new DeveloperError('clampToHeight requires depth texture support. Check clampToHeightSupported.');
  676. }
  677. //>>includeEnd('debug');
  678. var ray = getRayForClampToHeight(scene, cartesian);
  679. var pickResult = pickFromRay(this, scene, ray, objectsToExclude, width, true, false);
  680. if (defined(pickResult)) {
  681. return Cartesian3.clone(pickResult.position, result);
  682. }
  683. };
  684. Picking.prototype.sampleHeightMostDetailed = function(scene, positions, objectsToExclude, width) {
  685. //>>includeStart('debug', pragmas.debug);
  686. Check.defined('positions', positions);
  687. if (scene.mode !== SceneMode.SCENE3D) {
  688. throw new DeveloperError('sampleHeightMostDetailed is only supported in 3D mode.');
  689. }
  690. if (!scene.sampleHeightSupported) {
  691. throw new DeveloperError('sampleHeightMostDetailed requires depth texture support. Check sampleHeightSupported.');
  692. }
  693. //>>includeEnd('debug');
  694. objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
  695. var length = positions.length;
  696. var promises = new Array(length);
  697. for (var i = 0; i < length; ++i) {
  698. promises[i] = sampleHeightMostDetailed(this, scene, positions[i], objectsToExclude, width);
  699. }
  700. return deferPromiseUntilPostRender(scene, when.all(promises).then(function(heights) {
  701. var length = heights.length;
  702. for (var i = 0; i < length; ++i) {
  703. positions[i].height = heights[i];
  704. }
  705. return positions;
  706. }));
  707. };
  708. Picking.prototype.clampToHeightMostDetailed = function(scene, cartesians, objectsToExclude, width) {
  709. //>>includeStart('debug', pragmas.debug);
  710. Check.defined('cartesians', cartesians);
  711. if (scene.mode !== SceneMode.SCENE3D) {
  712. throw new DeveloperError('clampToHeightMostDetailed is only supported in 3D mode.');
  713. }
  714. if (!scene.clampToHeightSupported) {
  715. throw new DeveloperError('clampToHeightMostDetailed requires depth texture support. Check clampToHeightSupported.');
  716. }
  717. //>>includeEnd('debug');
  718. objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
  719. var length = cartesians.length;
  720. var promises = new Array(length);
  721. for (var i = 0; i < length; ++i) {
  722. promises[i] = clampToHeightMostDetailed(this, scene, cartesians[i], objectsToExclude, width, cartesians[i]);
  723. }
  724. return deferPromiseUntilPostRender(scene, when.all(promises).then(function(clampedCartesians) {
  725. var length = clampedCartesians.length;
  726. for (var i = 0; i < length; ++i) {
  727. cartesians[i] = clampedCartesians[i];
  728. }
  729. return cartesians;
  730. }));
  731. };
  732. Picking.prototype.destroy = function() {
  733. this._pickOffscreenView = this._pickOffscreenView && this._pickOffscreenView.destroy();
  734. };
  735. export default Picking;