molstar
Version:
A comprehensive macromolecular library.
164 lines (163 loc) • 7.22 kB
JavaScript
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Ke Ma <mark.ma@rcsb.org>
* @author Adam Midlik <midlik@gmail.com>
*/
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
import { Mat3 } from '../../mol-math/linear-algebra';
import { Loci } from '../../mol-model/loci';
import { StructureElement } from '../../mol-model/structure';
import { PluginStateObject } from '../objects';
import { pcaFocus } from './focus-camera/focus-first-residue';
import { getFocusSnapshot } from './focus-camera/focus-object';
import { changeCameraRotation, structureLayingTransform } from './focus-camera/orient-axes';
// TODO: make this customizable somewhere?
const DefaultCameraFocusOptions = {
minRadius: 5,
extraRadius: 4,
durationMs: 250,
};
export class CameraManager {
transformedLoci(loci) {
var _a, _b;
if (StructureElement.Loci.is(loci)) {
// use decorated (including 3d transforms) parent structure
const parent = (_b = (_a = this.plugin.helpers.substructureParent.get(loci.structure)) === null || _a === void 0 ? void 0 : _a.obj) === null || _b === void 0 ? void 0 : _b.data;
if (parent)
loci = StructureElement.Loci.remap(loci, parent);
}
return loci;
}
focusRenderObjects(objects, options) {
if (!objects)
return;
const spheres = [];
for (const o of objects) {
const s = o.values.boundingSphere.ref.value;
if (s.radius === 0)
continue;
spheres.push(s);
}
this.focusSpheres(spheres, s => s, options);
}
focusLoci(loci, options) {
// TODO: allow computation of principal axes here?
// perhaps have an optimized function, that does exact axes small Loci and approximate/sampled from big ones?
let sphere;
if (Array.isArray(loci) && loci.length > 1) {
const spheres = [];
for (const l of loci) {
const s = Loci.getBoundingSphere(this.transformedLoci(l));
if (s)
spheres.push(s);
}
if (spheres.length === 0)
return;
this.boundaryHelper.reset();
for (const s of spheres) {
this.boundaryHelper.includeSphere(s);
}
this.boundaryHelper.finishedIncludeStep();
for (const s of spheres) {
this.boundaryHelper.radiusSphere(s);
}
sphere = this.boundaryHelper.getSphere();
}
else if (Array.isArray(loci)) {
if (loci.length === 0)
return;
sphere = Loci.getBoundingSphere(this.transformedLoci(loci[0]));
}
else {
sphere = Loci.getBoundingSphere(this.transformedLoci(loci));
}
if (sphere) {
this.focusSphere(sphere, options);
}
}
focusSpheres(xs, sphere, options) {
const spheres = [];
for (const x of xs) {
const s = sphere(x);
if (s)
spheres.push(s);
}
if (spheres.length === 0)
return;
if (spheres.length === 1)
return this.focusSphere(spheres[0], options);
this.boundaryHelper.reset();
for (const s of spheres) {
this.boundaryHelper.includeSphere(s);
}
this.boundaryHelper.finishedIncludeStep();
for (const s of spheres) {
this.boundaryHelper.radiusSphere(s);
}
this.focusSphere(this.boundaryHelper.getSphere(), options);
}
focusSphere(sphere, options) {
var _a;
const { canvas3d } = this.plugin;
if (!canvas3d)
return;
const { extraRadius, minRadius, durationMs } = { ...DefaultCameraFocusOptions, ...options };
const radius = Math.max(sphere.radius + extraRadius, minRadius);
if (options === null || options === void 0 ? void 0 : options.principalAxes) {
const snapshot = pcaFocus(this.plugin, radius, options);
(_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.requestCameraReset({ durationMs, snapshot });
}
else {
const snapshot = canvas3d.camera.getFocus(sphere.center, radius);
canvas3d.requestCameraReset({ durationMs, snapshot });
}
}
/** Focus on a set of plugin state object cells (if `options.targets` is non-empty) or on the whole scene (if `options.targets` is empty). */
focusObject(options) {
var _a, _b, _c;
if (!this.plugin.canvas3d)
return;
const snapshot = getFocusSnapshot(this.plugin, {
...options,
targets: (_a = options.targets) === null || _a === void 0 ? void 0 : _a.map(t => { var _a; return ({ ...t, extraRadius: (_a = t.extraRadius) !== null && _a !== void 0 ? _a : DefaultCameraFocusOptions.extraRadius }); }),
minRadius: (_b = options.minRadius) !== null && _b !== void 0 ? _b : DefaultCameraFocusOptions.minRadius,
});
this.plugin.canvas3d.requestCameraReset({ snapshot, durationMs: (_c = options.durationMs) !== null && _c !== void 0 ? _c : DefaultCameraFocusOptions.durationMs });
}
/** Align PCA axes of `structures` (default: all loaded structures) to the screen axes. */
orientAxes(structures, durationMs) {
if (!this.plugin.canvas3d)
return;
if (!structures) {
const structCells = this.plugin.state.data.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure));
const rootStructCells = structCells.filter(cell => cell.obj && !cell.transform.transformer.definition.isDecorator && !cell.obj.data.parent);
structures = rootStructCells.map(cell => { var _a; return (_a = cell.obj) === null || _a === void 0 ? void 0 : _a.data; }).filter(struct => !!struct);
}
const { rotation } = structureLayingTransform(structures);
const newSnapshot = changeCameraRotation(this.plugin.canvas3d.camera.getSnapshot(), rotation);
this.setSnapshot(newSnapshot, durationMs);
}
/** Align Cartesian axes to the screen axes (X right, Y up). */
resetAxes(durationMs) {
if (!this.plugin.canvas3d)
return;
const newSnapshot = changeCameraRotation(this.plugin.canvas3d.camera.getSnapshot(), Mat3.Identity);
this.setSnapshot(newSnapshot, durationMs);
}
setSnapshot(snapshot, durationMs) {
var _a;
// TODO: setState and requestCameraReset are very similar now: unify them?
(_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.requestCameraReset({ snapshot, durationMs });
}
reset(snapshot, durationMs) {
var _a;
(_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.requestCameraReset({ snapshot, durationMs });
}
constructor(plugin) {
this.plugin = plugin;
this.boundaryHelper = new BoundaryHelper('98');
}
}