loci.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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 { Link } from './structure/structure/unit/links'
  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 { capitalize } from '../mol-util/string';
  15. import { PrincipalAxes } from '../mol-math/linear-algebra/matrix/principal-axes';
  16. /** A Loci that includes every loci */
  17. export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
  18. export type EveryLoci = typeof EveryLoci
  19. export function isEveryLoci(x?: Loci): x is EveryLoci {
  20. return !!x && x.kind === 'every-loci';
  21. }
  22. /** A Loci that is empty */
  23. export const EmptyLoci = { kind: 'empty-loci' as 'empty-loci' }
  24. export type EmptyLoci = typeof EmptyLoci
  25. export function isEmptyLoci(x?: Loci): x is EmptyLoci {
  26. return !!x && x.kind === 'empty-loci';
  27. }
  28. /** A generic data loci */
  29. export interface DataLoci {
  30. readonly kind: 'data-loci',
  31. readonly data: any,
  32. readonly tag: string
  33. readonly indices: OrderedSet<number>
  34. }
  35. export function isDataLoci(x?: Loci): x is DataLoci {
  36. return !!x && x.kind === 'data-loci';
  37. }
  38. export function areDataLociEqual(a: DataLoci, b: DataLoci) {
  39. return a.data === b.data && a.tag === b.tag && OrderedSet.areEqual(a.indices, b.indices)
  40. }
  41. export function isDataLociEmpty(loci: DataLoci) {
  42. return OrderedSet.size(loci.indices) === 0 ? true : false
  43. }
  44. export function createDataLoci(data: any, tag: string, indices: OrderedSet<number>): DataLoci {
  45. return { kind: 'data-loci', data, tag, indices }
  46. }
  47. export { Loci }
  48. type Loci = StructureElement.Loci | Structure.Loci | Link.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci
  49. namespace Loci {
  50. export type Pair = { lociA: Loci, lociB: Loci }
  51. export type Triple = { lociA: Loci, lociB: Loci, lociC: Loci }
  52. export type Quad = { lociA: Loci, lociB: Loci, lociC: Loci, lociD: Loci }
  53. export function areEqual(lociA: Loci, lociB: Loci) {
  54. if (isEveryLoci(lociA) && isEveryLoci(lociB)) return true
  55. if (isEmptyLoci(lociA) && isEmptyLoci(lociB)) return true
  56. if (isDataLoci(lociA) && isDataLoci(lociB)) {
  57. return areDataLociEqual(lociA, lociB)
  58. }
  59. if (Structure.isLoci(lociA) && Structure.isLoci(lociB)) {
  60. return Structure.areLociEqual(lociA, lociB)
  61. }
  62. if (StructureElement.Loci.is(lociA) && StructureElement.Loci.is(lociB)) {
  63. return StructureElement.Loci.areEqual(lociA, lociB)
  64. }
  65. if (Link.isLoci(lociA) && Link.isLoci(lociB)) {
  66. return Link.areLociEqual(lociA, lociB)
  67. }
  68. if (Shape.isLoci(lociA) && Shape.isLoci(lociB)) {
  69. return Shape.areLociEqual(lociA, lociB)
  70. }
  71. if (ShapeGroup.isLoci(lociA) && ShapeGroup.isLoci(lociB)) {
  72. return ShapeGroup.areLociEqual(lociA, lociB)
  73. }
  74. return false
  75. }
  76. export function isEvery(loci?: Loci): loci is EveryLoci {
  77. return !!loci && loci.kind === 'every-loci';
  78. }
  79. export function isEmpty(loci: Loci): loci is EmptyLoci {
  80. if (isEveryLoci(loci)) return false
  81. if (isEmptyLoci(loci)) return true
  82. if (isDataLoci(loci)) return isDataLociEmpty(loci)
  83. if (Structure.isLoci(loci)) return Structure.isLociEmpty(loci)
  84. if (StructureElement.Loci.is(loci)) return StructureElement.Loci.isEmpty(loci)
  85. if (Link.isLoci(loci)) return Link.isLociEmpty(loci)
  86. if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci)
  87. if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci)
  88. return false
  89. }
  90. export function remap<T>(loci: Loci, data: T) {
  91. if (data instanceof Structure) {
  92. if (StructureElement.Loci.is(loci)) {
  93. loci = StructureElement.Loci.remap(loci, data)
  94. } else if (Structure.isLoci(loci)) {
  95. loci = Structure.remapLoci(loci, data)
  96. } else if (Link.isLoci(loci)) {
  97. loci = Link.remapLoci(loci, data)
  98. }
  99. }
  100. return loci
  101. }
  102. const sphereHelper = new CentroidHelper(), tempPos = Vec3.zero();
  103. export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D): Sphere3D | undefined {
  104. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  105. if (!boundingSphere) boundingSphere = Sphere3D()
  106. sphereHelper.reset();
  107. if (loci.kind === 'structure-loci') {
  108. return Sphere3D.copy(boundingSphere, loci.structure.boundary.sphere)
  109. } else if (loci.kind === 'element-loci') {
  110. return StructureElement.Loci.getBoundary(loci).sphere;
  111. } else if (loci.kind === 'link-loci') {
  112. for (const e of loci.links) {
  113. e.aUnit.conformation.position(e.aUnit.elements[e.aIndex], tempPos);
  114. sphereHelper.includeStep(tempPos);
  115. e.bUnit.conformation.position(e.bUnit.elements[e.bIndex], tempPos);
  116. sphereHelper.includeStep(tempPos);
  117. }
  118. sphereHelper.finishedIncludeStep();
  119. for (const e of loci.links) {
  120. e.aUnit.conformation.position(e.aUnit.elements[e.aIndex], tempPos);
  121. sphereHelper.radiusStep(tempPos);
  122. e.aUnit.conformation.position(e.bUnit.elements[e.bIndex], tempPos);
  123. sphereHelper.radiusStep(tempPos);
  124. }
  125. } else if (loci.kind === 'shape-loci') {
  126. // TODO
  127. return void 0;
  128. } else if (loci.kind === 'group-loci') {
  129. // TODO
  130. return void 0;
  131. } else if (loci.kind === 'data-loci') {
  132. // TODO maybe add loci.getBoundingSphere()???
  133. return void 0;
  134. }
  135. Vec3.copy(boundingSphere.center, sphereHelper.center)
  136. boundingSphere.radius = Math.sqrt(sphereHelper.radiusSq)
  137. return boundingSphere
  138. }
  139. const tmpSphere3D = Sphere3D.zero()
  140. export function getCenter(loci: Loci, center?: Vec3): Vec3 | undefined {
  141. const boundingSphere = getBoundingSphere(loci, tmpSphere3D)
  142. return boundingSphere ? Vec3.copy(center || Vec3.zero(), boundingSphere.center) : undefined
  143. }
  144. export function getPrincipalAxes(loci: Loci): PrincipalAxes | undefined {
  145. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  146. if (loci.kind === 'structure-loci') {
  147. return StructureElement.Loci.getPrincipalAxes(Structure.toStructureElementLoci(loci.structure))
  148. } else if (loci.kind === 'element-loci') {
  149. return StructureElement.Loci.getPrincipalAxes(loci)
  150. } else if (loci.kind === 'link-loci') {
  151. // TODO
  152. return void 0;
  153. } else if (loci.kind === 'shape-loci') {
  154. // TODO
  155. return void 0;
  156. } else if (loci.kind === 'group-loci') {
  157. // TODO
  158. return void 0;
  159. } else if (loci.kind === 'data-loci') {
  160. // TODO maybe add loci.getPrincipalAxes()???
  161. return void 0;
  162. }
  163. }
  164. //
  165. const Granularity = {
  166. 'element': (loci: Loci) => loci,
  167. 'residue': (loci: Loci) => {
  168. return StructureElement.Loci.is(loci)
  169. ? StructureElement.Loci.extendToWholeResidues(loci, true)
  170. : loci
  171. },
  172. 'chain': (loci: Loci) => {
  173. return StructureElement.Loci.is(loci)
  174. ? StructureElement.Loci.extendToWholeChains(loci)
  175. : loci
  176. },
  177. 'structure': (loci: Loci) => {
  178. return StructureElement.Loci.is(loci)
  179. ? Structure.toStructureElementLoci(loci.structure)
  180. : loci
  181. }
  182. }
  183. export type Granularity = keyof typeof Granularity
  184. export const GranularityOptions = Object.keys(Granularity).map(n => [n, capitalize(n)]) as [Granularity, string][]
  185. export function applyGranularity(loci: Loci, granularity: Granularity) {
  186. return Granularity[granularity](loci)
  187. }
  188. /**
  189. * Converts structure related loci to StructureElement.Loci and applies
  190. * granularity if given
  191. */
  192. export function normalize(loci: Loci, granularity?: Granularity) {
  193. if (granularity !== 'element' && Link.isLoci(loci)) {
  194. // convert Link.Loci to a StructureElement.Loci so granularity can be applied
  195. loci = Link.toStructureElementLoci(loci)
  196. }
  197. if (Structure.isLoci(loci)) {
  198. // convert to StructureElement.Loci
  199. loci = Structure.toStructureElementLoci(loci.structure)
  200. }
  201. if (StructureElement.Loci.is(loci)) {
  202. // ensure the root structure is used
  203. loci = StructureElement.Loci.remap(loci, loci.structure.root)
  204. }
  205. if (granularity) {
  206. // needs to be applied AFTER remapping to root
  207. loci = applyGranularity(loci, granularity)
  208. }
  209. return loci
  210. }
  211. }