molstar
Version:
A comprehensive macromolecular library.
323 lines (322 loc) • 17.5 kB
JavaScript
"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);
}