Browse Source

Merge branch 'master' into protein-caps

Alexander Rose 1 year ago
parent
commit
46ea39703f

+ 4 - 0
CHANGELOG.md

@@ -8,6 +8,10 @@ Note that since we don't clearly distinguish between a public and private interf
 
 - Fix display issue with SIFTS mapping
 - Add 'NH2', 'FOR', 'FMT' to `CommonProteinCaps`
+- Add `opened` event to `PluginStateSnapshotManager`
+- Properly switch-off fog
+- Add `Euler` math primitive
+- Add stride option to element sphere & point visuals
 
 ## [v3.37.1] - 2023-06-20
 

File diff suppressed because it is too large
+ 308 - 255
package-lock.json


+ 19 - 19
package.json

@@ -106,44 +106,44 @@
     "@graphql-codegen/add": "^5.0.0",
     "@graphql-codegen/cli": "^4.0.1",
     "@graphql-codegen/time": "^5.0.0",
-    "@graphql-codegen/typescript": "^4.0.0",
+    "@graphql-codegen/typescript": "^4.0.1",
     "@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
     "@graphql-codegen/typescript-graphql-request": "^5.0.0",
-    "@graphql-codegen/typescript-operations": "^4.0.0",
+    "@graphql-codegen/typescript-operations": "^4.0.1",
     "@types/cors": "^2.8.13",
     "@types/gl": "^6.0.2",
     "@types/jpeg-js": "^0.3.7",
     "@types/pngjs": "^6.0.1",
     "@types/jest": "^29.5.2",
-    "@types/react": "^18.2.11",
-    "@types/react-dom": "^18.2.4",
-    "@typescript-eslint/eslint-plugin": "^5.59.9",
-    "@typescript-eslint/parser": "^5.59.9",
+    "@types/react": "^18.2.14",
+    "@types/react-dom": "^18.2.6",
+    "@typescript-eslint/eslint-plugin": "^5.61.0",
+    "@typescript-eslint/parser": "^5.61.0",
     "benchmark": "^2.1.4",
     "concurrently": "^8.2.0",
-    "cpx2": "^4.2.3",
+    "cpx2": "^5.0.0",
     "crypto-browserify": "^3.12.0",
     "css-loader": "^6.8.1",
-    "eslint": "^8.42.0",
+    "eslint": "^8.44.0",
     "extra-watch-webpack-plugin": "^1.0.3",
     "file-loader": "^6.2.0",
     "fs-extra": "^11.1.1",
-    "graphql": "^16.6.0",
+    "graphql": "^16.7.1",
     "http-server": "^14.1.1",
-    "jest": "^29.5.0",
+    "jest": "^29.6.1",
     "mini-css-extract-plugin": "^2.7.6",
     "path-browserify": "^1.0.1",
     "raw-loader": "^4.0.2",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
-    "sass": "^1.63.3",
+    "sass": "^1.63.6",
     "sass-loader": "^13.3.2",
-    "simple-git": "^3.19.0",
+    "simple-git": "^3.19.1",
     "stream-browserify": "^3.0.0",
     "style-loader": "^3.3.3",
-    "ts-jest": "^29.1.0",
-    "typescript": "^5.1.3",
-    "webpack": "^5.86.0",
+    "ts-jest": "^29.1.1",
+    "typescript": "^5.1.6",
+    "webpack": "^5.88.1",
     "webpack-cli": "^5.1.4"
   },
   "dependencies": {
@@ -151,7 +151,7 @@
     "@types/benchmark": "^2.1.2",
     "@types/compression": "1.7.2",
     "@types/express": "^4.17.17",
-    "@types/node": "^16.18.35",
+    "@types/node": "^16.18.38",
     "@types/node-fetch": "^2.6.4",
     "@types/swagger-ui-dist": "3.30.1",
     "argparse": "^2.0.1",
@@ -162,10 +162,10 @@
     "h264-mp4-encoder": "^1.0.12",
     "immer": "^9.0.21",
     "immutable": "^4.3.0",
-    "node-fetch": "^2.6.11",
+    "node-fetch": "^2.6.12",
     "rxjs": "^7.8.1",
-    "swagger-ui-dist": "^4.19.0",
-    "tslib": "^2.5.3",
+    "swagger-ui-dist": "^5.1.0",
+    "tslib": "^2.6.0",
     "util.promisify": "^1.1.2",
     "xhr2": "^0.2.1"
   },

+ 1 - 0
src/mol-gl/renderable/schema.ts

@@ -136,6 +136,7 @@ export const GlobalUniformSchema = {
     uCameraDir: UniformSpec('v3'),
     uNear: UniformSpec('f'),
     uFar: UniformSpec('f'),
+    uFog: UniformSpec('b'),
     uFogNear: UniformSpec('f'),
     uFogFar: UniformSpec('f'),
     uFogColor: UniformSpec('v3'),

+ 1 - 0
src/mol-gl/renderer.ts

@@ -213,6 +213,7 @@ namespace Renderer {
             uCameraDir: ValueCell.create(cameraDir),
             uNear: ValueCell.create(1),
             uFar: ValueCell.create(10000),
+            uFog: ValueCell.create(true),
             uFogNear: ValueCell.create(1),
             uFogFar: ValueCell.create(10000),
             uFogColor: ValueCell.create(bgColor),

+ 22 - 20
src/mol-gl/shader/chunks/apply-fog.glsl.ts

@@ -1,30 +1,32 @@
 export const apply_fog = `
-float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
-float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
-float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
 float preFogAlpha = gl_FragColor.a;
-if (!uTransparentBackground) {
-    if (gl_FragColor.a < 1.0) {
-        // transparent objects are blended with background color
-        gl_FragColor.a = fogAlpha;
-    } else {
-        // mix opaque objects with background color
-        gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
-    }
-} else {
-    #if defined(dRenderVariant_colorDpoit)
+if (uFog) {
+    float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
+    float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
+    float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
+    if (!uTransparentBackground) {
         if (gl_FragColor.a < 1.0) {
             // transparent objects are blended with background color
             gl_FragColor.a = fogAlpha;
         } else {
-            // opaque objects need to be pre-multiplied alpha
+            // mix opaque objects with background color
+            gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
+        }
+    } else {
+        #if defined(dRenderVariant_colorDpoit)
+            if (gl_FragColor.a < 1.0) {
+                // transparent objects are blended with background color
+                gl_FragColor.a = fogAlpha;
+            } else {
+                // opaque objects need to be pre-multiplied alpha
+                gl_FragColor.rgb *= fogAlpha;
+                gl_FragColor.a = fogAlpha;
+            }
+        #else
+            // pre-multiplied alpha expected for transparent background
             gl_FragColor.rgb *= fogAlpha;
             gl_FragColor.a = fogAlpha;
-        }
-    #else
-        // pre-multiplied alpha expected for transparent background
-        gl_FragColor.rgb *= fogAlpha;
-        gl_FragColor.a = fogAlpha;
-    #endif
+        #endif
+    }
 }
 `;

+ 1 - 0
src/mol-gl/shader/chunks/common-frag-params.glsl.ts

@@ -57,6 +57,7 @@ uniform float uNear;
 uniform float uFar;
 uniform float uIsOrtho;
 
+uniform bool uFog;
 uniform float uFogNear;
 uniform float uFogFar;
 uniform vec3 uFogColor;

+ 1 - 0
src/mol-gl/shader/direct-volume.frag.ts

@@ -68,6 +68,7 @@ uniform int uGroupCount;
 uniform float uMetalness;
 uniform float uRoughness;
 
+uniform bool uFog;
 uniform float uFogNear;
 uniform float uFogFar;
 uniform vec3 uFogColor;

+ 167 - 0
src/mol-math/linear-algebra/3d/euler.ts

@@ -0,0 +1,167 @@
+/**
+ * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ *
+ * This code has been modified from https://github.com/mrdoob/three.js/,
+ * copyright (c) 2010-2023 three.js authors. MIT License
+ */
+
+import { Mat4 } from './mat4';
+import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
+import { Quat } from './quat';
+import { Vec3 } from './vec3';
+import { clamp } from '../../interpolate';
+
+interface Euler extends Array<number> { [d: number]: number, '@type': 'euler', length: 3 }
+
+function Euler() {
+    return Euler.zero();
+}
+
+namespace Euler {
+    export type Order = 'XYZ' | 'YXZ' | 'ZXY' | 'ZYX' | 'YZX' | 'XZY'
+
+    export function zero(): Euler {
+        // force double backing array by 0.1.
+        const ret = [0.1, 0, 0];
+        ret[0] = 0.0;
+        return ret as any;
+    }
+
+    export function create(x: number, y: number, z: number): Euler {
+        const out = zero();
+        out[0] = x;
+        out[1] = y;
+        out[2] = z;
+        return out;
+    }
+
+    export function set(out: Euler, x: number, y: number, z: number) {
+        out[0] = x;
+        out[0] = y;
+        out[0] = z;
+        return out;
+    }
+
+    export function clone(a: Euler): Euler {
+        const out = zero();
+        out[0] = a[0];
+        out[1] = a[1];
+        out[2] = a[2];
+        return out;
+    }
+
+    export function copy(out: Euler, a: Euler) {
+        out[0] = a[0];
+        out[1] = a[1];
+        out[2] = a[2];
+        return out;
+    }
+
+    /**
+     * Assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+     */
+    export function fromMat4(out: Euler, m: Mat4, order: Order): Euler {
+        const m11 = m[0], m12 = m[4], m13 = m[8];
+        const m21 = m[1], m22 = m[5], m23 = m[9];
+        const m31 = m[2], m32 = m[6], m33 = m[10];
+
+        switch (order) {
+            case 'XYZ':
+                out[1] = Math.asin(clamp(m13, -1, 1));
+                if (Math.abs(m13) < 0.9999999) {
+                    out[0] = Math.atan2(-m23, m33);
+                    out[2] = Math.atan2(-m12, m11);
+                } else {
+                    out[0] = Math.atan2(m32, m22);
+                    out[2] = 0;
+                }
+                break;
+            case 'YXZ':
+                out[0] = Math.asin(-clamp(m23, -1, 1));
+                if (Math.abs(m23) < 0.9999999) {
+                    out[1] = Math.atan2(m13, m33);
+                    out[2] = Math.atan2(m21, m22);
+                } else {
+                    out[1] = Math.atan2(-m31, m11);
+                    out[2] = 0;
+                }
+                break;
+            case 'ZXY':
+                out[0] = Math.asin(clamp(m32, -1, 1));
+                if (Math.abs(m32) < 0.9999999) {
+                    out[1] = Math.atan2(-m31, m33);
+                    out[2] = Math.atan2(-m12, m22);
+                } else {
+                    out[1] = 0;
+                    out[2] = Math.atan2(m21, m11);
+                }
+                break;
+            case 'ZYX':
+                out[1] = Math.asin(-clamp(m31, -1, 1));
+                if (Math.abs(m31) < 0.9999999) {
+                    out[0] = Math.atan2(m32, m33);
+                    out[2] = Math.atan2(m21, m11);
+                } else {
+                    out[0] = 0;
+                    out[2] = Math.atan2(-m12, m22);
+                }
+                break;
+            case 'YZX':
+                out[2] = Math.asin(clamp(m21, -1, 1));
+                if (Math.abs(m21) < 0.9999999) {
+                    out[0] = Math.atan2(-m23, m22);
+                    out[1] = Math.atan2(-m31, m11);
+                } else {
+                    out[0] = 0;
+                    out[1] = Math.atan2(m13, m33);
+                }
+                break;
+            case 'XZY':
+                out[2] = Math.asin(-clamp(m12, -1, 1));
+                if (Math.abs(m12) < 0.9999999) {
+                    out[0] = Math.atan2(m32, m22);
+                    out[1] = Math.atan2(m13, m11);
+                } else {
+                    out[0] = Math.atan2(-m23, m33);
+                    out[1] = 0;
+                }
+                break;
+            default:
+                assertUnreachable(order);
+        }
+
+        return out;
+    }
+
+    const _mat4 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as Mat4;
+    export function fromQuat(out: Euler, q: Quat, order: Order) {
+        Mat4.fromQuat(_mat4, q);
+        return fromMat4(out, _mat4, order);
+    }
+
+    export function fromVec3(out: Euler, v: Vec3) {
+        return set(out, v[0], v[1], v[2]);
+    }
+
+    export function exactEquals(a: Euler, b: Euler) {
+        return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
+    }
+
+    export function fromArray(e: Euler, array: ArrayLike<number>, offset: number) {
+        e[0] = array[offset + 0];
+        e[1] = array[offset + 1];
+        e[2] = array[offset + 2];
+        return e;
+    }
+
+    export function toArray<T extends NumberArray>(e: Euler, out: T, offset: number) {
+        out[offset + 0] = e[0];
+        out[offset + 1] = e[1];
+        out[offset + 2] = e[2];
+        return out;
+    }
+}
+
+export { Euler };

+ 165 - 0
src/mol-math/linear-algebra/3d/mat4.ts

@@ -23,6 +23,7 @@ import { Quat } from './quat';
 import { degToRad } from '../../misc';
 import { NumberArray } from '../../../mol-util/type-helpers';
 import { Mat3 } from './mat3';
+import { Euler } from './euler';
 
 interface Mat4 extends Array<number> { [d: number]: number, '@type': 'mat4', length: 16 }
 interface ReadonlyMat4 extends Array<number> { readonly [d: number]: number, '@type': 'mat4', length: 16 }
@@ -717,6 +718,82 @@ namespace Mat4 {
         return out;
     }
 
+    export function compose(out: Mat4, position: Vec3, quaternion: Quat, scale: Vec3) {
+        const [x, y, z, w] = quaternion;
+        const x2 = x + x,	y2 = y + y, z2 = z + z;
+        const xx = x * x2, xy = x * y2, xz = x * z2;
+        const yy = y * y2, yz = y * z2, zz = z * z2;
+        const wx = w * x2, wy = w * y2, wz = w * z2;
+
+        const [sx, sy, sz] = scale;
+
+        out[0] = (1 - (yy + zz)) * sx;
+        out[1] = (xy + wz) * sx;
+        out[2] = (xz - wy) * sx;
+        out[3] = 0;
+
+        out[4] = (xy - wz) * sy;
+        out[5] = (1 - (xx + zz)) * sy;
+        out[6] = (yz + wx) * sy;
+        out[7] = 0;
+
+        out[8] = (xz + wy) * sz;
+        out[9] = (yz - wx) * sz;
+        out[10] = (1 - (xx + yy)) * sz;
+        out[11] = 0;
+
+        out[12] = position[0];
+        out[13] = position[1];
+        out[14] = position[2];
+        out[15] = 1;
+
+        return out;
+    }
+
+    const _v3 = [0, 0, 0] as Vec3;
+    const _m4 = zero();
+    export function decompose(m: Mat4, position: Vec3, quaternion: Quat, scale: Vec3) {
+
+        let sx = Vec3.magnitude(Vec3.set(_v3, m[0], m[1], m[2]));
+        const sy = Vec3.magnitude(Vec3.set(_v3, m[4], m[5], m[6]));
+        const sz = Vec3.magnitude(Vec3.set(_v3, m[8], m[9], m[10]));
+
+        // if determine is negative, we need to invert one scale
+        const det = determinant(m);
+        if (det < 0) sx = -sx;
+
+        position[0] = m[12];
+        position[1] = m[13];
+        position[2] = m[14];
+
+        // scale the rotation part
+        copy(_m4, m);
+
+        const invSX = 1 / sx;
+        const invSY = 1 / sy;
+        const invSZ = 1 / sz;
+
+        _m4[0] *= invSX;
+        _m4[1] *= invSX;
+        _m4[2] *= invSX;
+
+        _m4[4] *= invSY;
+        _m4[5] *= invSY;
+        _m4[6] *= invSY;
+
+        _m4[8] *= invSZ;
+        _m4[9] *= invSZ;
+        _m4[10] *= invSZ;
+
+        getRotation(quaternion, _m4);
+
+        scale[0] = sx;
+        scale[1] = sy;
+        scale[2] = sz;
+
+        return m;
+    }
+
     export function makeTable(m: Mat4) {
         let ret = '';
         for (let i = 0; i < 4; i++) {
@@ -851,6 +928,94 @@ namespace Mat4 {
         return out;
     }
 
+    export function fromEuler(out: Mat4, euler: Euler, order: Euler.Order) {
+        const x = euler[0], y = euler[1], z = euler[2];
+        const a = Math.cos(x), b = Math.sin(x);
+        const c = Math.cos(y), d = Math.sin(y);
+        const e = Math.cos(z), f = Math.sin(z);
+
+        if (order === 'XYZ') {
+            const ae = a * e, af = a * f, be = b * e, bf = b * f;
+            out[0] = c * e;
+            out[4] = - c * f;
+            out[8] = d;
+            out[1] = af + be * d;
+            out[5] = ae - bf * d;
+            out[9] = - b * c;
+            out[2] = bf - ae * d;
+            out[6] = be + af * d;
+            out[10] = a * c;
+        } else if (order === 'YXZ') {
+            const ce = c * e, cf = c * f, de = d * e, df = d * f;
+            out[0] = ce + df * b;
+            out[4] = de * b - cf;
+            out[8] = a * d;
+            out[1] = a * f;
+            out[5] = a * e;
+            out[9] = - b;
+            out[2] = cf * b - de;
+            out[6] = df + ce * b;
+            out[10] = a * c;
+        } else if (order === 'ZXY') {
+            const ce = c * e, cf = c * f, de = d * e, df = d * f;
+            out[0] = ce - df * b;
+            out[4] = - a * f;
+            out[8] = de + cf * b;
+            out[1] = cf + de * b;
+            out[5] = a * e;
+            out[9] = df - ce * b;
+            out[2] = - a * d;
+            out[6] = b;
+            out[10] = a * c;
+        } else if (order === 'ZYX') {
+            const ae = a * e, af = a * f, be = b * e, bf = b * f;
+            out[0] = c * e;
+            out[4] = be * d - af;
+            out[8] = ae * d + bf;
+            out[1] = c * f;
+            out[5] = bf * d + ae;
+            out[9] = af * d - be;
+            out[2] = - d;
+            out[6] = b * c;
+            out[10] = a * c;
+        } else if (order === 'YZX') {
+            const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+            out[0] = c * e;
+            out[4] = bd - ac * f;
+            out[8] = bc * f + ad;
+            out[1] = f;
+            out[5] = a * e;
+            out[9] = - b * e;
+            out[2] = - d * e;
+            out[6] = ad * f + bc;
+            out[10] = ac - bd * f;
+        } else if (order === 'XZY') {
+            const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+            out[0] = c * e;
+            out[4] = - f;
+            out[8] = d * e;
+            out[1] = ac * f + bd;
+            out[5] = a * e;
+            out[9] = ad * f - bc;
+            out[2] = bc * f - ad;
+            out[6] = b * e;
+            out[10] = bd * f + ac;
+        }
+
+        // bottom row
+        out[3] = 0;
+        out[7] = 0;
+        out[11] = 0;
+
+        // last column
+        out[12] = 0;
+        out[13] = 0;
+        out[14] = 0;
+        out[15] = 1;
+
+        return out;
+    }
+
     /**
      * Generates a perspective projection (frustum) matrix with the given bounds
      */

+ 64 - 2
src/mol-math/linear-algebra/3d/quat.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2023 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>
@@ -25,7 +25,8 @@
 import { Mat3 } from './mat3';
 import { Vec3 } from './vec3';
 import { EPSILON } from './common';
-import { NumberArray } from '../../../mol-util/type-helpers';
+import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
+import { Euler } from './euler';
 
 interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 }
 interface ReadonlyQuat extends Array<number> { readonly [d: number]: number, '@type': 'quat', length: 4 }
@@ -238,6 +239,10 @@ namespace Quat {
         return out;
     }
 
+    export function dot(a: Quat, b: Quat) {
+        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+    }
+
     /**
      * Creates a quaternion from the given 3x3 rotation matrix.
      *
@@ -277,6 +282,63 @@ namespace Quat {
         return out;
     }
 
+    export function fromEuler(out: Quat, euler: Euler, order: Euler.Order) {
+        const [x, y, z] = euler;
+
+        // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
+
+        const c1 = Math.cos(x / 2);
+        const c2 = Math.cos(y / 2);
+        const c3 = Math.cos(z / 2);
+
+        const s1 = Math.sin(x / 2);
+        const s2 = Math.sin(y / 2);
+        const s3 = Math.sin(z / 2);
+
+        switch (order) {
+            case 'XYZ':
+                out[0] = s1 * c2 * c3 + c1 * s2 * s3;
+                out[1] = c1 * s2 * c3 - s1 * c2 * s3;
+                out[2] = c1 * c2 * s3 + s1 * s2 * c3;
+                out[3] = c1 * c2 * c3 - s1 * s2 * s3;
+                break;
+            case 'YXZ':
+                out[0] = s1 * c2 * c3 + c1 * s2 * s3;
+                out[1] = c1 * s2 * c3 - s1 * c2 * s3;
+                out[2] = c1 * c2 * s3 - s1 * s2 * c3;
+                out[3] = c1 * c2 * c3 + s1 * s2 * s3;
+                break;
+            case 'ZXY':
+                out[0] = s1 * c2 * c3 - c1 * s2 * s3;
+                out[1] = c1 * s2 * c3 + s1 * c2 * s3;
+                out[2] = c1 * c2 * s3 + s1 * s2 * c3;
+                out[3] = c1 * c2 * c3 - s1 * s2 * s3;
+                break;
+            case 'ZYX':
+                out[0] = s1 * c2 * c3 - c1 * s2 * s3;
+                out[1] = c1 * s2 * c3 + s1 * c2 * s3;
+                out[2] = c1 * c2 * s3 - s1 * s2 * c3;
+                out[3] = c1 * c2 * c3 + s1 * s2 * s3;
+                break;
+            case 'YZX':
+                out[0] = s1 * c2 * c3 + c1 * s2 * s3;
+                out[1] = c1 * s2 * c3 + s1 * c2 * s3;
+                out[2] = c1 * c2 * s3 - s1 * s2 * c3;
+                out[3] = c1 * c2 * c3 - s1 * s2 * s3;
+                break;
+            case 'XZY':
+                out[0] = s1 * c2 * c3 - c1 * s2 * s3;
+                out[1] = c1 * s2 * c3 - s1 * c2 * s3;
+                out[2] = c1 * c2 * s3 + s1 * s2 * c3;
+                out[3] = c1 * c2 * c3 + s1 * s2 * s3;
+                break;
+            default:
+                assertUnreachable(order);
+        }
+
+        return out;
+    }
+
     const fromUnitVec3Temp = [0, 0, 0] as Vec3;
     /** Quaternion from two normalized unit vectors. */
     export function fromUnitVec3(out: Quat, a: Vec3, b: Vec3) {

+ 35 - 0
src/mol-math/linear-algebra/_spec/euler.spec.ts

@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Mat4 } from '../3d/mat4';
+import { Euler } from '../3d/euler';
+import { Quat } from '../3d/quat';
+
+const t = [
+    [Euler.create(0, 0, 0), 'XYZ'],
+    [Euler.create(1, 0, 0), 'XYZ'],
+    [Euler.create(0, 1, 0), 'ZYX'],
+] as const;
+
+describe('Euler', () => {
+    it('fromMat4', () => {
+        for (const [e, o] of t) {
+            const m = Mat4.fromEuler(Mat4(), e, o);
+            const e2 = Euler.fromMat4(Euler(), m, o);
+            const m2 = Mat4.fromEuler(Mat4(), e2, o);
+            expect(Mat4.areEqual(m, m2, 0.0001)).toBe(true);
+        }
+    });
+
+    it('fromQuat', () => {
+        for (const [e, o] of t) {
+            const q = Quat.fromEuler(Quat(), e, o);
+            const e2 = Euler.fromQuat(Euler(), q, o);
+            const q2 = Quat.fromEuler(Quat(), e2, o);
+            expect(Quat.equals(q, q2)).toBe(true);
+        }
+    });
+});

+ 2 - 2
src/mol-model-formats/structure/property/assembly.ts

@@ -49,7 +49,7 @@ function createAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assemb
     return Assembly.create(id, details, operatorGroupsProvider(generators, matrices));
 }
 
-function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () => OperatorGroups {
+export function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () => OperatorGroups {
     return () => {
         const groups: OperatorGroup[] = [];
 
@@ -71,7 +71,7 @@ function operatorGroupsProvider(generators: Generator[], matrices: Matrices): ()
     };
 }
 
-function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices {
+export function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices {
     const { id, matrix, vector, _schema } = pdbx_struct_oper_list;
     const matrices = new Map<string, Mat4>();
     const t = Vec3();

+ 7 - 5
src/mol-plugin-state/manager/snapshots.ts

@@ -31,7 +31,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
     private entryMap = new Map<string, PluginStateSnapshotManager.Entry>();
 
     readonly events = {
-        changed: this.ev()
+        changed: this.ev(),
+        opened: this.ev(),
     };
 
     getIndex(e: PluginStateSnapshotManager.Entry) {
@@ -242,11 +243,11 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
                 const snapshot = JSON.parse(data);
 
                 if (PluginStateSnapshotManager.isStateSnapshot(snapshot)) {
-                    return this.setStateSnapshot(snapshot);
+                    await this.setStateSnapshot(snapshot);
                 } else if (PluginStateSnapshotManager.isStateSnapshot(snapshot.data)) {
-                    return this.setStateSnapshot(snapshot.data);
+                    await this.setStateSnapshot(snapshot.data);
                 } else {
-                    this.plugin.state.setSnapshot(snapshot);
+                    await this.plugin.state.setSnapshot(snapshot);
                 }
             } else {
                 const data = await this.plugin.runTask(readFromFile(file, 'zip'));
@@ -270,8 +271,9 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
                 }
 
                 const snapshot = JSON.parse(stateData);
-                return this.setStateSnapshot(snapshot);
+                await this.setStateSnapshot(snapshot);
             }
+            this.events.opened.next(void 0);
         } catch (e) {
             console.error(e);
             this.plugin.log.error('Error reading state');

+ 2 - 0
src/mol-plugin-ui/controls/icons.tsx

@@ -172,6 +172,8 @@ const _VisibilityOutlined = <svg width='24px' height='24px' viewBox='0 0 24 24'>
 export function VisibilityOutlinedSvg() { return _VisibilityOutlined; }
 const _Warning = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z' /></svg>;
 export function WarningSvg() { return _Warning; }
+const _ContentCut = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M 9.64 7.64 c 0.23 -0.5 0.36 -1.05 0.36 -1.64 c 0 -2.21 -1.79 -4 -4 -4 S 2 3.79 2 6 s 1.79 4 4 4 c 0.59 0 1.14 -0.13 1.64 -0.36 L 10 12 l -2.36 2.36 C 7.14 14.13 6.59 14 6 14 c -2.21 0 -4 1.79 -4 4 s 1.79 4 4 4 s 4 -1.79 4 -4 c 0 -0.59 -0.13 -1.14 -0.36 -1.64 L 12 14 l 7 7 h 3 v -1 L 9.64 7.64 Z M 6 8 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 0 12 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 6 -7.5 c -0.28 0 -0.5 -0.22 -0.5 -0.5 s 0.22 -0.5 0.5 -0.5 s 0.5 0.22 0.5 0.5 s -0.22 0.5 -0.5 0.5 Z M 19 3 l -6 6 l 2 2 l 7 -7 V 3 Z' /></svg>;
+export function ContentCutSvg() { return _ContentCut; }
 
 // Aliases
 

+ 4 - 2
src/mol-repr/structure/visual/element-point.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -25,6 +25,7 @@ export const ElementPointParams = {
     ignoreHydrogens: PD.Boolean(false),
     ignoreHydrogensVariant: PD.Select('all', PD.arrayToOptions(['all', 'non-polar'] as const)),
     traceOnly: PD.Boolean(false),
+    stride: PD.Numeric(1, { min: 1, max: 100, step: 1 }),
 };
 export type ElementPointParams = typeof ElementPointParams
 
@@ -91,7 +92,8 @@ export function ElementPointVisual(materialId: number): UnitsVisual<ElementPoint
             state.createGeometry = (
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant ||
-                newProps.traceOnly !== currentProps.traceOnly
+                newProps.traceOnly !== currentProps.traceOnly ||
+                newProps.stride !== currentProps.stride
             );
         }
     }, materialId);

+ 6 - 3
src/mol-repr/structure/visual/element-sphere.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2023 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>
@@ -23,6 +23,7 @@ export const ElementSphereParams = {
     ignoreHydrogensVariant: PD.Select('all', PD.arrayToOptions(['all', 'non-polar'] as const)),
     traceOnly: PD.Boolean(false),
     tryUseImpostor: PD.Boolean(true),
+    stride: PD.Numeric(1, { min: 1, max: 100, step: 1 }),
 };
 export type ElementSphereParams = typeof ElementSphereParams
 
@@ -43,7 +44,8 @@ export function ElementSphereImpostorVisual(materialId: number): UnitsVisual<Ele
             state.createGeometry = (
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant ||
-                newProps.traceOnly !== currentProps.traceOnly
+                newProps.traceOnly !== currentProps.traceOnly ||
+                newProps.stride !== currentProps.stride
             );
         },
         mustRecreate: (structureGroup: StructureGroup, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
@@ -65,7 +67,8 @@ export function ElementSphereMeshVisual(materialId: number): UnitsVisual<Element
                 newProps.detail !== currentProps.detail ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant ||
-                newProps.traceOnly !== currentProps.traceOnly
+                newProps.traceOnly !== currentProps.traceOnly ||
+                newProps.stride !== currentProps.stride
             );
         },
         mustRecreate: (structureGroup: StructureGroup, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {

+ 8 - 3
src/mol-repr/structure/visual/util/element.ts

@@ -29,6 +29,7 @@ type ElementProps = {
     ignoreHydrogens: boolean,
     ignoreHydrogensVariant: 'all' | 'non-polar',
     traceOnly: boolean,
+    stride?: number
 }
 
 export type ElementSphereMeshProps = {
@@ -61,7 +62,7 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
     const childUnit = child?.unitMap.get(unit.id);
     if (child && !childUnit) return Mesh.createEmpty(mesh);
 
-    const { detail, sizeFactor } = props;
+    const { detail, sizeFactor, stride } = props;
 
     const { elements } = unit;
     const elementCount = elements.length;
@@ -78,6 +79,7 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
     let count = 0;
 
     for (let i = 0; i < elementCount; i++) {
+        if (stride && i % stride !== 0) continue;
         if (ignore && ignore(elements[i])) continue;
 
         pos(elements[i], v);
@@ -118,6 +120,8 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru
     const childUnit = child?.unitMap.get(unit.id);
     if (child && !childUnit) return Spheres.createEmpty(spheres);
 
+    const { sizeFactor, stride } = props;
+
     const { elements } = unit;
     const elementCount = elements.length;
     const builder = SpheresBuilder.create(elementCount, elementCount / 2, spheres);
@@ -132,8 +136,9 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru
     let maxSize = 0;
     let count = 0;
 
-    if (ignore || theme.size.granularity !== 'uniform') {
+    if ((stride && stride > 1) || ignore || theme.size.granularity !== 'uniform') {
         for (let i = 0; i < elementCount; i++) {
+            if (stride && i % stride !== 0) continue;
             if (ignore && ignore(elements[i])) continue;
 
             pos(elements[i], v);
@@ -165,7 +170,7 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru
     if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
         boundingSphere = oldBoundingSphere;
     } else {
-        boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * props.sizeFactor + 0.05);
+        boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * sizeFactor + 0.05);
     }
     s.setBoundingSphere(boundingSphere);
 

Some files were not shown because too many files changed in this diff