nci: water bridge support

This commit is contained in:
Sebastian
2026-05-06 09:56:06 +02:00
parent 86a74d1cc2
commit 0b30c7344b
9 changed files with 529 additions and 8 deletions

View File

@@ -25,6 +25,7 @@ export type InteractionElementSchema =
| { kind: 'weak-hydrogen-bond' } & InteractionElementSchemaBase
| { kind: 'hydrophobic' } & InteractionElementSchemaBase
| { kind: 'metal-coordination' } & InteractionElementSchemaBase
| { kind: 'water-bridge' } & InteractionElementSchemaBase
| { kind: 'covalent', degree?: 'aromatic' | 1 | 2 | 3 | 4 } & InteractionElementSchemaBase
export type InteractionKind = InteractionElementSchema['kind']
@@ -39,6 +40,7 @@ export const InteractionKinds: InteractionKind[] = [
'weak-hydrogen-bond',
'hydrophobic',
'metal-coordination',
'water-bridge',
'covalent',
];
@@ -52,6 +54,7 @@ export type InteractionInfo =
| { kind: 'weak-hydrogen-bond', hydrogenStructureRef?: string, hydrogen?: StructureElement.Loci }
| { kind: 'hydrophobic' }
| { kind: 'metal-coordination' }
| { kind: 'water-bridge' }
| { kind: 'covalent', degree?: 'aromatic' | 1 | 2 | 3 | 4 }
export interface StructureInteractionElement {
@@ -80,4 +83,5 @@ export const InteractionTypeToKind = {
[InteractionType.Hydrophobic]: 'hydrophobic' as InteractionKind,
[InteractionType.MetalCoordination]: 'metal-coordination' as InteractionKind,
[InteractionType.WeakHydrogenBond]: 'weak-hydrogen-bond' as InteractionKind,
[InteractionType.WaterBridge]: 'water-bridge' as InteractionKind,
};

View File

@@ -47,6 +47,7 @@ export const InteractionVisualParams = {
'weak-hydrogen-bond': hydrogenVisualParams({ color: Color(0x0) }),
'hydrophobic': visualParams({ color: Color(0x555555) }),
'metal-coordination': visualParams({ color: Color(0x952e8f) }),
'water-bridge': visualParams({ color: Color(0x00CCEE), style: 'dashed' }),
'covalent': PD.Group({
color: PD.Color(Color(0x999999)),
radius: PD.Numeric(0.1, { min: 0.01, max: 1, step: 0.01 }),

View File

@@ -133,6 +133,7 @@ export enum InteractionType {
Hydrophobic = 6,
MetalCoordination = 7,
WeakHydrogenBond = 8,
WaterBridge = 9,
}
export function interactionTypeLabel(type: InteractionType): string {
@@ -153,6 +154,8 @@ export function interactionTypeLabel(type: InteractionType): string {
return 'Pi Stacking';
case InteractionType.WeakHydrogenBond:
return 'Weak Hydrogen Bond';
case InteractionType.WaterBridge:
return 'Water Bridge';
case InteractionType.Unknown:
return 'Unknown Interaction';
}

View File

@@ -20,7 +20,7 @@ import { FeatureType, FeatureGroup, InteractionType } from './common';
import { ContactProvider } from './contacts';
import { MoleculeType, ProteinBackboneAtoms } from '../../../mol-model/structure/model/types';
const GeometryParams = {
export const GeometryParams = {
distanceMax: PD.Numeric(3.5, { min: 1, max: 5, step: 0.1 }),
backbone: PD.Boolean(true, { description: 'Include backbone-to-backbone hydrogen bonds' }),
accAngleDevMax: PD.Numeric(45, { min: 0, max: 180, step: 1 }, { description: 'Max deviation from ideal acceptor angle' }),
@@ -29,7 +29,7 @@ const GeometryParams = {
accOutOfPlaneAngleMax: PD.Numeric(90, { min: 0, max: 180, step: 1 }),
donOutOfPlaneAngleMax: PD.Numeric(45, { min: 0, max: 180, step: 1 }),
};
type GeometryParams = typeof GeometryParams
export type GeometryParams = typeof GeometryParams
type GeometryProps = PD.Values<GeometryParams>
const HydrogenBondsParams = {
@@ -208,7 +208,7 @@ function isWeakHydrogenBond(ti: FeatureType, tj: FeatureType) {
);
}
function getGeometryOptions(props: GeometryProps) {
export function getGeometryOptions(props: GeometryProps) {
return {
ignoreHydrogens: props.ignoreHydrogens,
includeBackbone: props.backbone,
@@ -218,7 +218,7 @@ function getGeometryOptions(props: GeometryProps) {
maxDonOutOfPlaneAngle: degToRad(props.donOutOfPlaneAngleMax),
};
}
type GeometryOptions = ReturnType<typeof getGeometryOptions>
export type GeometryOptions = ReturnType<typeof getGeometryOptions>
function getHydrogenBondsOptions(props: HydrogenBondsProps) {
return {
@@ -232,7 +232,7 @@ type HydrogenBondsOptions = ReturnType<typeof getHydrogenBondsOptions>
const deg120InRad = degToRad(120);
function checkGeometry(structure: Structure, don: Features.Info, acc: Features.Info, opts: GeometryOptions): true | undefined {
export function checkGeometry(structure: Structure, don: Features.Info, acc: Features.Info, opts: GeometryOptions): true | undefined {
const donIndex = don.members[don.offsets[don.feature]];
const accIndex = acc.members[acc.offsets[acc.feature]];

View File

@@ -15,6 +15,7 @@ import { IntMap } 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 { findWaterBridgeContacts, WaterBridgeContacts, WaterBridgesParams } from './water-bridges';
import { NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, IonicProvider, PiStackingProvider, CationPiProvider } from './charged';
import { HydrophobicAtomProvider, HydrophobicProvider } from './hydrophobic';
import { SetUtils } from '../../../mol-util/set';
@@ -37,6 +38,8 @@ interface Interactions {
unitsContacts: IntMap<InteractionsIntraContacts>
/** Interactions between units */
contacts: InteractionsInterContacts
/** Water-mediated hydrogen bonds (donor → water → acceptor) */
waterBridges: WaterBridgeContacts
}
namespace Interactions {
@@ -174,8 +177,16 @@ export const ContactProviderParams = getProvidersParams([
// 'weak-hydrogen-bonds',
]);
export const WaterBridgesToggleParams = {
'water-bridges': PD.MappedStatic('off', {
on: PD.Group(WaterBridgesParams),
off: PD.Group({})
}, { cycle: true }),
};
export const InteractionsParams = {
providers: PD.Group(ContactProviderParams, { isFlat: true }),
waterBridges: PD.Group(WaterBridgesToggleParams, { isFlat: true }),
contacts: PD.Group(ContactsParams, { label: 'Advanced Options' }),
};
export type InteractionsParams = typeof InteractionsParams
@@ -229,7 +240,12 @@ export async function computeInteractions(ctx: CustomProperty.Context, structure
const contacts = findInterUnitContacts(structure, unitsFeatures, contactTesters, p.contacts, options);
const interactions = { unitsFeatures, unitsContacts, contacts };
const wbToggle = p.waterBridges['water-bridges'];
const waterBridges = wbToggle.name === 'on'
? findWaterBridgeContacts(structure, unitsFeatures, wbToggle.params)
: [];
const interactions = { unitsFeatures, unitsContacts, contacts, waterBridges };
refineInteractions(structure, interactions);
return interactions;
}

View File

@@ -0,0 +1,243 @@
/**
* Copyright (c) 2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.m.bittrich@gmail.com>
*/
import { Structure, Unit, StructureElement } from '../../../mol-model/structure';
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 } 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 } from './common';
import { GeometryParams, GeometryOptions, getGeometryOptions, checkGeometry } from './hydrogen-bonds';
import { typeSymbol } from '../chemistry/util';
import { Elements } from '../../../mol-model/structure/model/properties/atomic/types';
import { elementLabel } from '../../../mol-theme/label';
export type { WaterBridgeContact, WaterBridgeContacts };
interface WaterBridgeContact {
/** non-water donor unit id */
readonly unitA: number
/** donor feature index in unitA */
readonly indexA: Features.FeatureIndex
/** non-water acceptor unit id */
readonly unitB: number
/** acceptor feature index in unitB */
readonly indexB: Features.FeatureIndex
/** bridging water unit id */
readonly unitW: number
/** water oxygen as HydrogenAcceptor (leg: donor → water) */
readonly indexWA: Features.FeatureIndex
/** water oxygen as HydrogenDonor (leg: water → acceptor) */
readonly indexWD: Features.FeatureIndex
}
type WaterBridgeContacts = ReadonlyArray<WaterBridgeContact>;
export const WaterBridgesParams = {
...GeometryParams,
sulfurDistanceMax: PD.Numeric(4.1, { min: 1, max: 5, step: 0.1 }),
};
export type WaterBridgesParams = typeof WaterBridgesParams;
export type WaterBridgesProps = PD.Values<WaterBridgesParams>;
function isWater(unit: Unit.Atomic, index: StructureElement.UnitIndex): boolean {
return unit.model.atomicHierarchy.derived.residue.moleculeType[
unit.residueIndex[unit.elements[index]]
] === MoleculeType.Water;
}
const _lookupCtx = StructureLookup3DResultContext();
export function findWaterBridgeContacts(
structure: Structure,
unitsFeatures: IntMap<Features>,
props: WaterBridgesProps
): WaterBridgeContacts {
const opts: GeometryOptions = getGeometryOptions(props);
const maxLeg = Math.max(props.distanceMax, props.sulfurDistanceMax);
const distMaxSq = props.distanceMax * props.distanceMax;
const sulfurDistMaxSq = props.sulfurDistanceMax * props.sulfurDistanceMax;
const edges: WaterBridgeContact[] = [];
const wPos = Vec3();
for (const unitW of structure.units) {
if (!Unit.isAtomic(unitW)) continue;
const featW = unitsFeatures.get(unitW.id);
if (!featW || featW.count === 0) continue;
// Map each water-oxygen local index to its acceptor and donor feature indices.
const waterMap = new Map<StructureElement.UnitIndex, {
acc: Features.FeatureIndex | undefined,
don: Features.FeatureIndex | undefined
}>();
for (let fi = 0 as Features.FeatureIndex; fi < featW.count; fi++) {
const mi = featW.members[featW.offsets[fi]] as StructureElement.UnitIndex;
if (!isWater(unitW, mi)) continue;
const t = featW.types[fi];
if (t !== FeatureType.HydrogenAcceptor && t !== FeatureType.HydrogenDonor) continue;
let e = waterMap.get(mi);
if (!e) waterMap.set(mi, (e = { acc: undefined, don: undefined }));
if (t === FeatureType.HydrogenAcceptor) e.acc = fi;
else e.don = fi;
}
for (const [waterAtomIdx, { acc: accFW, don: donFW }] of waterMap) {
if (accFW === undefined || donFW === undefined) continue;
// Water oxygen world-space position.
unitW.conformation.position(unitW.elements[waterAtomIdx], wPos);
// Both Info objects share the same unitW / featW; feature index differs.
const infoWAcc = Features.Info(structure, unitW, featW);
infoWAcc.feature = accFW;
const infoWDon = Features.Info(structure, unitW, featW);
infoWDon.feature = donFW;
// Find all non-water atoms within leg distance of the water oxygen.
const { count, indices, units: hitUnits, squaredDistances } =
structure.lookup3d.find(wPos[0], wPos[1], wPos[2], maxLeg, _lookupCtx);
type Candidate = { unitId: number; featureIdx: Features.FeatureIndex };
const donors: Candidate[] = [];
const acceptors: Candidate[] = [];
for (let r = 0; r < count; r++) {
const hitUnit = hitUnits[r];
if (!Unit.isAtomic(hitUnit)) continue;
if (hitUnit === unitW) continue;
const hitLocalIdx = indices[r] as StructureElement.UnitIndex;
if (isWater(hitUnit as Unit.Atomic, hitLocalIdx)) continue;
const hitFeat = unitsFeatures.get(hitUnit.id);
if (!hitFeat || hitFeat.count === 0) continue;
const { indices: fIdxs, offsets: fOff } = hitFeat.elementsIndex;
for (let k = fOff[hitLocalIdx], kl = fOff[hitLocalIdx + 1]; k < kl; k++) {
const fi = fIdxs[k] as Features.FeatureIndex;
const fType = hitFeat.types[fi];
if (fType !== FeatureType.HydrogenDonor &&
fType !== FeatureType.WeakHydrogenDonor &&
fType !== FeatureType.HydrogenAcceptor) continue;
const memberIdx = hitFeat.members[hitFeat.offsets[fi]] as StructureElement.UnitIndex;
const isSulfur = typeSymbol(hitUnit as Unit.Atomic, memberIdx) === Elements.S;
if (squaredDistances[r] > (isSulfur ? sulfurDistMaxSq : distMaxSq)) continue;
if (fType === FeatureType.HydrogenDonor || fType === FeatureType.WeakHydrogenDonor) {
const infoDon = Features.Info(structure, hitUnit as Unit.Atomic, hitFeat);
infoDon.feature = fi;
if (checkGeometry(structure, infoDon, infoWAcc, opts)) {
donors.push({ unitId: hitUnit.id, featureIdx: fi });
}
} else {
const infoAcc = Features.Info(structure, hitUnit as Unit.Atomic, hitFeat);
infoAcc.feature = fi;
if (checkGeometry(structure, infoWDon, infoAcc, opts)) {
acceptors.push({ unitId: hitUnit.id, featureIdx: fi });
}
}
}
}
// Cross-pair verified donors and acceptors.
for (const don of donors) {
for (const acc of acceptors) {
// Prevent donor == acceptor (same feature).
if (don.unitId === acc.unitId && don.featureIdx === acc.featureIdx) continue;
edges.push({
unitA: don.unitId, indexA: don.featureIdx,
unitB: acc.unitId, indexB: acc.featureIdx,
unitW: unitW.id, indexWA: accFW, indexWD: donFW,
});
}
}
}
}
return edges;
}
// ---------------------------------------------------------------------------
// 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<Features>
}
export interface Element { bridgeIndex: number }
export interface Location extends DataLocation<Data, Element> {}
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<Data, Element> {}
export function Loci(data: Data, elements: ReadonlyArray<Element>): Loci {
return DataLoci('water-bridges', data, elements,
bs => getBoundingSphere(data, elements, bs),
() => getLabel(data, elements));
}
function getLabel(data: Data, elements: ReadonlyArray<Element>): 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 locA = StructureElement.Location.create(structure, uA, uA.elements[fA.members[fA.offsets[wb.indexA]]]);
const locW = StructureElement.Location.create(structure, uW, uW.elements[fW.members[fW.offsets[wb.indexWA]]]);
const locB = StructureElement.Location.create(structure, uB, uB.elements[fB.members[fB.offsets[wb.indexB]]]);
return [
'Water Bridge',
`${elementLabel(locA, { granularity: 'element' })} → ${elementLabel(locW, { granularity: 'residue' })} → ${elementLabel(locB, { granularity: 'element' })}`,
].join('</br>');
}
export function isLoci(x: any): x is Loci {
return !!x && x.kind === 'data-loci' && x.tag === 'water-bridges';
}
function getBoundingSphere(data: Data, elements: ReadonlyArray<Element>, boundingSphere: Sphere3D) {
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
const wb = data.waterBridges[elements[i].bridgeIndex];
const uA = data.structure.unitMap.get(wb.unitA) as Unit.Atomic;
const fA = data.unitsFeatures.get(wb.unitA);
uA.conformation.position(uA.elements[fA.members[fA.offsets[wb.indexA]]], pA);
const uB = data.structure.unitMap.get(wb.unitB) as Unit.Atomic;
const fB = data.unitsFeatures.get(wb.unitB);
uB.conformation.position(uB.elements[fB.members[fB.offsets[wb.indexB]]], pB);
}, boundingSphere);
}
}

View File

@@ -0,0 +1,242 @@
/**
* Copyright (c) 2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.m.bittrich@gmail.com>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { VisualContext } from '../../../mol-repr/visual';
import { Structure, StructureElement, Unit } from '../../../mol-model/structure';
import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { PickingId } from '../../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Interval, OrderedSet } from '../../../mol-data/int';
import { InteractionsProvider } from '../interactions';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { WaterBridges } from '../interactions/water-bridges';
import { Sphere3D } from '../../../mol-math/geometry';
import { InteractionsSharedParams } from './shared';
import { Features } from '../interactions/features';
function createWaterBridgeCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<WaterBridgeInterUnitParams>, mesh?: Mesh) {
if (!structure.hasAtomic) return Mesh.createEmpty(mesh);
const interactions = InteractionsProvider.get(structure).value!;
const { waterBridges, unitsFeatures } = interactions;
const n = waterBridges.length;
if (!n) return Mesh.createEmpty(mesh);
const l = StructureElement.Location.create(structure);
const { sizeFactor } = props;
const builderProps = {
// Four half-cylinders per bridge (createLinkCylinderMesh draws only 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)
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 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);
} else if (leg === 1) {
// donor→water, B-side: draw water→mid (posA/posB swapped)
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);
} 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);
} else {
// water→acceptor, B-side: draw acceptor→mid (posA/posB swapped)
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);
}
},
style: (_edgeIndex: number) => LinkStyle.Dashed,
radius: (edgeIndex: number) => {
const wb = waterBridges[edgeIndex % n];
const leg = Math.floor(edgeIndex / n);
const isDonorWaterLeg = leg <= 1;
const fA = unitsFeatures.get(isDonorWaterLeg ? wb.unitA : wb.unitW);
const unitIdA = isDonorWaterLeg ? wb.unitA : wb.unitW;
const indexA = isDonorWaterLeg ? wb.indexA : wb.indexWD;
l.unit = structure.unitMap.get(unitIdA);
l.element = l.unit.elements[fA.members[fA.offsets[indexA]]];
const sizeA = theme.size.size(l);
const fB = unitsFeatures.get(isDonorWaterLeg ? wb.unitW : wb.unitB);
const unitIdB = isDonorWaterLeg ? wb.unitW : wb.unitB;
const indexB = isDonorWaterLeg ? wb.indexWA : wb.indexB;
l.unit = structure.unitMap.get(unitIdB);
l.element = l.unit.elements[fB.members[fB.offsets[indexB]]];
const sizeB = theme.size.size(l);
return Math.min(sizeA, sizeB) * sizeFactor;
},
ignore: (_edgeIndex: number) => false,
};
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}
function atomPosition(unit: Unit.Atomic, features: Features, featureIndex: Features.FeatureIndex, out: Vec3) {
const atomLocalIdx = features.members[features.offsets[featureIndex]];
unit.conformation.position(unit.elements[atomLocalIdx], out);
}
export const WaterBridgeInterUnitParams = {
...ComplexMeshParams,
...LinkCylinderParams,
...InteractionsSharedParams,
};
export type WaterBridgeInterUnitParams = typeof WaterBridgeInterUnitParams
export function WaterBridgeInterUnitVisual(materialId: number): ComplexVisual<WaterBridgeInterUnitParams> {
return ComplexMeshVisual<WaterBridgeInterUnitParams>({
defaultProps: PD.getDefaultValues(WaterBridgeInterUnitParams),
createGeometry: createWaterBridgeCylinderMesh,
createLocationIterator: createWaterBridgeIterator,
getLoci: getWaterBridgeLoci,
eachLocation: eachWaterBridgeInteraction,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<WaterBridgeInterUnitParams>, currentProps: PD.Values<WaterBridgeInterUnitParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.dashCount !== currentProps.dashCount ||
newProps.dashScale !== currentProps.dashScale ||
newProps.dashCap !== currentProps.dashCap ||
newProps.radialSegments !== currentProps.radialSegments
);
const interactionsHash = InteractionsProvider.get(newStructure).version;
if ((state.info.interactionsHash as number) !== interactionsHash) {
state.createGeometry = true;
state.updateTransform = true;
state.updateColor = true;
state.info.interactionsHash = interactionsHash;
}
}
}, materialId);
}
function getWaterBridgeLoci(pickingId: PickingId, structure: Structure, id: number) {
const { objectId, groupId } = pickingId;
if (id !== objectId) return EmptyLoci;
const interactions = InteractionsProvider.get(structure).value!;
const { waterBridges, unitsFeatures } = interactions;
const bridgeIndex = groupId % waterBridges.length;
return WaterBridges.Loci({ structure, waterBridges, unitsFeatures }, [{ bridgeIndex }]);
}
const __unitMap = new Map<number, OrderedSet<StructureElement.UnitIndex>>();
function eachWaterBridgeInteraction(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, _isMarking: boolean) {
let changed = false;
if (WaterBridges.isLoci(loci)) {
if (!Structure.areEquivalent(loci.data.structure, structure)) return false;
const interactions = InteractionsProvider.get(structure).value!;
const n = interactions.waterBridges.length;
for (const e of loci.elements) {
// Apply all four half-cylinders for this bridge.
if (apply(Interval.ofSingleton(e.bridgeIndex))) changed = true;
if (apply(Interval.ofSingleton(e.bridgeIndex + n))) changed = true;
if (apply(Interval.ofSingleton(e.bridgeIndex + 2 * n))) changed = true;
if (apply(Interval.ofSingleton(e.bridgeIndex + 3 * n))) changed = true;
}
} else if (StructureElement.Loci.is(loci)) {
if (!Structure.areEquivalent(loci.structure, structure)) return false;
const interactions = InteractionsProvider.get(structure).value;
if (!interactions) return false;
const { waterBridges, unitsFeatures } = interactions;
const n = waterBridges.length;
for (const e of loci.elements) __unitMap.set(e.unit.id, e.indices);
for (let i = 0; i < n; i++) {
const wb = waterBridges[i];
const indicesA = __unitMap.get(wb.unitA);
const indicesB = __unitMap.get(wb.unitB);
if (!indicesA && !indicesB) continue;
let hitA = false;
if (indicesA) {
const fA = unitsFeatures.get(wb.unitA);
const mi = fA.members[fA.offsets[wb.indexA]] as StructureElement.UnitIndex;
hitA = OrderedSet.has(indicesA, mi);
}
let hitB = false;
if (indicesB) {
const fB = unitsFeatures.get(wb.unitB);
const mi = fB.members[fB.offsets[wb.indexB]] as StructureElement.UnitIndex;
hitB = OrderedSet.has(indicesB, mi);
}
if (hitA) {
if (apply(Interval.ofSingleton(i))) changed = true;
if (apply(Interval.ofSingleton(i + n))) changed = true;
}
if (hitB) {
if (apply(Interval.ofSingleton(i + 2 * n))) changed = true;
if (apply(Interval.ofSingleton(i + 3 * n))) changed = true;
}
}
__unitMap.clear();
}
return changed;
}
function createWaterBridgeIterator(structure: Structure): LocationIterator {
const interactions = InteractionsProvider.get(structure).value!;
const { waterBridges, unitsFeatures } = interactions;
const n = waterBridges.length;
const groupCount = 4 * n;
const instanceCount = 1;
const data: WaterBridges.Data = { structure, waterBridges, unitsFeatures };
const location = WaterBridges.Location(data);
const { element } = location;
const getLocation = (groupIndex: number) => {
element.bridgeIndex = groupIndex % n;
return location;
};
return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
}

View File

@@ -12,20 +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 { CustomProperty } from '../../common/custom-property';
import { getUnitKindsParam } from '../../../mol-repr/structure/params';
const InteractionsVisuals = {
'intra-unit': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InteractionsIntraUnitParams>) => UnitsRepresentation('Intra-unit interactions cylinder', ctx, getParams, InteractionsIntraUnitVisual),
'inter-unit': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InteractionsInterUnitParams>) => ComplexRepresentation('Inter-unit interactions cylinder', ctx, getParams, InteractionsInterUnitVisual),
'water-bridge': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, WaterBridgeInterUnitParams>) => ComplexRepresentation('Water bridge cylinder', ctx, getParams, WaterBridgeInterUnitVisual),
};
export const InteractionsParams = {
...InteractionsIntraUnitParams,
...InteractionsInterUnitParams,
...WaterBridgeInterUnitParams,
unitKinds: getUnitKindsParam(['atomic']),
sizeFactor: PD.Numeric(0.2, { min: 0.01, max: 1, step: 0.01 }),
visuals: PD.MultiSelect(['intra-unit', 'inter-unit'], PD.objectToOptions(InteractionsVisuals)),
visuals: PD.MultiSelect(['intra-unit', 'inter-unit', 'water-bridge'], PD.objectToOptions(InteractionsVisuals)),
};
export type InteractionsParams = typeof InteractionsParams
export function getInteractionParams(ctx: ThemeRegistryContext, structure: Structure) {

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 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 <alexander.rose@weirdbyte.de>
* @author Sebastian Bittrich <sebastian.m.bittrich@gmail.com>
*/
import { Location } from '../../../mol-model/location';
@@ -13,6 +14,7 @@ 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 { CustomProperty } from '../../common/custom-property';
import { hash2 } from '../../../mol-data/util';
import { ColorThemeCategory } from '../../../mol-theme/color/categories';
@@ -29,6 +31,7 @@ const InteractionTypeColors = ColorMap({
CationPi: 0xFF8000,
PiStacking: 0x8CB366,
WeakHydrogenBond: 0xC5DDEC,
WaterBridge: 0x00CCEE,
});
const InteractionTypeColorTable: [string, Color][] = [
@@ -40,6 +43,7 @@ const InteractionTypeColorTable: [string, Color][] = [
['Cation Pi', InteractionTypeColors.CationPi],
['Pi Stacking', InteractionTypeColors.PiStacking],
['Weak HydrogenBond', InteractionTypeColors.WeakHydrogenBond],
['Water Bridge', InteractionTypeColors.WaterBridge],
];
function typeColor(type: InteractionType): Color {
@@ -60,6 +64,8 @@ function typeColor(type: InteractionType): Color {
return InteractionTypeColors.PiStacking;
case InteractionType.WeakHydrogenBond:
return InteractionTypeColors.WeakHydrogenBond;
case InteractionType.WaterBridge:
return InteractionTypeColors.WaterBridge;
case InteractionType.Unknown:
return DefaultColor;
}
@@ -91,6 +97,9 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value
return typeColor(contacts.edges[idx].props.type);
}
}
if (WaterBridges.isLocation(location)) {
return InteractionTypeColors.WaterBridge;
}
return DefaultColor;
};
} else {