Browse Source

added nucleotide blocks visual

Alexander Rose 6 years ago
parent
commit
9bcc5ab211

+ 14 - 4
src/mol-geo/representation/structure/cartoon.ts

@@ -12,29 +12,33 @@ import { Loci, isEmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../../util/marker-data';
 import { PolymerTraceVisual, DefaultPolymerTraceProps } from './visual/polymer-trace-mesh';
 import { PolymerGapVisual, DefaultPolymerGapProps } from './visual/polymer-gap-cylinder';
+import { NucleotideBlockVisual, DefaultNucleotideBlockProps } from './visual/nucleotide-block-mesh';
 
 export const DefaultCartoonProps = {
     ...DefaultPolymerTraceProps,
-    ...DefaultPolymerGapProps
+    ...DefaultPolymerGapProps,
+    ...DefaultNucleotideBlockProps
 }
 export type CartoonProps = Partial<typeof DefaultCartoonProps>
 
 export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
     const traceRepr = StructureUnitsRepresentation(PolymerTraceVisual)
     const gapRepr = StructureUnitsRepresentation(PolymerGapVisual)
+    const blockRepr = StructureUnitsRepresentation(NucleotideBlockVisual)
 
     return {
         get renderObjects() {
-            return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects ]
+            return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects, ...blockRepr.renderObjects ]
         },
         get props() {
-            return { ...traceRepr.props, ...gapRepr.props }
+            return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props }
         },
         create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => {
             const p = Object.assign({}, DefaultCartoonProps, props)
             return Task.create('CartoonRepresentation', async ctx => {
                 await traceRepr.create(structure, p).runInContext(ctx)
                 await gapRepr.create(structure, p).runInContext(ctx)
+                await blockRepr.create(structure, p).runInContext(ctx)
             })
         },
         update: (props: CartoonProps) => {
@@ -42,20 +46,26 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
             return Task.create('Updating CartoonRepresentation', async ctx => {
                 await traceRepr.update(p).runInContext(ctx)
                 await gapRepr.update(p).runInContext(ctx)
+                await blockRepr.update(p).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {
             const traceLoci = traceRepr.getLoci(pickingId)
             const gapLoci = gapRepr.getLoci(pickingId)
-            return isEmptyLoci(traceLoci) ? gapLoci : traceLoci
+            const blockLoci = blockRepr.getLoci(pickingId)
+            return !isEmptyLoci(traceLoci) ? traceLoci
+                : !isEmptyLoci(gapLoci) ? gapLoci
+                : blockLoci
         },
         mark: (loci: Loci, action: MarkerAction) => {
             traceRepr.mark(loci, action)
             gapRepr.mark(loci, action)
+            blockRepr.mark(loci, action)
         },
         destroy() {
             traceRepr.destroy()
             gapRepr.destroy()
+            blockRepr.destroy()
         }
     }
 }

+ 205 - 0
src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts

@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors } from './util/common';
+import { deepEqual } from 'mol-util';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { createMarkers, MarkerAction } from '../../../util/marker-data';
+import { Loci } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+import { MeshBuilder } from '../../../shape/mesh-builder';
+import { getElementLoci, markElement } from './util/element';
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+import { Segmentation, SortedArray } from 'mol-data/int';
+import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types';
+import { getElementIndexForAtomId } from 'mol-model/structure/util';
+import { getElementIndexForResidueTypeAtomId } from './util/polymer';
+
+async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+    if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
+
+    const builder = MeshBuilder.create(256, 128, mesh)
+
+    const { elements, model } = unit
+    const { chemicalComponentMap, modifiedResidues } = unit.model.properties
+    const { chainAtomSegments, residueAtomSegments, residues } = unit.model.atomicHierarchy
+    const { label_comp_id } = residues
+    const pos = unit.conformation.invariantPosition
+
+    const chainIt = Segmentation.transientSegments(chainAtomSegments, elements)
+    const residueIt = Segmentation.transientSegments(residueAtomSegments, elements)
+
+    const p1 = Vec3.zero()
+    const p2 = Vec3.zero()
+    const p3 = Vec3.zero()
+    const p4 = Vec3.zero()
+    const p5 = Vec3.zero()
+    const p6 = Vec3.zero()
+    const v12 = Vec3.zero()
+    const v34 = Vec3.zero()
+    const vC = Vec3.zero()
+    const center = Vec3.zero()
+    const t = Mat4.identity()
+
+    let i = 0
+    while (chainIt.hasNext) {
+        residueIt.setSegment(chainIt.move());
+
+        while (residueIt.hasNext) {
+            const { index: residueIndex } = residueIt.move();
+            const cc = chemicalComponentMap.get(label_comp_id.value(residueIndex))
+            const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown
+
+            if (isNucleic(moleculeType)) {
+                let compId = label_comp_id.value(residueIndex)
+                const parentId = modifiedResidues.parentId.get(compId)
+                if (parentId !== undefined) compId = parentId
+                let idx1 = -1, idx2 = -1, idx3 = -1, idx4 = -1, idx5 = -1, idx6 = -1
+                let width = 4.5, height = 4.5, depth = 0.5
+
+                if (isPurinBase(compId)) {
+                    height = 4.5
+                    idx1 = getElementIndexForAtomId(model, residueIndex, 'N1')
+                    idx2 = getElementIndexForAtomId(model, residueIndex, 'C4')
+                    idx3 = getElementIndexForAtomId(model, residueIndex, 'C6')
+                    idx4 = getElementIndexForAtomId(model, residueIndex, 'C2')
+                    idx5 = getElementIndexForAtomId(model, residueIndex, 'N9')
+                    idx6 = getElementIndexForResidueTypeAtomId(model, residueIndex, 'trace')
+                } else if (isPyrimidineBase(compId)) {
+                    height = 3.0
+                    idx1 = getElementIndexForAtomId(model, residueIndex, 'N3')
+                    idx2 = getElementIndexForAtomId(model, residueIndex, 'C6')
+                    idx3 = getElementIndexForAtomId(model, residueIndex, 'C4')
+                    idx4 = getElementIndexForAtomId(model, residueIndex, 'C2')
+                    idx5 = getElementIndexForAtomId(model, residueIndex, 'N1')
+                    idx6 = getElementIndexForResidueTypeAtomId(model, residueIndex, 'trace')
+                }
+
+                if (idx1 !== -1 && idx2 !== -1 && idx3 !== -1 && idx4 !== -1 && idx5 !== -1 && idx6 !== -1) {
+                    pos(idx1, p1); pos(idx2, p2); pos(idx3, p3); pos(idx4, p4); pos(idx5, p5); pos(idx6, p6)
+                    Vec3.normalize(v12, Vec3.sub(v12, p2, p1))
+                    Vec3.normalize(v34, Vec3.sub(v34, p4, p3))
+                    Vec3.normalize(vC, Vec3.cross(vC, v12, v34))
+                    Mat4.targetTo(t, p1, p2, vC)
+                    Vec3.scaleAndAdd(center, p1, v12, height / 2)
+                    Mat4.setTranslation(t, center)
+                    builder.setId(SortedArray.findPredecessorIndex(elements, idx6))
+                    builder.addBox(t, { width: width, height: depth, depth: height })
+                    builder.addCylinder(p5, p6, 1, { radiusTop: 0.2, radiusBottom: 0.2 })
+                }
+            }
+
+            if (i % 10000 === 0 && ctx.shouldUpdate) {
+                await ctx.update({ message: 'Gap mesh', current: i });
+            }
+            ++i
+        }
+    }
+
+    return builder.getMesh()
+}
+
+export const DefaultNucleotideBlockProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type NucleotideBlockProps = Partial<typeof DefaultNucleotideBlockProps>
+
+export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultNucleotideBlockProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: NucleotideBlockProps = {}) {
+            currentProps = Object.assign({}, DefaultNucleotideBlockProps, props)
+            currentGroup = group
+
+            const { colorTheme, unitKinds } = { ...DefaultNucleotideBlockProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            mesh = unitKinds.includes(unit.kind)
+                ? await createNucleotideBlockMesh(ctx, unit, mesh)
+                : Mesh.createEmpty(mesh)
+            // console.log(mesh)
+
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+                aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3))
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: NucleotideBlockProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            if (newProps.detail !== currentProps.detail) {
+                const unit = currentGroup.units[0]
+                mesh = await createNucleotideBlockMesh(ctx, unit, mesh)
+                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                const elementCount = currentGroup.elements.length
+                if (ctx.shouldUpdate) await ctx.update('Computing nucleotide block colors');
+                createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            return getElementLoci(renderObject.id, currentGroup, pickingId)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 3 - 26
src/mol-geo/representation/structure/visual/util/polymer.ts

@@ -11,6 +11,7 @@ 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';
+import { getElementIndexForAtomId, getMoleculeType } from 'mol-model/structure/util';
 
 export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> {
     switch (unit.kind) {
@@ -67,7 +68,7 @@ export function getPolymerGapCount(unit: Unit) {
     return count
 }
 
-function getResidueTypeAtomId(moleculeType: MoleculeType, atomType: 'trace' | 'direction') {
+export function getResidueTypeAtomId(moleculeType: MoleculeType, atomType: 'trace' | 'direction') {
     switch (moleculeType) {
         case MoleculeType.protein:
             switch (atomType) {
@@ -91,35 +92,11 @@ function getResidueTypeAtomId(moleculeType: MoleculeType, atomType: 'trace' | 'd
     return ''
 }
 
-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(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 offsets[rI] as ElementIndex
-}
-
-function getElementIndexForResidueTypeAtomId(model: Model, rI: ResidueIndex, atomType: 'trace' | 'direction') {
+export 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) {
-//     const { residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
-//     const { label_comp_id, label_seq_id } = residues
-//     const { label_asym_id } = chains
-//     const cI = chainSegments.segmentMap[residueSegments.segments[rI]]
-//     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
-// }
-
 /** Iterates over consecutive pairs of residues/coarse elements in polymers */
 export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePair> {
     switch (unit.kind) {

+ 72 - 0
src/mol-math/linear-algebra/3d/mat4.ts

@@ -355,6 +355,24 @@ namespace Mat4 {
         return out;
     }
 
+    /**
+     * Sets the specified quaternion with values corresponding to the given
+     * axes. Each axis is a vec3 and is expected to be unit length and
+     * perpendicular to all other specified axes.
+     */
+    export function setAxes(out: Mat4, view: Vec3, right: Vec3, up: Vec3) {
+        out[0] = right[0];
+        out[4] = right[1];
+        out[8] = right[2];
+        out[1] = up[0];
+        out[5] = up[1];
+        out[9] = up[2];
+        out[2] = view[0];
+        out[6] = view[1];
+        out[10] = view[2];
+        return out
+    }
+
     export function rotate(out: Mat4, a: Mat4, rad: number, axis: Mat4) {
         let x = axis[0], y = axis[1], z = axis[2],
             len = Math.sqrt(x * x + y * y + z * z),
@@ -747,6 +765,60 @@ namespace Mat4 {
         return out;
     }
 
+    /**
+     * Generates a matrix that makes something look at something else.
+     */
+    export function targetTo(out: Mat4, eye: Vec3, target: Vec3, up: Vec3) {
+        const eyex = eye[0],
+            eyey = eye[1],
+            eyez = eye[2],
+            upx = up[0],
+            upy = up[1],
+            upz = up[2];
+
+        let z0 = eyex - target[0],
+            z1 = eyey - target[1],
+            z2 = eyez - target[2];
+
+        let len = z0*z0 + z1*z1 + z2*z2;
+        if (len > 0) {
+            len = 1 / Math.sqrt(len);
+            z0 *= len;
+            z1 *= len;
+            z2 *= len;
+        }
+
+        let x0 = upy * z2 - upz * z1,
+            x1 = upz * z0 - upx * z2,
+            x2 = upx * z1 - upy * z0;
+
+        len = x0*x0 + x1*x1 + x2*x2;
+        if (len > 0) {
+            len = 1 / Math.sqrt(len);
+            x0 *= len;
+            x1 *= len;
+            x2 *= len;
+        }
+
+        out[0] = x0;
+        out[1] = x1;
+        out[2] = x2;
+        out[3] = 0;
+        out[4] = z1 * x2 - z2 * x1;
+        out[5] = z2 * x0 - z0 * x2;
+        out[6] = z0 * x1 - z1 * x0;
+        out[7] = 0;
+        out[8] = z0;
+        out[9] = z1;
+        out[10] = z2;
+        out[11] = 0;
+        out[12] = eyex;
+        out[13] = eyey;
+        out[14] = eyez;
+        out[15] = 1;
+        return out;
+    }
+
     /**
      * Perm is 0-indexed permutation
      */

+ 13 - 0
src/mol-model/structure/model/types.ts

@@ -108,6 +108,15 @@ export const WaterNames = [
     'SOL', 'WAT', 'HOH', 'H2O', 'W', 'DOD', 'D3O', 'TIP3', 'TIP4', 'SPC'
 ]
 
+export const RnaBaseNames = [ 'A', 'C', 'T', 'G', 'I', 'U' ]
+export const DnaBaseNames = [ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ]
+export const PurinBaseNames = [ 'A', 'G', 'DA', 'DG', 'DI' ]
+export const PyrimidineBaseNames = [ 'C', 'T', 'U', 'DC', 'DT', 'DU' ]
+export const BaseNames = RnaBaseNames.concat(DnaBaseNames)
+
+export const isPurinBase = (compId: string) => PurinBaseNames.includes(compId.toUpperCase())
+export const isPyrimidineBase = (compId: string) => PyrimidineBaseNames.includes(compId.toUpperCase())
+
 /** get the molecule type from component type and id */
 export function getMoleculeType(compType: string, compId: string) {
     compType = compType.toUpperCase()
@@ -133,6 +142,10 @@ export function isPolymer(moleculeType: MoleculeType) {
     return moleculeType === MoleculeType.protein || moleculeType === MoleculeType.DNA || moleculeType === MoleculeType.RNA
 }
 
+export function isNucleic(moleculeType: MoleculeType) {
+    return moleculeType === MoleculeType.DNA || moleculeType === MoleculeType.RNA
+}
+
 /**
  * all chemical components with the word "ion" in their name, Sep 2016
  *

+ 32 - 0
src/mol-model/structure/util.ts

@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Model, ResidueIndex, ElementIndex } from './model';
+import { MoleculeType } from './model/types';
+
+export function getMoleculeType(model: Model, rI: ResidueIndex) {
+    const compId = model.atomicHierarchy.residues.label_comp_id.value(rI)
+    const chemCompMap = model.properties.chemicalComponentMap
+    const cc = chemCompMap.get(compId)
+    return cc ? cc.moleculeType : MoleculeType.unknown
+}
+
+export 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 offsets[rI] as ElementIndex
+}
+
+export function residueLabel(model: Model, rI: number) {
+    const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
+    const { label_comp_id, label_seq_id } = residues
+    const { label_asym_id } = chains
+    const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
+    return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
+}

+ 8 - 1
src/mol-view/stage.ts

@@ -80,7 +80,14 @@ export class Stage {
         // this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water
         // this.loadPdbid('4v5a') // ribosome
         // this.loadPdbid('3j3q') // ...
-        this.loadPdbid('2np2') // dna
+        // this.loadPdbid('2np2') // dna
+        // this.loadPdbid('1d66') // dna
+        this.loadPdbid('1bna') // B form dna
+        // this.loadPdbid('1y26') // rna
+        // this.loadPdbid('1xv6') // rna, modified nucleotides
+        // this.loadPdbid('3bbm') // rna with linker
+        // this.loadPdbid('1gfl') // GFP, flourophore has carbonyl oxygen removed
+        // this.loadPdbid('1sfi') // contains cyclic peptid
         // this.loadPdbid('3sn6') // discontinuous chains
         // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
         // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`)