molstar
Version:
A comprehensive macromolecular library.
176 lines (175 loc) • 7.92 kB
JavaScript
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { addFixedCountDashedCylinder, addSimpleCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Sphere3D } from '../../mol-math/geometry';
import { Vec3 } from '../../mol-math/linear-algebra';
import { Shape } from '../../mol-model/shape';
import { StructureElement } from '../../mol-model/structure';
import { addLinkCylinderMesh, DefaultLinkCylinderProps } from '../../mol-repr/structure/visual/util/link';
import { Color } from '../../mol-util/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { stringToWords } from '../../mol-util/string';
import { InteractionKinds } from './model';
function visualParams({ color, style = 'dashed', radius = 0.04 }) {
return PD.Group({
color: PD.Color(color),
style: PD.Select(style, [['dashed', 'Dashed'], ['solid', 'Solid']]),
radius: PD.Numeric(radius, { min: 0.01, max: 1, step: 0.01 }),
});
}
function hydrogenVisualParams({ color, style = 'dashed', radius = 0.04, showArrow = true, arrowOffset = 0.18 }) {
return PD.Group({
color: PD.Color(color),
style: PD.Select(style, [['dashed', 'Dashed'], ['solid', 'Solid']]),
radius: PD.Numeric(radius, { min: 0.01, max: 1, step: 0.01 }),
showArrow: PD.Boolean(showArrow),
arrowOffset: PD.Numeric(arrowOffset, { min: 0, max: 1, step: 0.001 }),
});
}
export const InteractionVisualParams = {
kinds: PD.MultiSelect(InteractionKinds, InteractionKinds.map(k => [k, stringToWords(k)])),
styles: PD.Group({
'unknown': visualParams({ color: Color(0x0) }),
'ionic': visualParams({ color: Color(0xADD8E6) }),
'pi-stacking': visualParams({ color: Color(0x1E3F66) }),
'cation-pi': visualParams({ color: Color(0x06402B) }),
'halogen-bond': visualParams({ color: Color(0xFFDE21) }),
'hydrogen-bond': hydrogenVisualParams({ color: Color(0x0), style: 'solid' }),
'weak-hydrogen-bond': hydrogenVisualParams({ color: Color(0x0) }),
'hydrophobic': visualParams({ color: Color(0x555555) }),
'metal-coordination': visualParams({ color: Color(0x952e8f) }),
'covalent': PD.Group({
color: PD.Color(Color(0x999999)),
radius: PD.Numeric(0.1, { min: 0.01, max: 1, step: 0.01 }),
}),
})
};
export function buildInteractionsShape(interactions, params, prev) {
var _a, _b, _c;
const mesh = MeshBuilder.createState(interactions.elements.length * 128, 1024, prev);
mesh.currentGroup = -1;
const tooltips = new Map();
const visible = new Set(params.kinds);
const kindsToWords = new Map(InteractionKinds.map(k => [k, stringToWords(k)]));
const colors = new Map();
const bA = { sphere: Sphere3D.zero() };
const bB = { sphere: Sphere3D.zero() };
const pA = Vec3();
const pB = Vec3();
const dir = Vec3();
const capPos = Vec3();
const addLinkOptions = {
builderState: mesh,
props: { ...DefaultLinkCylinderProps },
};
const addLinkParams = {
a: pA,
b: pB,
group: 0,
linkStub: false,
linkStyle: 0 /* LinkStyle.Solid */,
linkRadius: 0,
};
for (const interaction of interactions.elements) {
mesh.currentGroup++;
if (!visible.has(interaction.info.kind))
continue;
let tooltip;
if (interaction.info.kind === 'covalent') {
if (interaction.info.degree === 'aromatic')
tooltip = 'Aromatic';
else if (interaction.info.degree === 1)
tooltip = 'Single';
else if (interaction.info.degree === 2)
tooltip = 'Double';
else if (interaction.info.degree === 3)
tooltip = 'Triple';
else if (interaction.info.degree === 4)
tooltip = 'Quadruple';
else
tooltip = 'Covalent';
}
else {
tooltip = (_a = kindsToWords.get(interaction.info.kind)) !== null && _a !== void 0 ? _a : interaction.info.kind;
}
if ((_b = interaction.sourceSchema) === null || _b === void 0 ? void 0 : _b.description) {
tooltip += ` (${interaction.sourceSchema.description})`;
}
tooltips.set(mesh.currentGroup, tooltip);
const options = params.styles[interaction.info.kind];
let style = 'solid';
if (interaction.info.kind !== 'covalent') {
style = params.styles[interaction.info.kind].style;
}
colors.set(mesh.currentGroup, params.styles[interaction.info.kind].color);
StructureElement.Loci.getBoundary(interaction.a, undefined, bA);
StructureElement.Loci.getBoundary(interaction.b, undefined, bB);
Vec3.sub(dir, bB.sphere.center, bA.sphere.center);
Vec3.normalize(dir, dir);
Vec3.copy(pA, bA.sphere.center);
Vec3.copy(pB, bB.sphere.center);
if (interaction.info.kind === 'hydrogen-bond' || interaction.info.kind === 'weak-hydrogen-bond') {
const hydrogenStyle = params.styles[interaction.info.kind];
if (hydrogenStyle.showArrow && hydrogenStyle.arrowOffset > 0) {
Vec3.scaleAndAdd(pB, pB, dir, -hydrogenStyle.arrowOffset);
}
if (hydrogenStyle.showArrow) {
const height = options.radius * 3;
Vec3.scaleAndAdd(capPos, pB, dir, -height);
cylinder(mesh, pA, capPos, options.radius, style);
addSimpleCylinder(mesh, capPos, pB, { radiusTop: 0, radiusBottom: height, topCap: false, bottomCap: true });
}
else {
cylinder(mesh, pA, pB, options.radius, style);
}
}
else {
if (interaction.info.kind !== 'covalent') {
cylinder(mesh, pA, pB, options.radius, style);
}
else {
addLinkParams.group = mesh.currentGroup;
addLinkParams.linkRadius = options.radius;
const degree = (_c = interaction.info.degree) !== null && _c !== void 0 ? _c : 1;
if (degree === 'aromatic')
addLinkParams.linkStyle = 7 /* LinkStyle.Aromatic */;
else if (degree === 2)
addLinkParams.linkStyle = 2 /* LinkStyle.Double */;
else if (degree === 3)
addLinkParams.linkStyle = 4 /* LinkStyle.Triple */;
else
addLinkParams.linkStyle = 0 /* LinkStyle.Solid */;
addLinkParams.a = pA;
addLinkParams.b = pB;
addLinkCylinderMesh(addLinkOptions, addLinkParams);
addLinkParams.a = pB;
addLinkParams.b = pA;
addLinkCylinderMesh(addLinkOptions, addLinkParams);
}
}
}
return Shape.create('Interactions', interactions, MeshBuilder.getMesh(mesh), (g) => { var _a; return (_a = colors.get(g)) !== null && _a !== void 0 ? _a : 0; }, (g) => 1, (g) => { var _a; return (_a = tooltips.get(g)) !== null && _a !== void 0 ? _a : ''; });
}
function cylinder(mesh, a, b, radius, style) {
const props = {
radiusBottom: radius,
radiusTop: radius,
topCap: true,
bottomCap: true,
};
if (style === 'dashed') {
const dist = Vec3.distance(a, b);
const count = Math.ceil(dist / (2 * radius));
addFixedCountDashedCylinder(mesh, a, b, 1.0, count, true, props);
}
else {
if (style !== 'solid') {
console.warn(`Unknown style '${style}', using 'solid' instead.`);
}
addSimpleCylinder(mesh, a, b, props);
}
}