polymer.ts 11 KB

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