geometry.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Fred Ludlow <Fred.Ludlow@astx.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { degToRad } from '../../../mol-math/misc';
  8. import { Vec3 } from '../../../mol-math/linear-algebra';
  9. import { Structure, Unit, StructureElement } from '../../../mol-model/structure';
  10. import { eachBondedAtom, typeSymbol } from './util';
  11. import { Elements } from '../../../mol-model/structure/model/properties/atomic/types';
  12. /**
  13. * Numbering mostly inline with coordination number from VSEPR,
  14. * breaks with `SquarePlanar = 7`
  15. */
  16. export const enum AtomGeometry {
  17. Spherical = 0,
  18. Terminal = 1,
  19. Linear = 2,
  20. Trigonal = 3,
  21. Tetrahedral = 4,
  22. TrigonalBiPyramidal = 5,
  23. Octahedral = 6,
  24. SquarePlanar = 7, // Okay, it breaks down somewhere!
  25. Unknown = 8
  26. }
  27. export function geometryLabel(geometry: AtomGeometry): string {
  28. switch (geometry) {
  29. case AtomGeometry.Spherical:
  30. return 'Spherical'
  31. case AtomGeometry.Terminal:
  32. return 'Terminal'
  33. case AtomGeometry.Linear:
  34. return 'Linear'
  35. case AtomGeometry.Trigonal:
  36. return 'Trigonal'
  37. case AtomGeometry.Tetrahedral:
  38. return 'Tetrahedral'
  39. case AtomGeometry.TrigonalBiPyramidal:
  40. return 'Trigonal Bi-Pyramidal'
  41. case AtomGeometry.Octahedral:
  42. return 'Octahedral'
  43. case AtomGeometry.SquarePlanar:
  44. return 'Square Planar'
  45. case AtomGeometry.Unknown:
  46. return 'Unknown'
  47. }
  48. }
  49. export function assignGeometry (totalCoordination: number): AtomGeometry {
  50. switch (totalCoordination) {
  51. case 0: return AtomGeometry.Spherical
  52. case 1: return AtomGeometry.Terminal
  53. case 2: return AtomGeometry.Linear
  54. case 3: return AtomGeometry.Trigonal
  55. case 4: return AtomGeometry.Tetrahedral
  56. default: return AtomGeometry.Unknown
  57. }
  58. }
  59. export const AtomGeometryAngles = new Map<AtomGeometry, number>([
  60. [ AtomGeometry.Linear, degToRad(180) ],
  61. [ AtomGeometry.Trigonal, degToRad(120) ],
  62. [ AtomGeometry.Tetrahedral, degToRad(109.4721) ],
  63. [ AtomGeometry.Octahedral, degToRad(90) ]
  64. ])
  65. // tmp objects for `calcAngles` and `calcPlaneAngle`
  66. const tmpDir1 = Vec3()
  67. const tmpDir2 = Vec3()
  68. const tmpPosA = Vec3()
  69. const tmpPosB = Vec3()
  70. const tmpPosX = Vec3()
  71. /**
  72. * Calculate the angles x-a1-a2 for all x where x is a heavy atom (not H) bonded to ap1.
  73. */
  74. export function calcAngles (structure: Structure, unitA: Unit.Atomic, indexA: StructureElement.UnitIndex, unitB: Unit.Atomic, indexB: StructureElement.UnitIndex): number[] {
  75. const angles: number[] = []
  76. unitA.conformation.position(unitA.elements[indexA], tmpPosA)
  77. unitB.conformation.position(unitB.elements[indexB], tmpPosB)
  78. Vec3.sub(tmpDir1, tmpPosB, tmpPosA)
  79. eachBondedAtom(structure, unitA, indexA, (unitX: Unit.Atomic, indexX: StructureElement.UnitIndex) => {
  80. if (typeSymbol(unitX, indexX) !== Elements.H) {
  81. unitX.conformation.position(unitX.elements[indexX], tmpPosX)
  82. Vec3.sub(tmpDir2, tmpPosX, tmpPosA)
  83. angles.push(Vec3.angle(tmpDir1, tmpDir2))
  84. }
  85. })
  86. return angles
  87. }
  88. /**
  89. * Find two neighbours of ap1 to define a plane (if possible) and
  90. * measure angle out of plane to ap2
  91. * @param {AtomProxy} ap1 First atom (angle centre)
  92. * @param {AtomProxy} ap2 Second atom (out-of-plane)
  93. * @return {number} Angle from plane to second atom
  94. */
  95. export function calcPlaneAngle (structure: Structure, unitA: Unit.Atomic, indexA: StructureElement.UnitIndex, unitB: Unit.Atomic, indexB: StructureElement.UnitIndex): number | undefined {
  96. unitA.conformation.position(unitA.elements[indexA], tmpPosA)
  97. unitB.conformation.position(unitB.elements[indexB], tmpPosB)
  98. Vec3.sub(tmpDir1, tmpPosB, tmpPosA)
  99. const neighbours = [Vec3(), Vec3()]
  100. let ni = 0
  101. let unitX1: Unit.Atomic | undefined
  102. let indexX1: StructureElement.UnitIndex | undefined
  103. eachBondedAtom(structure, unitA, indexA, (unitX: Unit.Atomic, indexX: StructureElement.UnitIndex) => {
  104. if (ni > 1) return
  105. if (typeSymbol(unitX, indexX) !== Elements.H) {
  106. unitX1 = unitX
  107. indexX1 = indexX
  108. unitX.conformation.position(unitX.elements[indexX], tmpPosX)
  109. Vec3.sub(neighbours[ni++], tmpPosX, tmpPosA)
  110. }
  111. })
  112. if (ni === 1 && unitX1 && indexX1) {
  113. eachBondedAtom(structure, unitX1, indexX1, (unitX: Unit.Atomic, indexX: StructureElement.UnitIndex) => {
  114. if (ni > 1) return
  115. if (unitX === unitA && indexX === indexA) return
  116. if (typeSymbol(unitX, indexX) !== Elements.H) {
  117. unitX.conformation.position(unitX.elements[indexX], tmpPosX)
  118. Vec3.sub(neighbours[ni++], tmpPosX, tmpPosA)
  119. }
  120. })
  121. }
  122. if (ni !== 2) {
  123. return
  124. }
  125. Vec3.cross(tmpDir2, neighbours[0], neighbours[1])
  126. return Math.abs((Math.PI / 2) - Vec3.angle(tmpDir2, tmpDir1))
  127. }