Browse Source

Merge branch 'master' into single-aromatic-dash-count

Alexander Rose 1 year ago
parent
commit
84aae8cf0a

+ 6 - 1
CHANGELOG.md

@@ -7,8 +7,12 @@ Note that since we don't clearly distinguish between a public and private interf
 ## [Unreleased]
 
 - Enable odd dash count (1,3,5)
+- Add principal axes spec and fix edge cases
 - Add a uniform color theme for NtC tube that still paints residue and segment dividers in a different color
-- Support points & lines in glTF export
+- Mesh exporter improvements
+    - Support points & lines in glTF export
+    - Set alphaMode and doubleSided in glTF export
+    - Fix flipped cylinder caps
 - Fix bond assignments `struct_conn` records referencing waters
 - Add StructConn extension providing functions for inspecting struct_conns
 - Fix `PluginState.setSnapshot` triggering unnecessary state updates
@@ -18,6 +22,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Parse HEADER record when reading PDB file
 - Support `ignoreHydrogens` in interactions representation
 - Add hydroxyproline (HYP) commonly present in collagen molecules to the list of amino acids
+- Fix assemblies for Archive PDB files (do not generate unique `label_asym_id` if `REMARK 350` is present)
 
 ## [v3.34.0] - 2023-04-16
 

+ 8 - 4
src/extensions/geo-export/glb-exporter.ts

@@ -170,8 +170,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
         return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
     }
 
-    private addMaterial(metalness: number, roughness: number) {
-        const hash = `${metalness}|${roughness}`;
+    private addMaterial(metalness: number, roughness: number, doubleSided: boolean, alpha: boolean) {
+        const hash = `${metalness}|${roughness}|${doubleSided}`;
         if (!this.materialMap.has(hash)) {
             this.materialMap.set(hash, this.materials.length);
             this.materials.push({
@@ -179,7 +179,9 @@ export class GlbExporter extends MeshExporter<GlbData> {
                     baseColorFactor: [1, 1, 1, 1],
                     metallicFactor: metalness,
                     roughnessFactor: roughness
-                }
+                },
+                doubleSided,
+                alphaMode: alpha ? 'BLEND' : 'OPAQUE',
             });
         }
         return this.materialMap.get(hash)!;
@@ -198,8 +200,10 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const instanceCount = values.uInstanceCount.ref.value;
         const metalness = values.uMetalness.ref.value;
         const roughness = values.uRoughness.ref.value;
+        const doubleSided = values.uDoubleSided?.ref.value || values.hasReflection.ref.value;
+        const alpha = values.uAlpha.ref.value < 1;
 
-        const material = this.addMaterial(metalness, roughness);
+        const material = this.addMaterial(metalness, roughness, doubleSided, alpha);
 
         let interpolatedColors: Uint8Array | undefined;
         if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) {

+ 12 - 3
src/extensions/geo-export/mesh-exporter.ts

@@ -29,11 +29,15 @@ import { unpackRGBToInt } from '../../mol-util/number-packing';
 import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
 import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
 import { assertUnreachable } from '../../mol-util/type-helpers';
+import { ValueCell } from '../../mol-util/value-cell';
 
 const GeoExportName = 'geo-export';
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
+const v3sub = Vec3.sub;
+const v3dot = Vec3.dot;
+const v3unitY = Vec3.unitY;
 
 type MeshMode = 'points' | 'lines' | 'triangles'
 
@@ -47,7 +51,7 @@ export interface AddMeshInput {
         drawCount: number
     } | undefined
     meshes: Mesh[] | undefined
-    values: BaseValues
+    values: BaseValues & { readonly uDoubleSided?: ValueCell<any> }
     isGeoTexture: boolean
     mode: MeshMode
     webgl: WebGLContext | undefined
@@ -509,6 +513,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
     private async addCylinders(values: CylindersValues, webgl: WebGLContext, ctx: RuntimeContext) {
         const start = Vec3();
         const end = Vec3();
+        const dir = Vec3();
 
         const aStart = values.aStart.ref.value;
         const aEnd = values.aEnd.ref.value;
@@ -546,12 +551,16 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
             for (let i = 0; i < vertexCount; i += 6) {
                 v3fromArray(start, aStart, i * 3);
                 v3fromArray(end, aEnd, i * 3);
+                v3sub(dir, end, start);
 
                 const group = aGroup[i];
                 const radius = MeshExporter.getSize(values, instanceIndex, group) * aScale[i];
                 const cap = aCap[i];
-                const topCap = cap === 1 || cap === 3;
-                const bottomCap = cap >= 2;
+                let topCap = cap === 1 || cap === 3;
+                let bottomCap = cap >= 2;
+                if (v3dot(v3unitY, dir) > 0) {
+                    [bottomCap, topCap] = [topCap, bottomCap];
+                }
                 const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments };
                 state.currentGroup = aGroup[i];
                 addCylinder(state, start, end, 1, cylinderProps);

+ 8 - 2
src/mol-math/linear-algebra/3d/vec3.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2022 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>
@@ -24,6 +24,8 @@ import { Mat3 } from './mat3';
 import { Quat } from './quat';
 import { EPSILON } from './common';
 
+const _isFinite = isFinite;
+
 export { ReadonlyVec3 };
 
 interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 }
@@ -48,6 +50,10 @@ namespace Vec3 {
         return out;
     }
 
+    export function isFinite(a: Vec3): boolean {
+        return _isFinite(a[0]) && _isFinite(a[1]) && _isFinite(a[2]);
+    }
+
     export function hasNaN(a: Vec3) {
         return isNaN(a[0]) || isNaN(a[1]) || isNaN(a[2]);
     }
@@ -636,4 +642,4 @@ namespace Vec3 {
     export const negUnitZ: ReadonlyVec3 = create(0, 0, -1);
 }
 
-export { Vec3 };
+export { Vec3 };

+ 19 - 0
src/mol-math/linear-algebra/_spec/principal-axes.spec.ts

@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Gianluca Tomasello <giagitom@gmail.com>
+ */
+
+import { NumberArray } from '../../../mol-util/type-helpers';
+import { PrincipalAxes } from '../matrix/principal-axes';
+
+describe('PrincipalAxes', () => {
+    it('same-cartesian-plane', () => {
+        const positions: NumberArray = [ // same y coordinate
+            0.1945, -0.0219, -0.0416,
+            -0.0219, -0.0219, -0.0119,
+        ];
+        const { origin } = PrincipalAxes.ofPositions(positions).boxAxes;
+        expect(origin[0] !== Infinity && origin[1] !== Infinity && origin[2] !== Infinity).toBe(true);
+    });
+});

+ 10 - 4
src/mol-math/linear-algebra/matrix/principal-axes.ts

@@ -1,7 +1,8 @@
 /**
- * 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>
+ * @author Gianluca Tomasello <giagitom@gmail.com>
  */
 
 import { Matrix } from './matrix';
@@ -123,12 +124,17 @@ namespace PrincipalAxes {
         const dirB = Vec3.setMagnitude(Vec3(), a.dirB, (d2a + d2b) / 2);
         const dirC = Vec3.setMagnitude(Vec3(), a.dirC, (d3a + d3b) / 2);
 
+        const okDirA = Vec3.isFinite(dirA);
+        const okDirB = Vec3.isFinite(dirB);
+        const okDirC = Vec3.isFinite(dirC);
+
         const origin = Vec3();
         const addCornerHelper = function (d1: number, d2: number, d3: number) {
             Vec3.copy(tmpBoxVec, center);
-            Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirA, d1);
-            Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirB, d2);
-            Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirC, d3);
+
+            if (okDirA) Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirA, d1);
+            if (okDirB) Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirB, d2);
+            if (okDirC) Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirC, d3);
             Vec3.add(origin, origin, tmpBoxVec);
         };
         addCornerHelper(d1a, d2a, d3a);

+ 5 - 3
src/mol-model-formats/structure/pdb/atom-site.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-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>
@@ -39,7 +39,7 @@ export function getAtomSiteTemplate(data: string, count: number) {
     };
 }
 
-export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
+export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>, options: { hasAssemblies: boolean }): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
     const pdbx_PDB_model_num = CifField.ofStrings(sites.pdbx_PDB_model_num);
     const auth_asym_id = CifField.ofTokens(sites.auth_asym_id);
     const auth_seq_id = CifField.ofTokens(sites.auth_seq_id);
@@ -87,7 +87,9 @@ export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): {
         if (asymIdCounts.has(asymId)) {
             // only change the chains name if there are TER records
             // otherwise assume repeated chain name use is from interleaved chains
-            if (terIndices.has(i)) {
+            // also don't change the chains name if there are assemblies
+            // as those require the original chain name
+            if (terIndices.has(i) && !options.hasAssemblies) {
                 const asymIdCount = asymIdCounts.get(asymId)! + 1;
                 asymIdCounts.set(asymId, asymIdCount);
                 currLabelAsymId = `${asymId}_${asymIdCount}`;

+ 4 - 2
src/mol-model-formats/structure/pdb/to-cif.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-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>
@@ -54,6 +54,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
 
     let modelNum = 0, modelStr = '';
     let conectRange: [number, number] | undefined = undefined;
+    let hasAssemblies = false;
     const terIndices = new Set<number>();
 
     for (let i = 0, _i = lines.count; i < _i; i++) {
@@ -152,6 +153,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
                     }
                     helperCategories.push(...parseRemark350(lines, i, j));
                     i = j - 1;
+                    hasAssemblies = true;
                 }
                 break;
             case 'S':
@@ -208,7 +210,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
         atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i));
     }
 
-    const atom_site = getAtomSite(atomSite, terIndices);
+    const atom_site = getAtomSite(atomSite, terIndices, { hasAssemblies });
     if (!isPdbqt) delete atom_site.partial_charge;
 
     if (conectRange) {