pick.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { PickingId } from '../../mol-geo/geometry/picking';
  7. import { Renderer } from '../../mol-gl/renderer';
  8. import { Scene } from '../../mol-gl/scene';
  9. import { WebGLContext } from '../../mol-gl/webgl/context';
  10. import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
  11. import { RenderTarget } from '../../mol-gl/webgl/render-target';
  12. import { Vec3 } from '../../mol-math/linear-algebra';
  13. import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
  14. import { Camera, ICamera } from '../camera';
  15. import { StereoCamera } from '../camera/stereo';
  16. import { cameraUnproject } from '../camera/util';
  17. import { Viewport } from '../camera/util';
  18. import { Helper } from '../helper/helper';
  19. import { DrawPass } from './draw';
  20. const NullId = Math.pow(2, 24) - 2;
  21. export type PickData = { id: PickingId, position: Vec3 }
  22. export class PickPass {
  23. readonly objectPickTarget: RenderTarget
  24. readonly instancePickTarget: RenderTarget
  25. readonly groupPickTarget: RenderTarget
  26. readonly depthPickTarget: RenderTarget
  27. private pickWidth: number
  28. private pickHeight: number
  29. constructor(private webgl: WebGLContext, private drawPass: DrawPass, readonly pickBaseScale: number) {
  30. const pickScale = pickBaseScale / webgl.pixelRatio;
  31. this.pickWidth = Math.ceil(drawPass.colorTarget.getWidth() * pickScale);
  32. this.pickHeight = Math.ceil(drawPass.colorTarget.getHeight() * pickScale);
  33. this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
  34. this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
  35. this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
  36. this.depthPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
  37. }
  38. get drawingBufferHeight() {
  39. return this.drawPass.colorTarget.getHeight();
  40. }
  41. syncSize() {
  42. const pickScale = this.pickBaseScale / this.webgl.pixelRatio;
  43. const pickWidth = Math.ceil(this.drawPass.colorTarget.getWidth() * pickScale);
  44. const pickHeight = Math.ceil(this.drawPass.colorTarget.getHeight() * pickScale);
  45. if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
  46. this.pickWidth = pickWidth;
  47. this.pickHeight = pickHeight;
  48. this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
  49. this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);
  50. this.groupPickTarget.setSize(this.pickWidth, this.pickHeight);
  51. this.depthPickTarget.setSize(this.pickWidth, this.pickHeight);
  52. }
  53. }
  54. private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
  55. const depth = this.drawPass.depthTexturePrimitives;
  56. renderer.clear(false);
  57. renderer.renderPick(scene.primitives, camera, variant, null);
  58. renderer.renderPick(scene.volumes, camera, variant, depth);
  59. renderer.renderPick(helper.handle.scene, camera, variant, null);
  60. }
  61. render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
  62. renderer.update(camera);
  63. this.objectPickTarget.bind();
  64. this.renderVariant(renderer, camera, scene, helper, 'pickObject');
  65. this.instancePickTarget.bind();
  66. this.renderVariant(renderer, camera, scene, helper, 'pickInstance');
  67. this.groupPickTarget.bind();
  68. this.renderVariant(renderer, camera, scene, helper, 'pickGroup');
  69. this.depthPickTarget.bind();
  70. this.renderVariant(renderer, camera, scene, helper, 'depth');
  71. }
  72. }
  73. export class PickHelper {
  74. dirty = true
  75. private objectBuffer: Uint8Array
  76. private instanceBuffer: Uint8Array
  77. private groupBuffer: Uint8Array
  78. private depthBuffer: Uint8Array
  79. private viewport = Viewport()
  80. private pickScale: number
  81. private pickX: number
  82. private pickY: number
  83. private pickWidth: number
  84. private pickHeight: number
  85. private halfPickWidth: number
  86. private setupBuffers() {
  87. const bufferSize = this.pickWidth * this.pickHeight * 4;
  88. if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
  89. this.objectBuffer = new Uint8Array(bufferSize);
  90. this.instanceBuffer = new Uint8Array(bufferSize);
  91. this.groupBuffer = new Uint8Array(bufferSize);
  92. this.depthBuffer = new Uint8Array(bufferSize);
  93. }
  94. }
  95. setViewport(x: number, y: number, width: number, height: number) {
  96. Viewport.set(this.viewport, x, y, width, height);
  97. this.pickScale = this.pickPass.pickBaseScale / this.webgl.pixelRatio;
  98. this.pickX = Math.ceil(x * this.pickScale);
  99. this.pickY = Math.ceil(y * this.pickScale);
  100. const pickWidth = Math.ceil(width * this.pickScale);
  101. const pickHeight = Math.ceil(height * this.pickScale);
  102. if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
  103. this.pickWidth = pickWidth;
  104. this.pickHeight = pickHeight;
  105. this.halfPickWidth = Math.floor(this.pickWidth / 2);
  106. this.setupBuffers();
  107. }
  108. }
  109. private syncBuffers() {
  110. const { pickX, pickY, pickWidth, pickHeight } = this;
  111. this.pickPass.objectPickTarget.bind();
  112. this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.objectBuffer);
  113. this.pickPass.instancePickTarget.bind();
  114. this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.instanceBuffer);
  115. this.pickPass.groupPickTarget.bind();
  116. this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.groupBuffer);
  117. this.pickPass.depthPickTarget.bind();
  118. this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.depthBuffer);
  119. }
  120. private getBufferIdx(x: number, y: number): number {
  121. return (y * this.pickWidth + x) * 4;
  122. }
  123. private getDepth(x: number, y: number): number {
  124. const idx = this.getBufferIdx(x, y);
  125. const b = this.depthBuffer;
  126. return unpackRGBAToDepth(b[idx], b[idx + 1], b[idx + 2], b[idx + 3]);
  127. }
  128. private getId(x: number, y: number, buffer: Uint8Array) {
  129. const idx = this.getBufferIdx(x, y);
  130. return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
  131. }
  132. private render(camera: Camera | StereoCamera) {
  133. const { pickX, pickY, pickWidth, pickHeight, halfPickWidth } = this;
  134. const { renderer, scene, helper } = this;
  135. renderer.setTransparentBackground(false);
  136. renderer.setDrawingBufferSize(this.pickPass.objectPickTarget.getWidth(), this.pickPass.objectPickTarget.getHeight());
  137. if (StereoCamera.is(camera)) {
  138. renderer.setViewport(pickX, pickY, halfPickWidth, pickHeight);
  139. this.pickPass.render(renderer, camera.left, scene, helper);
  140. renderer.setViewport(pickX + halfPickWidth, pickY, pickWidth - halfPickWidth, pickHeight);
  141. this.pickPass.render(renderer, camera.right, scene, helper);
  142. } else {
  143. renderer.setViewport(pickX, pickY, pickWidth, pickHeight);
  144. this.pickPass.render(renderer, camera, scene, helper);
  145. }
  146. this.dirty = false;
  147. }
  148. identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
  149. const { webgl, pickScale } = this;
  150. if (webgl.isContextLost) return;
  151. x *= webgl.pixelRatio;
  152. y *= webgl.pixelRatio;
  153. y = this.pickPass.drawingBufferHeight - y; // flip y
  154. const { viewport } = this;
  155. // check if within viewport
  156. if (x < viewport.x ||
  157. y < viewport.y ||
  158. x > viewport.x + viewport.width ||
  159. y > viewport.y + viewport.height
  160. ) return;
  161. if (this.dirty) {
  162. this.render(camera);
  163. this.syncBuffers();
  164. }
  165. const xv = x - viewport.x;
  166. const yv = y - viewport.y;
  167. const xp = Math.floor(xv * pickScale);
  168. const yp = Math.floor(yv * pickScale);
  169. const objectId = this.getId(xp, yp, this.objectBuffer);
  170. // console.log('objectId', objectId);
  171. if (objectId === -1 || objectId === NullId) return;
  172. const instanceId = this.getId(xp, yp, this.instanceBuffer);
  173. // console.log('instanceId', instanceId);
  174. if (instanceId === -1 || instanceId === NullId) return;
  175. const groupId = this.getId(xp, yp, this.groupBuffer);
  176. // console.log('groupId', groupId);
  177. if (groupId === -1 || groupId === NullId) return;
  178. const z = this.getDepth(xp, yp);
  179. const position = Vec3.create(x, viewport.height - y, z);
  180. if (StereoCamera.is(camera)) {
  181. const halfWidth = Math.floor(viewport.width / 2);
  182. if (x > viewport.x + halfWidth) {
  183. position[0] = viewport.x + (xv - halfWidth) * 2;
  184. cameraUnproject(position, position, viewport, camera.right.inverseProjectionView);
  185. } else {
  186. position[0] = viewport.x + xv * 2;
  187. cameraUnproject(position, position, viewport, camera.left.inverseProjectionView);
  188. }
  189. } else {
  190. cameraUnproject(position, position, viewport, camera.inverseProjectionView);
  191. }
  192. // console.log({ { objectId, instanceId, groupId }, position} );
  193. return { id: { objectId, instanceId, groupId }, position };
  194. }
  195. constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport) {
  196. this.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
  197. }
  198. }