UNPKG

molstar

Version:

A comprehensive macromolecular library.

1,022 lines (1,021 loc) 47.6 kB
"use strict"; /** * 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); } }