mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f833efae37 | ||
|
|
be4b787e66 | ||
|
|
950b1c179a | ||
|
|
2bd1a01afb | ||
|
|
9d34dbff0f | ||
|
|
ba1b03f01b | ||
|
|
791f7ca3c8 | ||
|
|
c14d50e4ff | ||
|
|
fc2765d376 | ||
|
|
9d85194082 | ||
|
|
abfcc60898 | ||
|
|
c688a83fa2 | ||
|
|
77376056b9 | ||
|
|
8efd943c2b | ||
|
|
b230655439 | ||
|
|
8ba792c4b0 | ||
|
|
fde8ca69e4 | ||
|
|
9ee1439299 | ||
|
|
195668760e | ||
|
|
bd64f1db9a | ||
|
|
38a5a857aa | ||
|
|
5e8cdfe3a7 | ||
|
|
738b7f4ca5 | ||
|
|
bce53d03a5 | ||
|
|
74f721ab9f | ||
|
|
23cf5c2fdd | ||
|
|
e211abd5ae | ||
|
|
7199be4d62 | ||
|
|
e1a40ded1d | ||
|
|
8999c3097d | ||
|
|
44308fa1fd | ||
|
|
f5dd2f4579 | ||
|
|
f9f8350d28 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -7,17 +7,35 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [v2.2.2] - 2021-08-11
|
||||
|
||||
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
|
||||
- Fix ``mol-script`` query compiler const expression recognition.
|
||||
|
||||
## [v2.2.1] - 2021-08-02
|
||||
|
||||
- Add surrounding atoms (5 Angstrom) structure selection query
|
||||
- [Breaking] Add maxDistance prop to ``IndexPairBonds``
|
||||
- Fix coordinateSystem not handled in ``Structure.asParent``
|
||||
- Add ``dynamicBonds`` to ``Structure`` props (force re-calc on model change)
|
||||
- Expose as optional param in root structure transform helper
|
||||
- Add overpaint support to geometry exporters
|
||||
- ``InputObserver`` improvements
|
||||
- normalize wheel speed across browsers/platforms
|
||||
- support Safari gestures (used by ``TrackballControls``)
|
||||
- ``PinchInput.fractionDelta`` and use it in ``TrackballControls``
|
||||
|
||||
## [v2.2.0] - 2021-07-31
|
||||
|
||||
- Add `tubularHelices` parameter to Cartoon representation
|
||||
- Add `SdfFormat` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
|
||||
- Add ``tubularHelices`` parameter to Cartoon representation
|
||||
- Add ``SdfFormat`` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
|
||||
- Fix mononucleotides detected as polymer components (#229)
|
||||
- Set default outline scale back to 1
|
||||
- Improved DCD reader cell angle handling (interpret near 0 angles as 90 deg)
|
||||
- Handle more residue/atom names commonly used in force-fields
|
||||
- Add USDZ support to ``geo-export`` extension.
|
||||
- Fix `includeParent` support for multi-instance bond visuals.
|
||||
- Add `operator` Loci granularity, selecting everything with the same operator name.
|
||||
- Fix ``includeParent`` support for multi-instance bond visuals.
|
||||
- Add ``operator`` Loci granularity, selecting everything with the same operator name.
|
||||
- Prefer ``_label_seq_id`` fields in secondary structure assignment.
|
||||
- Support new EMDB API (https://www.ebi.ac.uk/emdb/api/entry/map/[EMBD-ID]) for EM volume contour levels.
|
||||
- ``Canvas3D`` tweaks:
|
||||
@@ -57,8 +75,8 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Add ability to select residues from a list of identifiers to the Selection UI.
|
||||
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
|
||||
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
|
||||
- Add `MeshBuilder.addMesh`.
|
||||
- Add `Torus` primitive.
|
||||
- Add ``MeshBuilder.addMesh``.
|
||||
- Add ``Torus`` primitive.
|
||||
- Lazy volume loading support.
|
||||
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
|
||||
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
|
||||
@@ -76,12 +94,12 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
|
||||
|
||||
### Changed
|
||||
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
|
||||
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
|
||||
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
|
||||
|
||||
## [v2.0.2] - 2021-03-29
|
||||
### Added
|
||||
- `Canvas3D.getRenderObjects`.
|
||||
- ``Canvas3D.getRenderObjects``.
|
||||
- [WIP] Animate state interpolating, including model trajectories
|
||||
|
||||
### Changed
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -126,11 +126,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
};
|
||||
}
|
||||
|
||||
private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array) {
|
||||
private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined) {
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const uColor = values.uColor.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value;
|
||||
@@ -138,38 +135,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
const colorArray = new Uint8Array(vertexCount * 4);
|
||||
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(uColor, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, i * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + i) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, i * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + i) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
let color = GlbExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, i);
|
||||
|
||||
let alpha = uAlpha;
|
||||
if (dTransparency) {
|
||||
@@ -201,7 +167,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array;
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
|
||||
@@ -235,7 +201,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
|
||||
// create a color buffer if needed
|
||||
if (instanceIndex === 0 || !sameColorBuffer) {
|
||||
colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors!);
|
||||
colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors);
|
||||
}
|
||||
|
||||
// glTF mesh
|
||||
|
||||
@@ -194,6 +194,57 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
}
|
||||
}
|
||||
|
||||
protected static getColor(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined, vertexIndex: number): Color {
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const uColor = values.uColor.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const dOverpaint = values.dOverpaint.ref.value;
|
||||
const tOverpaint = values.tOverpaint.ref.value.array;
|
||||
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(uColor, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, vertexIndex * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + vertexIndex) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, vertexIndex * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + vertexIndex) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
|
||||
if (dOverpaint) {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
const overpaintColor = Color.fromArray(tOverpaint, (instanceIndex * groupCount + group) * 4);
|
||||
const overpaintAlpha = tOverpaint[(instanceIndex * groupCount + group) * 4 + 3] / 255;
|
||||
color = Color.interpolate(color, overpaintColor, overpaintAlpha);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
protected abstract addMeshWithColors(input: AddMeshInput): void;
|
||||
|
||||
private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
|
||||
@@ -79,14 +79,13 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array;
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
|
||||
ObjExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
|
||||
@@ -129,38 +128,8 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
|
||||
// face
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, indices![i] * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + indices![i]) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, (isGeoTexture ? i : indices![i]) * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + (isGeoTexture ? i : indices![i])) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
const color = ObjExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
|
||||
|
||||
let alpha = uAlpha;
|
||||
if (dTransparency) {
|
||||
|
||||
@@ -70,14 +70,13 @@ def Material "material${materialKey}"
|
||||
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array;
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
interpolatedColors = UsdzExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
|
||||
UsdzExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
|
||||
@@ -132,38 +131,8 @@ def Material "material${materialKey}"
|
||||
// color
|
||||
const faceIndicesByMaterial = new Map<number, number[]>();
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, indices![i] * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + indices![i]) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, (isGeoTexture ? i : indices![i]) * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + (isGeoTexture ? i : indices![i])) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
const color = UsdzExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
|
||||
|
||||
let alpha = uAlpha;
|
||||
if (dTransparency) {
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace Canvas3DContext {
|
||||
});
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context');
|
||||
|
||||
const input = InputObserver.fromElement(canvas, { pixelScale });
|
||||
const input = InputObserver.fromElement(canvas, { pixelScale, preventGestures: true });
|
||||
const webgl = createContext(gl, { pixelScale });
|
||||
const passes = new Passes(webgl, attribs);
|
||||
|
||||
|
||||
@@ -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>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
|
||||
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Camera } from '../camera';
|
||||
import { absMax } from '../../mol-math/misc';
|
||||
@@ -49,6 +49,9 @@ export const TrackballControlsParams = {
|
||||
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
|
||||
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
|
||||
|
||||
gestureScaleFactor: PD.Numeric(1, {}, { isHidden: true }),
|
||||
maxWheelDelta: PD.Numeric(0.02, {}, { isHidden: true }),
|
||||
|
||||
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
|
||||
|
||||
/**
|
||||
@@ -91,6 +94,7 @@ namespace TrackballControls {
|
||||
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd);
|
||||
const wheelSub = input.wheel.subscribe(onWheel);
|
||||
const pinchSub = input.pinch.subscribe(onPinch);
|
||||
const gestureSub = input.gesture.subscribe(onGesture);
|
||||
|
||||
let _isInteracting = false;
|
||||
|
||||
@@ -390,25 +394,33 @@ namespace TrackballControls {
|
||||
_isInteracting = false;
|
||||
}
|
||||
|
||||
function onWheel({ x, y, dx, dy, dz, buttons, modifiers }: WheelInput) {
|
||||
function onWheel({ x, y, spinX, spinY, dz, buttons, modifiers }: WheelInput) {
|
||||
if (outsideViewport(x, y)) return;
|
||||
|
||||
const delta = absMax(dx, dy, dz);
|
||||
let delta = absMax(spinX * 0.075, spinY * 0.075, dz * 0.0001);
|
||||
if (delta < -p.maxWheelDelta) delta = -p.maxWheelDelta;
|
||||
else if (delta > p.maxWheelDelta) delta = p.maxWheelDelta;
|
||||
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_zoomEnd[1] += delta * 0.0001;
|
||||
_zoomEnd[1] += delta;
|
||||
}
|
||||
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
|
||||
_focusEnd[1] += delta * 0.0001;
|
||||
_focusEnd[1] += delta;
|
||||
}
|
||||
}
|
||||
|
||||
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
|
||||
function onPinch({ fractionDelta, buttons, modifiers }: PinchInput) {
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_isInteracting = true;
|
||||
_zoomEnd[1] += (fraction - 1) * 0.1;
|
||||
_zoomEnd[1] += p.gestureScaleFactor * fractionDelta;
|
||||
}
|
||||
}
|
||||
|
||||
function onGesture({ deltaScale }: GestureInput) {
|
||||
_isInteracting = true;
|
||||
_zoomEnd[1] += p.gestureScaleFactor * deltaScale;
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
@@ -416,6 +428,7 @@ namespace TrackballControls {
|
||||
dragSub.unsubscribe();
|
||||
wheelSub.unsubscribe();
|
||||
pinchSub.unsubscribe();
|
||||
gestureSub.unsubscribe();
|
||||
interactionEndSub.unsubscribe();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ const tmpCylinderCenter = Vec3();
|
||||
const tmpCylinderMat = Mat4();
|
||||
const tmpCylinderMatRot = Mat4();
|
||||
const tmpCylinderScale = Vec3();
|
||||
const tmpCylinderMatScale = Mat4();
|
||||
const tmpCylinderStart = Vec3();
|
||||
const tmpUp = Vec3();
|
||||
|
||||
@@ -32,9 +31,9 @@ function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number, matchDi
|
||||
// direction so the triangles of adjacent cylinder will line up
|
||||
if (matchDir) Vec3.matchDirection(tmpUp, up, tmpCylinderMatDir);
|
||||
else Vec3.copy(tmpUp, up);
|
||||
Mat4.fromScaling(tmpCylinderMatScale, Vec3.set(tmpCylinderScale, 1, length, 1));
|
||||
Vec3.set(tmpCylinderScale, 1, length, 1);
|
||||
Vec3.makeRotation(tmpCylinderMatRot, tmpUp, tmpCylinderMatDir);
|
||||
Mat4.mul(m, tmpCylinderMatRot, tmpCylinderMatScale);
|
||||
Mat4.scale(m, tmpCylinderMatRot, tmpCylinderScale);
|
||||
return Mat4.setTranslation(m, tmpCylinderCenter);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -43,12 +43,13 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
|
||||
|
||||
if (transformData) {
|
||||
ValueCell.update(transformData.matrix, transformData.matrix.ref.value);
|
||||
ValueCell.update(transformData.transform, transformArray);
|
||||
const transform = transformData.transform.ref.value.length >= instanceCount * 16 ? transformData.transform.ref.value : new Float32Array(instanceCount * 16);
|
||||
transform.set(transformArray);
|
||||
ValueCell.update(transformData.transform, transform);
|
||||
ValueCell.updateIfChanged(transformData.uInstanceCount, instanceCount);
|
||||
ValueCell.updateIfChanged(transformData.instanceCount, instanceCount);
|
||||
|
||||
const aTransform = transformData.aTransform.ref.value.length >= instanceCount * 16 ? transformData.aTransform.ref.value : new Float32Array(instanceCount * 16);
|
||||
aTransform.set(transformArray);
|
||||
ValueCell.update(transformData.aTransform, aTransform);
|
||||
|
||||
// Note that this sets `extraTransform` to identity transforms
|
||||
@@ -64,9 +65,9 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
|
||||
return transformData;
|
||||
} else {
|
||||
return {
|
||||
aTransform: ValueCell.create(new Float32Array(transformArray)),
|
||||
aTransform: ValueCell.create(new Float32Array(instanceCount * 16)),
|
||||
matrix: ValueCell.create(Mat4.identity()),
|
||||
transform: ValueCell.create(transformArray),
|
||||
transform: ValueCell.create(new Float32Array(transformArray)),
|
||||
extraTransform: ValueCell.create(fillIdentityTransform(new Float32Array(instanceCount * 16), instanceCount)),
|
||||
uInstanceCount: ValueCell.create(instanceCount),
|
||||
instanceCount: ValueCell.create(instanceCount),
|
||||
|
||||
@@ -64,21 +64,19 @@ function createHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Te
|
||||
}
|
||||
|
||||
type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
|
||||
const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
|
||||
function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
|
||||
let textureFramebuffer = LevelTexturesFramebuffers[level];
|
||||
const size = Math.pow(2, level);
|
||||
if (textureFramebuffer === undefined) {
|
||||
const texture = ctx.isWebGL2
|
||||
? getTexture(`level${level}`, ctx, 'image-int32', 'alpha', 'int', 'nearest')
|
||||
: getTexture(`level${level}`, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
texture.define(size, size);
|
||||
const framebuffer = getFramebuffer(`level${level}`, ctx);
|
||||
const name = `level${level}`;
|
||||
const texture = ctx.isWebGL2
|
||||
? getTexture(name, ctx, 'image-int32', 'alpha', 'int', 'nearest')
|
||||
: getTexture(name, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
texture.define(size, size);
|
||||
let framebuffer = tryGetFramebuffer(name, ctx);
|
||||
if (!framebuffer) {
|
||||
framebuffer = getFramebuffer(name, ctx);
|
||||
texture.attachFramebuffer(framebuffer, 0);
|
||||
textureFramebuffer = { texture, framebuffer };
|
||||
LevelTexturesFramebuffers[level] = textureFramebuffer;
|
||||
}
|
||||
return textureFramebuffer;
|
||||
return { texture, framebuffer };
|
||||
}
|
||||
|
||||
function setRenderingDefaults(ctx: WebGLContext) {
|
||||
@@ -108,6 +106,11 @@ function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format
|
||||
return webgl.namedTextures[_name];
|
||||
}
|
||||
|
||||
function tryGetFramebuffer(name: string, webgl: WebGLContext): Framebuffer | undefined {
|
||||
const _name = `${HistogramPyramidName}-${name}`;
|
||||
return webgl.namedFramebuffers[_name];
|
||||
}
|
||||
|
||||
export interface HistogramPyramid {
|
||||
pyramidTex: Texture
|
||||
count: number
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -10,15 +10,17 @@ import { Column } from '../../../../mol-data/db';
|
||||
import { FormatPropertyProvider } from '../../common/property';
|
||||
import { BondType } from '../../../../mol-model/structure/model/types';
|
||||
import { ElementIndex } from '../../../../mol-model/structure';
|
||||
import { DefaultBondMaxRadius } from '../../../../mol-model/structure/structure/unit/bonds/common';
|
||||
|
||||
export type IndexPairBondsProps = {
|
||||
export type IndexPairsProps = {
|
||||
readonly order: ArrayLike<number>
|
||||
readonly distance: ArrayLike<number>
|
||||
readonly flag: ArrayLike<BondType.Flag>
|
||||
}
|
||||
export type IndexPairBonds = IntAdjacencyGraph<ElementIndex, IndexPairBondsProps>
|
||||
export type IndexPairs = IntAdjacencyGraph<ElementIndex, IndexPairsProps>
|
||||
export type IndexPairBonds = { bonds: IndexPairs, maxDistance: number }
|
||||
|
||||
function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairBondsProps>, count: number): IndexPairBonds {
|
||||
function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairsProps>, count: number): IndexPairs {
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(count, indexA, indexB);
|
||||
const order = new Int8Array(builder.slotCount);
|
||||
const distance = new Array(builder.slotCount);
|
||||
@@ -51,13 +53,20 @@ export namespace IndexPairBonds {
|
||||
count: number
|
||||
}
|
||||
|
||||
export function fromData(data: Data) {
|
||||
export const DefaultProps = { maxDistance: DefaultBondMaxRadius };
|
||||
export type Props = typeof DefaultProps
|
||||
|
||||
export function fromData(data: Data, props: Partial<Props> = {}): IndexPairBonds {
|
||||
const p = { ...DefaultProps, ...props };
|
||||
const { pairs, count } = data;
|
||||
const indexA = pairs.indexA.toArray() as ArrayLike<ElementIndex>;
|
||||
const indexB = pairs.indexB.toArray() as ArrayLike<ElementIndex>;
|
||||
const order = pairs.order && pairs.order.toArray();
|
||||
const distance = pairs.distance && pairs.distance.toArray();
|
||||
const flag = pairs.flag && pairs.flag.toArray();
|
||||
return getGraph(indexA, indexB, { order, distance, flag }, count);
|
||||
return {
|
||||
bonds: getGraph(indexA, indexB, { order, distance, flag }, count),
|
||||
maxDistance: p.maxDistance
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ type State = {
|
||||
boundary?: Boundary,
|
||||
lookup3d?: StructureLookup3D,
|
||||
interUnitBonds?: InterUnitBonds,
|
||||
dynamicBonds: boolean,
|
||||
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
|
||||
unitSymmetryGroupsIndexMap?: IntMap<number>,
|
||||
unitsSortedByVolume?: ReadonlyArray<Unit>;
|
||||
@@ -193,8 +194,8 @@ class Structure {
|
||||
}
|
||||
|
||||
/** The parent or itself in case this is the root */
|
||||
get root() {
|
||||
return this.state.parent || this;
|
||||
get root(): Structure {
|
||||
return this.state.parent ?? this;
|
||||
}
|
||||
|
||||
/** The root/top-most parent or `undefined` in case this is the root */
|
||||
@@ -231,10 +232,14 @@ class Structure {
|
||||
|
||||
get interUnitBonds() {
|
||||
if (this.state.interUnitBonds) return this.state.interUnitBonds;
|
||||
this.state.interUnitBonds = computeInterUnitBonds(this);
|
||||
this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds });
|
||||
return this.state.interUnitBonds;
|
||||
}
|
||||
|
||||
get dynamicBonds() {
|
||||
return this.state.dynamicBonds;
|
||||
}
|
||||
|
||||
get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
|
||||
if (this.state.unitSymmetryGroups) return this.state.unitSymmetryGroups;
|
||||
this.state.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
|
||||
@@ -351,18 +356,20 @@ class Structure {
|
||||
}
|
||||
|
||||
remapModel(m: Model) {
|
||||
const { dynamicBonds, interUnitBonds } = this.state;
|
||||
const units: Unit[] = [];
|
||||
for (const ug of this.unitSymmetryGroups) {
|
||||
const unit = ug.units[0].remapModel(m);
|
||||
const unit = ug.units[0].remapModel(m, dynamicBonds);
|
||||
units.push(unit);
|
||||
for (let i = 1, il = ug.units.length; i < il; ++i) {
|
||||
const u = ug.units[i];
|
||||
units.push(u.remapModel(m, unit.props));
|
||||
units.push(u.remapModel(m, dynamicBonds, unit.props));
|
||||
}
|
||||
}
|
||||
return Structure.create(units, {
|
||||
label: this.label,
|
||||
interUnitBonds: this.state.interUnitBonds,
|
||||
interUnitBonds: dynamicBonds ? undefined : interUnitBonds,
|
||||
dynamicBonds
|
||||
});
|
||||
}
|
||||
|
||||
@@ -376,7 +383,13 @@ class Structure {
|
||||
*/
|
||||
asParent(): Structure {
|
||||
if (this._proxy) return this._proxy;
|
||||
this._proxy = this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
|
||||
if (this.parent) {
|
||||
const p = this.parent.coordinateSystem.isIdentity ? this.parent : Structure.transform(this.parent, this.parent.coordinateSystem.inverse);
|
||||
const s = this.coordinateSystem.isIdentity ? p : Structure.transform(p, this.coordinateSystem.matrix);
|
||||
this._proxy = new Structure(s.units, s.unitMap, s.unitIndexMap, { ...s.state, dynamicBonds: this.dynamicBonds }, { child: this, target: this.parent });
|
||||
} else {
|
||||
this._proxy = this;
|
||||
}
|
||||
return this._proxy;
|
||||
}
|
||||
|
||||
@@ -398,6 +411,7 @@ class Structure {
|
||||
// always assign to ensure object shape
|
||||
this._child = asParent?.child;
|
||||
this._target = asParent?.target;
|
||||
this._proxy = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,6 +618,11 @@ namespace Structure {
|
||||
export interface Props {
|
||||
parent?: Structure
|
||||
interUnitBonds?: InterUnitBonds
|
||||
/**
|
||||
* Ensure bonds are recalculated upon model changes.
|
||||
* Also enables calculation of inter-unit bonds in water molecules.
|
||||
*/
|
||||
dynamicBonds?: boolean,
|
||||
coordinateSystem?: SymmetryOperator
|
||||
label?: string
|
||||
/** Master model for structures of a protein model and multiple ligand models */
|
||||
@@ -683,6 +702,7 @@ namespace Structure {
|
||||
polymerResidueCount: -1,
|
||||
polymerGapCount: -1,
|
||||
polymerUnitCount: -1,
|
||||
dynamicBonds: false,
|
||||
coordinateSystem: SymmetryOperator.Default,
|
||||
label: ''
|
||||
};
|
||||
@@ -691,6 +711,9 @@ namespace Structure {
|
||||
if (props.parent) state.parent = props.parent.parent || props.parent;
|
||||
if (props.interUnitBonds) state.interUnitBonds = props.interUnitBonds;
|
||||
|
||||
if (props.dynamicBonds) state.dynamicBonds = props.dynamicBonds;
|
||||
else if (props.parent) state.dynamicBonds = props.parent.dynamicBonds;
|
||||
|
||||
if (props.coordinateSystem) state.coordinateSystem = props.coordinateSystem;
|
||||
else if (props.parent) state.coordinateSystem = props.parent.coordinateSystem;
|
||||
|
||||
@@ -738,12 +761,12 @@ namespace Structure {
|
||||
* Generally, a single unit corresponds to a single chain, with the exception
|
||||
* of consecutive "single atom chains" with same entity_id and same auth_asym_id.
|
||||
*/
|
||||
export function ofModel(model: Model): Structure {
|
||||
export function ofModel(model: Model, props: Props = {}): Structure {
|
||||
const chains = model.atomicHierarchy.chainAtomSegments;
|
||||
const { index } = model.atomicHierarchy;
|
||||
const { auth_asym_id } = model.atomicHierarchy.chains;
|
||||
const { atomicChainOperatorMappinng } = model;
|
||||
const builder = new StructureBuilder({ label: model.label });
|
||||
const builder = new StructureBuilder({ label: model.label, ...props });
|
||||
|
||||
for (let c = 0 as ChainIndex; c < chains.count; c++) {
|
||||
const operator = atomicChainOperatorMappinng.get(c) || SymmetryOperator.Default;
|
||||
|
||||
@@ -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>
|
||||
@@ -27,7 +27,11 @@ namespace StructureSymmetry {
|
||||
if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
|
||||
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
|
||||
const assembler = Structure.Builder({ coordinateSystem, label: structure.label });
|
||||
const assembler = Structure.Builder({
|
||||
coordinateSystem,
|
||||
label: structure.label,
|
||||
dynamicBonds: structure.dynamicBonds
|
||||
});
|
||||
|
||||
const queryCtx = new QueryContext(structure);
|
||||
|
||||
@@ -57,7 +61,11 @@ namespace StructureSymmetry {
|
||||
if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.');
|
||||
|
||||
const modelCenter = Vec3();
|
||||
const assembler = Structure.Builder({ label: structure.label, representativeModel: models[0] });
|
||||
const assembler = Structure.Builder({
|
||||
label: structure.label,
|
||||
representativeModel: models[0],
|
||||
dynamicBonds: structure.dynamicBonds
|
||||
});
|
||||
|
||||
const queryCtx = new QueryContext(structure);
|
||||
|
||||
@@ -205,7 +213,10 @@ function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) {
|
||||
}
|
||||
|
||||
function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) {
|
||||
const assembler = Structure.Builder({ label: structure.label });
|
||||
const assembler = Structure.Builder({
|
||||
label: structure.label,
|
||||
dynamicBonds: structure.dynamicBonds
|
||||
});
|
||||
const { units } = structure;
|
||||
for (const oper of operators) {
|
||||
for (const unit of units) {
|
||||
@@ -263,7 +274,10 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
|
||||
return `${unit.invariantId}|${oper.name}`;
|
||||
}
|
||||
|
||||
const assembler = Structure.Builder({ label: structure.label });
|
||||
const assembler = Structure.Builder({
|
||||
label: structure.label,
|
||||
dynamicBonds: structure.dynamicBonds
|
||||
});
|
||||
|
||||
const { units } = structure;
|
||||
const center = Vec3.zero();
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Unit {
|
||||
|
||||
getChild(elements: StructureElement.Set): Unit,
|
||||
applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
|
||||
remapModel(model: Model): Unit,
|
||||
remapModel(model: Model, dynamicBonds: boolean): Unit,
|
||||
|
||||
readonly boundary: Boundary
|
||||
readonly lookup3d: Lookup3D<StructureElement.UnitIndex>
|
||||
@@ -218,9 +218,9 @@ namespace Unit {
|
||||
return new Atomic(id, this.invariantId, this.chainGroupId, this.traits, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation, this.conformation.r), this.props);
|
||||
}
|
||||
|
||||
remapModel(model: Model, props?: AtomicProperties) {
|
||||
remapModel(model: Model, dynamicBonds: boolean, props?: AtomicProperties) {
|
||||
if (!props) {
|
||||
props = { ...this.props, bonds: tryRemapBonds(this, this.props.bonds, model) };
|
||||
props = { ...this.props, bonds: dynamicBonds ? undefined : tryRemapBonds(this, this.props.bonds, model) };
|
||||
if (!Unit.isSameConformation(this, model)) {
|
||||
props.boundary = undefined;
|
||||
props.lookup3d = undefined;
|
||||
@@ -378,7 +378,7 @@ namespace Unit {
|
||||
return createCoarse(id, this.invariantId, this.chainGroupId, this.traits, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseConformation(), this.conformation.r), this.props);
|
||||
}
|
||||
|
||||
remapModel(model: Model, props?: CoarseProperties): Unit.Spheres | Unit.Gaussians {
|
||||
remapModel(model: Model, dynamicBonds: boolean, props?: CoarseProperties): Unit.Spheres | Unit.Gaussians {
|
||||
const coarseConformation = this.getCoarseConformation();
|
||||
const modelCoarseConformation = getCoarseConformation(this.kind, model);
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
import { ElementSymbol } from '../../../model/types';
|
||||
|
||||
/** Default for atomic bonds */
|
||||
export const DefaultBondMaxRadius = 4;
|
||||
|
||||
export interface BondComputationProps {
|
||||
forceCompute: boolean
|
||||
noCompute: boolean
|
||||
maxRadius: number
|
||||
}
|
||||
export const DefaultBondComputationProps: BondComputationProps = {
|
||||
forceCompute: false,
|
||||
noCompute: false
|
||||
noCompute: false,
|
||||
maxRadius: DefaultBondMaxRadius,
|
||||
};
|
||||
|
||||
// H,D,T are all mapped to H
|
||||
|
||||
@@ -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>
|
||||
@@ -21,8 +21,6 @@ import { StructConn } from '../../../../../mol-model-formats/structure/property/
|
||||
import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common';
|
||||
import { Model } from '../../../model';
|
||||
|
||||
const MAX_RADIUS = 4;
|
||||
|
||||
const tmpDistVecA = Vec3();
|
||||
const tmpDistVecB = Vec3();
|
||||
function getDistance(unitA: Unit.Atomic, indexA: ElementIndex, unitB: Unit.Atomic, indexB: ElementIndex) {
|
||||
@@ -35,6 +33,8 @@ const _imageTransform = Mat4();
|
||||
const _imageA = Vec3();
|
||||
|
||||
function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComputationProps, builder: InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>) {
|
||||
const { maxRadius } = props;
|
||||
|
||||
const { elements: atomsA, residueIndex: residueIndexA } = unitA;
|
||||
const { x: xA, y: yA, z: zA } = unitA.model.atomicConformation;
|
||||
const { elements: atomsB, residueIndex: residueIndexB } = unitB;
|
||||
@@ -62,7 +62,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
const isNotIdentity = !Mat4.isIdentity(imageTransform);
|
||||
|
||||
const { center: bCenter, radius: bRadius } = unitB.boundary.sphere;
|
||||
const testDistanceSq = (bRadius + MAX_RADIUS) * (bRadius + MAX_RADIUS);
|
||||
const testDistanceSq = (bRadius + maxRadius) * (bRadius + maxRadius);
|
||||
|
||||
builder.startUnitPair(unitA.id, unitB.id);
|
||||
|
||||
@@ -73,19 +73,20 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
if (Vec3.squaredDistance(_imageA, bCenter) > testDistanceSq) continue;
|
||||
|
||||
if (!props.forceCompute && indexPairs) {
|
||||
const { order, distance, flag } = indexPairs.edgeProps;
|
||||
const { maxDistance } = indexPairs;
|
||||
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
|
||||
|
||||
const srcA = sourceIndex.value(aI);
|
||||
for (let i = indexPairs.offset[srcA], il = indexPairs.offset[srcA + 1]; i < il; ++i) {
|
||||
const bI = invertedIndex![indexPairs.b[i]];
|
||||
for (let i = offset[srcA], il = offset[srcA + 1]; i < il; ++i) {
|
||||
const bI = invertedIndex![b[i]];
|
||||
|
||||
const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
|
||||
if (_bI < 0) continue;
|
||||
if (type_symbolA.value(aI) === 'H' && type_symbolB.value(bI) === 'H') continue;
|
||||
|
||||
const d = distance[i];
|
||||
// only allow inter-unit index-pair bonds when a distance is given
|
||||
if (d !== -1 && equalEps(getDistance(unitA, aI, unitB, bI), d, 0.5)) {
|
||||
const dist = getDistance(unitA, aI, unitB, bI);
|
||||
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
|
||||
builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
|
||||
}
|
||||
}
|
||||
@@ -102,7 +103,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
if (_bI < 0) continue;
|
||||
|
||||
// check if the bond is within MAX_RADIUS for this pair of units
|
||||
if (getDistance(unitA, aI, unitB, p.atomIndex) > MAX_RADIUS) continue;
|
||||
if (getDistance(unitA, aI, unitB, p.atomIndex) > maxRadius) continue;
|
||||
|
||||
builder.add(_aI, _bI, { order: se.order, flag: se.flags });
|
||||
added = true;
|
||||
@@ -116,7 +117,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
const occA = occupancyA.value(aI);
|
||||
|
||||
const { lookup3d } = unitB;
|
||||
const { indices, count, squaredDistances } = lookup3d.find(_imageA[0], _imageA[1], _imageA[2], MAX_RADIUS);
|
||||
const { indices, count, squaredDistances } = lookup3d.find(_imageA[0], _imageA[1], _imageA[2], maxRadius);
|
||||
if (count === 0) continue;
|
||||
|
||||
const aeI = getElementIdx(type_symbolA.value(aI));
|
||||
@@ -177,35 +178,29 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
|
||||
export interface InterBondComputationProps extends BondComputationProps {
|
||||
validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean
|
||||
ignoreWater: boolean
|
||||
}
|
||||
|
||||
const DefaultInterBondComputationProps = {
|
||||
...DefaultBondComputationProps,
|
||||
ignoreWater: true
|
||||
};
|
||||
|
||||
function findBonds(structure: Structure, props: InterBondComputationProps) {
|
||||
const builder = new InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>();
|
||||
const hasIndexPairBonds = structure.models.some(m => IndexPairBonds.Provider.get(m));
|
||||
|
||||
if (props.noCompute || structure.isCoarseGrained) {
|
||||
if (props.noCompute || (structure.isCoarseGrained && !hasIndexPairBonds)) {
|
||||
// TODO add function that only adds bonds defined in structConn and avoids using
|
||||
// structure.lookup and unit.lookup (expensive for large structure and not
|
||||
// needed for archival files or files with an MD topology)
|
||||
return new InterUnitBonds(builder.getMap());
|
||||
}
|
||||
|
||||
const indexPairs = structure.models.length === 1 && IndexPairBonds.Provider.get(structure.model);
|
||||
if (indexPairs) {
|
||||
const { distance } = indexPairs.edgeProps;
|
||||
let hasDistance = false;
|
||||
for (let i = 0, il = distance.length; i < il; ++i) {
|
||||
if (distance[i] !== -1) {
|
||||
hasDistance = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasDistance) return new InterUnitBonds(builder.getMap());
|
||||
}
|
||||
|
||||
Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
|
||||
findPairBonds(unitA as Unit.Atomic, unitB as Unit.Atomic, props, builder);
|
||||
}, {
|
||||
maxRadius: MAX_RADIUS,
|
||||
maxRadius: props.maxRadius,
|
||||
validUnit: (unit: Unit) => Unit.isAtomic(unit),
|
||||
validUnitPair: (unitA: Unit, unitB: Unit) => props.validUnitPair(structure, unitA, unitB)
|
||||
});
|
||||
@@ -214,8 +209,9 @@ function findBonds(structure: Structure, props: InterBondComputationProps) {
|
||||
}
|
||||
|
||||
function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondComputationProps>): InterUnitBonds {
|
||||
const p = { ...DefaultInterBondComputationProps, ...props };
|
||||
return findBonds(structure, {
|
||||
...DefaultBondComputationProps,
|
||||
...p,
|
||||
validUnitPair: (props && props.validUnitPair) || ((s, a, b) => {
|
||||
const mtA = a.model.atomicHierarchy.derived.residue.moleculeType;
|
||||
const mtB = b.model.atomicHierarchy.derived.residue.moleculeType;
|
||||
@@ -223,7 +219,7 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
|
||||
(!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Water) &&
|
||||
(!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Water)
|
||||
);
|
||||
return Structure.validUnitPair(s, a, b) && notWater;
|
||||
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -49,7 +49,8 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
const { elements: atoms } = unit;
|
||||
const { type_symbol } = unit.model.atomicHierarchy.atoms;
|
||||
const atomCount = unit.elements.length;
|
||||
const { edgeProps } = indexPairs;
|
||||
const { maxDistance } = indexPairs;
|
||||
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
|
||||
|
||||
const { atomSourceIndex: sourceIndex } = unit.model.atomicHierarchy;
|
||||
const { invertedIndex } = Model.getInvertedAtomSourceIndex(unit.model);
|
||||
@@ -57,7 +58,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
const atomA: StructureElement.UnitIndex[] = [];
|
||||
const atomB: StructureElement.UnitIndex[] = [];
|
||||
const flags: number[] = [];
|
||||
const order: number[] = [];
|
||||
const orders: number[] = [];
|
||||
|
||||
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
|
||||
const aI = atoms[_aI];
|
||||
@@ -65,29 +66,30 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
|
||||
const srcA = sourceIndex.value(aI);
|
||||
|
||||
for (let i = indexPairs.offset[srcA], il = indexPairs.offset[srcA + 1]; i < il; ++i) {
|
||||
const bI = invertedIndex[indexPairs.b[i]];
|
||||
for (let i = offset[srcA], il = offset[srcA + 1]; i < il; ++i) {
|
||||
const bI = invertedIndex[b[i]];
|
||||
if (aI >= bI) continue;
|
||||
|
||||
const _bI = SortedArray.indexOf(unit.elements, bI) as StructureElement.UnitIndex;
|
||||
if (_bI < 0) continue;
|
||||
if (isHa && type_symbol.value(bI) === 'H') continue;
|
||||
|
||||
const d = edgeProps.distance[i];
|
||||
if (d === -1 || d === void 0 || equalEps(getDistance(unit, aI, bI), d, 0.5)) {
|
||||
const d = distance[i];
|
||||
const dist = getDistance(unit, aI, bI);
|
||||
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
|
||||
atomA[atomA.length] = _aI;
|
||||
atomB[atomB.length] = _bI;
|
||||
order[order.length] = edgeProps.order[i];
|
||||
flags[flags.length] = edgeProps.flag[i];
|
||||
orders[order.length] = order[i];
|
||||
flags[flags.length] = flag[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getGraph(atomA, atomB, order, flags, atomCount, false);
|
||||
return getGraph(atomA, atomB, orders, flags, atomCount, false);
|
||||
}
|
||||
|
||||
function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds {
|
||||
const MAX_RADIUS = 4;
|
||||
const { maxRadius } = props;
|
||||
|
||||
const { x, y, z } = unit.model.atomicConformation;
|
||||
const atomCount = unit.elements.length;
|
||||
@@ -168,7 +170,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
const atomIdA = label_atom_id.value(aI);
|
||||
const componentPairs = componentMap ? componentMap.get(atomIdA) : void 0;
|
||||
|
||||
const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], MAX_RADIUS);
|
||||
const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], maxRadius);
|
||||
const isHa = isHydrogen(aeI);
|
||||
const thresholdA = getElementThreshold(aeI);
|
||||
const altA = label_alt_id.value(aI);
|
||||
@@ -245,7 +247,7 @@ function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputatio
|
||||
return IntraUnitBonds.Empty;
|
||||
}
|
||||
|
||||
if (!p.forceCompute && IndexPairBonds.Provider.get(unit.model)!) {
|
||||
if (!p.forceCompute && IndexPairBonds.Provider.get(unit.model)) {
|
||||
return findIndexPairBonds(unit);
|
||||
} else {
|
||||
return findBonds(unit, p);
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Sphere3D, SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { StructureUnitTransforms } from '../../mol-model/structure/structure/util/unit-transforms';
|
||||
|
||||
const _unwindMatrix = Mat4.zero();
|
||||
const _unwindMatrix = Mat4();
|
||||
export function unwindStructureAssembly(structure: Structure, unitTransforms: StructureUnitTransforms, t: number) {
|
||||
for (let i = 0, _i = structure.units.length; i < _i; i++) {
|
||||
const u = structure.units[i];
|
||||
@@ -20,15 +20,14 @@ export function unwindStructureAssembly(structure: Structure, unitTransforms: St
|
||||
}
|
||||
}
|
||||
|
||||
const _centerVec = Vec3.zero(), _transVec = Vec3.zero(), _transMat = Mat4.zero();
|
||||
export function explodeStructure(structure: Structure, unitTransforms: StructureUnitTransforms, t: number) {
|
||||
const boundary = structure.boundary.sphere;
|
||||
const d = boundary.radius * t;
|
||||
const _centerVec = Vec3(), _transVec = Vec3(), _transMat = Mat4();
|
||||
export function explodeStructure(structure: Structure, unitTransforms: StructureUnitTransforms, t: number, sphere: Sphere3D) {
|
||||
const d = sphere.radius * t;
|
||||
|
||||
for (let i = 0, _i = structure.units.length; i < _i; i++) {
|
||||
const u = structure.units[i];
|
||||
Vec3.transformMat4(_centerVec, u.lookup3d.boundary.sphere.center, u.conformation.operator.matrix);
|
||||
Vec3.sub(_transVec, _centerVec, boundary.center);
|
||||
Vec3.sub(_transVec, _centerVec, sphere.center);
|
||||
Vec3.setMagnitude(_transVec, _transVec, d);
|
||||
Mat4.fromTranslation(_transMat, _transVec);
|
||||
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -16,6 +16,11 @@ import { Assembly, Symmetry } from '../../mol-model/structure/model/properties/s
|
||||
import { PluginStateObject as SO } from '../objects';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
|
||||
const CommonStructureParams = {
|
||||
dynamicBonds: PD.Optional(PD.Boolean(false, { description: 'Ensure bonds are recalculated upon model changes. Also enables calculation of inter-unit bonds in water molecules.' })),
|
||||
};
|
||||
type CommonStructureProps = PD.ValuesFor<typeof CommonStructureParams>
|
||||
|
||||
export namespace RootStructureDefinition {
|
||||
export function getParams(model?: Model, defaultValue?: 'auto' | 'model' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
|
||||
const symmetry = model && ModelSymmetry.Provider.get(model);
|
||||
@@ -40,19 +45,22 @@ export namespace RootStructureDefinition {
|
||||
}
|
||||
|
||||
const modes = {
|
||||
auto: PD.EmptyGroup(),
|
||||
model: PD.EmptyGroup(),
|
||||
auto: PD.Group(CommonStructureParams),
|
||||
model: PD.Group(CommonStructureParams),
|
||||
assembly: PD.Group({
|
||||
id: PD.Optional(model
|
||||
? PD.Select(assemblyIds.length ? assemblyIds[0][0] : '', assemblyIds, { label: 'Asm Id', description: 'Assembly Id' })
|
||||
: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }))
|
||||
: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' })),
|
||||
...CommonStructureParams
|
||||
}, { isFlat: true }),
|
||||
'symmetry-mates': PD.Group({
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 1 })
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 1 }),
|
||||
...CommonStructureParams
|
||||
}, { isFlat: true }),
|
||||
'symmetry': PD.Group({
|
||||
ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { step: 1 }, { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
|
||||
ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { step: 1 }, { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
|
||||
ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { step: 1 }, { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
|
||||
...CommonStructureParams
|
||||
}, { isFlat: true }),
|
||||
'symmetry-assembly': PD.Group({
|
||||
generators: PD.ObjectList({
|
||||
@@ -65,7 +73,8 @@ export namespace RootStructureDefinition {
|
||||
asymIds: PD.MultiSelect([] as string[], asymIdsOptions)
|
||||
}, e => `${e.asymIds.length} asym ids, ${e.operators.length} operators`, {
|
||||
defaultValue: [] as { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[]
|
||||
})
|
||||
}),
|
||||
...CommonStructureParams
|
||||
}, { isFlat: true })
|
||||
};
|
||||
|
||||
@@ -99,7 +108,7 @@ export namespace RootStructureDefinition {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
|
||||
async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string, props?: CommonStructureProps) {
|
||||
let asm: Assembly | undefined = void 0;
|
||||
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
@@ -118,7 +127,7 @@ export namespace RootStructureDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
const base = Structure.ofModel(model);
|
||||
const base = Structure.ofModel(model, props);
|
||||
if (!asm) {
|
||||
const label = { label: 'Model', description: Structure.elementDescription(base) };
|
||||
return new SO.Molecule.Structure(base, label);
|
||||
@@ -126,56 +135,57 @@ export namespace RootStructureDefinition {
|
||||
|
||||
id = asm.id;
|
||||
const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
|
||||
const props = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, objProps);
|
||||
}
|
||||
|
||||
async function buildSymmetry(ctx: RuntimeContext, model: Model, ijkMin: Vec3, ijkMax: Vec3) {
|
||||
const base = Structure.ofModel(model);
|
||||
async function buildSymmetry(ctx: RuntimeContext, model: Model, ijkMin: Vec3, ijkMax: Vec3, props?: CommonStructureProps) {
|
||||
const base = Structure.ofModel(model, props);
|
||||
const s = await StructureSymmetry.buildSymmetryRange(base, ijkMin, ijkMax).runInContext(ctx);
|
||||
const props = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
const objProps = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, objProps);
|
||||
}
|
||||
|
||||
async function buildSymmetryMates(ctx: RuntimeContext, model: Model, radius: number) {
|
||||
const base = Structure.ofModel(model);
|
||||
async function buildSymmetryMates(ctx: RuntimeContext, model: Model, radius: number, props?: CommonStructureProps) {
|
||||
const base = Structure.ofModel(model, props);
|
||||
const s = await StructureSymmetry.builderSymmetryMates(base, radius).runInContext(ctx);
|
||||
const props = { label: `Symmetry Mates`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
const objProps = { label: `Symmetry Mates`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, objProps);
|
||||
}
|
||||
|
||||
async function buildSymmetryAssembly(ctx: RuntimeContext, model: Model, generators: StructureSymmetry.Generators, symmetry: Symmetry) {
|
||||
const base = Structure.ofModel(model);
|
||||
async function buildSymmetryAssembly(ctx: RuntimeContext, model: Model, generators: StructureSymmetry.Generators, symmetry: Symmetry, props?: CommonStructureProps) {
|
||||
const base = Structure.ofModel(model, props);
|
||||
const s = await StructureSymmetry.buildSymmetryAssembly(base, generators, symmetry).runInContext(ctx);
|
||||
const props = { label: `Symmetry Assembly`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
const objProps = { label: `Symmetry Assembly`, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, objProps);
|
||||
}
|
||||
|
||||
export async function create(plugin: PluginContext, ctx: RuntimeContext, model: Model, params?: Params): Promise<SO.Molecule.Structure> {
|
||||
const props = params?.params;
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
if (!symmetry || !params || params.name === 'model') {
|
||||
const s = Structure.ofModel(model);
|
||||
const s = Structure.ofModel(model, props);
|
||||
return new SO.Molecule.Structure(s, { label: 'Model', description: Structure.elementDescription(s) });
|
||||
}
|
||||
if (params.name === 'auto') {
|
||||
if (symmetry.assemblies.length === 0) {
|
||||
const s = Structure.ofModel(model);
|
||||
const s = Structure.ofModel(model, props);
|
||||
return new SO.Molecule.Structure(s, { label: 'Model', description: Structure.elementDescription(s) });
|
||||
} else {
|
||||
return buildAssembly(plugin, ctx, model);
|
||||
return buildAssembly(plugin, ctx, model, undefined, props);
|
||||
}
|
||||
}
|
||||
if (params.name === 'assembly') {
|
||||
return buildAssembly(plugin, ctx, model, params.params.id);
|
||||
return buildAssembly(plugin, ctx, model, params.params.id, props);
|
||||
}
|
||||
if (params.name === 'symmetry') {
|
||||
return buildSymmetry(ctx, model, params.params.ijkMin, params.params.ijkMax);
|
||||
return buildSymmetry(ctx, model, params.params.ijkMin, params.params.ijkMax, props);
|
||||
}
|
||||
if (params.name === 'symmetry-mates') {
|
||||
return buildSymmetryMates(ctx, model, params.params.radius);
|
||||
return buildSymmetryMates(ctx, model, params.params.radius, props);
|
||||
}
|
||||
if (params.name === 'symmetry-assembly') {
|
||||
return buildSymmetryAssembly(ctx, model, params.params.generators, symmetry);
|
||||
return buildSymmetryAssembly(ctx, model, params.params.generators, symmetry, props);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown represetation type: ${(params as any).name}`);
|
||||
|
||||
@@ -472,6 +472,21 @@ const surroundingLigands = StructureSelectionQuery('Surrounding Ligands (5 \u212
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const surroundingAtoms = StructureSelectionQuery('Surrounding Atoms (5 \u212B) of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.modifier.includeSurroundings({
|
||||
0: MS.internal.generator.current(),
|
||||
radius: 5,
|
||||
'as-whole-residues': false
|
||||
}),
|
||||
by: MS.internal.generator.current()
|
||||
})
|
||||
]), {
|
||||
description: 'Select atoms within 5 \u212B of the current selection.',
|
||||
category: StructureSelectionCategory.Manipulate,
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.generator.all(),
|
||||
@@ -694,6 +709,7 @@ export const StructureSelectionQueries = {
|
||||
aromaticRing,
|
||||
surroundings,
|
||||
surroundingLigands,
|
||||
surroundingAtoms,
|
||||
complement,
|
||||
covalentlyBonded,
|
||||
covalentlyOrMetallicBonded,
|
||||
|
||||
@@ -236,23 +236,23 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure.root);
|
||||
explodeStructure(structure, unitTransforms, params.t);
|
||||
const unitTransforms = new StructureUnitTransforms(structure);
|
||||
explodeStructure(structure, unitTransforms, params.t, structure.root.boundary.sphere);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
|
||||
info: structure.root,
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
|
||||
info: structure,
|
||||
repr: a.data.repr
|
||||
}, { label: `Explode T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.sourceData;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (b.data.info !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
explodeStructure(structure.root, unitTransforms, newParams.t);
|
||||
explodeStructure(structure, unitTransforms, newParams.t, structure.root.boundary.sphere);
|
||||
b.label = `Explode T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
@@ -275,27 +275,27 @@ const SpinStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure.root);
|
||||
const unitTransforms = new StructureUnitTransforms(structure);
|
||||
|
||||
const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, params);
|
||||
spinStructure(structure, unitTransforms, params.t, axis, origin);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
|
||||
info: structure.root,
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
|
||||
info: structure,
|
||||
repr: a.data.repr
|
||||
}, { label: `Spin T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.sourceData;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (b.data.info !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t && oldParams.axis === newParams.axis && oldParams.origin === newParams.origin) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, newParams);
|
||||
spinStructure(structure.root, unitTransforms, newParams.t, axis, origin);
|
||||
spinStructure(structure, unitTransforms, newParams.t, axis, origin);
|
||||
b.label = `Spin T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
|
||||
@@ -203,7 +203,7 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
|
||||
createLocationIterator: BondIterator.fromStructure,
|
||||
getLoci: getInterBondLoci,
|
||||
eachLocation: eachInterBond,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>) => {
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
@@ -218,6 +218,13 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
|
||||
);
|
||||
|
||||
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
|
||||
state.createGeometry = true;
|
||||
state.updateTransform = true;
|
||||
state.updateColor = true;
|
||||
state.updateSize = true;
|
||||
}
|
||||
},
|
||||
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return !props.tryUseImpostor || !webgl;
|
||||
@@ -232,7 +239,7 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
|
||||
createLocationIterator: BondIterator.fromStructure,
|
||||
getLoci: getInterBondLoci,
|
||||
eachLocation: eachInterBond,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>) => {
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
|
||||
@@ -249,6 +256,13 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
|
||||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
|
||||
);
|
||||
|
||||
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
|
||||
state.createGeometry = true;
|
||||
state.updateTransform = true;
|
||||
state.updateColor = true;
|
||||
state.updateSize = true;
|
||||
}
|
||||
},
|
||||
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
|
||||
return props.tryUseImpostor && !!webgl;
|
||||
|
||||
@@ -120,7 +120,7 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
|
||||
createLocationIterator: BondIterator.fromStructure,
|
||||
getLoci: getInterBondLoci,
|
||||
eachLocation: eachInterBond,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondLineParams>, currentProps: PD.Values<InterUnitBondLineParams>) => {
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondLineParams>, currentProps: PD.Values<InterUnitBondLineParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
@@ -130,6 +130,13 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
|
||||
);
|
||||
|
||||
if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
|
||||
state.createGeometry = true;
|
||||
state.updateTransform = true;
|
||||
state.updateColor = true;
|
||||
state.updateSize = true;
|
||||
}
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
@@ -130,9 +130,9 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
|
||||
},
|
||||
processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
|
||||
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
|
||||
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
|
||||
const csp = getColorSmoothingProps(props, theme, resolution);
|
||||
if (csp) {
|
||||
applyMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
|
||||
applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
|
||||
(geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
|
||||
}
|
||||
},
|
||||
@@ -190,9 +190,9 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
|
||||
},
|
||||
processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
|
||||
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
|
||||
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
|
||||
const csp = getColorSmoothingProps(props, theme, resolution);
|
||||
if (csp) {
|
||||
applyMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
|
||||
applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
|
||||
(geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
|
||||
}
|
||||
},
|
||||
@@ -263,9 +263,9 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
|
||||
},
|
||||
processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
|
||||
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
|
||||
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
|
||||
if (csp) {
|
||||
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
|
||||
const csp = getColorSmoothingProps(props, theme, resolution);
|
||||
if (csp && webgl) {
|
||||
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
|
||||
(geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
|
||||
}
|
||||
},
|
||||
@@ -339,9 +339,9 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
|
||||
},
|
||||
processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
|
||||
const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
|
||||
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
|
||||
if (csp) {
|
||||
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
|
||||
const csp = getColorSmoothingProps(props, theme, resolution);
|
||||
if (csp && webgl) {
|
||||
applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
|
||||
(geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -80,9 +80,9 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
|
||||
},
|
||||
processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
|
||||
const { resolution, colorTexture } = geometry.meta as MolecularSurfaceMeta;
|
||||
const csp = getColorSmoothingProps(props, theme, resolution, webgl);
|
||||
const csp = getColorSmoothingProps(props, theme, resolution);
|
||||
if (csp) {
|
||||
applyMeshColorSmoothing(values, csp.resolution, csp.stride, csp.webgl, colorTexture);
|
||||
applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
|
||||
(geometry.meta.colorTexture as MolecularSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -27,8 +27,8 @@ export const ColorSmoothingParams = {
|
||||
};
|
||||
export type ColorSmoothingParams = typeof ColorSmoothingParams
|
||||
|
||||
export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number, webgl?: WebGLContext) {
|
||||
if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3 && webgl) {
|
||||
export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number) {
|
||||
if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3) {
|
||||
let stride = 3;
|
||||
if (props.smoothColors.name === 'on') {
|
||||
resolution *= props.smoothColors.params.resolutionFactor;
|
||||
@@ -39,7 +39,7 @@ export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, t
|
||||
resolution = Math.max(0.5, resolution);
|
||||
if (resolution > 1.2) stride = 2;
|
||||
}
|
||||
return { resolution, stride, webgl };
|
||||
return { resolution, stride };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class SymbolRuntimeImpl<S extends MSymbol> implements QuerySymbolRuntime {
|
||||
constArgs = true;
|
||||
} else if (Expression.isArgumentsArray(inputArgs)) {
|
||||
args = [];
|
||||
constArgs = false;
|
||||
constArgs = true;
|
||||
for (const arg of inputArgs) {
|
||||
const compiled = _compile(ctx, arg);
|
||||
constArgs = constArgs && compiled.isConst;
|
||||
@@ -128,7 +128,7 @@ class SymbolRuntimeImpl<S extends MSymbol> implements QuerySymbolRuntime {
|
||||
}
|
||||
} else {
|
||||
args = Object.create(null);
|
||||
constArgs = false;
|
||||
constArgs = true;
|
||||
for (const key of Object.keys(inputArgs)) {
|
||||
const compiled = _compile(ctx, inputArgs[key]);
|
||||
constArgs = constArgs && compiled.isConst;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 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>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
@@ -59,6 +60,7 @@ export const DefaultInputObserverProps = {
|
||||
noContextMenu: true,
|
||||
noPinchZoom: true,
|
||||
noTextSelect: true,
|
||||
preventGestures: false,
|
||||
mask: (x: number, y: number) => true,
|
||||
|
||||
pixelScale: 1
|
||||
@@ -144,6 +146,8 @@ export type WheelInput = {
|
||||
dx: number,
|
||||
dy: number,
|
||||
dz: number,
|
||||
spinX: number,
|
||||
spinY: number
|
||||
} & BaseInput
|
||||
|
||||
export type ClickInput = {
|
||||
@@ -164,10 +168,20 @@ export type MoveInput = {
|
||||
export type PinchInput = {
|
||||
delta: number,
|
||||
fraction: number,
|
||||
fractionDelta: number,
|
||||
distance: number,
|
||||
isStart: boolean
|
||||
} & BaseInput
|
||||
|
||||
export type GestureInput = {
|
||||
scale: number,
|
||||
rotation: number,
|
||||
deltaScale: number,
|
||||
deltaRotation: number
|
||||
isStart?: boolean,
|
||||
isEnd?: boolean
|
||||
}
|
||||
|
||||
export type KeyInput = {
|
||||
key: string,
|
||||
modifiers: ModifiersKeys
|
||||
@@ -192,6 +206,11 @@ type PointerEvent = {
|
||||
preventDefault?: () => void
|
||||
}
|
||||
|
||||
type GestureEvent = {
|
||||
scale: number,
|
||||
rotation: number,
|
||||
} & MouseEvent
|
||||
|
||||
interface InputObserver {
|
||||
noScroll: boolean
|
||||
noContextMenu: boolean
|
||||
@@ -205,6 +224,7 @@ interface InputObserver {
|
||||
readonly interactionEnd: Observable<undefined>,
|
||||
readonly wheel: Observable<WheelInput>,
|
||||
readonly pinch: Observable<PinchInput>,
|
||||
readonly gesture: Observable<GestureInput>,
|
||||
readonly click: Observable<ClickInput>,
|
||||
readonly move: Observable<MoveInput>,
|
||||
readonly leave: Observable<undefined>,
|
||||
@@ -224,6 +244,7 @@ function createEvents() {
|
||||
move: new Subject<MoveInput>(),
|
||||
wheel: new Subject<WheelInput>(),
|
||||
pinch: new Subject<PinchInput>(),
|
||||
gesture: new Subject<GestureInput>(),
|
||||
resize: new Subject<ResizeInput>(),
|
||||
leave: new Subject<undefined>(),
|
||||
enter: new Subject<undefined>(),
|
||||
@@ -252,12 +273,12 @@ namespace InputObserver {
|
||||
}
|
||||
|
||||
export function fromElement(element: Element, props: InputObserverProps = {}): InputObserver {
|
||||
let { noScroll, noMiddleClickScroll, noContextMenu, noPinchZoom, noTextSelect, mask, pixelScale } = { ...DefaultInputObserverProps, ...props };
|
||||
let { noScroll, noMiddleClickScroll, noContextMenu, noPinchZoom, noTextSelect, mask, pixelScale, preventGestures } = { ...DefaultInputObserverProps, ...props };
|
||||
|
||||
let width = element.clientWidth * pixelRatio();
|
||||
let height = element.clientHeight * pixelRatio();
|
||||
|
||||
let lastTouchDistance = 0;
|
||||
let lastTouchDistance = 0, lastTouchFraction = 0;
|
||||
const pointerDown = Vec2();
|
||||
const pointerStart = Vec2();
|
||||
const pointerEnd = Vec2();
|
||||
@@ -285,19 +306,19 @@ namespace InputObserver {
|
||||
let isInside = false;
|
||||
|
||||
const events = createEvents();
|
||||
const { drag, interactionEnd, wheel, pinch, click, move, leave, enter, resize, modifiers, key } = events;
|
||||
const { drag, interactionEnd, wheel, pinch, gesture, click, move, leave, enter, resize, modifiers, key } = events;
|
||||
|
||||
attach();
|
||||
|
||||
return {
|
||||
get noScroll () { return noScroll; },
|
||||
set noScroll (value: boolean) { noScroll = value; },
|
||||
get noContextMenu () { return noContextMenu; },
|
||||
set noContextMenu (value: boolean) { noContextMenu = value; },
|
||||
get noScroll() { return noScroll; },
|
||||
set noScroll(value: boolean) { noScroll = value; },
|
||||
get noContextMenu() { return noContextMenu; },
|
||||
set noContextMenu(value: boolean) { noContextMenu = value; },
|
||||
|
||||
get width () { return width; },
|
||||
get height () { return height; },
|
||||
get pixelRatio () { return pixelRatio(); },
|
||||
get width() { return width; },
|
||||
get height() { return height; },
|
||||
get pixelRatio() { return pixelRatio(); },
|
||||
|
||||
...events,
|
||||
|
||||
@@ -305,7 +326,7 @@ namespace InputObserver {
|
||||
};
|
||||
|
||||
function attach() {
|
||||
element.addEventListener('contextmenu', onContextMenu as any, false );
|
||||
element.addEventListener('contextmenu', onContextMenu as any, false);
|
||||
|
||||
element.addEventListener('wheel', onMouseWheel as any, false);
|
||||
element.addEventListener('mousedown', onMouseDown as any, false);
|
||||
@@ -322,6 +343,10 @@ namespace InputObserver {
|
||||
element.addEventListener('touchmove', onTouchMove as any, false);
|
||||
element.addEventListener('touchend', onTouchEnd as any, false);
|
||||
|
||||
element.addEventListener('gesturechange', onGestureChange as any, false);
|
||||
element.addEventListener('gesturestart', onGestureStart as any, false);
|
||||
element.addEventListener('gestureend', onGestureEnd as any, false);
|
||||
|
||||
// reset buttons and modifier keys state when browser window looses focus
|
||||
window.addEventListener('blur', handleBlur);
|
||||
window.addEventListener('keyup', handleKeyUp as EventListener, false);
|
||||
@@ -335,7 +360,7 @@ namespace InputObserver {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
element.removeEventListener( 'contextmenu', onContextMenu as any, false );
|
||||
element.removeEventListener('contextmenu', onContextMenu as any, false);
|
||||
|
||||
element.removeEventListener('wheel', onMouseWheel as any, false);
|
||||
element.removeEventListener('mousedown', onMouseDown as any, false);
|
||||
@@ -349,6 +374,10 @@ namespace InputObserver {
|
||||
element.removeEventListener('touchmove', onTouchMove as any, false);
|
||||
element.removeEventListener('touchend', onTouchEnd as any, false);
|
||||
|
||||
element.removeEventListener('gesturechange', onGestureChange as any, false);
|
||||
element.removeEventListener('gesturestart', onGestureStart as any, false);
|
||||
element.removeEventListener('gestureend', onGestureEnd as any, false);
|
||||
|
||||
window.removeEventListener('blur', handleBlur);
|
||||
window.removeEventListener('keyup', handleKeyUp as EventListener, false);
|
||||
window.removeEventListener('keydown', handleKeyDown as EventListener, false);
|
||||
@@ -427,6 +456,8 @@ namespace InputObserver {
|
||||
}
|
||||
|
||||
function onTouchStart(ev: TouchEvent) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (ev.touches.length === 1) {
|
||||
buttons = button = ButtonsType.Flag.Primary;
|
||||
onPointerDown(ev.touches[0]);
|
||||
@@ -440,6 +471,7 @@ namespace InputObserver {
|
||||
pinch.next({
|
||||
distance: touchDistance,
|
||||
fraction: 1,
|
||||
fractionDelta: 0,
|
||||
delta: 0,
|
||||
isStart: true,
|
||||
buttons,
|
||||
@@ -480,15 +512,18 @@ namespace InputObserver {
|
||||
} else {
|
||||
buttons = ButtonsType.Flag.Auxilary;
|
||||
updateModifierKeys(ev);
|
||||
const fraction = lastTouchDistance / touchDistance;
|
||||
pinch.next({
|
||||
delta: touchDelta,
|
||||
fraction: lastTouchDistance / touchDistance,
|
||||
fraction,
|
||||
fractionDelta: lastTouchFraction - fraction,
|
||||
distance: touchDistance,
|
||||
isStart: false,
|
||||
buttons,
|
||||
button,
|
||||
modifiers: getModifierKeys()
|
||||
});
|
||||
lastTouchFraction = fraction;
|
||||
}
|
||||
lastTouchDistance = touchDistance;
|
||||
} else if (ev.touches.length === 3) {
|
||||
@@ -544,7 +579,7 @@ namespace InputObserver {
|
||||
eventOffset(pointerEnd, ev);
|
||||
if (Vec2.distance(pointerEnd, pointerDown) < 4) {
|
||||
const { pageX, pageY } = ev;
|
||||
const [ x, y ] = pointerEnd;
|
||||
const [x, y] = pointerEnd;
|
||||
|
||||
click.next({ x, y, pageX, pageY, buttons, button, modifiers: getModifierKeys() });
|
||||
}
|
||||
@@ -553,7 +588,7 @@ namespace InputObserver {
|
||||
function onPointerMove(ev: PointerEvent) {
|
||||
eventOffset(pointerEnd, ev);
|
||||
const { pageX, pageY } = ev;
|
||||
const [ x, y ] = pointerEnd;
|
||||
const [x, y] = pointerEnd;
|
||||
const inside = insideBounds(pointerEnd);
|
||||
move.next({ x, y, pageX, pageY, buttons, button, modifiers: getModifierKeys(), inside });
|
||||
|
||||
@@ -569,7 +604,7 @@ namespace InputObserver {
|
||||
const isStart = dragging === DraggingState.Started;
|
||||
if (isStart && !mask(ev.clientX, ev.clientY)) return;
|
||||
|
||||
const [ dx, dy ] = pointerDelta;
|
||||
const [dx, dy] = pointerDelta;
|
||||
drag.next({ x, y, dx, dy, pageX, pageY, buttons, button, modifiers: getModifierKeys(), isStart });
|
||||
|
||||
Vec2.copy(pointerStart, pointerEnd);
|
||||
@@ -581,30 +616,59 @@ namespace InputObserver {
|
||||
|
||||
eventOffset(pointerEnd, ev);
|
||||
const { pageX, pageY } = ev;
|
||||
const [ x, y ] = pointerEnd;
|
||||
const [x, y] = pointerEnd;
|
||||
|
||||
if (noScroll) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
let scale = 1;
|
||||
switch (ev.deltaMode) {
|
||||
case 0: scale = 1; break; // pixels
|
||||
case 1: scale = 40; break; // lines
|
||||
case 2: scale = 800; break; // pages
|
||||
}
|
||||
|
||||
const dx = (ev.deltaX || 0) * scale;
|
||||
const dy = (ev.deltaY || 0) * scale;
|
||||
const dz = (ev.deltaZ || 0) * scale;
|
||||
|
||||
const normalized = normalizeWheel(ev);
|
||||
buttons = button = ButtonsType.Flag.Auxilary;
|
||||
|
||||
if (dx || dy || dz) {
|
||||
wheel.next({ x, y, pageX, pageY, dx, dy, dz, buttons, button, modifiers: getModifierKeys() });
|
||||
if (normalized.dx || normalized.dy || normalized.dz) {
|
||||
wheel.next({ x, y, pageX, pageY, ...normalized, buttons, button, modifiers: getModifierKeys() });
|
||||
}
|
||||
}
|
||||
|
||||
function tryPreventGesture(ev: GestureEvent) {
|
||||
// console.log(ev, preventGestures);
|
||||
if (!preventGestures) return;
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation?.();
|
||||
ev.stopPropagation?.();
|
||||
}
|
||||
|
||||
let prevGestureScale = 0, prevGestureRotation = 0;
|
||||
|
||||
function onGestureStart(ev: GestureEvent) {
|
||||
tryPreventGesture(ev);
|
||||
prevGestureScale = ev.scale;
|
||||
prevGestureRotation = ev.rotation;
|
||||
gesture.next({ scale: ev.scale, rotation: ev.rotation, deltaRotation: 0, deltaScale: 0, isStart: true });
|
||||
}
|
||||
|
||||
function gestureDelta(ev: GestureEvent, isEnd?: boolean) {
|
||||
gesture.next({
|
||||
scale: ev.scale,
|
||||
rotation: ev.rotation,
|
||||
deltaRotation: prevGestureRotation - ev.rotation,
|
||||
deltaScale: prevGestureScale - ev.scale,
|
||||
isEnd
|
||||
});
|
||||
prevGestureRotation = ev.rotation;
|
||||
prevGestureScale = ev.scale;
|
||||
}
|
||||
|
||||
function onGestureChange(ev: GestureEvent) {
|
||||
tryPreventGesture(ev);
|
||||
gestureDelta(ev);
|
||||
}
|
||||
|
||||
function onGestureEnd(ev: GestureEvent) {
|
||||
tryPreventGesture(ev);
|
||||
gestureDelta(ev, true);
|
||||
}
|
||||
|
||||
function onMouseEnter(ev: Event) {
|
||||
isInside = true;
|
||||
enter.next();
|
||||
@@ -648,4 +712,54 @@ namespace InputObserver {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Adapted from https://stackoverflow.com/a/30134826
|
||||
// License: https://creativecommons.org/licenses/by-sa/3.0/
|
||||
function normalizeWheel(event: any) {
|
||||
// Reasonable defaults
|
||||
const PIXEL_STEP = 10;
|
||||
const LINE_HEIGHT = 40;
|
||||
const PAGE_HEIGHT = 800;
|
||||
let spinX = 0, spinY = 0,
|
||||
dx = 0, dy = 0, dz = 0; // pixelX, pixelY, pixelZ
|
||||
|
||||
// Legacy
|
||||
if ('detail' in event) { spinY = event.detail; }
|
||||
if ('wheelDelta' in event) { spinY = -event.wheelDelta / 120; }
|
||||
if ('wheelDeltaY' in event) { spinY = -event.wheelDeltaY / 120; }
|
||||
if ('wheelDeltaX' in event) { spinX = -event.wheelDeltaX / 120; }
|
||||
|
||||
// side scrolling on FF with DOMMouseScroll
|
||||
if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
|
||||
spinX = spinY;
|
||||
spinY = 0;
|
||||
}
|
||||
|
||||
dx = spinX * PIXEL_STEP;
|
||||
dy = spinY * PIXEL_STEP;
|
||||
|
||||
if ('deltaY' in event) { dy = event.deltaY; }
|
||||
if ('deltaX' in event) { dx = event.deltaX; }
|
||||
if ('deltaZ' in event) { dz = event.deltaZ; }
|
||||
|
||||
if ((dx || dy || dz) && event.deltaMode) {
|
||||
if (event.deltaMode === 1) { // delta in LINE units
|
||||
dx *= LINE_HEIGHT;
|
||||
dy *= LINE_HEIGHT;
|
||||
dz *= LINE_HEIGHT;
|
||||
} else { // delta in PAGE units
|
||||
dx *= PAGE_HEIGHT;
|
||||
dy *= PAGE_HEIGHT;
|
||||
dz *= PAGE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall-back if spin cannot be determined
|
||||
if (dx && !spinX) { spinX = (dx < 1) ? -1 : 1; }
|
||||
if (dy && !spinY) { spinY = (dy < 1) ? -1 : 1; }
|
||||
|
||||
return { spinX, spinY, dx, dy, dz };
|
||||
}
|
||||
|
||||
|
||||
export { InputObserver };
|
||||
Reference in New Issue
Block a user