loci.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { StructureElement } from './structure'
  7. import { Bond } from './structure/structure/unit/bonds'
  8. import { Shape, ShapeGroup } from './shape';
  9. import { Sphere3D } from '../mol-math/geometry';
  10. import { CentroidHelper } from '../mol-math/geometry/centroid-helper';
  11. import { Vec3 } from '../mol-math/linear-algebra';
  12. import { OrderedSet } from '../mol-data/int';
  13. import { Structure } from './structure/structure';
  14. import { PrincipalAxes } from '../mol-math/linear-algebra/matrix/principal-axes';
  15. import { ParamDefinition } from '../mol-util/param-definition';
  16. import { Interactions } from '../mol-model-props/computed/interactions/interactions';
  17. import { Features } from '../mol-model-props/computed/interactions/features';
  18. /** A Loci that includes every loci */
  19. export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
  20. export type EveryLoci = typeof EveryLoci
  21. export function isEveryLoci(x?: Loci): x is EveryLoci {
  22. return !!x && x.kind === 'every-loci';
  23. }
  24. /** A Loci that is empty */
  25. export const EmptyLoci = { kind: 'empty-loci' as 'empty-loci' }
  26. export type EmptyLoci = typeof EmptyLoci
  27. export function isEmptyLoci(x?: Loci): x is EmptyLoci {
  28. return !!x && x.kind === 'empty-loci';
  29. }
  30. /** A generic data loci */
  31. export interface DataLoci<T = unknown> {
  32. readonly kind: 'data-loci',
  33. readonly data: T,
  34. readonly tag: string
  35. readonly indices: OrderedSet<number>
  36. }
  37. export function isDataLoci(x?: Loci): x is DataLoci {
  38. return !!x && x.kind === 'data-loci';
  39. }
  40. export function areDataLociEqual(a: DataLoci, b: DataLoci) {
  41. return a.data === b.data && a.tag === b.tag && OrderedSet.areEqual(a.indices, b.indices)
  42. }
  43. export function isDataLociEmpty(loci: DataLoci) {
  44. return OrderedSet.size(loci.indices) === 0 ? true : false
  45. }
  46. export function createDataLoci<T = unknown>(data: T, tag: string, indices: OrderedSet<number>): DataLoci<T> {
  47. return { kind: 'data-loci', data, tag, indices }
  48. }
  49. export { Loci }
  50. type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | Interactions.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci
  51. namespace Loci {
  52. interface FiniteArray<T, L extends number = number> extends ReadonlyArray<T> { length: L };
  53. export interface Bundle<L extends number> { loci: FiniteArray<Loci, L> }
  54. export function areEqual(lociA: Loci, lociB: Loci) {
  55. if (isEveryLoci(lociA) && isEveryLoci(lociB)) return true
  56. if (isEmptyLoci(lociA) && isEmptyLoci(lociB)) return true
  57. if (isDataLoci(lociA) && isDataLoci(lociB)) {
  58. return areDataLociEqual(lociA, lociB)
  59. }
  60. if (Structure.isLoci(lociA) && Structure.isLoci(lociB)) {
  61. return Structure.areLociEqual(lociA, lociB)
  62. }
  63. if (StructureElement.Loci.is(lociA) && StructureElement.Loci.is(lociB)) {
  64. return StructureElement.Loci.areEqual(lociA, lociB)
  65. }
  66. if (Bond.isLoci(lociA) && Bond.isLoci(lociB)) {
  67. return Bond.areLociEqual(lociA, lociB)
  68. }
  69. if (Interactions.isLoci(lociA) && Interactions.isLoci(lociB)) {
  70. return Interactions.areLociEqual(lociA, lociB)
  71. }
  72. if (Shape.isLoci(lociA) && Shape.isLoci(lociB)) {
  73. return Shape.areLociEqual(lociA, lociB)
  74. }
  75. if (ShapeGroup.isLoci(lociA) && ShapeGroup.isLoci(lociB)) {
  76. return ShapeGroup.areLociEqual(lociA, lociB)
  77. }
  78. return false
  79. }
  80. export function isEvery(loci?: Loci): loci is EveryLoci {
  81. return !!loci && loci.kind === 'every-loci';
  82. }
  83. export function isEmpty(loci: Loci): loci is EmptyLoci {
  84. if (isEveryLoci(loci)) return false
  85. if (isEmptyLoci(loci)) return true
  86. if (isDataLoci(loci)) return isDataLociEmpty(loci)
  87. if (Structure.isLoci(loci)) return Structure.isLociEmpty(loci)
  88. if (StructureElement.Loci.is(loci)) return StructureElement.Loci.isEmpty(loci)
  89. if (Bond.isLoci(loci)) return Bond.isLociEmpty(loci)
  90. if (Interactions.isLoci(loci)) return Interactions.isLociEmpty(loci)
  91. if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci)
  92. if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci)
  93. return false
  94. }
  95. export function remap<T>(loci: Loci, data: T) {
  96. if (data instanceof Structure) {
  97. if (StructureElement.Loci.is(loci)) {
  98. loci = StructureElement.Loci.remap(loci, data)
  99. } else if (Structure.isLoci(loci)) {
  100. loci = Structure.remapLoci(loci, data)
  101. } else if (Bond.isLoci(loci)) {
  102. loci = Bond.remapLoci(loci, data)
  103. } else if (Interactions.isLoci(loci)) {
  104. // TODO might be too expensive
  105. // loci = Interactions.remapLoci(loci, data)
  106. }
  107. }
  108. return loci
  109. }
  110. const sphereHelper = new CentroidHelper(), tmpPos = Vec3.zero();
  111. export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D): Sphere3D | undefined {
  112. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  113. if (!boundingSphere) boundingSphere = Sphere3D()
  114. sphereHelper.reset();
  115. if (loci.kind === 'structure-loci') {
  116. return Sphere3D.copy(boundingSphere, loci.structure.boundary.sphere)
  117. } else if (loci.kind === 'element-loci') {
  118. return Sphere3D.copy(boundingSphere, StructureElement.Loci.getBoundary(loci).sphere);
  119. } else if (loci.kind === 'bond-loci') {
  120. for (const e of loci.bonds) {
  121. e.aUnit.conformation.position(e.aUnit.elements[e.aIndex], tmpPos);
  122. sphereHelper.includeStep(tmpPos);
  123. e.bUnit.conformation.position(e.bUnit.elements[e.bIndex], tmpPos);
  124. sphereHelper.includeStep(tmpPos);
  125. }
  126. sphereHelper.finishedIncludeStep();
  127. for (const e of loci.bonds) {
  128. e.aUnit.conformation.position(e.aUnit.elements[e.aIndex], tmpPos);
  129. sphereHelper.radiusStep(tmpPos);
  130. e.aUnit.conformation.position(e.bUnit.elements[e.bIndex], tmpPos);
  131. sphereHelper.radiusStep(tmpPos);
  132. }
  133. } else if (loci.kind === 'interaction-loci') {
  134. const { unitsFeatures } = loci.interactions
  135. for (const e of loci.contacts) {
  136. Features.setPosition(tmpPos, e.unitA, e.indexA, unitsFeatures.get(e.unitA.id))
  137. sphereHelper.includeStep(tmpPos)
  138. Features.setPosition(tmpPos, e.unitB, e.indexB, unitsFeatures.get(e.unitB.id))
  139. sphereHelper.includeStep(tmpPos);
  140. }
  141. sphereHelper.finishedIncludeStep();
  142. for (const e of loci.contacts) {
  143. Features.setPosition(tmpPos, e.unitA, e.indexA, unitsFeatures.get(e.unitA.id))
  144. sphereHelper.radiusStep(tmpPos)
  145. Features.setPosition(tmpPos, e.unitB, e.indexB, unitsFeatures.get(e.unitB.id))
  146. sphereHelper.radiusStep(tmpPos);
  147. }
  148. } else if (loci.kind === 'shape-loci') {
  149. return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
  150. } else if (loci.kind === 'group-loci') {
  151. // TODO
  152. return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
  153. } else if (loci.kind === 'data-loci') {
  154. // TODO maybe add loci.getBoundingSphere()???
  155. return void 0;
  156. }
  157. Vec3.copy(boundingSphere.center, sphereHelper.center)
  158. boundingSphere.radius = Math.sqrt(sphereHelper.radiusSq)
  159. return boundingSphere
  160. }
  161. const tmpSphere3D = Sphere3D.zero()
  162. export function getCenter(loci: Loci, center?: Vec3): Vec3 | undefined {
  163. const boundingSphere = getBoundingSphere(loci, tmpSphere3D)
  164. return boundingSphere ? Vec3.copy(center || Vec3.zero(), boundingSphere.center) : undefined
  165. }
  166. export function getPrincipalAxes(loci: Loci): PrincipalAxes | undefined {
  167. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  168. if (loci.kind === 'structure-loci') {
  169. return StructureElement.Loci.getPrincipalAxes(Structure.toStructureElementLoci(loci.structure))
  170. } else if (loci.kind === 'element-loci') {
  171. return StructureElement.Loci.getPrincipalAxes(loci)
  172. } else if (loci.kind === 'bond-loci') {
  173. // TODO
  174. return void 0;
  175. } else if (loci.kind === 'interaction-loci') {
  176. // TODO
  177. return void 0;
  178. } else if (loci.kind === 'shape-loci') {
  179. // TODO
  180. return void 0;
  181. } else if (loci.kind === 'group-loci') {
  182. // TODO
  183. return void 0;
  184. } else if (loci.kind === 'data-loci') {
  185. // TODO maybe add loci.getPrincipalAxes()???
  186. return void 0;
  187. }
  188. }
  189. //
  190. const Granularity = {
  191. 'element': (loci: Loci) => loci,
  192. 'residue': (loci: Loci) => {
  193. return StructureElement.Loci.is(loci)
  194. ? StructureElement.Loci.extendToWholeResidues(loci, true)
  195. : loci
  196. },
  197. 'chain': (loci: Loci) => {
  198. return StructureElement.Loci.is(loci)
  199. ? StructureElement.Loci.extendToWholeChains(loci)
  200. : loci
  201. },
  202. 'elementInstances': (loci: Loci) => {
  203. return StructureElement.Loci.is(loci)
  204. ? StructureElement.Loci.extendToAllInstances(loci)
  205. : loci
  206. },
  207. 'residueInstances': (loci: Loci) => {
  208. return StructureElement.Loci.is(loci)
  209. ? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
  210. : loci
  211. },
  212. 'chainInstances': (loci: Loci) => {
  213. return StructureElement.Loci.is(loci)
  214. ? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
  215. : loci
  216. },
  217. 'entity': (loci: Loci) => {
  218. return StructureElement.Loci.is(loci)
  219. ? StructureElement.Loci.extendToWholeEntities(loci)
  220. : loci
  221. },
  222. 'model': (loci: Loci) => {
  223. return StructureElement.Loci.is(loci)
  224. ? StructureElement.Loci.extendToWholeModels(loci)
  225. : loci
  226. },
  227. 'structure': (loci: Loci) => {
  228. return StructureElement.Loci.is(loci)
  229. ? Structure.toStructureElementLoci(loci.structure)
  230. : loci
  231. }
  232. }
  233. export type Granularity = keyof typeof Granularity
  234. export const GranularityOptions = ParamDefinition.objectToOptions(Granularity);
  235. export function applyGranularity(loci: Loci, granularity: Granularity) {
  236. return Granularity[granularity](loci)
  237. }
  238. /**
  239. * Converts structure related loci to StructureElement.Loci and applies
  240. * granularity if given
  241. */
  242. export function normalize(loci: Loci, granularity?: Granularity) {
  243. if (granularity !== 'element' && Bond.isLoci(loci)) {
  244. // convert Bond.Loci to a StructureElement.Loci so granularity can be applied
  245. loci = Bond.toStructureElementLoci(loci)
  246. }
  247. if (Structure.isLoci(loci)) {
  248. // convert to StructureElement.Loci
  249. loci = Structure.toStructureElementLoci(loci.structure)
  250. }
  251. if (StructureElement.Loci.is(loci)) {
  252. // ensure the root structure is used
  253. loci = StructureElement.Loci.remap(loci, loci.structure.root)
  254. }
  255. if (granularity) {
  256. // needs to be applied AFTER remapping to root
  257. loci = applyGranularity(loci, granularity)
  258. }
  259. return loci
  260. }
  261. }