mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d67eb642 | ||
|
|
c76c8335d1 | ||
|
|
42528b7be5 | ||
|
|
3d651b40f0 | ||
|
|
c94acff82e | ||
|
|
93b9953f6d | ||
|
|
dcaf6f8927 | ||
|
|
d96eb404e1 | ||
|
|
07322819f0 | ||
|
|
9be686686d | ||
|
|
3df539c9e1 | ||
|
|
903f06bab6 | ||
|
|
13f2810f90 | ||
|
|
e840059a38 | ||
|
|
1bd0339dec | ||
|
|
d0eaf2f71e | ||
|
|
254c9efbf5 | ||
|
|
73e9aed98c | ||
|
|
6e60d9713a | ||
|
|
ef0593b1e2 | ||
|
|
7831fa8b33 | ||
|
|
d4bb1a6e93 |
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -8,10 +8,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install node v12
|
||||
- name: install node v14
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 14
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: eslint
|
||||
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -6,15 +6,31 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v2.3.4] - 2021-10-12
|
||||
|
||||
- Fix pickScale not taken into account in line/point shader
|
||||
- Add pixel-scale, pick-scale & pick-padding GET params to Viewer app
|
||||
- Fix selecting bonds not adding their atoms in selection manager
|
||||
- Add ``preferAtoms`` option to SelectLoci/HighlightLoci behaviors
|
||||
- Make the implicit atoms of bond visuals pickable
|
||||
- Add ``preferAtomPixelPadding`` to Canvas3dInteractionHelper
|
||||
- Add points & crosses visuals to Line representation
|
||||
- Add ``pickPadding`` config option (look around in case target pixel is empty)
|
||||
- Add ``multipleBonds`` param to bond visuals with options: off, symmetric, offset
|
||||
- Fix ``argparse`` config in servers.
|
||||
|
||||
## [v2.3.3] - 2021-10-01
|
||||
|
||||
- Fix direct volume shader
|
||||
|
||||
## [v2.3.2] - 2021-10-01
|
||||
|
||||
- Prefer WebGL1 on iOS devices until WebGL2 support has stabilized.
|
||||
|
||||
|
||||
## [v2.3.1] - 2021-09-28
|
||||
|
||||
- Add Charmm saccharide names
|
||||
- Treat missing occupancy column as occupany of 1
|
||||
- Treat missing occupancy column as occupancy of 1
|
||||
- Fix line shader not accounting for aspect ratio
|
||||
- [Breaking] Fix point repr & shader
|
||||
- Was unusable with ``wboit``
|
||||
|
||||
15814
package-lock.json
generated
15814
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.3.2",
|
||||
"version": "2.3.4",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -94,33 +94,33 @@
|
||||
"@graphql-codegen/typescript": "^2.2.2",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.1.4",
|
||||
"@graphql-codegen/typescript-operations": "^2.1.4",
|
||||
"@graphql-codegen/typescript-operations": "^2.1.6",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
||||
"@typescript-eslint/parser": "^4.31.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.32.0",
|
||||
"@typescript-eslint/parser": "^4.32.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^6.2.1",
|
||||
"cpx2": "^3.0.2",
|
||||
"concurrently": "^6.3.0",
|
||||
"cpx2": "^4.0.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.2.0",
|
||||
"css-loader": "^6.3.0",
|
||||
"eslint": "^7.32.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"graphql": "^15.5.3",
|
||||
"http-server": "^13.0.1",
|
||||
"jest": "^27.1.1",
|
||||
"graphql": "^15.6.0",
|
||||
"http-server": "^13.0.2",
|
||||
"jest": "^27.2.4",
|
||||
"mini-css-extract-plugin": "^2.3.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass-loader": "^12.1.0",
|
||||
"simple-git": "^2.45.1",
|
||||
"simple-git": "^2.46.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.2.1",
|
||||
"style-loader": "^3.3.0",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^4.4.3",
|
||||
"webpack": "^5.52.1",
|
||||
"webpack": "^5.56.0",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
},
|
||||
@@ -129,10 +129,10 @@
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.9.1",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/node": "^16.10.2",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/react": "^17.0.20",
|
||||
"@types/react": "^17.0.27",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
@@ -146,8 +146,8 @@
|
||||
"node-fetch": "^2.6.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"rxjs": "^7.3.0",
|
||||
"swagger-ui-dist": "^3.52.1",
|
||||
"rxjs": "^7.3.1",
|
||||
"swagger-ui-dist": "^3.52.3",
|
||||
"tslib": "^2.3.1",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var mapProvider = getParam('map-provider', '[^&]+').trim().toLowerCase();
|
||||
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
@@ -61,7 +64,10 @@
|
||||
emdbProvider: emdbProvider || 'pdbe',
|
||||
volumeStreamingServer: (mapProvider || 'pdbe') === 'rcsb'
|
||||
? 'https://maps.rcsb.org'
|
||||
: 'https://www.ebi.ac.uk/pdbe/densities'
|
||||
: 'https://www.ebi.ac.uk/pdbe/densities',
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
});
|
||||
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 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>
|
||||
@@ -71,9 +71,11 @@ const DefaultViewerOptions = {
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
disableAntialiasing: false,
|
||||
pixelScale: 1,
|
||||
enableWboit: true,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
pickPadding: PluginConfig.General.PickPadding.defaultValue,
|
||||
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
@@ -130,6 +132,8 @@ export class Viewer {
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.PickPadding, o.pickPadding],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 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>
|
||||
@@ -27,6 +27,10 @@ interface ICamera {
|
||||
readonly fogNear: number,
|
||||
}
|
||||
|
||||
const tmpPos1 = Vec3();
|
||||
const tmpPos2 = Vec3();
|
||||
const tmpClip = Vec4();
|
||||
|
||||
class Camera implements ICamera {
|
||||
readonly view: Mat4 = Mat4.identity();
|
||||
readonly projection: Mat4 = Mat4.identity();
|
||||
@@ -155,14 +159,32 @@ class Camera implements ICamera {
|
||||
}
|
||||
}
|
||||
|
||||
/** Transform point into 2D window coordinates. */
|
||||
project(out: Vec4, point: Vec3) {
|
||||
return cameraProject(out, point, this.viewport, this.projectionView);
|
||||
}
|
||||
|
||||
unproject(out: Vec3, point: Vec3) {
|
||||
/**
|
||||
* Transform point from screen space to 3D coordinates.
|
||||
* The point must have `x` and `y` set to 2D window coordinates
|
||||
* and `z` between 0 (near) and 1 (far); the optional `w` is not used.
|
||||
*/
|
||||
unproject(out: Vec3, point: Vec3 | Vec4) {
|
||||
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView);
|
||||
}
|
||||
|
||||
/** World space pixel size at given `point` */
|
||||
getPixelSize(point: Vec3) {
|
||||
// project -> unproject of `point` does not exactly return the same
|
||||
// to get a sufficiently accurate measure we unproject the original
|
||||
// clip position in addition to the one shifted bey one pixel
|
||||
this.project(tmpClip, point);
|
||||
this.unproject(tmpPos1, tmpClip);
|
||||
tmpClip[0] += 1;
|
||||
this.unproject(tmpPos2, tmpClip);
|
||||
return Vec3.distance(tmpPos1, tmpPos2);
|
||||
}
|
||||
|
||||
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(0, 0, 128, 128), props: Partial<{ pixelScale: number }> = {}) {
|
||||
this.viewport = viewport;
|
||||
this.pixelScale = props.pixelScale || 1;
|
||||
@@ -178,7 +200,7 @@ namespace Camera {
|
||||
/**
|
||||
* Sets an offseted view in a larger frustum. This is useful for
|
||||
* - multi-window or multi-monitor/multi-machine setups
|
||||
* - jittering the camera position for
|
||||
* - jittering the camera position for sampling
|
||||
*/
|
||||
export interface ViewOffset {
|
||||
enabled: boolean,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -55,14 +55,11 @@ namespace Viewport {
|
||||
|
||||
//
|
||||
|
||||
const NEAR_RANGE = 0;
|
||||
const FAR_RANGE = 1;
|
||||
|
||||
const tmpVec4 = Vec4();
|
||||
|
||||
/** Transform point into 2D window coordinates. */
|
||||
export function cameraProject(out: Vec4, point: Vec3, viewport: Viewport, projectionView: Mat4) {
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
|
||||
const { x, y, width, height } = viewport;
|
||||
|
||||
// clip space -> NDC -> window coordinates, implicit 1.0 for w component
|
||||
Vec4.set(tmpVec4, point[0], point[1], point[2], 1.0);
|
||||
@@ -78,27 +75,28 @@ export function cameraProject(out: Vec4, point: Vec3, viewport: Viewport, projec
|
||||
tmpVec4[2] /= w;
|
||||
}
|
||||
|
||||
// transform into window coordinates, set fourth component is (1/clip.w) as in gl_FragCoord.w
|
||||
out[0] = vX + vWidth / 2 * tmpVec4[0] + (0 + vWidth / 2);
|
||||
out[1] = vY + vHeight / 2 * tmpVec4[1] + (0 + vHeight / 2);
|
||||
out[2] = (FAR_RANGE - NEAR_RANGE) / 2 * tmpVec4[2] + (FAR_RANGE + NEAR_RANGE) / 2;
|
||||
// transform into window coordinates, set fourth component to 1 / clip.w as in gl_FragCoord.w
|
||||
out[0] = (tmpVec4[0] + 1) * width * 0.5 + x;
|
||||
out[1] = (1 - tmpVec4[1]) * height * 0.5 + y; // flip Y
|
||||
out[2] = (tmpVec4[2] + 1) * 0.5;
|
||||
out[3] = w === 0 ? 0 : 1 / w;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform point from screen space to 3D coordinates.
|
||||
* The point must have x and y set to 2D window coordinates and z between 0 (near) and 1 (far).
|
||||
* The point must have `x` and `y` set to 2D window coordinates
|
||||
* and `z` between 0 (near) and 1 (far); the optional `w` is not used.
|
||||
*/
|
||||
export function cameraUnproject(out: Vec3, point: Vec3, viewport: Viewport, inverseProjectionView: Mat4) {
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
|
||||
export function cameraUnproject(out: Vec3, point: Vec3 | Vec4, viewport: Viewport, inverseProjectionView: Mat4) {
|
||||
const { x, y, width, height } = viewport;
|
||||
|
||||
const x = point[0] - vX;
|
||||
const y = (vHeight - point[1] - 1) - vY;
|
||||
const z = point[2];
|
||||
const px = point[0] - x;
|
||||
const py = (height - point[1] - 1) - y;
|
||||
const pz = point[2];
|
||||
|
||||
out[0] = (2 * x) / vWidth - 1;
|
||||
out[1] = (2 * y) / vHeight - 1;
|
||||
out[2] = 2 * z - 1;
|
||||
out[0] = (2 * px) / width - 1;
|
||||
out[1] = (2 * py) / height - 1;
|
||||
out[2] = 2 * pz - 1;
|
||||
return Vec3.transformMat4(out, out, inverseProjectionView);
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import { Camera } from './camera';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { DebugHelperParams } from './helper/bounding-sphere-helper';
|
||||
import { SetUtils } from '../mol-util/set';
|
||||
import { Canvas3dInteractionHelper } from './helper/interaction-events';
|
||||
import { Canvas3dInteractionHelper, Canvas3dInteractionHelperParams } from './helper/interaction-events';
|
||||
import { PostprocessingParams } from './passes/postprocessing';
|
||||
import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
|
||||
import { PickData } from './passes/pick';
|
||||
@@ -84,6 +84,7 @@ export const Canvas3DParams = {
|
||||
marking: PD.Group(MarkingParams),
|
||||
renderer: PD.Group(RendererParams),
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
interaction: PD.Group(Canvas3dInteractionHelperParams),
|
||||
debug: PD.Group(DebugHelperParams),
|
||||
handle: PD.Group(HandleHelperParams),
|
||||
};
|
||||
@@ -115,6 +116,8 @@ namespace Canvas3DContext {
|
||||
preserveDrawingBuffer: true,
|
||||
pixelScale: 1,
|
||||
pickScale: 0.25,
|
||||
/** extra pixels to around target to check in case target is empty */
|
||||
pickPadding: 1,
|
||||
enableWboit: true,
|
||||
preferWebGl1: false
|
||||
};
|
||||
@@ -307,8 +310,8 @@ namespace Canvas3D {
|
||||
const renderer = Renderer.create(webgl, p.renderer);
|
||||
const helper = new Helper(webgl, scene, p);
|
||||
|
||||
const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height });
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera);
|
||||
const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height }, attribs.pickPadding);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera, p.interaction);
|
||||
const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
|
||||
|
||||
let cameraResetRequested = false;
|
||||
@@ -644,6 +647,7 @@ namespace Canvas3D {
|
||||
multiSample: { ...p.multiSample },
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
interaction: { ...interactionHelper.props },
|
||||
debug: { ...helper.debug.props },
|
||||
handle: { ...helper.handle.props },
|
||||
};
|
||||
@@ -780,6 +784,7 @@ namespace Canvas3D {
|
||||
if (props.multiSample) Object.assign(p.multiSample, props.multiSample);
|
||||
if (props.renderer) renderer.setProps(props.renderer);
|
||||
if (props.trackball) controls.setProps(props.trackball);
|
||||
if (props.interaction) interactionHelper.setProps(props.interaction);
|
||||
if (props.debug) helper.debug.setProps(props.debug);
|
||||
if (props.handle) helper.handle.setProps(props.handle);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 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>
|
||||
@@ -11,6 +11,8 @@ import { InputObserver, ModifiersKeys, ButtonsType } from '../../mol-util/input/
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Camera } from '../camera';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Bond } from '../../mol-model/structure';
|
||||
|
||||
type Canvas3D = import('../canvas3d').Canvas3D
|
||||
type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
|
||||
@@ -19,6 +21,17 @@ type ClickEvent = import('../canvas3d').Canvas3D.ClickEvent
|
||||
|
||||
const enum InputEvent { Move, Click, Drag }
|
||||
|
||||
const tmpPosA = Vec3();
|
||||
const tmpPos = Vec3();
|
||||
const tmpNorm = Vec3();
|
||||
|
||||
export const Canvas3dInteractionHelperParams = {
|
||||
maxFps: PD.Numeric(30, { min: 10, max: 60, step: 10 }),
|
||||
preferAtomPixelPadding: PD.Numeric(3, { min: 0, max: 20, step: 1 }, { description: 'Number of extra pixels at which to prefer atoms over bonds.' }),
|
||||
};
|
||||
export type Canvas3dInteractionHelperParams = typeof Canvas3dInteractionHelperParams
|
||||
export type Canvas3dInteractionHelperProps = PD.Values<Canvas3dInteractionHelperParams>
|
||||
|
||||
export class Canvas3dInteractionHelper {
|
||||
private ev = RxEventHelper.create();
|
||||
|
||||
@@ -48,6 +61,12 @@ export class Canvas3dInteractionHelper {
|
||||
private button: ButtonsType.Flag = ButtonsType.create(0);
|
||||
private modifiers: ModifiersKeys = ModifiersKeys.None;
|
||||
|
||||
readonly props: Canvas3dInteractionHelperProps;
|
||||
|
||||
setProps(props: Partial<Canvas3dInteractionHelperProps>) {
|
||||
Object.assign(this.props, props);
|
||||
}
|
||||
|
||||
private identify(e: InputEvent, t: number) {
|
||||
const xyChanged = this.startX !== this.endX || this.startY !== this.endY;
|
||||
|
||||
@@ -70,7 +89,7 @@ export class Canvas3dInteractionHelper {
|
||||
}
|
||||
|
||||
if (e === InputEvent.Click) {
|
||||
const loci = this.getLoci(this.id);
|
||||
const loci = this.getLoci(this.id, this.position);
|
||||
this.events.click.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
|
||||
this.prevLoci = loci;
|
||||
return;
|
||||
@@ -78,13 +97,13 @@ export class Canvas3dInteractionHelper {
|
||||
|
||||
if (!this.inside || this.currentIdentifyT !== t || !xyChanged || this.outsideViewport(this.endX, this.endY)) return;
|
||||
|
||||
const loci = this.getLoci(this.id);
|
||||
const loci = this.getLoci(this.id, this.position);
|
||||
this.events.hover.next({ current: loci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, page: Vec2.create(this.endX, this.endY), position: this.position });
|
||||
this.prevLoci = loci;
|
||||
}
|
||||
|
||||
tick(t: number) {
|
||||
if (this.inside && t - this.prevT > 1000 / this.maxFps) {
|
||||
if (this.inside && t - this.prevT > 1000 / this.props.maxFps) {
|
||||
this.prevT = t;
|
||||
this.currentIdentifyT = t;
|
||||
this.identify(this.isInteracting ? InputEvent.Drag : InputEvent.Move, t);
|
||||
@@ -144,11 +163,34 @@ export class Canvas3dInteractionHelper {
|
||||
);
|
||||
}
|
||||
|
||||
private getLoci(pickingId: PickingId | undefined, position: Vec3 | undefined) {
|
||||
const { repr, loci } = this.lociGetter(pickingId);
|
||||
if (position && repr && Bond.isLoci(loci) && loci.bonds.length === 2) {
|
||||
const { aUnit, aIndex } = loci.bonds[0];
|
||||
aUnit.conformation.position(aUnit.elements[aIndex], tmpPosA);
|
||||
Vec3.sub(tmpNorm, this.camera.state.position, this.camera.state.target);
|
||||
Vec3.projectPointOnPlane(tmpPos, position, tmpNorm, tmpPosA);
|
||||
const pixelSize = this.camera.getPixelSize(tmpPos);
|
||||
let radius = repr.theme.size.size(loci.bonds[0]) * (repr.props.sizeFactor ?? 1);
|
||||
if (repr.props.lineSizeAttenuation === false) {
|
||||
// divide by two to get radius
|
||||
radius *= pixelSize / 2;
|
||||
}
|
||||
radius += this.props.preferAtomPixelPadding * pixelSize;
|
||||
if (Vec3.distance(tmpPos, tmpPosA) < radius) {
|
||||
return { repr, loci: Bond.toFirstStructureElementLoci(loci) };
|
||||
}
|
||||
}
|
||||
return { repr, loci };
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.ev.dispose();
|
||||
}
|
||||
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, private maxFps: number = 30) {
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private lociGetter: Canvas3D['getLoci'], private input: InputObserver, private camera: Camera, props: Partial<Canvas3dInteractionHelperProps> = {}) {
|
||||
this.props = { ...PD.getDefaultValues(Canvas3dInteractionHelperParams), ...props };
|
||||
|
||||
input.drag.subscribe(({ x, y, buttons, button, modifiers }) => {
|
||||
this.isInteracting = true;
|
||||
// console.log('drag');
|
||||
|
||||
@@ -362,6 +362,7 @@ export class DrawPass {
|
||||
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
|
||||
renderer.setTransparentBackground(transparentBackground);
|
||||
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
|
||||
renderer.setPixelRatio(this.webgl.pixelRatio);
|
||||
|
||||
if (StereoCamera.is(camera)) {
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,6 +11,7 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { spiral2d } from '../../mol-math/misc';
|
||||
import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
|
||||
import { Camera, ICamera } from '../camera';
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
@@ -88,6 +89,7 @@ export class PickPass {
|
||||
|
||||
this.groupPickTarget.bind();
|
||||
this.renderVariant(renderer, camera, scene, helper, 'pickGroup');
|
||||
// printTexture(this.webgl, this.groupPickTarget.texture, { id: 'group' })
|
||||
|
||||
this.depthPickTarget.bind();
|
||||
this.renderVariant(renderer, camera, scene, helper, 'depth');
|
||||
@@ -111,6 +113,8 @@ export class PickHelper {
|
||||
private pickHeight: number
|
||||
private halfPickWidth: number
|
||||
|
||||
private spiral: [number, number][]
|
||||
|
||||
private setupBuffers() {
|
||||
const bufferSize = this.pickWidth * this.pickHeight * 4;
|
||||
if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
|
||||
@@ -138,6 +142,8 @@ export class PickHelper {
|
||||
|
||||
this.setupBuffers();
|
||||
}
|
||||
|
||||
this.spiral = spiral2d(Math.round(this.pickScale * this.pickPadding));
|
||||
}
|
||||
|
||||
private syncBuffers() {
|
||||
@@ -177,6 +183,7 @@ export class PickHelper {
|
||||
|
||||
renderer.setTransparentBackground(false);
|
||||
renderer.setDrawingBufferSize(this.pickPass.objectPickTarget.getWidth(), this.pickPass.objectPickTarget.getHeight());
|
||||
renderer.setPixelRatio(this.pickScale);
|
||||
|
||||
if (StereoCamera.is(camera)) {
|
||||
renderer.setViewport(pickX, pickY, halfPickWidth, pickHeight);
|
||||
@@ -192,7 +199,7 @@ export class PickHelper {
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
|
||||
private identifyInternal(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
|
||||
const { webgl, pickScale } = this;
|
||||
if (webgl.isContextLost) return;
|
||||
|
||||
@@ -251,7 +258,14 @@ export class PickHelper {
|
||||
return { id: { objectId, instanceId, groupId }, position };
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport) {
|
||||
identify(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
|
||||
for (const d of this.spiral) {
|
||||
const pickData = this.identifyInternal(x + d[0], y + d[1], camera);
|
||||
if (pickData) return pickData;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private helper: Helper, private pickPass: PickPass, viewport: Viewport, readonly pickPadding = 1) {
|
||||
this.setViewport(viewport.x, viewport.y, viewport.width, viewport.height);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { LocationIterator, PositionLocation } from '../../../mol-geo/util/locati
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
|
||||
import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { createNullTexture, Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Mat4, Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
@@ -129,7 +129,15 @@ export namespace DirectVolume {
|
||||
}
|
||||
|
||||
export function createEmpty(directVolume?: DirectVolume): DirectVolume {
|
||||
return {} as DirectVolume; // TODO
|
||||
const bbox = Box3D();
|
||||
const gridDimension = Vec3();
|
||||
const transform = Mat4.identity();
|
||||
const unitToCartn = Mat4.identity();
|
||||
const cellDim = Vec3();
|
||||
const texture = createNullTexture();
|
||||
const stats = Grid.One.stats;
|
||||
const packedGroup = false;
|
||||
return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume);
|
||||
}
|
||||
|
||||
export function createRenderModeParam(stats?: Grid['stats']) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { hashFnv32a } from '../../../mol-data/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { calculateTransformBoundingSphere, TextureImage } from '../../../mol-gl/renderable/util';
|
||||
import { calculateTransformBoundingSphere, createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Vec2, Vec4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
@@ -113,7 +113,10 @@ namespace Image {
|
||||
}
|
||||
|
||||
export function createEmpty(image?: Image): Image {
|
||||
return {} as Image; // TODO
|
||||
const imageTexture = createTextureImage(0, 4, Uint8Array);
|
||||
const corners = image ? image.cornerBuffer.ref.value : new Float32Array(8 * 3);
|
||||
const groupTexture = createTextureImage(0, 4, Uint8Array);
|
||||
return create(imageTexture, corners, groupTexture, image);
|
||||
}
|
||||
|
||||
export const Params = {
|
||||
|
||||
@@ -164,7 +164,7 @@ export namespace Lines {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
|
||||
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -127,7 +127,7 @@ export namespace Points {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
|
||||
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
|
||||
pointSizeAttenuation: PD.Boolean(false),
|
||||
pointStyle: PD.Select('square', PD.objectToOptions(StyleTypes)),
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
|
||||
import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { createNullTexture, Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
|
||||
import { createEmptyClipping } from '../clipping-data';
|
||||
import { NullLocation } from '../../../mol-model/location';
|
||||
@@ -97,7 +97,11 @@ export namespace TextureMesh {
|
||||
}
|
||||
|
||||
export function createEmpty(textureMesh?: TextureMesh): TextureMesh {
|
||||
return {} as TextureMesh; // TODO
|
||||
const vt = textureMesh ? textureMesh.vertexTexture.ref.value : createNullTexture();
|
||||
const gt = textureMesh ? textureMesh.groupTexture.ref.value : createNullTexture();
|
||||
const nt = textureMesh ? textureMesh.normalTexture.ref.value : createNullTexture();
|
||||
const bs = textureMesh ? textureMesh.boundingSphere : Sphere3D();
|
||||
return create(0, 0, vt, gt, nt, bs, textureMesh);
|
||||
}
|
||||
|
||||
export const Params = {
|
||||
|
||||
@@ -62,6 +62,7 @@ interface Renderer {
|
||||
setViewport: (x: number, y: number, width: number, height: number) => void
|
||||
setTransparentBackground: (value: boolean) => void
|
||||
setDrawingBufferSize: (width: number, height: number) => void
|
||||
setPixelRatio: (value: number) => void
|
||||
|
||||
dispose: () => void
|
||||
}
|
||||
@@ -716,6 +717,9 @@ namespace Renderer {
|
||||
ValueCell.update(globalUniforms.uDrawingBufferSize, Vec2.set(drawingBufferSize, width, height));
|
||||
}
|
||||
},
|
||||
setPixelRatio: (value: number) => {
|
||||
ValueCell.update(globalUniforms.uPixelRatio, value);
|
||||
},
|
||||
|
||||
props: p,
|
||||
get stats(): RendererStats {
|
||||
|
||||
@@ -55,6 +55,7 @@ uniform vec3 uHighlightColor;
|
||||
uniform vec3 uSelectColor;
|
||||
uniform float uHighlightStrength;
|
||||
uniform float uSelectStrength;
|
||||
uniform int uMarkerPriority;
|
||||
|
||||
#if defined(dMarkerType_uniform)
|
||||
uniform float uMarker;
|
||||
|
||||
@@ -105,6 +105,7 @@ void main(){
|
||||
#else
|
||||
linewidth = size * uPixelRatio;
|
||||
#endif
|
||||
linewidth = max(1.0, linewidth);
|
||||
|
||||
// adjust for linewidth
|
||||
offset *= linewidth;
|
||||
|
||||
@@ -36,6 +36,7 @@ void main(){
|
||||
#else
|
||||
gl_PointSize = size * uPixelRatio;
|
||||
#endif
|
||||
gl_PointSize = max(1.0, gl_PointSize);
|
||||
|
||||
gl_Position = uProjection * mvPosition;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 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>
|
||||
@@ -540,9 +540,17 @@ namespace Vec3 {
|
||||
|
||||
/** Project `point` onto `vector` starting from `origin` */
|
||||
export function projectPointOnVector(out: Vec3, point: Vec3, vector: Vec3, origin: Vec3) {
|
||||
sub(out, copy(out, point), origin);
|
||||
sub(out, point, origin);
|
||||
const scalar = dot(vector, out) / squaredMagnitude(vector);
|
||||
return add(out, scale(out, copy(out, vector), scalar), origin);
|
||||
return add(out, scale(out, vector, scalar), origin);
|
||||
}
|
||||
|
||||
const tmpProjectPlane = zero();
|
||||
/** Project `point` onto `plane` defined by `normal` starting from `origin` */
|
||||
export function projectPointOnPlane(out: Vec3, point: Vec3, normal: Vec3, origin: Vec3) {
|
||||
normalize(tmpProjectPlane, normal);
|
||||
sub(out, point, origin);
|
||||
return sub(out, point, scale(tmpProjectPlane, tmpProjectPlane, dot(out, tmpProjectPlane)));
|
||||
}
|
||||
|
||||
export function projectOnVector(out: Vec3, p: Vec3, vector: Vec3) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -37,4 +37,28 @@ export function absMax(...values: number[]) {
|
||||
/** Length of an arc with angle in radians */
|
||||
export function arcLength(angle: number, radius: number) {
|
||||
return angle * radius;
|
||||
}
|
||||
|
||||
/** Create an outward spiral of given `radius` on a 2d grid */
|
||||
export function spiral2d(radius: number) {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
const delta = [0, -1];
|
||||
const size = radius * 2 + 1;
|
||||
const halfSize = size / 2;
|
||||
const out: [number, number][] = [];
|
||||
|
||||
for (let i = Math.pow(size, 2); i > 0; --i) {
|
||||
if ((-halfSize < x && x <= halfSize) && (-halfSize < y && y <= halfSize)) {
|
||||
out.push([x, y]);
|
||||
}
|
||||
|
||||
if (x === y || (x < 0 && x === -y) || (x > 0 && x === 1 - y)) {
|
||||
[delta[0], delta[1]] = [-delta[1], delta[0]]; // change direction
|
||||
}
|
||||
|
||||
x += delta[0];
|
||||
y += delta[1];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -283,8 +283,8 @@ namespace Loci {
|
||||
* Converts structure related loci to StructureElement.Loci and applies
|
||||
* granularity if given
|
||||
*/
|
||||
export function normalize(loci: Loci, granularity?: Granularity) {
|
||||
if (granularity !== 'element' && Bond.isLoci(loci)) {
|
||||
export function normalize(loci: Loci, granularity?: Granularity, alwaysConvertBonds = false) {
|
||||
if ((granularity !== 'element' || alwaysConvertBonds) && Bond.isLoci(loci)) {
|
||||
// convert Bond.Loci to a StructureElement.Loci so granularity can be applied
|
||||
loci = Bond.toStructureElementLoci(loci);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 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>
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Unit, StructureElement } from '../../structure';
|
||||
import { Structure } from '../structure';
|
||||
import { BondType } from '../../model/types';
|
||||
import { SortedArray, Iterator } from '../../../../mol-data/int';
|
||||
import { SortedArray, Iterator, OrderedSet } from '../../../../mol-data/int';
|
||||
import { CentroidHelper } from '../../../../mol-math/geometry/centroid-helper';
|
||||
import { Sphere3D } from '../../../../mol-math/geometry';
|
||||
|
||||
@@ -132,6 +132,11 @@ namespace Bond {
|
||||
return StructureElement.Loci(loci.structure, elements);
|
||||
}
|
||||
|
||||
export function toFirstStructureElementLoci(loci: Loci): StructureElement.Loci {
|
||||
const { aUnit, aIndex } = loci.bonds[0];
|
||||
return StructureElement.Loci(loci.structure, [{ unit: aUnit, indices: OrderedSet.ofSingleton(aIndex) }]);
|
||||
}
|
||||
|
||||
export function getType(structure: Structure, location: Location<Unit.Atomic>): BondType {
|
||||
if (location.aUnit === location.bUnit) {
|
||||
const bonds = location.aUnit.bonds;
|
||||
|
||||
@@ -101,10 +101,10 @@ namespace InteractivityManager {
|
||||
// TODO clear, then re-apply remaining providers
|
||||
}
|
||||
|
||||
protected normalizedLoci(reprLoci: Representation.Loci, applyGranularity = true) {
|
||||
protected normalizedLoci(reprLoci: Representation.Loci, applyGranularity: boolean, alwaysConvertBonds = false) {
|
||||
const { loci, repr } = reprLoci;
|
||||
const granularity = applyGranularity ? this.props.granularity : undefined;
|
||||
return { loci: Loci.normalize(loci, granularity), repr };
|
||||
return { loci: Loci.normalize(loci, granularity, alwaysConvertBonds), repr };
|
||||
}
|
||||
|
||||
protected mark(current: Representation.Loci, action: MarkerAction, noRender = false) {
|
||||
@@ -187,7 +187,8 @@ namespace InteractivityManager {
|
||||
toggle(current: Representation.Loci, applyGranularity = true) {
|
||||
if (Loci.isEmpty(current.loci)) return;
|
||||
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
const normalized = this.normalizedLoci(current, applyGranularity, true);
|
||||
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
this.toggleSel(normalized);
|
||||
} else {
|
||||
@@ -198,7 +199,7 @@ namespace InteractivityManager {
|
||||
toggleExtend(current: Representation.Loci, applyGranularity = true) {
|
||||
if (Loci.isEmpty(current.loci)) return;
|
||||
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
const normalized = this.normalizedLoci(current, applyGranularity, true);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
const loci = this.sel.tryGetRange(normalized.loci) || normalized.loci;
|
||||
this.toggleSel({ loci, repr: normalized.repr });
|
||||
@@ -206,7 +207,7 @@ namespace InteractivityManager {
|
||||
}
|
||||
|
||||
select(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
const normalized = this.normalizedLoci(current, applyGranularity, true);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
this.sel.modify('add', normalized.loci);
|
||||
}
|
||||
@@ -214,7 +215,7 @@ namespace InteractivityManager {
|
||||
}
|
||||
|
||||
selectJoin(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
const normalized = this.normalizedLoci(current, applyGranularity, true);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
this.sel.modify('intersect', normalized.loci);
|
||||
}
|
||||
@@ -222,7 +223,7 @@ namespace InteractivityManager {
|
||||
}
|
||||
|
||||
selectOnly(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
const normalized = this.normalizedLoci(current, applyGranularity, true);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
// only deselect for the structure of the given loci
|
||||
this.deselect({ loci: Structure.toStructureElementLoci(normalized.loci.structure), repr: normalized.repr }, false);
|
||||
@@ -232,7 +233,7 @@ namespace InteractivityManager {
|
||||
}
|
||||
|
||||
deselect(current: Representation.Loci, applyGranularity = true) {
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
const normalized = this.normalizedLoci(current, applyGranularity, true);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
this.sel.modify('remove', normalized.loci);
|
||||
}
|
||||
@@ -255,8 +256,9 @@ namespace InteractivityManager {
|
||||
// do a full deselect/select for the current structure so visuals that are
|
||||
// marked with granularity unequal to 'element' and join/intersect operations
|
||||
// are handled properly
|
||||
super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect, true);
|
||||
super.mark({ loci: this.sel.getLoci(loci.structure) }, MarkerAction.Select);
|
||||
const selLoci = this.sel.getLoci(loci.structure);
|
||||
super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect, !Loci.isEmpty(selLoci));
|
||||
super.mark({ loci: selLoci }, MarkerAction.Select);
|
||||
} else {
|
||||
super.mark(current, action);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observ
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { Structure, StructureElement, StructureProperties } from '../../../mol-model/structure';
|
||||
import { Bond, Structure, StructureElement, StructureProperties } from '../../../mol-model/structure';
|
||||
import { arrayMax } from '../../../mol-util/array';
|
||||
import { Representation } from '../../../mol-repr/representation';
|
||||
import { LociLabel } from '../../../mol-plugin-state/manager/loci-label';
|
||||
@@ -34,6 +34,7 @@ const DefaultHighlightLociBindings = {
|
||||
const HighlightLociParams = {
|
||||
bindings: PD.Value(DefaultHighlightLociBindings, { isHidden: true }),
|
||||
ignore: PD.Value<Loci['kind'][]>([], { isHidden: true }),
|
||||
preferAtoms: PD.Boolean(false, { description: 'Always prefer atoms over bonds' }),
|
||||
mark: PD.Boolean(true)
|
||||
};
|
||||
type HighlightLociProps = PD.Values<typeof HighlightLociParams>
|
||||
@@ -46,10 +47,17 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark(interactionLoci, action, noRender);
|
||||
}
|
||||
private getLoci(loci: Loci) {
|
||||
return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2
|
||||
? Bond.toFirstStructureElementLoci(loci)
|
||||
: loci;
|
||||
}
|
||||
register() {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
|
||||
if (this.params.ignore?.indexOf(current.loci.kind) >= 0) {
|
||||
|
||||
const loci = this.getLoci(current.loci);
|
||||
if (this.params.ignore?.indexOf(loci.kind) >= 0) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EmptyLoci });
|
||||
return;
|
||||
}
|
||||
@@ -58,13 +66,13 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
|
||||
if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
|
||||
// remove repr to highlight loci everywhere on hover
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ loci: current.loci });
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
matched = true;
|
||||
}
|
||||
|
||||
if (Binding.match(this.params.bindings.hoverHighlightOnlyExtend, buttons, modifiers)) {
|
||||
// remove repr to highlight loci everywhere on hover
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend({ loci: current.loci });
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend({ loci });
|
||||
matched = true;
|
||||
}
|
||||
|
||||
@@ -95,6 +103,7 @@ const DefaultSelectLociBindings = {
|
||||
const SelectLociParams = {
|
||||
bindings: PD.Value(DefaultSelectLociBindings, { isHidden: true }),
|
||||
ignore: PD.Value<Loci['kind'][]>([], { isHidden: true }),
|
||||
preferAtoms: PD.Boolean(false, { description: 'Always prefer atoms over bonds' }),
|
||||
mark: PD.Boolean(true)
|
||||
};
|
||||
type SelectLociProps = PD.Values<typeof SelectLociParams>
|
||||
@@ -108,6 +117,11 @@ export const SelectLoci = PluginBehavior.create({
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action, noRender);
|
||||
}
|
||||
private getLoci(loci: Loci) {
|
||||
return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2
|
||||
? Bond.toFirstStructureElementLoci(loci)
|
||||
: loci;
|
||||
}
|
||||
private applySelectMark(ref: string, clear?: boolean) {
|
||||
const cell = this.ctx.state.data.cells.get(ref);
|
||||
if (cell && SO.isRepresentation3D(cell.obj)) {
|
||||
@@ -123,10 +137,10 @@ export const SelectLoci = PluginBehavior.create({
|
||||
}
|
||||
}
|
||||
register() {
|
||||
const lociIsEmpty = (current: Representation.Loci) => Loci.isEmpty(current.loci);
|
||||
const lociIsNotEmpty = (current: Representation.Loci) => !Loci.isEmpty(current.loci);
|
||||
const lociIsEmpty = (loci: Loci) => Loci.isEmpty(loci);
|
||||
const lociIsNotEmpty = (loci: Loci) => !Loci.isEmpty(loci);
|
||||
|
||||
const actions: [keyof typeof DefaultSelectLociBindings, (current: Representation.Loci) => void, ((current: Representation.Loci) => boolean) | undefined][] = [
|
||||
const actions: [keyof typeof DefaultSelectLociBindings, (current: Representation.Loci) => void, ((current: Loci) => boolean) | undefined][] = [
|
||||
['clickSelect', current => this.ctx.managers.interactivity.lociSelects.select(current), lociIsNotEmpty],
|
||||
['clickToggle', current => this.ctx.managers.interactivity.lociSelects.toggle(current), lociIsNotEmpty],
|
||||
['clickToggleExtend', current => this.ctx.managers.interactivity.lociSelects.toggleExtend(current), lociIsNotEmpty],
|
||||
@@ -145,12 +159,14 @@ export const SelectLoci = PluginBehavior.create({
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || this.ctx.isBusy || !this.ctx.selectionMode) return;
|
||||
if (this.params.ignore?.indexOf(current.loci.kind) >= 0) return;
|
||||
|
||||
const loci = this.getLoci(current.loci);
|
||||
if (this.params.ignore?.indexOf(loci.kind) >= 0) return;
|
||||
|
||||
// only trigger the 1st action that matches
|
||||
for (const [binding, action, condition] of actions) {
|
||||
if (Binding.match(this.params.bindings[binding], button, modifiers) && (!condition || condition(current))) {
|
||||
action(current);
|
||||
if (Binding.match(this.params.bindings[binding], button, modifiers) && (!condition || condition(loci))) {
|
||||
action({ repr: current.repr, loci });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export const PluginConfig = {
|
||||
DisablePreserveDrawingBuffer: item('plugin-config.disable-preserve-drawing-buffer', false),
|
||||
PixelScale: item('plugin-config.pixel-scale', 1),
|
||||
PickScale: item('plugin-config.pick-scale', 0.25),
|
||||
PickPadding: item('plugin-config.pick-padding', 3),
|
||||
EnableWboit: item('plugin-config.enable-wboit', true),
|
||||
// as of Oct 1 2021, WebGL 2 doesn't work on iOS 15.
|
||||
// TODO: check back in a few weeks to see if it was fixed
|
||||
|
||||
@@ -196,9 +196,11 @@ export class PluginContext {
|
||||
const preserveDrawingBuffer = !(this.config.get(PluginConfig.General.DisablePreserveDrawingBuffer) ?? false);
|
||||
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
|
||||
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
|
||||
const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
|
||||
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
|
||||
const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit, preferWebGl1 });
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit });
|
||||
}
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
|
||||
this.canvas3dInit.next(true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -14,23 +14,32 @@ import { Representation, RepresentationParamsGetter, RepresentationContext } fro
|
||||
import { ThemeRegistryContext } from '../../../mol-theme/theme';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { getUnitKindsParam } from '../params';
|
||||
import { ElementPointParams, ElementPointVisual } from '../visual/element-point';
|
||||
import { ElementCrossParams, ElementCrossVisual } from '../visual/element-cross';
|
||||
|
||||
const LineVisuals = {
|
||||
'intra-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitBondLineParams>) => UnitsRepresentation('Intra-unit bond line', ctx, getParams, IntraUnitBondLineVisual),
|
||||
'inter-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitBondLineParams>) => ComplexRepresentation('Inter-unit bond line', ctx, getParams, InterUnitBondLineVisual),
|
||||
'element-point': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementPointParams>) => UnitsRepresentation('Points', ctx, getParams, ElementPointVisual),
|
||||
'element-cross': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementCrossParams>) => UnitsRepresentation('Crosses', ctx, getParams, ElementCrossVisual),
|
||||
};
|
||||
|
||||
export const LineParams = {
|
||||
...IntraUnitBondLineParams,
|
||||
...InterUnitBondLineParams,
|
||||
...ElementPointParams,
|
||||
...ElementCrossParams,
|
||||
multipleBonds: PD.Select('offset', PD.arrayToOptions(['off', 'symmetric', 'offset'] as const)),
|
||||
includeParent: PD.Boolean(false),
|
||||
sizeFactor: PD.Numeric(1.5, { min: 0.01, max: 10, step: 0.01 }),
|
||||
sizeFactor: PD.Numeric(3, { min: 0.01, max: 10, step: 0.01 }),
|
||||
unitKinds: getUnitKindsParam(['atomic']),
|
||||
visuals: PD.MultiSelect(['intra-bond', 'inter-bond'], PD.objectToOptions(LineVisuals))
|
||||
visuals: PD.MultiSelect(['intra-bond', 'inter-bond', 'element-point', 'element-cross'], PD.objectToOptions(LineVisuals))
|
||||
};
|
||||
export type LineParams = typeof LineParams
|
||||
export function getLineParams(ctx: ThemeRegistryContext, structure: Structure) {
|
||||
return PD.clone(LineParams);
|
||||
const params = PD.clone(LineParams);
|
||||
params.pointStyle.defaultValue = 'circle';
|
||||
return params;
|
||||
}
|
||||
|
||||
export type LineRepresentation = StructureRepresentation<LineParams>
|
||||
@@ -41,7 +50,7 @@ export function LineRepresentation(ctx: RepresentationContext, getParams: Repres
|
||||
export const LineRepresentationProvider = StructureRepresentationProvider({
|
||||
name: 'line',
|
||||
label: 'Line',
|
||||
description: 'Displays bonds as lines.',
|
||||
description: 'Displays bonds as lines and atoms as points or croses.',
|
||||
factory: LineRepresentation,
|
||||
getParams: getLineParams,
|
||||
defaultValues: PD.getDefaultValues(LineParams),
|
||||
|
||||
@@ -40,7 +40,10 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
|
||||
|
||||
const bonds = structure.interUnitBonds;
|
||||
const { edgeCount, edges } = bonds;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds } = props;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds, multipleBonds } = props;
|
||||
|
||||
const mbOff = multipleBonds === 'off';
|
||||
const mbSymmetric = multipleBonds === 'symmetric';
|
||||
|
||||
const delta = Vec3();
|
||||
|
||||
@@ -136,14 +139,16 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
return mbOff ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Triple :
|
||||
LinkStyle.OffsetTriple;
|
||||
} else if (aromaticBonds && BondType.is(f, BondType.Flag.Aromatic)) {
|
||||
return LinkStyle.Aromatic;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
}
|
||||
|
||||
return LinkStyle.Solid;
|
||||
return (o !== 2 || mbOff) ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Double :
|
||||
LinkStyle.OffsetDouble;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
return radius(edgeIndex) * sizeAspectRatio;
|
||||
@@ -216,7 +221,8 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
|
||||
newProps.stubCap !== currentProps.stubCap ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
|
||||
newProps.multipleBonds !== currentProps.multipleBonds
|
||||
);
|
||||
|
||||
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
|
||||
@@ -254,7 +260,8 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
|
||||
newProps.stubCap !== currentProps.stubCap ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
|
||||
newProps.multipleBonds !== currentProps.multipleBonds
|
||||
);
|
||||
|
||||
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
|
||||
|
||||
@@ -32,10 +32,14 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde
|
||||
function createInterUnitBondLines(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondLineParams>, lines?: Lines) {
|
||||
const bonds = structure.interUnitBonds;
|
||||
const { edgeCount, edges } = bonds;
|
||||
const { sizeFactor, aromaticBonds } = props;
|
||||
|
||||
if (!edgeCount) return Lines.createEmpty(lines);
|
||||
|
||||
const { sizeFactor, aromaticBonds, multipleBonds } = props;
|
||||
|
||||
const mbOff = multipleBonds === 'off';
|
||||
const mbSymmetric = multipleBonds === 'symmetric';
|
||||
|
||||
const ref = Vec3();
|
||||
const loc = StructureElement.Location.create();
|
||||
|
||||
@@ -74,14 +78,16 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
return mbOff ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Triple :
|
||||
LinkStyle.OffsetTriple;
|
||||
} else if (aromaticBonds && BondType.is(f, BondType.Flag.Aromatic)) {
|
||||
return LinkStyle.Aromatic;
|
||||
} else if (o === 2) {
|
||||
return LinkStyle.Double;
|
||||
}
|
||||
|
||||
return LinkStyle.Solid;
|
||||
return (o !== 2 || mbOff) ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Double :
|
||||
LinkStyle.OffsetDouble;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
const b = edges[edgeIndex];
|
||||
@@ -128,7 +134,8 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.multipleBonds !== currentProps.multipleBonds
|
||||
);
|
||||
|
||||
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
|
||||
|
||||
@@ -33,7 +33,10 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
const bonds = unit.bonds;
|
||||
const { edgeCount, a, b, edgeProps, offset } = bonds;
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds, includeTypes, excludeTypes } = props;
|
||||
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds, includeTypes, excludeTypes, multipleBonds } = props;
|
||||
|
||||
const mbOff = multipleBonds === 'off';
|
||||
const mbSymmetric = multipleBonds === 'symmetric';
|
||||
|
||||
const include = BondType.fromNames(includeTypes);
|
||||
const exclude = BondType.fromNames(excludeTypes);
|
||||
@@ -130,7 +133,9 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
return mbOff ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Triple :
|
||||
LinkStyle.OffsetTriple;
|
||||
} else if (aromaticBonds) {
|
||||
const aI = a[edgeIndex], bI = b[edgeIndex];
|
||||
const aR = elementAromaticRingIndices.get(aI);
|
||||
@@ -146,7 +151,9 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
}
|
||||
}
|
||||
|
||||
return o === 2 ? LinkStyle.Double : LinkStyle.Solid;
|
||||
return (o !== 2 || mbOff) ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Double :
|
||||
LinkStyle.OffsetDouble;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
return radius(edgeIndex) * sizeAspectRatio;
|
||||
@@ -228,7 +235,8 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds ||
|
||||
newProps.multipleBonds !== currentProps.multipleBonds
|
||||
);
|
||||
|
||||
const newUnit = newStructureGroup.group.units[0];
|
||||
@@ -271,7 +279,8 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds ||
|
||||
newProps.multipleBonds !== currentProps.multipleBonds
|
||||
);
|
||||
|
||||
const newUnit = newStructureGroup.group.units[0];
|
||||
|
||||
@@ -39,7 +39,10 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
if (!edgeCount) return Lines.createEmpty(lines);
|
||||
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { sizeFactor, aromaticBonds, includeTypes, excludeTypes } = props;
|
||||
const { sizeFactor, aromaticBonds, includeTypes, excludeTypes, multipleBonds } = props;
|
||||
|
||||
const mbOff = multipleBonds === 'off';
|
||||
const mbSymmetric = multipleBonds === 'symmetric';
|
||||
|
||||
const include = BondType.fromNames(includeTypes);
|
||||
const exclude = BondType.fromNames(excludeTypes);
|
||||
@@ -91,7 +94,9 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
// show metallic coordinations and hydrogen bonds with dashed cylinders
|
||||
return LinkStyle.Dashed;
|
||||
} else if (o === 3) {
|
||||
return LinkStyle.Triple;
|
||||
return mbOff ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Triple :
|
||||
LinkStyle.OffsetTriple;
|
||||
} else if (aromaticBonds) {
|
||||
const aI = a[edgeIndex], bI = b[edgeIndex];
|
||||
const aR = elementAromaticRingIndices.get(aI);
|
||||
@@ -107,7 +112,9 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
}
|
||||
}
|
||||
|
||||
return o === 2 ? LinkStyle.Double : LinkStyle.Solid;
|
||||
return (o !== 2 || mbOff) ? LinkStyle.Solid :
|
||||
mbSymmetric ? LinkStyle.Double :
|
||||
LinkStyle.OffsetDouble;
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
location.element = elements[a[edgeIndex]];
|
||||
@@ -150,7 +157,8 @@ export function IntraUnitBondLineVisual(materialId: number): UnitsVisual<IntraUn
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds
|
||||
newProps.aromaticBonds !== currentProps.aromaticBonds ||
|
||||
newProps.multipleBonds !== currentProps.multipleBonds
|
||||
);
|
||||
|
||||
const newUnit = newStructureGroup.group.units[0];
|
||||
|
||||
94
src/mol-repr/structure/visual/element-cross.ts
Normal file
94
src/mol-repr/structure/visual/element-cross.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { UnitsVisual, UnitsLinesParams, UnitsLinesVisual } from '../units-visual';
|
||||
import { VisualContext } from '../../visual';
|
||||
import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ElementIterator, getElementLoci, eachElement, makeElementIgnoreTest } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Lines } from '../../../mol-geo/geometry/lines/lines';
|
||||
import { LinesBuilder } from '../../../mol-geo/geometry/lines/lines-builder';
|
||||
import { bondCount } from '../../../mol-model-props/computed/chemistry/util';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3scaleAndAdd = Vec3.scaleAndAdd;
|
||||
const v3unitX = Vec3.unitX;
|
||||
const v3unitY = Vec3.unitY;
|
||||
const v3unitZ = Vec3.unitZ;
|
||||
|
||||
export const ElementCrossParams = {
|
||||
...UnitsLinesParams,
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
traceOnly: PD.Boolean(false),
|
||||
crosses: PD.Select('lone', PD.arrayToOptions(['lone', 'all'] as const)),
|
||||
crossSize: PD.Numeric(0.35, { min: 0, max: 2, step: 0.01 }),
|
||||
};
|
||||
export type ElementCrossParams = typeof ElementCrossParams
|
||||
|
||||
export function createElementCross(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ElementCrossParams>, lines: Lines) {
|
||||
const { child } = structure;
|
||||
if (child && !child.unitMap.get(unit.id)) return Lines.createEmpty(lines);
|
||||
|
||||
const elements = unit.elements;
|
||||
const n = elements.length;
|
||||
const builder = LinesBuilder.create(n, n / 10, lines);
|
||||
|
||||
const p = Vec3();
|
||||
const s = Vec3();
|
||||
const e = Vec3();
|
||||
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
const ignore = makeElementIgnoreTest(structure, unit, props);
|
||||
|
||||
const r = props.crossSize / 2;
|
||||
const lone = props.crosses === 'lone';
|
||||
|
||||
for (let i = 0 as StructureElement.UnitIndex; i < n; ++i) {
|
||||
if (ignore && ignore(elements[i])) continue;
|
||||
if (lone && Unit.isAtomic(unit) && bondCount(structure, unit, i) !== 0) continue;
|
||||
|
||||
pos(elements[i], p);
|
||||
v3scaleAndAdd(s, p, v3unitX, r);
|
||||
v3scaleAndAdd(e, p, v3unitX, -r);
|
||||
builder.add(s[0], s[1], s[2], e[0], e[1], e[2], i);
|
||||
v3scaleAndAdd(s, p, v3unitY, r);
|
||||
v3scaleAndAdd(e, p, v3unitY, -r);
|
||||
builder.add(s[0], s[1], s[2], e[0], e[1], e[2], i);
|
||||
v3scaleAndAdd(s, p, v3unitZ, r);
|
||||
v3scaleAndAdd(e, p, v3unitZ, -r);
|
||||
builder.add(s[0], s[1], s[2], e[0], e[1], e[2], i);
|
||||
}
|
||||
|
||||
const l = builder.getLines();
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
|
||||
l.setBoundingSphere(sphere);
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
export function ElementCrossVisual(materialId: number): UnitsVisual<ElementCrossParams> {
|
||||
return UnitsLinesVisual<ElementCrossParams>({
|
||||
defaultProps: PD.getDefaultValues(ElementCrossParams),
|
||||
createGeometry: createElementCross,
|
||||
createLocationIterator: ElementIterator.fromGroup,
|
||||
getLoci: getElementLoci,
|
||||
eachLocation: eachElement,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<ElementCrossParams>, currentProps: PD.Values<ElementCrossParams>) => {
|
||||
state.createGeometry = (
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
newProps.traceOnly !== currentProps.traceOnly ||
|
||||
newProps.crosses !== currentProps.crosses ||
|
||||
newProps.crossSize !== currentProps.crossSize
|
||||
);
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export const BondParams = {
|
||||
excludeTypes: PD.MultiSelect([] as BondType.Names[], PD.objectToOptions(BondType.Names)),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
aromaticBonds: PD.Boolean(false, { description: 'Display aromatic bonds with dashes' }),
|
||||
multipleBonds: PD.Select('symmetric', PD.arrayToOptions(['off', 'symmetric', 'offset'] as const)),
|
||||
};
|
||||
export const DefaultBondProps = PD.getDefaultValues(BondParams);
|
||||
export type BondProps = typeof DefaultBondProps
|
||||
|
||||
@@ -83,10 +83,12 @@ export const enum LinkStyle {
|
||||
Solid = 0,
|
||||
Dashed = 1,
|
||||
Double = 2,
|
||||
Triple = 3,
|
||||
Disk = 4,
|
||||
Aromatic = 5,
|
||||
MirroredAromatic = 6,
|
||||
OffsetDouble = 3,
|
||||
Triple = 4,
|
||||
OffsetTriple = 5,
|
||||
Disk = 6,
|
||||
Aromatic = 7,
|
||||
MirroredAromatic = 8,
|
||||
}
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
@@ -128,14 +130,16 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
|
||||
position(va, vb, edgeIndex);
|
||||
v3sub(tmpV12, vb, va);
|
||||
const dirFlag = v3dot(tmpV12, up) > 0;
|
||||
|
||||
const linkRadius = radius(edgeIndex);
|
||||
const linkStyle = style ? style(edgeIndex) : LinkStyle.Solid;
|
||||
const linkStub = stubCap && (stub ? stub(edgeIndex) : false);
|
||||
const [topCap, bottomCap] = (v3dot(tmpV12, up) > 0) ? [linkStub, linkCap] : [linkCap, linkStub];
|
||||
const [topCap, bottomCap] = dirFlag ? [linkStub, linkCap] : [linkCap, linkStub];
|
||||
builderState.currentGroup = edgeIndex;
|
||||
|
||||
const aromaticOffsetFactor = 4.5;
|
||||
const aromaticOffsetFactor = 5.5;
|
||||
const multipleOffsetFactor = 4;
|
||||
|
||||
if (linkStyle === LinkStyle.Solid) {
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
||||
@@ -148,9 +152,9 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
|
||||
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double ? 2 :
|
||||
linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
|
||||
linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
|
||||
const multiRadius = linkRadius * (linkScale / (0.5 * order));
|
||||
const absOffset = (linkRadius - multiRadius) * linkSpacing;
|
||||
|
||||
@@ -176,6 +180,30 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
v3add(vb, vb, vShift);
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
|
||||
v3scale(tmpV12, tmpV12, linkSpacing * linkScale * 0.2);
|
||||
v3add(va, va, tmpV12);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
|
||||
cylinderProps.topCap = dirFlag ? linkStub : dashCap;
|
||||
cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
|
||||
v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
|
||||
v3sub(va, va, vShift);
|
||||
v3sub(vb, vb, vShift);
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
|
||||
if (order === 3) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor * 2);
|
||||
v3add(va, va, vShift);
|
||||
v3add(vb, vb, vShift);
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
}
|
||||
} else {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
@@ -224,7 +252,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
|
||||
const aromaticSegmentCount = 3;
|
||||
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
||||
const aromaticOffsetFactor = 4.5;
|
||||
const aromaticOffsetFactor = 5.5;
|
||||
const multipleOffsetFactor = 4;
|
||||
|
||||
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
|
||||
if (ignore && ignore(edgeIndex)) continue;
|
||||
@@ -242,9 +271,9 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), lengthScale);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double ? 2 :
|
||||
linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
|
||||
linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
|
||||
const multiScale = linkScale / (0.5 * order);
|
||||
const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
|
||||
|
||||
@@ -268,10 +297,20 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
v3add(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
|
||||
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
|
||||
|
||||
v3scale(tmpV12, v3sub(tmpV12, va, vb), linkSpacing * linkScale * 0.2);
|
||||
v3sub(va, va, tmpV12);
|
||||
|
||||
if (order === 3) builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vm[0] - vShift[0], vm[1] - vShift[1], vm[2] - vShift[2], multiScale, dashCap, linkStub, edgeIndex);
|
||||
} else {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
|
||||
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], multiScale, linkCap, false, edgeIndex);
|
||||
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vm[0] - vShift[0], vm[1] - vShift[1], vm[2] - vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
}
|
||||
@@ -313,6 +352,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
||||
const aromaticSegmentCount = 3;
|
||||
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
||||
const aromaticOffsetFactor = 4.5;
|
||||
const multipleOffsetFactor = 3;
|
||||
|
||||
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
|
||||
if (ignore && ignore(edgeIndex)) continue;
|
||||
@@ -328,9 +368,9 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), lengthScale);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
builder.addFixedCountDashes(va, vb, segmentCount, edgeIndex);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double ? 2 :
|
||||
linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
|
||||
linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
|
||||
const multiRadius = 1 * (linkScale / (0.5 * order));
|
||||
const absOffset = (1 - multiRadius) * linkSpacing;
|
||||
|
||||
@@ -354,8 +394,18 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
||||
v3add(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, edgeIndex);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
|
||||
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
|
||||
|
||||
v3scale(tmpV12, v3sub(tmpV12, va, vb), linkSpacing * linkScale);
|
||||
v3sub(va, va, tmpV12);
|
||||
|
||||
if (order === 3) builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], edgeIndex);
|
||||
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vm[0] - vShift[0], vm[1] - vShift[1], vm[2] - vShift[2], edgeIndex);
|
||||
} else {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
v3setMagnitude(vShift, vShift, absOffset * 1.5);
|
||||
|
||||
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
|
||||
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], edgeIndex);
|
||||
|
||||
@@ -230,7 +230,6 @@ function addJsonConfigArgs(parser: argparse.ArgumentParser) {
|
||||
'If a property is not specified, cmd line param/OS variable/default value are used.'
|
||||
].join('\n'),
|
||||
required: false,
|
||||
action: 'store_true'
|
||||
});
|
||||
parser.add_argument('--printCfg', { help: 'Print current config for validation and exit.', required: false, action: 'store_true' });
|
||||
parser.add_argument('--cfgTemplate', { help: 'Prints default JSON config template to be modified and exits.', required: false, action: 'store_true' });
|
||||
|
||||
@@ -90,7 +90,6 @@ function addJsonConfigArgs(parser: argparse.ArgumentParser) {
|
||||
'If a property is not specified, cmd line param/OS variable/default value are used.'
|
||||
].join('\n'),
|
||||
required: false,
|
||||
action: 'store_true'
|
||||
});
|
||||
parser.add_argument('--printCfg', { help: 'Print current config for validation and exit.', required: false, action: 'store_true' });
|
||||
parser.add_argument('--cfgTemplate', { help: 'Prints default JSON config template to be modified and exits.', required: false, action: 'store_true' });
|
||||
|
||||
Reference in New Issue
Block a user