loci.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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. import { Volume } from './volume/volume';
  19. /** A Loci that includes every loci */
  20. export const EveryLoci = { kind: 'every-loci' as 'every-loci' };
  21. export type EveryLoci = typeof EveryLoci
  22. export function isEveryLoci(x?: Loci): x is EveryLoci {
  23. return !!x && x.kind === 'every-loci';
  24. }
  25. /** A Loci that is empty */
  26. export const EmptyLoci = { kind: 'empty-loci' as 'empty-loci' };
  27. export type EmptyLoci = typeof EmptyLoci
  28. export function isEmptyLoci(x?: Loci): x is EmptyLoci {
  29. return !!x && x.kind === 'empty-loci';
  30. }
  31. /** A generic data loci */
  32. export interface DataLoci<T = unknown, E = unknown> {
  33. readonly kind: 'data-loci',
  34. readonly tag: string
  35. readonly data: T,
  36. readonly elements: ReadonlyArray<E>
  37. getBoundingSphere(boundingSphere: Sphere3D): Sphere3D
  38. getLabel(): string
  39. }
  40. export function isDataLoci(x?: Loci): x is DataLoci {
  41. return !!x && x.kind === 'data-loci';
  42. }
  43. export function areDataLociEqual(a: DataLoci, b: DataLoci) {
  44. // use shallowEqual to allow simple data objects that are contructed on-the-fly
  45. if (!shallowEqual(a.data, b.data) || a.tag !== b.tag) return false;
  46. if (a.elements.length !== b.elements.length) return false;
  47. for (let i = 0, il = a.elements.length; i < il; ++i) {
  48. if (!shallowEqual(a.elements[i], b.elements[i])) return false;
  49. }
  50. return true;
  51. }
  52. export function isDataLociEmpty(loci: DataLoci) {
  53. return loci.elements.length === 0 ? true : false;
  54. }
  55. 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> {
  56. return { kind: 'data-loci', tag, data, elements, getBoundingSphere, getLabel };
  57. }
  58. export { Loci };
  59. type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci | Volume.Loci | Volume.Isosurface.Loci | Volume.Cell.Loci
  60. namespace Loci {
  61. export interface Bundle<L extends number> { loci: FiniteArray<Loci, L> }
  62. const boundaryHelper = new BoundaryHelper('98');
  63. export function getBundleBoundingSphere(bundle: Bundle<any>): Sphere3D {
  64. const spheres = bundle.loci.map(l => getBoundingSphere(l)).filter(s => !!s) as Sphere3D[];
  65. boundaryHelper.reset();
  66. for (const s of spheres) boundaryHelper.includePositionRadius(s.center, s.radius);
  67. boundaryHelper.finishedIncludeStep();
  68. for (const s of spheres) boundaryHelper.radiusPositionRadius(s.center, s.radius);
  69. return boundaryHelper.getSphere();
  70. }
  71. export function areEqual(lociA: Loci, lociB: Loci) {
  72. if (isEveryLoci(lociA) && isEveryLoci(lociB)) return true;
  73. if (isEmptyLoci(lociA) && isEmptyLoci(lociB)) return true;
  74. if (isDataLoci(lociA) && isDataLoci(lociB)) {
  75. return areDataLociEqual(lociA, lociB);
  76. }
  77. if (Structure.isLoci(lociA) && Structure.isLoci(lociB)) {
  78. return Structure.areLociEqual(lociA, lociB);
  79. }
  80. if (StructureElement.Loci.is(lociA) && StructureElement.Loci.is(lociB)) {
  81. return StructureElement.Loci.areEqual(lociA, lociB);
  82. }
  83. if (Bond.isLoci(lociA) && Bond.isLoci(lociB)) {
  84. return Bond.areLociEqual(lociA, lociB);
  85. }
  86. if (Shape.isLoci(lociA) && Shape.isLoci(lociB)) {
  87. return Shape.areLociEqual(lociA, lociB);
  88. }
  89. if (ShapeGroup.isLoci(lociA) && ShapeGroup.isLoci(lociB)) {
  90. return ShapeGroup.areLociEqual(lociA, lociB);
  91. }
  92. if (Volume.isLoci(lociA) && Volume.isLoci(lociB)) {
  93. return Volume.areLociEqual(lociA, lociB);
  94. }
  95. if (Volume.Isosurface.isLoci(lociA) && Volume.Isosurface.isLoci(lociB)) {
  96. return Volume.Isosurface.areLociEqual(lociA, lociB);
  97. }
  98. if (Volume.Cell.isLoci(lociA) && Volume.Cell.isLoci(lociB)) {
  99. return Volume.Cell.areLociEqual(lociA, lociB);
  100. }
  101. return false;
  102. }
  103. export function isEvery(loci?: Loci): loci is EveryLoci {
  104. return !!loci && loci.kind === 'every-loci';
  105. }
  106. export function isEmpty(loci: Loci): loci is EmptyLoci {
  107. if (isEveryLoci(loci)) return false;
  108. if (isEmptyLoci(loci)) return true;
  109. if (isDataLoci(loci)) return isDataLociEmpty(loci);
  110. if (Structure.isLoci(loci)) return Structure.isLociEmpty(loci);
  111. if (StructureElement.Loci.is(loci)) return StructureElement.Loci.isEmpty(loci);
  112. if (Bond.isLoci(loci)) return Bond.isLociEmpty(loci);
  113. if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci);
  114. if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci);
  115. if (Volume.isLoci(loci)) return Volume.isLociEmpty(loci);
  116. if (Volume.Isosurface.isLoci(loci)) return Volume.Isosurface.isLociEmpty(loci);
  117. if (Volume.Cell.isLoci(loci)) return Volume.Cell.isLociEmpty(loci);
  118. return false;
  119. }
  120. export function remap<T>(loci: Loci, data: T) {
  121. if (data instanceof Structure) {
  122. if (StructureElement.Loci.is(loci)) {
  123. loci = StructureElement.Loci.remap(loci, data);
  124. } else if (Structure.isLoci(loci)) {
  125. loci = Structure.remapLoci(loci, data);
  126. } else if (Bond.isLoci(loci)) {
  127. loci = Bond.remapLoci(loci, data);
  128. }
  129. }
  130. return loci;
  131. }
  132. export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D): Sphere3D | undefined {
  133. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  134. if (!boundingSphere) boundingSphere = Sphere3D();
  135. if (loci.kind === 'structure-loci') {
  136. return Sphere3D.copy(boundingSphere, loci.structure.boundary.sphere);
  137. } else if (loci.kind === 'element-loci') {
  138. return Sphere3D.copy(boundingSphere, StructureElement.Loci.getBoundary(loci).sphere);
  139. } else if (loci.kind === 'bond-loci') {
  140. return Bond.getBoundingSphere(loci, boundingSphere);
  141. } else if (loci.kind === 'shape-loci') {
  142. return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere);
  143. } else if (loci.kind === 'group-loci') {
  144. return ShapeGroup.getBoundingSphere(loci, boundingSphere);
  145. } else if (loci.kind === 'data-loci') {
  146. return loci.getBoundingSphere(boundingSphere);
  147. } else if (loci.kind === 'volume-loci') {
  148. return Volume.getBoundingSphere(loci.volume, boundingSphere);
  149. } else if (loci.kind === 'isosurface-loci') {
  150. return Volume.Isosurface.getBoundingSphere(loci.volume, loci.isoValue, boundingSphere);
  151. } else if (loci.kind === 'cell-loci') {
  152. return Volume.Cell.getBoundingSphere(loci.volume, loci.indices, boundingSphere);
  153. }
  154. }
  155. const tmpSphere3D = Sphere3D.zero();
  156. export function getCenter(loci: Loci, center?: Vec3): Vec3 | undefined {
  157. const boundingSphere = getBoundingSphere(loci, tmpSphere3D);
  158. return boundingSphere ? Vec3.copy(center || Vec3(), boundingSphere.center) : undefined;
  159. }
  160. export function getPrincipalAxes(loci: Loci): PrincipalAxes | undefined {
  161. if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
  162. if (loci.kind === 'structure-loci') {
  163. return StructureElement.Loci.getPrincipalAxes(Structure.toStructureElementLoci(loci.structure));
  164. } else if (loci.kind === 'element-loci') {
  165. return StructureElement.Loci.getPrincipalAxes(loci);
  166. } else if (loci.kind === 'bond-loci') {
  167. // TODO
  168. return void 0;
  169. } else if (loci.kind === 'shape-loci') {
  170. // TODO
  171. return void 0;
  172. } else if (loci.kind === 'group-loci') {
  173. // TODO
  174. return void 0;
  175. } else if (loci.kind === 'data-loci') {
  176. // TODO maybe add loci.getPrincipalAxes()???
  177. return void 0;
  178. } else if (loci.kind === 'volume-loci') {
  179. // TODO
  180. return void 0;
  181. } else if (loci.kind === 'isosurface-loci') {
  182. // TODO
  183. return void 0;
  184. } else if (loci.kind === 'cell-loci') {
  185. // TODO
  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. 'entity': (loci: Loci) => {
  203. return StructureElement.Loci.is(loci)
  204. ? StructureElement.Loci.extendToWholeEntities(loci)
  205. : loci;
  206. },
  207. 'model': (loci: Loci) => {
  208. return StructureElement.Loci.is(loci)
  209. ? StructureElement.Loci.extendToWholeModels(loci)
  210. : loci;
  211. },
  212. 'structure': (loci: Loci) => {
  213. return StructureElement.Loci.is(loci)
  214. ? Structure.toStructureElementLoci(loci.structure)
  215. : ShapeGroup.isLoci(loci)
  216. ? Shape.Loci(loci.shape)
  217. : loci;
  218. },
  219. 'elementInstances': (loci: Loci) => {
  220. return StructureElement.Loci.is(loci)
  221. ? StructureElement.Loci.extendToAllInstances(loci)
  222. : loci;
  223. },
  224. 'residueInstances': (loci: Loci) => {
  225. return StructureElement.Loci.is(loci)
  226. ? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
  227. : loci;
  228. },
  229. 'chainInstances': (loci: Loci) => {
  230. return StructureElement.Loci.is(loci)
  231. ? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
  232. : loci;
  233. },
  234. };
  235. export type Granularity = keyof typeof Granularity
  236. export const GranularityOptions = ParamDefinition.objectToOptions(Granularity, k => {
  237. switch (k) {
  238. case 'element': return'Atom/Coarse Element';
  239. case 'elementInstances': return ['Atom/Coarse Element Instances', 'With Symmetry'];
  240. case 'structure': return'Structure/Shape';
  241. default: return k.indexOf('Instances')
  242. ? [stringToWords(k), 'With Symmetry'] : stringToWords(k);
  243. }
  244. });
  245. export function applyGranularity(loci: Loci, granularity: Granularity) {
  246. return Granularity[granularity](loci);
  247. }
  248. /**
  249. * Converts structure related loci to StructureElement.Loci and applies
  250. * granularity if given
  251. */
  252. export function normalize(loci: Loci, granularity?: Granularity) {
  253. if (granularity !== 'element' && Bond.isLoci(loci)) {
  254. // convert Bond.Loci to a StructureElement.Loci so granularity can be applied
  255. loci = Bond.toStructureElementLoci(loci);
  256. }
  257. if (Structure.isLoci(loci)) {
  258. // convert to StructureElement.Loci
  259. loci = Structure.toStructureElementLoci(loci.structure);
  260. }
  261. if (StructureElement.Loci.is(loci)) {
  262. // ensure the root structure is used
  263. loci = StructureElement.Loci.remap(loci, loci.structure.root);
  264. }
  265. if (granularity) {
  266. // needs to be applied AFTER remapping to root
  267. loci = applyGranularity(loci, granularity);
  268. }
  269. return loci;
  270. }
  271. }