Picking.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  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 Camera from "./Camera.js";
  19. import Cesium3DTileFeature from "./Cesium3DTileFeature.js";
  20. import Cesium3DTilePass from "./Cesium3DTilePass.js";
  21. import Cesium3DTilePassState from "./Cesium3DTilePassState.js";
  22. import PickDepth from "./PickDepth.js";
  23. import PrimitiveCollection from "./PrimitiveCollection.js";
  24. import SceneMode from "./SceneMode.js";
  25. import SceneTransforms from "./SceneTransforms.js";
  26. import View from "./View.js";
  27. const offscreenDefaultWidth = 0.1;
  28. const mostDetailedPreloadTilesetPassState = new Cesium3DTilePassState({
  29. pass: Cesium3DTilePass.MOST_DETAILED_PRELOAD,
  30. });
  31. const mostDetailedPickTilesetPassState = new Cesium3DTilePassState({
  32. pass: Cesium3DTilePass.MOST_DETAILED_PICK,
  33. });
  34. const pickTilesetPassState = new Cesium3DTilePassState({
  35. pass: Cesium3DTilePass.PICK,
  36. });
  37. /**
  38. * @private
  39. */
  40. function Picking(scene) {
  41. this._mostDetailedRayPicks = [];
  42. this.pickRenderStateCache = {};
  43. this._pickPositionCache = {};
  44. this._pickPositionCacheDirty = false;
  45. const pickOffscreenViewport = new BoundingRectangle(0, 0, 1, 1);
  46. const pickOffscreenCamera = new Camera(scene);
  47. pickOffscreenCamera.frustum = new OrthographicFrustum({
  48. width: offscreenDefaultWidth,
  49. aspectRatio: 1.0,
  50. near: 0.1,
  51. });
  52. this._pickOffscreenView = new View(
  53. scene,
  54. pickOffscreenCamera,
  55. pickOffscreenViewport
  56. );
  57. }
  58. Picking.prototype.update = function () {
  59. this._pickPositionCacheDirty = true;
  60. };
  61. Picking.prototype.getPickDepth = function (scene, index) {
  62. const pickDepths = scene.view.pickDepths;
  63. let pickDepth = pickDepths[index];
  64. if (!defined(pickDepth)) {
  65. pickDepth = new PickDepth();
  66. pickDepths[index] = pickDepth;
  67. }
  68. return pickDepth;
  69. };
  70. const scratchOrthoPickingFrustum = new OrthographicOffCenterFrustum();
  71. const scratchOrthoOrigin = new Cartesian3();
  72. const scratchOrthoDirection = new Cartesian3();
  73. const scratchOrthoPixelSize = new Cartesian2();
  74. const scratchOrthoPickVolumeMatrix4 = new Matrix4();
  75. function getPickOrthographicCullingVolume(
  76. scene,
  77. drawingBufferPosition,
  78. width,
  79. height,
  80. viewport
  81. ) {
  82. const camera = scene.camera;
  83. let frustum = camera.frustum;
  84. const offCenterFrustum = frustum.offCenterFrustum;
  85. if (defined(offCenterFrustum)) {
  86. frustum = offCenterFrustum;
  87. }
  88. let x = (2.0 * (drawingBufferPosition.x - viewport.x)) / viewport.width - 1.0;
  89. x *= (frustum.right - frustum.left) * 0.5;
  90. let y =
  91. (2.0 * (viewport.height - drawingBufferPosition.y - viewport.y)) /
  92. viewport.height -
  93. 1.0;
  94. y *= (frustum.top - frustum.bottom) * 0.5;
  95. const transform = Matrix4.clone(
  96. camera.transform,
  97. scratchOrthoPickVolumeMatrix4
  98. );
  99. camera._setTransform(Matrix4.IDENTITY);
  100. const origin = Cartesian3.clone(camera.position, scratchOrthoOrigin);
  101. Cartesian3.multiplyByScalar(camera.right, x, scratchOrthoDirection);
  102. Cartesian3.add(scratchOrthoDirection, origin, origin);
  103. Cartesian3.multiplyByScalar(camera.up, y, scratchOrthoDirection);
  104. Cartesian3.add(scratchOrthoDirection, origin, origin);
  105. camera._setTransform(transform);
  106. if (scene.mode === SceneMode.SCENE2D) {
  107. Cartesian3.fromElements(origin.z, origin.x, origin.y, origin);
  108. }
  109. const pixelSize = frustum.getPixelDimensions(
  110. viewport.width,
  111. viewport.height,
  112. 1.0,
  113. 1.0,
  114. scratchOrthoPixelSize
  115. );
  116. const ortho = scratchOrthoPickingFrustum;
  117. ortho.right = pixelSize.x * 0.5;
  118. ortho.left = -ortho.right;
  119. ortho.top = pixelSize.y * 0.5;
  120. ortho.bottom = -ortho.top;
  121. ortho.near = frustum.near;
  122. ortho.far = frustum.far;
  123. return ortho.computeCullingVolume(origin, camera.directionWC, camera.upWC);
  124. }
  125. const scratchPerspPickingFrustum = new PerspectiveOffCenterFrustum();
  126. const scratchPerspPixelSize = new Cartesian2();
  127. function getPickPerspectiveCullingVolume(
  128. scene,
  129. drawingBufferPosition,
  130. width,
  131. height,
  132. viewport
  133. ) {
  134. const camera = scene.camera;
  135. const frustum = camera.frustum;
  136. const near = frustum.near;
  137. const tanPhi = Math.tan(frustum.fovy * 0.5);
  138. const tanTheta = frustum.aspectRatio * tanPhi;
  139. const x =
  140. (2.0 * (drawingBufferPosition.x - viewport.x)) / viewport.width - 1.0;
  141. const y =
  142. (2.0 * (viewport.height - drawingBufferPosition.y - viewport.y)) /
  143. viewport.height -
  144. 1.0;
  145. const xDir = x * near * tanTheta;
  146. const yDir = y * near * tanPhi;
  147. const pixelSize = frustum.getPixelDimensions(
  148. viewport.width,
  149. viewport.height,
  150. 1.0,
  151. 1.0,
  152. scratchPerspPixelSize
  153. );
  154. const pickWidth = pixelSize.x * width * 0.5;
  155. const pickHeight = pixelSize.y * height * 0.5;
  156. const offCenter = scratchPerspPickingFrustum;
  157. offCenter.top = yDir + pickHeight;
  158. offCenter.bottom = yDir - pickHeight;
  159. offCenter.right = xDir + pickWidth;
  160. offCenter.left = xDir - pickWidth;
  161. offCenter.near = near;
  162. offCenter.far = frustum.far;
  163. return offCenter.computeCullingVolume(
  164. camera.positionWC,
  165. camera.directionWC,
  166. camera.upWC
  167. );
  168. }
  169. function getPickCullingVolume(
  170. scene,
  171. drawingBufferPosition,
  172. width,
  173. height,
  174. viewport
  175. ) {
  176. const frustum = scene.camera.frustum;
  177. if (
  178. frustum instanceof OrthographicFrustum ||
  179. frustum instanceof OrthographicOffCenterFrustum
  180. ) {
  181. return getPickOrthographicCullingVolume(
  182. scene,
  183. drawingBufferPosition,
  184. width,
  185. height,
  186. viewport
  187. );
  188. }
  189. return getPickPerspectiveCullingVolume(
  190. scene,
  191. drawingBufferPosition,
  192. width,
  193. height,
  194. viewport
  195. );
  196. }
  197. // pick rectangle width and height, assumed odd
  198. let scratchRectangleWidth = 3.0;
  199. let scratchRectangleHeight = 3.0;
  200. let scratchRectangle = new BoundingRectangle(
  201. 0.0,
  202. 0.0,
  203. scratchRectangleWidth,
  204. scratchRectangleHeight
  205. );
  206. const scratchPosition = new Cartesian2();
  207. const scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0);
  208. Picking.prototype.pick = function (scene, windowPosition, width, height) {
  209. //>>includeStart('debug', pragmas.debug);
  210. if (!defined(windowPosition)) {
  211. throw new DeveloperError("windowPosition is undefined.");
  212. }
  213. //>>includeEnd('debug');
  214. scratchRectangleWidth = defaultValue(width, 3.0);
  215. scratchRectangleHeight = defaultValue(height, scratchRectangleWidth);
  216. const context = scene.context;
  217. const us = context.uniformState;
  218. const frameState = scene.frameState;
  219. const view = scene.defaultView;
  220. scene.view = view;
  221. const viewport = view.viewport;
  222. viewport.x = 0;
  223. viewport.y = 0;
  224. viewport.width = context.drawingBufferWidth;
  225. viewport.height = context.drawingBufferHeight;
  226. let passState = view.passState;
  227. passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
  228. const drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(
  229. scene,
  230. windowPosition,
  231. scratchPosition
  232. );
  233. scene.jobScheduler.disableThisFrame();
  234. scene.updateFrameState();
  235. frameState.cullingVolume = getPickCullingVolume(
  236. scene,
  237. drawingBufferPosition,
  238. scratchRectangleWidth,
  239. scratchRectangleHeight,
  240. viewport
  241. );
  242. frameState.invertClassification = false;
  243. frameState.passes.pick = true;
  244. frameState.tilesetPassState = pickTilesetPassState;
  245. us.update(frameState);
  246. scene.updateEnvironment();
  247. scratchRectangle.x =
  248. drawingBufferPosition.x - (scratchRectangleWidth - 1.0) * 0.5;
  249. scratchRectangle.y =
  250. scene.drawingBufferHeight -
  251. drawingBufferPosition.y -
  252. (scratchRectangleHeight - 1.0) * 0.5;
  253. scratchRectangle.width = scratchRectangleWidth;
  254. scratchRectangle.height = scratchRectangleHeight;
  255. passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
  256. scene.updateAndExecuteCommands(passState, scratchColorZero);
  257. scene.resolveFramebuffers(passState);
  258. const object = view.pickFramebuffer.end(scratchRectangle);
  259. context.endFrame();
  260. return object;
  261. };
  262. function renderTranslucentDepthForPick(scene, drawingBufferPosition) {
  263. // PERFORMANCE_IDEA: render translucent only and merge with the previous frame
  264. const context = scene.context;
  265. const frameState = scene.frameState;
  266. const environmentState = scene.environmentState;
  267. const view = scene.defaultView;
  268. scene.view = view;
  269. const viewport = view.viewport;
  270. viewport.x = 0;
  271. viewport.y = 0;
  272. viewport.width = context.drawingBufferWidth;
  273. viewport.height = context.drawingBufferHeight;
  274. let passState = view.passState;
  275. passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
  276. scene.clearPasses(frameState.passes);
  277. frameState.passes.pick = true;
  278. frameState.passes.depth = true;
  279. frameState.cullingVolume = getPickCullingVolume(
  280. scene,
  281. drawingBufferPosition,
  282. 1,
  283. 1,
  284. viewport
  285. );
  286. frameState.tilesetPassState = pickTilesetPassState;
  287. scene.updateEnvironment();
  288. environmentState.renderTranslucentDepthForPick = true;
  289. passState = view.pickDepthFramebuffer.update(
  290. context,
  291. drawingBufferPosition,
  292. viewport
  293. );
  294. scene.updateAndExecuteCommands(passState, scratchColorZero);
  295. scene.resolveFramebuffers(passState);
  296. context.endFrame();
  297. }
  298. const scratchPerspectiveFrustum = new PerspectiveFrustum();
  299. const scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
  300. const scratchOrthographicFrustum = new OrthographicFrustum();
  301. const scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum();
  302. Picking.prototype.pickPositionWorldCoordinates = function (
  303. scene,
  304. windowPosition,
  305. result
  306. ) {
  307. if (!scene.useDepthPicking) {
  308. return undefined;
  309. }
  310. //>>includeStart('debug', pragmas.debug);
  311. if (!defined(windowPosition)) {
  312. throw new DeveloperError("windowPosition is undefined.");
  313. }
  314. if (!scene.context.depthTexture) {
  315. throw new DeveloperError(
  316. "Picking from the depth buffer is not supported. Check pickPositionSupported."
  317. );
  318. }
  319. //>>includeEnd('debug');
  320. const cacheKey = windowPosition.toString();
  321. if (this._pickPositionCacheDirty) {
  322. this._pickPositionCache = {};
  323. this._pickPositionCacheDirty = false;
  324. } else if (this._pickPositionCache.hasOwnProperty(cacheKey)) {
  325. return Cartesian3.clone(this._pickPositionCache[cacheKey], result);
  326. }
  327. const frameState = scene.frameState;
  328. const context = scene.context;
  329. const uniformState = context.uniformState;
  330. const view = scene.defaultView;
  331. scene.view = view;
  332. const drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(
  333. scene,
  334. windowPosition,
  335. scratchPosition
  336. );
  337. if (scene.pickTranslucentDepth) {
  338. renderTranslucentDepthForPick(scene, drawingBufferPosition);
  339. } else {
  340. scene.updateFrameState();
  341. uniformState.update(frameState);
  342. scene.updateEnvironment();
  343. }
  344. drawingBufferPosition.y = scene.drawingBufferHeight - drawingBufferPosition.y;
  345. const camera = scene.camera;
  346. // Create a working frustum from the original camera frustum.
  347. let frustum;
  348. if (defined(camera.frustum.fov)) {
  349. frustum = camera.frustum.clone(scratchPerspectiveFrustum);
  350. } else if (defined(camera.frustum.infiniteProjectionMatrix)) {
  351. frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
  352. } else if (defined(camera.frustum.width)) {
  353. frustum = camera.frustum.clone(scratchOrthographicFrustum);
  354. } else {
  355. frustum = camera.frustum.clone(scratchOrthographicOffCenterFrustum);
  356. }
  357. const frustumCommandsList = view.frustumCommandsList;
  358. const numFrustums = frustumCommandsList.length;
  359. for (let i = 0; i < numFrustums; ++i) {
  360. const pickDepth = this.getPickDepth(scene, i);
  361. const depth = pickDepth.getDepth(
  362. context,
  363. drawingBufferPosition.x,
  364. drawingBufferPosition.y
  365. );
  366. if (!defined(depth)) {
  367. continue;
  368. }
  369. if (depth > 0.0 && depth < 1.0) {
  370. const renderedFrustum = frustumCommandsList[i];
  371. let height2D;
  372. if (scene.mode === SceneMode.SCENE2D) {
  373. height2D = camera.position.z;
  374. camera.position.z = height2D - renderedFrustum.near + 1.0;
  375. frustum.far = Math.max(1.0, renderedFrustum.far - renderedFrustum.near);
  376. frustum.near = 1.0;
  377. uniformState.update(frameState);
  378. uniformState.updateFrustum(frustum);
  379. } else {
  380. frustum.near =
  381. renderedFrustum.near *
  382. (i !== 0 ? scene.opaqueFrustumNearOffset : 1.0);
  383. frustum.far = renderedFrustum.far;
  384. uniformState.updateFrustum(frustum);
  385. }
  386. result = SceneTransforms.drawingBufferToWgs84Coordinates(
  387. scene,
  388. drawingBufferPosition,
  389. depth,
  390. result
  391. );
  392. if (scene.mode === SceneMode.SCENE2D) {
  393. camera.position.z = height2D;
  394. uniformState.update(frameState);
  395. }
  396. this._pickPositionCache[cacheKey] = Cartesian3.clone(result);
  397. return result;
  398. }
  399. }
  400. this._pickPositionCache[cacheKey] = undefined;
  401. return undefined;
  402. };
  403. const scratchPickPositionCartographic = new Cartographic();
  404. Picking.prototype.pickPosition = function (scene, windowPosition, result) {
  405. result = this.pickPositionWorldCoordinates(scene, windowPosition, result);
  406. if (defined(result) && scene.mode !== SceneMode.SCENE3D) {
  407. Cartesian3.fromElements(result.y, result.z, result.x, result);
  408. const projection = scene.mapProjection;
  409. const ellipsoid = projection.ellipsoid;
  410. const cart = projection.unproject(result, scratchPickPositionCartographic);
  411. ellipsoid.cartographicToCartesian(cart, result);
  412. }
  413. return result;
  414. };
  415. function drillPick(limit, pickCallback) {
  416. // PERFORMANCE_IDEA: This function calls each primitive's update for each pass. Instead
  417. // we could update the primitive once, and then just execute their commands for each pass,
  418. // and cull commands for picked primitives. e.g., base on the command's owner.
  419. let i;
  420. let attributes;
  421. const result = [];
  422. const pickedPrimitives = [];
  423. const pickedAttributes = [];
  424. const pickedFeatures = [];
  425. if (!defined(limit)) {
  426. limit = Number.MAX_VALUE;
  427. }
  428. let pickedResult = pickCallback();
  429. while (defined(pickedResult)) {
  430. const object = pickedResult.object;
  431. const position = pickedResult.position;
  432. const exclude = pickedResult.exclude;
  433. if (defined(position) && !defined(object)) {
  434. result.push(pickedResult);
  435. break;
  436. }
  437. if (!defined(object) || !defined(object.primitive)) {
  438. break;
  439. }
  440. if (!exclude) {
  441. result.push(pickedResult);
  442. if (0 >= --limit) {
  443. break;
  444. }
  445. }
  446. const primitive = object.primitive;
  447. let hasShowAttribute = false;
  448. // If the picked object has a show attribute, use it.
  449. if (typeof primitive.getGeometryInstanceAttributes === "function") {
  450. if (defined(object.id)) {
  451. attributes = primitive.getGeometryInstanceAttributes(object.id);
  452. if (defined(attributes) && defined(attributes.show)) {
  453. hasShowAttribute = true;
  454. attributes.show = ShowGeometryInstanceAttribute.toValue(
  455. false,
  456. attributes.show
  457. );
  458. pickedAttributes.push(attributes);
  459. }
  460. }
  461. }
  462. if (object instanceof Cesium3DTileFeature) {
  463. hasShowAttribute = true;
  464. object.show = false;
  465. pickedFeatures.push(object);
  466. }
  467. // Otherwise, hide the entire primitive
  468. if (!hasShowAttribute) {
  469. primitive.show = false;
  470. pickedPrimitives.push(primitive);
  471. }
  472. pickedResult = pickCallback();
  473. }
  474. // Unhide everything we hid while drill picking
  475. for (i = 0; i < pickedPrimitives.length; ++i) {
  476. pickedPrimitives[i].show = true;
  477. }
  478. for (i = 0; i < pickedAttributes.length; ++i) {
  479. attributes = pickedAttributes[i];
  480. attributes.show = ShowGeometryInstanceAttribute.toValue(
  481. true,
  482. attributes.show
  483. );
  484. }
  485. for (i = 0; i < pickedFeatures.length; ++i) {
  486. pickedFeatures[i].show = true;
  487. }
  488. return result;
  489. }
  490. Picking.prototype.drillPick = function (
  491. scene,
  492. windowPosition,
  493. limit,
  494. width,
  495. height
  496. ) {
  497. const that = this;
  498. const pickCallback = function () {
  499. const object = that.pick(scene, windowPosition, width, height);
  500. if (defined(object)) {
  501. return {
  502. object: object,
  503. position: undefined,
  504. exclude: false,
  505. };
  506. }
  507. };
  508. const objects = drillPick(limit, pickCallback);
  509. return objects.map(function (element) {
  510. return element.object;
  511. });
  512. };
  513. const scratchRight = new Cartesian3();
  514. const scratchUp = new Cartesian3();
  515. function MostDetailedRayPick(ray, width, tilesets) {
  516. this.ray = ray;
  517. this.width = width;
  518. this.tilesets = tilesets;
  519. this.ready = false;
  520. const pick = this;
  521. this.promise = new Promise((resolve) => {
  522. pick._completePick = () => {
  523. resolve();
  524. };
  525. });
  526. }
  527. function updateOffscreenCameraFromRay(picking, ray, width, camera) {
  528. const direction = ray.direction;
  529. const orthogonalAxis = Cartesian3.mostOrthogonalAxis(direction, scratchRight);
  530. const right = Cartesian3.cross(direction, orthogonalAxis, scratchRight);
  531. const up = Cartesian3.cross(direction, right, scratchUp);
  532. camera.position = ray.origin;
  533. camera.direction = direction;
  534. camera.up = up;
  535. camera.right = right;
  536. camera.frustum.width = defaultValue(width, offscreenDefaultWidth);
  537. return camera.frustum.computeCullingVolume(
  538. camera.positionWC,
  539. camera.directionWC,
  540. camera.upWC
  541. );
  542. }
  543. function updateMostDetailedRayPick(picking, scene, rayPick) {
  544. const frameState = scene.frameState;
  545. const ray = rayPick.ray;
  546. const width = rayPick.width;
  547. const tilesets = rayPick.tilesets;
  548. const camera = picking._pickOffscreenView.camera;
  549. const cullingVolume = updateOffscreenCameraFromRay(
  550. picking,
  551. ray,
  552. width,
  553. camera
  554. );
  555. const tilesetPassState = mostDetailedPreloadTilesetPassState;
  556. tilesetPassState.camera = camera;
  557. tilesetPassState.cullingVolume = cullingVolume;
  558. let ready = true;
  559. const tilesetsLength = tilesets.length;
  560. for (let i = 0; i < tilesetsLength; ++i) {
  561. const tileset = tilesets[i];
  562. if (tileset.show && scene.primitives.contains(tileset)) {
  563. // Only update tilesets that are still contained in the scene's primitive collection and are still visible
  564. // Update tilesets continually until all tilesets are ready. This way tiles are never removed from the cache.
  565. tileset.updateForPass(frameState, tilesetPassState);
  566. ready = ready && tilesetPassState.ready;
  567. }
  568. }
  569. if (ready) {
  570. rayPick._completePick();
  571. }
  572. return ready;
  573. }
  574. Picking.prototype.updateMostDetailedRayPicks = function (scene) {
  575. // Modifies array during iteration
  576. const rayPicks = this._mostDetailedRayPicks;
  577. for (let i = 0; i < rayPicks.length; ++i) {
  578. if (updateMostDetailedRayPick(this, scene, rayPicks[i])) {
  579. rayPicks.splice(i--, 1);
  580. }
  581. }
  582. };
  583. function getTilesets(primitives, objectsToExclude, tilesets) {
  584. const length = primitives.length;
  585. for (let i = 0; i < length; ++i) {
  586. const primitive = primitives.get(i);
  587. if (primitive.show) {
  588. if (defined(primitive.isCesium3DTileset)) {
  589. if (
  590. !defined(objectsToExclude) ||
  591. objectsToExclude.indexOf(primitive) === -1
  592. ) {
  593. tilesets.push(primitive);
  594. }
  595. } else if (primitive instanceof PrimitiveCollection) {
  596. getTilesets(primitive, objectsToExclude, tilesets);
  597. }
  598. }
  599. }
  600. }
  601. function launchMostDetailedRayPick(
  602. picking,
  603. scene,
  604. ray,
  605. objectsToExclude,
  606. width,
  607. callback
  608. ) {
  609. const tilesets = [];
  610. getTilesets(scene.primitives, objectsToExclude, tilesets);
  611. if (tilesets.length === 0) {
  612. return Promise.resolve(callback());
  613. }
  614. const rayPick = new MostDetailedRayPick(ray, width, tilesets);
  615. picking._mostDetailedRayPicks.push(rayPick);
  616. return rayPick.promise.then(function () {
  617. return callback();
  618. });
  619. }
  620. function isExcluded(object, objectsToExclude) {
  621. if (
  622. !defined(object) ||
  623. !defined(objectsToExclude) ||
  624. objectsToExclude.length === 0
  625. ) {
  626. return false;
  627. }
  628. return (
  629. objectsToExclude.indexOf(object) > -1 ||
  630. objectsToExclude.indexOf(object.primitive) > -1 ||
  631. objectsToExclude.indexOf(object.id) > -1
  632. );
  633. }
  634. function getRayIntersection(
  635. picking,
  636. scene,
  637. ray,
  638. objectsToExclude,
  639. width,
  640. requirePosition,
  641. mostDetailed
  642. ) {
  643. const context = scene.context;
  644. const uniformState = context.uniformState;
  645. const frameState = scene.frameState;
  646. const view = picking._pickOffscreenView;
  647. scene.view = view;
  648. updateOffscreenCameraFromRay(picking, ray, width, view.camera);
  649. scratchRectangle = BoundingRectangle.clone(view.viewport, scratchRectangle);
  650. const passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
  651. scene.jobScheduler.disableThisFrame();
  652. scene.updateFrameState();
  653. frameState.invertClassification = false;
  654. frameState.passes.pick = true;
  655. frameState.passes.offscreen = true;
  656. if (mostDetailed) {
  657. frameState.tilesetPassState = mostDetailedPickTilesetPassState;
  658. } else {
  659. frameState.tilesetPassState = pickTilesetPassState;
  660. }
  661. uniformState.update(frameState);
  662. scene.updateEnvironment();
  663. scene.updateAndExecuteCommands(passState, scratchColorZero);
  664. scene.resolveFramebuffers(passState);
  665. let position;
  666. const object = view.pickFramebuffer.end(scratchRectangle);
  667. if (scene.context.depthTexture) {
  668. const numFrustums = view.frustumCommandsList.length;
  669. for (let i = 0; i < numFrustums; ++i) {
  670. const pickDepth = picking.getPickDepth(scene, i);
  671. const depth = pickDepth.getDepth(context, 0, 0);
  672. if (!defined(depth)) {
  673. continue;
  674. }
  675. if (depth > 0.0 && depth < 1.0) {
  676. const renderedFrustum = view.frustumCommandsList[i];
  677. const near =
  678. renderedFrustum.near *
  679. (i !== 0 ? scene.opaqueFrustumNearOffset : 1.0);
  680. const far = renderedFrustum.far;
  681. const distance = near + depth * (far - near);
  682. position = Ray.getPoint(ray, distance);
  683. break;
  684. }
  685. }
  686. }
  687. scene.view = scene.defaultView;
  688. context.endFrame();
  689. if (defined(object) || defined(position)) {
  690. return {
  691. object: object,
  692. position: position,
  693. exclude:
  694. (!defined(position) && requirePosition) ||
  695. isExcluded(object, objectsToExclude),
  696. };
  697. }
  698. }
  699. function getRayIntersections(
  700. picking,
  701. scene,
  702. ray,
  703. limit,
  704. objectsToExclude,
  705. width,
  706. requirePosition,
  707. mostDetailed
  708. ) {
  709. const pickCallback = function () {
  710. return getRayIntersection(
  711. picking,
  712. scene,
  713. ray,
  714. objectsToExclude,
  715. width,
  716. requirePosition,
  717. mostDetailed
  718. );
  719. };
  720. return drillPick(limit, pickCallback);
  721. }
  722. function pickFromRay(
  723. picking,
  724. scene,
  725. ray,
  726. objectsToExclude,
  727. width,
  728. requirePosition,
  729. mostDetailed
  730. ) {
  731. const results = getRayIntersections(
  732. picking,
  733. scene,
  734. ray,
  735. 1,
  736. objectsToExclude,
  737. width,
  738. requirePosition,
  739. mostDetailed
  740. );
  741. if (results.length > 0) {
  742. return results[0];
  743. }
  744. }
  745. function drillPickFromRay(
  746. picking,
  747. scene,
  748. ray,
  749. limit,
  750. objectsToExclude,
  751. width,
  752. requirePosition,
  753. mostDetailed
  754. ) {
  755. return getRayIntersections(
  756. picking,
  757. scene,
  758. ray,
  759. limit,
  760. objectsToExclude,
  761. width,
  762. requirePosition,
  763. mostDetailed
  764. );
  765. }
  766. function deferPromiseUntilPostRender(scene, promise) {
  767. // Resolve promise after scene's postRender in case entities are created when the promise resolves.
  768. // Entities can't be created between viewer._onTick and viewer._postRender.
  769. return new Promise((resolve, reject) => {
  770. promise
  771. .then(function (result) {
  772. const removeCallback = scene.postRender.addEventListener(function () {
  773. removeCallback();
  774. resolve(result);
  775. });
  776. scene.requestRender();
  777. })
  778. .catch(function (error) {
  779. reject(error);
  780. });
  781. });
  782. }
  783. Picking.prototype.pickFromRay = function (scene, ray, objectsToExclude, width) {
  784. //>>includeStart('debug', pragmas.debug);
  785. Check.defined("ray", ray);
  786. if (scene.mode !== SceneMode.SCENE3D) {
  787. throw new DeveloperError(
  788. "Ray intersections are only supported in 3D mode."
  789. );
  790. }
  791. //>>includeEnd('debug');
  792. return pickFromRay(this, scene, ray, objectsToExclude, width, false, false);
  793. };
  794. Picking.prototype.drillPickFromRay = function (
  795. scene,
  796. ray,
  797. limit,
  798. objectsToExclude,
  799. width
  800. ) {
  801. //>>includeStart('debug', pragmas.debug);
  802. Check.defined("ray", ray);
  803. if (scene.mode !== SceneMode.SCENE3D) {
  804. throw new DeveloperError(
  805. "Ray intersections are only supported in 3D mode."
  806. );
  807. }
  808. //>>includeEnd('debug');
  809. return drillPickFromRay(
  810. this,
  811. scene,
  812. ray,
  813. limit,
  814. objectsToExclude,
  815. width,
  816. false,
  817. false
  818. );
  819. };
  820. Picking.prototype.pickFromRayMostDetailed = function (
  821. scene,
  822. ray,
  823. objectsToExclude,
  824. width
  825. ) {
  826. //>>includeStart('debug', pragmas.debug);
  827. Check.defined("ray", ray);
  828. if (scene.mode !== SceneMode.SCENE3D) {
  829. throw new DeveloperError(
  830. "Ray intersections are only supported in 3D mode."
  831. );
  832. }
  833. //>>includeEnd('debug');
  834. const that = this;
  835. ray = Ray.clone(ray);
  836. objectsToExclude = defined(objectsToExclude)
  837. ? objectsToExclude.slice()
  838. : objectsToExclude;
  839. return deferPromiseUntilPostRender(
  840. scene,
  841. launchMostDetailedRayPick(
  842. that,
  843. scene,
  844. ray,
  845. objectsToExclude,
  846. width,
  847. function () {
  848. return pickFromRay(
  849. that,
  850. scene,
  851. ray,
  852. objectsToExclude,
  853. width,
  854. false,
  855. true
  856. );
  857. }
  858. )
  859. );
  860. };
  861. Picking.prototype.drillPickFromRayMostDetailed = function (
  862. scene,
  863. ray,
  864. limit,
  865. objectsToExclude,
  866. width
  867. ) {
  868. //>>includeStart('debug', pragmas.debug);
  869. Check.defined("ray", ray);
  870. if (scene.mode !== SceneMode.SCENE3D) {
  871. throw new DeveloperError(
  872. "Ray intersections are only supported in 3D mode."
  873. );
  874. }
  875. //>>includeEnd('debug');
  876. const that = this;
  877. ray = Ray.clone(ray);
  878. objectsToExclude = defined(objectsToExclude)
  879. ? objectsToExclude.slice()
  880. : objectsToExclude;
  881. return deferPromiseUntilPostRender(
  882. scene,
  883. launchMostDetailedRayPick(
  884. that,
  885. scene,
  886. ray,
  887. objectsToExclude,
  888. width,
  889. function () {
  890. return drillPickFromRay(
  891. that,
  892. scene,
  893. ray,
  894. limit,
  895. objectsToExclude,
  896. width,
  897. false,
  898. true
  899. );
  900. }
  901. )
  902. );
  903. };
  904. const scratchSurfacePosition = new Cartesian3();
  905. const scratchSurfaceNormal = new Cartesian3();
  906. const scratchSurfaceRay = new Ray();
  907. const scratchCartographic = new Cartographic();
  908. function getRayForSampleHeight(scene, cartographic) {
  909. const globe = scene.globe;
  910. const ellipsoid = defined(globe)
  911. ? globe.ellipsoid
  912. : scene.mapProjection.ellipsoid;
  913. const height = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  914. const surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(
  915. cartographic,
  916. scratchSurfaceNormal
  917. );
  918. const surfacePosition = Cartographic.toCartesian(
  919. cartographic,
  920. ellipsoid,
  921. scratchSurfacePosition
  922. );
  923. const surfaceRay = scratchSurfaceRay;
  924. surfaceRay.origin = surfacePosition;
  925. surfaceRay.direction = surfaceNormal;
  926. const ray = new Ray();
  927. Ray.getPoint(surfaceRay, height, ray.origin);
  928. Cartesian3.negate(surfaceNormal, ray.direction);
  929. return ray;
  930. }
  931. function getRayForClampToHeight(scene, cartesian) {
  932. const globe = scene.globe;
  933. const ellipsoid = defined(globe)
  934. ? globe.ellipsoid
  935. : scene.mapProjection.ellipsoid;
  936. const cartographic = Cartographic.fromCartesian(
  937. cartesian,
  938. ellipsoid,
  939. scratchCartographic
  940. );
  941. return getRayForSampleHeight(scene, cartographic);
  942. }
  943. function getHeightFromCartesian(scene, cartesian) {
  944. const globe = scene.globe;
  945. const ellipsoid = defined(globe)
  946. ? globe.ellipsoid
  947. : scene.mapProjection.ellipsoid;
  948. const cartographic = Cartographic.fromCartesian(
  949. cartesian,
  950. ellipsoid,
  951. scratchCartographic
  952. );
  953. return cartographic.height;
  954. }
  955. function sampleHeightMostDetailed(
  956. picking,
  957. scene,
  958. cartographic,
  959. objectsToExclude,
  960. width
  961. ) {
  962. const ray = getRayForSampleHeight(scene, cartographic);
  963. return launchMostDetailedRayPick(
  964. picking,
  965. scene,
  966. ray,
  967. objectsToExclude,
  968. width,
  969. function () {
  970. const pickResult = pickFromRay(
  971. picking,
  972. scene,
  973. ray,
  974. objectsToExclude,
  975. width,
  976. true,
  977. true
  978. );
  979. if (defined(pickResult)) {
  980. return getHeightFromCartesian(scene, pickResult.position);
  981. }
  982. }
  983. );
  984. }
  985. function clampToHeightMostDetailed(
  986. picking,
  987. scene,
  988. cartesian,
  989. objectsToExclude,
  990. width,
  991. result
  992. ) {
  993. const ray = getRayForClampToHeight(scene, cartesian);
  994. return launchMostDetailedRayPick(
  995. picking,
  996. scene,
  997. ray,
  998. objectsToExclude,
  999. width,
  1000. function () {
  1001. const pickResult = pickFromRay(
  1002. picking,
  1003. scene,
  1004. ray,
  1005. objectsToExclude,
  1006. width,
  1007. true,
  1008. true
  1009. );
  1010. if (defined(pickResult)) {
  1011. return Cartesian3.clone(pickResult.position, result);
  1012. }
  1013. }
  1014. );
  1015. }
  1016. Picking.prototype.sampleHeight = function (
  1017. scene,
  1018. position,
  1019. objectsToExclude,
  1020. width
  1021. ) {
  1022. //>>includeStart('debug', pragmas.debug);
  1023. Check.defined("position", position);
  1024. if (scene.mode !== SceneMode.SCENE3D) {
  1025. throw new DeveloperError("sampleHeight is only supported in 3D mode.");
  1026. }
  1027. if (!scene.sampleHeightSupported) {
  1028. throw new DeveloperError(
  1029. "sampleHeight requires depth texture support. Check sampleHeightSupported."
  1030. );
  1031. }
  1032. //>>includeEnd('debug');
  1033. const ray = getRayForSampleHeight(scene, position);
  1034. const pickResult = pickFromRay(
  1035. this,
  1036. scene,
  1037. ray,
  1038. objectsToExclude,
  1039. width,
  1040. true,
  1041. false
  1042. );
  1043. if (defined(pickResult)) {
  1044. return getHeightFromCartesian(scene, pickResult.position);
  1045. }
  1046. };
  1047. Picking.prototype.clampToHeight = function (
  1048. scene,
  1049. cartesian,
  1050. objectsToExclude,
  1051. width,
  1052. result
  1053. ) {
  1054. //>>includeStart('debug', pragmas.debug);
  1055. Check.defined("cartesian", cartesian);
  1056. if (scene.mode !== SceneMode.SCENE3D) {
  1057. throw new DeveloperError("clampToHeight is only supported in 3D mode.");
  1058. }
  1059. if (!scene.clampToHeightSupported) {
  1060. throw new DeveloperError(
  1061. "clampToHeight requires depth texture support. Check clampToHeightSupported."
  1062. );
  1063. }
  1064. //>>includeEnd('debug');
  1065. const ray = getRayForClampToHeight(scene, cartesian);
  1066. const pickResult = pickFromRay(
  1067. this,
  1068. scene,
  1069. ray,
  1070. objectsToExclude,
  1071. width,
  1072. true,
  1073. false
  1074. );
  1075. if (defined(pickResult)) {
  1076. return Cartesian3.clone(pickResult.position, result);
  1077. }
  1078. };
  1079. Picking.prototype.sampleHeightMostDetailed = function (
  1080. scene,
  1081. positions,
  1082. objectsToExclude,
  1083. width
  1084. ) {
  1085. //>>includeStart('debug', pragmas.debug);
  1086. Check.defined("positions", positions);
  1087. if (scene.mode !== SceneMode.SCENE3D) {
  1088. throw new DeveloperError(
  1089. "sampleHeightMostDetailed is only supported in 3D mode."
  1090. );
  1091. }
  1092. if (!scene.sampleHeightSupported) {
  1093. throw new DeveloperError(
  1094. "sampleHeightMostDetailed requires depth texture support. Check sampleHeightSupported."
  1095. );
  1096. }
  1097. //>>includeEnd('debug');
  1098. objectsToExclude = defined(objectsToExclude)
  1099. ? objectsToExclude.slice()
  1100. : objectsToExclude;
  1101. const length = positions.length;
  1102. const promises = new Array(length);
  1103. for (let i = 0; i < length; ++i) {
  1104. promises[i] = sampleHeightMostDetailed(
  1105. this,
  1106. scene,
  1107. positions[i],
  1108. objectsToExclude,
  1109. width
  1110. );
  1111. }
  1112. return deferPromiseUntilPostRender(
  1113. scene,
  1114. Promise.all(promises).then(function (heights) {
  1115. const length = heights.length;
  1116. for (let i = 0; i < length; ++i) {
  1117. positions[i].height = heights[i];
  1118. }
  1119. return positions;
  1120. })
  1121. );
  1122. };
  1123. Picking.prototype.clampToHeightMostDetailed = function (
  1124. scene,
  1125. cartesians,
  1126. objectsToExclude,
  1127. width
  1128. ) {
  1129. //>>includeStart('debug', pragmas.debug);
  1130. Check.defined("cartesians", cartesians);
  1131. if (scene.mode !== SceneMode.SCENE3D) {
  1132. throw new DeveloperError(
  1133. "clampToHeightMostDetailed is only supported in 3D mode."
  1134. );
  1135. }
  1136. if (!scene.clampToHeightSupported) {
  1137. throw new DeveloperError(
  1138. "clampToHeightMostDetailed requires depth texture support. Check clampToHeightSupported."
  1139. );
  1140. }
  1141. //>>includeEnd('debug');
  1142. objectsToExclude = defined(objectsToExclude)
  1143. ? objectsToExclude.slice()
  1144. : objectsToExclude;
  1145. const length = cartesians.length;
  1146. const promises = new Array(length);
  1147. for (let i = 0; i < length; ++i) {
  1148. promises[i] = clampToHeightMostDetailed(
  1149. this,
  1150. scene,
  1151. cartesians[i],
  1152. objectsToExclude,
  1153. width,
  1154. cartesians[i]
  1155. );
  1156. }
  1157. return deferPromiseUntilPostRender(
  1158. scene,
  1159. Promise.all(promises).then(function (clampedCartesians) {
  1160. const length = clampedCartesians.length;
  1161. for (let i = 0; i < length; ++i) {
  1162. cartesians[i] = clampedCartesians[i];
  1163. }
  1164. return cartesians;
  1165. })
  1166. );
  1167. };
  1168. Picking.prototype.destroy = function () {
  1169. this._pickOffscreenView =
  1170. this._pickOffscreenView && this._pickOffscreenView.destroy();
  1171. };
  1172. export default Picking;