UNPKG

molstar

Version:

A comprehensive macromolecular library.

323 lines (322 loc) 17.5 kB
"use strict"; /** * Copyright (c) 2025 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.PlaneImageParams = void 0; exports.PlaneImageVisual = PlaneImageVisual; exports.createPlaneImage = createPlaneImage; const param_definition_1 = require("../../../mol-util/param-definition"); const structure_1 = require("../../../mol-model/structure"); const linear_algebra_1 = require("../../../mol-math/linear-algebra"); const geometry_1 = require("../../../mol-math/geometry"); const complex_visual_1 = require("../complex-visual"); const element_1 = require("./util/element"); const image_1 = require("../../../mol-geo/geometry/image/image"); const util_1 = require("../../../mol-geo/util"); const base_1 = require("../../../mol-geo/geometry/base"); const location_iterator_1 = require("../../../mol-geo/util/location-iterator"); const color_1 = require("../../../mol-util/color/color"); const interpolate_1 = require("../../../mol-math/interpolate"); const color_2 = require("../../../mol-theme/color"); const number_packing_1 = require("../../../mol-util/number-packing"); const size_1 = require("../../../mol-theme/size"); const plane3d_1 = require("../../../mol-math/geometry/primitives/plane3d"); const misc_1 = require("../../../mol-math/misc"); // avoiding namespace lookup improved performance in Chrome (Aug 2020) const v3set = linear_algebra_1.Vec3.set; const v3transformMat4 = linear_algebra_1.Vec3.transformMat4; const v3squaredDistance = linear_algebra_1.Vec3.squaredDistance; exports.PlaneImageParams = { ...complex_visual_1.ComplexImageParams, interpolation: param_definition_1.ParamDefinition.Select('nearest', param_definition_1.ParamDefinition.objectToOptions(image_1.InterpolationTypes)), imageResolution: param_definition_1.ParamDefinition.Numeric(0.5, { min: 0.01, max: 20, step: 0.01 }, { description: 'Grid resolution/cell spacing.', ...base_1.BaseGeometry.CustomQualityParamInfo }), offset: param_definition_1.ParamDefinition.Numeric(0, { min: -1, max: 1, step: 0.01 }, { isEssential: true, immediateUpdate: true }), axis: param_definition_1.ParamDefinition.Select('c', param_definition_1.ParamDefinition.arrayToOptions(['a', 'b', 'c']), { isEssential: true }), margin: param_definition_1.ParamDefinition.Numeric(4, { min: 0, max: 50, step: 1 }, { immediateUpdate: true, description: 'Margin around the structure in Angstrom' }), frame: param_definition_1.ParamDefinition.Select('principalAxes', param_definition_1.ParamDefinition.arrayToOptions(['principalAxes', 'boundingBox'])), extent: param_definition_1.ParamDefinition.Select('frame', param_definition_1.ParamDefinition.arrayToOptions(['frame', 'sphere']), { description: 'Extent of the plane, either box (frame) or sphere.' }), rotation: param_definition_1.ParamDefinition.Group({ axis: param_definition_1.ParamDefinition.Vec3(linear_algebra_1.Vec3.create(1, 0, 0), {}, { description: 'Axis of rotation' }), angle: param_definition_1.ParamDefinition.Numeric(0, { min: -180, max: 180, step: 1 }, { immediateUpdate: true, description: 'Axis rotation angle in Degrees' }), }, { isExpanded: true }), antialias: param_definition_1.ParamDefinition.Boolean(true, { description: 'Antialiasing of structure edges.' }), cutout: param_definition_1.ParamDefinition.Boolean(false, { description: 'Cutout the structure from the image.' }), defaultColor: param_definition_1.ParamDefinition.Color((0, color_1.Color)(0xCCCCCC), { description: 'Default color for parts of the image that are not covered by the color theme.' }), includeParent: param_definition_1.ParamDefinition.Boolean(false, { description: 'Show parent structure (but within extent of this structure).' }), }; function PlaneImageVisual(materialId) { return (0, complex_visual_1.ComplexImageVisual)({ defaultProps: param_definition_1.ParamDefinition.getDefaultValues(exports.PlaneImageParams), createGeometry: createPlaneImage, createLocationIterator: element_1.ElementIterator.fromStructure, getLoci: element_1.getSerialElementLoci, eachLocation: element_1.eachSerialElement, setUpdateState: (state, newProps, currentProps, newTheme, currentTheme) => { state.createGeometry = (newProps.imageResolution !== currentProps.imageResolution || newProps.margin !== currentProps.margin || newProps.frame !== currentProps.frame || newProps.extent !== currentProps.extent || !linear_algebra_1.Vec3.equals(newProps.rotation.axis, currentProps.rotation.axis) || newProps.rotation.angle !== currentProps.rotation.angle || newProps.offset !== currentProps.offset || newProps.axis !== currentProps.axis || newProps.antialias !== currentProps.antialias || newProps.cutout !== currentProps.cutout || newProps.defaultColor !== currentProps.defaultColor || !color_2.ColorTheme.areEqual(newTheme.color, currentTheme.color) || !size_1.SizeTheme.areEqual(newTheme.size, currentTheme.size)); } }, materialId); } function getFrame(structure, props) { const { axis, frame, extent, margin, rotation, includeParent } = props; if (includeParent && structure.child) { structure = structure.child; } const size = (0, linear_algebra_1.Vec3)(); const scale = (0, linear_algebra_1.Vec3)(); const major = (0, linear_algebra_1.Vec3)(); const minor = (0, linear_algebra_1.Vec3)(); const normal = (0, linear_algebra_1.Vec3)(); const center = (0, linear_algebra_1.Vec3)(); let a = 0, b = 0, c = 0; let dirA, dirB, dirC; if (frame === 'principalAxes') { const axes = structure_1.Structure.getPrincipalAxes(structure).boxAxes; [a, b, c] = geometry_1.Axes3D.size((0, linear_algebra_1.Vec3)(), axes); dirA = axes.dirA; dirB = axes.dirB; dirC = axes.dirC; linear_algebra_1.Vec3.copy(center, axes.origin); } else { [a, b, c] = geometry_1.Box3D.size((0, linear_algebra_1.Vec3)(), structure.boundary.box); dirA = linear_algebra_1.Vec3.create(1, 0, 0); dirB = linear_algebra_1.Vec3.create(0, 1, 0); dirC = linear_algebra_1.Vec3.create(0, 0, 1); linear_algebra_1.Vec3.copy(center, structure.boundary.sphere.center); } linear_algebra_1.Vec3.set(scale, a, b, c); if (axis === 'c') { linear_algebra_1.Vec3.set(size, a, b, c); linear_algebra_1.Vec3.copy(major, dirA); linear_algebra_1.Vec3.copy(minor, dirB); linear_algebra_1.Vec3.copy(normal, dirC); } else if (axis === 'b') { linear_algebra_1.Vec3.set(size, a, c, b); linear_algebra_1.Vec3.copy(major, dirA); linear_algebra_1.Vec3.copy(normal, dirB); linear_algebra_1.Vec3.copy(minor, dirC); } else { linear_algebra_1.Vec3.set(size, b, c, a); linear_algebra_1.Vec3.copy(normal, dirA); linear_algebra_1.Vec3.copy(major, dirB); linear_algebra_1.Vec3.copy(minor, dirC); } if (rotation.angle !== 0) { const ra = (0, linear_algebra_1.Vec3)(); linear_algebra_1.Vec3.scaleAndAdd(ra, ra, dirA, rotation.axis[0]); linear_algebra_1.Vec3.scaleAndAdd(ra, ra, dirB, rotation.axis[1]); linear_algebra_1.Vec3.scaleAndAdd(ra, ra, dirC, rotation.axis[2]); linear_algebra_1.Vec3.normalize(ra, ra); const rm = linear_algebra_1.Mat4.fromRotation((0, linear_algebra_1.Mat4)(), (0, misc_1.degToRad)(rotation.angle), ra); linear_algebra_1.Vec3.transformDirection(major, major, rm); linear_algebra_1.Vec3.transformDirection(minor, minor, rm); linear_algebra_1.Vec3.transformDirection(normal, normal, rm); } if (extent === 'sphere' || rotation.angle !== 0) { const r = structure.boundary.sphere.radius * 2; const s = linear_algebra_1.Vec3.magnitude(geometry_1.Box3D.size((0, linear_algebra_1.Vec3)(), geometry_1.Box3D.fromSphere3D((0, geometry_1.Box3D)(), structure.boundary.sphere))); linear_algebra_1.Vec3.set(size, s, s, r); if (extent === 'sphere') { linear_algebra_1.Vec3.set(scale, r, r, r); } } linear_algebra_1.Vec3.addScalar(size, size, margin * 2); linear_algebra_1.Vec3.addScalar(scale, scale, margin * 2); const trimRotation = linear_algebra_1.Quat.identity(); if (frame === 'principalAxes') { linear_algebra_1.Quat.fromBasis(trimRotation, linear_algebra_1.Vec3.normalize((0, linear_algebra_1.Vec3)(), dirA), linear_algebra_1.Vec3.normalize((0, linear_algebra_1.Vec3)(), dirB), linear_algebra_1.Vec3.normalize((0, linear_algebra_1.Vec3)(), dirC)); } const trim = { type: extent === 'sphere' ? 2 : 3, center, scale, rotation: trimRotation, transform: linear_algebra_1.Mat4.identity(), }; return { size, major, minor, normal, center, trim }; } function createPlaneImage(ctx, structure, theme, props, image) { const { imageResolution, offset, antialias, cutout, defaultColor } = props; const scaleFactor = 1 / imageResolution; const color = 'color' in theme.color && theme.color.color ? theme.color.color : () => (0, color_1.Color)(0xffffff); const { size, major, minor, normal, center, trim } = getFrame(structure, props); const scale = linear_algebra_1.Vec3.create(size[0], size[1], 1); const offsetDir = linear_algebra_1.Vec3.setMagnitude((0, linear_algebra_1.Vec3)(), normal, size[2] / 2); const width = Math.floor(size[1] * scaleFactor); const height = Math.floor(size[0] * scaleFactor); const m = linear_algebra_1.Mat4.identity(); const v = (0, linear_algebra_1.Vec3)(); const anchor = (0, linear_algebra_1.Vec3)(); linear_algebra_1.Vec3.add(v, center, major); linear_algebra_1.Mat4.targetTo(m, center, v, minor); linear_algebra_1.Vec3.scaleAndAdd(anchor, center, offsetDir, offset); linear_algebra_1.Mat4.setTranslation(m, anchor); linear_algebra_1.Mat4.mul(m, m, linear_algebra_1.Mat4.rotY90); linear_algebra_1.Mat4.scale(m, m, scale); const { getSerialIndex } = structure.serialMapping; const isVertex = theme.color.granularity.startsWith('vertex'); const plane = plane3d_1.Plane3D.fromNormalAndCoplanarPoint((0, plane3d_1.Plane3D)(), linear_algebra_1.Vec3.normalize((0, linear_algebra_1.Vec3)(), normal), anchor); const invM = linear_algebra_1.Mat4.invert((0, linear_algebra_1.Mat4)(), m); const pl = (0, location_iterator_1.PositionLocation)((0, linear_algebra_1.Vec3)(), (0, linear_algebra_1.Vec3)()); const el = structure_1.StructureElement.Location.create(structure); const { units } = structure; let maxRadius = 0; for (let i = 0, il = units.length; i < il; ++i) { const { elements } = units[i]; el.unit = units[i]; for (let j = 0, jl = elements.length; j < jl; ++j) { el.element = elements[j]; const r = theme.size.size(el); if (r > maxRadius) maxRadius = r; } } const imageArray = new Uint8Array(width * height * 4); const groupArray = new Uint8Array(width * height * 4); const distArray = new Float32Array(width * height); distArray.fill(Number.MAX_VALUE); const p = (0, linear_algebra_1.Vec3)(); const pp = (0, linear_algebra_1.Vec3)(); const pn = (0, linear_algebra_1.Vec3)(); if (isVertex) { let i = 0; for (let ih = 0; ih < height; ++ih) { for (let iw = 0; iw < width; ++iw) { const y = ((0, interpolate_1.clamp)(iw + 0.5, 0, width - 1) / width) - 0.5; const x = ((0, interpolate_1.clamp)(ih + 0.5, 0, height - 1) / height) - 0.5; linear_algebra_1.Vec3.set(v, x, -y, 0); linear_algebra_1.Vec3.transformMat4(v, v, m); linear_algebra_1.Vec3.copy(pl.position, v); const c = color(pl, false); color_1.Color.toArray(c, imageArray, i); imageArray[i + 3] = cutout ? 0 : 255; i += 4; } } } else { for (let i = 0, il = width * height * 4; i < il; i += 4) { color_1.Color.toArray(defaultColor, imageArray, i); imageArray[i + 3] = cutout ? 0 : 255; } } for (let i = 0, il = units.length; i < il; ++i) { const unit = units[i]; const { elements, conformation: c } = unit; el.unit = units[i]; for (let j = 0, jl = elements.length; j < jl; ++j) { const eI = elements[j]; el.element = eI; c.position(eI, p); const dist = plane3d_1.Plane3D.distanceToPoint(plane, p); if (Math.abs(dist) > maxRadius) continue; const r = theme.size.size(el); if (Math.abs(dist) > r) continue; const rf = Math.cos(Math.abs(dist) / r); const tol = antialias ? imageResolution * rf : 0; const rTol = r + (tol / 2); const rTolSq = rTol * rTol; linear_algebra_1.Vec3.scaleAndAdd(pp, p, plane.normal, -dist); linear_algebra_1.Vec3.transformMat4(pn, pp, invM); linear_algebra_1.Vec3.addScalar(pn, pn, 0.5); const x = Math.floor(pn[0] * height); const y = width - Math.ceil(pn[1] * width); // Number of grid points, round this up... const ng = Math.ceil(r * scaleFactor); // Extents of grid to consider for this atom const begX = Math.max(0, x - ng); const begY = Math.max(0, y - ng); // Add two to these points: // - x,y are floor'd values so this ensures coverage // - these are loop limits (exclusive) const endX = Math.min(height, x + ng + 2); const endY = Math.min(width, y + ng + 2); linear_algebra_1.Vec3.copy(pl.position, pp); const col = isVertex ? defaultColor : color(el, false); const idx = getSerialIndex(el.unit, el.element); for (let xi = begX; xi < endX; ++xi) { for (let yi = begY; yi < endY; ++yi) { const xx = ((0, interpolate_1.clamp)(xi + 0.5, 0, height - 1) / height) - 0.5; const yy = ((0, interpolate_1.clamp)(yi + 0.5, 0, width - 1) / width) - 0.5; v3set(v, xx, -yy, 0); v3transformMat4(v, v, m); const distSq = v3squaredDistance(v, p); if (distSq > rTolSq) continue; const k = xi * width + yi; if (distSq < distArray[k]) { const k4 = k * 4; const d = Math.sqrt(distSq) - r + tol / 2; let f = d > 0 ? 1 - d / tol : 1; if (isVertex) { if (f === 1) { distArray[k] = distSq; } else { if (cutout) { if (groupArray[k4] !== 0 || groupArray[k4 + 1] !== 0 || groupArray[k4 + 2] !== 0) { f = 1; } } } } else { if (f === 1) { distArray[k] = distSq; color_1.Color.toArray(col, imageArray, k4); } else { if (cutout) { color_1.Color.toArray(col, imageArray, k4); if (groupArray[k4] !== 0 || groupArray[k4 + 1] !== 0 || groupArray[k4 + 2] !== 0) { f = 1; } } else { color_1.Color.toArray(color_1.Color.interpolate(color_1.Color.fromArray(imageArray, k4), col, f), imageArray, k4); } } } (0, number_packing_1.packIntToRGBArray)(idx, groupArray, k4); groupArray[k4 + 3] = antialias ? Math.round(255 * f) : 255; if (cutout) { imageArray[k * 4 + 3] = antialias ? Math.round(255 * f) : 255; } } } } } } const imageTexture = { width, height, array: imageArray, flipY: true }; const groupTexture = { width, height, array: groupArray, flipY: true }; const valueTexture = { width: 1, height: 1, array: new Float32Array(1), flipY: true }; const corners = new Float32Array([ -0.5, 0.5, 0, 0.5, 0.5, 0, -0.5, -0.5, 0, 0.5, -0.5, 0 ]); (0, util_1.transformPositionArray)(m, corners, 0, 4); return image_1.Image.create(imageTexture, corners, groupTexture, valueTexture, trim, -1, image); }