mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
generic bridge visuals
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user