/** * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose */ import { defaults } from '../mol-util'; import { Structure } from '../mol-model/structure'; import { VisualQuality } from '../mol-geo/geometry/base'; import { Box3D, SpacegroupCell } from '../mol-math/geometry'; import { ModelSymmetry } from '../mol-model-formats/structure/property/symmetry'; import { Volume } from '../mol-model/volume'; export interface VisualUpdateState { updateTransform: boolean updateMatrix: boolean updateColor: boolean updateSize: boolean createGeometry: boolean createNew: boolean /** holds contextual info, is not reset */ info: { [k: string]: unknown } } export namespace VisualUpdateState { export function create(): VisualUpdateState { return { updateTransform: false, updateMatrix: false, updateColor: false, updateSize: false, createGeometry: false, createNew: false, info: {} }; } export function reset(state: VisualUpdateState) { state.updateTransform = false; state.updateMatrix = false; state.updateColor = false; state.updateSize = false; state.createGeometry = false; state.createNew = false; } } // export interface QualityProps { quality: VisualQuality detail: number radialSegments: number linearSegments: number resolution: number probePositions: number doubleSided: boolean xrayShaded: boolean alpha: number transparentBackfaces: 'off' | 'on' | 'opaque' } export const DefaultQualityThresholds = { lowestElementCount: 1_000_000, lowerElementCount: 500_000, lowElementCount: 100_000, mediumElementCount: 20_000, highElementCount: 2_000, coarseGrainedFactor: 10, elementCountFactor: 1 }; export type QualityThresholds = typeof DefaultQualityThresholds export function getStructureQuality(structure: Structure, tresholds: Partial = {}): VisualQuality { const t = { ...DefaultQualityThresholds, ...tresholds }; let score = structure.elementCount * t.elementCountFactor; if (structure.isCoarseGrained) score *= t.coarseGrainedFactor; if (score > t.lowestElementCount) { return 'lowest'; } else if (score > t.lowerElementCount) { return 'lower'; } else if (score > t.lowElementCount) { return 'low'; } else if (score > t.mediumElementCount) { return 'medium'; } else if (score > t.highElementCount) { return 'high'; } else { return 'higher'; } } /** * Uses cell volume to avoid costly boundary calculation if * - single model * - non-empty 'P 1' spacegroup */ function getRootVolume(structure: Structure) { if (structure.root.models.length === 1) { const sym = ModelSymmetry.Provider.get(structure.root.model); if (sym && sym.spacegroup.name === 'P 1' && !SpacegroupCell.isZero(sym.spacegroup.cell)) { return sym.spacegroup.cell.volume; } } return Box3D.volume(structure.root.boundary.box); } export function getQualityProps(props: Partial, data?: any) { let quality = defaults(props.quality, 'auto' as VisualQuality); let detail = defaults(props.detail, 1); let radialSegments = defaults(props.radialSegments, 12); let linearSegments = defaults(props.linearSegments, 8); let resolution = defaults(props.resolution, 2); let probePositions = defaults(props.probePositions, 12); let doubleSided = defaults(props.doubleSided, true); let volume = 0; if (quality === 'auto') { if (data instanceof Structure) { quality = getStructureQuality(data.root); volume = getRootVolume(data); } else if (Volume.is(data)) { const [x, y, z] = data.grid.cells.space.dimensions; volume = x * y * z; quality = volume < 10_000_000 ? 'medium' : 'low'; } } switch (quality) { case 'highest': detail = 3; radialSegments = 36; linearSegments = 18; resolution = 0.1; probePositions = 72; doubleSided = true; break; case 'higher': detail = 3; radialSegments = 28; linearSegments = 14; resolution = 0.3; probePositions = 48; doubleSided = true; break; case 'high': detail = 2; radialSegments = 20; linearSegments = 10; resolution = 0.5; probePositions = 36; doubleSided = true; break; case 'medium': detail = 1; radialSegments = 12; linearSegments = 8; resolution = 0.8; probePositions = 24; doubleSided = true; break; case 'low': detail = 0; radialSegments = 8; linearSegments = 3; resolution = 1.3; probePositions = 24; doubleSided = false; break; case 'lower': detail = 0; radialSegments = 4; linearSegments = 2; resolution = 3; probePositions = 12; doubleSided = false; break; case 'lowest': detail = 0; radialSegments = 2; linearSegments = 1; resolution = 8; probePositions = 12; doubleSided = false; break; case 'custom': // use defaults or given props as set above break; } // max resolution based on volume (for 'auto' quality) resolution = Math.max(resolution, volume / 500_000_000); resolution = Math.min(resolution, 20); if (props.transparentBackfaces === 'off' && ((props.alpha !== undefined && props.alpha < 1) || !!props.xrayShaded)) { doubleSided = false; } return { detail, radialSegments, linearSegments, resolution, probePositions, doubleSided }; }