UNPKG

molstar

Version:

A comprehensive macromolecular library.

468 lines (467 loc) 24.5 kB
"use strict"; /** * Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BackgroundPass = exports.BackgroundParams = void 0; const util_1 = require("../../mol-gl/compute/util"); const renderable_1 = require("../../mol-gl/renderable"); const schema_1 = require("../../mol-gl/renderable/schema"); const shader_code_1 = require("../../mol-gl/shader-code"); const background_frag_1 = require("../../mol-gl/shader/background.frag"); const background_vert_1 = require("../../mol-gl/shader/background.vert"); const render_item_1 = require("../../mol-gl/webgl/render-item"); const texture_1 = require("../../mol-gl/webgl/texture"); const mat4_1 = require("../../mol-math/linear-algebra/3d/mat4"); const value_cell_1 = require("../../mol-util/value-cell"); const param_definition_1 = require("../../mol-util/param-definition"); const debug_1 = require("../../mol-util/debug"); const camera_1 = require("../camera"); const vec3_1 = require("../../mol-math/linear-algebra/3d/vec3"); const vec2_1 = require("../../mol-math/linear-algebra/3d/vec2"); const color_1 = require("../../mol-util/color"); const assets_1 = require("../../mol-util/assets"); const vec4_1 = require("../../mol-math/linear-algebra/3d/vec4"); const misc_1 = require("../../mol-math/misc"); const mat3_1 = require("../../mol-math/linear-algebra/3d/mat3"); const euler_1 = require("../../mol-math/linear-algebra/3d/euler"); const SharedParams = { opacity: param_definition_1.ParamDefinition.Numeric(1, { min: 0.0, max: 1.0, step: 0.01 }), saturation: param_definition_1.ParamDefinition.Numeric(0, { min: -1, max: 1, step: 0.01 }), lightness: param_definition_1.ParamDefinition.Numeric(0, { min: -1, max: 1, step: 0.01 }), }; const SkyboxParams = { faces: param_definition_1.ParamDefinition.MappedStatic('urls', { urls: param_definition_1.ParamDefinition.Group({ nx: param_definition_1.ParamDefinition.Text('', { label: 'Negative X / Left' }), ny: param_definition_1.ParamDefinition.Text('', { label: 'Negative Y / Bottom' }), nz: param_definition_1.ParamDefinition.Text('', { label: 'Negative Z / Back' }), px: param_definition_1.ParamDefinition.Text('', { label: 'Positive X / Right' }), py: param_definition_1.ParamDefinition.Text('', { label: 'Positive Y / Top' }), pz: param_definition_1.ParamDefinition.Text('', { label: 'Positive Z / Front' }), }, { isExpanded: true, label: 'URLs' }), files: param_definition_1.ParamDefinition.Group({ nx: param_definition_1.ParamDefinition.File({ label: 'Negative X / Left', accept: 'image/*' }), ny: param_definition_1.ParamDefinition.File({ label: 'Negative Y / Bottom', accept: 'image/*' }), nz: param_definition_1.ParamDefinition.File({ label: 'Negative Z / Back', accept: 'image/*' }), px: param_definition_1.ParamDefinition.File({ label: 'Positive X / Right', accept: 'image/*' }), py: param_definition_1.ParamDefinition.File({ label: 'Positive Y / Top', accept: 'image/*' }), pz: param_definition_1.ParamDefinition.File({ label: 'Positive Z / Front', accept: 'image/*' }), }, { isExpanded: true, label: 'Files' }), }), blur: param_definition_1.ParamDefinition.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or when "EXT_shader_texture_lod" is available.' }), rotation: param_definition_1.ParamDefinition.Group({ x: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: 360, step: 1 }, { immediateUpdate: true }), y: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: 360, step: 1 }, { immediateUpdate: true }), z: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: 360, step: 1 }, { immediateUpdate: true }), }), ...SharedParams, }; const ImageParams = { source: param_definition_1.ParamDefinition.MappedStatic('url', { url: param_definition_1.ParamDefinition.Text(''), file: param_definition_1.ParamDefinition.File({ accept: 'image/*' }), }), blur: param_definition_1.ParamDefinition.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or with power-of-two images and when "EXT_shader_texture_lod" is available.' }), ...SharedParams, coverage: param_definition_1.ParamDefinition.Select('viewport', param_definition_1.ParamDefinition.arrayToOptions(['viewport', 'canvas'])), }; const HorizontalGradientParams = { topColor: param_definition_1.ParamDefinition.Color((0, color_1.Color)(0xDDDDDD)), bottomColor: param_definition_1.ParamDefinition.Color((0, color_1.Color)(0xEEEEEE)), ratio: param_definition_1.ParamDefinition.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), coverage: param_definition_1.ParamDefinition.Select('viewport', param_definition_1.ParamDefinition.arrayToOptions(['viewport', 'canvas'])), }; const RadialGradientParams = { centerColor: param_definition_1.ParamDefinition.Color((0, color_1.Color)(0xDDDDDD)), edgeColor: param_definition_1.ParamDefinition.Color((0, color_1.Color)(0xEEEEEE)), ratio: param_definition_1.ParamDefinition.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), coverage: param_definition_1.ParamDefinition.Select('viewport', param_definition_1.ParamDefinition.arrayToOptions(['viewport', 'canvas'])), }; exports.BackgroundParams = { variant: param_definition_1.ParamDefinition.MappedStatic('off', { off: param_definition_1.ParamDefinition.EmptyGroup(), skybox: param_definition_1.ParamDefinition.Group(SkyboxParams, { isExpanded: true }), image: param_definition_1.ParamDefinition.Group(ImageParams, { isExpanded: true }), horizontalGradient: param_definition_1.ParamDefinition.Group(HorizontalGradientParams, { isExpanded: true }), radialGradient: param_definition_1.ParamDefinition.Group(RadialGradientParams, { isExpanded: true }), }, { label: 'Environment' }), }; class BackgroundPass { constructor(webgl, assetManager, width, height) { this.webgl = webgl; this.assetManager = assetManager; this.camera = new camera_1.Camera(); this.target = (0, vec3_1.Vec3)(); this.position = (0, vec3_1.Vec3)(); this.dir = (0, vec3_1.Vec3)(); this.bgColor = (0, vec3_1.Vec3)(); this.renderable = getBackgroundRenderable(webgl, width, height); } setSize(width, height) { const [w, h] = this.renderable.values.uTexSize.ref.value; if (width !== w || height !== h) { value_cell_1.ValueCell.update(this.renderable.values.uTexSize, vec2_1.Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); } } clearSkybox() { if (this.skybox !== undefined) { this.skybox.texture.destroy(); this.skybox.assets.forEach(a => this.assetManager.release(a)); this.skybox = undefined; } } updateSkybox(camera, props, onload) { var _a; const tf = (_a = this.skybox) === null || _a === void 0 ? void 0 : _a.props.faces; const f = props.faces.params; if (!f.nx || !f.ny || !f.nz || !f.px || !f.py || !f.pz) { this.clearSkybox(); onload === null || onload === void 0 ? void 0 : onload(false); return; } if (!this.skybox || !tf || !areSkyboxTexturePropsEqual(props.faces, this.skybox.props.faces)) { this.clearSkybox(); const { texture, assets } = getSkyboxTexture(this.webgl, this.assetManager, props.faces, errored => { if (this.skybox) this.skybox.loaded = !errored; onload === null || onload === void 0 ? void 0 : onload(true); }); this.skybox = { texture, props: { ...props }, assets, loaded: false }; value_cell_1.ValueCell.update(this.renderable.values.tSkybox, texture); this.renderable.update(); } else { onload === null || onload === void 0 ? void 0 : onload(false); } if (!this.skybox) return; let cam = camera; if (camera.state.mode === 'orthographic') { this.camera.setState({ ...camera.state, mode: 'perspective' }); this.camera.update(); cam = this.camera; } const m = this.renderable.values.uViewDirectionProjectionInverse.ref.value; vec3_1.Vec3.sub(this.dir, cam.state.position, cam.state.target); vec3_1.Vec3.setMagnitude(this.dir, this.dir, 0.1); vec3_1.Vec3.copy(this.position, this.dir); mat4_1.Mat4.lookAt(m, this.position, this.target, cam.state.up); mat4_1.Mat4.mul(m, cam.projection, m); mat4_1.Mat4.invert(m, m); value_cell_1.ValueCell.update(this.renderable.values.uViewDirectionProjectionInverse, m); const r = this.renderable.values.uRotation.ref.value; mat3_1.Mat3.fromEuler(r, euler_1.Euler.create((0, misc_1.degToRad)(props.rotation.x), (0, misc_1.degToRad)(props.rotation.y), (0, misc_1.degToRad)(props.rotation.z)), 'XYZ'); value_cell_1.ValueCell.update(this.renderable.values.uRotation, r); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.dVariant, 'skybox'); this.renderable.update(); } clearImage() { if (this.image !== undefined) { this.image.texture.destroy(); this.assetManager.release(this.image.asset); this.image = undefined; } } updateImage(props, onload) { if (!props.source.params) { this.clearImage(); onload === null || onload === void 0 ? void 0 : onload(false); return; } if (!this.image || !this.image.props.source.params || !areImageTexturePropsEqual(props.source, this.image.props.source)) { this.clearImage(); const { texture, asset } = getImageTexture(this.webgl, this.assetManager, props.source, errored => { if (this.image) this.image.loaded = !errored; onload === null || onload === void 0 ? void 0 : onload(true); }); this.image = { texture, props: { ...props }, asset, loaded: false }; value_cell_1.ValueCell.update(this.renderable.values.tImage, texture); this.renderable.update(); } else { onload === null || onload === void 0 ? void 0 : onload(false); } if (!this.image) return; value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uViewportAdjusted, props.coverage === 'viewport' ? true : false); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.dVariant, 'image'); this.renderable.update(); } updateImageScaling() { var _a, _b; const v = this.renderable.values; const [w, h] = v.uTexSize.ref.value; const iw = ((_a = this.image) === null || _a === void 0 ? void 0 : _a.texture.getWidth()) || 0; const ih = ((_b = this.image) === null || _b === void 0 ? void 0 : _b.texture.getHeight()) || 0; const r = w / h; const ir = iw / ih; // responsive scaling with offset if (r < ir) { value_cell_1.ValueCell.update(v.uImageScale, vec2_1.Vec2.set(v.uImageScale.ref.value, iw * h / ih, h)); } else { value_cell_1.ValueCell.update(v.uImageScale, vec2_1.Vec2.set(v.uImageScale.ref.value, w, ih * w / iw)); } const [rw, rh] = v.uImageScale.ref.value; const sr = rw / rh; if (sr > r) { value_cell_1.ValueCell.update(v.uImageOffset, vec2_1.Vec2.set(v.uImageOffset.ref.value, (1 - r / sr) / 2, 0)); } else { value_cell_1.ValueCell.update(v.uImageOffset, vec2_1.Vec2.set(v.uImageOffset.ref.value, 0, (1 - sr / r) / 2)); } } updateGradient(colorA, colorB, ratio, variant, viewportAdjusted) { value_cell_1.ValueCell.update(this.renderable.values.uGradientColorA, color_1.Color.toVec3Normalized(this.renderable.values.uGradientColorA.ref.value, colorA)); value_cell_1.ValueCell.update(this.renderable.values.uGradientColorB, color_1.Color.toVec3Normalized(this.renderable.values.uGradientColorB.ref.value, colorB)); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uGradientRatio, ratio); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.uViewportAdjusted, viewportAdjusted); value_cell_1.ValueCell.updateIfChanged(this.renderable.values.dVariant, variant); this.renderable.update(); } update(camera, props, onload) { if (props.variant.name === 'off') { this.clearSkybox(); this.clearImage(); onload === null || onload === void 0 ? void 0 : onload(false); return; } else if (props.variant.name === 'skybox') { this.clearImage(); this.updateSkybox(camera, props.variant.params, onload); } else if (props.variant.name === 'image') { this.clearSkybox(); this.updateImage(props.variant.params, onload); } else if (props.variant.name === 'horizontalGradient') { this.clearSkybox(); this.clearImage(); this.updateGradient(props.variant.params.topColor, props.variant.params.bottomColor, props.variant.params.ratio, props.variant.name, props.variant.params.coverage === 'viewport' ? true : false); onload === null || onload === void 0 ? void 0 : onload(false); } else if (props.variant.name === 'radialGradient') { this.clearSkybox(); this.clearImage(); this.updateGradient(props.variant.params.centerColor, props.variant.params.edgeColor, props.variant.params.ratio, props.variant.name, props.variant.params.coverage === 'viewport' ? true : false); onload === null || onload === void 0 ? void 0 : onload(false); } const { x, y, width, height } = camera.viewport; value_cell_1.ValueCell.update(this.renderable.values.uViewport, vec4_1.Vec4.set(this.renderable.values.uViewport.ref.value, x, y, width, height)); } isEnabled(props) { return !!((this.skybox && this.skybox.loaded) || (this.image && this.image.loaded) || props.variant.name === 'horizontalGradient' || props.variant.name === 'radialGradient'); } isReady() { return !!((this.skybox && this.skybox.loaded) || (this.image && this.image.loaded) || this.renderable.values.dVariant.ref.value === 'horizontalGradient' || this.renderable.values.dVariant.ref.value === 'radialGradient'); } clear(props, transparentBackground, backgroundColor) { const { gl, state } = this.webgl; if (this.isEnabled(props)) { if (transparentBackground) { state.clearColor(0, 0, 0, 0); } else { color_1.Color.toVec3Normalized(this.bgColor, backgroundColor); state.clearColor(this.bgColor[0], this.bgColor[1], this.bgColor[2], 1); } gl.clear(gl.COLOR_BUFFER_BIT); state.enable(gl.BLEND); state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { state.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); } } render(props) { if (!this.isEnabled(props) || !this.isReady()) return; if (this.renderable.values.dVariant.ref.value === 'image') { this.updateImageScaling(); } if (debug_1.isTimingMode) this.webgl.timer.mark('BackgroundPass.render'); this.renderable.render(); if (debug_1.isTimingMode) this.webgl.timer.markEnd('BackgroundPass.render'); } dispose() { this.clearSkybox(); this.clearImage(); } } exports.BackgroundPass = BackgroundPass; // const SkyboxName = 'background-skybox'; function getCubeAssets(assetManager, faces) { if (faces.name === 'urls') { return { nx: assets_1.Asset.getUrlAsset(assetManager, faces.params.nx), ny: assets_1.Asset.getUrlAsset(assetManager, faces.params.ny), nz: assets_1.Asset.getUrlAsset(assetManager, faces.params.nz), px: assets_1.Asset.getUrlAsset(assetManager, faces.params.px), py: assets_1.Asset.getUrlAsset(assetManager, faces.params.py), pz: assets_1.Asset.getUrlAsset(assetManager, faces.params.pz), }; } else { return { nx: faces.params.nx, ny: faces.params.ny, nz: faces.params.nz, px: faces.params.px, py: faces.params.py, pz: faces.params.pz, }; } } function getCubeFaces(assetManager, cubeAssets) { const resolve = (asset) => { return assetManager.resolve(asset, 'binary').run().then(a => new Blob([a.data])); }; return { nx: resolve(cubeAssets.nx), ny: resolve(cubeAssets.ny), nz: resolve(cubeAssets.nz), px: resolve(cubeAssets.px), py: resolve(cubeAssets.py), pz: resolve(cubeAssets.pz), }; } function getSkyboxHash(faces) { var _a, _b, _c, _d, _e, _f; if (faces.name === 'urls') { return `${SkyboxName}_${faces.params.nx}|${faces.params.ny}|${faces.params.nz}|${faces.params.px}|${faces.params.py}|${faces.params.pz}`; } else { return `${SkyboxName}_${(_a = faces.params.nx) === null || _a === void 0 ? void 0 : _a.id}|${(_b = faces.params.ny) === null || _b === void 0 ? void 0 : _b.id}|${(_c = faces.params.nz) === null || _c === void 0 ? void 0 : _c.id}|${(_d = faces.params.px) === null || _d === void 0 ? void 0 : _d.id}|${(_e = faces.params.py) === null || _e === void 0 ? void 0 : _e.id}|${(_f = faces.params.pz) === null || _f === void 0 ? void 0 : _f.id}`; } } function areSkyboxTexturePropsEqual(facesA, facesB) { return getSkyboxHash(facesA) === getSkyboxHash(facesB); } function getSkyboxTexture(ctx, assetManager, faces, onload) { const cubeAssets = getCubeAssets(assetManager, faces); const cubeFaces = getCubeFaces(assetManager, cubeAssets); const assets = [cubeAssets.nx, cubeAssets.ny, cubeAssets.nz, cubeAssets.px, cubeAssets.py, cubeAssets.pz]; if (typeof HTMLImageElement === 'undefined') { console.error(`Missing "HTMLImageElement" required for background skybox`); onload === null || onload === void 0 ? void 0 : onload(true); return { texture: (0, texture_1.createNullTexture)(), assets }; } const texture = ctx.resources.cubeTexture(cubeFaces, true, onload); return { texture, assets }; } // const ImageName = 'background-image'; function getImageHash(source) { var _a; if (source.name === 'url') { return `${ImageName}_${source.params}`; } else { return `${ImageName}_${(_a = source.params) === null || _a === void 0 ? void 0 : _a.id}`; } } function areImageTexturePropsEqual(sourceA, sourceB) { return getImageHash(sourceA) === getImageHash(sourceB); } function getImageTexture(ctx, assetManager, source, onload) { const asset = source.name === 'url' ? assets_1.Asset.getUrlAsset(assetManager, source.params) : source.params; if (typeof HTMLImageElement === 'undefined') { console.error(`Missing "HTMLImageElement" required for background image`); onload === null || onload === void 0 ? void 0 : onload(true); return { texture: (0, texture_1.createNullTexture)(), asset }; } const texture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); const img = new Image(); img.onload = () => { texture.load(img); if (ctx.isWebGL2 || ((0, misc_1.isPowerOfTwo)(img.width) && (0, misc_1.isPowerOfTwo)(img.height))) { texture.mipmap(); } onload === null || onload === void 0 ? void 0 : onload(); }; img.onerror = () => { onload === null || onload === void 0 ? void 0 : onload(true); }; assetManager.resolve(asset, 'binary').run().then(a => { const blob = new Blob([a.data]); img.src = URL.createObjectURL(blob); }); return { texture, asset }; } // const BackgroundSchema = { drawCount: (0, schema_1.ValueSpec)('number'), instanceCount: (0, schema_1.ValueSpec)('number'), aPosition: (0, schema_1.AttributeSpec)('float32', 2, 0), tSkybox: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'linear'), tImage: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'linear'), uImageScale: (0, schema_1.UniformSpec)('v2'), uImageOffset: (0, schema_1.UniformSpec)('v2'), uTexSize: (0, schema_1.UniformSpec)('v2'), uViewport: (0, schema_1.UniformSpec)('v4'), uViewportAdjusted: (0, schema_1.UniformSpec)('b'), uViewDirectionProjectionInverse: (0, schema_1.UniformSpec)('m4'), uGradientColorA: (0, schema_1.UniformSpec)('v3'), uGradientColorB: (0, schema_1.UniformSpec)('v3'), uGradientRatio: (0, schema_1.UniformSpec)('f'), uBlur: (0, schema_1.UniformSpec)('f'), uOpacity: (0, schema_1.UniformSpec)('f'), uSaturation: (0, schema_1.UniformSpec)('f'), uLightness: (0, schema_1.UniformSpec)('f'), uRotation: (0, schema_1.UniformSpec)('m3'), dVariant: (0, schema_1.DefineSpec)('string', ['skybox', 'image', 'verticalGradient', 'horizontalGradient', 'radialGradient']), }; const SkyboxShaderCode = (0, shader_code_1.ShaderCode)('background', background_vert_1.background_vert, background_frag_1.background_frag, { shaderTextureLod: 'optional' }); function getBackgroundRenderable(ctx, width, height) { const values = { drawCount: value_cell_1.ValueCell.create(6), instanceCount: value_cell_1.ValueCell.create(1), aPosition: value_cell_1.ValueCell.create(util_1.QuadPositions), tSkybox: value_cell_1.ValueCell.create((0, texture_1.createNullTexture)()), tImage: value_cell_1.ValueCell.create((0, texture_1.createNullTexture)()), uImageScale: value_cell_1.ValueCell.create((0, vec2_1.Vec2)()), uImageOffset: value_cell_1.ValueCell.create((0, vec2_1.Vec2)()), uTexSize: value_cell_1.ValueCell.create(vec2_1.Vec2.create(width, height)), uViewport: value_cell_1.ValueCell.create((0, vec4_1.Vec4)()), uViewportAdjusted: value_cell_1.ValueCell.create(true), uViewDirectionProjectionInverse: value_cell_1.ValueCell.create((0, mat4_1.Mat4)()), uGradientColorA: value_cell_1.ValueCell.create((0, vec3_1.Vec3)()), uGradientColorB: value_cell_1.ValueCell.create((0, vec3_1.Vec3)()), uGradientRatio: value_cell_1.ValueCell.create(0.5), uBlur: value_cell_1.ValueCell.create(0), uOpacity: value_cell_1.ValueCell.create(1), uSaturation: value_cell_1.ValueCell.create(0), uLightness: value_cell_1.ValueCell.create(0), uRotation: value_cell_1.ValueCell.create(mat3_1.Mat3.identity()), dVariant: value_cell_1.ValueCell.create('skybox'), }; const schema = { ...BackgroundSchema }; const renderItem = (0, render_item_1.createComputeRenderItem)(ctx, 'triangles', SkyboxShaderCode, schema, values); return (0, renderable_1.createComputeRenderable)(renderItem, values); }