generic bridge visuals

This commit is contained in:
Sebastian
2026-05-29 10:55:48 +02:00
parent 2601d2ba63
commit 3ae72e5c60
6 changed files with 247 additions and 261 deletions

View File

@@ -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<BridgeContact>
interface Interactions {
@@ -136,6 +148,93 @@ namespace Interactions {
}
}
namespace Bridges {
export interface Data {
readonly structure: Structure
readonly bridges: BridgeContacts
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('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<Data, Element> {}
export function Loci(data: Data, elements: ReadonlyArray<Element>): 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<Element>): 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('</br>');
}
function getBoundingSphere(data: Data, elements: ReadonlyArray<Element>, 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,

View File

@@ -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;

View File

@@ -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<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 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('</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 * 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);
}
}

View File

@@ -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<WaterBridgeContacts, CanonicalLegIndices>();
const CanonicalLegIndicesCache = new WeakMap<BridgeContacts, CanonicalLegIndices>();
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<string, number>();
const acceptorLegs = new Map<string, number>();
const legA = new Map<string, number>();
const legB = new Map<string, number>();
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<WaterBridgeInterUnitParams>, mesh?: Mesh) {
function createBridgeCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<BridgeParams>, 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<WaterBridgeInterUnitParams> {
return ComplexMeshVisual<WaterBridgeInterUnitParams>({
defaultProps: PD.getDefaultValues(WaterBridgeInterUnitParams),
createGeometry: createWaterBridgeCylinderMesh,
createLocationIterator: createWaterBridgeIterator,
getLoci: getWaterBridgeLoci,
eachLocation: eachWaterBridgeInteraction,
export function BridgeVisual(materialId: number): ComplexVisual<BridgeParams> {
return ComplexMeshVisual<BridgeParams>({
defaultProps: PD.getDefaultValues(BridgeParams),
createGeometry: createBridgeCylinderMesh,
createLocationIterator: createBridgeIterator,
getLoci: getBridgeLoci,
eachLocation: eachBridgeInteraction,
setUpdateState: (
state: VisualUpdateState,
newProps: PD.Values<WaterBridgeInterUnitParams>,
currentProps: PD.Values<WaterBridgeInterUnitParams>,
newProps: PD.Values<BridgeParams>,
currentProps: PD.Values<BridgeParams>,
newTheme: Theme,
currentTheme: Theme,
newStructure: Structure,
@@ -283,7 +274,7 @@ export function WaterBridgeInterUnitVisual(materialId: number): ComplexVisual<Wa
}, materialId);
}
function getWaterBridgeLoci(pickingId: PickingId, structure: Structure, id: number) {
function getBridgeLoci(pickingId: PickingId, structure: Structure, id: number) {
const { objectId, groupId } = pickingId;
if (id !== objectId) return EmptyLoci;
@@ -291,38 +282,37 @@ function getWaterBridgeLoci(pickingId: PickingId, structure: Structure, id: numb
if (!interactions) return EmptyLoci;
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 || 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<number, OrderedSet<StructureElement.UnitIndex>>();
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);
}
}

View File

@@ -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 <alexander.rose@weirdbyte.de>
*/
@@ -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<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),
'bridge': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, BridgeParams>) => 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) {

View File

@@ -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;
};