Bladeren bron

wip, visual .mark and .getLoci

Alexander Rose 6 jaren geleden
bovenliggende
commit
1190256e76

+ 1 - 0
src/mol-data/int/impl/interval.ts

@@ -19,6 +19,7 @@ export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
 export const hashCode = Tuple.hashCode;
 
 export function has(int: Tuple, v: number) { return Tuple.fst(int) <= v && v < Tuple.snd(int); }
+/** Returns the index of `x` in `set` or -1 if not found. */
 export function indexOf(int: Tuple, x: number) { const m = start(int); return x >= m && x < end(int) ? x - m : -1; }
 export function getAt(int: Tuple, i: number) { return Tuple.fst(int) + i; }
 

+ 1 - 0
src/mol-data/int/impl/ordered-set.ts

@@ -25,6 +25,7 @@ export function ofSortedArray(xs: Nums): OrderedSetImpl {
 
 export function size(set: OrderedSetImpl) { return I.is(set) ? I.size(set) : S.size(set); }
 export function has(set: OrderedSetImpl, x: number) { return I.is(set) ? I.has(set, x) : S.has(set, x); }
+/** Returns the index of `x` in `set` or -1 if not found. */
 export function indexOf(set: OrderedSetImpl, x: number) { return I.is(set) ? I.indexOf(set, x) : S.indexOf(set, x); }
 export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : set[i]; }
 export function min(set: OrderedSetImpl) { return I.is(set) ? I.min(set) : S.min(set); }

+ 1 - 0
src/mol-data/int/impl/sorted-array.ts

@@ -36,6 +36,7 @@ export function hashCode(xs: Nums) {
     return hash3(s, xs[0], xs[s - 1]);
 }
 
+/** Returns the index of `x` in `set` or -1 if not found. */
 export function indexOf(xs: Nums, v: number) {
     const l = xs.length;
     return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;

+ 1 - 0
src/mol-data/int/interval.ts

@@ -18,6 +18,7 @@ namespace Interval {
 
     /** Test if a value is within the bounds of the interval */
     export const has: <T extends number = number>(interval: Interval<T>, x: T) => boolean = Impl.has as any;
+    /** Returns the index of `x` in `set` or -1 if not found. */
     export const indexOf: <T extends number = number>(interval: Interval<T>, x: T) => number = Impl.indexOf as any;
     export const getAt: <T extends number = number>(interval: Interval<T>, i: number) => T = Impl.getAt as any;
 

+ 1 - 0
src/mol-data/int/ordered-set.ts

@@ -18,6 +18,7 @@ namespace OrderedSet {
     export const ofSortedArray: <T extends number = number>(xs: ArrayLike<T>) => OrderedSet<T> = Base.ofSortedArray as any;
 
     export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any;
+    /** Returns the index of `x` in `set` or -1 if not found. */
     export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any;
     export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any;
 

+ 1 - 0
src/mol-data/int/sorted-array.ts

@@ -19,6 +19,7 @@ namespace SortedArray {
     export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any;
 
     export const has: <T extends number = number>(array: SortedArray<T>, x: T) => boolean = Impl.has as any;
+    /** Returns the index of `x` in `set` or -1 if not found. */
     export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any;
     export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any;
 

+ 12 - 10
src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts

@@ -92,8 +92,8 @@ function CarbohydrateLinkIterator(structure: Structure): LocationIterator {
         const link = links[groupIndex]
         const carbA = elements[link.carbohydrateIndexA]
         const carbB = elements[link.carbohydrateIndexB]
-        const indexA = OrderedSet.findPredecessorIndex(carbA.unit.elements, carbA.anomericCarbon)
-        const indexB = OrderedSet.findPredecessorIndex(carbB.unit.elements, carbB.anomericCarbon)
+        const indexA = OrderedSet.indexOf(carbA.unit.elements, carbA.anomericCarbon)
+        const indexB = OrderedSet.indexOf(carbB.unit.elements, carbB.anomericCarbon)
         location.aUnit = carbA.unit
         location.aIndex = indexA as StructureElement.UnitIndex
         location.bUnit = carbB.unit
@@ -110,14 +110,16 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
         const l = links[groupId]
         const carbA = elements[l.carbohydrateIndexA]
         const carbB = elements[l.carbohydrateIndexB]
-        const indexA = OrderedSet.findPredecessorIndex(carbA.unit.elements, carbA.anomericCarbon)
-        const indexB = OrderedSet.findPredecessorIndex(carbB.unit.elements, carbB.anomericCarbon)
-        return Link.Loci([
-            Link.Location(
-                carbA.unit, indexA as StructureElement.UnitIndex,
-                carbB.unit, indexB as StructureElement.UnitIndex
-            )
-        ])
+        const indexA = OrderedSet.indexOf(carbA.unit.elements, carbA.anomericCarbon)
+        const indexB = OrderedSet.indexOf(carbB.unit.elements, carbB.anomericCarbon)
+        if (indexA !== -1 && indexB !== -1) {
+            return Link.Loci([
+                Link.Location(
+                    carbA.unit, indexA as StructureElement.UnitIndex,
+                    carbB.unit, indexB as StructureElement.UnitIndex
+                )
+            ])
+        }
     }
     return EmptyLoci
 }

+ 5 - 3
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -182,9 +182,11 @@ function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: num
     if (id === objectId) {
         const carb = structure.carbohydrates.elements[Math.floor(groupId / 2)]
         const { unit } = carb
-        const index = OrderedSet.findPredecessorIndex(unit.elements, carb.anomericCarbon)
-        const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex)
-        return StructureElement.Loci([{ unit, indices }])
+        const index = OrderedSet.indexOf(unit.elements, carb.anomericCarbon)
+        if (index !== -1) {
+            const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex)
+            return StructureElement.Loci([{ unit, indices }])
+        }
     }
     return EmptyLoci
 }

+ 2 - 1
src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts

@@ -102,12 +102,13 @@ function markLink(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Inter
 
     let changed = false
     if (Unit.isAtomic(unit) && Link.isLoci(loci)) {
+        const groupCount = unit.links.edgeCount * 2
         for (const b of loci.links) {
             const unitIdx = group.unitIndexMap.get(b.aUnit.id)
             if (unitIdx !== undefined) {
                 const idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex)
                 if (idx !== -1) {
-                    if (apply(Interval.ofSingleton(idx))) changed = true
+                    if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
                 }
             }
         }

+ 3 - 4
src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts

@@ -9,7 +9,6 @@ import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { Mesh } from '../../../mesh/mesh';
 import { MeshBuilder } from '../../../mesh/mesh-builder';
-import { getElementLoci, markElement } from './util/element';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Segmentation } from 'mol-data/int';
 import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types';
@@ -17,7 +16,7 @@ import { getElementIndexForAtomId, getElementIndexForAtomRole } from 'mol-model/
 import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual';
 import { addCylinder } from '../../../mesh/builder/cylinder';
 import { Box } from '../../../primitive/box';
-import { NucleotideLocationIterator } from './util/nucleotide';
+import { NucleotideLocationIterator, markNucleotideElement, getNucleotideElementLoci } from './util/nucleotide';
 
 const p1 = Vec3.zero()
 const p2 = Vec3.zero()
@@ -121,8 +120,8 @@ export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockProps> {
         defaultProps: DefaultNucleotideBlockProps,
         createMesh: createNucleotideBlockMesh,
         createLocationIterator: NucleotideLocationIterator.fromGroup,
-        getLoci: getElementLoci,
-        mark: markElement,
+        getLoci: getNucleotideElementLoci,
+        mark: markNucleotideElement,
         setUpdateState: () => {}
     })
 }

+ 2 - 2
src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts

@@ -47,11 +47,11 @@ async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit
         pos(centerB.element, pB)
 
         cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerA)
-        builder.setGroup(OrderedSet.findPredecessorIndex(elements, centerA.element))
+        builder.setGroup(OrderedSet.indexOf(elements, centerA.element))
         addCylinder(builder, pA, pB, 0.5, cylinderProps)
 
         cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerB)
-        builder.setGroup(OrderedSet.findPredecessorIndex(elements, centerB.element))
+        builder.setGroup(OrderedSet.indexOf(elements, centerB.element))
         addCylinder(builder, pB, pA, 0.5, cylinderProps)
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {

+ 3 - 4
src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts

@@ -9,8 +9,7 @@ import { UnitsVisual, MeshUpdateState } from '..';
 import { RuntimeContext } from 'mol-task'
 import { Mesh } from '../../../mesh/mesh';
 import { MeshBuilder } from '../../../mesh/mesh-builder';
-import { PolymerGapIterator, PolymerGapLocationIterator } from './util/polymer';
-import { getElementLoci, markElement } from './util/element';
+import { PolymerGapIterator, PolymerGapLocationIterator, markPolymerGapElement, getPolymerGapElementLoci } from './util/polymer';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
@@ -83,8 +82,8 @@ export function PolymerGapVisual(): UnitsVisual<PolymerGapProps> {
         defaultProps: DefaultPolymerGapProps,
         createMesh: createPolymerGapCylinderMesh,
         createLocationIterator: PolymerGapLocationIterator.fromGroup,
-        getLoci: getElementLoci,
-        mark: markElement,
+        getLoci: getPolymerGapElementLoci,
+        mark: markPolymerGapElement,
         setUpdateState: (state: MeshUpdateState, newProps: PolymerGapProps, currentProps: PolymerGapProps) => {
             state.createMesh = newProps.radialSegments !== currentProps.radialSegments
         }

+ 51 - 30
src/mol-geo/representation/structure/visual/util/nucleotide.ts

@@ -3,40 +3,18 @@
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
-import { Unit, ElementIndex, StructureElement } from 'mol-model/structure';
-import { LocationIterator } from '../../../../util/location-iterator';
-import { Segmentation } from 'mol-data/int';
-import { isNucleic, MoleculeType } from 'mol-model/structure/model/types';
-import { getElementIndexForAtomRole } from 'mol-model/structure/util';
-
-export function getNucleotideElementIndices(unit: Unit) {
-    const indices: ElementIndex[] = []
-    const { elements, model } = unit
-    const { chemicalComponentMap } = model.properties
-    const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy
-    const { label_comp_id } = residues
-    const chainIt = Segmentation.transientSegments(chainAtomSegments, elements)
-    const residueIt = Segmentation.transientSegments(residueAtomSegments, elements)
-    while (chainIt.hasNext) {
-        residueIt.setSegment(chainIt.move());
-
-        while (residueIt.hasNext) {
-            const { index } = residueIt.move();
-            const cc = chemicalComponentMap.get(label_comp_id.value(index))
-            const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown
 
-            if (isNucleic(moleculeType)) {
-                const elementIndex = getElementIndexForAtomRole(model, index, 'trace')
-                indices.push(elementIndex === -1 ? residueAtomSegments.offsets[index] : elementIndex)
-            }
-        }
-    }
-    return indices
-}
+import { Unit, StructureElement } from 'mol-model/structure';
+import { LocationIterator } from '../../../../util/location-iterator';
+import { getNucleotideElements } from 'mol-model/structure/structure/util/nucleotide';
+import { PickingId } from '../../../../util/picking';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { OrderedSet, Interval } from 'mol-data/int';
 
 export namespace NucleotideLocationIterator {
     export function fromGroup(group: Unit.SymmetryGroup): LocationIterator {
-        const nucleotideElementIndices = getNucleotideElementIndices(group.units[0])
+        const u = group.units[0]
+        const nucleotideElementIndices = Unit.isAtomic(u) ? getNucleotideElements(u) : []
         const groupCount = nucleotideElementIndices.length
         const instanceCount = group.units.length
         const location = StructureElement.create()
@@ -48,4 +26,47 @@ export namespace NucleotideLocationIterator {
         }
         return LocationIterator(groupCount, instanceCount, getLocation)
     }
+}
+
+export function getNucleotideElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+    const { objectId, instanceId, groupId } = pickingId
+    if (id === objectId) {
+        const unit = group.units[instanceId]
+        if (Unit.isAtomic(unit)) {
+            const unitIndex = OrderedSet.indexOf(unit.elements, unit.nucleotideElements[groupId]) as StructureElement.UnitIndex
+            if (unitIndex !== -1) {
+                const indices = OrderedSet.ofSingleton(unitIndex)
+                return StructureElement.Loci([{ unit, indices }])
+            }
+        }
+    }
+    return EmptyLoci
+}
+
+export function markNucleotideElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) {
+    let changed = false
+    const u = group.units[0]
+    if (StructureElement.isLoci(loci) && Unit.isAtomic(u)) {
+        const groupCount = u.nucleotideElements.length
+        for (const e of loci.elements) {
+            const unitIdx = group.unitIndexMap.get(e.unit.id)
+            if (unitIdx !== undefined && Unit.isAtomic(e.unit)) {
+                if (Interval.is(e.indices)) {
+                    const min = unitIdx * groupCount + OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.min(e.indices)])
+                    const max = unitIdx * groupCount + OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.max(e.indices)])
+                    if (min !== -1 && max !== -1) {
+                        if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true
+                    }
+                } else {
+                    for (let i = 0, _i = e.indices.length; i < _i; i++) {
+                        const idx = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[e.indices[i]])
+                        if (idx !== -1) {
+                            if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return changed
 }

+ 46 - 9
src/mol-geo/representation/structure/visual/util/polymer.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, ElementIndex, StructureElement } from 'mol-model/structure';
+import { Unit, ElementIndex, StructureElement, Link } from 'mol-model/structure';
 import SortedRanges from 'mol-data/int/sorted-ranges';
 import { LocationIterator } from '../../../../util/location-iterator';
 import { PickingId } from '../../../../util/picking';
@@ -68,9 +68,11 @@ export function getPolymerElementLoci(pickingId: PickingId, group: Unit.Symmetry
     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 }])
+        const unitIndex = OrderedSet.indexOf(unit.elements, unit.polymerElements[groupId]) as StructureElement.UnitIndex
+        if (unitIndex !== -1) {
+            const indices = OrderedSet.ofSingleton(unitIndex)
+            return StructureElement.Loci([{ unit, indices }])
+        }
     }
     return EmptyLoci
 }
@@ -84,17 +86,52 @@ export function markPolymerElement(loci: Loci, group: Unit.SymmetryGroup, apply:
             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
+                    const min =  + OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.min(e.indices)])
+                    const max = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.max(e.indices)])
+                    if (min !== -1 && max !== -1) {
+                        if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) 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
+                        const idx = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[e.indices[i]])
+                        if (idx !== -1) {
+                            if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
+                        }
                     }
                 }
             }
         }
     }
     return changed
+}
+
+export function getPolymerGapElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+    const { objectId, instanceId, groupId } = pickingId
+    if (id === objectId) {
+        const unit = group.units[instanceId]
+        const unitIndexA = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId]) as StructureElement.UnitIndex
+        const unitIndexB = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId % 2 ? groupId - 1 : groupId + 1]) as StructureElement.UnitIndex
+        if (unitIndexA !== -1 && unitIndexB !== -1) {
+            return Link.Loci([ Link.Location(unit, unitIndexA, unit, unitIndexB) ])
+        }
+    }
+    return EmptyLoci
+}
+
+export function markPolymerGapElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) {
+    let changed = false
+    if (Link.isLoci(loci)) {
+        const groupCount = group.units[0].gapElements.length
+        for (const b of loci.links) {
+            const unitIdx = group.unitIndexMap.get(b.aUnit.id)
+            if (unitIdx !== undefined) {
+                const idxA = OrderedSet.indexOf(b.aUnit.gapElements, b.aUnit.elements[b.aIndex])
+                const idxB = OrderedSet.indexOf(b.bUnit.gapElements, b.bUnit.elements[b.bIndex])
+                if (idxA !== -1 && idxB !== -1) {
+                    if (apply(Interval.ofSingleton(unitIdx * groupCount + idxA))) changed = true
+                }
+            }
+        }
+    }
+    return changed
 }

+ 9 - 0
src/mol-model/structure/structure/unit.ts

@@ -17,6 +17,7 @@ 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';
+import { getNucleotideElements } from './util/nucleotide';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
@@ -169,6 +170,12 @@ namespace Unit {
             return this.props.gapElements.ref;
         }
 
+        get nucleotideElements() {
+            if (this.props.nucleotideElements.ref) return this.props.nucleotideElements.ref;
+            this.props.nucleotideElements.ref = getNucleotideElements(this);
+            return this.props.nucleotideElements.ref;
+        }
+
         getResidueIndex(elementIndex: StructureElement.UnitIndex) {
             return this.model.atomicHierarchy.residueAtomSegments.index[this.elements[elementIndex]];
         }
@@ -192,6 +199,7 @@ namespace Unit {
         rings: ValueRef<UnitRings | undefined>
         polymerElements: ValueRef<SortedArray<ElementIndex> | undefined>
         gapElements: ValueRef<SortedArray<ElementIndex> | undefined>
+        nucleotideElements: ValueRef<SortedArray<ElementIndex> | undefined>
     }
 
     function AtomicProperties(): AtomicProperties {
@@ -201,6 +209,7 @@ namespace Unit {
             rings: ValueRef.create(void 0),
             polymerElements: ValueRef.create(void 0),
             gapElements: ValueRef.create(void 0),
+            nucleotideElements: ValueRef.create(void 0),
         };
     }
 

+ 35 - 0
src/mol-model/structure/structure/util/nucleotide.ts

@@ -0,0 +1,35 @@
+/**
+ * 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, SortedArray } from 'mol-data/int';
+import { isNucleic, MoleculeType } from 'mol-model/structure/model/types';
+import { getElementIndexForAtomRole } from 'mol-model/structure/util';
+
+export function getNucleotideElements(unit: Unit.Atomic) {
+    const indices: ElementIndex[] = []
+    const { elements, model } = unit
+    const { chemicalComponentMap } = model.properties
+    const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy
+    const { label_comp_id } = residues
+    const chainIt = Segmentation.transientSegments(chainAtomSegments, elements)
+    const residueIt = Segmentation.transientSegments(residueAtomSegments, elements)
+    while (chainIt.hasNext) {
+        residueIt.setSegment(chainIt.move());
+
+        while (residueIt.hasNext) {
+            const { index } = residueIt.move();
+            const cc = chemicalComponentMap.get(label_comp_id.value(index))
+            const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown
+
+            if (isNucleic(moleculeType)) {
+                const elementIndex = getElementIndexForAtomRole(model, index, 'trace')
+                indices.push(elementIndex === -1 ? residueAtomSegments.offsets[index] : elementIndex)
+            }
+        }
+    }
+    return SortedArray.ofSortedArray<ElementIndex>(indices)
+}