Picking.js 33 KB

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