Skip to content
Snippets Groups Projects
Select Git revision
  • edda4fe0d491a36b0e9dd5787a958efa67795632
  • master default protected
  • base-pairs-ladder
  • rednatco-v2
  • rednatco
  • test
  • ntc-tube-uniform-color
  • ntc-tube-missing-atoms
  • restore-vertex-array-per-program
  • watlas2
  • dnatco_new
  • cleanup-old-nodejs
  • webmmb
  • fix_auth_seq_id
  • update_deps
  • ext_dev
  • ntc_balls
  • nci-2
  • plugin
  • bugfix-0.4.5
  • nci
  • v0.5.0-dev.1
  • v0.4.5
  • v0.4.4
  • v0.4.3
  • v0.4.2
  • v0.4.1
  • v0.4.0
  • v0.3.12
  • v0.3.11
  • v0.3.10
  • v0.3.9
  • v0.3.8
  • v0.3.7
  • v0.3.6
  • v0.3.5
  • v0.3.4
  • v0.3.3
  • v0.3.2
  • v0.3.1
  • v0.3.0
41 results

constants.ts

Blame
  • pick.ts 10.63 KiB
    /**
     * Copyright (c) 2019-2021 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 { PickType, 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 { spiral2d } from '../../mol-math/misc';
    import { unpackRGBToInt, unpackRGBAToDepth } from '../../mol-util/number-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, pickType: number) {
            const depth = this.drawPass.depthTexturePrimitives;
            renderer.clear(false);
    
            renderer.update(camera);
            renderer.renderPick(scene.primitives, camera, variant, null, pickType);
            renderer.renderPick(scene.volumes, camera, variant, depth, pickType);
            renderer.renderPick(helper.handle.scene, camera, variant, null, pickType);
    
            if (helper.camera.isEnabled) {
                helper.camera.update(camera);
                renderer.update(helper.camera.camera);
                renderer.renderPick(helper.camera.scene, helper.camera.camera, variant, null, pickType);
            }
        }
    
        render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
            this.objectPickTarget.bind();
            this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Object);
    
            this.instancePickTarget.bind();
            this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Instance);
    
            this.groupPickTarget.bind();
            this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Group);
            // printTexture(this.webgl, this.groupPickTarget.texture, { id: 'group' })
    
            this.depthPickTarget.bind();
            this.renderVariant(renderer, camera, scene, helper, 'depth', PickType.None);
        }
    }
    
    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 spiral: [number, 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.floor(width * this.pickScale);
            const pickHeight = Math.floor(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();
            }
    
            this.spiral = spiral2d(Math.round(this.pickScale * this.pickPadding));
        }
    
        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 unpackRGBToInt(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.setDrawingBufferSize(this.pickPass.objectPickTarget.getWidth(), this.pickPass.objectPickTarget.getHeight());
            renderer.setPixelRatio(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;
        }
    
        private identifyInternal(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 };
        }
    
        identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
            for (const d of this.spiral) {
                const pickData = this.identifyInternal(x + d[0], y + d[1], camera);
                if (pickData) return pickData;
            }
        }
    
        constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport, readonly pickPadding = 1) {
            this.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
        }
    }