link.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Vec3 } from 'mol-math/linear-algebra';
  7. import { RuntimeContext } from 'mol-task';
  8. import { LinkType } from 'mol-model/structure/model/types';
  9. import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
  10. import { Unit, StructureElement, Structure, Link } from 'mol-model/structure';
  11. import { SelectParam, RangeParam, NumberParam, paramDefaultValues } from 'mol-util/parameter';
  12. import { Mesh } from 'mol-geo/geometry/mesh/mesh';
  13. import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
  14. import { CylinderProps } from 'mol-geo/primitive/cylinder';
  15. import { addFixedCountDashedCylinder, addCylinder, addDoubleCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
  16. import { LocationIterator } from 'mol-geo/util/location-iterator';
  17. export const LinkCylinderParams = {
  18. sizeTheme: SelectParam<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
  19. sizeValue: NumberParam('Size Value', '', 1, 0, 20, 0.1),
  20. sizeFactor: NumberParam('Size Factor', '', 1, 0, 10, 0.1),
  21. linkScale: RangeParam('Link Scale', '', 0.4, 0, 1, 0.1),
  22. linkSpacing: RangeParam('Link Spacing', '', 1, 0, 2, 0.01),
  23. linkRadius: RangeParam('Link Radius', '', 0.25, 0, 10, 0.05),
  24. radialSegments: NumberParam('Radial Segments', '', 16, 3, 56, 1),
  25. }
  26. export const DefaultLinkCylinderProps = paramDefaultValues(LinkCylinderParams)
  27. export type LinkCylinderProps = typeof DefaultLinkCylinderProps
  28. const tmpShiftV12 = Vec3.zero()
  29. const tmpShiftV13 = Vec3.zero()
  30. /** Calculate 'shift' direction that is perpendiculat to v1 - v2 and goes through v3 */
  31. export function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | null) {
  32. Vec3.normalize(tmpShiftV12, Vec3.sub(tmpShiftV12, v1, v2))
  33. if (v3 !== null) {
  34. Vec3.sub(tmpShiftV13, v1, v3)
  35. } else {
  36. Vec3.copy(tmpShiftV13, v1) // no reference point, use v1
  37. }
  38. Vec3.normalize(tmpShiftV13, tmpShiftV13)
  39. // ensure v13 and v12 are not colinear
  40. let dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
  41. if (1 - Math.abs(dp) < 1e-5) {
  42. Vec3.set(tmpShiftV13, 1, 0, 0)
  43. dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
  44. if (1 - Math.abs(dp) < 1e-5) {
  45. Vec3.set(tmpShiftV13, 0, 1, 0)
  46. dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
  47. }
  48. }
  49. Vec3.setMagnitude(tmpShiftV12, tmpShiftV12, dp)
  50. Vec3.sub(tmpShiftV13, tmpShiftV13, tmpShiftV12)
  51. return Vec3.normalize(out, tmpShiftV13)
  52. }
  53. export interface LinkCylinderMeshBuilderProps {
  54. linkCount: number
  55. referencePosition(edgeIndex: number): Vec3 | null
  56. position(posA: Vec3, posB: Vec3, edgeIndex: number): void
  57. order(edgeIndex: number): number
  58. flags(edgeIndex: number): LinkType
  59. radius(edgeIndex: number): number
  60. }
  61. /**
  62. * Each edge is included twice to allow for coloring/picking
  63. * the half closer to the first vertex, i.e. vertex a.
  64. */
  65. export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: LinkCylinderMeshBuilderProps, props: LinkCylinderProps, mesh?: Mesh) {
  66. const { linkCount, referencePosition, position, order, flags, radius } = linkBuilder
  67. if (!linkCount) return Mesh.createEmpty(mesh)
  68. const { linkScale, linkSpacing, radialSegments } = props
  69. const vertexCountEstimate = radialSegments * 2 * linkCount * 2
  70. const meshBuilder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 4, mesh)
  71. const va = Vec3.zero()
  72. const vb = Vec3.zero()
  73. const vShift = Vec3.zero()
  74. const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments }
  75. for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
  76. position(va, vb, edgeIndex)
  77. const linkRadius = radius(edgeIndex)
  78. const o = order(edgeIndex)
  79. const f = flags(edgeIndex)
  80. meshBuilder.setGroup(edgeIndex)
  81. if (LinkType.is(f, LinkType.Flag.MetallicCoordination) || LinkType.is(f, LinkType.Flag.Hydrogen)) {
  82. // show metall coordinations and hydrogen bonds with dashed cylinders
  83. cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius / 3
  84. addFixedCountDashedCylinder(meshBuilder, va, vb, 0.5, 7, cylinderProps)
  85. } else if (o === 2 || o === 3) {
  86. // show bonds with order 2 or 3 using 2 or 3 parallel cylinders
  87. const multiRadius = linkRadius * (linkScale / (0.5 * o))
  88. const absOffset = (linkRadius - multiRadius) * linkSpacing
  89. calculateShiftDir(vShift, va, vb, referencePosition(edgeIndex))
  90. Vec3.setMagnitude(vShift, vShift, absOffset)
  91. cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius
  92. if (o === 3) addCylinder(meshBuilder, va, vb, 0.5, cylinderProps)
  93. addDoubleCylinder(meshBuilder, va, vb, 0.5, vShift, cylinderProps)
  94. } else {
  95. cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius
  96. addCylinder(meshBuilder, va, vb, 0.5, cylinderProps)
  97. }
  98. if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) {
  99. await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: linkCount });
  100. }
  101. }
  102. return meshBuilder.getMesh()
  103. }
  104. export namespace LinkIterator {
  105. export function fromGroup(group: Unit.SymmetryGroup): LocationIterator {
  106. const unit = group.units[0]
  107. const groupCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0
  108. const instanceCount = group.units.length
  109. const location = StructureElement.create()
  110. const getLocation = (groupIndex: number, instanceIndex: number) => {
  111. const unit = group.units[instanceIndex]
  112. location.unit = unit
  113. location.element = unit.elements[(unit as Unit.Atomic).links.a[groupIndex]]
  114. return location
  115. }
  116. return LocationIterator(groupCount, instanceCount, getLocation)
  117. }
  118. export function fromStructure(structure: Structure): LocationIterator {
  119. const groupCount = structure.links.bondCount
  120. const instanceCount = 1
  121. const location = Link.Location()
  122. const getLocation = (groupIndex: number) => {
  123. const bond = structure.links.bonds[groupIndex]
  124. location.aUnit = bond.unitA
  125. location.aIndex = bond.indexA as StructureElement.UnitIndex
  126. location.bUnit = bond.unitB
  127. location.bIndex = bond.indexB as StructureElement.UnitIndex
  128. return location
  129. }
  130. return LocationIterator(groupCount, instanceCount, getLocation, true)
  131. }
  132. }