diff --git a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts index fb3521a29f0de78be028ff18880f1bfedb401c6b..dd277116de925dbc3d0d210626d797167e536e9d 100644 --- a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts @@ -9,7 +9,7 @@ import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; import { MeshBuilder } from '../../../mesh/mesh-builder'; -import { getPolymerElementCount, PolymerBackboneIterator } from './util/polymer'; +import { PolymerBackboneIterator } from './util/polymer'; import { getElementLoci, markElement, StructureElementIterator } from './util/element'; import { Vec3 } from 'mol-math/linear-algebra'; import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual'; @@ -24,7 +24,7 @@ export interface PolymerBackboneCylinderProps { } async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerBackboneCylinderProps, mesh?: Mesh) { - const polymerElementCount = getPolymerElementCount(unit) + const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) const sizeTheme = SizeTheme(props.sizeTheme) diff --git a/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts b/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts index da3d7513ccee7ab5ec10f911074b15c10e76e4e1..1e6b8e42a198d66249becde9ff3219b723a659ee 100644 --- a/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts +++ b/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts @@ -7,10 +7,9 @@ import { Unit } from 'mol-model/structure'; import { UnitsVisual } from '..'; import { RuntimeContext } from 'mol-task' -import { markElement, getElementLoci } from './util/element'; import { Mesh } from '../../../mesh/mesh'; import { MeshBuilder } from '../../../mesh/mesh-builder'; -import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment, PolymerLocationIterator } from './util/polymer'; +import { PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment, PolymerLocationIterator, getPolymerElementLoci, markPolymerElement } from './util/polymer'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types'; import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual'; @@ -34,7 +33,7 @@ export interface PolymerDirectionWedgeProps { } async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, props: PolymerDirectionWedgeProps, mesh?: Mesh) { - const polymerElementCount = getPolymerElementCount(unit) + const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) const sizeTheme = SizeTheme(props.sizeTheme) @@ -95,8 +94,8 @@ export function PolymerDirectionVisual(): UnitsVisual<PolymerDirectionProps> { defaultProps: DefaultPolymerDirectionProps, createMesh: createPolymerDirectionWedgeMesh, createLocationIterator: PolymerLocationIterator.fromGroup, - getLoci: getElementLoci, - mark: markElement, + getLoci: getPolymerElementLoci, + mark: markPolymerElement, setUpdateState: () => {} }) } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts index 3c7afa8028f1b9c1361eee3a885f8e9d95c2a8bb..e5a319c08e9e83a8c760145664228697696cc6bf 100644 --- a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts @@ -9,7 +9,7 @@ import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; import { MeshBuilder } from '../../../mesh/mesh-builder'; -import { getPolymerGapCount, PolymerGapIterator, PolymerGapLocationIterator } from './util/polymer'; +import { PolymerGapIterator, PolymerGapLocationIterator } from './util/polymer'; import { getElementLoci, markElement } from './util/element'; import { Vec3 } from 'mol-math/linear-algebra'; import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; @@ -26,7 +26,7 @@ export interface PolymerGapCylinderProps { } async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerGapCylinderProps, mesh?: Mesh) { - const polymerGapCount = getPolymerGapCount(unit) + const polymerGapCount = unit.gapElements.length if (!polymerGapCount) return Mesh.createEmpty(mesh) const sizeTheme = SizeTheme(props.sizeTheme) diff --git a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts index f5fa0efd09789fcd4a264c561273e9e59c63ddce..1a37e4d9809fa189a779c2dcb2b969ccc382dcba 100644 --- a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts +++ b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts @@ -7,10 +7,9 @@ import { Unit } from 'mol-model/structure'; import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' -import { markElement, getElementLoci } from './util/element'; import { Mesh } from '../../../mesh/mesh'; import { MeshBuilder } from '../../../mesh/mesh-builder'; -import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment, PolymerLocationIterator } from './util/polymer'; +import { PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment, PolymerLocationIterator, getPolymerElementLoci, markPolymerElement } from './util/polymer'; import { SecondaryStructureType, isNucleic } from 'mol-model/structure/model/types'; import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; @@ -28,7 +27,7 @@ export interface PolymerTraceMeshProps { // TODO handle polymer ends properly async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: PolymerTraceMeshProps, mesh?: Mesh) { - const polymerElementCount = getPolymerElementCount(unit) + const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) const sizeTheme = SizeTheme(props.sizeTheme) @@ -95,8 +94,8 @@ export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> { defaultProps: DefaultPolymerTraceProps, createMesh: createPolymerTraceMesh, createLocationIterator: PolymerLocationIterator.fromGroup, - getLoci: getElementLoci, - mark: markElement, + getLoci: getPolymerElementLoci, + mark: markPolymerElement, setUpdateState: (state: MeshUpdateState, newProps: PolymerTraceProps, currentProps: PolymerTraceProps) => { state.createMesh = ( newProps.linearSegments !== currentProps.linearSegments || diff --git a/src/mol-geo/representation/structure/visual/util/polymer.ts b/src/mol-geo/representation/structure/visual/util/polymer.ts index c8a66c6a42613d6925a71a40cc0e296f852f859b..41458be2e366e10e957d6bc7af871694b9806f09 100644 --- a/src/mol-geo/representation/structure/visual/util/polymer.ts +++ b/src/mol-geo/representation/structure/visual/util/polymer.ts @@ -5,11 +5,11 @@ */ import { Unit, ElementIndex, StructureElement } from 'mol-model/structure'; -import { Segmentation, OrderedSet, Interval } from 'mol-data/int'; import SortedRanges from 'mol-data/int/sorted-ranges'; import { LocationIterator } from '../../../../util/location-iterator'; -import { getElementIndexForAtomRole } from 'mol-model/structure/util'; -import { PolymerGapIterator } from './polymer/gap-iterator'; +import { PickingId } from '../../../../util/picking'; +import { OrderedSet, Interval } from 'mol-data/int'; +import { EmptyLoci, Loci } from 'mol-model/loci'; export * from './polymer/backbone-iterator' export * from './polymer/gap-iterator' @@ -32,119 +32,69 @@ export function getGapRanges(unit: Unit): SortedRanges<ElementIndex> { } } -// polymer element - -export function getPolymerElementCount(unit: Unit) { - let count = 0 - const { elements } = unit - const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), elements) - switch (unit.kind) { - case Unit.Kind.Atomic: - const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements) - while (polymerIt.hasNext) { - const polymerSegment = polymerIt.move() - residueIt.setSegment(polymerSegment) - while (residueIt.hasNext) { - const residueSegment = residueIt.move() - const { start, end } = residueSegment - if (OrderedSet.areIntersecting(Interval.ofBounds(elements[start], elements[end - 1]), elements)) ++count - } - } - break - case Unit.Kind.Spheres: - case Unit.Kind.Gaussians: - while (polymerIt.hasNext) { - const { start, end } = polymerIt.move() - count += OrderedSet.intersectionSize(Interval.ofBounds(elements[start], elements[end - 1]), elements) - } - break - } - return count -} - -export function getPolymerElementIndices(unit: Unit) { - const indices: ElementIndex[] = [] - const { elements, model } = unit - const { residueAtomSegments } = unit.model.atomicHierarchy - const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), elements) - switch (unit.kind) { - case Unit.Kind.Atomic: - const residueIt = Segmentation.transientSegments(residueAtomSegments, elements) - while (polymerIt.hasNext) { - const polymerSegment = polymerIt.move() - residueIt.setSegment(polymerSegment) - while (residueIt.hasNext) { - const residueSegment = residueIt.move() - const { start, end, index } = residueSegment - if (OrderedSet.areIntersecting(Interval.ofBounds(elements[start], elements[end - 1]), elements)) { - const elementIndex = getElementIndexForAtomRole(model, index, 'trace') - indices.push(elementIndex === -1 ? residueAtomSegments.offsets[index] : elementIndex) - } - } - } - break - case Unit.Kind.Spheres: - case Unit.Kind.Gaussians: - while (polymerIt.hasNext) { - const { start, end } = polymerIt.move() - for (let i = start; i < end; ++i) { indices.push(elements[i]) } - } - break - } - return indices -} - export namespace PolymerLocationIterator { export function fromGroup(group: Unit.SymmetryGroup): LocationIterator { - const polymerElementIndices = getPolymerElementIndices(group.units[0]) - const groupCount = polymerElementIndices.length + const polymerElements = group.units[0].polymerElements + const groupCount = polymerElements.length const instanceCount = group.units.length const location = StructureElement.create() const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit - location.element = polymerElementIndices[groupIndex] + location.element = polymerElements[groupIndex] return location } return LocationIterator(groupCount, instanceCount, getLocation) } } -// polymer gap - -export function getPolymerGapCount(unit: Unit) { - let count = 0 - const { elements } = unit - const gapIt = SortedRanges.transientSegments(getGapRanges(unit), elements) - while (gapIt.hasNext) { - const { start, end } = gapIt.move() - if (OrderedSet.areIntersecting(Interval.ofBounds(elements[start], elements[end - 1]), elements)) ++count - } - return count -} - -export function getPolymerGapElementIndices(unit: Unit) { - const indices: ElementIndex[] = [] - const polymerGapIt = PolymerGapIterator(unit) - while (polymerGapIt.hasNext) { - const { centerA, centerB } = polymerGapIt.move() - indices.push(centerA.element, centerB.element) - } - return indices -} - export namespace PolymerGapLocationIterator { export function fromGroup(group: Unit.SymmetryGroup): LocationIterator { - const polymerGapElementIndices = getPolymerGapElementIndices(group.units[0]) - const groupCount = polymerGapElementIndices.length + const gapElements = group.units[0].gapElements + const groupCount = gapElements.length const instanceCount = group.units.length const location = StructureElement.create() const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit - location.element = polymerGapElementIndices[groupIndex] + location.element = gapElements[groupIndex] return location } return LocationIterator(groupCount, instanceCount, getLocation) } +} + +export function getPolymerElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) { + const { objectId, instanceId, groupId } = pickingId + if (id === objectId) { + const unit = group.units[instanceId] + const unitIndex = OrderedSet.findPredecessorIndex(unit.elements, unit.polymerElements[groupId]) as StructureElement.UnitIndex + const indices = OrderedSet.ofSingleton(unitIndex); + return StructureElement.Loci([{ unit, indices }]) + } + return EmptyLoci +} + +export function markPolymerElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) { + const groupCount = group.units[0].polymerElements.length + + let changed = false + if (StructureElement.isLoci(loci)) { + for (const e of loci.elements) { + const unitIdx = group.unitIndexMap.get(e.unit.id) + if (unitIdx !== undefined) { + if (Interval.is(e.indices)) { + const start = unitIdx * groupCount + OrderedSet.findPredecessorIndex(e.unit.polymerElements, e.unit.elements[Interval.start(e.indices)]) + const end = unitIdx * groupCount + OrderedSet.findPredecessorIndex(e.unit.polymerElements, e.unit.elements[Interval.end(e.indices)]) + if (apply(Interval.ofBounds(start, end))) changed = true + } else { + for (let i = 0, _i = e.indices.length; i < _i; i++) { + const idx = unitIdx * groupCount + OrderedSet.findPredecessorIndex(e.unit.polymerElements, e.unit.elements[e.indices[i]]) + if (apply(Interval.ofSingleton(idx))) changed = true + } + } + } + } + } + return changed } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index c7014ae582fd6cc11ebddfc028bd50f974f51c0b..3cd9ed382ce7a881dc88d94ed80fd3678855d5dd 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -13,9 +13,10 @@ import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } import { ValueRef } from 'mol-util'; import { UnitRings } from './unit/rings'; import StructureElement from './element' -import { ChainIndex, ResidueIndex } from '../model/indexing'; +import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing'; import { IntMap, SortedArray } from 'mol-data/int'; import { hash2 } from 'mol-data/util'; +import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements } from './util/polymer'; // A building block of a structure that corresponds to an atomic or a coarse grained representation // 'conveniently grouped together'. @@ -32,8 +33,8 @@ namespace Unit { export function create(id: number, invariantId: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set): Unit { switch (kind) { case Kind.Atomic: return new Atomic(id, invariantId, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation, void 0), AtomicProperties()); - case Kind.Spheres: return createCoarse(id, invariantId, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model))); - case Kind.Gaussians: return createCoarse(id, invariantId, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model))); + case Kind.Spheres: return createCoarse(id, invariantId, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model)), CoarseProperties()); + case Kind.Gaussians: return createCoarse(id, invariantId, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model)), CoarseProperties()); } } @@ -91,6 +92,8 @@ namespace Unit { applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit, readonly lookup3d: Lookup3D + readonly polymerElements: SortedArray<ElementIndex> + readonly gapElements: SortedArray<ElementIndex> } function getSphereRadiusFunc(model: Model) { @@ -154,6 +157,18 @@ namespace Unit { return this.props.rings.ref; } + get polymerElements() { + if (this.props.polymerElements.ref) return this.props.polymerElements.ref; + this.props.polymerElements.ref = getAtomicPolymerElements(this); + return this.props.polymerElements.ref; + } + + get gapElements() { + if (this.props.gapElements.ref) return this.props.gapElements.ref; + this.props.gapElements.ref = getAtomicGapElements(this); + return this.props.gapElements.ref; + } + getResidueIndex(elementIndex: StructureElement.UnitIndex) { return this.model.atomicHierarchy.residueAtomSegments.index[this.elements[elementIndex]]; } @@ -175,10 +190,18 @@ namespace Unit { lookup3d: ValueRef<Lookup3D | undefined>, links: ValueRef<IntraUnitLinks | undefined>, rings: ValueRef<UnitRings | undefined> + polymerElements: ValueRef<SortedArray<ElementIndex> | undefined> + gapElements: ValueRef<SortedArray<ElementIndex> | undefined> } function AtomicProperties(): AtomicProperties { - return { lookup3d: ValueRef.create(void 0), links: ValueRef.create(void 0), rings: ValueRef.create(void 0) }; + return { + lookup3d: ValueRef.create(void 0), + links: ValueRef.create(void 0), + rings: ValueRef.create(void 0), + polymerElements: ValueRef.create(void 0), + gapElements: ValueRef.create(void 0), + }; } class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base { @@ -193,32 +216,45 @@ namespace Unit { readonly coarseElements: CoarseElements; readonly coarseConformation: C; + private props: CoarseProperties; + getChild(elements: StructureElement.Set): Unit { if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */; - return createCoarse(this.id, this.invariantId, this.model, this.kind, elements, this.conformation); + return createCoarse(this.id, this.invariantId, this.model, this.kind, elements, this.conformation, CoarseProperties()); } applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit { const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator); - const ret = createCoarse(id, this.invariantId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements(), this.conformation.r)); - (ret as Coarse<K, C>)._lookup3d = this._lookup3d; + const ret = createCoarse(id, this.invariantId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements(), this.conformation.r), this.props); + // (ret as Coarse<K, C>)._lookup3d = this._lookup3d; return ret; } - private _lookup3d: ValueRef<Lookup3D | undefined> = ValueRef.create(void 0); get lookup3d() { - if (this._lookup3d.ref) return this._lookup3d.ref; + if (this.props.lookup3d.ref) return this.props.lookup3d.ref; // TODO: support sphere radius? const { x, y, z } = this.getCoarseElements(); - this._lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements }); - return this._lookup3d.ref; + this.props.lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements }); + return this.props.lookup3d.ref; + } + + get polymerElements() { + if (this.props.polymerElements.ref) return this.props.polymerElements.ref; + this.props.polymerElements.ref = getCoarsePolymerElements(this as Unit.Spheres | Unit.Gaussians); // TODO + return this.props.polymerElements.ref; + } + + get gapElements() { + if (this.props.gapElements.ref) return this.props.gapElements.ref; + this.props.gapElements.ref = getCoarseGapElements(this as Unit.Spheres | Unit.Gaussians); // TODO + return this.props.gapElements.ref; } private getCoarseElements() { return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians; } - constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping) { + constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: CoarseProperties) { this.kind = kind; this.id = id; this.invariantId = invariantId; @@ -227,11 +263,26 @@ namespace Unit { this.conformation = conformation; this.coarseElements = kind === Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians; this.coarseConformation = (kind === Kind.Spheres ? model.coarseConformation.spheres : model.coarseConformation.gaussians) as C; + this.props = props; } } - function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping): Unit { - return new Coarse(id, invariantId, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */; + interface CoarseProperties { + lookup3d: ValueRef<Lookup3D | undefined>, + polymerElements: ValueRef<SortedArray<ElementIndex> | undefined> + gapElements: ValueRef<SortedArray<ElementIndex> | undefined> + } + + function CoarseProperties(): CoarseProperties { + return { + lookup3d: ValueRef.create(void 0), + polymerElements: ValueRef.create(void 0), + gapElements: ValueRef.create(void 0), + }; + } + + function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: CoarseProperties): Unit { + return new Coarse(id, invariantId, model, kind, elements, conformation, props) as any as Unit /** lets call this an ugly temporary hack */; } export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { } diff --git a/src/mol-model/structure/structure/util/polymer.ts b/src/mol-model/structure/structure/util/polymer.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3e9825b4e8f674e1be14efddbeeed2ec77c01e3 --- /dev/null +++ b/src/mol-model/structure/structure/util/polymer.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, ElementIndex } from 'mol-model/structure'; +import { Segmentation, OrderedSet, Interval, SortedArray } from 'mol-data/int'; +import SortedRanges from 'mol-data/int/sorted-ranges'; +import { getElementIndexForAtomRole } from 'mol-model/structure/util'; + +export function getAtomicPolymerElements(unit: Unit.Atomic) { + const indices: ElementIndex[] = [] + const { elements, model } = unit + const { residueAtomSegments } = unit.model.atomicHierarchy + const polymerIt = SortedRanges.transientSegments(unit.model.atomicHierarchy.polymerRanges, elements) + const residueIt = Segmentation.transientSegments(residueAtomSegments, elements) + while (polymerIt.hasNext) { + const polymerSegment = polymerIt.move() + residueIt.setSegment(polymerSegment) + while (residueIt.hasNext) { + const residueSegment = residueIt.move() + const { start, end, index } = residueSegment + if (OrderedSet.areIntersecting(Interval.ofBounds(elements[start], elements[end - 1]), elements)) { + const elementIndex = getElementIndexForAtomRole(model, index, 'trace') + indices.push(elementIndex === -1 ? residueAtomSegments.offsets[index] : elementIndex) + } + } + } + return SortedArray.ofSortedArray<ElementIndex>(indices) +} + +export function getCoarsePolymerElements(unit: Unit.Spheres | Unit.Gaussians) { + const indices: ElementIndex[] = [] + const { elements, model } = unit + const { spheres, gaussians } = model.coarseHierarchy + const polymerRanges = Unit.isSpheres(unit) ? spheres.polymerRanges : gaussians.polymerRanges + const polymerIt = SortedRanges.transientSegments(polymerRanges, elements) + while (polymerIt.hasNext) { + const { start, end } = polymerIt.move() + for (let i = start; i < end; ++i) { indices.push(elements[i]) } + } + return SortedArray.ofSortedArray<ElementIndex>(indices) +} + +export function getAtomicGapElements(unit: Unit.Atomic) { + const indices: ElementIndex[] = [] + const { elements, model, residueIndex } = unit + const { residueAtomSegments } = unit.model.atomicHierarchy + const gapIt = SortedRanges.transientSegments(unit.model.atomicHierarchy.gapRanges, unit.elements); + while (gapIt.hasNext) { + const gapSegment = gapIt.move(); + const indexStart = residueIndex[elements[gapSegment.start]] + const indexEnd = residueIndex[elements[gapSegment.end - 1]] + const elementIndexStart = getElementIndexForAtomRole(model, indexStart, 'trace') + const elementIndexEnd = getElementIndexForAtomRole(model, indexEnd, 'trace') + indices.push(elementIndexStart === -1 ? residueAtomSegments.offsets[indexStart] : elementIndexStart) + indices.push(elementIndexEnd === -1 ? residueAtomSegments.offsets[indexEnd] : elementIndexEnd) + + } + return SortedArray.ofSortedArray<ElementIndex>(indices) +} + +export function getCoarseGapElements(unit: Unit.Spheres | Unit.Gaussians) { + const indices: ElementIndex[] = [] + const { elements, model } = unit + const { spheres, gaussians } = model.coarseHierarchy + const gapRanges = Unit.isSpheres(unit) ? spheres.gapRanges : gaussians.gapRanges + const gapIt = SortedRanges.transientSegments(gapRanges, elements) + while (gapIt.hasNext) { + const { start, end } = gapIt.move() + indices.push(elements[start], elements[end - 1]) + } + return SortedArray.ofSortedArray<ElementIndex>(indices) +} \ No newline at end of file