diff --git a/src/mol-data/int/sorted-ranges.ts b/src/mol-data/int/sorted-ranges.ts index 0e7e5f139c50a8bde253e7d414dea78fe3752fa0..032f73ff145468fd63804eafba710b728fc9330a 100644 --- a/src/mol-data/int/sorted-ranges.ts +++ b/src/mol-data/int/sorted-ranges.ts @@ -24,24 +24,24 @@ namespace SortedRanges { return size } - export function transientSegments<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) { - return new Iterator<T>(ranges, set) + export function transientSegments<T extends number = number, I extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) { + return new Iterator<T, I>(ranges, set) } - export class Iterator<I extends number = number> implements _Iterator<Segmentation.Segment<I>> { - private value: Segmentation.Segment<I> = { index: 0 as I, start: 0, end: 0 } - + export class Iterator<T extends number = number, I extends number = number> implements _Iterator<Segmentation.Segment<I>> { + private value: Segmentation.Segment<I> = { index: 0 as I, start: 0 as T, end: 0 as T } + private curIndex = 0 private maxIndex = 0 - private interval: Interval<I> - private curMin: I = 0 as I - + private interval: Interval<T> + private curMin: T = 0 as T + hasNext: boolean = false; - + updateInterval() { this.interval = Interval.ofRange(this.ranges[this.curIndex], this.ranges[this.curIndex + 1]) } - + updateValue() { this.value.index = this.curIndex / 2 as I this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex]) @@ -61,13 +61,13 @@ namespace SortedRanges { } return this.value; } - + getRangeIndex(value: number) { const index = SortedArray.findPredecessorIndex(this.ranges, value) return (index % 2 === 1) ? index - 1 : index } - - constructor(private ranges: SortedRanges<I>, private set: OrderedSet<I>) { + + constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) { if (ranges.length) { this.curIndex = this.getRangeIndex(OrderedSet.min(set)) this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set))) 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 949d4d9f450b78354414850046dd5756e0eb521f..956fe0fdb0f5a210238eab2895f8de150cbe041c 100644 --- a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts @@ -43,11 +43,11 @@ async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit while (polymerBackboneIt.hasNext) { // TODO size theme const { centerA, centerB } = polymerBackboneIt.move() - pos(centerA.element, pA) - pos(centerB.element, pB) - builder.setId(elements[centerA.element]) + pos(elements[centerA.element], pA) + pos(elements[centerB.element], pB) + builder.setId(centerA.element) builder.addCylinder(pA, pB, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) - builder.setId(elements[centerB.element]) + builder.setId(centerB.element) builder.addCylinder(pB, pA, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) if (i % 10000 === 0 && ctx.shouldUpdate) { 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 96207c6a33988a480023317643d598189bf186e6..45be0c38218090659bcc4a99b1f80a6e11b4a4af 100644 --- a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts +++ b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts @@ -81,15 +81,13 @@ function interpolateNormals(controlPoints: Helpers.NumberArray, tangentVectors: async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) { const polymerElementCount = getPolymerElementCount(unit) - console.log('polymerElementCount', polymerElementCount) + console.log('polymerElementCount trace', polymerElementCount) if (!polymerElementCount) return Mesh.createEmpty(mesh) - const { elements } = unit - // TODO better vertex count estimates const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh) - const linearSegments = 12 - const radialSegments = 16 + const linearSegments = 8 + const radialSegments = 12 const tension = 0.9 const tanA = Vec3.zero() @@ -110,7 +108,8 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Me const polymerTraceIt = PolymerTraceIterator(unit) while (polymerTraceIt.hasNext) { const v = polymerTraceIt.move() - builder.setId(elements[v.center.element]) + // builder.setId(elements[v.center.element]) + builder.setId(v.center.element) for (let j = 0; j <= linearSegments; ++j) { const t = j * 1.0 / linearSegments; diff --git a/src/mol-geo/representation/structure/visual/util/polymer.ts b/src/mol-geo/representation/structure/visual/util/polymer.ts index 9565bd1c49c4d2a4fb120fcc49e9f555801997a0..9e369770c70e2af439bbebc04d360c7d7fe48c86 100644 --- a/src/mol-geo/representation/structure/visual/util/polymer.ts +++ b/src/mol-geo/representation/structure/visual/util/polymer.ts @@ -5,11 +5,12 @@ */ import { Unit, StructureElement, Model, ElementIndex, ResidueIndex } from 'mol-model/structure'; -import { Segmentation, OrderedSet, Interval } from 'mol-data/int'; +import { Segmentation, OrderedSet, Interval, SortedArray } from 'mol-data/int'; import { MoleculeType, SecondaryStructureType } from 'mol-model/structure/model/types'; import Iterator from 'mol-data/iterator'; import { Vec3 } from 'mol-math/linear-algebra'; import SortedRanges from 'mol-data/int/sorted-ranges'; +import { CoarseSphereConformation, CoarseGaussianConformation } from 'mol-model/structure/model/properties/coarse'; export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> { switch (unit.kind) { @@ -71,25 +72,25 @@ function getResidueTypeAtomId(moleculeType: MoleculeType, atomType: 'trace' | 'd return '' } -function getMoleculeType(model: Model, residueIndex: number) { +function getMoleculeType(model: Model, residueIndex: ResidueIndex) { const compId = model.atomicHierarchy.residues.label_comp_id.value(residueIndex) const chemCompMap = model.properties.chemicalComponentMap const cc = chemCompMap.get(compId) return cc ? cc.moleculeType : MoleculeType.unknown } -function getElementIndexForAtomId(unit: Unit.Atomic, residueSegment: Segmentation.Segment, atomId: string) { - const elements = unit.elements - const { label_atom_id } = unit.model.atomicHierarchy.atoms - for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) { - if (label_atom_id.value(elements[j]) === atomId) return j as ElementIndex +function getElementIndexForAtomId(model: Model, rI: ResidueIndex, atomId: string): ElementIndex { + const { offsets } = model.atomicHierarchy.residueAtomSegments + const { label_atom_id } = model.atomicHierarchy.atoms + for (let j = offsets[rI], _j = offsets[rI + 1]; j < _j; j++) { + if (label_atom_id.value(j) === atomId) return j as ElementIndex } - return residueSegment.end - 1 as ElementIndex + return offsets[rI] as ElementIndex } -function getResidueTypeAtomIdElementIndex(unit: Unit.Atomic, residueSegment: Segmentation.Segment, type: 'trace' | 'direction') { - const atomId = getResidueTypeAtomId(getMoleculeType(unit.model, residueSegment.index), type) - return getElementIndexForAtomId(unit, residueSegment, atomId) +function getElementIndexForResidueTypeAtomId(model: Model, rI: ResidueIndex, atomType: 'trace' | 'direction') { + const atomId = getResidueTypeAtomId(getMoleculeType(model, rI), atomType) + return getElementIndexForAtomId(model, rI, atomId) } // function residueLabel(model: Model, rI: number) { @@ -124,21 +125,32 @@ function createPolymerBackbonePair (unit: Unit) { const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextResidue } -export class AtomicPolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> { +export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePair> { private value: PolymerBackbonePair - private polymerIt: SortedRanges.Iterator<ElementIndex> + private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex> private residueIt: Segmentation.SegmentIterator<ResidueIndex> private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer hasNext: boolean = false; + getElementIndex(residueIndex: ResidueIndex, atomType: 'trace' | 'direction') { + const index = getElementIndexForResidueTypeAtomId(this.unit.model, residueIndex, atomType) + // // TODO handle case when it returns -1 + // return SortedArray.indexOf(this.unit.elements, index) as ElementIndex + + const elementIndex = SortedArray.indexOf(this.unit.elements, index) as ElementIndex + if (elementIndex === -1) { + console.log('-1', residueIndex, atomType, index) + } + return elementIndex === -1 ? 0 as ElementIndex : elementIndex + } + move() { if (this.state === AtomicPolymerBackboneIteratorState.nextPolymer) { while (this.polymerIt.hasNext) { const residueSegment = this.polymerIt.move() this.residueIt.setSegment(residueSegment); if (this.residueIt.hasNext) { - this.value.centerB.element = getResidueTypeAtomIdElementIndex(this.unit, residueSegment, 'trace') - // setTraceElement(this.value.centerB, this.residueIt.move()) + this.value.centerB.element = this.getElementIndex(this.residueIt.move().index, 'trace') this.state = AtomicPolymerBackboneIteratorState.nextResidue break } @@ -147,8 +159,7 @@ export class AtomicPolymerBackboneIterator<T extends number = number> implements if (this.state === AtomicPolymerBackboneIteratorState.nextResidue) { this.value.centerA.element = this.value.centerB.element - this.value.centerB.element = getResidueTypeAtomIdElementIndex(this.unit, this.residueIt.move(), 'trace') - // setTraceElement(this.value.centerB, this.residueIt.move()) + this.value.centerB.element = this.getElementIndex(this.residueIt.move().index, 'trace') if (!this.residueIt.hasNext) { // TODO need to advance to a polymer that has two or more residues (can't assume it has) this.state = AtomicPolymerBackboneIteratorState.nextPolymer @@ -169,10 +180,10 @@ export class AtomicPolymerBackboneIterator<T extends number = number> implements const enum CoarsePolymerBackboneIteratorState { nextPolymer, nextElement } -export class CoarsePolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> { +export class CoarsePolymerBackboneIterator implements Iterator<PolymerBackbonePair> { private value: PolymerBackbonePair - private polymerIt: SortedRanges.Iterator<ElementIndex> - private polymerSegment: Segmentation.Segment<ElementIndex> + private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex> + private polymerSegment: Segmentation.Segment<ResidueIndex> private state: CoarsePolymerBackboneIteratorState = CoarsePolymerBackboneIteratorState.nextPolymer private elementIndex: number hasNext: boolean = false; @@ -182,9 +193,8 @@ export class CoarsePolymerBackboneIterator<T extends number = number> implements if (this.polymerIt.hasNext) { this.polymerSegment = this.polymerIt.move(); this.elementIndex = this.polymerSegment.start - // this.elementIndex += 1 if (this.elementIndex + 1 < this.polymerSegment.end) { - this.value.centerB.element = this.value.centerB.unit.elements[this.elementIndex] + this.value.centerB.element = this.unit.elements[this.elementIndex] this.state = CoarsePolymerBackboneIteratorState.nextElement } else { this.state = CoarsePolymerBackboneIteratorState.nextPolymer @@ -195,7 +205,7 @@ export class CoarsePolymerBackboneIterator<T extends number = number> implements if (this.state === CoarsePolymerBackboneIteratorState.nextElement) { this.elementIndex += 1 this.value.centerA.element = this.value.centerB.element - this.value.centerB.element = this.value.centerB.unit.elements[this.elementIndex] + this.value.centerB.element = this.unit.elements[this.elementIndex] if (this.elementIndex + 1 >= this.polymerSegment.end) { this.state = CoarsePolymerBackboneIteratorState.nextPolymer } @@ -205,16 +215,13 @@ export class CoarsePolymerBackboneIterator<T extends number = number> implements return this.value; } - constructor(unit: Unit.Spheres | Unit.Gaussians) { + constructor(private unit: Unit.Spheres | Unit.Gaussians) { this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements); this.value = createPolymerBackbonePair(unit) this.hasNext = this.polymerIt.hasNext } } - - - /** * Iterates over individual residues/coarse elements in polymers of a unit while * providing information about the neighbourhood in the underlying model for drawing splines @@ -242,32 +249,21 @@ function createPolymerTraceElement (unit: Unit): PolymerTraceElement { first: false, last: false, secStrucType: SecondaryStructureType.create(SecondaryStructureType.Flag.NA), t0: Vec3.zero(), t1: Vec3.zero(), t2: Vec3.zero(), t3: Vec3.zero(), t4: Vec3.zero(), - d12: Vec3.zero(), d23: Vec3.zero(), + d12: Vec3.create(1, 0, 0), d23: Vec3.create(1, 0, 0), } } const enum AtomicPolymerTraceIteratorState { nextPolymer, nextResidue } -function setSegment (outSegment: Segmentation.Segment<number>, index: number, segments: Segmentation<number>, min: number, max: number): Segmentation.Segment<number> { - // index = Math.min(Math.max(0, index), segments.segments.length - 2) - const _index = Math.min(Math.max(min, index), max) - if (isNaN(_index)) console.log(_index, index, min, max) - outSegment.index = _index - outSegment.start = segments.offsets[_index] - outSegment.end = segments.offsets[_index + 1] - // console.log(index, {...outSegment}, {...boundingSegment}, segments.segments[boundingSegment.index]) - return outSegment -} - -export class AtomicPolymerTraceIterator<T extends number = number> implements Iterator<PolymerTraceElement> { +export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement> { private value: PolymerTraceElement - private polymerIt: SortedRanges.Iterator<ElementIndex> + private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex> private residueIt: Segmentation.SegmentIterator<ResidueIndex> - private residueAtomSegmentMin: number - private residueAtomSegmentMax: number + private polymerSegment: Segmentation.Segment<ResidueIndex> + private residueSegmentMin: ResidueIndex + private residueSegmentMax: ResidueIndex private state: AtomicPolymerTraceIteratorState = AtomicPolymerTraceIteratorState.nextPolymer private residueAtomSegments: Segmentation<ElementIndex, ResidueIndex> - private tmpSegment: Segmentation.Segment<ResidueIndex> hasNext: boolean = false; @@ -277,12 +273,25 @@ export class AtomicPolymerTraceIterator<T extends number = number> implements It target[2] = this.unit.model.atomicConformation.z[index] } - updateResidueSegmentRange(polymerSegment: Segmentation.Segment<ElementIndex>) { - const { polymerRanges, residueAtomSegments } = this.unit.model.atomicHierarchy - const sMin = polymerRanges[polymerSegment.index * 2] - const sMax = polymerRanges[polymerSegment.index * 2 + 1] - this.residueAtomSegmentMin = residueAtomSegments.index[sMin] - this.residueAtomSegmentMax = residueAtomSegments.index[sMax] + updateResidueSegmentRange(polymerSegment: Segmentation.Segment<ResidueIndex>) { + const { index } = this.unit.model.atomicHierarchy.residueAtomSegments + this.residueSegmentMin = index[this.unit.elements[polymerSegment.start]] + this.residueSegmentMax = index[this.unit.elements[polymerSegment.end - 1]] + } + + getAtomIndex(residueIndex: number, atomType: 'trace' | 'direction') { + const index = Math.min(Math.max(this.residueSegmentMin, residueIndex), this.residueSegmentMax) + return getElementIndexForResidueTypeAtomId(this.unit.model, index as ResidueIndex, atomType) + } + + getElementIndex(residueIndex: number, atomType: 'trace' | 'direction') { + const index = this.getAtomIndex(residueIndex, atomType) + // TODO handle case when it returns -1 + const elementIndex = SortedArray.indexOf(this.unit.elements, index) as ElementIndex + if (elementIndex === -1) { + console.log('-1', residueIndex, atomType, index) + } + return elementIndex === -1 ? 0 as ElementIndex : elementIndex } move() { @@ -290,10 +299,9 @@ export class AtomicPolymerTraceIterator<T extends number = number> implements It if (this.state === AtomicPolymerTraceIteratorState.nextPolymer) { while (polymerIt.hasNext) { - const polymerSegment = polymerIt.move(); - // console.log('polymerSegment', {...polymerSegment}) - residueIt.setSegment(polymerSegment); - this.updateResidueSegmentRange(polymerSegment) + this.polymerSegment = polymerIt.move(); + residueIt.setSegment(this.polymerSegment); + this.updateResidueSegmentRange(this.polymerSegment) if (residueIt.hasNext) { this.state = AtomicPolymerTraceIteratorState.nextResidue break @@ -302,32 +310,22 @@ export class AtomicPolymerTraceIterator<T extends number = number> implements It } if (this.state === AtomicPolymerTraceIteratorState.nextResidue) { - const { tmpSegment, residueAtomSegments, residueAtomSegmentMin, residueAtomSegmentMax } = this - const residueSegment = residueIt.move(); - const resSegIdx = residueSegment.index - // console.log(residueLabel(this.unit.model, resSegIdx), resSegIdx, this.unit.model.properties.secondaryStructure.type[resSegIdx]) - value.center.element = getResidueTypeAtomIdElementIndex(this.unit, residueSegment, 'trace') + const { index: residueIndex } = residueIt.move(); + value.center.element = this.getElementIndex(residueIndex, 'trace') - setSegment(tmpSegment, resSegIdx - 2, residueAtomSegments, residueAtomSegmentMin, residueAtomSegmentMax) - this.pos(value.t0, getResidueTypeAtomIdElementIndex(this.unit, tmpSegment, 'trace')) + this.pos(value.t0, this.getAtomIndex(residueIndex - 2, 'trace')) + this.pos(value.t1, this.getAtomIndex(residueIndex - 1, 'trace')) + this.pos(value.t2, this.getAtomIndex(residueIndex, 'trace')) + this.pos(value.t3, this.getAtomIndex(residueIndex + 1, 'trace')) + this.pos(value.t4, this.getAtomIndex(residueIndex + 2, 'trace')) - setSegment(tmpSegment, resSegIdx - 1, residueAtomSegments, residueAtomSegmentMin, residueAtomSegmentMax) - this.pos(value.t1, getResidueTypeAtomIdElementIndex(this.unit, tmpSegment, 'trace')) - this.pos(value.d12, getResidueTypeAtomIdElementIndex(this.unit, tmpSegment, 'direction')) + this.pos(value.d12, this.getAtomIndex(residueIndex - 1, 'direction')) + this.pos(value.d23, this.getAtomIndex(residueIndex, 'direction')) - setSegment(tmpSegment, resSegIdx, residueAtomSegments, residueAtomSegmentMin, residueAtomSegmentMax) - value.secStrucType = this.unit.model.properties.secondaryStructure.type[resSegIdx] - this.pos(value.t2, getResidueTypeAtomIdElementIndex(this.unit, tmpSegment, 'trace')) - this.pos(value.d23, getResidueTypeAtomIdElementIndex(this.unit, tmpSegment, 'direction')) + this.value.secStrucType = this.unit.model.properties.secondaryStructure.type[residueIndex] - setSegment(tmpSegment, resSegIdx + 1, residueAtomSegments, residueAtomSegmentMin, residueAtomSegmentMax) - this.pos(value.t3, getResidueTypeAtomIdElementIndex(this.unit, tmpSegment, 'trace')) - - setSegment(tmpSegment, resSegIdx + 2, residueAtomSegments, residueAtomSegmentMin, residueAtomSegmentMax) - this.pos(value.t4, getResidueTypeAtomIdElementIndex(this.unit, tmpSegment, 'trace')) - - value.first = resSegIdx === residueAtomSegmentMin - value.last = resSegIdx === residueAtomSegmentMax + value.first = residueIndex === this.polymerSegment.start + value.last = residueIndex === this.polymerSegment.end - 1 if (!residueIt.hasNext) { this.state = AtomicPolymerTraceIteratorState.nextPolymer @@ -344,22 +342,71 @@ export class AtomicPolymerTraceIterator<T extends number = number> implements It this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements) this.residueIt = Segmentation.transientSegments(this.residueAtomSegments, unit.elements); this.value = createPolymerTraceElement(unit) - this.tmpSegment = { index: 0 as ResidueIndex, start: 0 as ElementIndex, end: 0 as ElementIndex } this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext } } -export class CoarsePolymerTraceIterator<T extends number = number> implements Iterator<PolymerTraceElement> { - private value: PolymerTraceElement +const enum CoarsePolymerTraceIteratorState { nextPolymer, nextElement } +export class CoarsePolymerTraceIterator implements Iterator<PolymerTraceElement> { + private value: PolymerTraceElement + private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex> + private polymerSegment: Segmentation.Segment<ResidueIndex> + private state: CoarsePolymerTraceIteratorState = CoarsePolymerTraceIteratorState.nextPolymer + private conformation: CoarseSphereConformation | CoarseGaussianConformation + private elementIndex: number hasNext: boolean = false; + private pos(target: Vec3, elementIndex: number) { + elementIndex = Math.min(Math.max(this.polymerSegment.start, elementIndex), this.polymerSegment.end - 1) + const index = this.unit.elements[elementIndex] + target[0] = this.conformation.x[index] + target[1] = this.conformation.y[index] + target[2] = this.conformation.z[index] + } + move() { + if (this.state === CoarsePolymerTraceIteratorState.nextPolymer) { + while (this.polymerIt.hasNext) { + this.polymerSegment = this.polymerIt.move(); + this.elementIndex = this.polymerSegment.start + + if (this.elementIndex + 1 < this.polymerSegment.end) { + this.state = CoarsePolymerTraceIteratorState.nextElement + break + } + } + } + + if (this.state === CoarsePolymerTraceIteratorState.nextElement) { + this.elementIndex += 1 + this.value.center.element = this.value.center.unit.elements[this.elementIndex] + + this.pos(this.value.t0, this.elementIndex - 2) + this.pos(this.value.t1, this.elementIndex - 1) + this.pos(this.value.t2, this.elementIndex) + this.pos(this.value.t3, this.elementIndex + 1) + this.pos(this.value.t4, this.elementIndex + 2) + + this.value.first = this.elementIndex === this.polymerSegment.start + this.value.last = this.elementIndex === this.polymerSegment.end - 1 + + if (this.elementIndex + 1 >= this.polymerSegment.end) { + this.state = CoarsePolymerTraceIteratorState.nextPolymer + } + } + + this.hasNext = this.elementIndex + 1 < this.polymerSegment.end || this.polymerIt.hasNext return this.value; } - constructor(unit: Unit.Spheres | Unit.Gaussians) { + constructor(private unit: Unit.Spheres | Unit.Gaussians) { + this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements); this.value = createPolymerTraceElement(unit) - this.hasNext = false + switch (unit.kind) { + case Unit.Kind.Spheres: this.conformation = unit.model.coarseConformation.spheres; break + case Unit.Kind.Gaussians: this.conformation = unit.model.coarseConformation.gaussians; break + } + this.hasNext = this.polymerIt.hasNext } } \ No newline at end of file