Skip to content
Snippets Groups Projects
Commit 9bcc5ab2 authored by Alexander Rose's avatar Alexander Rose
Browse files

added nucleotide blocks visual

parent 5a7a4355
Branches
Tags
No related merge requests found
......@@ -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()
}
}
}
\ No newline at end of file
/**
* 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
}
}
}
......@@ -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) {
......
......@@ -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
*/
......
......@@ -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
*
......
/**
* 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)}`
}
\ No newline at end of file
......@@ -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`)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment