Преглед изворни кода

support ignoreHydrogens for interactions

Alexander Rose пре 1 година
родитељ
комит
f3a5369690

+ 2 - 0
CHANGELOG.md

@@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Support `ignoreHydrogens` in interactions representation
+
 ## [v3.34.0] - 2023-04-16
 
 - Avoid `renderMarkingDepth` for fully transparent renderables

+ 55 - 11
src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.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 Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -19,11 +19,13 @@ import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int';
 import { Interactions } from '../interactions/interactions';
 import { InteractionsProvider } from '../interactions';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
-import { InteractionFlag } from '../interactions/common';
+import { InteractionFlag, InteractionType } from '../interactions/common';
 import { Unit } from '../../../mol-model/structure/structure';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { assertUnreachable } from '../../../mol-util/type-helpers';
 import { InteractionsSharedParams } from './shared';
+import { eachBondedAtom } from '../chemistry/util';
+import { isHydrogen } from '../../../mol-repr/structure/visual/util/common';
 
 function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) {
     if (!structure.hasAtomic) return Mesh.createEmpty(mesh);
@@ -33,26 +35,66 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
     const { contacts, unitsFeatures } = interactions;
 
     const { edgeCount, edges } = contacts;
-    const { sizeFactor, parentDisplay } = props;
+    const { sizeFactor, ignoreHydrogens, ignoreHydrogensVariant, parentDisplay } = props;
 
     if (!edgeCount) return Mesh.createEmpty(mesh);
 
     const { child } = structure;
+    const p = Vec3();
+    const pA = Vec3();
+    const pB = Vec3();
 
     const builderProps = {
         linkCount: edgeCount,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
-            const { unitA, indexA, unitB, indexB } = edges[edgeIndex];
+            const { unitA, indexA, unitB, indexB, props: { type: t } } = edges[edgeIndex];
             const fA = unitsFeatures.get(unitA);
             const fB = unitsFeatures.get(unitB);
-            const uA = structure.unitMap.get(unitA);
-            const uB = structure.unitMap.get(unitB);
-
-            Vec3.set(posA, fA.x[indexA], fA.y[indexA], fA.z[indexA]);
-            Vec3.transformMat4(posA, posA, uA.conformation.operator.matrix);
+            const uA = structure.unitMap.get(unitA) as Unit.Atomic;
+            const uB = structure.unitMap.get(unitB) as Unit.Atomic;
+
+            if ((!ignoreHydrogens || ignoreHydrogensVariant !== 'all') && (
+                t === InteractionType.HydrogenBond || t === InteractionType.WeakHydrogenBond)
+            ) {
+                const idxA = fA.members[fA.offsets[indexA]];
+                const idxB = fB.members[fB.offsets[indexB]];
+                uA.conformation.position(uA.elements[idxA], pA);
+                uB.conformation.position(uB.elements[idxB], pB);
+                let minDistA = Vec3.distance(pA, pB);
+                let minDistB = minDistA;
+                Vec3.copy(posA, pA);
+                Vec3.copy(posB, pB);
+
+                eachBondedAtom(structure, uA, idxA, (u, idx) => {
+                    const eI = u.elements[idx];
+                    if (isHydrogen(structure, u, eI, 'polar')) {
+                        u.conformation.position(eI, p);
+                        const dist = Vec3.distance(p, pB);
+                        if (dist < minDistA) {
+                            minDistA = dist;
+                            Vec3.copy(posA, p);
+                        }
+                    }
+                });
+
+                eachBondedAtom(structure, uB, idxB, (u, idx) => {
+                    const eI = u.elements[idx];
+                    if (isHydrogen(structure, u, eI, 'polar')) {
+                        u.conformation.position(eI, p);
+                        const dist = Vec3.distance(p, pA);
+                        if (dist < minDistB) {
+                            minDistB = dist;
+                            Vec3.copy(posB, p);
+                        }
+                    }
+                });
+            } else {
+                Vec3.set(posA, fA.x[indexA], fA.y[indexA], fA.z[indexA]);
+                Vec3.transformMat4(posA, posA, uA.conformation.operator.matrix);
 
-            Vec3.set(posB, fB.x[indexB], fB.y[indexB], fB.z[indexB]);
-            Vec3.transformMat4(posB, posB, uB.conformation.operator.matrix);
+                Vec3.set(posB, fB.x[indexB], fB.y[indexB], fB.z[indexB]);
+                Vec3.transformMat4(posB, posB, uB.conformation.operator.matrix);
+            }
         },
         style: (edgeIndex: number) => LinkStyle.Dashed,
         radius: (edgeIndex: number) => {
@@ -155,6 +197,8 @@ export function InteractionsInterUnitVisual(materialId: number): ComplexVisual<I
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.radialSegments !== currentProps.radialSegments ||
+                newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
+                newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant ||
                 newProps.parentDisplay !== currentProps.parentDisplay
             );
 

+ 57 - 11
src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.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 Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -21,9 +21,11 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator';
 import { Interactions } from '../interactions/interactions';
 import { InteractionFlag } from '../interactions/common';
 import { Sphere3D } from '../../../mol-math/geometry';
-import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
+import { StructureGroup, isHydrogen } from '../../../mol-repr/structure/visual/util/common';
 import { assertUnreachable } from '../../../mol-util/type-helpers';
 import { InteractionsSharedParams } from './shared';
+import { InteractionType } from '../interactions/common';
+import { eachIntraBondedAtom } from '../chemistry/util';
 
 async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
@@ -39,22 +41,64 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
     const contacts = interactions.unitsContacts.get(unit.id);
 
     const { x, y, z, members, offsets } = features;
-    const { edgeCount, a, b, edgeProps: { flag } } = contacts;
-    const { sizeFactor, parentDisplay } = props;
+    const { edgeCount, a, b, edgeProps: { flag, type } } = contacts;
+    const { sizeFactor, ignoreHydrogens, ignoreHydrogensVariant, parentDisplay } = props;
 
     if (!edgeCount) return Mesh.createEmpty(mesh);
 
+    const pos = unit.conformation.invariantPosition;
+    const { elements } = unit;
+    const p = Vec3();
+    const pA = Vec3();
+    const pB = Vec3();
+
     const builderProps = {
         linkCount: edgeCount * 2,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
-            Vec3.set(posA, x[a[edgeIndex]], y[a[edgeIndex]], z[a[edgeIndex]]);
-            Vec3.set(posB, x[b[edgeIndex]], y[b[edgeIndex]], z[b[edgeIndex]]);
+            const t = type[edgeIndex];
+            if ((!ignoreHydrogens || ignoreHydrogensVariant !== 'all') && (
+                t === InteractionType.HydrogenBond || t === InteractionType.WeakHydrogenBond)
+            ) {
+                const idxA = members[offsets[a[edgeIndex]]];
+                const idxB = members[offsets[b[edgeIndex]]];
+                pos(elements[idxA], pA);
+                pos(elements[idxB], pB);
+                let minDistA = Vec3.distance(pA, pB);
+                let minDistB = minDistA;
+                Vec3.copy(posA, pA);
+                Vec3.copy(posB, pB);
+
+                eachIntraBondedAtom(unit, idxA, (_, idx) => {
+                    if (isHydrogen(structure, unit, elements[idx], 'polar')) {
+                        pos(elements[idx], p);
+                        const dist = Vec3.distance(p, pB);
+                        if (dist < minDistA) {
+                            minDistA = dist;
+                            Vec3.copy(posA, p);
+                        }
+                    }
+                });
+
+                eachIntraBondedAtom(unit, idxB, (_, idx) => {
+                    if (isHydrogen(structure, unit, elements[idx], 'polar')) {
+                        pos(elements[idx], p);
+                        const dist = Vec3.distance(p, pA);
+                        if (dist < minDistB) {
+                            minDistB = dist;
+                            Vec3.copy(posB, p);
+                        }
+                    }
+                });
+            } else {
+                Vec3.set(posA, x[a[edgeIndex]], y[a[edgeIndex]], z[a[edgeIndex]]);
+                Vec3.set(posB, x[b[edgeIndex]], y[b[edgeIndex]], z[b[edgeIndex]]);
+            }
         },
         style: (edgeIndex: number) => LinkStyle.Dashed,
         radius: (edgeIndex: number) => {
-            location.element = unit.elements[members[offsets[a[edgeIndex]]]];
+            location.element = elements[members[offsets[a[edgeIndex]]]];
             const sizeA = theme.size.size(location);
-            location.element = unit.elements[members[offsets[b[edgeIndex]]]];
+            location.element = elements[members[offsets[b[edgeIndex]]]];
             const sizeB = theme.size.size(location);
             return Math.min(sizeA, sizeB) * sizeFactor;
         },
@@ -65,7 +109,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
                 if (parentDisplay === 'stub') {
                     const f = a[edgeIndex];
                     for (let i = offsets[f], il = offsets[f + 1]; i < il; ++i) {
-                        const e = unit.elements[members[offsets[i]]];
+                        const e = elements[members[offsets[i]]];
                         if (!SortedArray.has(childUnit.elements, e)) return true;
                     }
                 } else if (parentDisplay === 'full' || parentDisplay === 'between') {
@@ -74,13 +118,13 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
 
                     const fA = a[edgeIndex];
                     for (let i = offsets[fA], il = offsets[fA + 1]; i < il; ++i) {
-                        const eA = unit.elements[members[offsets[i]]];
+                        const eA = elements[members[offsets[i]]];
                         if (!SortedArray.has(childUnit.elements, eA)) flagA = true;
                     }
 
                     const fB = b[edgeIndex];
                     for (let i = offsets[fB], il = offsets[fB + 1]; i < il; ++i) {
-                        const eB = unit.elements[members[offsets[i]]];
+                        const eB = elements[members[offsets[i]]];
                         if (!SortedArray.has(childUnit.elements, eB)) flagB = true;
                     }
 
@@ -127,6 +171,8 @@ export function InteractionsIntraUnitVisual(materialId: number): UnitsVisual<Int
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.radialSegments !== currentProps.radialSegments ||
+                newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
+                newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant ||
                 newProps.parentDisplay !== currentProps.parentDisplay
             );
 

+ 3 - 1
src/mol-model-props/computed/representations/shared.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -10,6 +10,8 @@ export const InteractionsSharedParams = {
     sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
     dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
     dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
+    ignoreHydrogens: PD.Boolean(false),
+    ignoreHydrogensVariant: PD.Select('all', PD.arrayToOptions(['all', 'non-polar'] as const)),
     includeParent: PD.Boolean(false),
     parentDisplay: PD.Select('stub', PD.arrayToOptions(['stub', 'full', 'between'] as const), { description: 'Only has an effect when "includeParent" is enabled. "Stub" shows just the child side of interactions to the parent. "Full" shows both sides of interactions to the parent. "Between" shows only interactions to the parent.' }),
 };

+ 8 - 6
src/mol-repr/structure/visual/util/common.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>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -314,12 +314,14 @@ export function getStructureConformationAndRadius(structure: Structure, sizeThem
 }
 
 const _H = AtomicNumbers['H'];
-export function isHydrogen(structure: Structure, unit: Unit, element: ElementIndex, variant: 'all' | 'non-polar') {
+export function isHydrogen(structure: Structure, unit: Unit, element: ElementIndex, variant: 'all' | 'non-polar' | 'polar') {
     if (Unit.isCoarse(unit)) return false;
-    return (
-        unit.model.atomicHierarchy.derived.atom.atomicNumber[element] === _H &&
-        (variant === 'all' || !hasPolarNeighbour(structure, unit, SortedArray.indexOf(unit.elements, element) as StructureElement.UnitIndex))
-    );
+    if (unit.model.atomicHierarchy.derived.atom.atomicNumber[element] !== _H) return false;
+    if (variant === 'all') return true;
+    const polar = hasPolarNeighbour(structure, unit, SortedArray.indexOf(unit.elements, element) as StructureElement.UnitIndex);
+    if (polar && variant === 'polar') return true;
+    if (!polar && variant === 'non-polar') return true;
+    return false;
 }
 export function isH(atomicNumber: ArrayLike<number>, element: ElementIndex) {
     return atomicNumber[element] === _H;