Picking.js 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313
  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 defer from "../Core/defer.js";
  10. import defined from "../Core/defined.js";
  11. import DeveloperError from "../Core/DeveloperError.js";
  12. import Matrix4 from "../Core/Matrix4.js";
  13. import OrthographicFrustum from "../Core/OrthographicFrustum.js";
  14. import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
  15. import PerspectiveFrustum from "../Core/PerspectiveFrustum.js";
  16. import PerspectiveOffCenterFrustum from "../Core/PerspectiveOffCenterFrustum.js";
  17. import Ray from "../Core/Ray.js";
  18. import ShowGeometryInstanceAttribute from "../Core/ShowGeometryInstanceAttribute.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 PickDepth from "./PickDepth.js";
  24. import PrimitiveCollection from "./PrimitiveCollection.js";
  25. import SceneMode from "./SceneMode.js";
  26. import SceneTransforms from "./SceneTransforms.js";
  27. import View from "./View.js";
  28. const offscreenDefaultWidth = 0.1;
  29. const mostDetailedPreloadTilesetPassState = new Cesium3DTilePassState({
  30. pass: Cesium3DTilePass.MOST_DETAILED_PRELOAD,
  31. });
  32. const mostDetailedPickTilesetPassState = new Cesium3DTilePassState({
  33. pass: Cesium3DTilePass.MOST_DETAILED_PICK,
  34. });
  35. const pickTilesetPassState = new Cesium3DTilePassState({
  36. pass: Cesium3DTilePass.PICK,
  37. });
  38. /**
  39. * @private
  40. */
  41. function Picking(scene) {
  42. this._mostDetailedRayPicks = [];
  43. this.pickRenderStateCache = {};
  44. this._pickPositionCache = {};
  45. this._pickPositionCacheDirty = false;
  46. const pickOffscreenViewport = new BoundingRectangle(0, 0, 1, 1);
  47. const pickOffscreenCamera = new Camera(scene);
  48. pickOffscreenCamera.frustum = new OrthographicFrustum({
  49. width: offscreenDefaultWidth,
  50. aspectRatio: 1.0,
  51. near: 0.1,
  52. });
  53. this._pickOffscreenView = new View(
  54. scene,
  55. pickOffscreenCamera,
  56. pickOffscreenViewport
  57. );
  58. }
  59. Picking.prototype.update = function () {
  60. this._pickPositionCacheDirty = true;
  61. };
  62. Picking.prototype.getPickDepth = function (scene, index) {
  63. const pickDepths = scene.view.pickDepths;
  64. let pickDepth = pickDepths[index];
  65. if (!defined(pickDepth)) {
  66. pickDepth = new PickDepth();
  67. pickDepths[index] = pickDepth;
  68. }
  69. return pickDepth;
  70. };
  71. const scratchOrthoPickingFrustum = new OrthographicOffCenterFrustum();
  72. const scratchOrthoOrigin = new Cartesian3();
  73. const scratchOrthoDirection = new Cartesian3();
  74. const scratchOrthoPixelSize = new Cartesian2();
  75. const scratchOrthoPickVolumeMatrix4 = new Matrix4();
  76. function getPickOrthographicCullingVolume(
  77. scene,
  78. drawingBufferPosition,
  79. width,
  80. height,
  81. viewport
  82. ) {
  83. const camera = scene.camera;
  84. let frustum = camera.frustum;
  85. if (defined(frustum._offCenterFrustum)) {
  86. frustum = 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. this.deferred = defer();
  521. this.promise = this.deferred.promise;
  522. }
  523. function updateOffscreenCameraFromRay(picking, ray, width, camera) {
  524. const direction = ray.direction;
  525. const orthogonalAxis = Cartesian3.mostOrthogonalAxis(direction, scratchRight);
  526. const right = Cartesian3.cross(direction, orthogonalAxis, scratchRight);
  527. const up = Cartesian3.cross(direction, right, scratchUp);
  528. camera.position = ray.origin;
  529. camera.direction = direction;
  530. camera.up = up;
  531. camera.right = right;
  532. camera.frustum.width = defaultValue(width, offscreenDefaultWidth);
  533. return camera.frustum.computeCullingVolume(
  534. camera.positionWC,
  535. camera.directionWC,
  536. camera.upWC
  537. );
  538. }
  539. function updateMostDetailedRayPick(picking, scene, rayPick) {
  540. const frameState = scene.frameState;
  541. const ray = rayPick.ray;
  542. const width = rayPick.width;
  543. const tilesets = rayPick.tilesets;
  544. const camera = picking._pickOffscreenView.camera;
  545. const cullingVolume = updateOffscreenCameraFromRay(
  546. picking,
  547. ray,
  548. width,
  549. camera
  550. );
  551. const tilesetPassState = mostDetailedPreloadTilesetPassState;
  552. tilesetPassState.camera = camera;
  553. tilesetPassState.cullingVolume = cullingVolume;
  554. let ready = true;
  555. const tilesetsLength = tilesets.length;
  556. for (let i = 0; i < tilesetsLength; ++i) {
  557. const tileset = tilesets[i];
  558. if (tileset.show && scene.primitives.contains(tileset)) {
  559. // Only update tilesets that are still contained in the scene's primitive collection and are still visible
  560. // Update tilesets continually until all tilesets are ready. This way tiles are never removed from the cache.
  561. tileset.updateForPass(frameState, tilesetPassState);
  562. ready = ready && tilesetPassState.ready;
  563. }
  564. }
  565. if (ready) {
  566. rayPick.deferred.resolve();
  567. }
  568. return ready;
  569. }
  570. Picking.prototype.updateMostDetailedRayPicks = function (scene) {
  571. // Modifies array during iteration
  572. const rayPicks = this._mostDetailedRayPicks;
  573. for (let i = 0; i < rayPicks.length; ++i) {
  574. if (updateMostDetailedRayPick(this, scene, rayPicks[i])) {
  575. rayPicks.splice(i--, 1);
  576. }
  577. }
  578. };
  579. function getTilesets(primitives, objectsToExclude, tilesets) {
  580. const length = primitives.length;
  581. for (let i = 0; i < length; ++i) {
  582. const primitive = primitives.get(i);
  583. if (primitive.show) {
  584. if (defined(primitive.isCesium3DTileset)) {
  585. if (
  586. !defined(objectsToExclude) ||
  587. objectsToExclude.indexOf(primitive) === -1
  588. ) {
  589. tilesets.push(primitive);
  590. }
  591. } else if (primitive instanceof PrimitiveCollection) {
  592. getTilesets(primitive, objectsToExclude, tilesets);
  593. }
  594. }
  595. }
  596. }
  597. function launchMostDetailedRayPick(
  598. picking,
  599. scene,
  600. ray,
  601. objectsToExclude,
  602. width,
  603. callback
  604. ) {
  605. const tilesets = [];
  606. getTilesets(scene.primitives, objectsToExclude, tilesets);
  607. if (tilesets.length === 0) {
  608. return Promise.resolve(callback());
  609. }
  610. const rayPick = new MostDetailedRayPick(ray, width, tilesets);
  611. picking._mostDetailedRayPicks.push(rayPick);
  612. return rayPick.promise.then(function () {
  613. return callback();
  614. });
  615. }
  616. function isExcluded(object, objectsToExclude) {
  617. if (
  618. !defined(object) ||
  619. !defined(objectsToExclude) ||
  620. objectsToExclude.length === 0
  621. ) {
  622. return false;
  623. }
  624. return (
  625. objectsToExclude.indexOf(object) > -1 ||
  626. objectsToExclude.indexOf(object.primitive) > -1 ||
  627. objectsToExclude.indexOf(object.id) > -1
  628. );
  629. }
  630. function getRayIntersection(
  631. picking,
  632. scene,
  633. ray,
  634. objectsToExclude,
  635. width,
  636. requirePosition,
  637. mostDetailed
  638. ) {
  639. const context = scene.context;
  640. const uniformState = context.uniformState;
  641. const frameState = scene.frameState;
  642. const view = picking._pickOffscreenView;
  643. scene.view = view;
  644. updateOffscreenCameraFromRay(picking, ray, width, view.camera);
  645. scratchRectangle = BoundingRectangle.clone(view.viewport, scratchRectangle);
  646. const passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
  647. scene.jobScheduler.disableThisFrame();
  648. scene.updateFrameState();
  649. frameState.invertClassification = false;
  650. frameState.passes.pick = true;
  651. frameState.passes.offscreen = true;
  652. if (mostDetailed) {
  653. frameState.tilesetPassState = mostDetailedPickTilesetPassState;
  654. } else {
  655. frameState.tilesetPassState = pickTilesetPassState;
  656. }
  657. uniformState.update(frameState);
  658. scene.updateEnvironment();
  659. scene.updateAndExecuteCommands(passState, scratchColorZero);
  660. scene.resolveFramebuffers(passState);
  661. let position;
  662. const object = view.pickFramebuffer.end(scratchRectangle);
  663. if (scene.context.depthTexture) {
  664. const numFrustums = view.frustumCommandsList.length;
  665. for (let i = 0; i < numFrustums; ++i) {
  666. const pickDepth = picking.getPickDepth(scene, i);
  667. const depth = pickDepth.getDepth(context, 0, 0);
  668. if (!defined(depth)) {
  669. continue;
  670. }
  671. if (depth > 0.0 && depth < 1.0) {
  672. const renderedFrustum = view.frustumCommandsList[i];
  673. const near =
  674. renderedFrustum.near *
  675. (i !== 0 ? scene.opaqueFrustumNearOffset : 1.0);
  676. const far = renderedFrustum.far;
  677. const distance = near + depth * (far - near);
  678. position = Ray.getPoint(ray, distance);
  679. break;
  680. }
  681. }
  682. }
  683. scene.view = scene.defaultView;
  684. context.endFrame();
  685. if (defined(object) || defined(position)) {
  686. return {
  687. object: object,
  688. position: position,
  689. exclude:
  690. (!defined(position) && requirePosition) ||
  691. isExcluded(object, objectsToExclude),
  692. };
  693. }
  694. }
  695. function getRayIntersections(
  696. picking,
  697. scene,
  698. ray,
  699. limit,
  700. objectsToExclude,
  701. width,
  702. requirePosition,
  703. mostDetailed
  704. ) {
  705. const pickCallback = function () {
  706. return getRayIntersection(
  707. picking,
  708. scene,
  709. ray,
  710. objectsToExclude,
  711. width,
  712. requirePosition,
  713. mostDetailed
  714. );
  715. };
  716. return drillPick(limit, pickCallback);
  717. }
  718. function pickFromRay(
  719. picking,
  720. scene,
  721. ray,
  722. objectsToExclude,
  723. width,
  724. requirePosition,
  725. mostDetailed
  726. ) {
  727. const results = getRayIntersections(
  728. picking,
  729. scene,
  730. ray,
  731. 1,
  732. objectsToExclude,
  733. width,
  734. requirePosition,
  735. mostDetailed
  736. );
  737. if (results.length > 0) {
  738. return results[0];
  739. }
  740. }
  741. function drillPickFromRay(
  742. picking,
  743. scene,
  744. ray,
  745. limit,
  746. objectsToExclude,
  747. width,
  748. requirePosition,
  749. mostDetailed
  750. ) {
  751. return getRayIntersections(
  752. picking,
  753. scene,
  754. ray,
  755. limit,
  756. objectsToExclude,
  757. width,
  758. requirePosition,
  759. mostDetailed
  760. );
  761. }
  762. function deferPromiseUntilPostRender(scene, promise) {
  763. // Resolve promise after scene's postRender in case entities are created when the promise resolves.
  764. // Entities can't be created between viewer._onTick and viewer._postRender.
  765. const deferred = defer();
  766. promise
  767. .then(function (result) {
  768. const removeCallback = scene.postRender.addEventListener(function () {
  769. deferred.resolve(result);
  770. removeCallback();
  771. });
  772. scene.requestRender();
  773. })
  774. .catch(function (error) {
  775. deferred.reject(error);
  776. });
  777. return deferred.promise;
  778. }
  779. Picking.prototype.pickFromRay = function (scene, ray, objectsToExclude, width) {
  780. //>>includeStart('debug', pragmas.debug);
  781. Check.defined("ray", ray);
  782. if (scene.mode !== SceneMode.SCENE3D) {
  783. throw new DeveloperError(
  784. "Ray intersections are only supported in 3D mode."
  785. );
  786. }
  787. //>>includeEnd('debug');
  788. return pickFromRay(this, scene, ray, objectsToExclude, width, false, false);
  789. };
  790. Picking.prototype.drillPickFromRay = function (
  791. scene,
  792. ray,
  793. limit,
  794. objectsToExclude,
  795. width
  796. ) {
  797. //>>includeStart('debug', pragmas.debug);
  798. Check.defined("ray", ray);
  799. if (scene.mode !== SceneMode.SCENE3D) {
  800. throw new DeveloperError(
  801. "Ray intersections are only supported in 3D mode."
  802. );
  803. }
  804. //>>includeEnd('debug');
  805. return drillPickFromRay(
  806. this,
  807. scene,
  808. ray,
  809. limit,
  810. objectsToExclude,
  811. width,
  812. false,
  813. false
  814. );
  815. };
  816. Picking.prototype.pickFromRayMostDetailed = function (
  817. scene,
  818. ray,
  819. objectsToExclude,
  820. width
  821. ) {
  822. //>>includeStart('debug', pragmas.debug);
  823. Check.defined("ray", ray);
  824. if (scene.mode !== SceneMode.SCENE3D) {
  825. throw new DeveloperError(
  826. "Ray intersections are only supported in 3D mode."
  827. );
  828. }
  829. //>>includeEnd('debug');
  830. const that = this;
  831. ray = Ray.clone(ray);
  832. objectsToExclude = defined(objectsToExclude)
  833. ? objectsToExclude.slice()
  834. : objectsToExclude;
  835. return deferPromiseUntilPostRender(
  836. scene,
  837. launchMostDetailedRayPick(
  838. that,
  839. scene,
  840. ray,
  841. objectsToExclude,
  842. width,
  843. function () {
  844. return pickFromRay(
  845. that,
  846. scene,
  847. ray,
  848. objectsToExclude,
  849. width,
  850. false,
  851. true
  852. );
  853. }
  854. )
  855. );
  856. };
  857. Picking.prototype.drillPickFromRayMostDetailed = function (
  858. scene,
  859. ray,
  860. limit,
  861. objectsToExclude,
  862. width
  863. ) {
  864. //>>includeStart('debug', pragmas.debug);
  865. Check.defined("ray", ray);
  866. if (scene.mode !== SceneMode.SCENE3D) {
  867. throw new DeveloperError(
  868. "Ray intersections are only supported in 3D mode."
  869. );
  870. }
  871. //>>includeEnd('debug');
  872. const that = this;
  873. ray = Ray.clone(ray);
  874. objectsToExclude = defined(objectsToExclude)
  875. ? objectsToExclude.slice()
  876. : objectsToExclude;
  877. return deferPromiseUntilPostRender(
  878. scene,
  879. launchMostDetailedRayPick(
  880. that,
  881. scene,
  882. ray,
  883. objectsToExclude,
  884. width,
  885. function () {
  886. return drillPickFromRay(
  887. that,
  888. scene,
  889. ray,
  890. limit,
  891. objectsToExclude,
  892. width,
  893. false,
  894. true
  895. );
  896. }
  897. )
  898. );
  899. };
  900. const scratchSurfacePosition = new Cartesian3();
  901. const scratchSurfaceNormal = new Cartesian3();
  902. const scratchSurfaceRay = new Ray();
  903. const scratchCartographic = new Cartographic();
  904. function getRayForSampleHeight(scene, cartographic) {
  905. const globe = scene.globe;
  906. const ellipsoid = defined(globe)
  907. ? globe.ellipsoid
  908. : scene.mapProjection.ellipsoid;
  909. const height = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  910. const surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(
  911. cartographic,
  912. scratchSurfaceNormal
  913. );
  914. const surfacePosition = Cartographic.toCartesian(
  915. cartographic,
  916. ellipsoid,
  917. scratchSurfacePosition
  918. );
  919. const surfaceRay = scratchSurfaceRay;
  920. surfaceRay.origin = surfacePosition;
  921. surfaceRay.direction = surfaceNormal;
  922. const ray = new Ray();
  923. Ray.getPoint(surfaceRay, height, ray.origin);
  924. Cartesian3.negate(surfaceNormal, ray.direction);
  925. return ray;
  926. }
  927. function getRayForClampToHeight(scene, cartesian) {
  928. const globe = scene.globe;
  929. const ellipsoid = defined(globe)
  930. ? globe.ellipsoid
  931. : scene.mapProjection.ellipsoid;
  932. const cartographic = Cartographic.fromCartesian(
  933. cartesian,
  934. ellipsoid,
  935. scratchCartographic
  936. );
  937. return getRayForSampleHeight(scene, cartographic);
  938. }
  939. function getHeightFromCartesian(scene, cartesian) {
  940. const globe = scene.globe;
  941. const ellipsoid = defined(globe)
  942. ? globe.ellipsoid
  943. : scene.mapProjection.ellipsoid;
  944. const cartographic = Cartographic.fromCartesian(
  945. cartesian,
  946. ellipsoid,
  947. scratchCartographic
  948. );
  949. return cartographic.height;
  950. }
  951. function sampleHeightMostDetailed(
  952. picking,
  953. scene,
  954. cartographic,
  955. objectsToExclude,
  956. width
  957. ) {
  958. const ray = getRayForSampleHeight(scene, cartographic);
  959. return launchMostDetailedRayPick(
  960. picking,
  961. scene,
  962. ray,
  963. objectsToExclude,
  964. width,
  965. function () {
  966. const pickResult = pickFromRay(
  967. picking,
  968. scene,
  969. ray,
  970. objectsToExclude,
  971. width,
  972. true,
  973. true
  974. );
  975. if (defined(pickResult)) {
  976. return getHeightFromCartesian(scene, pickResult.position);
  977. }
  978. }
  979. );
  980. }
  981. function clampToHeightMostDetailed(
  982. picking,
  983. scene,
  984. cartesian,
  985. objectsToExclude,
  986. width,
  987. result
  988. ) {
  989. const ray = getRayForClampToHeight(scene, cartesian);
  990. return launchMostDetailedRayPick(
  991. picking,
  992. scene,
  993. ray,
  994. objectsToExclude,
  995. width,
  996. function () {
  997. const pickResult = pickFromRay(
  998. picking,
  999. scene,
  1000. ray,
  1001. objectsToExclude,
  1002. width,
  1003. true,
  1004. true
  1005. );
  1006. if (defined(pickResult)) {
  1007. return Cartesian3.clone(pickResult.position, result);
  1008. }
  1009. }
  1010. );
  1011. }
  1012. Picking.prototype.sampleHeight = function (
  1013. scene,
  1014. position,
  1015. objectsToExclude,
  1016. width
  1017. ) {
  1018. //>>includeStart('debug', pragmas.debug);
  1019. Check.defined("position", position);
  1020. if (scene.mode !== SceneMode.SCENE3D) {
  1021. throw new DeveloperError("sampleHeight is only supported in 3D mode.");
  1022. }
  1023. if (!scene.sampleHeightSupported) {
  1024. throw new DeveloperError(
  1025. "sampleHeight requires depth texture support. Check sampleHeightSupported."
  1026. );
  1027. }
  1028. //>>includeEnd('debug');
  1029. const ray = getRayForSampleHeight(scene, position);
  1030. const pickResult = pickFromRay(
  1031. this,
  1032. scene,
  1033. ray,
  1034. objectsToExclude,
  1035. width,
  1036. true,
  1037. false
  1038. );
  1039. if (defined(pickResult)) {
  1040. return getHeightFromCartesian(scene, pickResult.position);
  1041. }
  1042. };
  1043. Picking.prototype.clampToHeight = function (
  1044. scene,
  1045. cartesian,
  1046. objectsToExclude,
  1047. width,
  1048. result
  1049. ) {
  1050. //>>includeStart('debug', pragmas.debug);
  1051. Check.defined("cartesian", cartesian);
  1052. if (scene.mode !== SceneMode.SCENE3D) {
  1053. throw new DeveloperError("clampToHeight is only supported in 3D mode.");
  1054. }
  1055. if (!scene.clampToHeightSupported) {
  1056. throw new DeveloperError(
  1057. "clampToHeight requires depth texture support. Check clampToHeightSupported."
  1058. );
  1059. }
  1060. //>>includeEnd('debug');
  1061. const ray = getRayForClampToHeight(scene, cartesian);
  1062. const pickResult = pickFromRay(
  1063. this,
  1064. scene,
  1065. ray,
  1066. objectsToExclude,
  1067. width,
  1068. true,
  1069. false
  1070. );
  1071. if (defined(pickResult)) {
  1072. return Cartesian3.clone(pickResult.position, result);
  1073. }
  1074. };
  1075. Picking.prototype.sampleHeightMostDetailed = function (
  1076. scene,
  1077. positions,
  1078. objectsToExclude,
  1079. width
  1080. ) {
  1081. //>>includeStart('debug', pragmas.debug);
  1082. Check.defined("positions", positions);
  1083. if (scene.mode !== SceneMode.SCENE3D) {
  1084. throw new DeveloperError(
  1085. "sampleHeightMostDetailed is only supported in 3D mode."
  1086. );
  1087. }
  1088. if (!scene.sampleHeightSupported) {
  1089. throw new DeveloperError(
  1090. "sampleHeightMostDetailed requires depth texture support. Check sampleHeightSupported."
  1091. );
  1092. }
  1093. //>>includeEnd('debug');
  1094. objectsToExclude = defined(objectsToExclude)
  1095. ? objectsToExclude.slice()
  1096. : objectsToExclude;
  1097. const length = positions.length;
  1098. const promises = new Array(length);
  1099. for (let i = 0; i < length; ++i) {
  1100. promises[i] = sampleHeightMostDetailed(
  1101. this,
  1102. scene,
  1103. positions[i],
  1104. objectsToExclude,
  1105. width
  1106. );
  1107. }
  1108. return deferPromiseUntilPostRender(
  1109. scene,
  1110. Promise.all(promises).then(function (heights) {
  1111. const length = heights.length;
  1112. for (let i = 0; i < length; ++i) {
  1113. positions[i].height = heights[i];
  1114. }
  1115. return positions;
  1116. })
  1117. );
  1118. };
  1119. Picking.prototype.clampToHeightMostDetailed = function (
  1120. scene,
  1121. cartesians,
  1122. objectsToExclude,
  1123. width
  1124. ) {
  1125. //>>includeStart('debug', pragmas.debug);
  1126. Check.defined("cartesians", cartesians);
  1127. if (scene.mode !== SceneMode.SCENE3D) {
  1128. throw new DeveloperError(
  1129. "clampToHeightMostDetailed is only supported in 3D mode."
  1130. );
  1131. }
  1132. if (!scene.clampToHeightSupported) {
  1133. throw new DeveloperError(
  1134. "clampToHeightMostDetailed requires depth texture support. Check clampToHeightSupported."
  1135. );
  1136. }
  1137. //>>includeEnd('debug');
  1138. objectsToExclude = defined(objectsToExclude)
  1139. ? objectsToExclude.slice()
  1140. : objectsToExclude;
  1141. const length = cartesians.length;
  1142. const promises = new Array(length);
  1143. for (let i = 0; i < length; ++i) {
  1144. promises[i] = clampToHeightMostDetailed(
  1145. this,
  1146. scene,
  1147. cartesians[i],
  1148. objectsToExclude,
  1149. width,
  1150. cartesians[i]
  1151. );
  1152. }
  1153. return deferPromiseUntilPostRender(
  1154. scene,
  1155. Promise.all(promises).then(function (clampedCartesians) {
  1156. const length = clampedCartesians.length;
  1157. for (let i = 0; i < length; ++i) {
  1158. cartesians[i] = clampedCartesians[i];
  1159. }
  1160. return cartesians;
  1161. })
  1162. );
  1163. };
  1164. Picking.prototype.destroy = function () {
  1165. this._pickOffscreenView =
  1166. this._pickOffscreenView && this._pickOffscreenView.destroy();
  1167. };
  1168. export default Picking;