diff --git a/src/mol-geo/representation/structure/cartoon.ts b/src/mol-geo/representation/structure/cartoon.ts index 6296fce4978ef5d1795770ebb30baae35f74a9af..37c540cd6551fd95e93f2e4b5ffec426c4a0ba61 100644 --- a/src/mol-geo/representation/structure/cartoon.ts +++ b/src/mol-geo/representation/structure/cartoon.ts @@ -15,6 +15,7 @@ import { PolymerGapVisual, DefaultPolymerGapProps } from './visual/polymer-gap-c import { NucleotideBlockVisual, DefaultNucleotideBlockProps } from './visual/nucleotide-block-mesh'; import { PolymerDirectionVisual, DefaultPolymerDirectionProps } from './visual/polymer-direction-wedge'; import { CarbohydrateSymbolVisual } from './visual/carbohydrate-symbol-mesh'; +import { CarbohydrateLinkVisual } from './visual/carbohydrate-link-cylinder'; export const DefaultCartoonProps = { ...DefaultPolymerTraceProps, @@ -29,14 +30,20 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { const gapRepr = StructureUnitsRepresentation(PolymerGapVisual) const blockRepr = StructureUnitsRepresentation(NucleotideBlockVisual) const directionRepr = StructureUnitsRepresentation(PolymerDirectionVisual) - const carbohydrateRepr = StructureRepresentation(CarbohydrateSymbolVisual) + + // TODO move to own repr + const carbohydrateSymbolRepr = StructureRepresentation(CarbohydrateSymbolVisual) + const carbohydrateLinkRepr = StructureRepresentation(CarbohydrateLinkVisual) return { get renderObjects() { - return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects, ...blockRepr.renderObjects, ...directionRepr.renderObjects, ...carbohydrateRepr.renderObjects ] + return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects, + ...blockRepr.renderObjects, ...directionRepr.renderObjects, + ...carbohydrateSymbolRepr.renderObjects, ...carbohydrateLinkRepr.renderObjects ] }, get props() { - return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props, ...carbohydrateRepr.props } + return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props, + ...carbohydrateSymbolRepr.props, ...carbohydrateLinkRepr.props } }, create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => { const p = Object.assign({}, DefaultCartoonProps, props) @@ -45,7 +52,8 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { await gapRepr.create(structure, p).runInContext(ctx) await blockRepr.create(structure, p).runInContext(ctx) await directionRepr.create(structure, p).runInContext(ctx) - await carbohydrateRepr.create(structure, p).runInContext(ctx) + await carbohydrateSymbolRepr.create(structure, p).runInContext(ctx) + await carbohydrateLinkRepr.create(structure, p).runInContext(ctx) }) }, update: (props: CartoonProps) => { @@ -55,7 +63,8 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { await gapRepr.update(p).runInContext(ctx) await blockRepr.update(p).runInContext(ctx) await directionRepr.update(p).runInContext(ctx) - await carbohydrateRepr.update(p).runInContext(ctx) + await carbohydrateSymbolRepr.update(p).runInContext(ctx) + await carbohydrateLinkRepr.update(p).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { @@ -63,26 +72,30 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { const gapLoci = gapRepr.getLoci(pickingId) const blockLoci = blockRepr.getLoci(pickingId) const directionLoci = directionRepr.getLoci(pickingId) - const carbohydrateRepr = directionRepr.getLoci(pickingId) + const carbohydrateSymbolLoci = carbohydrateSymbolRepr.getLoci(pickingId) + const carbohydrateLinkLoci = carbohydrateLinkRepr.getLoci(pickingId) return !isEmptyLoci(traceLoci) ? traceLoci : !isEmptyLoci(gapLoci) ? gapLoci : !isEmptyLoci(blockLoci) ? blockLoci : !isEmptyLoci(directionLoci) ? directionLoci - : carbohydrateRepr + : !isEmptyLoci(carbohydrateSymbolLoci) ? carbohydrateSymbolLoci + : carbohydrateLinkLoci }, mark: (loci: Loci, action: MarkerAction) => { traceRepr.mark(loci, action) gapRepr.mark(loci, action) blockRepr.mark(loci, action) directionRepr.mark(loci, action) - carbohydrateRepr.mark(loci, action) + carbohydrateSymbolRepr.mark(loci, action) + carbohydrateLinkRepr.mark(loci, action) }, destroy() { traceRepr.destroy() gapRepr.destroy() blockRepr.destroy() directionRepr.destroy() - carbohydrateRepr.destroy() + carbohydrateSymbolRepr.destroy() + carbohydrateLinkRepr.destroy() } } } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts new file mode 100644 index 0000000000000000000000000000000000000000..83be8d6a34304e4b244bfc558222fc7e9a1ca4c1 --- /dev/null +++ b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts @@ -0,0 +1,189 @@ +/** + * 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, Structure, Link, StructureElement } from 'mol-model/structure'; +import { DefaultStructureProps, StructureVisual } from '..'; +import { RuntimeContext } from 'mol-task' +import { createIdentityTransform, createColors } from './util/common'; +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, MarkerData } from '../../../util/marker-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { SizeTheme } from '../../../theme'; +import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { deepEqual } from 'mol-util'; +import { LocationIterator } from './util/location-iterator'; +import { createValueColor } from '../../../util/color-data'; +import { createLinkCylinderMesh, DefaultLinkCylinderProps, LinkCylinderProps } from './util/link'; +import { OrderedSet } from 'mol-data/int'; + +// TODO create seperate visual +// for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) { +// const tl = carbohydrates.terminalLinks[i] +// const center = carbohydrates.elements[tl.carbohydrateIndex].geometry.center +// tl.elementUnit.conformation.position(tl.elementUnit.elements[tl.elementIndex], p) +// if (tl.fromCarbohydrate) { +// builder.addCylinder(center, p, 0.5, linkParams) +// } else { +// builder.addCylinder(p, center, 0.5, linkParams) +// } +// } + +async function createCarbohydrateLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) { + const { links, elements } = structure.carbohydrates + + const builderProps = { + linkCount: links.length, + referencePosition: (edgeIndex: number) => null, + position: (posA: Vec3, posB: Vec3, edgeIndex: number) => { + const l = links[edgeIndex] + Vec3.copy(posA, elements[l.carbohydrateIndexA].geometry.center) + Vec3.copy(posB, elements[l.carbohydrateIndexB].geometry.center) + }, + order: (edgeIndex: number) => 1, + flags: (edgeIndex: number) => 0 + } + + return createLinkCylinderMesh(ctx, builderProps, props, mesh) +} + +export const DefaultCarbohydrateLinkProps = { + ...DefaultMeshProps, + ...DefaultStructureProps, + ...DefaultLinkCylinderProps, + sizeTheme: { name: 'physical', factor: 1 } as SizeTheme, + detail: 0, + unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[] +} +export type CarbohydrateLinkProps = Partial<typeof DefaultCarbohydrateLinkProps> + +export function CarbohydrateLinkVisual(): StructureVisual<CarbohydrateLinkProps> { + let renderObject: MeshRenderObject + let currentProps: typeof DefaultCarbohydrateLinkProps + let mesh: Mesh + let currentStructure: Structure + + return { + get renderObject () { return renderObject }, + async create(ctx: RuntimeContext, structure: Structure, props: CarbohydrateLinkProps = {}) { + currentProps = Object.assign({}, DefaultCarbohydrateLinkProps, props) + currentStructure = structure + + const { colorTheme } = { ...DefaultCarbohydrateLinkProps, ...props } + const instanceCount = 1 + const elementCount = currentStructure.elementCount + + mesh = await createCarbohydrateLinkCylinderMesh(ctx, currentStructure, currentProps, mesh) + // console.log(mesh) + + const transforms = createIdentityTransform() + const color = createValueColor(0x119911)//createColors(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: CarbohydrateLinkProps) { + const newProps = Object.assign({}, currentProps, props) + + if (!renderObject) return false + + let updateColor = false + + // if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) { + // updateColor = true + // } + + // if (updateColor) { + // createColors(LinkIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values) + // } + + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) + + currentProps = newProps + return false + }, + getLoci(pickingId: PickingId) { + return getLinkLoci(pickingId, currentStructure, renderObject.id) + }, + mark(loci: Loci, action: MarkerAction) { + // TODO + // markLink(loci, action, currentStructure, renderObject.values) + }, + destroy() { + // TODO + } + } +} + +function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) { + const { objectId, elementId } = pickingId + if (id === objectId) { + const { links, elements } = structure.carbohydrates + const l = links[elementId] + const carbA = elements[l.carbohydrateIndexA] + const carbB = elements[l.carbohydrateIndexB] + const indexA = OrderedSet.findPredecessorIndex(carbA.unit.elements, carbA.anomericCarbon) + const indexB = OrderedSet.findPredecessorIndex(carbB.unit.elements, carbB.anomericCarbon) + return Link.Loci([ + Link.Location( + carbA.unit, indexA as StructureElement.UnitIndex, + carbB.unit, indexB as StructureElement.UnitIndex + ) + ]) + } + return EmptyLoci +} + +// TODO +// function markLink(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) { +// const tMarker = values.tMarker + +// const links = structure.links +// const elementCount = links.bondCount +// const instanceCount = 1 + +// let changed = false +// const array = tMarker.ref.value.array +// if (isEveryLoci(loci)) { +// applyMarkerAction(array, 0, elementCount * instanceCount, action) +// changed = true +// } else if (Link.isLoci(loci)) { +// for (const b of loci.links) { +// const _idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit) +// if (_idx !== -1) { +// const idx = _idx +// if (applyMarkerAction(array, idx, idx + 1, action) && !changed) { +// changed = true +// } +// } +// } +// } else { +// return +// } +// if (changed) { +// ValueCell.update(tMarker, tMarker.ref.value) +// } +// } \ No newline at end of file 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 eb65b9e8ddad298fb886976ea0d811693b3c5009..b0352d0b4986b46413924663351f65f1b5c38807 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -7,26 +7,27 @@ import { ValueCell } from 'mol-util/value-cell' import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' -import { Unit, Structure } from 'mol-model/structure'; +import { Unit, Structure, StructureElement } from 'mol-model/structure'; import { DefaultStructureProps, StructureVisual } from '..'; import { RuntimeContext } from 'mol-task' -import { createIdentityTransform } from './util/common'; +import { createIdentityTransform, createColors } from './util/common'; 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, EmptyLoci } from 'mol-model/loci'; +import { createMarkers, MarkerAction, MarkerData, applyMarkerAction } from '../../../util/marker-data'; +import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci'; import { SizeTheme } from '../../../theme'; import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util'; import { MeshBuilder } from '../../../shape/mesh-builder'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; -import { createValueColor } from '../../../util/color-data'; import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants'; +import { deepEqual } from 'mol-util'; +import { LocationIterator } from './util/location-iterator'; +import { OrderedSet } from 'mol-data/int'; const t = Mat4.identity() const sVec = Vec3.zero() -const p = Vec3.zero() const pd = Vec3.zero() async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, mesh?: Mesh) { @@ -34,89 +35,74 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru const carbohydrates = structure.carbohydrates - 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 linkParams = { radiusTop: 0.4, radiusBottom: 0.4 } - for (let i = 0, il = carbohydrates.elements.length; i < il; ++i) { const c = carbohydrates.elements[i]; - if (!c.hasRing) continue; - - const cGeo = c.geometry! const shapeType = getSaccharideShape(c.component.type) + const { center, normal, direction } = c.geometry + Vec3.add(pd, center, direction) + Mat4.targetTo(t, center, pd, normal) + Mat4.setTranslation(t, center) + + builder.setId(i) + switch (shapeType) { case SaccharideShapes.FilledSphere: - builder.addSphere(cGeo.center, radius, 2) + builder.addSphere(center, radius, 2) break; case SaccharideShapes.FilledCube: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.scaleUniformly(t, t, side) builder.addBox(t) break; case SaccharideShapes.CrossedCube: // TODO split - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.scaleUniformly(t, t, side) builder.addBox(t) break; case SaccharideShapes.FilledCone: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.scaleUniformly(t, t, side * 1.2) builder.addOctagonalPyramid(t) break case SaccharideShapes.DevidedCone: // TODO split - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.scaleUniformly(t, t, side * 1.2) builder.addOctagonalPyramid(t) break case SaccharideShapes.FlatBox: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.mul(t, t, Mat4.rotZY90) Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2)) builder.addBox(t) break case SaccharideShapes.FilledStar: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.mul(t, t, Mat4.rotZY90) builder.addStar(t, { outerRadius: side, innerRadius: side / 2, thickness: side / 2, pointCount: 5 }) break case SaccharideShapes.FilledDiamond: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.mul(t, t, Mat4.rotZY90) Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4)) builder.addOctahedron(t) break case SaccharideShapes.DividedDiamond: // TODO split - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.mul(t, t, Mat4.rotZY90) Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4)) builder.addOctahedron(t) break case SaccharideShapes.FlatDiamond: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.mul(t, t, Mat4.rotZY90) Mat4.scale(t, t, Vec3.set(sVec, side, side / 2, side / 2)) builder.addDiamondPrism(t) break case SaccharideShapes.Pentagon: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.mul(t, t, Mat4.rotZY90) Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2)) builder.addPentagonalPrism(t) break case SaccharideShapes.FlatHexagon: default: - centerAlign(cGeo.center, cGeo.normal, cGeo.direction) Mat4.mul(t, t, Mat4.rotZYZ90) Mat4.scale(t, t, Vec3.set(sVec, side / 1.5, side , side / 2)) builder.addHexagonalPrism(t) @@ -124,24 +110,6 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru } } - for (let i = 0, il = carbohydrates.links.length; i < il; ++i) { - const l = carbohydrates.links[i] - const centerA = carbohydrates.elements[l.carbohydrateIndexA].geometry!.center - const centerB = carbohydrates.elements[l.carbohydrateIndexB].geometry!.center - builder.addCylinder(centerA, centerB, 0.5, linkParams) - } - - for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) { - const tl = carbohydrates.terminalLinks[i] - const center = carbohydrates.elements[tl.carbohydrateIndex].geometry!.center - tl.elementUnit.conformation.position(tl.elementUnit.elements[tl.elementIndex], p) - if (tl.fromCarbohydrate) { - builder.addCylinder(center, p, 0.5, linkParams) - } else { - builder.addCylinder(p, center, 0.5, linkParams) - } - } - return builder.getMesh() } @@ -166,14 +134,14 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr currentProps = Object.assign({}, DefaultCarbohydrateSymbolProps, props) currentStructure = structure + const { colorTheme } = { ...DefaultCarbohydrateSymbolProps, ...props } const instanceCount = 1 const elementCount = currentStructure.elementCount mesh = await createCarbohydrateSymbolMesh(ctx, currentStructure, mesh) - // console.log(mesh) const transforms = createIdentityTransform() - const color = createValueColor(0x999911) // TODO + const color = createColors(createCarbohydrateIterator(structure), colorTheme) const marker = createMarkers(instanceCount * elementCount) const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } @@ -196,19 +164,87 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr if (!renderObject) return false + let updateColor = false + + if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) { + updateColor = true + } + + if (updateColor) { + createColors(createCarbohydrateIterator(currentStructure), newProps.colorTheme, renderObject.values) + } + updateMeshValues(renderObject.values, newProps) updateRenderableState(renderObject.state, newProps) + currentProps = newProps return false }, getLoci(pickingId: PickingId) { - return EmptyLoci + return getCarbohydrateLoci(pickingId, currentStructure, renderObject.id) }, mark(loci: Loci, action: MarkerAction) { - // TODO + markCarbohydrate(loci, action, currentStructure, renderObject.values) }, destroy() { // TODO } } } + +function createCarbohydrateIterator(structure: Structure): LocationIterator { + const carbs = structure.carbohydrates.elements + const elementCount = carbs.length + const instanceCount = 1 + const location = StructureElement.create() + const getLocation = (elementIndex: number, instanceIndex: number) => { + const carb = carbs[elementIndex] + location.unit = carb.unit + location.element = carb.anomericCarbon + return location + } + return LocationIterator(elementCount, instanceCount, getLocation) +} + +function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: number) { + const { objectId, elementId } = pickingId + if (id === objectId) { + const carb = structure.carbohydrates.elements[elementId] + const { unit } = carb + const index = OrderedSet.findPredecessorIndex(unit.elements, carb.anomericCarbon) + const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex) + return StructureElement.Loci([{ unit, indices }]) + } + return EmptyLoci +} + +function markCarbohydrate(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) { + const tMarker = values.tMarker + + const { byUnitAndElement } = structure.carbohydrates + const elementCount = structure.carbohydrates.elements.length + + let changed = false + const array = tMarker.ref.value.array + if (isEveryLoci(loci)) { + if (applyMarkerAction(array, 0, elementCount, action)) { + changed = true + } + } else if (StructureElement.isLoci(loci)) { + for (const e of loci.elements) { + OrderedSet.forEach(e.indices, index => { + const idx = byUnitAndElement(e.unit, e.unit.elements[index]) + if (idx !== undefined) { + if (applyMarkerAction(array, idx, idx + 1, action) && !changed) { + changed = true + } + } + }) + } + } else { + return + } + if (changed) { + ValueCell.update(tMarker, tMarker.ref.value) + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts index bf55d079d25cd346fc2436638986917589fb5e01..9a4ec8d3b7973667448955b35b04bf3808564a3f 100644 --- a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts +++ b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts @@ -105,7 +105,7 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?: } if (i % 10000 === 0 && ctx.shouldUpdate) { - await ctx.update({ message: 'Gap mesh', current: i }); + await ctx.update({ message: 'Nucleotide block mesh', current: i }); } ++i } diff --git a/src/mol-geo/representation/structure/visual/util/common.ts b/src/mol-geo/representation/structure/visual/util/common.ts index e2e43030fd29a438e04924175c920d5541c63d9e..07bc94993cb11eafcf00208fd395fe0aed5175dd 100644 --- a/src/mol-geo/representation/structure/visual/util/common.ts +++ b/src/mol-geo/representation/structure/visual/util/common.ts @@ -16,6 +16,7 @@ import { ColorTheme, SizeTheme } from '../../../../theme'; import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../../../theme/structure/color'; import { ValueCell, defaults } from 'mol-util'; import { LocationIterator } from './location-iterator'; +import { carbohydrateSymbolColorData } from '../../../../theme/structure/color/carbohydrate-symbol'; export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) { const unitCount = units.length @@ -37,6 +38,8 @@ export function createColors(locationIt: LocationIterator, props: ColorTheme, co switch (props.name) { case 'atom-index': return elementIndexColorData(locationIt, colorData) + case 'carbohydrate-symbol': + return carbohydrateSymbolColorData(locationIt, colorData) case 'chain-id': return chainIdColorData(locationIt, colorData) case 'element-symbol': diff --git a/src/mol-geo/theme/index.ts b/src/mol-geo/theme/index.ts index a306838fc92e1d7afc1d037d078c14c0b633b4a6..d34cb26a6d101a9ff03d58d9d03b136337ce69d1 100644 --- a/src/mol-geo/theme/index.ts +++ b/src/mol-geo/theme/index.ts @@ -5,8 +5,6 @@ */ import { Color } from 'mol-util/color'; -// import { Loci } from 'mol-model/loci'; -// import { Structure } from 'mol-model/structure'; export interface UniformColorTheme { name: 'uniform' @@ -14,27 +12,15 @@ export interface UniformColorTheme { } export interface ScaleColorTheme { - name: 'atom-index' | 'chain-id' | 'element-symbol' | 'instance-index' + name: 'atom-index' | 'chain-id'| 'instance-index' domain?: [number, number] } -// interface StructureColorProvider { -// uniform(): Color -// instance(instanceIdx: number): Color -// element(elementIdx: number): Color -// elementInstance(elementIdx: number, instanceIdx: number): Color - -// lociColor(loci: Loci): Color -// } - -// export namespace ColorProvider { -// export function fromLociColor(lociColor: (loci: Loci) => Color) { - -// return -// } -// } +export interface TableColorTheme { + name: 'carbohydrate-symbol' | 'element-symbol' +} -export type ColorTheme = UniformColorTheme | ScaleColorTheme +export type ColorTheme = UniformColorTheme | ScaleColorTheme | TableColorTheme export interface UniformSizeTheme { name: 'uniform', diff --git a/src/mol-geo/theme/structure/color/carbohydrate-symbol.ts b/src/mol-geo/theme/structure/color/carbohydrate-symbol.ts new file mode 100644 index 0000000000000000000000000000000000000000..e6f8065b436ea3260b77f9aea806b1f8dead892a --- /dev/null +++ b/src/mol-geo/theme/structure/color/carbohydrate-symbol.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/structure'; + +import { ColorData, createElementColor } from '../../../util/color-data'; +import { ColorScale, Color } from 'mol-util/color'; +import { LocationIterator, LocationValue } from '../../../representation/structure/visual/util/location-iterator'; + +function getAsymId(unit: Unit): StructureElement.Property<string> { + switch (unit.kind) { + case Unit.Kind.Atomic: + return StructureProperties.chain.label_asym_id + case Unit.Kind.Spheres: + case Unit.Kind.Gaussians: + return StructureProperties.coarse.asym_id + } +} + +export function carbohydrateSymbolColorData(locationIt: LocationIterator, colorData?: ColorData) { + const l = StructureElement.create() + + function colorFn(locationValue: LocationValue): Color { + const { location } = locationValue + if (StructureElement.isLocation(location)) { + const map = location.unit.model.properties.asymIdSerialMap + const scale = ColorScale.create({ domain: [ 0, map.size - 1 ] }) + const asym_id = getAsymId(location.unit) + return scale.color(map.get(asym_id(location)) || 0) + } else if (Link.isLocation(location)) { + const map = location.aUnit.model.properties.asymIdSerialMap + const scale = ColorScale.create({ domain: [ 0, map.size - 1 ] }) + const asym_id = getAsymId(location.aUnit) + l.unit = location.aUnit + l.element = location.aUnit.elements[location.aIndex] + return scale.color(map.get(asym_id(l)) || 0) + } + return 0 + } + + return createElementColor(locationIt, colorFn, colorData) +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 97034a5c5e856fc6b9ba91326ac0c903a6c949d9..cb1f9a33aa945d78624476e51a55b601038a90ac 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -18,7 +18,7 @@ import StructureElement from '../element'; import Structure from '../structure'; import Unit from '../unit'; import { SaccharideNameMap, UnknownSaccharideComponent } from './constants'; -import { CarbohydrateElement, CarbohydrateLink, Carbohydrates, CarbohydrateTerminalLink } from './data'; +import { CarbohydrateElement, CarbohydrateLink, Carbohydrates, CarbohydrateTerminalLink, PartialCarbohydrateElement } from './data'; import { UnitRings, UnitRing } from '../unit/rings'; import { ElementIndex } from '../../model/indexing'; @@ -49,7 +49,7 @@ function getAnomericCarbon(unit: Unit.Atomic, ringAtoms: ArrayLike<StructureElem // possibly an anomeric carbon if this is a mono-saccharide without a glycosidic bond indexHasOxygenAndCarbon = ei } else if (label_atom_id.value(ei).startsWith('C1')) { - // likely the anomeric carbon as it is name C1 by convention + // likely the anomeric carbon as it is named C1 by convention indexHasC1Name = ei } else { // use any carbon as a fallback @@ -122,6 +122,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { const links: CarbohydrateLink[] = [] const terminalLinks: CarbohydrateTerminalLink[] = [] const elements: CarbohydrateElement[] = [] + const partialElements: PartialCarbohydrateElement[] = [] const elementsWithRingMap = new Map<string, number>() @@ -173,10 +174,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { const sugarRings = filterFusedRings(unit.rings, sugarResidueMap.get(residueIndex)); if (!sugarRings || !sugarRings.length) { - elements.push({ - hasRing: false, - unit, residueIndex, component: saccharideComp - }) + partialElements.push({ unit, residueIndex, component: saccharideComp }) continue; } @@ -199,8 +197,8 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { elementsWithRingMap.set(elementKey(residueIndex, unit.id, altId), elementIndex) elements.push({ geometry: { center, normal, direction }, - hasRing: true, - unit, residueIndex, component: saccharideComp + component: saccharideComp, + unit, residueIndex, anomericCarbon }) } @@ -279,5 +277,20 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { }) } - return { links, terminalLinks, elements } + // build lookup map + const map = new Map<string, number>() + for (let i = 0, il = elements.length; i < il; ++i) { + const { unit, anomericCarbon } = elements[i] + map.set(key(unit, anomericCarbon), i) + } + + function key(unit: Unit, anomericCarbon: ElementIndex) { + return `${unit.id}|${anomericCarbon}` + } + + function byUnitAndElement(unit: Unit, anomericCarbon: ElementIndex) { + return map.get(key(unit, anomericCarbon)) + } + + return { links, terminalLinks, elements, partialElements, byUnitAndElement } } \ No newline at end of file diff --git a/src/mol-model/structure/structure/carbohydrates/data.ts b/src/mol-model/structure/structure/carbohydrates/data.ts index 0c9361042df84c5d713888b20be4424cb96f66b3..046748f921acd5d5efc31d5e6d42166d7797d1a4 100644 --- a/src/mol-model/structure/structure/carbohydrates/data.ts +++ b/src/mol-model/structure/structure/carbohydrates/data.ts @@ -6,7 +6,7 @@ import Unit from '../unit'; import { Vec3 } from 'mol-math/linear-algebra'; -import { ResidueIndex } from '../../model'; +import { ResidueIndex, ElementIndex } from '../../model'; import { SaccharideComponent } from './constants'; export interface CarbohydrateLink { @@ -23,16 +23,24 @@ export interface CarbohydrateTerminalLink { } export interface CarbohydrateElement { - // geometry is only defined if at least one ring is present. - readonly geometry?: { readonly center: Vec3, readonly normal: Vec3, readonly direction: Vec3 }, - readonly hasRing: boolean, + readonly geometry: { readonly center: Vec3, readonly normal: Vec3, readonly direction: Vec3 }, + readonly anomericCarbon: ElementIndex, readonly unit: Unit.Atomic, readonly residueIndex: ResidueIndex, - readonly component: SaccharideComponent + readonly component: SaccharideComponent, +} + +// partial carbohydrate with no ring present +export interface PartialCarbohydrateElement { + readonly unit: Unit.Atomic, + readonly residueIndex: ResidueIndex, + readonly component: SaccharideComponent, } export interface Carbohydrates { links: ReadonlyArray<CarbohydrateLink> terminalLinks: ReadonlyArray<CarbohydrateTerminalLink> elements: ReadonlyArray<CarbohydrateElement> + partialElements: ReadonlyArray<PartialCarbohydrateElement> + byUnitAndElement: (unit: Unit, element: ElementIndex) => number | undefined } \ No newline at end of file diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index c88e165ecbd419f523053e38b8743c9fa9bbfdfc..ed5ce1cc1e9c53bb933dab8bc12afa39c5c25e3c 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -110,7 +110,8 @@ export class Stage { // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`) // this.loadMmcifUrl(`../../examples/1crn.cif`) - // this.loadPdbid('1zag') // temp + // this.loadPdbid('5u0q') // mixed dna/rna in same polymer + // this.loadPdbid('5u0q') // temp // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) // ok // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000002.cif`) // ok @@ -154,8 +155,8 @@ export class Stage { } loadPdbid (pdbid: string) { - return this.loadMmcifUrl(`http://www.ebi.ac.uk/pdbe/static/entry/${pdbid}_updated.cif`) - // return this.loadMmcifUrl(`https://files.rcsb.org/download/${pdbid}.cif`) + // return this.loadMmcifUrl(`http://www.ebi.ac.uk/pdbe/static/entry/${pdbid}_updated.cif`) + return this.loadMmcifUrl(`https://files.rcsb.org/download/${pdbid}.cif`) } dispose () {