polymer.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import { Unit, ElementIndex, StructureElement, Bond, Structure, ResidueIndex } from '../../../../mol-model/structure';
  8. import { SortedRanges } from '../../../../mol-data/int/sorted-ranges';
  9. import { OrderedSet, Interval, SortedArray } from '../../../../mol-data/int';
  10. import { EmptyLoci, Loci } from '../../../../mol-model/loci';
  11. import { LocationIterator } from '../../../../mol-geo/util/location-iterator';
  12. import { PickingId } from '../../../../mol-geo/geometry/picking';
  13. import { getResidueLoci, StructureGroup } from './common';
  14. export * from './polymer/backbone';
  15. export * from './polymer/gap-iterator';
  16. export * from './polymer/trace-iterator';
  17. export * from './polymer/curve-segment';
  18. export const StandardTension = 0.5;
  19. export const HelixTension = 0.9;
  20. export const StandardShift = 0.5;
  21. export const NucleicShift = 0.3;
  22. export const OverhangFactor = 2;
  23. export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> {
  24. switch (unit.kind) {
  25. case Unit.Kind.Atomic: return unit.model.atomicRanges.polymerRanges;
  26. case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.polymerRanges;
  27. case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.polymerRanges;
  28. }
  29. }
  30. export function getGapRanges(unit: Unit): SortedRanges<ElementIndex> {
  31. switch (unit.kind) {
  32. case Unit.Kind.Atomic: return unit.model.atomicRanges.gapRanges;
  33. case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.gapRanges;
  34. case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.gapRanges;
  35. }
  36. }
  37. export namespace PolymerLocationIterator {
  38. export function fromGroup(structureGroup: StructureGroup): LocationIterator {
  39. const { group, structure } = structureGroup;
  40. const polymerElements = group.units[0].polymerElements;
  41. const groupCount = polymerElements.length;
  42. const instanceCount = group.units.length;
  43. const location = StructureElement.Location.create(structure);
  44. const getLocation = (groupIndex: number, instanceIndex: number) => {
  45. const unit = group.units[instanceIndex];
  46. location.unit = unit;
  47. location.element = polymerElements[groupIndex];
  48. return location;
  49. };
  50. return LocationIterator(groupCount, instanceCount, 1, getLocation);
  51. }
  52. }
  53. export namespace PolymerGapLocationIterator {
  54. export function fromGroup(structureGroup: StructureGroup): LocationIterator {
  55. const { group, structure } = structureGroup;
  56. const gapElements = group.units[0].gapElements;
  57. const groupCount = gapElements.length;
  58. const instanceCount = group.units.length;
  59. const location = StructureElement.Location.create(structure);
  60. const getLocation = (groupIndex: number, instanceIndex: number) => {
  61. const unit = group.units[instanceIndex];
  62. location.unit = unit;
  63. location.element = gapElements[groupIndex];
  64. return location;
  65. };
  66. return LocationIterator(groupCount, instanceCount, 1, getLocation);
  67. }
  68. }
  69. /** Return a Loci for the elements of the whole residue of a polymer element. */
  70. export function getPolymerElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
  71. const { objectId, instanceId, groupId } = pickingId;
  72. if (id === objectId) {
  73. const { structure, group } = structureGroup;
  74. const unit = group.units[instanceId];
  75. if (Unit.isAtomic(unit)) {
  76. return getResidueLoci(structure, unit, unit.polymerElements[groupId]);
  77. } else {
  78. const { elements } = unit;
  79. const elementIndex = unit.polymerElements[groupId];
  80. const unitIndex = OrderedSet.indexOf(elements, elementIndex) as StructureElement.UnitIndex | -1;
  81. if (unitIndex !== -1) {
  82. const indices = OrderedSet.ofSingleton(unitIndex);
  83. return StructureElement.Loci(structure, [{ unit, indices }]);
  84. }
  85. }
  86. }
  87. return EmptyLoci;
  88. }
  89. function tryApplyResidueInterval(offset: number, elements: SortedArray<ElementIndex>, traceElementIndex: ArrayLike<ElementIndex | -1>, apply: (interval: Interval) => boolean, r1: ResidueIndex, r2: ResidueIndex) {
  90. let start = -1, startIdx = -1;
  91. for (let rI = r1; rI <= r2; rI++) {
  92. const eI = traceElementIndex[rI];
  93. if (eI < 0) continue;
  94. start = OrderedSet.indexOf(elements, eI);
  95. if (start >= 0) {
  96. startIdx = rI;
  97. break;
  98. }
  99. }
  100. if (start < 0) {
  101. return false;
  102. }
  103. let end = start;
  104. for (let rI = r2; rI > startIdx; rI--) {
  105. const eI = traceElementIndex[rI];
  106. if (eI < 0) continue;
  107. const e = OrderedSet.indexOf(elements, eI);
  108. if (e >= 0) {
  109. end = e;
  110. break;
  111. }
  112. }
  113. return apply(Interval.ofRange(offset + start, offset + end));
  114. }
  115. export function eachAtomicUnitTracedElement(offset: number, groupSize: number, elementsSelector: (u: Unit.Atomic) => SortedArray<ElementIndex>, apply: (interval: Interval) => boolean, e: StructureElement.Loci['elements'][0]) {
  116. let changed = false;
  117. const { elements } = e.unit;
  118. const { traceElementIndex } = e.unit.model.atomicHierarchy.derived.residue;
  119. const { index: resIndex } = e.unit.model.atomicHierarchy.residueAtomSegments;
  120. const tracedElements = elementsSelector(e.unit as Unit.Atomic);
  121. if (Interval.is(e.indices)) {
  122. if (Interval.start(e.indices) === 0 && Interval.end(e.indices) === e.unit.elements.length) {
  123. // full unit here
  124. changed = apply(Interval.ofBounds(offset, offset + groupSize)) || changed;
  125. } else {
  126. const r1 = resIndex[elements[Interval.min(e.indices)]];
  127. const r2 = resIndex[elements[Interval.max(e.indices)]];
  128. changed = tryApplyResidueInterval(offset, tracedElements, traceElementIndex, apply, r1, r2) || changed;
  129. }
  130. } else {
  131. const { indices } = e;
  132. for (let i = 0, _i = indices.length; i < _i; i++) {
  133. const r1 = resIndex[elements[indices[i]]];
  134. let r2 = r1;
  135. let endI = i + 1;
  136. while (endI < _i) {
  137. const _r = resIndex[elements[indices[endI]]];
  138. if (_r - r2 > 1) break;
  139. r2 = _r;
  140. endI++;
  141. }
  142. i = endI - 1;
  143. changed = tryApplyResidueInterval(offset, tracedElements, traceElementIndex, apply, r1, r2) || changed;
  144. }
  145. }
  146. return changed;
  147. }
  148. function selectPolymerElements(u: Unit) { return u.polymerElements; }
  149. /** Mark a polymer element (e.g. part of a cartoon trace) */
  150. export function eachPolymerElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
  151. let changed = false;
  152. if (!StructureElement.Loci.is(loci)) return false;
  153. const { structure, group } = structureGroup;
  154. if (!Structure.areEquivalent(loci.structure, structure)) return false;
  155. const groupCount = group.units[0].polymerElements.length;
  156. for (const e of loci.elements) {
  157. if (!group.unitIndexMap.has(e.unit.id)) continue;
  158. const offset = group.unitIndexMap.get(e.unit.id) * groupCount; // to target unit instance
  159. if (Unit.isAtomic(e.unit)) {
  160. changed = eachAtomicUnitTracedElement(offset, groupCount, selectPolymerElements, apply, e) || changed;
  161. } else {
  162. if (Interval.is(e.indices)) {
  163. const start = offset + Interval.start(e.indices);
  164. const end = offset + Interval.end(e.indices);
  165. changed = apply(Interval.ofBounds(start, end)) || changed;
  166. } else {
  167. for (let i = 0, _i = e.indices.length; i < _i; i++) {
  168. const start = e.indices[i];
  169. let endI = i + 1;
  170. while (endI < _i && e.indices[endI] === start) endI++;
  171. i = endI - 1;
  172. const end = e.indices[i];
  173. changed = apply(Interval.ofRange(offset + start, offset + end)) || changed;
  174. }
  175. }
  176. }
  177. }
  178. return changed;
  179. }
  180. /** Return a Loci for both directions of the polymer gap element. */
  181. export function getPolymerGapElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
  182. const { objectId, instanceId, groupId } = pickingId;
  183. if (id === objectId) {
  184. const { structure, group } = structureGroup;
  185. const unit = group.units[instanceId];
  186. const unitIndexA = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId]) as StructureElement.UnitIndex;
  187. const unitIndexB = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId % 2 ? groupId - 1 : groupId + 1]) as StructureElement.UnitIndex;
  188. if (unitIndexA !== -1 && unitIndexB !== -1) {
  189. return Bond.Loci(structure, [
  190. Bond.Location(structure, unit, unitIndexA, structure, unit, unitIndexB),
  191. Bond.Location(structure, unit, unitIndexB, structure, unit, unitIndexA)
  192. ]);
  193. }
  194. }
  195. return EmptyLoci;
  196. }
  197. export function eachPolymerGapElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
  198. let changed = false;
  199. if (Bond.isLoci(loci)) {
  200. const { structure, group } = structureGroup;
  201. if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
  202. loci = Bond.remapLoci(loci, structure);
  203. const groupCount = group.units[0].gapElements.length;
  204. for (const b of loci.bonds) {
  205. const unitIdx = group.unitIndexMap.get(b.aUnit.id);
  206. if (unitIdx !== undefined) {
  207. const idxA = OrderedSet.indexOf(b.aUnit.gapElements, b.aUnit.elements[b.aIndex]);
  208. const idxB = OrderedSet.indexOf(b.bUnit.gapElements, b.bUnit.elements[b.bIndex]);
  209. if (idxA !== -1 && idxB !== -1) {
  210. if (apply(Interval.ofSingleton(unitIdx * groupCount + idxA))) changed = true;
  211. }
  212. }
  213. }
  214. } else if (StructureElement.Loci.is(loci)) {
  215. const { structure, group } = structureGroup;
  216. if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
  217. loci = StructureElement.Loci.remap(loci, structure);
  218. const groupCount = group.units[0].gapElements.length;
  219. for (const e of loci.elements) {
  220. const unitIdx = group.unitIndexMap.get(e.unit.id);
  221. if (unitIdx !== undefined) {
  222. OrderedSet.forEach(e.indices, v => {
  223. const idx = OrderedSet.indexOf(e.unit.gapElements, e.unit.elements[v]);
  224. if (idx !== -1) {
  225. if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true;
  226. }
  227. });
  228. }
  229. }
  230. }
  231. return changed;
  232. }