123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
- import { PickingId } from '../../mol-geo/geometry/picking';
- import Renderer from '../../mol-gl/renderer';
- import Scene from '../../mol-gl/scene';
- import { WebGLContext } from '../../mol-gl/webgl/context';
- import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
- import { RenderTarget } from '../../mol-gl/webgl/render-target';
- import { Vec3 } from '../../mol-math/linear-algebra';
- import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
- import { Camera, ICamera } from '../camera';
- import { StereoCamera } from '../camera/stereo';
- import { cameraUnproject } from '../camera/util';
- import { Viewport } from '../camera/util';
- import { Helper } from '../helper/helper';
- import { DrawPass } from './draw';
- const NullId = Math.pow(2, 24) - 2;
- export type PickData = { id: PickingId, position: Vec3 }
- export class PickPass {
- readonly objectPickTarget: RenderTarget
- readonly instancePickTarget: RenderTarget
- readonly groupPickTarget: RenderTarget
- readonly depthPickTarget: RenderTarget
- private pickWidth: number
- private pickHeight: number
- constructor(private webgl: WebGLContext, private drawPass: DrawPass, readonly pickBaseScale: number) {
- const pickScale = pickBaseScale / webgl.pixelRatio;
- this.pickWidth = Math.ceil(drawPass.colorTarget.getWidth() * pickScale);
- this.pickHeight = Math.ceil(drawPass.colorTarget.getHeight() * pickScale);
- this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
- this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
- this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
- this.depthPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
- }
- get drawingBufferHeight() {
- return this.drawPass.colorTarget.getHeight();
- }
- syncSize() {
- const pickScale = this.pickBaseScale / this.webgl.pixelRatio;
- const pickWidth = Math.ceil(this.drawPass.colorTarget.getWidth() * pickScale);
- const pickHeight = Math.ceil(this.drawPass.colorTarget.getHeight() * pickScale);
- if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
- this.pickWidth = pickWidth;
- this.pickHeight = pickHeight;
- this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
- this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);
- this.groupPickTarget.setSize(this.pickWidth, this.pickHeight);
- this.depthPickTarget.setSize(this.pickWidth, this.pickHeight);
- }
- }
- private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
- const depth = this.drawPass.depthTexturePrimitives;
- renderer.clear(false);
- renderer.renderPick(scene.primitives, camera, variant, null);
- renderer.renderPick(scene.volumes, camera, variant, depth);
- renderer.renderPick(helper.handle.scene, camera, variant, null);
- }
- render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
- renderer.update(camera);
- this.objectPickTarget.bind();
- this.renderVariant(renderer, camera, scene, helper, 'pickObject');
- this.instancePickTarget.bind();
- this.renderVariant(renderer, camera, scene, helper, 'pickInstance');
- this.groupPickTarget.bind();
- this.renderVariant(renderer, camera, scene, helper, 'pickGroup');
- this.depthPickTarget.bind();
- this.renderVariant(renderer, camera, scene, helper, 'depth');
- }
- }
- export class PickHelper {
- dirty = true
- private objectBuffer: Uint8Array
- private instanceBuffer: Uint8Array
- private groupBuffer: Uint8Array
- private depthBuffer: Uint8Array
- private viewport = Viewport()
- private pickScale: number
- private pickX: number
- private pickY: number
- private pickWidth: number
- private pickHeight: number
- private halfPickWidth: number
- private setupBuffers() {
- const bufferSize = this.pickWidth * this.pickHeight * 4;
- if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
- this.objectBuffer = new Uint8Array(bufferSize);
- this.instanceBuffer = new Uint8Array(bufferSize);
- this.groupBuffer = new Uint8Array(bufferSize);
- this.depthBuffer = new Uint8Array(bufferSize);
- }
- }
- setViewport(x: number, y: number, width: number, height: number) {
- Viewport.set(this.viewport, x, y, width, height);
- this.pickScale = this.pickPass.pickBaseScale / this.webgl.pixelRatio;
- this.pickX = Math.ceil(x * this.pickScale);
- this.pickY = Math.ceil(y * this.pickScale);
- const pickWidth = Math.ceil(width * this.pickScale);
- const pickHeight = Math.ceil(height * this.pickScale);
- if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
- this.pickWidth = pickWidth;
- this.pickHeight = pickHeight;
- this.halfPickWidth = Math.floor(this.pickWidth / 2);
- this.setupBuffers();
- }
- }
- private syncBuffers() {
- const { pickX, pickY, pickWidth, pickHeight } = this;
- this.pickPass.objectPickTarget.bind();
- this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.objectBuffer);
- this.pickPass.instancePickTarget.bind();
- this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.instanceBuffer);
- this.pickPass.groupPickTarget.bind();
- this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.groupBuffer);
- this.pickPass.depthPickTarget.bind();
- this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.depthBuffer);
- }
- private getBufferIdx(x: number, y: number): number {
- return (y * this.pickWidth + x) * 4;
- }
- private getDepth(x: number, y: number): number {
- const idx = this.getBufferIdx(x, y);
- const b = this.depthBuffer;
- return unpackRGBAToDepth(b[idx], b[idx + 1], b[idx + 2], b[idx + 3]);
- }
- private getId(x: number, y: number, buffer: Uint8Array) {
- const idx = this.getBufferIdx(x, y);
- return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
- }
- private render(camera: Camera | StereoCamera) {
- const { pickX, pickY, pickWidth, pickHeight, halfPickWidth } = this;
- const { renderer, scene, helper } = this;
- renderer.setTransparentBackground(false);
- renderer.setDrawingBufferScale(this.pickScale);
- if (StereoCamera.is(camera)) {
- renderer.setViewport(pickX, pickY, halfPickWidth, pickHeight);
- this.pickPass.render(renderer, camera.left, scene, helper);
- renderer.setViewport(pickX + halfPickWidth, pickY, pickWidth - halfPickWidth, pickHeight);
- this.pickPass.render(renderer, camera.right, scene, helper);
- } else {
- renderer.setViewport(pickX, pickY, pickWidth, pickHeight);
- this.pickPass.render(renderer, camera, scene, helper);
- }
- this.dirty = false;
- }
- identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
- const { webgl, pickScale } = this;
- if (webgl.isContextLost) return;
- x *= webgl.pixelRatio;
- y *= webgl.pixelRatio;
- y = this.pickPass.drawingBufferHeight - y; // flip y
- const { viewport } = this;
- // check if within viewport
- if (x < viewport.x ||
- y < viewport.y ||
- x > viewport.x + viewport.width ||
- y > viewport.y + viewport.height
- ) return;
- if (this.dirty) {
- this.render(camera);
- this.syncBuffers();
- }
- const xv = x - viewport.x;
- const yv = y - viewport.y;
- const xp = Math.floor(xv * pickScale);
- const yp = Math.floor(yv * pickScale);
- const objectId = this.getId(xp, yp, this.objectBuffer);
- // console.log('objectId', objectId);
- if (objectId === -1 || objectId === NullId) return;
- const instanceId = this.getId(xp, yp, this.instanceBuffer);
- // console.log('instanceId', instanceId);
- if (instanceId === -1 || instanceId === NullId) return;
- const groupId = this.getId(xp, yp, this.groupBuffer);
- // console.log('groupId', groupId);
- if (groupId === -1 || groupId === NullId) return;
- const z = this.getDepth(xp, yp);
- const position = Vec3.create(x, viewport.height - y, z);
- if (StereoCamera.is(camera)) {
- const halfWidth = Math.floor(viewport.width / 2);
- if (x > viewport.x + halfWidth) {
- position[0] = viewport.x + (xv - halfWidth) * 2;
- cameraUnproject(position, position, viewport, camera.right.inverseProjectionView);
- } else {
- position[0] = viewport.x + xv * 2;
- cameraUnproject(position, position, viewport, camera.left.inverseProjectionView);
- }
- } else {
- cameraUnproject(position, position, viewport, camera.inverseProjectionView);
- }
- // console.log({ { objectId, instanceId, groupId }, position} );
- return { id: { objectId, instanceId, groupId }, position };
- }
- constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport) {
- this.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
- }
- }
|