diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts index 68ce664400f7cc82f57173ec336244781f63c714..1882c8ea49ec73a401ce75267193936723a52339 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -22,20 +22,75 @@ import { createMeshValues, updateMeshValues, updateRenderableState, createRender import { MeshBuilder } from '../../../shape/mesh-builder'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { createUniformColor } from '../../../util/color-data'; +import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants'; async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, mesh?: Mesh) { const builder = MeshBuilder.create(256, 128, mesh) const t = Mat4.identity() const p = Vec3.zero() + const pd = Vec3.zero() + const p1 = Vec3.zero() + const p2 = Vec3.zero() const carbohydrates = structure.carbohydrates - const linkParams = { radiusTop: 0.2, radiusBottom: 0.2 } + function centerAlign(center: Vec3, normal: Vec3, direction: Vec3) { + Vec3.add(pd, center, direction) + Mat4.targetTo(t, center, pd, normal) + Mat4.setTranslation(t, center) + } + + const side = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4) + const radius = 1.75 + const coneParams = { radiusTop: radius, radiusBottom: 0.0, topCap: true } + + const linkParams = { radiusTop: 0.4, radiusBottom: 0.4 } for (let i = 0, il = carbohydrates.elements.length; i < il; ++i) { const c = carbohydrates.elements[i] - Mat4.setTranslation(t, c.center) - builder.addBox(t, { width: 2, height: 2, depth: 2 }) + const shapeType = getSaccharideShape(c.component.type) + switch (shapeType) { + case SaccharideShapes.FilledSphere: + builder.addIcosahedron(c.center, radius, 1) + break; + case SaccharideShapes.FilledCube: + centerAlign(c.center, c.normal, c.direction) + builder.addBox(t, { width: side, height: side, depth: side }) + break; + case SaccharideShapes.CrossedCube: + // TODO split + centerAlign(c.center, c.normal, c.direction) + builder.addBox(t, { width: side, height: side, depth: side }) + break; + case SaccharideShapes.FilledCone: + Vec3.scaleAndAdd(p1, c.center, c.normal, radius) + Vec3.scaleAndSub(p2, c.center, c.normal, radius) + builder.addCylinder(p1, p2, 1, coneParams) + break + case SaccharideShapes.DevidedCone: + // TODO split + Vec3.scaleAndAdd(p1, c.center, c.normal, radius) + Vec3.scaleAndSub(p2, c.center, c.normal, radius) + builder.addCylinder(p1, p2, 1, coneParams) + break + case SaccharideShapes.FlatBox: + centerAlign(c.center, c.normal, c.direction) + builder.addBox(t, { width: side, height: side / 2, depth: side }) + break + case SaccharideShapes.FilledDiamond: + case SaccharideShapes.DividedDiamond: + case SaccharideShapes.FilledStar: + case SaccharideShapes.FlatDiamond: + case SaccharideShapes.Pentagon: + centerAlign(c.center, c.normal, c.direction) + builder.addBox(t, { width: side, height: 0.5, depth: side }) + break + case SaccharideShapes.FlatHexagon: + default: + centerAlign(c.center, c.normal, c.direction) + builder.addBox(t, { width: side, height: 0.1, depth: side }) + break + } } for (let i = 0, il = carbohydrates.links.length; i < il; ++i) { diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index 659e35acc0c005e151f43ace20d3e0ffc5c13e01..0dddd92f0886f9eeaabd174d1258465862d32eab 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/src/mol-math/linear-algebra/3d/vec3.ts @@ -132,6 +132,14 @@ namespace Vec3 { return out; } + /** Scales b, then subtracts b from a */ + export function scaleAndSub(out: Vec3, a: Vec3, b: Vec3, scale: number) { + out[0] = a[0] - (b[0] * scale); + out[1] = a[1] - (b[1] * scale); + out[2] = a[2] - (b[2] * scale); + return out; + } + /** * Math.round the components of a Vec3 */ diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 94705c6ede10279b8e84df4f2554f28e7aa1c483..2d560f6eee2aea13fb084b3998dcdc3668f533cc 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -11,11 +11,12 @@ import Structure from '../structure'; import { Carbohydrates, CarbohydrateLink, CarbohydrateTerminalLink, CarbohydrateElement } from './data'; import { SaccharideNameMap, UnknownSaccharideComponent } from './constants'; import { Vec3 } from 'mol-math/linear-algebra'; -import { getCenterAndRadius, getMoleculeType } from '../../util'; -import { MoleculeType } from '../../model/types'; +import { getMoleculeType, getPositionMatrix } from '../../util'; +import { MoleculeType, ElementSymbol } from '../../model/types'; import { areConnected } from 'mol-math/graph'; import { combinations } from 'mol-data/util/combination'; import { fillSerial } from 'mol-util/array'; +import PrincipalAxes from 'mol-math/linear-algebra/matrix/principal-axes'; function getResidueIndex(elementIndex: number, unit: Unit.Atomic) { return unit.model.atomicHierarchy.residueAtomSegments.index[unit.elements[elementIndex]] @@ -40,6 +41,32 @@ function getRingIndices(unit: Unit.Atomic, rI: ResidueIndex) { return sugarRings } +const C = ElementSymbol('C') +function getDirection(direction: Vec3, unit: Unit.Atomic, indices: ReadonlyArray<number>, center: Vec3) { + let indexC1 = -1, indexC1X = -1, indexC = -1 + const { elements } = unit + const { position } = unit.conformation + const { label_atom_id, type_symbol } = unit.model.atomicHierarchy.atoms + for (let i = 0, il = indices.length; i < il; ++i) { + const ei = elements[indices[i]] + const atomId = label_atom_id.value(ei) + if (atomId === 'C1') { + indexC1 = ei + break + } else if (indexC1X === -1 && atomId.startsWith('C1')) { + indexC1X = ei + } else if (indexC === -1 && type_symbol.value(ei) === C) { + indexC = ei + } + } + const index = indexC1 !== -1 ? indexC1 + : indexC1X !== -1 ? indexC1X + : indexC !== -1 ? indexC + : elements[indices[0]] + Vec3.normalize(direction, Vec3.sub(direction, center, position(index, direction))) + return direction +} + export function computeCarbohydrates(structure: Structure): Carbohydrates { const links: CarbohydrateLink[] = [] const terminalLinks: CarbohydrateTerminalLink[] = [] @@ -76,15 +103,15 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { const sugarRings = getRingIndices(unit, residueIndex) const ringElements: number[] = [] - console.log('sugarRings', sugarRings) for (let j = 0, jl = sugarRings.length; j < jl; ++j) { - const center = Vec3.zero() - const normal = Vec3.zero() - const direction = Vec3.zero() - const elementIndex = elements.length - getCenterAndRadius(center, unit, sugarRings[j]) + const pa = new PrincipalAxes(getPositionMatrix(unit, sugarRings[j])) + const center = Vec3.copy(Vec3.zero(), pa.center) + const normal = Vec3.copy(Vec3.zero(), pa.normVecC) + const direction = getDirection(Vec3.zero(), unit, sugarRings[j], center) + Vec3.orthogonalize(direction, normal, direction) + const elementIndex = elements.length ringElements.push(elementIndex) elementsMap.set(elementKey(residueIndex, unit.id), elementIndex) elements.push({ center, normal, direction, unit, residueIndex, component: saccharideComp }) diff --git a/src/mol-model/structure/structure/carbohydrates/constants.ts b/src/mol-model/structure/structure/carbohydrates/constants.ts index b5b617a2ea17d41aabc321f8cf84e82e10566c6a..eddb98e1bf3ec18746c73b21ca48533756cac274 100644 --- a/src/mol-model/structure/structure/carbohydrates/constants.ts +++ b/src/mol-model/structure/structure/carbohydrates/constants.ts @@ -175,7 +175,11 @@ const Monosaccharides: SaccharideComponent[] = [ const CommonSaccharideNames: { [k: string]: string[] } = { // Hexose - Glc: ['GLC', 'BGC'], + Glc: [ + 'GLC', 'BGC', + 'BOG', // via GlyFinder + 'TRE', // via GlyFinder, disaccharide but homomer + ], Man: ['MAN', 'BMA'], Gal: ['GAL', 'GLA'], Gul: ['GUP', 'GL0'], @@ -192,7 +196,10 @@ const CommonSaccharideNames: { [k: string]: string[] } = { TalNAc: [], IdoNAc: ['HSQ'], // Hexosamine - GlcN: ['GCS', 'PA1'], + GlcN: [ + 'GCS', 'PA1', + 'IDU', 'SGN', 'SUS', // via GlyFinder + ], ManN: ['95Z'], GalN: ['X6X', '1GN'], GulN: [], @@ -208,7 +215,10 @@ const CommonSaccharideNames: { [k: string]: string[] } = { AltA: [], AllA: [], TalA: ['X0X', 'X1X'], - IdoA: ['IDR'], + IdoA: [ + 'IDR', + 'IDS', // via GlyFinder + ], // Deoxyhexose Qui: ['G6D'], Rha: ['RAM', 'RM4'], diff --git a/src/mol-model/structure/util.ts b/src/mol-model/structure/util.ts index bac2778ca445ea5ed1d17fb7103cc17c5748f9a2..a20abb42e47bf97df10a7d7feb76d9dc01794407 100644 --- a/src/mol-model/structure/util.ts +++ b/src/mol-model/structure/util.ts @@ -8,6 +8,7 @@ import { Model, ResidueIndex, ElementIndex } from './model'; import { MoleculeType, AtomRole, MoleculeTypeAtomRoleId } from './model/types'; import { Vec3 } from 'mol-math/linear-algebra'; import { Unit } from './structure'; +import Matrix from 'mol-math/linear-algebra/matrix/matrix'; export function getMoleculeType(model: Model, rI: ResidueIndex) { const compId = model.atomicHierarchy.residues.label_comp_id.value(rI) @@ -60,4 +61,16 @@ export function getCenterAndRadius(centroid: Vec3, unit: Unit, indices: ArrayLik } Vec3.scale(centroid, centroid, 1/indices.length) return Vec3.distance(centerMin, centroid) +} + +const matrixPos = Vec3.zero() +export function getPositionMatrix(unit: Unit, indices: ArrayLike<number>) { + const pos = unit.conformation.position + const mat = Matrix.create(3, indices.length) + const { elements } = unit + for (let i = 0, il = indices.length; i < il; ++i) { + pos(elements[indices[i]], matrixPos) + Vec3.toArray(matrixPos, mat.data, i * 3) + } + return mat } \ No newline at end of file diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 78d1ef9d6247c1b4af0419ef086501e0ee90f184..96a24b36b5b657dc82b6e1435c343f77ae0b554a 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -96,14 +96,20 @@ export class Stage { // this.loadPdbid('1gfl') // GFP, flourophore has carbonyl oxygen removed // this.loadPdbid('1sfi') // contains cyclic peptid // this.loadPdbid('3sn6') // discontinuous chains - // this.loadPdbid('2zex') // small, contains carbohydrate polymer + // this.loadPdbid('2zex') // contains carbohydrate polymer + // this.loadPdbid('3sgj') // contains carbohydrate polymer + // this.loadPdbid('3ina') // contains GlcN and IdoA + this.loadPdbid('1umz') // contains Xyl (Xyloglucan) + // this.loadPdbid('1mfb') // contains Abe // this.loadPdbid('2gdu') // contains sucrose // this.loadPdbid('2fnc') // contains maltotriose - this.loadPdbid('4zs9') // contains raffinose + // this.loadPdbid('4zs9') // contains raffinose + // this.loadPdbid('2yft') // contains kestose // this.loadPdbid('2b5t') // contains large carbohydrate polymer // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`) // this.loadMmcifUrl(`../../examples/1crn.cif`) + // this.loadPdbid('1zag') // temp // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) // ok // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000002.cif`) // ok