bond-inter-unit-cylinder.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  7. import { VisualContext } from '../../visual';
  8. import { Structure, StructureElement, Bond, Unit } from '../../../mol-model/structure';
  9. import { Theme } from '../../../mol-theme/theme';
  10. import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
  11. import { Vec3 } from '../../../mol-math/linear-algebra';
  12. import { BitFlags, arrayEqual } from '../../../mol-util';
  13. import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps, LinkStyle } from './util/link';
  14. import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual, ComplexCylindersParams, ComplexCylindersVisual } from '../complex-visual';
  15. import { VisualUpdateState } from '../../util';
  16. import { BondType } from '../../../mol-model/structure/model/types';
  17. import { BondCylinderParams, BondIterator, getInterBondLoci, eachInterBond, makeInterBondIgnoreTest } from './util/bond';
  18. import { Sphere3D } from '../../../mol-math/geometry';
  19. import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
  20. import { WebGLContext } from '../../../mol-gl/webgl/context';
  21. import { SortedArray } from '../../../mol-data/int/sorted-array';
  22. const tmpRefPosBondIt = new Bond.ElementBondIterator();
  23. function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, index: StructureElement.UnitIndex) {
  24. tmpRefPosBondIt.setElement(structure, unit, index);
  25. while (tmpRefPosBondIt.hasNext) {
  26. const bA = tmpRefPosBondIt.move();
  27. bA.otherUnit.conformation.position(bA.otherUnit.elements[bA.otherIndex], pos);
  28. return pos;
  29. }
  30. return null;
  31. }
  32. const tmpRef = Vec3();
  33. function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>): LinkBuilderProps {
  34. const locE = StructureElement.Location.create(structure);
  35. const locB = Bond.Location(structure, undefined, undefined, structure, undefined, undefined);
  36. const bonds = structure.interUnitBonds;
  37. const { edgeCount, edges } = bonds;
  38. const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds, multipleBonds } = props;
  39. const mbOff = multipleBonds === 'off';
  40. const mbSymmetric = multipleBonds === 'symmetric';
  41. const delta = Vec3();
  42. let stub: undefined | ((edgeIndex: number) => boolean);
  43. const { child } = structure;
  44. if (props.includeParent && child) {
  45. stub = (edgeIndex: number) => {
  46. const b = edges[edgeIndex];
  47. const childUnitA = child.unitMap.get(b.unitA);
  48. const childUnitB = child.unitMap.get(b.unitB);
  49. const unitA = structure.unitMap.get(b.unitA);
  50. const eA = unitA.elements[b.indexA];
  51. const unitB = structure.unitMap.get(b.unitB);
  52. const eB = unitB.elements[b.indexB];
  53. return (
  54. childUnitA && SortedArray.has(childUnitA.elements, eA) &&
  55. (!childUnitB || !SortedArray.has(childUnitB.elements, eB))
  56. );
  57. };
  58. }
  59. const radius = (edgeIndex: number) => {
  60. const b = edges[edgeIndex];
  61. locB.aUnit = structure.unitMap.get(b.unitA);
  62. locB.aIndex = b.indexA;
  63. locB.bUnit = structure.unitMap.get(b.unitB);
  64. locB.bIndex = b.indexB;
  65. return theme.size.size(locB) * sizeFactor;
  66. };
  67. const radiusA = (edgeIndex: number) => {
  68. const b = edges[edgeIndex];
  69. locE.unit = structure.unitMap.get(b.unitA);
  70. locE.element = locE.unit.elements[b.indexA];
  71. return theme.size.size(locE) * sizeFactor;
  72. };
  73. const radiusB = (edgeIndex: number) => {
  74. const b = edges[edgeIndex];
  75. locE.unit = structure.unitMap.get(b.unitB);
  76. locE.element = locE.unit.elements[b.indexB];
  77. return theme.size.size(locE) * sizeFactor;
  78. };
  79. return {
  80. linkCount: edgeCount,
  81. referencePosition: (edgeIndex: number) => {
  82. const b = edges[edgeIndex];
  83. let unitA: Unit.Atomic, unitB: Unit.Atomic;
  84. let indexA: StructureElement.UnitIndex, indexB: StructureElement.UnitIndex;
  85. if (b.unitA < b.unitB) {
  86. unitA = structure.unitMap.get(b.unitA) as Unit.Atomic;
  87. unitB = structure.unitMap.get(b.unitB) as Unit.Atomic;
  88. indexA = b.indexA;
  89. indexB = b.indexB;
  90. } else if (b.unitA > b.unitB) {
  91. unitA = structure.unitMap.get(b.unitB) as Unit.Atomic;
  92. unitB = structure.unitMap.get(b.unitA) as Unit.Atomic;
  93. indexA = b.indexB;
  94. indexB = b.indexA;
  95. } else {
  96. throw new Error('same units in createInterUnitBondCylinderMesh');
  97. }
  98. return setRefPosition(tmpRef, structure, unitA, indexA) || setRefPosition(tmpRef, structure, unitB, indexB);
  99. },
  100. position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
  101. const b = edges[edgeIndex];
  102. const uA = structure.unitMap.get(b.unitA);
  103. const uB = structure.unitMap.get(b.unitB);
  104. uA.conformation.position(uA.elements[b.indexA], posA);
  105. uB.conformation.position(uB.elements[b.indexB], posB);
  106. if (adjustCylinderLength) {
  107. const rA = radiusA(edgeIndex), rB = radiusB(edgeIndex);
  108. const r = Math.min(rA, rB) * sizeAspectRatio;
  109. const oA = Math.sqrt(Math.max(0, rA * rA - r * r)) - 0.05;
  110. const oB = Math.sqrt(Math.max(0, rB * rB - r * r)) - 0.05;
  111. if (oA <= 0.01 && oB <= 0.01) return;
  112. Vec3.normalize(delta, Vec3.sub(delta, posB, posA));
  113. Vec3.scaleAndAdd(posA, posA, delta, oA);
  114. Vec3.scaleAndAdd(posB, posB, delta, -oB);
  115. }
  116. },
  117. style: (edgeIndex: number) => {
  118. const o = edges[edgeIndex].props.order;
  119. const f = BitFlags.create(edges[edgeIndex].props.flag);
  120. if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
  121. // show metallic coordinations and hydrogen bonds with dashed cylinders
  122. return LinkStyle.Dashed;
  123. } else if (o === 3) {
  124. return mbOff ? LinkStyle.Solid :
  125. mbSymmetric ? LinkStyle.Triple :
  126. LinkStyle.OffsetTriple;
  127. } else if (aromaticBonds && BondType.is(f, BondType.Flag.Aromatic)) {
  128. return LinkStyle.Aromatic;
  129. }
  130. return (o !== 2 || mbOff) ? LinkStyle.Solid :
  131. mbSymmetric ? LinkStyle.Double :
  132. LinkStyle.OffsetDouble;
  133. },
  134. radius: (edgeIndex: number) => {
  135. return radius(edgeIndex) * sizeAspectRatio;
  136. },
  137. ignore: makeInterBondIgnoreTest(structure, props),
  138. stub
  139. };
  140. }
  141. function createInterUnitBondCylinderImpostors(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>, cylinders?: Cylinders) {
  142. if (!structure.interUnitBonds.edgeCount) return Cylinders.createEmpty(cylinders);
  143. const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
  144. const m = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
  145. const { child } = structure;
  146. const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
  147. m.setBoundingSphere(sphere);
  148. return m;
  149. }
  150. function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>, mesh?: Mesh) {
  151. if (!structure.interUnitBonds.edgeCount) return Mesh.createEmpty(mesh);
  152. const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
  153. const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
  154. const { child } = structure;
  155. const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
  156. m.setBoundingSphere(sphere);
  157. return m;
  158. }
  159. export const InterUnitBondCylinderParams = {
  160. ...ComplexMeshParams,
  161. ...ComplexCylindersParams,
  162. ...BondCylinderParams,
  163. sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
  164. sizeAspectRatio: PD.Numeric(2 / 3, { min: 0, max: 3, step: 0.01 }),
  165. tryUseImpostor: PD.Boolean(true),
  166. includeParent: PD.Boolean(false),
  167. };
  168. export type InterUnitBondCylinderParams = typeof InterUnitBondCylinderParams
  169. export function InterUnitBondCylinderVisual(materialId: number, structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) {
  170. return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
  171. ? InterUnitBondCylinderImpostorVisual(materialId)
  172. : InterUnitBondCylinderMeshVisual(materialId);
  173. }
  174. export function InterUnitBondCylinderImpostorVisual(materialId: number): ComplexVisual<InterUnitBondCylinderParams> {
  175. return ComplexCylindersVisual<InterUnitBondCylinderParams>({
  176. defaultProps: PD.getDefaultValues(InterUnitBondCylinderParams),
  177. createGeometry: createInterUnitBondCylinderImpostors,
  178. createLocationIterator: BondIterator.fromStructure,
  179. getLoci: getInterBondLoci,
  180. eachLocation: eachInterBond,
  181. setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
  182. state.createGeometry = (
  183. newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
  184. newProps.linkScale !== currentProps.linkScale ||
  185. newProps.linkSpacing !== currentProps.linkSpacing ||
  186. newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
  187. newProps.linkCap !== currentProps.linkCap ||
  188. newProps.aromaticScale !== currentProps.aromaticScale ||
  189. newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
  190. newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
  191. newProps.dashCount !== currentProps.dashCount ||
  192. newProps.dashScale !== currentProps.dashScale ||
  193. newProps.dashCap !== currentProps.dashCap ||
  194. newProps.stubCap !== currentProps.stubCap ||
  195. !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
  196. !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
  197. newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
  198. newProps.multipleBonds !== currentProps.multipleBonds
  199. );
  200. if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
  201. state.createGeometry = true;
  202. state.updateTransform = true;
  203. state.updateColor = true;
  204. state.updateSize = true;
  205. }
  206. },
  207. mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
  208. return !props.tryUseImpostor || !webgl;
  209. }
  210. }, materialId);
  211. }
  212. export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisual<InterUnitBondCylinderParams> {
  213. return ComplexMeshVisual<InterUnitBondCylinderParams>({
  214. defaultProps: PD.getDefaultValues(InterUnitBondCylinderParams),
  215. createGeometry: createInterUnitBondCylinderMesh,
  216. createLocationIterator: BondIterator.fromStructure,
  217. getLoci: getInterBondLoci,
  218. eachLocation: eachInterBond,
  219. setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
  220. state.createGeometry = (
  221. newProps.sizeFactor !== currentProps.sizeFactor ||
  222. newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
  223. newProps.radialSegments !== currentProps.radialSegments ||
  224. newProps.linkScale !== currentProps.linkScale ||
  225. newProps.linkSpacing !== currentProps.linkSpacing ||
  226. newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
  227. newProps.linkCap !== currentProps.linkCap ||
  228. newProps.aromaticScale !== currentProps.aromaticScale ||
  229. newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
  230. newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
  231. newProps.dashCount !== currentProps.dashCount ||
  232. newProps.dashScale !== currentProps.dashScale ||
  233. newProps.dashCap !== currentProps.dashCap ||
  234. newProps.stubCap !== currentProps.stubCap ||
  235. !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
  236. !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
  237. newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
  238. newProps.multipleBonds !== currentProps.multipleBonds
  239. );
  240. if (newStructure.interUnitBonds !== currentStructure.interUnitBonds) {
  241. state.createGeometry = true;
  242. state.updateTransform = true;
  243. state.updateColor = true;
  244. state.updateSize = true;
  245. }
  246. },
  247. mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
  248. return props.tryUseImpostor && !!webgl;
  249. }
  250. }, materialId);
  251. }