molstar
Version:
A comprehensive macromolecular library.
1,022 lines (1,021 loc) • 47.6 kB
JavaScript
/**
* Copyright (c) 2024-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Adam Midlik <midlik@gmail.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MVSBuildPrimitiveShape = exports.MVSInlinePrimitiveData = exports.MVSDownloadPrimitiveData = exports.MVSPrimitiveShapes = exports.MVSPrimitivesData = void 0;
exports.getPrimitiveStructureRefs = getPrimitiveStructureRefs;
const lines_1 = require("../../../mol-geo/geometry/lines/lines");
const lines_builder_1 = require("../../../mol-geo/geometry/lines/lines-builder");
const cylinder_1 = require("../../../mol-geo/geometry/mesh/builder/cylinder");
const ellipsoid_1 = require("../../../mol-geo/geometry/mesh/builder/ellipsoid");
const mesh_1 = require("../../../mol-geo/geometry/mesh/mesh");
const mesh_builder_1 = require("../../../mol-geo/geometry/mesh/mesh-builder");
const text_1 = require("../../../mol-geo/geometry/text/text");
const text_builder_1 = require("../../../mol-geo/geometry/text/text-builder");
const box_1 = require("../../../mol-geo/primitive/box");
const circle_1 = require("../../../mol-geo/primitive/circle");
const string_like_1 = require("../../../mol-io/common/string-like");
const geometry_1 = require("../../../mol-math/geometry");
const linear_algebra_1 = require("../../../mol-math/linear-algebra");
const misc_1 = require("../../../mol-math/misc");
const shape_1 = require("../../../mol-model/shape");
const structure_1 = require("../../../mol-model/structure");
const structure_query_1 = require("../../../mol-plugin-state/helpers/structure-query");
const objects_1 = require("../../../mol-plugin-state/objects");
const mol_state_1 = require("../../../mol-state");
const mol_task_1 = require("../../../mol-task");
const mol_util_1 = require("../../../mol-util");
const array_1 = require("../../../mol-util/array");
const assets_1 = require("../../../mol-util/assets");
const color_1 = require("../../../mol-util/color");
const param_definition_1 = require("../../../mol-util/param-definition");
const string_1 = require("../../../mol-util/string");
const selections_1 = require("../helpers/selections");
const utils_1 = require("../helpers/utils");
const param_types_1 = require("../tree/mvs/param-types");
const annotation_structure_component_1 = require("./annotation-structure-component");
function getPrimitiveStructureRefs(primitives) {
var _a, _b, _c;
const refs = new Set();
for (const c of (_a = primitives.children) !== null && _a !== void 0 ? _a : []) {
if (c.kind !== 'primitive')
continue;
const p = c.params;
(_c = (_b = Builders[p.kind]).resolveRefs) === null || _c === void 0 ? void 0 : _c.call(_b, p, refs);
}
return refs;
}
class MVSPrimitivesData extends objects_1.PluginStateObject.Create({ name: 'Primitive Data', typeClass: 'Object' }) {
}
exports.MVSPrimitivesData = MVSPrimitivesData;
class MVSPrimitiveShapes extends objects_1.PluginStateObject.Create({ name: 'Primitive Shapes', typeClass: 'Object' }) {
}
exports.MVSPrimitiveShapes = MVSPrimitiveShapes;
exports.MVSDownloadPrimitiveData = (0, annotation_structure_component_1.MVSTransform)({
name: 'mvs-download-primitive-data',
display: { name: 'MVS Primitives' },
from: [objects_1.PluginStateObject.Root, objects_1.PluginStateObject.Molecule.Structure],
to: MVSPrimitivesData,
params: {
uri: param_definition_1.ParamDefinition.Url('', { isHidden: true }),
format: param_definition_1.ParamDefinition.Text('mvs-node-json', { isHidden: true })
},
})({
apply({ a, params, cache }, plugin) {
return mol_task_1.Task.create('Download Primitive Data', async (ctx) => {
const url = assets_1.Asset.getUrlAsset(plugin.managers.asset, params.uri);
const asset = await plugin.managers.asset.resolve(url, 'string').runInContext(ctx);
const node = JSON.parse(string_like_1.StringLike.toString(asset.data));
cache.asset = asset;
return new MVSPrimitivesData({
node,
defaultStructure: objects_1.PluginStateObject.Molecule.Structure.is(a) ? a.data : undefined,
structureRefs: {},
primitives: getPrimitives(node),
options: { ...node.params },
positionCache: new Map(),
instances: getInstances(node.params),
}, { label: 'Primitive Data' });
});
},
dispose({ cache }) {
var _a;
(_a = cache === null || cache === void 0 ? void 0 : cache.asset) === null || _a === void 0 ? void 0 : _a.dispose();
},
});
exports.MVSInlinePrimitiveData = (0, annotation_structure_component_1.MVSTransform)({
name: 'mvs-inline-primitive-data',
display: { name: 'MVS Primitives' },
from: [objects_1.PluginStateObject.Root, objects_1.PluginStateObject.Molecule.Structure],
to: MVSPrimitivesData,
params: {
node: param_definition_1.ParamDefinition.Value(undefined, { isHidden: true }),
},
})({
apply({ a, params }) {
return new MVSPrimitivesData({
node: params.node,
defaultStructure: objects_1.PluginStateObject.Molecule.Structure.is(a) ? a.data : undefined,
structureRefs: {},
primitives: getPrimitives(params.node),
options: { ...params.node.params },
positionCache: new Map(),
instances: getInstances(params.node.params),
}, { label: 'Primitive Data' });
}
});
exports.MVSBuildPrimitiveShape = (0, annotation_structure_component_1.MVSTransform)({
name: 'mvs-build-primitive-shape',
display: { name: 'MVS Primitives' },
from: MVSPrimitivesData,
to: objects_1.PluginStateObject.Shape.Provider,
params: {
kind: param_definition_1.ParamDefinition.Text('mesh')
}
})({
apply({ a, params, dependencies }) {
var _a, _b, _c, _d, _e, _f;
const structureRefs = dependencies ? (0, utils_1.collectMVSReferences)([objects_1.PluginStateObject.Molecule.Structure], dependencies) : {};
const context = { ...a.data, structureRefs };
const label = (0, string_1.capitalize)(params.kind);
if (params.kind === 'mesh') {
if (!hasPrimitiveKind(a.data, 'mesh'))
return mol_state_1.StateObject.Null;
return new objects_1.PluginStateObject.Shape.Provider({
label,
data: context,
params: param_definition_1.ParamDefinition.withDefaults(mesh_1.Mesh.Params, { alpha: (_b = (_a = a.data.options) === null || _a === void 0 ? void 0 : _a.opacity) !== null && _b !== void 0 ? _b : 1 }),
getShape: (_, data, __, prev) => buildPrimitiveMesh(data, prev === null || prev === void 0 ? void 0 : prev.geometry),
geometryUtils: mesh_1.Mesh.Utils,
}, { label });
}
else if (params.kind === 'labels') {
if (!hasPrimitiveKind(a.data, 'label'))
return mol_state_1.StateObject.Null;
return new objects_1.PluginStateObject.Shape.Provider({
label,
data: context,
params: param_definition_1.ParamDefinition.withDefaults(DefaultLabelParams, { alpha: (_d = (_c = a.data.options) === null || _c === void 0 ? void 0 : _c.label_opacity) !== null && _d !== void 0 ? _d : 1 }),
getShape: (_, data, __, prev) => buildPrimitiveLabels(data, prev === null || prev === void 0 ? void 0 : prev.geometry),
geometryUtils: text_1.Text.Utils,
}, { label });
}
else if (params.kind === 'lines') {
if (!hasPrimitiveKind(a.data, 'line'))
return mol_state_1.StateObject.Null;
return new objects_1.PluginStateObject.Shape.Provider({
label,
data: context,
params: param_definition_1.ParamDefinition.withDefaults(lines_1.Lines.Params, { alpha: (_f = (_e = a.data.options) === null || _e === void 0 ? void 0 : _e.opacity) !== null && _f !== void 0 ? _f : 1 }),
getShape: (_, data, __, prev) => buildPrimitiveLines(data, prev === null || prev === void 0 ? void 0 : prev.geometry),
geometryUtils: lines_1.Lines.Utils,
}, { label });
}
return mol_state_1.StateObject.Null;
}
});
/* **************************************************** */
class GroupManager {
constructor() {
this.current = -1;
this.groupToNodeMap = new Map();
this.sizes = new Map();
this.colors = new Map();
this.tooltips = new Map();
}
allocateSingle(node) {
const group = ++this.current;
this.groupToNodeMap.set(group, node);
return group;
}
allocateMany(node, groups) {
const newGroups = new Map();
const base = this.current;
for (const g of groups) {
if (newGroups.has(g))
continue;
const group = base + newGroups.size + 1;
this.groupToNodeMap.set(group, node);
newGroups.set(g, group);
}
this.current += newGroups.size + 1;
return newGroups;
}
updateColor(group, color) {
const c = (0, utils_1.decodeColor)(color);
if (typeof c === 'number')
this.colors.set(group, c);
}
updateTooltip(group, tooltip) {
if (typeof tooltip === 'string')
this.tooltips.set(group, tooltip);
}
updateSize(group, size) {
if (typeof size === 'number')
this.sizes.set(group, size);
}
}
function printEmptySelectionWarning(ctx, position) {
if (!ctx.emptySelectionWarningPrinted) {
console.warn('Some primitives use positions which refer to empty substructure, not showing these primitives.', position, '(There may be more)');
ctx.emptySelectionWarningPrinted = true;
}
}
const BaseLabelProps = {
...param_definition_1.ParamDefinition.getDefaultValues(text_1.Text.Params),
attachment: 'middle-center',
fontQuality: 3,
fontWeight: 'normal',
borderWidth: 0.15,
borderColor: (0, color_1.Color)(0x0),
background: false,
backgroundOpacity: 0.5,
tether: false,
};
const DefaultLabelParams = param_definition_1.ParamDefinition.withDefaults(text_1.Text.Params, BaseLabelProps);
const Builders = {
mesh: {
builders: {
mesh: addMesh,
line: addMeshWireframe,
},
isApplicable: {
mesh: (m) => m.show_triangles,
line: (m) => m.show_wireframe,
},
},
lines: {
builders: {
line: addLines,
},
},
tube: {
builders: {
mesh: addTubeMesh,
},
resolveRefs: resolveLineRefs,
},
arrow: {
builders: {
mesh: addArrowMesh,
},
resolveRefs: (params, refs) => {
addRef(params.start, refs);
if (params.end)
addRef(params.end, refs);
},
},
label: {
builders: {
label: addPrimitiveLabel,
},
resolveRefs: resolveLabelRefs,
},
distance_measurement: {
builders: {
mesh: addDistanceMesh,
label: addDistanceLabel,
},
resolveRefs: resolveLineRefs,
},
angle_measurement: {
builders: {
mesh: addAngleMesh,
label: addAngleLabel,
},
resolveRefs: (params, refs) => {
addRef(params.a, refs);
addRef(params.b, refs);
addRef(params.c, refs);
},
},
ellipse: {
builders: {
mesh: addEllipseMesh,
},
resolveRefs: (params, refs) => {
addRef(params.center, refs);
if (params.major_axis_endpoint)
addRef(params.major_axis_endpoint, refs);
if (params.minor_axis_endpoint)
addRef(params.minor_axis_endpoint, refs);
},
},
ellipsoid: {
builders: {
mesh: addEllipsoidMesh,
},
resolveRefs: (params, refs) => {
addRef(params.center, refs);
if (params.major_axis_endpoint)
addRef(params.major_axis_endpoint, refs);
if (params.minor_axis_endpoint)
addRef(params.minor_axis_endpoint, refs);
},
},
box: {
builders: {
mesh: addBoxMesh,
},
resolveRefs: (params, refs) => {
addRef(params.center, refs);
},
}
};
function getPrimitives(primitives) {
var _a;
return ((_a = primitives.children) !== null && _a !== void 0 ? _a : []).filter(c => c.kind === 'primitive');
}
function addRef(position, refs) {
if ((0, param_types_1.isPrimitiveComponentExpressions)(position) && position.structure_ref) {
refs.add(position.structure_ref);
}
}
function hasPrimitiveKind(context, kind) {
var _a;
for (const c of context.primitives) {
const params = c.params;
const b = Builders[params.kind];
const builderFunction = b.builders[kind];
if (builderFunction) {
const test = (_a = b.isApplicable) === null || _a === void 0 ? void 0 : _a[kind];
if (test === undefined || test(params, context)) {
return true;
}
}
}
return false;
}
/** Save resolved position into `targetPosition`.
* Return `true` if the resolved position is defined (i.e. vector or non-empty selection);
* return `false` if the resolved position is not defined (i.e. empty selection). */
function resolveBasePosition(context, position, targetPosition) {
return resolvePosition(context, position, targetPosition, undefined, undefined);
}
const _EmptySphere = geometry_1.Sphere3D.zero();
const _EmptyBox = geometry_1.Box3D.zero();
/** Save resolved position into `targetPosition`, `targetSphere`, `targetBox`.
* Return `true` if the resolved position is defined (i.e. vector or non-empty selection);
* return `false` if the resolved position is not defined (i.e. empty selection). */
function resolvePosition(context, position, targetPosition, targetSphere, targetBox) {
let expr;
let pivotRef;
if ((0, param_types_1.isVector3)(position)) {
if (targetPosition)
linear_algebra_1.Vec3.copy(targetPosition, position);
if (targetSphere)
geometry_1.Sphere3D.set(targetSphere, position, 0);
if (targetBox)
geometry_1.Box3D.set(targetBox, position, position);
return true;
}
if ((0, param_types_1.isPrimitiveComponentExpressions)(position)) {
// TODO: take schema into account for possible optimization
expr = (0, selections_1.rowsToExpression)(position.expressions);
pivotRef = position.structure_ref;
}
else if ((0, param_types_1.isComponentExpression)(position)) {
expr = (0, selections_1.rowToExpression)(position);
}
if (!expr) {
console.error('Invalid expression', position);
throw new Error('Invalid primitive potition expression, see console for details.');
}
const pivot = !pivotRef ? context.defaultStructure : context.structureRefs[pivotRef];
if (!pivot) {
throw new Error(`Structure with ref '${pivotRef !== null && pivotRef !== void 0 ? pivotRef : '<default>'}' not found.`);
}
const cacheKey = JSON.stringify(position);
if (context.positionCache.has(cacheKey)) {
const [isDefined, sphere, box] = context.positionCache.get(cacheKey);
if (targetPosition)
linear_algebra_1.Vec3.copy(targetPosition, sphere.center);
if (targetSphere)
geometry_1.Sphere3D.copy(targetSphere, sphere);
if (targetBox)
geometry_1.Box3D.copy(targetBox, box);
return isDefined;
}
const { selection } = structure_query_1.StructureQueryHelper.createAndRun(pivot, expr);
let box;
let sphere;
let isDefined;
if (structure_1.StructureSelection.isEmpty(selection)) {
if (targetPosition)
linear_algebra_1.Vec3.set(targetPosition, 0, 0, 0);
box = _EmptyBox;
sphere = _EmptySphere;
isDefined = false;
printEmptySelectionWarning(context, position);
}
else {
const loci = structure_1.StructureSelection.toLociWithSourceUnits(selection);
const boundary = structure_1.StructureElement.Loci.getBoundary(loci);
if (targetPosition)
linear_algebra_1.Vec3.copy(targetPosition, boundary.sphere.center);
box = boundary.box;
sphere = boundary.sphere;
isDefined = true;
}
if (targetSphere)
geometry_1.Sphere3D.copy(targetSphere, sphere);
if (targetBox)
geometry_1.Box3D.copy(targetBox, box);
context.positionCache.set(cacheKey, [isDefined, sphere, box]);
return isDefined;
}
function getInstances(options) {
var _a;
if (!((_a = options === null || options === void 0 ? void 0 : options.instances) === null || _a === void 0 ? void 0 : _a.length))
return undefined;
return options.instances.map(i => linear_algebra_1.Mat4.fromArray((0, linear_algebra_1.Mat4)(), i, 0));
}
function buildPrimitiveMesh(context, prev) {
var _a, _b, _c, _d, _e, _f;
const meshBuilder = mesh_builder_1.MeshBuilder.createState(1024, 1024, prev);
const state = { groups: new GroupManager(), mesh: meshBuilder };
meshBuilder.currentGroup = -1;
for (const c of context.primitives) {
const p = c.params;
const b = Builders[p.kind];
if (!b) {
console.warn(`Primitive ${p.kind} not supported`);
continue;
}
(_b = (_a = b.builders).mesh) === null || _b === void 0 ? void 0 : _b.call(_a, context, state, c, p);
}
const { colors, tooltips } = state.groups;
const tooltip = (_d = (_c = context.options) === null || _c === void 0 ? void 0 : _c.tooltip) !== null && _d !== void 0 ? _d : '';
const color = (_f = (0, utils_1.decodeColor)((_e = context.options) === null || _e === void 0 ? void 0 : _e.color)) !== null && _f !== void 0 ? _f : (0, color_1.Color)(0);
return shape_1.Shape.create('Mesh', {
kind: 'mvs-primitives',
node: context.node,
groupToNode: state.groups.groupToNodeMap,
}, mesh_builder_1.MeshBuilder.getMesh(meshBuilder), (g) => { var _a; return (_a = colors.get(g)) !== null && _a !== void 0 ? _a : color; }, (g) => 1, (g) => { var _a; return (_a = tooltips.get(g)) !== null && _a !== void 0 ? _a : tooltip; }, context.instances);
}
function buildPrimitiveLines(context, prev) {
var _a, _b, _c, _d, _e, _f;
const linesBuilder = lines_builder_1.LinesBuilder.create(1024, 1024, prev);
const state = { groups: new GroupManager(), lines: linesBuilder };
for (const c of context.primitives) {
const p = c.params;
const b = Builders[p.kind];
if (!b) {
console.warn(`Primitive ${p.kind} not supported`);
continue;
}
(_b = (_a = b.builders).line) === null || _b === void 0 ? void 0 : _b.call(_a, context, state, c, p);
}
const { colors, sizes, tooltips } = state.groups;
const tooltip = (_d = (_c = context.options) === null || _c === void 0 ? void 0 : _c.tooltip) !== null && _d !== void 0 ? _d : '';
const color = (_f = (0, utils_1.decodeColor)((_e = context.options) === null || _e === void 0 ? void 0 : _e.color)) !== null && _f !== void 0 ? _f : (0, color_1.Color)(0);
return shape_1.Shape.create('Lines', {
kind: 'mvs-primitives',
node: context.node,
groupToNode: state.groups.groupToNodeMap,
}, linesBuilder.getLines(), (g) => { var _a; return (_a = colors.get(g)) !== null && _a !== void 0 ? _a : color; }, (g) => { var _a; return (_a = sizes.get(g)) !== null && _a !== void 0 ? _a : 1; }, (g) => { var _a; return (_a = tooltips.get(g)) !== null && _a !== void 0 ? _a : tooltip; }, context.instances);
}
function buildPrimitiveLabels(context, prev) {
var _a, _b, _c, _d;
const labelsBuilder = text_builder_1.TextBuilder.create(BaseLabelProps, 1024, 1024, prev);
const state = { groups: new GroupManager(), labels: labelsBuilder };
for (const c of context.primitives) {
const p = c.params;
const b = Builders[p.kind];
if (!b) {
console.warn(`Primitive ${p.kind} not supported`);
continue;
}
(_b = (_a = b.builders).label) === null || _b === void 0 ? void 0 : _b.call(_a, context, state, c, p);
}
const color = (_d = (0, utils_1.decodeColor)((_c = context.options) === null || _c === void 0 ? void 0 : _c.label_color)) !== null && _d !== void 0 ? _d : (0, color_1.Color)(0);
const { colors, sizes, tooltips } = state.groups;
return shape_1.Shape.create('Labels', {
kind: 'mvs-primitives',
node: context.node,
groupToNode: state.groups.groupToNodeMap,
}, labelsBuilder.getText(), (g) => { var _a; return (_a = colors.get(g)) !== null && _a !== void 0 ? _a : color; }, (g) => { var _a; return (_a = sizes.get(g)) !== null && _a !== void 0 ? _a : 1; }, (g) => { var _a; return (_a = tooltips.get(g)) !== null && _a !== void 0 ? _a : ''; }, context.instances);
}
function addMeshFaces(context, groups, node, params, addFace) {
const a = linear_algebra_1.Vec3.zero();
const b = linear_algebra_1.Vec3.zero();
const c = linear_algebra_1.Vec3.zero();
let { indices, vertices, triangle_groups } = params;
const nTriangles = Math.floor(indices.length / 3);
triangle_groups !== null && triangle_groups !== void 0 ? triangle_groups : (triangle_groups = (0, array_1.range)(nTriangles)); // implicit grouping (triangle i = group i)
const groupSet = groups.allocateMany(node, triangle_groups);
for (let i = 0; i < nTriangles; i++) {
const mvsGroup = triangle_groups[i];
const builderGroup = groupSet.get(mvsGroup);
linear_algebra_1.Vec3.fromArray(a, vertices, 3 * indices[3 * i]);
linear_algebra_1.Vec3.fromArray(b, vertices, 3 * indices[3 * i + 1]);
linear_algebra_1.Vec3.fromArray(c, vertices, 3 * indices[3 * i + 2]);
addFace(mvsGroup, builderGroup, a, b, c);
}
}
function addMesh(context, { groups, mesh }, node, params) {
if (!params.show_triangles)
return;
const { group_colors, group_tooltips, color, tooltip } = params;
addMeshFaces(context, groups, node, params, (mvsGroup, builderGroup, a, b, c) => {
var _a, _b;
groups.updateColor(builderGroup, (_a = group_colors[mvsGroup]) !== null && _a !== void 0 ? _a : color);
groups.updateTooltip(builderGroup, (_b = group_tooltips[mvsGroup]) !== null && _b !== void 0 ? _b : tooltip);
mesh.currentGroup = builderGroup;
mesh_builder_1.MeshBuilder.addTriangle(mesh, a, b, c);
});
// this could be slightly improved by only updating color and tooltip once per group instead of once per triangle
}
function addMeshWireframe(context, { groups, lines }, node, params) {
if (!params.show_wireframe)
return;
const width = params.wireframe_width;
const { group_colors, group_tooltips, wireframe_color, color, tooltip } = params;
addMeshFaces(context, groups, node, params, (mvsGroup, builderGroup, a, b, c) => {
var _a, _b;
groups.updateColor(builderGroup, (_a = wireframe_color !== null && wireframe_color !== void 0 ? wireframe_color : group_colors[mvsGroup]) !== null && _a !== void 0 ? _a : color);
groups.updateTooltip(builderGroup, (_b = group_tooltips[mvsGroup]) !== null && _b !== void 0 ? _b : tooltip);
groups.updateSize(builderGroup, width);
lines.add(a[0], a[1], a[2], b[0], b[1], b[2], builderGroup);
lines.add(b[0], b[1], b[2], c[0], c[1], c[2], builderGroup);
lines.add(c[0], c[1], c[2], a[0], a[1], a[2], builderGroup);
});
}
function addLines(context, { groups, lines }, node, params) {
var _a, _b, _c;
const a = linear_algebra_1.Vec3.zero();
const b = linear_algebra_1.Vec3.zero();
let { indices, vertices, line_groups, group_colors, group_tooltips, group_widths } = params;
const width = params.width;
const nLines = Math.floor(indices.length / 2);
line_groups !== null && line_groups !== void 0 ? line_groups : (line_groups = (0, array_1.range)(nLines)); // implicit grouping (line i = group i)
const groupSet = groups.allocateMany(node, line_groups);
for (let i = 0; i < nLines; i++) {
const mvsGroup = line_groups[i];
const builderGroup = groupSet.get(mvsGroup);
groups.updateColor(builderGroup, (_a = group_colors[mvsGroup]) !== null && _a !== void 0 ? _a : params.color);
groups.updateTooltip(builderGroup, (_b = group_tooltips[mvsGroup]) !== null && _b !== void 0 ? _b : params.tooltip);
groups.updateSize(builderGroup, (_c = group_widths[mvsGroup]) !== null && _c !== void 0 ? _c : width);
linear_algebra_1.Vec3.fromArray(a, vertices, 3 * indices[2 * i]);
linear_algebra_1.Vec3.fromArray(b, vertices, 3 * indices[2 * i + 1]);
lines.add(a[0], a[1], a[2], b[0], b[1], b[2], builderGroup);
}
}
function resolveLineRefs(params, refs) {
addRef(params.start, refs);
addRef(params.end, refs);
}
const lStart = linear_algebra_1.Vec3.zero();
const lEnd = linear_algebra_1.Vec3.zero();
function addTubeMesh(context, { groups, mesh }, node, params, options) {
if (!(options === null || options === void 0 ? void 0 : options.skipResolvePosition)) {
const startDefined = resolveBasePosition(context, params.start, lStart);
const endDefined = resolveBasePosition(context, params.end, lEnd);
if (!startDefined || !endDefined)
return;
}
const radius = params.radius;
const cylinderProps = {
radiusBottom: radius,
radiusTop: radius,
topCap: true,
bottomCap: true,
};
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
if (params.dash_length) {
const dist = linear_algebra_1.Vec3.distance(lStart, lEnd);
const count = Math.ceil(dist / (2 * params.dash_length));
(0, cylinder_1.addFixedCountDashedCylinder)(mesh, lStart, lEnd, 1.0, count, true, cylinderProps);
}
else {
(0, cylinder_1.addSimpleCylinder)(mesh, lStart, lEnd, cylinderProps);
}
}
const ArrowState = {
start: linear_algebra_1.Vec3.zero(),
end: linear_algebra_1.Vec3.zero(),
dir: linear_algebra_1.Vec3.zero(),
startCap: linear_algebra_1.Vec3.zero(),
endCap: linear_algebra_1.Vec3.zero(),
};
function addArrowMesh(context, { groups, mesh }, node, params) {
var _a, _b, _c, _d;
const startDefined = resolveBasePosition(context, params.start, ArrowState.start);
if (!startDefined)
return;
if (params.end) {
const endDefined = resolveBasePosition(context, params.end, ArrowState.end);
if (!endDefined)
return;
}
else if (params.direction) {
linear_algebra_1.Vec3.add(ArrowState.end, ArrowState.start, params.direction);
}
else {
console.warn(`Primitive arrow does not contain "end" nor "distance". Not showing.`);
return;
}
linear_algebra_1.Vec3.sub(ArrowState.dir, ArrowState.end, ArrowState.start);
linear_algebra_1.Vec3.normalize(ArrowState.dir, ArrowState.dir);
if (params.length) {
linear_algebra_1.Vec3.scaleAndAdd(ArrowState.end, ArrowState.start, ArrowState.dir, params.length);
}
const length = linear_algebra_1.Vec3.distance(ArrowState.start, ArrowState.end);
if (length < 1e-3)
return;
const tubeRadius = params.tube_radius;
const tubeProps = {
radiusBottom: tubeRadius,
radiusTop: tubeRadius,
topCap: !params.show_end_cap,
bottomCap: !params.show_start_cap,
};
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
if (params.show_start_cap) {
const startRadius = (_a = params.start_cap_radius) !== null && _a !== void 0 ? _a : 2 * tubeRadius;
const startCapLength = (_b = params.start_cap_length) !== null && _b !== void 0 ? _b : 2 * startRadius;
linear_algebra_1.Vec3.scaleAndAdd(ArrowState.startCap, ArrowState.start, ArrowState.dir, startCapLength);
(0, cylinder_1.addSimpleCylinder)(mesh, ArrowState.startCap, ArrowState.start, {
radiusBottom: startRadius,
radiusTop: 0,
topCap: false,
bottomCap: true,
radialSegments: 12,
});
}
else {
linear_algebra_1.Vec3.copy(ArrowState.startCap, ArrowState.start);
}
if (params.show_end_cap) {
const endRadius = (_c = params.end_cap_radius) !== null && _c !== void 0 ? _c : 2 * tubeRadius;
const endCapLength = (_d = params.end_cap_length) !== null && _d !== void 0 ? _d : 2 * endRadius;
linear_algebra_1.Vec3.scaleAndAdd(ArrowState.endCap, ArrowState.end, ArrowState.dir, -endCapLength);
(0, cylinder_1.addSimpleCylinder)(mesh, ArrowState.endCap, ArrowState.end, {
radiusBottom: endRadius,
radiusTop: 0,
topCap: false,
bottomCap: true,
radialSegments: 12,
});
}
else {
linear_algebra_1.Vec3.copy(ArrowState.endCap, ArrowState.end);
}
if (params.show_tube) {
if (params.tube_dash_length) {
const dist = linear_algebra_1.Vec3.distance(ArrowState.startCap, ArrowState.endCap);
const count = Math.ceil(dist / (2 * params.tube_dash_length));
(0, cylinder_1.addFixedCountDashedCylinder)(mesh, ArrowState.startCap, ArrowState.endCap, 1.0, count, true, tubeProps);
}
else {
(0, cylinder_1.addSimpleCylinder)(mesh, ArrowState.startCap, ArrowState.endCap, tubeProps);
}
}
}
/** Return distance in angstroms, or `undefined` if any of the endpoints corresponds to empty substructure.
* This function also sets `lStart`, `lEnd` globals. */
function computeDistance(context, start, end) {
const startDefined = resolveBasePosition(context, start, lStart);
const endDefined = resolveBasePosition(context, end, lEnd);
if (startDefined && endDefined)
return linear_algebra_1.Vec3.distance(lStart, lEnd);
else
return undefined;
}
// /** Return text for distance measurement label/tooltip. */
function distanceLabel(distance, params) {
const distStr = `${(0, mol_util_1.round)(distance, 2)} Å`;
if (typeof params.label_template === 'string')
return params.label_template.replace('{{distance}}', distStr);
else
return distStr;
}
function addDistanceMesh(context, state, node, params) {
const distance = computeDistance(context, params.start, params.end); // sets lStart, lEnd
if (distance === undefined)
return; // empty substructure in measurement
const tooltip = distanceLabel(distance, params);
addTubeMesh(context, state, node, { ...params, tooltip }, { skipResolvePosition: true });
}
const labelPos = linear_algebra_1.Vec3.zero();
function addDistanceLabel(context, state, node, params) {
const { labels, groups } = state;
const dist = computeDistance(context, params.start, params.end); // sets lStart, lEnd
if (dist === undefined)
return; // empty substructure in measurement
let size;
if (typeof params.label_size === 'number') {
size = params.label_size;
}
else {
size = Math.max(dist * (params.label_auto_size_scale), params.label_auto_size_min);
}
linear_algebra_1.Vec3.add(labelPos, lStart, lEnd);
linear_algebra_1.Vec3.scale(labelPos, labelPos, 0.5);
const group = groups.allocateSingle(node);
groups.updateColor(group, params.label_color);
groups.updateSize(group, size);
labels.add(distanceLabel(dist, params), labelPos[0], labelPos[1], labelPos[2], 1.05 * (params.radius), 1, group);
}
const AngleState = {
isDefined: false,
a: (0, linear_algebra_1.Vec3)(),
b: (0, linear_algebra_1.Vec3)(),
c: (0, linear_algebra_1.Vec3)(),
ba: (0, linear_algebra_1.Vec3)(),
bc: (0, linear_algebra_1.Vec3)(),
labelPos: (0, linear_algebra_1.Vec3)(),
/** Sector radius */
radius: 0,
label: '',
};
function syncAngleState(context, params) {
const aDefined = resolveBasePosition(context, params.a, AngleState.a);
const bDefined = resolveBasePosition(context, params.b, AngleState.b);
const cDefined = resolveBasePosition(context, params.c, AngleState.c);
AngleState.isDefined = aDefined && bDefined && cDefined;
if (!AngleState.isDefined)
return;
linear_algebra_1.Vec3.sub(AngleState.ba, AngleState.a, AngleState.b);
linear_algebra_1.Vec3.sub(AngleState.bc, AngleState.c, AngleState.b);
const value = (0, misc_1.radToDeg)(linear_algebra_1.Vec3.angle(AngleState.ba, AngleState.bc));
const angle = `${(0, mol_util_1.round)(value, 2)}\u00B0`;
AngleState.label = typeof params.label_template === 'string' ? params.label_template.replace('{{angle}}', angle) : angle;
if (typeof params.section_radius === 'number') {
AngleState.radius = params.section_radius;
}
else {
AngleState.radius = Math.min(linear_algebra_1.Vec3.magnitude(AngleState.ba), linear_algebra_1.Vec3.magnitude(AngleState.bc));
if (typeof params.section_radius_scale === 'number') {
AngleState.radius *= params.section_radius_scale;
}
}
}
function addAngleMesh(context, state, node, params) {
var _a;
syncAngleState(context, params);
if (!AngleState.isDefined)
return; // empty substructure in measurement
const { groups, mesh } = state;
if (params.show_vector) {
const radius = (_a = params.vector_radius) !== null && _a !== void 0 ? _a : 0.05;
const cylinderProps = {
radiusBottom: radius,
radiusTop: radius,
topCap: true,
bottomCap: true,
};
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.vector_color);
groups.updateTooltip(mesh.currentGroup, AngleState.label);
let count = Math.ceil(linear_algebra_1.Vec3.magnitude(AngleState.ba) / (2 * radius));
(0, cylinder_1.addFixedCountDashedCylinder)(mesh, AngleState.a, AngleState.b, 1.0, count, true, cylinderProps);
count = Math.ceil(linear_algebra_1.Vec3.magnitude(AngleState.bc) / (2 * radius));
(0, cylinder_1.addFixedCountDashedCylinder)(mesh, AngleState.b, AngleState.c, 1.0, count, true, cylinderProps);
}
if (params.show_section) {
const angle = linear_algebra_1.Vec3.angle(AngleState.ba, AngleState.bc);
linear_algebra_1.Vec3.normalize(AngleState.ba, AngleState.ba);
linear_algebra_1.Vec3.normalize(AngleState.bc, AngleState.bc);
linear_algebra_1.Vec3.scale(AngleState.ba, AngleState.ba, AngleState.radius);
linear_algebra_1.Vec3.scale(AngleState.bc, AngleState.bc, AngleState.radius);
addEllipseMesh(context, state, node, {
kind: 'ellipse',
as_circle: true,
center: AngleState.b,
major_axis_endpoint: null,
major_axis: AngleState.ba,
minor_axis_endpoint: null,
minor_axis: AngleState.bc,
radius_major: AngleState.radius,
radius_minor: AngleState.radius,
theta_start: 0,
theta_end: angle,
color: params.section_color,
tooltip: AngleState.label,
});
}
}
function addAngleLabel(context, state, node, params) {
const { labels, groups } = state;
syncAngleState(context, params);
if (!AngleState.isDefined)
return; // empty substructure in measurement
linear_algebra_1.Vec3.normalize(AngleState.ba, AngleState.ba);
linear_algebra_1.Vec3.normalize(AngleState.bc, AngleState.bc);
linear_algebra_1.Vec3.scale(AngleState.ba, AngleState.ba, AngleState.radius);
linear_algebra_1.Vec3.scale(AngleState.bc, AngleState.bc, AngleState.radius);
let size;
if (typeof params.label_size === 'number') {
size = params.label_size;
}
else {
size = Math.max(AngleState.radius * (params.label_auto_size_scale), params.label_auto_size_min);
}
linear_algebra_1.Vec3.add(AngleState.labelPos, AngleState.ba, AngleState.bc);
linear_algebra_1.Vec3.normalize(AngleState.labelPos, AngleState.labelPos);
linear_algebra_1.Vec3.scale(AngleState.labelPos, AngleState.labelPos, AngleState.radius);
linear_algebra_1.Vec3.add(AngleState.labelPos, AngleState.labelPos, AngleState.b);
const group = groups.allocateSingle(node);
groups.updateColor(group, params.label_color);
groups.updateSize(group, size);
labels.add(AngleState.label, AngleState.labelPos[0], AngleState.labelPos[1], AngleState.labelPos[2], 1, 1, group);
}
function resolveLabelRefs(params, refs) {
addRef(params.position, refs);
}
const PrimitiveLabelState = {
position: linear_algebra_1.Vec3.zero(),
sphere: geometry_1.Sphere3D.zero(),
};
function addPrimitiveLabel(context, state, node, params) {
const { labels, groups } = state;
const positionDefined = resolvePosition(context, params.position, PrimitiveLabelState.position, PrimitiveLabelState.sphere, undefined);
if (!positionDefined)
return;
const group = groups.allocateSingle(node);
groups.updateColor(group, params.label_color);
groups.updateSize(group, params.label_size);
const offset = PrimitiveLabelState.sphere.radius + params.label_offset;
labels.add(params.text, PrimitiveLabelState.position[0], PrimitiveLabelState.position[1], PrimitiveLabelState.position[2], offset, 1, group);
}
const circleCache = new Map();
function getCircle(options) {
var _a, _b, _c;
const key = JSON.stringify(options);
if (circleCache.has(key))
return circleCache.get(key);
const thetaLength = ((_a = options.thetaEnd) !== null && _a !== void 0 ? _a : 2 * Math.PI) - ((_b = options.thetaStart) !== null && _b !== void 0 ? _b : 0);
if (Math.abs(thetaLength) < 1e-3)
return null;
const circle = (0, circle_1.Circle)({
radius: 1,
thetaStart: (_c = options.thetaStart) !== null && _c !== void 0 ? _c : 0,
thetaLength,
segments: Math.ceil(2 * Math.PI / thetaLength * 64),
});
circleCache.set(key, circle);
return circle;
}
const EllipseState = {
centerPos: linear_algebra_1.Vec3.zero(),
majorPos: linear_algebra_1.Vec3.zero(),
minorPos: linear_algebra_1.Vec3.zero(),
majorAxis: linear_algebra_1.Vec3.zero(),
minorAxis: linear_algebra_1.Vec3.zero(),
scale: linear_algebra_1.Vec3.zero(),
normal: linear_algebra_1.Vec3.zero(),
scaleXform: linear_algebra_1.Mat4.identity(),
rotationXform: linear_algebra_1.Mat4.identity(),
translationXform: linear_algebra_1.Mat4.identity(),
xform: linear_algebra_1.Mat4.identity(),
};
function addEllipseMesh(context, state, node, params) {
// Unit circle in the XZ plane (Y up)
// X = minor axis, Y = normal, Z = major axis
var _a, _b, _c;
const circle = getCircle({ thetaStart: params.theta_start, thetaEnd: params.theta_end });
if (!circle)
return;
const centerDefined = resolvePosition(context, params.center, EllipseState.centerPos, undefined, undefined);
if (!centerDefined)
return;
if (params.major_axis_endpoint) {
const endpointDefined = resolvePosition(context, params.major_axis_endpoint, EllipseState.majorPos, undefined, undefined);
if (!endpointDefined)
return;
linear_algebra_1.Vec3.sub(EllipseState.majorAxis, EllipseState.majorPos, EllipseState.centerPos);
}
else {
linear_algebra_1.Vec3.copy(EllipseState.majorAxis, params.major_axis);
}
if (params.minor_axis_endpoint) {
const endpointDefined = resolvePosition(context, params.minor_axis_endpoint, EllipseState.minorPos, undefined, undefined);
if (!endpointDefined)
return;
linear_algebra_1.Vec3.sub(EllipseState.minorAxis, EllipseState.minorPos, EllipseState.centerPos);
}
else {
linear_algebra_1.Vec3.copy(EllipseState.minorAxis, params.minor_axis);
}
const { mesh, groups } = state;
// Translation
linear_algebra_1.Mat4.fromTranslation(EllipseState.translationXform, EllipseState.centerPos);
// Scale
if (params.as_circle) {
const r = (_a = params.radius_major) !== null && _a !== void 0 ? _a : linear_algebra_1.Vec3.magnitude(EllipseState.majorAxis);
linear_algebra_1.Vec3.set(EllipseState.scale, r, 1, r);
}
else {
const major = (_b = params.radius_major) !== null && _b !== void 0 ? _b : linear_algebra_1.Vec3.magnitude(EllipseState.majorAxis);
const minor = (_c = params.radius_minor) !== null && _c !== void 0 ? _c : linear_algebra_1.Vec3.magnitude(EllipseState.minorAxis);
linear_algebra_1.Vec3.set(EllipseState.scale, minor, 1, major);
}
linear_algebra_1.Mat4.fromScaling(EllipseState.scaleXform, EllipseState.scale);
// Rotation
linear_algebra_1.Vec3.normalize(EllipseState.minorAxis, EllipseState.minorAxis);
linear_algebra_1.Vec3.normalize(EllipseState.majorAxis, EllipseState.majorAxis);
linear_algebra_1.Vec3.cross(EllipseState.normal, EllipseState.majorAxis, EllipseState.minorAxis);
linear_algebra_1.Mat4.targetTo(EllipseState.rotationXform, linear_algebra_1.Vec3.origin, EllipseState.majorAxis, EllipseState.normal);
linear_algebra_1.Mat4.mul(EllipseState.rotationXform, EllipseState.rotationXform, linear_algebra_1.Mat4.rotY180);
// Final xform
linear_algebra_1.Mat4.mul3(EllipseState.xform, EllipseState.translationXform, EllipseState.rotationXform, EllipseState.scaleXform);
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
mesh_builder_1.MeshBuilder.addPrimitive(mesh, EllipseState.xform, circle);
mesh_builder_1.MeshBuilder.addPrimitiveFlipped(mesh, EllipseState.xform, circle);
}
const EllipsoidState = {
centerPos: linear_algebra_1.Vec3.zero(),
majorPos: linear_algebra_1.Vec3.zero(),
minorPos: linear_algebra_1.Vec3.zero(),
majorAxis: linear_algebra_1.Vec3.zero(),
minorAxis: linear_algebra_1.Vec3.zero(),
sphere: geometry_1.Sphere3D.zero(),
radius: linear_algebra_1.Vec3.zero(),
extent: linear_algebra_1.Vec3.zero(),
up: linear_algebra_1.Vec3.zero(),
};
function addEllipsoidMesh(context, state, node, params) {
const centerDefined = resolvePosition(context, params.center, EllipsoidState.centerPos, EllipsoidState.sphere, undefined);
if (!centerDefined)
return;
if (params.major_axis_endpoint) {
const endpointDefined = resolvePosition(context, params.major_axis_endpoint, EllipsoidState.majorPos, undefined, undefined);
if (!endpointDefined)
return;
linear_algebra_1.Vec3.sub(EllipsoidState.majorAxis, EllipsoidState.majorPos, EllipsoidState.centerPos);
}
else if (params.major_axis) {
linear_algebra_1.Vec3.copy(EllipsoidState.majorAxis, params.major_axis);
}
else {
linear_algebra_1.Vec3.copy(EllipsoidState.majorAxis, linear_algebra_1.Vec3.unitX);
}
if (params.minor_axis_endpoint) {
const endpointDefined = resolvePosition(context, params.minor_axis_endpoint, EllipsoidState.minorPos, undefined, undefined);
if (!endpointDefined)
return;
linear_algebra_1.Vec3.sub(EllipsoidState.minorAxis, EllipsoidState.minorPos, EllipsoidState.centerPos);
}
else if (params.minor_axis) {
linear_algebra_1.Vec3.copy(EllipsoidState.minorAxis, params.minor_axis);
}
else {
linear_algebra_1.Vec3.copy(EllipsoidState.minorAxis, linear_algebra_1.Vec3.unitY);
}
if (typeof params.radius === 'number') {
linear_algebra_1.Vec3.set(EllipsoidState.radius, params.radius, params.radius, params.radius);
}
else if (params.radius) {
linear_algebra_1.Vec3.copy(EllipsoidState.radius, params.radius);
}
else {
const r = EllipsoidState.sphere.radius;
linear_algebra_1.Vec3.set(EllipsoidState.radius, r, r, r);
}
if (typeof params.radius_extent === 'number') {
linear_algebra_1.Vec3.set(EllipsoidState.extent, params.radius_extent, params.radius_extent, params.radius_extent);
}
else if (params.radius_extent) {
linear_algebra_1.Vec3.copy(EllipsoidState.extent, params.radius_extent);
}
else {
linear_algebra_1.Vec3.set(EllipsoidState.extent, 0, 0, 0);
}
linear_algebra_1.Vec3.add(EllipsoidState.radius, EllipsoidState.radius, EllipsoidState.extent);
const { mesh, groups } = state;
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
linear_algebra_1.Vec3.normalize(EllipsoidState.majorAxis, EllipsoidState.majorAxis);
linear_algebra_1.Vec3.normalize(EllipsoidState.minorAxis, EllipsoidState.minorAxis);
linear_algebra_1.Vec3.cross(EllipsoidState.up, EllipsoidState.majorAxis, EllipsoidState.minorAxis);
(0, ellipsoid_1.addEllipsoid)(mesh, EllipsoidState.centerPos, EllipsoidState.up, EllipsoidState.minorAxis, EllipsoidState.radius, 3);
}
const BoxState = {
center: linear_algebra_1.Vec3.zero(),
boundary: geometry_1.Box3D.zero(),
size: linear_algebra_1.Vec3.zero(),
cage: (0, box_1.BoxCage)(),
translationXform: linear_algebra_1.Mat4.identity(),
scaleXform: linear_algebra_1.Mat4.identity(),
xform: linear_algebra_1.Mat4.identity(),
};
function addBoxMesh(context, state, node, params) {
if (!params.show_edges && !params.show_faces)
return;
const positionDefined = resolvePosition(context, params.center, BoxState.center, undefined, BoxState.boundary);
if (!positionDefined)
return;
if (params.extent) {
geometry_1.Box3D.expand(BoxState.boundary, BoxState.boundary, params.extent);
}
if (geometry_1.Box3D.volume(BoxState.boundary) < 1e-3)
return;
const { mesh, groups } = state;
linear_algebra_1.Mat4.fromScaling(BoxState.scaleXform, geometry_1.Box3D.size(BoxState.size, BoxState.boundary));
linear_algebra_1.Mat4.fromTranslation(BoxState.translationXform, BoxState.center);
linear_algebra_1.Mat4.mul(BoxState.xform, BoxState.translationXform, BoxState.scaleXform);
if (params.show_faces) {
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.face_color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
mesh_builder_1.MeshBuilder.addPrimitive(mesh, BoxState.xform, (0, box_1.Box)());
}
if (params.show_edges) {
mesh.currentGroup = groups.allocateSingle(node);
groups.updateColor(mesh.currentGroup, params.edge_color);
groups.updateTooltip(mesh.currentGroup, params.tooltip);
mesh_builder_1.MeshBuilder.addCage(mesh, BoxState.xform, (0, box_1.BoxCage)(), params.edge_radius, 2, 8);
}
}
;