UNPKG

molstar

Version:

A comprehensive macromolecular library.

314 lines (313 loc) 15.5 kB
/** * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { isWebGL2 } from '../../mol-gl/webgl/compat'; import { Vec3 } from '../../mol-math/linear-algebra'; import { spiral2d } from '../../mol-math/misc'; import { isTimingMode } from '../../mol-util/debug'; import { unpackRGBToInt, unpackRGBAToDepth } from '../../mol-util/number-packing'; import { StereoCamera } from '../camera/stereo'; import { cameraUnproject } from '../camera/util'; import { Viewport } from '../camera/util'; var NullId = Math.pow(2, 24) - 2; var PickPass = /** @class */ (function () { function PickPass(webgl, drawPass, pickBaseScale) { this.webgl = webgl; this.drawPass = drawPass; this.pickBaseScale = pickBaseScale; var pickScale = pickBaseScale / webgl.pixelRatio; this.pickWidth = Math.ceil(drawPass.colorTarget.getWidth() * pickScale); this.pickHeight = Math.ceil(drawPass.colorTarget.getHeight() * pickScale); var resources = webgl.resources, drawBuffers = webgl.extensions.drawBuffers, gl = webgl.gl; if (drawBuffers) { this.objectPickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); this.objectPickTexture.define(this.pickWidth, this.pickHeight); this.instancePickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); this.instancePickTexture.define(this.pickWidth, this.pickHeight); this.groupPickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); this.groupPickTexture.define(this.pickWidth, this.pickHeight); this.depthPickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); this.depthPickTexture.define(this.pickWidth, this.pickHeight); this.framebuffer = resources.framebuffer(); this.objectPickFramebuffer = resources.framebuffer(); this.instancePickFramebuffer = resources.framebuffer(); this.groupPickFramebuffer = resources.framebuffer(); this.depthPickFramebuffer = resources.framebuffer(); this.framebuffer.bind(); drawBuffers.drawBuffers([ drawBuffers.COLOR_ATTACHMENT0, drawBuffers.COLOR_ATTACHMENT1, drawBuffers.COLOR_ATTACHMENT2, drawBuffers.COLOR_ATTACHMENT3, ]); this.objectPickTexture.attachFramebuffer(this.framebuffer, 'color0'); this.instancePickTexture.attachFramebuffer(this.framebuffer, 'color1'); this.groupPickTexture.attachFramebuffer(this.framebuffer, 'color2'); this.depthPickTexture.attachFramebuffer(this.framebuffer, 'color3'); this.depthRenderbuffer = isWebGL2(gl) ? resources.renderbuffer('depth32f', 'depth', this.pickWidth, this.pickHeight) : resources.renderbuffer('depth16', 'depth', this.pickWidth, this.pickHeight); this.depthRenderbuffer.attachFramebuffer(this.framebuffer); this.objectPickTexture.attachFramebuffer(this.objectPickFramebuffer, 'color0'); this.instancePickTexture.attachFramebuffer(this.instancePickFramebuffer, 'color0'); this.groupPickTexture.attachFramebuffer(this.groupPickFramebuffer, 'color0'); this.depthPickTexture.attachFramebuffer(this.depthPickFramebuffer, 'color0'); } else { 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); } } PickPass.prototype.bindObject = function () { if (this.webgl.extensions.drawBuffers) { this.objectPickFramebuffer.bind(); } else { this.objectPickTarget.bind(); } }; PickPass.prototype.bindInstance = function () { if (this.webgl.extensions.drawBuffers) { this.instancePickFramebuffer.bind(); } else { this.instancePickTarget.bind(); } }; PickPass.prototype.bindGroup = function () { if (this.webgl.extensions.drawBuffers) { this.groupPickFramebuffer.bind(); } else { this.groupPickTarget.bind(); } }; PickPass.prototype.bindDepth = function () { if (this.webgl.extensions.drawBuffers) { this.depthPickFramebuffer.bind(); } else { this.depthPickTarget.bind(); } }; Object.defineProperty(PickPass.prototype, "drawingBufferHeight", { get: function () { return this.drawPass.colorTarget.getHeight(); }, enumerable: false, configurable: true }); PickPass.prototype.syncSize = function () { var pickScale = this.pickBaseScale / this.webgl.pixelRatio; var pickWidth = Math.ceil(this.drawPass.colorTarget.getWidth() * pickScale); var pickHeight = Math.ceil(this.drawPass.colorTarget.getHeight() * pickScale); if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) { this.pickWidth = pickWidth; this.pickHeight = pickHeight; if (this.webgl.extensions.drawBuffers) { this.objectPickTexture.define(this.pickWidth, this.pickHeight); this.instancePickTexture.define(this.pickWidth, this.pickHeight); this.groupPickTexture.define(this.pickWidth, this.pickHeight); this.depthPickTexture.define(this.pickWidth, this.pickHeight); this.depthRenderbuffer.setSize(this.pickWidth, this.pickHeight); } else { 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); } } }; PickPass.prototype.renderVariant = function (renderer, camera, scene, helper, variant, pickType) { renderer.clear(false); renderer.update(camera); renderer.renderPick(scene.primitives, camera, variant, null, pickType); if (helper.handle.isEnabled) { 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); } }; PickPass.prototype.render = function (renderer, camera, scene, helper) { if (this.webgl.extensions.drawBuffers) { this.framebuffer.bind(); this.renderVariant(renderer, camera, scene, helper, 'pick', 0 /* PickType.None */); } else { this.objectPickTarget.bind(); this.renderVariant(renderer, camera, scene, helper, 'pick', 1 /* PickType.Object */); this.instancePickTarget.bind(); this.renderVariant(renderer, camera, scene, helper, 'pick', 2 /* PickType.Instance */); this.groupPickTarget.bind(); this.renderVariant(renderer, camera, scene, helper, 'pick', 3 /* PickType.Group */); // printTexture(this.webgl, this.groupPickTarget.texture, { id: 'group' }) this.depthPickTarget.bind(); this.renderVariant(renderer, camera, scene, helper, 'depth', 0 /* PickType.None */); } }; return PickPass; }()); export { PickPass }; var PickHelper = /** @class */ (function () { function PickHelper(webgl, renderer, scene, helper, pickPass, viewport, pickPadding) { if (pickPadding === void 0) { pickPadding = 1; } this.webgl = webgl; this.renderer = renderer; this.scene = scene; this.helper = helper; this.pickPass = pickPass; this.pickPadding = pickPadding; this.dirty = true; this.viewport = Viewport(); this.setViewport(viewport.x, viewport.y, viewport.width, viewport.height); } PickHelper.prototype.setupBuffers = function () { var 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); } }; PickHelper.prototype.setViewport = function (x, y, width, height) { 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); var pickWidth = Math.floor(width * this.pickScale); var 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)); }; PickHelper.prototype.syncBuffers = function () { if (isTimingMode) this.webgl.timer.mark('PickHelper.syncBuffers'); var _a = this, pickX = _a.pickX, pickY = _a.pickY, pickWidth = _a.pickWidth, pickHeight = _a.pickHeight; this.pickPass.bindObject(); this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.objectBuffer); this.pickPass.bindInstance(); this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.instanceBuffer); this.pickPass.bindGroup(); this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.groupBuffer); this.pickPass.bindDepth(); this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.depthBuffer); if (isTimingMode) this.webgl.timer.markEnd('PickHelper.syncBuffers'); }; PickHelper.prototype.getBufferIdx = function (x, y) { return (y * this.pickWidth + x) * 4; }; PickHelper.prototype.getDepth = function (x, y) { var idx = this.getBufferIdx(x, y); var b = this.depthBuffer; return unpackRGBAToDepth(b[idx], b[idx + 1], b[idx + 2], b[idx + 3]); }; PickHelper.prototype.getId = function (x, y, buffer) { var idx = this.getBufferIdx(x, y); return unpackRGBToInt(buffer[idx], buffer[idx + 1], buffer[idx + 2]); }; PickHelper.prototype.render = function (camera) { if (isTimingMode) this.webgl.timer.mark('PickHelper.render'); var _a = this, pickX = _a.pickX, pickY = _a.pickY, pickWidth = _a.pickWidth, pickHeight = _a.pickHeight, halfPickWidth = _a.halfPickWidth; var _b = this, renderer = _b.renderer, scene = _b.scene, helper = _b.helper; renderer.setTransparentBackground(false); renderer.setDrawingBufferSize(pickWidth, pickHeight); 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; if (isTimingMode) this.webgl.timer.markEnd('PickHelper.render'); }; PickHelper.prototype.identifyInternal = function (x, y, camera) { var _a = this, webgl = _a.webgl, pickScale = _a.pickScale; if (webgl.isContextLost) return; x *= webgl.pixelRatio; y *= webgl.pixelRatio; y = this.pickPass.drawingBufferHeight - y; // flip y var viewport = this.viewport; // 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) { if (isTimingMode) this.webgl.timer.mark('PickHelper.identify'); this.render(camera); this.syncBuffers(); if (isTimingMode) this.webgl.timer.markEnd('PickHelper.identify'); } var xv = x - viewport.x; var yv = y - viewport.y; var xp = Math.floor(xv * pickScale); var yp = Math.floor(yv * pickScale); var objectId = this.getId(xp, yp, this.objectBuffer); // console.log('objectId', objectId); if (objectId === -1 || objectId === NullId) return; var instanceId = this.getId(xp, yp, this.instanceBuffer); // console.log('instanceId', instanceId); if (instanceId === -1 || instanceId === NullId) return; var groupId = this.getId(xp, yp, this.groupBuffer); // console.log('groupId', groupId); if (groupId === -1 || groupId === NullId) return; var z = this.getDepth(xp, yp); // console.log('z', z); var position = Vec3.create(x, viewport.height - y, z); if (StereoCamera.is(camera)) { var 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({ id: { objectId, instanceId, groupId }, position }); return { id: { objectId: objectId, instanceId: instanceId, groupId: groupId }, position: position }; }; PickHelper.prototype.identify = function (x, y, camera) { for (var _i = 0, _a = this.spiral; _i < _a.length; _i++) { var d = _a[_i]; var pickData = this.identifyInternal(x + d[0], y + d[1], camera); if (pickData) return pickData; } }; return PickHelper; }()); export { PickHelper };