Parcourir la source

bond & interaction fixes

- add all inter-bond from index-pair (indexA >= indexB is handled by eachUnit loop)
- add inter contacts only once (not twice)
- allow same index contacts when between different units
- more granular each loci marking for interactions and bonds
- use bounding sphere from structure for inter bonds
Alexander Rose il y a 4 ans
Parent
commit
e6fd0202a6

+ 7 - 18
src/mol-model-props/computed/interactions/common.ts

@@ -109,24 +109,13 @@ class InteractionsInterContacts extends InterUnitGraph<number, Features.FeatureI
         };
 
         this.map.forEach(pairEdgesArray => {
-            pairEdgesArray.forEach(pairEdges => {
-                pairEdges.connectedIndices.forEach(indexA => {
-                    pairEdges.getEdges(indexA).forEach(edgeInfo => {
-                        const { unitA, unitB } = pairEdges;
-
-                        const { offsets: offsetsA, members: membersA } = unitsFeatures.get(unitA);
-                        for (let j = offsetsA[indexA], jl = offsetsA[indexA + 1]; j < jl; ++j) {
-                            add(membersA[j], unitA);
-                        }
-
-                        const { indexB } = edgeInfo;
-                        const { offsets: offsetsB, members: membersB } = unitsFeatures.get(unitB);
-                        for (let j = offsetsB[indexB], jl = offsetsB[indexB + 1]; j < jl; ++j) {
-                            add(membersB[j], unitB);
-                        }
-
-                        count += 1;
-                    });
+            pairEdgesArray.forEach(({ unitA, connectedIndices }) => {
+                connectedIndices.forEach(indexA => {
+                    const { offsets: offsetsA, members: membersA } = unitsFeatures.get(unitA);
+                    for (let j = offsetsA[indexA], jl = offsetsA[indexA + 1]; j < jl; ++j) {
+                        add(membersA[j], unitA);
+                    }
+                    count += 1;
                 });
             });
         });

+ 4 - 2
src/mol-model-props/computed/interactions/contacts.ts

@@ -38,11 +38,13 @@ export interface ContactTester {
 function validPair(structure: Structure, infoA: Features.Info, infoB: Features.Info): boolean {
     const indexA = infoA.members[infoA.offsets[infoA.feature]];
     const indexB = infoB.members[infoB.offsets[infoB.feature]];
-    if (indexA === indexB) return false; // no self interaction
+    if (indexA === indexB && infoA.unit === infoB.unit) return false; // no self interaction
+
     const altA = altLoc(infoA.unit, indexA);
     const altB = altLoc(infoB.unit, indexB);
     if (altA && altB && altA !== altB) return false; // incompatible alternate location id
-    if (infoA.unit.residueIndex[infoA.unit.elements[indexA]] === infoB.unit.residueIndex[infoB.unit.elements[indexB]]) return false; // same residue
+    if (infoA.unit.residueIndex[infoA.unit.elements[indexA]] === infoB.unit.residueIndex[infoB.unit.elements[indexB]] && infoA.unit === infoB.unit) return false; // same residue
+
     // e.g. no hbond if donor and acceptor are bonded
     if (connectedTo(structure, infoA.unit, indexA, infoB.unit, indexB)) return false;
 

+ 22 - 2
src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts

@@ -15,11 +15,12 @@ import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mo
 import { VisualUpdateState } from '../../../mol-repr/util';
 import { PickingId } from '../../../mol-geo/geometry/picking';
 import { EmptyLoci, Loci } from '../../../mol-model/loci';
-import { Interval } from '../../../mol-data/int';
+import { Interval, OrderedSet } 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 { Unit } from '../../../mol-model/structure/structure';
 
 const tmpLoc = StructureElement.Location.create(void 0);
 
@@ -114,7 +115,7 @@ function getInteractionLoci(pickingId: PickingId, structure: Structure, id: numb
     return EmptyLoci;
 }
 
-function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
+function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean) {
     let changed = false;
     if (Interactions.isLoci(loci)) {
         if (!Structure.areEquivalent(loci.data.structure, structure)) return false;
@@ -128,6 +129,25 @@ function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Int
                 if (apply(Interval.ofSingleton(idx))) changed = true;
             }
         }
+    } else if (StructureElement.Loci.is(loci)) {
+        if (!Structure.areEquivalent(loci.structure, structure)) return false;
+        if (isMarking && loci.elements.length === 1) return false; // only a single unit
+
+        const contacts = InteractionsProvider.get(structure).value?.contacts;
+        if (!contacts) return false;
+
+        // TODO when isMarking, all elements of contact features need to be in the loci
+        for (const e of loci.elements) {
+            const { unit } = e;
+            if (!Unit.isAtomic(unit)) continue;
+            if (isMarking && OrderedSet.size(e.indices) === 1) continue;
+
+            OrderedSet.forEach(e.indices, v => {
+                for (const idx of contacts.getContactIndicesForElement(v, unit)) {
+                    if (apply(Interval.ofSingleton(idx))) changed = true;
+                }
+            });
+        }
     }
     return changed;
 }

+ 31 - 2
src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts

@@ -7,7 +7,7 @@
 import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { Loci, EmptyLoci } from '../../../mol-model/loci';
-import { Interval } from '../../../mol-data/int';
+import { Interval, OrderedSet } from '../../../mol-data/int';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { PickingId } from '../../../mol-geo/geometry/picking';
@@ -101,7 +101,7 @@ function getInteractionLoci(pickingId: PickingId, structureGroup: StructureGroup
     return EmptyLoci;
 }
 
-function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
+function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean) {
     let changed = false;
     if (Interactions.isLoci(loci)) {
         const { structure, group } = structureGroup;
@@ -120,6 +120,35 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
                 }
             }
         }
+    } else if (StructureElement.Loci.is(loci)) {
+        const { structure, group } = structureGroup;
+        if (!Structure.areEquivalent(loci.structure, structure)) return false;
+
+        const interactions = InteractionsProvider.get(structure).value;
+        if (!interactions) return false;
+        const unit = group.units[0];
+        const contacts = interactions.unitsContacts.get(unit.id);
+        const features = interactions.unitsFeatures.get(unit.id);
+        const groupCount = contacts.edgeCount * 2;
+
+        const { offset } = contacts;
+        const { offsets: fOffsets, indices: fIndices } = features.elementsIndex;
+
+        // TODO when isMarking, all elements of contact features need to be in the loci
+        for (const e of loci.elements) {
+            const unitIdx = group.unitIndexMap.get(e.unit.id);
+            if (unitIdx !== undefined) continue;
+            if (isMarking && OrderedSet.size(e.indices) === 1) continue;
+
+            OrderedSet.forEach(e.indices, v => {
+                for (let i = fOffsets[v], il = fOffsets[v + 1]; i < il; ++i) {
+                    const fI = fIndices[i];
+                    for (let j = offset[fI], jl = offset[fI + 1]; j < jl; ++j) {
+                        if (apply(Interval.ofSingleton(unitIdx * groupCount + j))) changed = true;
+                    }
+                }
+            });
+        }
     }
     return changed;
 }

+ 0 - 1
src/mol-model/structure/structure/unit/bonds/inter-compute.ts

@@ -70,7 +70,6 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
             const { order, distance, flag } = indexPairs.edgeProps;
             for (let i = indexPairs.offset[aI], il = indexPairs.offset[aI + 1]; i < il; ++i) {
                 const bI = indexPairs.b[i];
-                if (aI >= bI) continue;
 
                 const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
                 if (_bI < 0) continue;

+ 7 - 1
src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts

@@ -16,6 +16,7 @@ import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../complex-
 import { VisualUpdateState } from '../../util';
 import { BondType } from '../../../mol-model/structure/model/types';
 import { BondCylinderParams, BondIterator, getInterBondLoci, eachInterBond, makeInterBondIgnoreTest } from './util/bond';
+import { Sphere3D } from '../../../mol-math/geometry';
 
 const tmpRefPosBondIt = new Bond.ElementBondIterator();
 function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, index: StructureElement.UnitIndex) {
@@ -116,7 +117,12 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
         ignore: makeInterBondIgnoreTest(structure, props)
     };
 
-    return createLinkCylinderMesh(ctx, builderProps, props, mesh);
+    const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
+
+    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
+    m.setBoundingSphere(sphere);
+
+    return m;
 }
 
 export const InterUnitBondCylinderParams = {

+ 7 - 1
src/mol-repr/structure/visual/bond-inter-unit-line.ts

@@ -16,6 +16,7 @@ import { VisualUpdateState } from '../../util';
 import { BondType } from '../../../mol-model/structure/model/types';
 import { BondIterator, getInterBondLoci, eachInterBond, BondLineParams, makeInterBondIgnoreTest } from './util/bond';
 import { Lines } from '../../../mol-geo/geometry/lines/lines';
+import { Sphere3D } from '../../../mol-math/geometry';
 
 const tmpRefPosBondIt = new Bond.ElementBondIterator();
 function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, index: StructureElement.UnitIndex) {
@@ -94,7 +95,12 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
         ignore: makeInterBondIgnoreTest(structure, props)
     };
 
-    return createLinkLines(ctx, builderProps, props, lines);
+    const l = createLinkLines(ctx, builderProps, props, lines);
+
+    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
+    l.setBoundingSphere(sphere);
+
+    return l;
 }
 
 export const InterUnitBondLineParams = {

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

@@ -213,7 +213,7 @@ export function eachInterBond(loci: Loci, structure: Structure, apply: (interval
         }
     } else if (StructureElement.Loci.is(loci)) {
         if (!Structure.areEquivalent(loci.structure, structure)) return false;
-        if (loci.elements.length === 1) return false; // only a single unit
+        if (isMarking && loci.elements.length === 1) return false; // only a single unit
 
         const map = new Map<number, OrderedSet<StructureElement.UnitIndex>>();
         for (const e of loci.elements) map.set(e.unit.id, e.indices);
@@ -223,11 +223,11 @@ export function eachInterBond(loci: Loci, structure: Structure, apply: (interval
             if (!Unit.isAtomic(unit)) continue;
             structure.interUnitBonds.getConnectedUnits(unit.id).forEach(b => {
                 const otherLociIndices = map.get(b.unitB);
-                if (otherLociIndices) {
+                if (!isMarking || otherLociIndices) {
                     OrderedSet.forEach(e.indices, v => {
                         if (!b.connectedIndices.includes(v)) return;
                         b.getEdges(v).forEach(bi => {
-                            if (!isMarking || OrderedSet.has(otherLociIndices, bi.indexB)) {
+                            if (!isMarking || (otherLociIndices && OrderedSet.has(otherLociIndices, bi.indexB))) {
                                 const idx = structure.interUnitBonds.getEdgeIndex(v, unit.id, bi.indexB, b.unitB);
                                 if (apply(Interval.ofSingleton(idx))) changed = true;
                             }