UNPKG

molstar

Version:

A comprehensive macromolecular library.

298 lines (297 loc) 14.6 kB
"use strict"; /** * Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Gianluca Tomasello <giagitom@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DotRepresentationProvider = exports.DotParams = exports.VolumePointParams = exports.VolumeSphereParams = exports.VolumeDotParams = void 0; exports.VolumeSphereVisual = VolumeSphereVisual; exports.VolumeSphereImpostorVisual = VolumeSphereImpostorVisual; exports.VolumeSphereMeshVisual = VolumeSphereMeshVisual; exports.createVolumeSphereImpostor = createVolumeSphereImpostor; exports.createVolumeSphereMesh = createVolumeSphereMesh; exports.VolumePointVisual = VolumePointVisual; exports.createVolumePoint = createVolumePoint; exports.getDotParams = getDotParams; exports.DotRepresentation = DotRepresentation; const param_definition_1 = require("../../mol-util/param-definition.js"); const volume_1 = require("../../mol-model/volume.js"); const mesh_1 = require("../../mol-geo/geometry/mesh/mesh.js"); const representation_1 = require("./representation.js"); const representation_2 = require("../representation.js"); const picking_1 = require("../../mol-geo/geometry/picking.js"); const loci_1 = require("../../mol-model/loci.js"); const util_1 = require("./util.js"); const base_1 = require("../../mol-geo/geometry/base.js"); const spheres_1 = require("../../mol-geo/geometry/spheres/spheres.js"); const mesh_builder_1 = require("../../mol-geo/geometry/mesh/mesh-builder.js"); const spheres_builder_1 = require("../../mol-geo/geometry/spheres/spheres-builder.js"); const vec3_1 = require("../../mol-math/linear-algebra/3d/vec3.js"); const sphere_1 = require("../../mol-geo/geometry/mesh/builder/sphere.js"); const sphere_2 = require("../../mol-geo/primitive/sphere.js"); const points_1 = require("../../mol-geo/geometry/points/points.js"); const points_builder_1 = require("../../mol-geo/geometry/points/points-builder.js"); const linear_algebra_1 = require("../../mol-math/linear-algebra.js"); const interval_1 = require("../../mol-data/int/interval.js"); const ordered_set_1 = require("../../mol-data/int/ordered-set.js"); exports.VolumeDotParams = { isoValue: volume_1.Volume.IsoValueParam, perturbPositions: param_definition_1.ParamDefinition.Boolean(false) }; // exports.VolumeSphereParams = { ...spheres_1.Spheres.Params, ...mesh_1.Mesh.Params, ...exports.VolumeDotParams, tryUseImpostor: param_definition_1.ParamDefinition.Boolean(true), detail: param_definition_1.ParamDefinition.Numeric(0, { min: 0, max: 3, step: 1 }, base_1.BaseGeometry.CustomQualityParamInfo), }; function VolumeSphereVisual(materialId, volume, key, props, webgl) { return props.tryUseImpostor && webgl && webgl.extensions.fragDepth && webgl.extensions.textureFloat ? VolumeSphereImpostorVisual(materialId) : VolumeSphereMeshVisual(materialId); } function VolumeSphereImpostorVisual(materialId) { return (0, representation_1.VolumeVisual)({ defaultProps: param_definition_1.ParamDefinition.getDefaultValues(exports.VolumeSphereParams), createGeometry: createVolumeSphereImpostor, createLocationIterator: util_1.createVolumeCellLocationIterator, getLoci: getDotLoci, eachLocation: eachDot, setUpdateState: (state, volume, newProps, currentProps, newTheme, currentTheme) => { state.createGeometry = (!volume_1.Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) || newProps.perturbPositions !== currentProps.perturbPositions); }, geometryUtils: spheres_1.Spheres.Utils, mustRecreate: (volumekey, props, webgl) => { return !props.tryUseImpostor || !webgl; } }, materialId); } function VolumeSphereMeshVisual(materialId) { return (0, representation_1.VolumeVisual)({ defaultProps: param_definition_1.ParamDefinition.getDefaultValues(exports.VolumeSphereParams), createGeometry: createVolumeSphereMesh, createLocationIterator: util_1.createVolumeCellLocationIterator, getLoci: getDotLoci, eachLocation: eachDot, setUpdateState: (state, volume, newProps, currentProps, newTheme, currentTheme) => { state.createGeometry = (!volume_1.Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) || newProps.perturbPositions !== currentProps.perturbPositions || newProps.sizeFactor !== currentProps.sizeFactor || newProps.detail !== currentProps.detail); }, geometryUtils: mesh_1.Mesh.Utils, mustRecreate: (volumekey, props, webgl) => { return props.tryUseImpostor && !!webgl; } }, materialId); } function getBasis(m) { return { ...linear_algebra_1.Mat4.extractBasis(m), maxScale: linear_algebra_1.Mat4.getMaxScaleOnAxis(m) }; } const offset = (0, vec3_1.Vec3)(); function getRandomOffsetFromBasis({ x, y, z, maxScale }) { const rx = (Math.random() - 0.5) * maxScale; const ry = (Math.random() - 0.5) * maxScale; const rz = (Math.random() - 0.5) * maxScale; vec3_1.Vec3.scale(offset, x, rx); vec3_1.Vec3.scaleAndAdd(offset, offset, y, ry); vec3_1.Vec3.scaleAndAdd(offset, offset, z, rz); return offset; } function createVolumeSphereImpostor(ctx, volume, key, theme, props, spheres) { const { cells: { space, data }, stats } = volume.grid; const gridToCartn = volume_1.Grid.getGridToCartesianTransform(volume.grid); const isoVal = volume_1.Volume.IsoValue.toAbsolute(props.isoValue, stats).absoluteValue; const p = (0, vec3_1.Vec3)(); const [xn, yn, zn] = space.dimensions; const count = Math.ceil((xn * yn * zn) / 10); const builder = spheres_builder_1.SpheresBuilder.create(count, Math.ceil(count / 2), spheres); const invert = isoVal < 0; // Precompute basis vectors and largest cell axis length const basis = props.perturbPositions ? getBasis(gridToCartn) : undefined; for (let z = 0; z < zn; ++z) { for (let y = 0; y < yn; ++y) { for (let x = 0; x < xn; ++x) { const value = space.get(data, x, y, z); if (!invert && value < isoVal || invert && value > isoVal) continue; const cellIdx = space.dataOffset(x, y, z); if (basis) { vec3_1.Vec3.set(p, x, y, z); vec3_1.Vec3.transformMat4(p, p, gridToCartn); const offset = getRandomOffsetFromBasis(basis); vec3_1.Vec3.add(p, p, offset); } else { vec3_1.Vec3.set(p, x, y, z); vec3_1.Vec3.transformMat4(p, p, gridToCartn); } builder.add(p[0], p[1], p[2], cellIdx); } } } const s = builder.getSpheres(); s.setBoundingSphere(volume_1.Volume.Isosurface.getBoundingSphere(volume, props.isoValue)); return s; } function createVolumeSphereMesh(ctx, volume, key, theme, props, mesh) { const { detail, sizeFactor } = props; const { cells: { space, data }, stats } = volume.grid; const gridToCartn = volume_1.Grid.getGridToCartesianTransform(volume.grid); const isoVal = volume_1.Volume.IsoValue.toAbsolute(props.isoValue, stats).absoluteValue; const p = (0, vec3_1.Vec3)(); const [xn, yn, zn] = space.dimensions; const count = Math.ceil((xn * yn * zn) / 10); const vertexCount = count * (0, sphere_2.sphereVertexCount)(detail); const builderState = mesh_builder_1.MeshBuilder.createState(vertexCount, Math.ceil(vertexCount / 2), mesh); const l = volume_1.Volume.Cell.Location(volume); const themeSize = theme.size.size; const invert = isoVal < 0; // Precompute basis vectors and largest cell axis length const basis = props.perturbPositions ? getBasis(gridToCartn) : undefined; for (let z = 0; z < zn; ++z) { for (let y = 0; y < yn; ++y) { for (let x = 0; x < xn; ++x) { const value = space.get(data, x, y, z); if (!invert && value < isoVal || invert && value > isoVal) continue; const cellIdx = space.dataOffset(x, y, z); l.cell = cellIdx; const size = themeSize(l) * sizeFactor; if (basis) { vec3_1.Vec3.set(p, x, y, z); vec3_1.Vec3.transformMat4(p, p, gridToCartn); const offset = getRandomOffsetFromBasis(basis); vec3_1.Vec3.add(p, p, offset); } else { vec3_1.Vec3.set(p, x, y, z); vec3_1.Vec3.transformMat4(p, p, gridToCartn); } builderState.currentGroup = cellIdx; (0, sphere_1.addSphere)(builderState, p, size, detail); } } } const m = mesh_builder_1.MeshBuilder.getMesh(builderState); m.setBoundingSphere(volume_1.Volume.Isosurface.getBoundingSphere(volume, props.isoValue)); return m; } // exports.VolumePointParams = { ...points_1.Points.Params, ...exports.VolumeDotParams, }; function VolumePointVisual(materialId) { return (0, representation_1.VolumeVisual)({ defaultProps: param_definition_1.ParamDefinition.getDefaultValues(exports.VolumePointParams), createGeometry: createVolumePoint, createLocationIterator: util_1.createVolumeCellLocationIterator, getLoci: getDotLoci, eachLocation: eachDot, setUpdateState: (state, volume, newProps, currentProps, newTheme, currentTheme) => { state.createGeometry = (!volume_1.Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) || newProps.perturbPositions !== currentProps.perturbPositions); }, geometryUtils: points_1.Points.Utils, }, materialId); } function createVolumePoint(ctx, volume, key, theme, props, points) { const { cells: { space, data }, stats } = volume.grid; const gridToCartn = volume_1.Grid.getGridToCartesianTransform(volume.grid); const isoVal = volume_1.Volume.IsoValue.toAbsolute(props.isoValue, stats).absoluteValue; const p = (0, vec3_1.Vec3)(); const [xn, yn, zn] = space.dimensions; const count = Math.ceil((xn * yn * zn) / 10); const builder = points_builder_1.PointsBuilder.create(count, Math.ceil(count / 2), points); const invert = isoVal < 0; // Precompute basis vectors and largest cell axis length const basis = props.perturbPositions ? getBasis(gridToCartn) : undefined; for (let z = 0; z < zn; ++z) { for (let y = 0; y < yn; ++y) { for (let x = 0; x < xn; ++x) { const value = space.get(data, x, y, z); if (!invert && value < isoVal || invert && value > isoVal) continue; const cellIdx = space.dataOffset(x, y, z); if (basis) { vec3_1.Vec3.set(p, x, y, z); vec3_1.Vec3.transformMat4(p, p, gridToCartn); const offset = getRandomOffsetFromBasis(basis); vec3_1.Vec3.add(p, p, offset); } else { vec3_1.Vec3.set(p, x, y, z); vec3_1.Vec3.transformMat4(p, p, gridToCartn); } builder.add(p[0], p[1], p[2], cellIdx); } } } const pt = builder.getPoints(); pt.setBoundingSphere(volume_1.Volume.Isosurface.getBoundingSphere(volume, props.isoValue)); return pt; } // function getLoci(volume, props) { const instances = interval_1.Interval.ofLength(volume.instances.length); return volume_1.Volume.Isosurface.Loci(volume, props.isoValue, instances); } function getDotLoci(pickingId, volume, key, props, id) { const { objectId, groupId, instanceId } = pickingId; if (id === objectId) { const granularity = volume_1.Volume.PickingGranularity.get(volume); const instances = ordered_set_1.OrderedSet.ofSingleton(instanceId); if (granularity === 'volume') { return volume_1.Volume.Loci(volume, instances); } else if (granularity === 'object' || groupId === picking_1.PickingId.Null) { return volume_1.Volume.Isosurface.Loci(volume, props.isoValue, instances); } else { const indices = interval_1.Interval.ofSingleton(groupId); return volume_1.Volume.Cell.Loci(volume, [{ indices, instances }]); } } return loci_1.EmptyLoci; } function eachDot(loci, volume, key, props, apply) { return (0, util_1.eachVolumeLoci)(loci, volume, { isoValue: props.isoValue }, apply); } // const DotVisuals = { 'sphere': (ctx, getParams) => (0, representation_1.VolumeRepresentation)('Dot sphere', ctx, getParams, VolumeSphereVisual, getLoci), 'point': (ctx, getParams) => (0, representation_1.VolumeRepresentation)('Dot point', ctx, getParams, VolumePointVisual, getLoci), }; exports.DotParams = { ...exports.VolumeSphereParams, ...exports.VolumePointParams, visuals: param_definition_1.ParamDefinition.MultiSelect(['sphere'], param_definition_1.ParamDefinition.objectToOptions(DotVisuals)), bumpFrequency: param_definition_1.ParamDefinition.Numeric(1, { min: 0, max: 10, step: 0.1 }, base_1.BaseGeometry.ShadingCategory), }; function getDotParams(ctx, volume) { const p = param_definition_1.ParamDefinition.clone(exports.DotParams); p.isoValue = volume_1.Volume.createIsoValueParam(volume_1.Volume.IsoValue.relative(2), volume.grid.stats); return p; } function DotRepresentation(ctx, getParams) { return representation_2.Representation.createMulti('Dot', ctx, getParams, representation_2.Representation.StateBuilder, DotVisuals); } exports.DotRepresentationProvider = (0, representation_1.VolumeRepresentationProvider)({ name: 'dot', label: 'Dot', description: 'Displays dots of volumetric data.', factory: DotRepresentation, getParams: getDotParams, defaultValues: param_definition_1.ParamDefinition.getDefaultValues(exports.DotParams), defaultColorTheme: { name: 'uniform' }, defaultSizeTheme: { name: 'uniform' }, isApplicable: (volume) => !volume_1.Volume.isEmpty(volume) && !volume_1.Volume.Segmentation.get(volume) });