diff --git a/src/mol-model-props/computed/interactions/interactions.ts b/src/mol-model-props/computed/interactions/interactions.ts index 5c9083104..ef41fc70f 100644 --- a/src/mol-model-props/computed/interactions/interactions.ts +++ b/src/mol-model-props/computed/interactions/interactions.ts @@ -6,16 +6,16 @@ */ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; -import { Structure, Unit, Bond } from '../../../mol-model/structure'; +import { Structure, Unit, Bond, StructureElement } from '../../../mol-model/structure'; import { Features, FeaturesBuilder } from './features'; import { ValenceModelProvider } from '../valence-model'; -import { InteractionsIntraContacts, InteractionsInterContacts, FeatureType, interactionTypeLabel } from './common'; +import { InteractionsIntraContacts, InteractionsInterContacts, FeatureType, InteractionType, InteractionFlag, interactionTypeLabel } from './common'; import { IntraContactsBuilder, InterContactsBuilder } from './contacts-builder'; -import { IntMap } from '../../../mol-data/int'; +import { IntMap, OrderedSet } from '../../../mol-data/int'; import { addUnitContacts, ContactTester, addStructureContacts, ContactsParams, ContactsProps } from './contacts'; import { HalogenDonorProvider, HalogenAcceptorProvider, HalogenBondsProvider } from './halogen-bonds'; import { HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider, HydrogenBondsProvider, WeakHydrogenBondsProvider } from './hydrogen-bonds'; -import { WaterBridgesProvider, WaterBridgeContact } from './water-bridges'; +import { WaterBridgesProvider } from './water-bridges'; import { NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, IonicProvider, PiStackingProvider, CationPiProvider } from './charged'; import { HydrophobicAtomProvider, HydrophobicProvider } from './hydrophobic'; import { SetUtils } from '../../../mol-util/set'; @@ -26,13 +26,25 @@ import { DataLocation } from '../../../mol-model/location'; import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper'; import { Sphere3D } from '../../../mol-math/geometry'; import { DataLoci } from '../../../mol-model/loci'; -import { bondLabel, LabelGranularity } from '../../../mol-theme/label'; +import { bondLabel, bundleLabel, LabelGranularity } from '../../../mol-theme/label'; import { ObjectKeys } from '../../../mol-util/type-helpers'; -export { Interactions }; +export { Interactions, Bridges }; export type { BridgeContact, BridgeContacts }; -type BridgeContact = WaterBridgeContact +interface BridgeContact { + readonly unitA: number + readonly indexA: Features.FeatureIndex + readonly unitB: number + readonly indexB: Features.FeatureIndex + /** mediator unit id */ + readonly unitM: number + /** mediator feature facing endpoint A */ + readonly indexMA: Features.FeatureIndex + /** mediator feature facing endpoint B */ + readonly indexMB: Features.FeatureIndex + props: { type: InteractionType, flag: InteractionFlag } +} type BridgeContacts = ReadonlyArray interface Interactions { @@ -136,6 +148,93 @@ namespace Interactions { } } +namespace Bridges { + export interface Data { + readonly structure: Structure + readonly bridges: BridgeContacts + readonly unitsFeatures: IntMap + } + + export interface Element { bridgeIndex: number } + + export interface Location extends DataLocation {} + + export function Location(data: Data, bridgeIndex = 0): Location { + return DataLocation('bridges', data, { bridgeIndex }); + } + + export function isLocation(x: any): x is Location { + return !!x && x.kind === 'data-location' && x.tag === 'bridges'; + } + + export interface Loci extends DataLoci {} + + export function Loci(data: Data, elements: ReadonlyArray): Loci { + return DataLoci('bridges', data, elements, + bs => getBoundingSphere(data, elements, bs), + () => getLabel(data, elements)); + } + + export function isLoci(x: any): x is Loci { + return !!x && x.kind === 'data-loci' && x.tag === 'bridges'; + } + + function getLabel(data: Data, elements: ReadonlyArray): string { + const e = elements[0]; + if (e === undefined) return ''; + + const { structure, bridges, unitsFeatures } = data; + const bridge = bridges[e.bridgeIndex]; + + const uA = structure.unitMap.get(bridge.unitA) as Unit.Atomic; + const fA = unitsFeatures.get(bridge.unitA); + const uM = structure.unitMap.get(bridge.unitM) as Unit.Atomic; + const fM = unitsFeatures.get(bridge.unitM); + const uB = structure.unitMap.get(bridge.unitB) as Unit.Atomic; + const fB = unitsFeatures.get(bridge.unitB); + + const options = { granularity: 'element' as LabelGranularity }; + if (fA.offsets[bridge.indexA + 1] - fA.offsets[bridge.indexA] > 1 || + fB.offsets[bridge.indexB + 1] - fB.offsets[bridge.indexB] > 1) { + options.granularity = 'residue'; + } + + return [ + interactionTypeLabel(bridge.props.type), + bundleLabel({ loci: [ + StructureElement.Loci(structure, [{ unit: uA, indices: OrderedSet.ofSingleton(fA.members[fA.offsets[bridge.indexA]] as StructureElement.UnitIndex) }]), + StructureElement.Loci(structure, [{ unit: uM, indices: OrderedSet.ofSingleton(fM.members[fM.offsets[bridge.indexMA]] as StructureElement.UnitIndex) }]), + StructureElement.Loci(structure, [{ unit: uB, indices: OrderedSet.ofSingleton(fB.members[fB.offsets[bridge.indexB]] as StructureElement.UnitIndex) }]), + ] }, options), + ].join('
'); + } + + function getBoundingSphere(data: Data, elements: ReadonlyArray, boundingSphere: Sphere3D) { + return CentroidHelper.fromPairProvider(elements.length * 2, (i, pA, pB) => { + const bridge = data.bridges[elements[i >> 1].bridgeIndex]; + + const uA = data.structure.unitMap.get(bridge.unitA) as Unit.Atomic; + const fA = data.unitsFeatures.get(bridge.unitA); + const uM = data.structure.unitMap.get(bridge.unitM) as Unit.Atomic; + const fM = data.unitsFeatures.get(bridge.unitM); + const uB = data.structure.unitMap.get(bridge.unitB) as Unit.Atomic; + const fB = data.unitsFeatures.get(bridge.unitB); + + const aIdx = fA.members[fA.offsets[bridge.indexA]]; + const mIdx = fM.members[fM.offsets[bridge.indexMA]]; + const bIdx = fB.members[fB.offsets[bridge.indexB]]; + + if ((i & 1) === 0) { + uA.conformation.position(uA.elements[aIdx], pA); + uM.conformation.position(uM.elements[mIdx], pB); + } else { + uM.conformation.position(uM.elements[mIdx], pA); + uB.conformation.position(uB.elements[bIdx], pB); + } + }, boundingSphere); + } +} + const FeatureProviders = [ HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider, NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, diff --git a/src/mol-model-props/computed/interactions/refine.ts b/src/mol-model-props/computed/interactions/refine.ts index c0a18d98f..37107754e 100644 --- a/src/mol-model-props/computed/interactions/refine.ts +++ b/src/mol-model-props/computed/interactions/refine.ts @@ -355,21 +355,21 @@ function waterBridgeRefiner(_structure: Structure, interactions: Interactions): if (wb.props.type !== InteractionType.WaterBridge) continue; const fA = unitsFeatures.get(wb.unitA); - const fW = unitsFeatures.get(wb.unitW); + const fM = unitsFeatures.get(wb.unitM); const fB = unitsFeatures.get(wb.unitB); - if (!fA || !fW || !fB) continue; + if (!fA || !fM || !fB) continue; const atomA = featureMember(fA, wb.indexA); - const atomWA = featureMember(fW, wb.indexWA); - const atomWD = featureMember(fW, wb.indexWD); + const atomMA = featureMember(fM, wb.indexMA); + const atomMB = featureMember(fM, wb.indexMB); const atomB = featureMember(fB, wb.indexB); // donor atom ↔ water oxygen - addAtomPair(bridgeLegs, wb.unitA, atomA, wb.unitW, atomWA); + addAtomPair(bridgeLegs, wb.unitA, atomA, wb.unitM, atomMA); // water oxygen ↔ acceptor atom - addAtomPair(bridgeLegs, wb.unitW, atomWD, wb.unitB, atomB); + addAtomPair(bridgeLegs, wb.unitM, atomMB, wb.unitB, atomB); } let intraContacts: InteractionsIntraContacts | undefined; diff --git a/src/mol-model-props/computed/interactions/water-bridges.ts b/src/mol-model-props/computed/interactions/water-bridges.ts index 2c99e343f..616599063 100644 --- a/src/mol-model-props/computed/interactions/water-bridges.ts +++ b/src/mol-model-props/computed/interactions/water-bridges.ts @@ -5,20 +5,15 @@ */ import { Structure, Unit, StructureElement } from '../../../mol-model/structure'; -import { IntMap, OrderedSet } from '../../../mol-data/int'; +import { IntMap } from '../../../mol-data/int'; import { Vec3 } from '../../../mol-math/linear-algebra'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; -import { DataLocation } from '../../../mol-model/location'; -import { DataLoci } from '../../../mol-model/loci'; import { MoleculeType, NucleicBackboneAtoms, ProteinBackboneAtoms } from '../../../mol-model/structure/model/types'; import { StructureLookup3DResultContext } from '../../../mol-model/structure/structure/util/lookup3d'; -import { Sphere3D } from '../../../mol-math/geometry'; -import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper'; import { Features } from './features'; import { FeatureType, InteractionType, InteractionFlag } from './common'; import { GeometryOptions, checkGeometry } from './hydrogen-bonds'; import { degToRad } from '../../../mol-math/misc'; -import { bundleLabel, LabelGranularity } from '../../../mol-theme/label'; import { cantorPairing } from '../../../mol-data/util/hash-functions'; export type { WaterBridgeContact, WaterBridgeContacts }; @@ -33,11 +28,11 @@ interface WaterBridgeContact { /** acceptor feature index in unitB */ readonly indexB: Features.FeatureIndex /** bridging water unit id */ - readonly unitW: number + readonly unitM: number /** water oxygen as HydrogenAcceptor (leg: donor → water) */ - readonly indexWA: Features.FeatureIndex + readonly indexMA: Features.FeatureIndex /** water oxygen as HydrogenDonor (leg: water → acceptor) */ - readonly indexWD: Features.FeatureIndex + readonly indexMB: Features.FeatureIndex props: { type: InteractionType.WaterBridge, flag: InteractionFlag } } @@ -319,9 +314,9 @@ export function findWaterBridgeContacts( indexA: don.featureIdx, unitB: acc.unit.id, indexB: acc.featureIdx, - unitW: unitW.id, - indexWA: accFW, - indexWD: donFW, + unitM: unitW.id, + indexMA: accFW, + indexMB: donFW, props: { type: InteractionType.WaterBridge, flag: InteractionFlag.None }, }, combinedDistSq, @@ -334,98 +329,3 @@ export function findWaterBridgeContacts( return bestBridgeValues(best).map(e => e.contact); } - -// --------------------------------------------------------------------------- -// Location / Loci for use by the renderer and color theme. -// --------------------------------------------------------------------------- - -export { WaterBridges }; - -namespace WaterBridges { - export interface Data { - readonly structure: Structure - readonly waterBridges: WaterBridgeContacts - readonly unitsFeatures: IntMap - } - - export interface Element { bridgeIndex: number } - - export interface Location extends DataLocation {} - - export function Location(data: Data, bridgeIndex = 0): Location { - return DataLocation('water-bridges', data, { bridgeIndex }); - } - - export function isLocation(x: any): x is Location { - return !!x && x.kind === 'data-location' && x.tag === 'water-bridges'; - } - - export interface Loci extends DataLoci {} - - export function Loci(data: Data, elements: ReadonlyArray): Loci { - return DataLoci('water-bridges', data, elements, - bs => getBoundingSphere(data, elements, bs), - () => getLabel(data, elements)); - } - - function getLabel(data: Data, elements: ReadonlyArray): string { - const e = elements[0]; - if (e === undefined) return ''; - - const { structure, waterBridges, unitsFeatures } = data; - const wb = waterBridges[e.bridgeIndex]; - - const uA = structure.unitMap.get(wb.unitA) as Unit.Atomic; - const fA = unitsFeatures.get(wb.unitA); - const uW = structure.unitMap.get(wb.unitW) as Unit.Atomic; - const fW = unitsFeatures.get(wb.unitW); - const uB = structure.unitMap.get(wb.unitB) as Unit.Atomic; - const fB = unitsFeatures.get(wb.unitB); - - const options = { granularity: 'element' as LabelGranularity }; - if (fA.offsets[wb.indexA + 1] - fA.offsets[wb.indexA] > 1 || - fB.offsets[wb.indexB + 1] - fB.offsets[wb.indexB] > 1) { - options.granularity = 'residue'; - } - - return [ - 'Water Bridge', - bundleLabel({ loci: [ - StructureElement.Loci(structure, [{ unit: uA, indices: OrderedSet.ofSingleton(fA.members[fA.offsets[wb.indexA]] as StructureElement.UnitIndex) }]), - StructureElement.Loci(structure, [{ unit: uW, indices: OrderedSet.ofSingleton(fW.members[fW.offsets[wb.indexWA]] as StructureElement.UnitIndex) }]), - StructureElement.Loci(structure, [{ unit: uB, indices: OrderedSet.ofSingleton(fB.members[fB.offsets[wb.indexB]] as StructureElement.UnitIndex) }]), - ] }, options), - ].join('
'); - } - - export function isLoci(x: any): x is Loci { - return !!x && x.kind === 'data-loci' && x.tag === 'water-bridges'; - } - - function getBoundingSphere(data: Data, elements: ReadonlyArray, boundingSphere: Sphere3D) { - return CentroidHelper.fromPairProvider(elements.length * 2, (i, pA, pB) => { - const wb = data.waterBridges[elements[i >> 1].bridgeIndex]; - - const uA = data.structure.unitMap.get(wb.unitA) as Unit.Atomic; - const fA = data.unitsFeatures.get(wb.unitA); - - const uW = data.structure.unitMap.get(wb.unitW) as Unit.Atomic; - const fW = data.unitsFeatures.get(wb.unitW); - - const uB = data.structure.unitMap.get(wb.unitB) as Unit.Atomic; - const fB = data.unitsFeatures.get(wb.unitB); - - const aIdx = fA.members[fA.offsets[wb.indexA]] as StructureElement.UnitIndex; - const wIdx = fW.members[fW.offsets[wb.indexWA]] as StructureElement.UnitIndex; - const bIdx = fB.members[fB.offsets[wb.indexB]] as StructureElement.UnitIndex; - - if ((i & 1) === 0) { - uA.conformation.position(uA.elements[aIdx], pA); - uW.conformation.position(uW.elements[wIdx], pB); - } else { - uW.conformation.position(uW.elements[wIdx], pA); - uB.conformation.position(uB.elements[bIdx], pB); - } - }, boundingSphere); - } -} \ No newline at end of file diff --git a/src/mol-model-props/computed/representations/interactions-water-bridge-cylinder.ts b/src/mol-model-props/computed/representations/interactions-bridge-cylinder.ts similarity index 51% rename from src/mol-model-props/computed/representations/interactions-water-bridge-cylinder.ts rename to src/mol-model-props/computed/representations/interactions-bridge-cylinder.ts index 3b66bdbca..0d428d66c 100644 --- a/src/mol-model-props/computed/representations/interactions-water-bridge-cylinder.ts +++ b/src/mol-model-props/computed/representations/interactions-bridge-cylinder.ts @@ -19,55 +19,52 @@ import { NullLocation } from '../../../mol-model/location'; import { Interval, OrderedSet } from '../../../mol-data/int'; import { InteractionsProvider } from '../interactions'; import { LocationIterator } from '../../../mol-geo/util/location-iterator'; -import { WaterBridges, WaterBridgeContact } from '../interactions/water-bridges'; -import { InteractionType } from '../interactions/common'; +import { BridgeContacts, Bridges } from '../interactions/interactions'; import { Sphere3D } from '../../../mol-math/geometry'; import { InteractionsSharedParams } from './shared'; import { Features } from '../interactions/features'; -type WaterBridgeContacts = WaterBridges.Data['waterBridges']; - type CanonicalLegIndices = { - donor: Int32Array - acceptor: Int32Array + endpointA: Int32Array + endpointB: Int32Array }; -const CanonicalLegIndicesCache = new WeakMap(); +const CanonicalLegIndicesCache = new WeakMap(); -function getCanonicalLegIndices(waterBridges: WaterBridgeContacts): CanonicalLegIndices { - const cached = CanonicalLegIndicesCache.get(waterBridges); +function getCanonicalLegIndices(bridges: BridgeContacts): CanonicalLegIndices { + const cached = CanonicalLegIndicesCache.get(bridges); if (cached) return cached; - const n = waterBridges.length; - const donor = new Int32Array(n); - const acceptor = new Int32Array(n); + const n = bridges.length; + const endpointA = new Int32Array(n); + const endpointB = new Int32Array(n); - const donorLegs = new Map(); - const acceptorLegs = new Map(); + const legA = new Map(); + const legB = new Map(); for (let i = 0; i < n; i++) { - const wb = waterBridges[i]; + const b = bridges[i]; - const dk = `${wb.unitA}|${wb.indexA}|${wb.unitW}|${wb.indexWA}`; - const ak = `${wb.unitW}|${wb.indexWD}|${wb.unitB}|${wb.indexB}`; + const kA = `${b.unitA}|${b.indexA}|${b.unitM}|${b.indexMA}`; + const kB = `${b.unitM}|${b.indexMB}|${b.unitB}|${b.indexB}`; - let di = donorLegs.get(dk); - if (di === undefined) { - di = i; - donorLegs.set(dk, i); - } - donor[i] = di; - - let ai = acceptorLegs.get(ak); + let ai = legA.get(kA); if (ai === undefined) { ai = i; - acceptorLegs.set(ak, i); + legA.set(kA, i); } - acceptor[i] = ai; + endpointA[i] = ai; + + let bi = legB.get(kB); + if (bi === undefined) { + bi = i; + legB.set(kB, i); + } + endpointB[i] = bi; } - const indices = { donor, acceptor }; - CanonicalLegIndicesCache.set(waterBridges, indices); + const indices = { endpointA, endpointB }; + CanonicalLegIndicesCache.set(bridges, indices); return indices; } @@ -94,14 +91,14 @@ function setFeatureLocation( location.element = unit.elements[atomLocalIdx]; } -function applyDonorLeg( +function applyLegA( bridgeIndex: number, bridgeCount: number, canonical: CanonicalLegIndices, apply: (interval: Interval) => boolean ) { let changed = false; - const i = canonical.donor[bridgeIndex]; + const i = canonical.endpointA[bridgeIndex]; if (apply(Interval.ofSingleton(i))) changed = true; if (apply(Interval.ofSingleton(i + bridgeCount))) changed = true; @@ -109,14 +106,14 @@ function applyDonorLeg( return changed; } -function applyAcceptorLeg( +function applyLegB( bridgeIndex: number, bridgeCount: number, canonical: CanonicalLegIndices, apply: (interval: Interval) => boolean ) { let changed = false; - const i = canonical.acceptor[bridgeIndex]; + const i = canonical.endpointB[bridgeIndex]; if (apply(Interval.ofSingleton(i + 2 * bridgeCount))) changed = true; if (apply(Interval.ofSingleton(i + 3 * bridgeCount))) changed = true; @@ -124,16 +121,15 @@ function applyAcceptorLeg( return changed; } -function createWaterBridgeCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values, mesh?: Mesh) { +function createBridgeCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values, mesh?: Mesh) { if (!structure.hasAtomic) return Mesh.createEmpty(mesh); const interactions = InteractionsProvider.get(structure).value; if (!interactions) return Mesh.createEmpty(mesh); const { bridges, unitsFeatures } = interactions; - const waterBridges = bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge); - const n = waterBridges.length; + const n = bridges.length; if (!n) return Mesh.createEmpty(mesh); const l = StructureElement.Location.create(structure); @@ -142,46 +138,41 @@ function createWaterBridgeCylinderMesh(ctx: VisualContext, structure: Structure, const builderProps = { // Four half-cylinders per bridge; createLinkCylinderMesh draws the A-side half per call: - // [0, n): donor→water, forward (donor side) - // [n, 2n): donor→water, backward (water side) - // [2n, 3n): water→acceptor, forward (water side) - // [3n, 4n): water→acceptor, backward (acceptor side) + // [0, n): A→mediator, forward (A side) + // [n, 2n): A→mediator, backward (mediator side) + // [2n, 3n): mediator→B, forward (mediator side) + // [3n, 4n): mediator→B, backward (B side) // // When multiple bridges share the same physical leg, only the first - // occurrence is drawn. Marking later maps duplicate legs back to the - // canonical drawn edge index. + // occurrence is drawn; later ones map back to the canonical edge index. linkCount: 4 * n, position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { - const wb = waterBridges[edgeIndex % n]; - const uW = structure.unitMap.get(wb.unitW) as Unit.Atomic; - const fW = unitsFeatures.get(wb.unitW); + const b = bridges[edgeIndex % n]; + const uM = structure.unitMap.get(b.unitM) as Unit.Atomic; + const fM = unitsFeatures.get(b.unitM); const leg = Math.floor(edgeIndex / n); if (leg === 0) { - // donor→water, A-side: draw donor→mid - const uA = structure.unitMap.get(wb.unitA) as Unit.Atomic; - const fA = unitsFeatures.get(wb.unitA); - atomPosition(uA, fA, wb.indexA, posA); - atomPosition(uW, fW, wb.indexWA, posB); + const uA = structure.unitMap.get(b.unitA) as Unit.Atomic; + const fA = unitsFeatures.get(b.unitA); + atomPosition(uA, fA, b.indexA, posA); + atomPosition(uM, fM, b.indexMA, posB); } else if (leg === 1) { - // donor→water, B-side: draw water→mid - const uA = structure.unitMap.get(wb.unitA) as Unit.Atomic; - const fA = unitsFeatures.get(wb.unitA); - atomPosition(uW, fW, wb.indexWA, posA); - atomPosition(uA, fA, wb.indexA, posB); + const uA = structure.unitMap.get(b.unitA) as Unit.Atomic; + const fA = unitsFeatures.get(b.unitA); + atomPosition(uM, fM, b.indexMA, posA); + atomPosition(uA, fA, b.indexA, posB); } else if (leg === 2) { - // water→acceptor, A-side: draw water→mid - const uB = structure.unitMap.get(wb.unitB) as Unit.Atomic; - const fB = unitsFeatures.get(wb.unitB); - atomPosition(uW, fW, wb.indexWD, posA); - atomPosition(uB, fB, wb.indexB, posB); + const uB = structure.unitMap.get(b.unitB) as Unit.Atomic; + const fB = unitsFeatures.get(b.unitB); + atomPosition(uM, fM, b.indexMB, posA); + atomPosition(uB, fB, b.indexB, posB); } else { - // water→acceptor, B-side: draw acceptor→mid - const uB = structure.unitMap.get(wb.unitB) as Unit.Atomic; - const fB = unitsFeatures.get(wb.unitB); - atomPosition(uB, fB, wb.indexB, posA); - atomPosition(uW, fW, wb.indexWD, posB); + const uB = structure.unitMap.get(b.unitB) as Unit.Atomic; + const fB = unitsFeatures.get(b.unitB); + atomPosition(uB, fB, b.indexB, posA); + atomPosition(uM, fM, b.indexMB, posB); } }, @@ -190,39 +181,39 @@ function createWaterBridgeCylinderMesh(ctx: VisualContext, structure: Structure, const leg = Math.floor(edgeIndex / n); return leg <= 1 - ? canonical.donor[bi] !== bi - : canonical.acceptor[bi] !== bi; + ? canonical.endpointA[bi] !== bi + : canonical.endpointB[bi] !== bi; }, style: (_edgeIndex: number) => LinkStyle.Dashed, radius: (edgeIndex: number) => { - const wb = waterBridges[edgeIndex % n]; + const b = bridges[edgeIndex % n]; const leg = Math.floor(edgeIndex / n); - const isDonorWaterLeg = leg <= 1; + const isLegA = leg <= 1; - if (isDonorWaterLeg) { - const fA = unitsFeatures.get(wb.unitA); - const fW = unitsFeatures.get(wb.unitW); + if (isLegA) { + const fA = unitsFeatures.get(b.unitA); + const fM = unitsFeatures.get(b.unitM); - setFeatureLocation(structure, l, wb.unitA, fA, wb.indexA); + setFeatureLocation(structure, l, b.unitA, fA, b.indexA); const sizeA = theme.size.size(l); - setFeatureLocation(structure, l, wb.unitW, fW, wb.indexWA); - const sizeW = theme.size.size(l); + setFeatureLocation(structure, l, b.unitM, fM, b.indexMA); + const sizeM = theme.size.size(l); - return Math.min(sizeA, sizeW) * sizeFactor; + return Math.min(sizeA, sizeM) * sizeFactor; } else { - const fW = unitsFeatures.get(wb.unitW); - const fB = unitsFeatures.get(wb.unitB); + const fM = unitsFeatures.get(b.unitM); + const fB = unitsFeatures.get(b.unitB); - setFeatureLocation(structure, l, wb.unitW, fW, wb.indexWD); - const sizeW = theme.size.size(l); + setFeatureLocation(structure, l, b.unitM, fM, b.indexMB); + const sizeM = theme.size.size(l); - setFeatureLocation(structure, l, wb.unitB, fB, wb.indexB); + setFeatureLocation(structure, l, b.unitB, fB, b.indexB); const sizeB = theme.size.size(l); - return Math.min(sizeW, sizeB) * sizeFactor; + return Math.min(sizeM, sizeB) * sizeFactor; } }, }; @@ -239,25 +230,25 @@ function createWaterBridgeCylinderMesh(ctx: VisualContext, structure: Structure, return m; } -export const WaterBridgeInterUnitParams = { +export const BridgeParams = { ...ComplexMeshParams, ...LinkCylinderParams, ...InteractionsSharedParams, }; -export type WaterBridgeInterUnitParams = typeof WaterBridgeInterUnitParams +export type BridgeParams = typeof BridgeParams -export function WaterBridgeInterUnitVisual(materialId: number): ComplexVisual { - return ComplexMeshVisual({ - defaultProps: PD.getDefaultValues(WaterBridgeInterUnitParams), - createGeometry: createWaterBridgeCylinderMesh, - createLocationIterator: createWaterBridgeIterator, - getLoci: getWaterBridgeLoci, - eachLocation: eachWaterBridgeInteraction, +export function BridgeVisual(materialId: number): ComplexVisual { + return ComplexMeshVisual({ + defaultProps: PD.getDefaultValues(BridgeParams), + createGeometry: createBridgeCylinderMesh, + createLocationIterator: createBridgeIterator, + getLoci: getBridgeLoci, + eachLocation: eachBridgeInteraction, setUpdateState: ( state: VisualUpdateState, - newProps: PD.Values, - currentProps: PD.Values, + newProps: PD.Values, + currentProps: PD.Values, newTheme: Theme, currentTheme: Theme, newStructure: Structure, @@ -283,7 +274,7 @@ export function WaterBridgeInterUnitVisual(materialId: number): ComplexVisual b.props.type === InteractionType.WaterBridge); - const n = waterBridges.length; + const n = bridges.length; if (!n || groupId < 0 || groupId >= 4 * n) return EmptyLoci; const bridgeIndex = groupId % n; - return WaterBridges.Loci({ structure, waterBridges: bridges, unitsFeatures }, [{ bridgeIndex }]); + return Bridges.Loci({ structure, bridges, unitsFeatures }, [{ bridgeIndex }]); } const __unitMap = new Map>(); -function eachWaterBridgeInteraction(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, _isMarking: boolean) { +function eachBridgeInteraction(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, _isMarking: boolean) { let changed = false; - if (WaterBridges.isLoci(loci)) { + if (Bridges.isLoci(loci)) { if (!Structure.areEquivalent(loci.data.structure, structure)) return false; const interactions = InteractionsProvider.get(structure).value; if (!interactions) return false; - const waterBridges = interactions.bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge); - const n = waterBridges.length; + const { bridges } = interactions; + const n = bridges.length; if (!n) return false; - const canonical = getCanonicalLegIndices(waterBridges); + const canonical = getCanonicalLegIndices(bridges); for (const e of loci.elements) { if (e.bridgeIndex < 0 || e.bridgeIndex >= n) continue; - if (applyDonorLeg(e.bridgeIndex, n, canonical, apply)) changed = true; - if (applyAcceptorLeg(e.bridgeIndex, n, canonical, apply)) changed = true; + if (applyLegA(e.bridgeIndex, n, canonical, apply)) changed = true; + if (applyLegB(e.bridgeIndex, n, canonical, apply)) changed = true; } } else if (StructureElement.Loci.is(loci)) { if (!Structure.areEquivalent(loci.structure, structure)) return false; @@ -331,11 +321,10 @@ function eachWaterBridgeInteraction(loci: Loci, structure: Structure, apply: (in if (!interactions) return false; const { bridges, unitsFeatures } = interactions; - const waterBridges = bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge); - const n = waterBridges.length; + const n = bridges.length; if (!n) return false; - const canonical = getCanonicalLegIndices(waterBridges); + const canonical = getCanonicalLegIndices(bridges); __unitMap.clear(); for (const e of loci.elements) { @@ -343,42 +332,42 @@ function eachWaterBridgeInteraction(loci: Loci, structure: Structure, apply: (in } for (let i = 0; i < n; i++) { - const wb = waterBridges[i]; + const b = bridges[i]; - const indicesA = __unitMap.get(wb.unitA); - const indicesW = __unitMap.get(wb.unitW); - const indicesB = __unitMap.get(wb.unitB); + const indicesA = __unitMap.get(b.unitA); + const indicesM = __unitMap.get(b.unitM); + const indicesB = __unitMap.get(b.unitB); - if (!indicesA && !indicesW && !indicesB) continue; + if (!indicesA && !indicesM && !indicesB) continue; let hitA = false; if (indicesA) { - const fA = unitsFeatures.get(wb.unitA); - const mi = getFeatureMember(fA, wb.indexA); + const fA = unitsFeatures.get(b.unitA); + const mi = getFeatureMember(fA, b.indexA); hitA = OrderedSet.has(indicesA, mi); } - let hitW = false; - if (indicesW) { - const fW = unitsFeatures.get(wb.unitW); - const miA = getFeatureMember(fW, wb.indexWA); - const miD = getFeatureMember(fW, wb.indexWD); - hitW = OrderedSet.has(indicesW, miA) || OrderedSet.has(indicesW, miD); + let hitM = false; + if (indicesM) { + const fM = unitsFeatures.get(b.unitM); + const miA = getFeatureMember(fM, b.indexMA); + const miB = getFeatureMember(fM, b.indexMB); + hitM = OrderedSet.has(indicesM, miA) || OrderedSet.has(indicesM, miB); } let hitB = false; if (indicesB) { - const fB = unitsFeatures.get(wb.unitB); - const mi = getFeatureMember(fB, wb.indexB); + const fB = unitsFeatures.get(b.unitB); + const mi = getFeatureMember(fB, b.indexB); hitB = OrderedSet.has(indicesB, mi); } - if (hitA || hitW) { - if (applyDonorLeg(i, n, canonical, apply)) changed = true; + if (hitA || hitM) { + if (applyLegA(i, n, canonical, apply)) changed = true; } - if (hitB || hitW) { - if (applyAcceptorLeg(i, n, canonical, apply)) changed = true; + if (hitB || hitM) { + if (applyLegB(i, n, canonical, apply)) changed = true; } } @@ -388,19 +377,18 @@ function eachWaterBridgeInteraction(loci: Loci, structure: Structure, apply: (in return changed; } -function createWaterBridgeIterator(structure: Structure): LocationIterator { +function createBridgeIterator(structure: Structure): LocationIterator { const interactions = InteractionsProvider.get(structure).value; if (!interactions) return LocationIterator(0, 1, 1, () => NullLocation, true); const { bridges, unitsFeatures } = interactions; - const waterBridges = bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge); - const n = waterBridges.length; + const n = bridges.length; const groupCount = 4 * n; const instanceCount = 1; - const data: WaterBridges.Data = { structure, waterBridges, unitsFeatures }; - const location = WaterBridges.Location(data); + const data: Bridges.Data = { structure, bridges, unitsFeatures }; + const location = Bridges.Location(data); const { element } = location; const getLocation = (groupIndex: number) => { @@ -409,4 +397,4 @@ function createWaterBridgeIterator(structure: Structure): LocationIterator { }; return LocationIterator(groupCount, instanceCount, 1, getLocation, true); -} \ No newline at end of file +} diff --git a/src/mol-model-props/computed/representations/interactions.ts b/src/mol-model-props/computed/representations/interactions.ts index 89ba60ca2..9994eedbf 100644 --- a/src/mol-model-props/computed/representations/interactions.ts +++ b/src/mol-model-props/computed/representations/interactions.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2026 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose */ @@ -12,23 +12,23 @@ import { UnitsRepresentation, StructureRepresentation, StructureRepresentationSt import { InteractionsIntraUnitParams, InteractionsIntraUnitVisual } from './interactions-intra-unit-cylinder'; import { InteractionsProvider } from '../interactions'; import { InteractionsInterUnitParams, InteractionsInterUnitVisual } from './interactions-inter-unit-cylinder'; -import { WaterBridgeInterUnitParams, WaterBridgeInterUnitVisual } from './interactions-water-bridge-cylinder'; +import { BridgeParams, BridgeVisual } from './interactions-bridge-cylinder'; import { CustomProperty } from '../../common/custom-property'; import { getUnitKindsParam } from '../../../mol-repr/structure/params'; const InteractionsVisuals = { 'intra-unit': (ctx: RepresentationContext, getParams: RepresentationParamsGetter) => UnitsRepresentation('Intra-unit interactions cylinder', ctx, getParams, InteractionsIntraUnitVisual), 'inter-unit': (ctx: RepresentationContext, getParams: RepresentationParamsGetter) => ComplexRepresentation('Inter-unit interactions cylinder', ctx, getParams, InteractionsInterUnitVisual), - 'water-bridge': (ctx: RepresentationContext, getParams: RepresentationParamsGetter) => ComplexRepresentation('Water bridge cylinder', ctx, getParams, WaterBridgeInterUnitVisual), + 'bridge': (ctx: RepresentationContext, getParams: RepresentationParamsGetter) => ComplexRepresentation('Bridge cylinder', ctx, getParams, BridgeVisual), }; export const InteractionsParams = { ...InteractionsIntraUnitParams, ...InteractionsInterUnitParams, - ...WaterBridgeInterUnitParams, + ...BridgeParams, unitKinds: getUnitKindsParam(['atomic']), sizeFactor: PD.Numeric(0.2, { min: 0.01, max: 1, step: 0.01 }), - visuals: PD.MultiSelect(['intra-unit', 'inter-unit', 'water-bridge'], PD.objectToOptions(InteractionsVisuals)), + visuals: PD.MultiSelect(['intra-unit', 'inter-unit', 'bridge'], PD.objectToOptions(InteractionsVisuals)), }; export type InteractionsParams = typeof InteractionsParams export function getInteractionParams(ctx: ThemeRegistryContext, structure: Structure) { diff --git a/src/mol-model-props/computed/themes/interaction-type.ts b/src/mol-model-props/computed/themes/interaction-type.ts index 3c666b72a..40b032f85 100644 --- a/src/mol-model-props/computed/themes/interaction-type.ts +++ b/src/mol-model-props/computed/themes/interaction-type.ts @@ -13,8 +13,7 @@ import { ThemeDataContext } from '../../../mol-theme/theme'; import { ColorTheme, LocationColor } from '../../../mol-theme/color'; import { InteractionType } from '../interactions/common'; import { TableLegend } from '../../../mol-util/legend'; -import { Interactions } from '../interactions/interactions'; -import { WaterBridges } from '../interactions/water-bridges'; +import { Interactions, Bridges } from '../interactions/interactions'; import { CustomProperty } from '../../common/custom-property'; import { hash2 } from '../../../mol-data/util'; import { ColorThemeCategory } from '../../../mol-theme/color/categories'; @@ -97,8 +96,8 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value return typeColor(contacts.edges[idx].props.type); } } - if (WaterBridges.isLocation(location)) { - return InteractionTypeColors.WaterBridge; + if (Bridges.isLocation(location)) { + return typeColor(location.data.bridges[location.element.bridgeIndex].props.type); } return DefaultColor; };