molstar
Version:
A comprehensive macromolecular library.
344 lines (343 loc) • 18.3 kB
JavaScript
"use strict";
/**
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CameraHelperAxis = exports.CameraHelper = exports.CameraHelperParams = void 0;
exports.isCameraAxesLoci = isCameraAxesLoci;
const immer_1 = require("immer");
const interval_1 = require("../../mol-data/int/interval");
const cylinder_1 = require("../../mol-geo/geometry/mesh/builder/cylinder");
const sphere_1 = require("../../mol-geo/geometry/mesh/builder/sphere");
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 scene_1 = require("../../mol-gl/scene");
const geometry_1 = require("../../mol-math/geometry");
const linear_algebra_1 = require("../../mol-math/linear-algebra");
const loci_1 = require("../../mol-model/loci");
const shape_1 = require("../../mol-model/shape");
const visual_1 = require("../../mol-repr/visual");
const names_1 = require("../../mol-util/color/names");
const marker_action_1 = require("../../mol-util/marker-action");
const param_definition_1 = require("../../mol-util/param-definition");
const type_helpers_1 = require("../../mol-util/type-helpers");
const camera_1 = require("../camera");
// TODO add scale line/grid
const AxesParams = {
alpha: param_definition_1.ParamDefinition.Numeric(0.51, { min: 0, max: 1, step: 0.01 }, { isEssential: true, label: 'Opacity' }),
colorX: param_definition_1.ParamDefinition.Color(names_1.ColorNames.red, { isEssential: true }),
colorY: param_definition_1.ParamDefinition.Color(names_1.ColorNames.green, { isEssential: true }),
colorZ: param_definition_1.ParamDefinition.Color(names_1.ColorNames.blue, { isEssential: true }),
scale: param_definition_1.ParamDefinition.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
location: param_definition_1.ParamDefinition.Select('bottom-left', param_definition_1.ParamDefinition.arrayToOptions(['bottom-left', 'bottom-right', 'top-left', 'top-right'])),
locationOffsetX: param_definition_1.ParamDefinition.Numeric(0),
locationOffsetY: param_definition_1.ParamDefinition.Numeric(0),
originColor: param_definition_1.ParamDefinition.Color(names_1.ColorNames.grey),
radiusScale: param_definition_1.ParamDefinition.Numeric(0.075, { min: 0.01, max: 0.3, step: 0.001 }),
showPlanes: param_definition_1.ParamDefinition.Boolean(true),
planeColorXY: param_definition_1.ParamDefinition.Color(names_1.ColorNames.grey, { label: 'Plane Color XY' }),
planeColorXZ: param_definition_1.ParamDefinition.Color(names_1.ColorNames.grey, { label: 'Plane Color XZ' }),
planeColorYZ: param_definition_1.ParamDefinition.Color(names_1.ColorNames.grey, { label: 'Plane Color YZ' }),
showLabels: param_definition_1.ParamDefinition.Boolean(false),
labelX: param_definition_1.ParamDefinition.Text('X'),
labelY: param_definition_1.ParamDefinition.Text('Y'),
labelZ: param_definition_1.ParamDefinition.Text('Z'),
labelColorX: param_definition_1.ParamDefinition.Color(names_1.ColorNames.grey),
labelColorY: param_definition_1.ParamDefinition.Color(names_1.ColorNames.grey),
labelColorZ: param_definition_1.ParamDefinition.Color(names_1.ColorNames.grey),
labelOpacity: param_definition_1.ParamDefinition.Numeric(1, { min: 0, max: 1, step: 0.01 }),
labelScale: param_definition_1.ParamDefinition.Numeric(0.25, { min: 0.1, max: 1.0, step: 0.01 }),
};
exports.CameraHelperParams = {
axes: param_definition_1.ParamDefinition.MappedStatic('on', {
on: param_definition_1.ParamDefinition.Group(AxesParams),
off: param_definition_1.ParamDefinition.Group({})
}, { cycle: true, description: 'Show camera orientation axes' }),
};
class CameraHelper {
constructor(webgl, props = {}) {
this.webgl = webgl;
this.props = {
axes: { name: 'off', params: {} }
};
this.pixelRatio = 1;
this.eachGroup = (loci, apply) => {
if (!isCameraAxesLoci(loci))
return false;
let changed = false;
if (this.meshRenderObject) {
const groupCount = this.meshRenderObject.values.uGroupCount.ref.value;
for (const { groupId, instanceId } of loci.elements) {
const idx = instanceId * groupCount + groupId;
if (apply(interval_1.Interval.ofSingleton(idx)))
changed = true;
}
}
if (this.textRenderObject) {
const groupCount = this.textRenderObject.values.uGroupCount.ref.value;
for (const { groupId, instanceId } of loci.elements) {
const idx = instanceId * groupCount + groupId;
if (apply(interval_1.Interval.ofSingleton(idx)))
changed = true;
}
}
return changed;
};
this.scene = scene_1.Scene.create(webgl, 'blended');
this.camera = new camera_1.Camera();
linear_algebra_1.Vec3.set(this.camera.up, 0, 1, 0);
linear_algebra_1.Vec3.set(this.camera.target, 0, 0, 0);
this.setProps(props);
}
setProps(props) {
this.props = (0, immer_1.produce)(this.props, p => {
if (props.axes !== undefined) {
p.axes.name = props.axes.name;
if (props.axes.name === 'on') {
this.scene.clear();
this.pixelRatio = this.webgl.pixelRatio;
const params = {
...props.axes.params,
scale: props.axes.params.scale * this.pixelRatio,
labelScale: props.axes.params.labelScale * this.pixelRatio,
};
this.meshRenderObject = createMeshRenderObject(params);
this.scene.add(this.meshRenderObject);
if (props.axes.params.showLabels) {
this.textRenderObject = createTextRenderObject(params);
this.scene.add(this.textRenderObject);
}
else {
this.textRenderObject = undefined;
}
this.scene.commit();
linear_algebra_1.Vec3.set(this.camera.position, 0, 0, params.scale * 200);
linear_algebra_1.Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up);
p.axes.params = { ...props.axes.params };
}
}
});
}
get isEnabled() {
return this.props.axes.name === 'on';
}
getLoci(pickingId) {
const { objectId, groupId, instanceId } = pickingId;
if (((!this.meshRenderObject || objectId !== this.meshRenderObject.id) &&
(!this.textRenderObject || objectId !== this.textRenderObject.id)) || groupId === CameraHelperAxis.None)
return loci_1.EmptyLoci;
return CameraAxesLoci(this, groupId, instanceId);
}
mark(loci, action) {
if (!marker_action_1.MarkerActions.is(marker_action_1.MarkerActions.Highlighting, action))
return false;
if (!(0, loci_1.isEveryLoci)(loci)) {
if (!isCameraAxesLoci(loci))
return false;
if (loci.data !== this)
return false;
}
return (visual_1.Visual.mark(this.meshRenderObject, loci, action, this.eachGroup) ||
visual_1.Visual.mark(this.textRenderObject, loci, action, this.eachGroup));
}
update(camera) {
if (!this.meshRenderObject || this.props.axes.name === 'off')
return;
if (this.pixelRatio !== this.webgl.pixelRatio) {
this.setProps(this.props);
}
updateCamera(this.camera, camera.viewport, camera.viewOffset);
linear_algebra_1.Mat4.extractRotation(this.scene.view, camera.view);
const r = this.textRenderObject
? this.textRenderObject.values.boundingSphere.ref.value.radius
: this.meshRenderObject.values.boundingSphere.ref.value.radius;
const l = this.props.axes.params.location;
const ox = this.props.axes.params.locationOffsetX * this.pixelRatio;
const oy = this.props.axes.params.locationOffsetY * this.pixelRatio;
if (l === 'bottom-left') {
linear_algebra_1.Mat4.setTranslation(this.scene.view, linear_algebra_1.Vec3.create(-camera.viewport.width / 2 + r + ox, -camera.viewport.height / 2 + r + oy, 0));
}
else if (l === 'bottom-right') {
linear_algebra_1.Mat4.setTranslation(this.scene.view, linear_algebra_1.Vec3.create(camera.viewport.width / 2 - r - ox, -camera.viewport.height / 2 + r + oy, 0));
}
else if (l === 'top-left') {
linear_algebra_1.Mat4.setTranslation(this.scene.view, linear_algebra_1.Vec3.create(-camera.viewport.width / 2 + r + ox, camera.viewport.height / 2 - r - oy, 0));
}
else if (l === 'top-right') {
linear_algebra_1.Mat4.setTranslation(this.scene.view, linear_algebra_1.Vec3.create(camera.viewport.width / 2 - r - ox, camera.viewport.height / 2 - r - oy, 0));
}
else {
(0, type_helpers_1.assertUnreachable)(l);
}
}
}
exports.CameraHelper = CameraHelper;
var CameraHelperAxis;
(function (CameraHelperAxis) {
CameraHelperAxis[CameraHelperAxis["None"] = 0] = "None";
CameraHelperAxis[CameraHelperAxis["X"] = 1] = "X";
CameraHelperAxis[CameraHelperAxis["Y"] = 2] = "Y";
CameraHelperAxis[CameraHelperAxis["Z"] = 3] = "Z";
CameraHelperAxis[CameraHelperAxis["XY"] = 4] = "XY";
CameraHelperAxis[CameraHelperAxis["XZ"] = 5] = "XZ";
CameraHelperAxis[CameraHelperAxis["YZ"] = 6] = "YZ";
CameraHelperAxis[CameraHelperAxis["Origin"] = 7] = "Origin";
})(CameraHelperAxis || (exports.CameraHelperAxis = CameraHelperAxis = {}));
function getAxisLabel(axis, cameraHelper) {
const a = cameraHelper.props.axes;
const x = a.name === 'on' ? a.params.labelX : 'X';
const y = a.name === 'on' ? a.params.labelY : 'Y';
const z = a.name === 'on' ? a.params.labelZ : 'Z';
switch (axis) {
case CameraHelperAxis.X: return `${x} Axis`;
case CameraHelperAxis.Y: return `${y} Axis`;
case CameraHelperAxis.Z: return `${z} Axis`;
case CameraHelperAxis.XY: return `${x}${y} Plane`;
case CameraHelperAxis.XZ: return `${x}${z} Plane`;
case CameraHelperAxis.YZ: return `${y}${z} Plane`;
case CameraHelperAxis.Origin: return 'Origin';
default: return 'Axes';
}
}
function CameraAxesLoci(cameraHelper, groupId, instanceId) {
return (0, loci_1.DataLoci)('camera-axes', cameraHelper, [{ groupId, instanceId }], void 0 /** bounding sphere */, () => getAxisLabel(groupId, cameraHelper));
}
function isCameraAxesLoci(x) {
return x.kind === 'data-loci' && x.tag === 'camera-axes';
}
function updateCamera(camera, viewport, viewOffset) {
const { near, far } = camera;
const fullLeft = -viewport.width / 2;
const fullRight = viewport.width / 2;
const fullTop = viewport.height / 2;
const fullBottom = -viewport.height / 2;
const dx = (fullRight - fullLeft) / 2;
const dy = (fullTop - fullBottom) / 2;
const cx = (fullRight + fullLeft) / 2;
const cy = (fullTop + fullBottom) / 2;
let left = cx - dx;
let right = cx + dx;
let top = cy + dy;
let bottom = cy - dy;
if (viewOffset.enabled) {
const scaleW = (fullRight - fullLeft) / viewOffset.width;
const scaleH = (fullTop - fullBottom) / viewOffset.height;
left += scaleW * viewOffset.offsetX;
right = left + scaleW * viewOffset.width;
top -= scaleH * viewOffset.offsetY;
bottom = top - scaleH * viewOffset.height;
}
linear_algebra_1.Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
}
function createAxesMesh(props, mesh) {
const state = mesh_builder_1.MeshBuilder.createState(512, 256, mesh);
const scale = 100 * props.scale;
const radius = props.radiusScale * scale;
const textScale = props.showLabels ? 100 * props.labelScale / 3 : 0;
const x = linear_algebra_1.Vec3.scale((0, linear_algebra_1.Vec3)(), linear_algebra_1.Vec3.unitX, scale - textScale);
const y = linear_algebra_1.Vec3.scale((0, linear_algebra_1.Vec3)(), linear_algebra_1.Vec3.unitY, scale - textScale);
const z = linear_algebra_1.Vec3.scale((0, linear_algebra_1.Vec3)(), linear_algebra_1.Vec3.unitZ, scale - textScale);
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
state.currentGroup = CameraHelperAxis.Origin;
(0, sphere_1.addSphere)(state, linear_algebra_1.Vec3.origin, radius, 2);
state.currentGroup = CameraHelperAxis.X;
(0, sphere_1.addSphere)(state, x, radius, 2);
(0, cylinder_1.addCylinder)(state, linear_algebra_1.Vec3.origin, x, 1, cylinderProps);
state.currentGroup = CameraHelperAxis.Y;
(0, sphere_1.addSphere)(state, y, radius, 2);
(0, cylinder_1.addCylinder)(state, linear_algebra_1.Vec3.origin, y, 1, cylinderProps);
state.currentGroup = CameraHelperAxis.Z;
(0, sphere_1.addSphere)(state, z, radius, 2);
(0, cylinder_1.addCylinder)(state, linear_algebra_1.Vec3.origin, z, 1, cylinderProps);
if (props.showPlanes) {
linear_algebra_1.Vec3.scale(x, x, 0.5);
linear_algebra_1.Vec3.scale(y, y, 0.5);
linear_algebra_1.Vec3.scale(z, z, 0.5);
state.currentGroup = CameraHelperAxis.XY;
mesh_builder_1.MeshBuilder.addTriangle(state, linear_algebra_1.Vec3.origin, x, y);
mesh_builder_1.MeshBuilder.addTriangle(state, linear_algebra_1.Vec3.origin, y, x);
const xy = linear_algebra_1.Vec3.add((0, linear_algebra_1.Vec3)(), x, y);
mesh_builder_1.MeshBuilder.addTriangle(state, xy, x, y);
mesh_builder_1.MeshBuilder.addTriangle(state, xy, y, x);
state.currentGroup = CameraHelperAxis.XZ;
mesh_builder_1.MeshBuilder.addTriangle(state, linear_algebra_1.Vec3.origin, x, z);
mesh_builder_1.MeshBuilder.addTriangle(state, linear_algebra_1.Vec3.origin, z, x);
const xz = linear_algebra_1.Vec3.add((0, linear_algebra_1.Vec3)(), x, z);
mesh_builder_1.MeshBuilder.addTriangle(state, xz, x, z);
mesh_builder_1.MeshBuilder.addTriangle(state, xz, z, x);
state.currentGroup = CameraHelperAxis.YZ;
mesh_builder_1.MeshBuilder.addTriangle(state, linear_algebra_1.Vec3.origin, y, z);
mesh_builder_1.MeshBuilder.addTriangle(state, linear_algebra_1.Vec3.origin, z, y);
const yz = linear_algebra_1.Vec3.add((0, linear_algebra_1.Vec3)(), y, z);
mesh_builder_1.MeshBuilder.addTriangle(state, yz, y, z);
mesh_builder_1.MeshBuilder.addTriangle(state, yz, z, y);
}
return mesh_builder_1.MeshBuilder.getMesh(state);
}
function getAxesMeshShape(props, shape) {
const scale = 100 * props.scale;
const mesh = createAxesMesh(props, shape === null || shape === void 0 ? void 0 : shape.geometry);
mesh.setBoundingSphere(geometry_1.Sphere3D.create(linear_algebra_1.Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
const getColor = (groupId) => {
switch (groupId) {
case CameraHelperAxis.X: return props.colorX;
case CameraHelperAxis.Y: return props.colorY;
case CameraHelperAxis.Z: return props.colorZ;
case CameraHelperAxis.XY: return props.planeColorXY;
case CameraHelperAxis.XZ: return props.planeColorXZ;
case CameraHelperAxis.YZ: return props.planeColorYZ;
case CameraHelperAxis.Origin: return props.originColor;
default: return names_1.ColorNames.grey;
}
};
return shape_1.Shape.create('axes-mesh', {}, mesh, getColor, () => 1, () => '');
}
function createMeshRenderObject(props) {
const shape = getAxesMeshShape(props);
return shape_1.Shape.createRenderObject(shape, {
...param_definition_1.ParamDefinition.getDefaultValues(mesh_1.Mesh.Params),
...props,
ignoreLight: true,
cellSize: 0,
});
}
//
function createAxesText(props, text) {
const builder = text_builder_1.TextBuilder.create(props, 8, 8, text);
const scale = 100 * props.scale;
const x = linear_algebra_1.Vec3.scale((0, linear_algebra_1.Vec3)(), linear_algebra_1.Vec3.unitX, scale);
const y = linear_algebra_1.Vec3.scale((0, linear_algebra_1.Vec3)(), linear_algebra_1.Vec3.unitY, scale);
const z = linear_algebra_1.Vec3.scale((0, linear_algebra_1.Vec3)(), linear_algebra_1.Vec3.unitZ, scale);
const textScale = 100 * props.labelScale;
builder.add(props.labelX, x[0], x[1], x[2], 0.0, textScale, CameraHelperAxis.X);
builder.add(props.labelY, y[0], y[1], y[2], 0.0, textScale, CameraHelperAxis.Y);
builder.add(props.labelZ, z[0], z[1], z[2], 0.0, textScale, CameraHelperAxis.Z);
return builder.getText();
}
function getAxesTextShape(props, shape) {
const scale = 100 * props.scale;
const text = createAxesText(props, shape === null || shape === void 0 ? void 0 : shape.geometry);
text.setBoundingSphere(geometry_1.Sphere3D.create(linear_algebra_1.Vec3.create(scale / 2, scale / 2, scale / 2), scale));
const getColor = (groupId) => {
switch (groupId) {
case CameraHelperAxis.X: return props.labelColorX;
case CameraHelperAxis.Y: return props.labelColorY;
case CameraHelperAxis.Z: return props.labelColorZ;
default: return names_1.ColorNames.grey;
}
};
return shape_1.Shape.create('axes-text', {}, text, getColor, () => 1, () => '');
}
function createTextRenderObject(props) {
const shape = getAxesTextShape(props);
return shape_1.Shape.createRenderObject(shape, {
...param_definition_1.ParamDefinition.getDefaultValues(text_1.Text.Params),
...props,
alpha: props.labelOpacity,
cellSize: 0,
});
}