From f3a5369690537844f87aa8b96845004e2993f4d4 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sat, 22 Apr 2023 09:56:21 -0700 Subject: [PATCH] support ignoreHydrogens for interactions --- CHANGELOG.md | 2 + .../interactions-inter-unit-cylinder.ts | 66 +++++++++++++++--- .../interactions-intra-unit-cylinder.ts | 68 ++++++++++++++++--- .../computed/representations/shared.ts | 4 +- src/mol-repr/structure/visual/util/common.ts | 14 ++-- 5 files changed, 125 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a27ba48d..6621454c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] +- Support `ignoreHydrogens` in interactions representation + ## [v3.34.0] - 2023-04-16 - Avoid `renderMarkingDepth` for fully transparent renderables diff --git a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts index 9a0d91642..f8d421e8d 100644 --- a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts +++ b/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -19,11 +19,13 @@ import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int'; import { Interactions } from '../interactions/interactions'; import { InteractionsProvider } from '../interactions'; import { LocationIterator } from '../../../mol-geo/util/location-iterator'; -import { InteractionFlag } from '../interactions/common'; +import { InteractionFlag, InteractionType } from '../interactions/common'; import { Unit } from '../../../mol-model/structure/structure'; import { Sphere3D } from '../../../mol-math/geometry'; import { assertUnreachable } from '../../../mol-util/type-helpers'; import { InteractionsSharedParams } from './shared'; +import { eachBondedAtom } from '../chemistry/util'; +import { isHydrogen } from '../../../mol-repr/structure/visual/util/common'; function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) { if (!structure.hasAtomic) return Mesh.createEmpty(mesh); @@ -33,26 +35,66 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S const { contacts, unitsFeatures } = interactions; const { edgeCount, edges } = contacts; - const { sizeFactor, parentDisplay } = props; + const { sizeFactor, ignoreHydrogens, ignoreHydrogensVariant, parentDisplay } = props; if (!edgeCount) return Mesh.createEmpty(mesh); const { child } = structure; + const p = Vec3(); + const pA = Vec3(); + const pB = Vec3(); const builderProps = { linkCount: edgeCount, position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { - const { unitA, indexA, unitB, indexB } = edges[edgeIndex]; + const { unitA, indexA, unitB, indexB, props: { type: t } } = edges[edgeIndex]; const fA = unitsFeatures.get(unitA); const fB = unitsFeatures.get(unitB); - const uA = structure.unitMap.get(unitA); - const uB = structure.unitMap.get(unitB); - - Vec3.set(posA, fA.x[indexA], fA.y[indexA], fA.z[indexA]); - Vec3.transformMat4(posA, posA, uA.conformation.operator.matrix); + const uA = structure.unitMap.get(unitA) as Unit.Atomic; + const uB = structure.unitMap.get(unitB) as Unit.Atomic; + + if ((!ignoreHydrogens || ignoreHydrogensVariant !== 'all') && ( + t === InteractionType.HydrogenBond || t === InteractionType.WeakHydrogenBond) + ) { + const idxA = fA.members[fA.offsets[indexA]]; + const idxB = fB.members[fB.offsets[indexB]]; + uA.conformation.position(uA.elements[idxA], pA); + uB.conformation.position(uB.elements[idxB], pB); + let minDistA = Vec3.distance(pA, pB); + let minDistB = minDistA; + Vec3.copy(posA, pA); + Vec3.copy(posB, pB); + + eachBondedAtom(structure, uA, idxA, (u, idx) => { + const eI = u.elements[idx]; + if (isHydrogen(structure, u, eI, 'polar')) { + u.conformation.position(eI, p); + const dist = Vec3.distance(p, pB); + if (dist < minDistA) { + minDistA = dist; + Vec3.copy(posA, p); + } + } + }); + + eachBondedAtom(structure, uB, idxB, (u, idx) => { + const eI = u.elements[idx]; + if (isHydrogen(structure, u, eI, 'polar')) { + u.conformation.position(eI, p); + const dist = Vec3.distance(p, pA); + if (dist < minDistB) { + minDistB = dist; + Vec3.copy(posB, p); + } + } + }); + } else { + Vec3.set(posA, fA.x[indexA], fA.y[indexA], fA.z[indexA]); + Vec3.transformMat4(posA, posA, uA.conformation.operator.matrix); - Vec3.set(posB, fB.x[indexB], fB.y[indexB], fB.z[indexB]); - Vec3.transformMat4(posB, posB, uB.conformation.operator.matrix); + Vec3.set(posB, fB.x[indexB], fB.y[indexB], fB.z[indexB]); + Vec3.transformMat4(posB, posB, uB.conformation.operator.matrix); + } }, style: (edgeIndex: number) => LinkStyle.Dashed, radius: (edgeIndex: number) => { @@ -155,6 +197,8 @@ export function InteractionsInterUnitVisual(materialId: number): ComplexVisual<I newProps.dashScale !== currentProps.dashScale || newProps.dashCap !== currentProps.dashCap || newProps.radialSegments !== currentProps.radialSegments || + newProps.ignoreHydrogens !== currentProps.ignoreHydrogens || + newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant || newProps.parentDisplay !== currentProps.parentDisplay ); diff --git a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts index e0289f7b7..a21140c29 100644 --- a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts +++ b/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -21,9 +21,11 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator'; import { Interactions } from '../interactions/interactions'; import { InteractionFlag } from '../interactions/common'; import { Sphere3D } from '../../../mol-math/geometry'; -import { StructureGroup } from '../../../mol-repr/structure/visual/util/common'; +import { StructureGroup, isHydrogen } from '../../../mol-repr/structure/visual/util/common'; import { assertUnreachable } from '../../../mol-util/type-helpers'; import { InteractionsSharedParams } from './shared'; +import { InteractionType } from '../interactions/common'; +import { eachIntraBondedAtom } from '../chemistry/util'; async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh); @@ -39,22 +41,64 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: const contacts = interactions.unitsContacts.get(unit.id); const { x, y, z, members, offsets } = features; - const { edgeCount, a, b, edgeProps: { flag } } = contacts; - const { sizeFactor, parentDisplay } = props; + const { edgeCount, a, b, edgeProps: { flag, type } } = contacts; + const { sizeFactor, ignoreHydrogens, ignoreHydrogensVariant, parentDisplay } = props; if (!edgeCount) return Mesh.createEmpty(mesh); + const pos = unit.conformation.invariantPosition; + const { elements } = unit; + const p = Vec3(); + const pA = Vec3(); + const pB = Vec3(); + const builderProps = { linkCount: edgeCount * 2, position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { - Vec3.set(posA, x[a[edgeIndex]], y[a[edgeIndex]], z[a[edgeIndex]]); - Vec3.set(posB, x[b[edgeIndex]], y[b[edgeIndex]], z[b[edgeIndex]]); + const t = type[edgeIndex]; + if ((!ignoreHydrogens || ignoreHydrogensVariant !== 'all') && ( + t === InteractionType.HydrogenBond || t === InteractionType.WeakHydrogenBond) + ) { + const idxA = members[offsets[a[edgeIndex]]]; + const idxB = members[offsets[b[edgeIndex]]]; + pos(elements[idxA], pA); + pos(elements[idxB], pB); + let minDistA = Vec3.distance(pA, pB); + let minDistB = minDistA; + Vec3.copy(posA, pA); + Vec3.copy(posB, pB); + + eachIntraBondedAtom(unit, idxA, (_, idx) => { + if (isHydrogen(structure, unit, elements[idx], 'polar')) { + pos(elements[idx], p); + const dist = Vec3.distance(p, pB); + if (dist < minDistA) { + minDistA = dist; + Vec3.copy(posA, p); + } + } + }); + + eachIntraBondedAtom(unit, idxB, (_, idx) => { + if (isHydrogen(structure, unit, elements[idx], 'polar')) { + pos(elements[idx], p); + const dist = Vec3.distance(p, pA); + if (dist < minDistB) { + minDistB = dist; + Vec3.copy(posB, p); + } + } + }); + } else { + Vec3.set(posA, x[a[edgeIndex]], y[a[edgeIndex]], z[a[edgeIndex]]); + Vec3.set(posB, x[b[edgeIndex]], y[b[edgeIndex]], z[b[edgeIndex]]); + } }, style: (edgeIndex: number) => LinkStyle.Dashed, radius: (edgeIndex: number) => { - location.element = unit.elements[members[offsets[a[edgeIndex]]]]; + location.element = elements[members[offsets[a[edgeIndex]]]]; const sizeA = theme.size.size(location); - location.element = unit.elements[members[offsets[b[edgeIndex]]]]; + location.element = elements[members[offsets[b[edgeIndex]]]]; const sizeB = theme.size.size(location); return Math.min(sizeA, sizeB) * sizeFactor; }, @@ -65,7 +109,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: if (parentDisplay === 'stub') { const f = a[edgeIndex]; for (let i = offsets[f], il = offsets[f + 1]; i < il; ++i) { - const e = unit.elements[members[offsets[i]]]; + const e = elements[members[offsets[i]]]; if (!SortedArray.has(childUnit.elements, e)) return true; } } else if (parentDisplay === 'full' || parentDisplay === 'between') { @@ -74,13 +118,13 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: const fA = a[edgeIndex]; for (let i = offsets[fA], il = offsets[fA + 1]; i < il; ++i) { - const eA = unit.elements[members[offsets[i]]]; + const eA = elements[members[offsets[i]]]; if (!SortedArray.has(childUnit.elements, eA)) flagA = true; } const fB = b[edgeIndex]; for (let i = offsets[fB], il = offsets[fB + 1]; i < il; ++i) { - const eB = unit.elements[members[offsets[i]]]; + const eB = elements[members[offsets[i]]]; if (!SortedArray.has(childUnit.elements, eB)) flagB = true; } @@ -127,6 +171,8 @@ export function InteractionsIntraUnitVisual(materialId: number): UnitsVisual<Int newProps.dashScale !== currentProps.dashScale || newProps.dashCap !== currentProps.dashCap || newProps.radialSegments !== currentProps.radialSegments || + newProps.ignoreHydrogens !== currentProps.ignoreHydrogens || + newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant || newProps.parentDisplay !== currentProps.parentDisplay ); diff --git a/src/mol-model-props/computed/representations/shared.ts b/src/mol-model-props/computed/representations/shared.ts index 61d70bf07..35193e7ef 100644 --- a/src/mol-model-props/computed/representations/shared.ts +++ b/src/mol-model-props/computed/representations/shared.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -10,6 +10,8 @@ export const InteractionsSharedParams = { sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }), dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }), dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }), + ignoreHydrogens: PD.Boolean(false), + ignoreHydrogensVariant: PD.Select('all', PD.arrayToOptions(['all', 'non-polar'] as const)), includeParent: PD.Boolean(false), parentDisplay: PD.Select('stub', PD.arrayToOptions(['stub', 'full', 'between'] as const), { description: 'Only has an effect when "includeParent" is enabled. "Stub" shows just the child side of interactions to the parent. "Full" shows both sides of interactions to the parent. "Between" shows only interactions to the parent.' }), }; diff --git a/src/mol-repr/structure/visual/util/common.ts b/src/mol-repr/structure/visual/util/common.ts index 20dd1da69..8141b0918 100644 --- a/src/mol-repr/structure/visual/util/common.ts +++ b/src/mol-repr/structure/visual/util/common.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -314,12 +314,14 @@ export function getStructureConformationAndRadius(structure: Structure, sizeThem } const _H = AtomicNumbers['H']; -export function isHydrogen(structure: Structure, unit: Unit, element: ElementIndex, variant: 'all' | 'non-polar') { +export function isHydrogen(structure: Structure, unit: Unit, element: ElementIndex, variant: 'all' | 'non-polar' | 'polar') { if (Unit.isCoarse(unit)) return false; - return ( - unit.model.atomicHierarchy.derived.atom.atomicNumber[element] === _H && - (variant === 'all' || !hasPolarNeighbour(structure, unit, SortedArray.indexOf(unit.elements, element) as StructureElement.UnitIndex)) - ); + if (unit.model.atomicHierarchy.derived.atom.atomicNumber[element] !== _H) return false; + if (variant === 'all') return true; + const polar = hasPolarNeighbour(structure, unit, SortedArray.indexOf(unit.elements, element) as StructureElement.UnitIndex); + if (polar && variant === 'polar') return true; + if (!polar && variant === 'non-polar') return true; + return false; } export function isH(atomicNumber: ArrayLike<number>, element: ElementIndex) { return atomicNumber[element] === _H; -- GitLab