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 23256b8ed47c8c8fe5107b38b893d5cf85df3519..22916064d52e50d22a7aad9364bbc5df710d364f 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -42,7 +42,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru 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 coneParams = { radiusTop: 0.0, radiusBottom: radius, bottomCap: true } const linkParams = { radiusTop: 0.4, radiusBottom: 0.4 } @@ -63,15 +63,14 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru 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) - // TODO fix cylinder direction flipping code + Vec3.scaleAndAdd(p1, c.center, c.direction, radius) + Vec3.scaleAndSub(p2, c.center, c.direction, 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) + Vec3.scaleAndAdd(p1, c.center, c.direction, radius) + Vec3.scaleAndSub(p2, c.center, c.direction, radius) builder.addCylinder(p1, p2, 1, coneParams) break case SaccharideShapes.FlatBox: @@ -87,12 +86,12 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru case SaccharideShapes.FlatDiamond: case SaccharideShapes.Pentagon: centerAlign(c.center, c.normal, c.direction) - builder.addBox(t, { width: side, height: 0.5, depth: side }) + builder.addBox(t, { width: side, height: side, depth: 0.5 }) break case SaccharideShapes.FlatHexagon: default: centerAlign(c.center, c.normal, c.direction) - builder.addBox(t, { width: side, height: 0.1, depth: side }) + builder.addBox(t, { width: side, height: side, depth: 0.1 }) break } } diff --git a/src/mol-geo/shape/mesh-builder.ts b/src/mol-geo/shape/mesh-builder.ts index 1fa344ade16569c9ba5b6a2c37505d9e77f7b41e..f566e1b08e37aa3afc290ef5a027edbf5e1adbe9 100644 --- a/src/mol-geo/shape/mesh-builder.ts +++ b/src/mol-geo/shape/mesh-builder.ts @@ -60,14 +60,16 @@ const tmpCylinderCenter = Vec3.zero() const tmpCylinderMat = Mat4.zero() // const tmpCylinderMatTrans = Mat4.zero() const tmpCylinderStart = Vec3.zero() +const tmpUp = Vec3.zero() function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number) { Vec3.setMagnitude(tmpCylinderMatDir, dir, length / 2) Vec3.add(tmpCylinderCenter, start, tmpCylinderMatDir) - // ensure the direction use to create the rotation is always pointing in the same + // ensure the direction used to create the rotation is always pointing in the same // direction so the triangles of adjacent cylinder will line up - if (Vec3.dot(tmpCylinderMatDir, up) < 0) Vec3.scale(tmpCylinderMatDir, tmpCylinderMatDir, -1) - Vec3.makeRotation(m, up, tmpCylinderMatDir) + Vec3.copy(tmpUp, up) + if (Vec3.dot(tmpCylinderMatDir, tmpUp) < 0) Vec3.scale(tmpUp, tmpUp, -1) + Vec3.makeRotation(m, tmpUp, tmpCylinderMatDir) return Mat4.setTranslation(m, tmpCylinderCenter) } diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 2d560f6eee2aea13fb084b3998dcdc3668f533cc..2e0ac1168b13766bc0d815a7c850d5496d58fa43 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -67,6 +67,12 @@ function getDirection(direction: Vec3, unit: Unit.Atomic, indices: ReadonlyArray return direction } +function getAtomId(unit: Unit.Atomic, index: number) { + const { elements } = unit + const { label_atom_id } = unit.model.atomicHierarchy.atoms + return label_atom_id.value(elements[index]) +} + export function computeCarbohydrates(structure: Structure): Carbohydrates { const links: CarbohydrateLink[] = [] const terminalLinks: CarbohydrateTerminalLink[] = [] @@ -78,6 +84,18 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { return `${residueIndex}|${unitId}` } + function fixLinkDirection(iA: number, iB: number) { + Vec3.sub(elements[iA].direction, elements[iB].center, elements[iA].center) + Vec3.normalize(elements[iA].direction, elements[iA].direction) + } + + const tmpV = Vec3.zero() + function fixTerminalLinkDirection(iA: number, indexB: number, unitB: Unit.Atomic) { + const pos = unitB.conformation.position + Vec3.sub(elements[iA].direction, pos(unitB.elements[indexB], tmpV), elements[iA].center) + Vec3.normalize(elements[iA].direction, elements[iA].direction) + } + // get carbohydrate elements and carbohydrate links induced by intra-residue bonds for (let i = 0, il = structure.units.length; i < il; ++i) { const unit = structure.units[i] @@ -122,6 +140,9 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { for (let j = 0, jl = ringCombinations.length; j < jl; ++j) { const rc = ringCombinations[j] if (areConnected(sugarRings[rc[0]], sugarRings[rc[1]], unit.links, 2)) { + // fix both directions as it is unlcear where the C1 atom is + fixLinkDirection(ringElements[rc[0]], ringElements[rc[1]]) + fixLinkDirection(ringElements[rc[1]], ringElements[rc[0]]) links.push({ carbohydrateIndexA: ringElements[rc[0]], carbohydrateIndexB: ringElements[rc[1]] @@ -154,11 +175,17 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { const elementIndexB = elementsMap.get(elementKey(getResidueIndex(indexB, unitB), unitB.id)) if (elementIndexA !== undefined && elementIndexB !== undefined) { + if (getAtomId(unitA, indexA).startsWith('C1')) { + fixLinkDirection(elementIndexA, elementIndexB) + } links.push({ carbohydrateIndexA: elementIndexA, carbohydrateIndexB: elementIndexB }) } else if (elementIndexA !== undefined) { + if (getAtomId(unitA, indexA).startsWith('C1')) { + fixTerminalLinkDirection(elementIndexA, indexB, unitB) + } terminalLinks.push({ carbohydrateIndex: elementIndexA, elementIndex: indexB, diff --git a/src/mol-model/structure/structure/carbohydrates/constants.ts b/src/mol-model/structure/structure/carbohydrates/constants.ts index eddb98e1bf3ec18746c73b21ca48533756cac274..a6c16844db26d6db7587be46f6ba822d37c3e7e2 100644 --- a/src/mol-model/structure/structure/carbohydrates/constants.ts +++ b/src/mol-model/structure/structure/carbohydrates/constants.ts @@ -178,7 +178,8 @@ const CommonSaccharideNames: { [k: string]: string[] } = { Glc: [ 'GLC', 'BGC', 'BOG', // via GlyFinder - 'TRE', // via GlyFinder, disaccharide but homomer + 'TRE', // via GlyFinder, di-saccharide but homomer + 'MLR', // via GlyFinder, tri-saccharide but homomer ], Man: ['MAN', 'BMA'], Gal: ['GAL', 'GLA'], diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 96a24b36b5b657dc82b6e1435c343f77ae0b554a..1317b4024434b7dc431e6ca1725f3ce24c844846 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -78,7 +78,7 @@ export class Stage { // this.loadPdbid('1hrv') // viral assembly // this.loadPdbid('1rb8') // virus // this.loadPdbid('1blu') // metal coordination - // this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein + this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein // this.loadPdbid('4v5a') // ribosome // this.loadPdbid('3j3q') // ... // this.loadPdbid('2np2') // dna @@ -99,7 +99,7 @@ export class Stage { // 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('1umz') // contains Xyl (Xyloglucan) // this.loadPdbid('1mfb') // contains Abe // this.loadPdbid('2gdu') // contains sucrose // this.loadPdbid('2fnc') // contains maltotriose