loci.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /**
  2. * Copyright (c) 2018-2020 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 { Vec3 } from '../mol-math/linear-algebra';
  11. import { Structure } from './structure/structure';
  12. import { PrincipalAxes } from '../mol-math/linear-algebra/matrix/principal-axes';
  13. import { ParamDefinition } from '../mol-util/param-definition';
  14. import { shallowEqual } from '../mol-util';
  15. import { FiniteArray } from '../mol-util/type-helpers';
  16. import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
  17. import { stringToWords } from '../mol-util/string';
  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, E = unknown> {
  32. readonly kind: 'data-loci',
  33. readonly tag: string
  34. readonly data: T,
  35. readonly elements: ReadonlyArray<E>
  36. getBoundingSphere(boundingSphere: Sphere3D): Sphere3D
  37. getLabel(): string
  38. }
  39. export function isDataLoci(x?: Loci): x is DataLoci {
  40. return !!x && x.kind === 'data-loci';
  41. }
  42. export function areDataLociEqual(a: DataLoci, b: DataLoci) {
  43. // use shallowEqual to allow simple data objects that are contructed on-the-fly
  44. if (!shallowEqual(a.data, b.data) || a.tag !== b.tag) return false
  45. if (a.elements.length !== b.elements.length) return false
  46. for (let i = 0, il = a.elements.length; i < il; ++i) {
  47. if (!shallowEqual(a.elements[i], b.elements[i])) return false
  48. }
  49. return true
  50. }
  51. export function isDataLociEmpty(loci: DataLoci) {
  52. return loci.elements.length === 0 ? true : false
  53. }
  54. export function DataLoci<T = unknown, E = unknown>(tag: string, data: T, elements: ReadonlyArray<E>, getBoundingSphere: DataLoci<T, E>['getBoundingSphere'], getLabel: DataLoci<T, E>['getLabel']): DataLoci<T, E> {
  55. return { kind: 'data-loci', tag, data, elements, getBoundingSphere, getLabel }
  56. }
  57. export { Loci }
  58. type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci
  59. namespace Loci {
  60. export interface Bundle<L extends number> { loci: FiniteArray<Loci, L> }
  61. const boundaryHelper = new BoundaryHelper('98');
  62. export function getBundleBoundingSphere(bundle: Bundle<any>): Sphere3D {
  63. const spheres = bundle.loci.map(l => getBoundingSphere(l)).filter(s => !!s) as Sphere3D[]
  64. boundaryHelper.reset();
  65. for (const s of spheres) boundaryHelper.includeSphereStep(s.center, s.radius);
  66. boundaryHelper.finishedIncludeStep();
  67. for (const s of spheres) boundaryHelper.radiusSphereStep(s.center, s.radius);
  68. return boundaryHelper.getSphere();
  69. }
  70. export function areEqual(lociA: Loci, lociB: Loci) {
  71. if (isEveryLoci(lociA) && isEveryLoci(lociB)) return true
  72. if (isEmptyLoci(lociA) && isEmptyLoci(lociB)) return true
  73. if (isDataLoci(lociA) && isDataLoci(lociB)) {
  74. return areDataLociEqual(lociA, lociB)
  75. }
  76. if (Structure.isLoci(lociA) && Structure.isLoci(lociB)) {
  77. return Structure.areLociEqual(lociA, lociB)
  78. }
  79. if (StructureElement.Loci.is(lociA) && StructureElement.Loci.is(lociB)) {
  80. return StructureElement.Loci.areEqual(lociA, lociB)
  81. }
  82. if (Bond.isLoci(lociA) && Bond.isLoci(lociB)) {
  83. return Bond.areLociEqual(lociA, lociB)
  84. }
  85. if (Shape.isLoci(lociA) && Shape.isLoci(lociB)) {
  86. return Shape.areLociEqual(lociA, lociB)
  87. }
  88. if (ShapeGroup.isLoci(lociA) && ShapeGroup.isLoci(lociB)) {
  89. return ShapeGroup.areLociEqual(lociA, lociB)
  90. }
  91. return false
  92. }
  93. export function isEvery(loci?: Loci): loci is EveryLoci {
  94. return !!loci && loci.kind === 'every-loci';
  95. }
  96. export function isEmpty(loci: Loci): loci is EmptyLoci {
  97. if (isEveryLoci(loci)) return false
  98. if (isEmptyLoci(loci)) return true
  99. if (isDataLoci(loci)) return isDataLociEmpty(loci)
  100. if (Structure.isLoci(loci)) return Structure.isLociEmpty(loci)
  101. if (StructureElement.Loci.is(loci)) return StructureElement.Loci.isEmpty(loci)
  102. if (Bond.isLoci(loci)) return Bond.isLociEmpty(loci)
  103. if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci)
  104. if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci)
  105. return false
  106. }
  107. export function remap<T>(loci: Loci, data: T) {
  108. if (data instanceof Structure) {
  109. if (StructureElement.Loci.is(loci)) {
  110. loci = StructureElement.Loci.remap(loci, data)
  111. } else if (Structure.isLoci(loci)) {
  112. loci = Structure.remapLoci(loci, data)
  113. } else if (Bond.isLoci(loci)) {
  114. loci = Bond.remapLoci(loci, data)
  115. }
  116. }
  117. return loci
  118. }
  119. export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D): Sphere3D | undefined {
  120. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  121. if (!boundingSphere) boundingSphere = Sphere3D()
  122. if (loci.kind === 'structure-loci') {
  123. return Sphere3D.copy(boundingSphere, loci.structure.boundary.sphere)
  124. } else if (loci.kind === 'element-loci') {
  125. return Sphere3D.copy(boundingSphere, StructureElement.Loci.getBoundary(loci).sphere);
  126. } else if (loci.kind === 'bond-loci') {
  127. return Bond.getBoundingSphere(loci, boundingSphere)
  128. } else if (loci.kind === 'shape-loci') {
  129. return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
  130. } else if (loci.kind === 'group-loci') {
  131. return ShapeGroup.getBoundingSphere(loci, boundingSphere)
  132. } else if (loci.kind === 'data-loci') {
  133. return loci.getBoundingSphere(boundingSphere)
  134. }
  135. }
  136. const tmpSphere3D = Sphere3D.zero()
  137. export function getCenter(loci: Loci, center?: Vec3): Vec3 | undefined {
  138. const boundingSphere = getBoundingSphere(loci, tmpSphere3D)
  139. return boundingSphere ? Vec3.copy(center || Vec3(), boundingSphere.center) : undefined
  140. }
  141. export function getPrincipalAxes(loci: Loci): PrincipalAxes | undefined {
  142. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  143. if (loci.kind === 'structure-loci') {
  144. return StructureElement.Loci.getPrincipalAxes(Structure.toStructureElementLoci(loci.structure))
  145. } else if (loci.kind === 'element-loci') {
  146. return StructureElement.Loci.getPrincipalAxes(loci)
  147. } else if (loci.kind === 'bond-loci') {
  148. // TODO
  149. return void 0;
  150. } else if (loci.kind === 'shape-loci') {
  151. // TODO
  152. return void 0;
  153. } else if (loci.kind === 'group-loci') {
  154. // TODO
  155. return void 0;
  156. } else if (loci.kind === 'data-loci') {
  157. // TODO maybe add loci.getPrincipalAxes()???
  158. return void 0;
  159. }
  160. }
  161. //
  162. const Granularity = {
  163. 'element': (loci: Loci) => loci,
  164. 'residue': (loci: Loci) => {
  165. return StructureElement.Loci.is(loci)
  166. ? StructureElement.Loci.extendToWholeResidues(loci, true)
  167. : loci
  168. },
  169. 'chain': (loci: Loci) => {
  170. return StructureElement.Loci.is(loci)
  171. ? StructureElement.Loci.extendToWholeChains(loci)
  172. : loci
  173. },
  174. 'elementInstances': (loci: Loci) => {
  175. return StructureElement.Loci.is(loci)
  176. ? StructureElement.Loci.extendToAllInstances(loci)
  177. : loci
  178. },
  179. 'residueInstances': (loci: Loci) => {
  180. return StructureElement.Loci.is(loci)
  181. ? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
  182. : loci
  183. },
  184. 'chainInstances': (loci: Loci) => {
  185. return StructureElement.Loci.is(loci)
  186. ? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
  187. : loci
  188. },
  189. 'entity': (loci: Loci) => {
  190. return StructureElement.Loci.is(loci)
  191. ? StructureElement.Loci.extendToWholeEntities(loci)
  192. : loci
  193. },
  194. 'model': (loci: Loci) => {
  195. return StructureElement.Loci.is(loci)
  196. ? StructureElement.Loci.extendToWholeModels(loci)
  197. : loci
  198. },
  199. 'structure': (loci: Loci) => {
  200. return StructureElement.Loci.is(loci)
  201. ? Structure.toStructureElementLoci(loci.structure)
  202. : ShapeGroup.isLoci(loci)
  203. ? Shape.Loci(loci.shape)
  204. : loci
  205. },
  206. }
  207. export type Granularity = keyof typeof Granularity
  208. export const GranularityOptions = ParamDefinition.objectToOptions(Granularity, k => {
  209. switch (k) {
  210. case 'element': return'Atom/Coarse Element'
  211. case 'structure': return'Structure/Shape'
  212. default: return stringToWords(k)
  213. }
  214. });
  215. export function applyGranularity(loci: Loci, granularity: Granularity) {
  216. return Granularity[granularity](loci)
  217. }
  218. /**
  219. * Converts structure related loci to StructureElement.Loci and applies
  220. * granularity if given
  221. */
  222. export function normalize(loci: Loci, granularity?: Granularity) {
  223. if (granularity !== 'element' && Bond.isLoci(loci)) {
  224. // convert Bond.Loci to a StructureElement.Loci so granularity can be applied
  225. loci = Bond.toStructureElementLoci(loci)
  226. }
  227. if (Structure.isLoci(loci)) {
  228. // convert to StructureElement.Loci
  229. loci = Structure.toStructureElementLoci(loci.structure)
  230. }
  231. if (StructureElement.Loci.is(loci)) {
  232. // ensure the root structure is used
  233. loci = StructureElement.Loci.remap(loci, loci.structure.root)
  234. }
  235. if (granularity) {
  236. // needs to be applied AFTER remapping to root
  237. loci = applyGranularity(loci, granularity)
  238. }
  239. return loci
  240. }
  241. }