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

wip, bond order rendering

parent 0a0d5552
No related branches found
No related tags found
No related merge requests found
...@@ -18,7 +18,7 @@ export const DefaultBallAndStickProps = { ...@@ -18,7 +18,7 @@ export const DefaultBallAndStickProps = {
...DefaultElementSphereProps, ...DefaultElementSphereProps,
...DefaultIntraUnitLinkProps, ...DefaultIntraUnitLinkProps,
sizeTheme: { name: 'physical', factor: 0.2 } as SizeTheme, sizeTheme: { name: 'uniform', value: 0.25 } as SizeTheme,
} }
export type BallAndStickProps = Partial<typeof DefaultBallAndStickProps> export type BallAndStickProps = Partial<typeof DefaultBallAndStickProps>
...@@ -31,7 +31,7 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti ...@@ -31,7 +31,7 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti
return [ ...sphereRepr.renderObjects, ...intraLinkRepr.renderObjects ] return [ ...sphereRepr.renderObjects, ...intraLinkRepr.renderObjects ]
}, },
create: (structure: Structure, props: BallAndStickProps = {} as BallAndStickProps) => { create: (structure: Structure, props: BallAndStickProps = {} as BallAndStickProps) => {
const p = Object.assign({}, props, DefaultBallAndStickProps) const p = Object.assign({}, DefaultBallAndStickProps, props)
return Task.create('Creating BallAndStickRepresentation', async ctx => { return Task.create('Creating BallAndStickRepresentation', async ctx => {
await sphereRepr.create(structure, p).runInContext(ctx) await sphereRepr.create(structure, p).runInContext(ctx)
await intraLinkRepr.create(structure, p).runInContext(ctx) await intraLinkRepr.create(structure, p).runInContext(ctx)
...@@ -49,7 +49,7 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti ...@@ -49,7 +49,7 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti
if (isEmptyLoci(sphereLoci)) { if (isEmptyLoci(sphereLoci)) {
return intraLinkLoci return intraLinkLoci
} else { } else {
return sphereLoci return sphereLoci
} }
}, },
mark: (loci: Loci, action: MarkerAction) => { mark: (loci: Loci, action: MarkerAction) => {
......
...@@ -28,42 +28,121 @@ import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../. ...@@ -28,42 +28,121 @@ import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../.
import { SizeTheme } from '../../../theme'; import { SizeTheme } from '../../../theme';
import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id'; import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id';
async function createLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) { const DefaultLinkCylinderProps = {
linkScale: 0.4,
linkSpacing: 1,
linkRadius: 0.25,
radialSegments: 16
}
type LinkCylinderProps = typeof DefaultLinkCylinderProps
async function createLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) {
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
const elements = unit.elements; const elements = unit.elements;
const links = unit.links const links = unit.links
const { edgeCount, a, b } = links const { edgeCount, a, b, edgeProps, offset } = links
const orders = edgeProps.order
if (!edgeCount) return Mesh.createEmpty(mesh) if (!edgeCount) return Mesh.createEmpty(mesh)
// TODO calculate vertextCount properly // approximate vertextCount, exact calculation would need to take link orders into account
const vertexCount = 32 * edgeCount const vertexCount = 32 * edgeCount
const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh) const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
const va = Vec3.zero() const va = Vec3.zero()
const vb = Vec3.zero() const vb = Vec3.zero()
const vt = Vec3.zero() const vd = Vec3.zero()
const vc = Vec3.zero()
const m = Mat4.identity() const m = Mat4.identity()
const mt = Mat4.identity()
const vShift = Vec3.zero()
const vCenter = Vec3.zero()
const vRef = Vec3.zero()
const { linkScale, linkSpacing, linkRadius, radialSegments } = props
const cylinderParams = {
height: 1,
radiusTop: linkRadius,
radiusBottom: linkRadius,
radialSegments,
openEnded: true
}
const pos = unit.conformation.invariantPosition const pos = unit.conformation.invariantPosition
// const l = Element.Location() // const l = Element.Location()
// l.unit = unit // l.unit = unit
// assumes aI < bI
function getRefPos(aI: number, bI: number) {
if (aI > bI) console.log('aI > bI')
for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) {
if (b[i] !== bI) return pos(elements[b[i]], vRef)
}
for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) {
if (a[i] !== aI) return pos(elements[a[i]], vRef)
}
// console.log('no ref', aI, bI, unit.model.atomicHierarchy.atoms.auth_atom_id.value(aI), unit.model.atomicHierarchy.atoms.auth_atom_id.value(bI), offset[aI], offset[aI + 1], offset[bI], offset[bI + 1], offset)
return null
}
for (let edgeIndex = 0, _eI = edgeCount * 2; edgeIndex < _eI; ++edgeIndex) { for (let edgeIndex = 0, _eI = edgeCount * 2; edgeIndex < _eI; ++edgeIndex) {
const aI = elements[a[edgeIndex]], bI = elements[b[edgeIndex]]; const aI = elements[a[edgeIndex]], bI = elements[b[edgeIndex]];
// each edge is included twice because of the "adjacency list" structure // Each edge is included twice to allow for coloring/picking
// keep only the 1st occurence. // the half closer to the first vertex, i.e. vertex a.
if (aI >= bI) continue;
pos(aI, va) pos(aI, va)
pos(bI, vb) pos(bI, vb)
const d = Vec3.distance(va, vb)
Vec3.sub(vd, vb, va)
Vec3.scale(vd, Vec3.normalize(vd, vd), d / 4)
Vec3.add(vc, va, vd)
// ensure both edge halfs are pointing in the the same direction so the triangles align
if (aI > bI) Vec3.scale(vd, vd, -1)
Vec3.makeRotation(m, Vec3.create(0, 1, 0), vd)
Vec3.scale(vt, Vec3.add(vt, va, vb), 0.5) const order = orders[edgeIndex]
Vec3.makeRotation(m, Vec3.create(0, 1, 0), Vec3.sub(vb, vb, va))
Mat4.setTranslation(m, vt)
meshBuilder.setId(edgeIndex) meshBuilder.setId(edgeIndex)
meshBuilder.addCylinder(m, { radiusTop: 0.2, radiusBottom: 0.2 }) cylinderParams.height = d / 2
if (order === 2 || order === 3) {
const multiRadius = linkRadius * (linkScale / (0.5 * order))
const absOffset = (linkRadius - multiRadius) * linkSpacing
if (aI < bI) {
calculateShiftDir(vShift, va, vb, getRefPos(a[edgeIndex], b[edgeIndex]))
} else {
calculateShiftDir(vShift, vb, va, getRefPos(b[edgeIndex], a[edgeIndex]))
}
Vec3.setMagnitude(vShift, vShift, absOffset)
cylinderParams.radiusTop = multiRadius
cylinderParams.radiusBottom = multiRadius
if (order === 3) {
Mat4.fromTranslation(mt, vc)
Mat4.mul(mt, mt, m)
meshBuilder.addCylinder(mt, cylinderParams)
}
Vec3.add(vCenter, vc, vShift)
Mat4.fromTranslation(mt, vCenter)
Mat4.mul(mt, mt, m)
meshBuilder.addCylinder(mt, cylinderParams)
Vec3.sub(vCenter, vc, vShift)
Mat4.fromTranslation(mt, vCenter)
Mat4.mul(mt, mt, m)
meshBuilder.addCylinder(mt, cylinderParams)
} else {
cylinderParams.radiusTop = linkRadius
cylinderParams.radiusBottom = linkRadius
Mat4.setTranslation(m, vc)
meshBuilder.addCylinder(m, cylinderParams)
}
if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) { if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) {
await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: edgeCount }); await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: edgeCount });
...@@ -75,6 +154,7 @@ async function createLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Me ...@@ -75,6 +154,7 @@ async function createLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Me
export const DefaultIntraUnitLinkProps = { export const DefaultIntraUnitLinkProps = {
...DefaultStructureProps, ...DefaultStructureProps,
...DefaultLinkCylinderProps,
sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme, sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
flipSided: false, flipSided: false,
flatShaded: false, flatShaded: false,
...@@ -87,7 +167,6 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> { ...@@ -87,7 +167,6 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
let currentProps: typeof DefaultIntraUnitLinkProps let currentProps: typeof DefaultIntraUnitLinkProps
let mesh: Mesh let mesh: Mesh
let currentGroup: Unit.SymmetryGroup let currentGroup: Unit.SymmetryGroup
// let vertexMap: VertexMap
return { return {
renderObjects, renderObjects,
...@@ -101,7 +180,7 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> { ...@@ -101,7 +180,7 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0 const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0
const instanceCount = group.units.length const instanceCount = group.units.length
mesh = await createLinkCylinderMesh(ctx, unit) mesh = await createLinkCylinderMesh(ctx, unit, currentProps)
if (ctx.shouldUpdate) await ctx.update('Computing link transforms'); if (ctx.shouldUpdate) await ctx.update('Computing link transforms');
const transforms = createTransforms(group) const transforms = createTransforms(group)
...@@ -201,7 +280,7 @@ function markLink(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, v ...@@ -201,7 +280,7 @@ function markLink(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, v
for (const b of loci.links) { for (const b of loci.links) {
const unitIdx = Unit.findUnitById(b.aUnit.id, group.units) const unitIdx = Unit.findUnitById(b.aUnit.id, group.units)
if (unitIdx !== -1) { if (unitIdx !== -1) {
const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex) const _idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex)
if (_idx !== -1) { if (_idx !== -1) {
const idx = _idx const idx = _idx
if (applyMarkerAction(array, idx, idx + 1, action) && !changed) { if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
...@@ -216,4 +295,34 @@ function markLink(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, v ...@@ -216,4 +295,34 @@ function markLink(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, v
if (changed) { if (changed) {
ValueCell.update(tMarker, tMarker.ref.value) ValueCell.update(tMarker, tMarker.ref.value)
} }
}
const tmpShiftV12 = Vec3.zero()
const tmpShiftV13 = Vec3.zero()
/** Calculate 'shift' direction that is perpendiculat to v1 - v2 and goes through v3 */
function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | null) {
Vec3.sub(tmpShiftV12, v1, v2)
if (v3 !== null) {
Vec3.sub(tmpShiftV13, v1, v3)
} else {
Vec3.copy(tmpShiftV13, v1) // no reference point, use v1
}
Vec3.normalize(tmpShiftV13, tmpShiftV13)
// ensure v13 and v12 are not colinear
let dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
if (1 - Math.abs(dp) < 1e-5) {
Vec3.set(tmpShiftV13, 1, 0, 0)
dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
if (1 - Math.abs(dp) < 1e-5) {
Vec3.set(tmpShiftV13, 0, 1, 0)
dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
}
}
Vec3.setMagnitude(tmpShiftV12, tmpShiftV12, dp)
Vec3.sub(tmpShiftV13, tmpShiftV13, tmpShiftV12)
return Vec3.normalize(out, tmpShiftV13)
} }
\ No newline at end of file
...@@ -28,8 +28,17 @@ interface IntAdjacencyGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase = ...@@ -28,8 +28,17 @@ interface IntAdjacencyGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase =
* *
* Because the a and b arrays contains each edge twice, * Because the a and b arrays contains each edge twice,
* this always returns the smaller of the indices. * this always returns the smaller of the indices.
*
* `getEdgeIndex(i, j) === getEdgeIndex(j, i)`
*/ */
getEdgeIndex(i: number, j: number): number, getEdgeIndex(i: number, j: number): number,
/**
* Get the edge index between i-th and j-th vertex.
* -1 if the edge does not exist.
*
* `getEdgeIndex(i, j) !== getEdgeIndex(j, i)`
*/
getDirectedEdgeIndex(i: number, j: number): number,
getVertexEdgeCount(i: number): number getVertexEdgeCount(i: number): number
} }
...@@ -50,6 +59,13 @@ namespace IntAdjacencyGraph { ...@@ -50,6 +59,13 @@ namespace IntAdjacencyGraph {
return -1; return -1;
} }
getDirectedEdgeIndex(i: number, j: number): number {
for (let t = this.offset[i], _t = this.offset[i + 1]; t < _t; t++) {
if (this.b[t] === j) return t;
}
return -1;
}
getVertexEdgeCount(i: number): number { getVertexEdgeCount(i: number): number {
return this.offset[i + 1] - this.offset[i]; return this.offset[i + 1] - this.offset[i];
} }
......
...@@ -11,15 +11,7 @@ import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBal ...@@ -11,15 +11,7 @@ import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBal
import { UrlEntity } from './state/entity'; import { UrlEntity } from './state/entity';
import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
import { Context } from 'mol-app/context/context'; import { Context } from 'mol-app/context/context';
import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
// export const ColorTheme = {
// 'atom-index': {},
// 'chain-id': {},
// 'element-symbol': {},
// 'instance-index': {},
// 'uniform': {}
// }
// export type ColorTheme = keyof typeof ColorTheme
const spacefillProps: SpacefillProps = { const spacefillProps: SpacefillProps = {
doubleSided: true, doubleSided: true,
...@@ -27,6 +19,14 @@ const spacefillProps: SpacefillProps = { ...@@ -27,6 +19,14 @@ const spacefillProps: SpacefillProps = {
colorTheme: { name: 'atom-index' } colorTheme: { name: 'atom-index' }
} }
const ballAndStickProps: BallAndStickProps = {
doubleSided: true,
detail: 1,
radialSegments: 8,
colorTheme: { name: 'chain-id' },
sizeTheme: { name: 'uniform', value: 0.25 },
}
export class Stage { export class Stage {
viewer: Viewer viewer: Viewer
ctx = new StateContext(Progress.format) ctx = new StateContext(Progress.format)
...@@ -39,22 +39,27 @@ export class Stage { ...@@ -39,22 +39,27 @@ export class Stage {
this.viewer = Viewer.create(canvas, container) this.viewer = Viewer.create(canvas, container)
this.viewer.animate() this.viewer.animate()
this.ctx.viewer = this.viewer this.ctx.viewer = this.viewer
//this.loadPdbid('1jj2')
this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) // this.loadPdbid('1jj2')
this.loadPdbid('4umt') // ligand has bond with order 3
// this.loadPdbid('1crn')
// this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
} }
async loadMmcifUrl (url: string) { async loadMmcifUrl (url: string) {
const urlEntity = UrlEntity.ofUrl(this.ctx, url) const urlEntity = UrlEntity.ofUrl(this.ctx, url)
const modelEntity = await MmcifUrlToModel.apply(this.ctx, urlEntity) const modelEntity = await MmcifUrlToModel.apply(this.ctx, urlEntity)
const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity) const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity)
StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: false }) StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: false })
StructureToBallAndStick.apply(this.ctx, structureEntity, spacefillProps) // TODO props StructureToBallAndStick.apply(this.ctx, structureEntity, ballAndStickProps)
this.globalContext.components.sequenceView.setState({ structure: structureEntity.value }); this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });
} }
loadPdbid (pdbid: string) { loadPdbid (pdbid: string) {
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 () { dispose () {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment