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

molecular surface improvements

parent e87abc47
Branches
Tags
No related merge requests found
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
import { fillUniform } from 'mol-util/array'; import { fillUniform } from 'mol-util/array';
import { Vec3, Tensor } from 'mol-math/linear-algebra'; import { Vec3, Tensor } from 'mol-math/linear-algebra';
import { ParamDefinition as PD } from 'mol-util/param-definition'; import { ParamDefinition as PD } from 'mol-util/param-definition';
import { Lookup3D, Result } from './lookup3d/common';
import { RuntimeContext } from 'mol-task'; import { RuntimeContext } from 'mol-task';
import { OrderedSet } from 'mol-data/int'; import { OrderedSet } from 'mol-data/int';
import { PositionData } from './common'; import { PositionData } from './common';
...@@ -44,320 +43,293 @@ function getAngleTables (probePositions: number): AnglesTables { ...@@ -44,320 +43,293 @@ function getAngleTables (probePositions: number): AnglesTables {
return { cosTable, sinTable} return { cosTable, sinTable}
} }
/** //
* Is the point at x,y,z obscured by any of the atoms specifeid by indices in neighbours.
* Ignore indices a and b (these are the relevant atoms in projectPoints/Torii)
*
* Cache the last clipped atom (as very often the same one in subsequent calls)
*
* `a` and `b` must be resolved indices
*/
function obscured (state: MolSurfCalcState, x: number, y: number, z: number, a: number, b: number) {
if (state.lastClip !== -1) {
const ai = state.lastClip
if (ai !== a && ai !== b && singleAtomObscures(state, ai, x, y, z)) {
return ai
} else {
state.lastClip = -1
}
}
for (let j = 0, jl = state.neighbours.count; j < jl; ++j) {
const ai = OrderedSet.getAt(state.position.indices, state.neighbours.indices[j])
if (ai !== a && ai !== b && singleAtomObscures(state, ai, x, y, z)) {
state.lastClip = ai
return ai
}
}
return -1
}
/** export const MolecularSurfaceCalculationParams = {
* `ai` must be a resolved index resolution: PD.Numeric(0.5, { min: 0.01, max: 10, step: 0.01 }),
*/ probeRadius: PD.Numeric(1.4, { min: 0, max: 10, step: 0.1 }),
function singleAtomObscures (state: MolSurfCalcState, ai: number, x: number, y: number, z: number) { probePositions: PD.Numeric(30, { min: 12, max: 90, step: 1 }),
const r = state.position.radius[ai]
const dx = state.position.x[ai] - x
const dy = state.position.y[ai] - y
const dz = state.position.z[ai] - z
const dSq = dx * dx + dy * dy + dz * dz
return dSq < (r * r)
} }
export const DefaultMolecularSurfaceCalculationProps = PD.getDefaultValues(MolecularSurfaceCalculationParams)
export type MolecularSurfaceCalculationProps = typeof DefaultMolecularSurfaceCalculationProps
/**
* For each atom:
* Iterate over a subsection of the grid, for each point:
* If current value < 0.0, unvisited, set positive
*
* In any case: Project this point onto surface of the atomic sphere
* If this projected point is not obscured by any other atom
* Calculate delta distance and set grid value to minimum of
* itself and delta
*/
async function projectPoints (ctx: RuntimeContext, state: MolSurfCalcState) {
const { position, lookup3d, min, space, data, idData, scaleFactor, updateChunk } = state
const { gridx, gridy, gridz } = state
const { indices, x, y, z, radius } = position
const n = OrderedSet.size(indices)
const [ dimX, dimY, dimZ ] = space.dimensions export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, props: MolecularSurfaceCalculationProps) {
const iu = dimZ, iv = dimY, iuv = iu * iv // Field generation method adapted from AstexViewer (Mike Hartshorn) by Fred Ludlow.
// Other parts based heavily on NGL (Alexander Rose) EDT Surface class
for (let i = 0; i < n; ++i) { let lastClip = -1
const j = OrderedSet.getAt(indices, i)
const vx = x[j], vy = y[j], vz = z[j] /**
const rad = radius[j] * Is the point at x,y,z obscured by any of the atoms specifeid by indices in neighbours.
const rSq = rad * rad * Ignore indices a and b (these are the relevant atoms in projectPoints/Torii)
*
state.neighbours = lookup3d.find(vx, vy, vz, rad) * Cache the last clipped atom (as very often the same one in subsequent calls)
*
// Number of grid points, round this up... * `a` and `b` must be resolved indices
const ng = Math.ceil(rad * scaleFactor) */
function obscured(x: number, y: number, z: number, a: number, b: number) {
// Center of the atom, mapped to grid points (take floor) if (lastClip !== -1) {
const iax = Math.floor(scaleFactor * (vx - min[0])) const ai = lastClip
const iay = Math.floor(scaleFactor * (vy - min[1])) if (ai !== a && ai !== b && singleAtomObscures(ai, x, y, z)) {
const iaz = Math.floor(scaleFactor * (vz - min[2])) return ai
} else {
// Extents of grid to consider for this atom lastClip = -1
const begX = Math.max(0, iax - ng)
const begY = Math.max(0, iay - ng)
const begZ = Math.max(0, iaz - ng)
// Add two to these points:
// - iax are floor'd values so this ensures coverage
// - these are loop limits (exclusive)
const endX = Math.min(dimX, iax + ng + 2)
const endY = Math.min(dimY, iay + ng + 2)
const endZ = Math.min(dimZ, iaz + ng + 2)
for (let xi = begX; xi < endX; ++xi) {
const dx = gridx[xi] - vx
const xIdx = xi * iuv
for (let yi = begY; yi < endY; ++yi) {
const dy = gridy[yi] - vy
const dxySq = dx * dx + dy * dy
const xyIdx = yi * iu + xIdx
for (let zi = begZ; zi < endZ; ++zi) {
const dz = gridz[zi] - vz
const dSq = dxySq + dz * dz
if (dSq < rSq) {
const idx = zi + xyIdx
// if unvisited, make positive
if (data[idx] < 0.0) data[idx] *= -1
// Project on to the surface of the sphere
// sp is the projected point ( dx, dy, dz ) * ( ra / d )
const d = Math.sqrt(dSq)
const ap = rad / d
const spx = dx * ap + vx
const spy = dy * ap + vy
const spz = dz * ap + vz
if (obscured(state, spx, spy, spz, j, -1) === -1) {
const dd = rad - d
if (dd < data[idx]) {
data[idx] = dd
idData[idx] = i
}
}
}
}
} }
} }
if (i % updateChunk === 0 && ctx.shouldUpdate) { for (let j = 0, jl = neighbours.count; j < jl; ++j) {
await ctx.update({ message: 'projecting points', current: i, max: n }) const ai = OrderedSet.getAt(position.indices, neighbours.indices[j])
if (ai !== a && ai !== b && singleAtomObscures(ai, x, y, z)) {
lastClip = ai
return ai
}
} }
}
}
// Vectors for Torus Projection
const atob = Vec3()
const mid = Vec3()
const n1 = Vec3()
const n2 = Vec3()
/**
* `a` and `b` must be resolved indices
*/
function projectTorus (state: MolSurfCalcState, a: number, b: number) {
const { position, min, space, data, idData } = state
const { cosTable, sinTable, probePositions, probeRadius, scaleFactor } = state
const { gridx, gridy, gridz } = state
const [ dimX, dimY, dimZ ] = space.dimensions
const iu = dimZ, iv = dimY, iuv = iu * iv
const ng = Math.max(5, 2 + Math.floor(probeRadius * scaleFactor))
const rA = position.radius[a]
const rB = position.radius[b]
const dx = atob[0] = position.x[b] - position.x[a]
const dy = atob[1] = position.y[b] - position.y[a]
const dz = atob[2] = position.z[b] - position.z[a]
const dSq = dx * dx + dy * dy + dz * dz
// This check now redundant as already done in AVHash.withinRadii
// if (dSq > ((rA + rB) * (rA + rB))) { return }
const d = Math.sqrt(dSq)
// Find angle between a->b vector and the circle
// of their intersection by cosine rule
const cosA = (rA * rA + d * d - rB * rB) / (2.0 * rA * d)
// distance along a->b at intersection
const dmp = rA * cosA
Vec3.normalize(atob, atob)
// Create normal to line
normalToLine(n1, atob)
Vec3.normalize(n1, n1)
// Cross together for second normal vector
Vec3.cross(n2, atob, n1)
Vec3.normalize(n2, n2)
// r is radius of circle of intersection
const rInt = Math.sqrt(rA * rA - dmp * dmp)
Vec3.scale(n1, n1, rInt)
Vec3.scale(n2, n2, rInt)
Vec3.scale(atob, atob, dmp)
mid[0] = atob[0] + position.x[a]
mid[1] = atob[1] + position.y[a]
mid[2] = atob[2] + position.z[a]
state.lastClip = -1
for (let i = 0; i < probePositions; ++i) { return -1
const cost = cosTable[i] }
const sint = sinTable[i]
const px = mid[0] + cost * n1[0] + sint * n2[0]
const py = mid[1] + cost * n1[1] + sint * n2[1]
const pz = mid[2] + cost * n1[2] + sint * n2[2]
if (obscured(state, px, py, pz, a, b) === -1) { /**
const iax = Math.floor(scaleFactor * (px - min[0])) * `ai` must be a resolved index
const iay = Math.floor(scaleFactor * (py - min[1])) */
const iaz = Math.floor(scaleFactor * (pz - min[2])) function singleAtomObscures(ai: number, x: number, y: number, z: number) {
const r = radius[ai]
const dx = px[ai] - x
const dy = py[ai] - y
const dz = pz[ai] - z
const dSq = dx * dx + dy * dy + dz * dz
return dSq < (r * r)
}
/**
* For each atom:
* Iterate over a subsection of the grid, for each point:
* If current value < 0.0, unvisited, set positive
*
* In any case: Project this point onto surface of the atomic sphere
* If this projected point is not obscured by any other atom
* Calculate delta distance and set grid value to minimum of
* itself and delta
*/
function projectPointsRange (begI: number, endI: number) {
for (let i = begI; i < endI; ++i) {
const j = OrderedSet.getAt(indices, i)
const vx = px[j], vy = py[j], vz = pz[j]
const rad = radius[j]
const rSq = rad * rad
lookup3d.find(vx, vy, vz, rad)
// Number of grid points, round this up...
const ng = Math.ceil(rad * scaleFactor)
// Center of the atom, mapped to grid points (take floor)
const iax = Math.floor(scaleFactor * (vx - minX))
const iay = Math.floor(scaleFactor * (vy - minY))
const iaz = Math.floor(scaleFactor * (vz - minZ))
// Extents of grid to consider for this atom
const begX = Math.max(0, iax - ng) const begX = Math.max(0, iax - ng)
const begY = Math.max(0, iay - ng) const begY = Math.max(0, iay - ng)
const begZ = Math.max(0, iaz - ng) const begZ = Math.max(0, iaz - ng)
// Add two to these points:
// - iax are floor'd values so this ensures coverage
// - these are loop limits (exclusive)
const endX = Math.min(dimX, iax + ng + 2) const endX = Math.min(dimX, iax + ng + 2)
const endY = Math.min(dimY, iay + ng + 2) const endY = Math.min(dimY, iay + ng + 2)
const endZ = Math.min(dimZ, iaz + ng + 2) const endZ = Math.min(dimZ, iaz + ng + 2)
for (let xi = begX; xi < endX; ++xi) { for (let xi = begX; xi < endX; ++xi) {
const dx = px - gridx[xi] const dx = gridx[xi] - vx
const xIdx = xi * iuv const xIdx = xi * iuv
for (let yi = begY; yi < endY; ++yi) { for (let yi = begY; yi < endY; ++yi) {
const dy = py - gridy[yi] const dy = gridy[yi] - vy
const dxySq = dx * dx + dy * dy const dxySq = dx * dx + dy * dy
const xyIdx = yi * iu + xIdx const xyIdx = yi * iu + xIdx
for (let zi = begZ; zi < endZ; ++zi) { for (let zi = begZ; zi < endZ; ++zi) {
const dz = pz - gridz[zi] const dz = gridz[zi] - vz
const dSq = dxySq + dz * dz const dSq = dxySq + dz * dz
const idx = zi + xyIdx if (dSq < rSq) {
const current = data[idx] const idx = zi + xyIdx
if (current > 0.0 && dSq < (current * current)) { // if unvisited, make positive
data[idx] = Math.sqrt(dSq) if (data[idx] < 0.0) data[idx] *= -1
// Is this grid point closer to a or b?
// Take dot product of atob and gridpoint->p (dx, dy, dz) // Project on to the surface of the sphere
const dp = dx * atob[0] + dy * atob[1] + dz * atob[2] // sp is the projected point ( dx, dy, dz ) * ( ra / d )
idData[idx] = OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a) const d = Math.sqrt(dSq)
const ap = rad / d
const spx = dx * ap + vx
const spy = dy * ap + vy
const spz = dz * ap + vz
if (obscured(spx, spy, spz, j, -1) === -1) {
const dd = rad - d
if (dd < data[idx]) {
data[idx] = dd
idData[idx] = i
}
}
} }
} }
} }
} }
} }
} }
}
async function projectTorii (ctx: RuntimeContext, state: MolSurfCalcState) { async function projectPoints() {
const { n, lookup3d, position, updateChunk } = state for (let i = 0; i < n; i += updateChunk) {
const { x, y, z, indices, radius } = position projectPointsRange(i, Math.min(i + updateChunk, n))
for (let i = 0; i < n; ++i) {
const k = OrderedSet.getAt(indices, i) if (ctx.shouldUpdate) {
state.neighbours = lookup3d.find(x[k], y[k], z[k], radius[k]) await ctx.update({ message: 'projecting points', current: i, max: n })
for (let j = 0, jl = state.neighbours.count; j < jl; ++j) { }
const l = OrderedSet.getAt(indices, state.neighbours.indices[j])
if (k < l) projectTorus(state, k, l)
} }
}
if (i % updateChunk === 0 && ctx.shouldUpdate) { // Vectors for Torus Projection
await ctx.update({ message: 'projecting torii', current: i, max: n }) const atob = Vec3()
const mid = Vec3()
const n1 = Vec3()
const n2 = Vec3()
/**
* `a` and `b` must be resolved indices
*/
function projectTorus(a: number, b: number) {
const rA = radius[a]
const rB = radius[b]
const dx = atob[0] = px[b] - px[a]
const dy = atob[1] = py[b] - py[a]
const dz = atob[2] = pz[b] - pz[a]
const dSq = dx * dx + dy * dy + dz * dz
// This check now redundant as already done in AVHash.withinRadii
// if (dSq > ((rA + rB) * (rA + rB))) { return }
const d = Math.sqrt(dSq)
// Find angle between a->b vector and the circle
// of their intersection by cosine rule
const cosA = (rA * rA + d * d - rB * rB) / (2.0 * rA * d)
// distance along a->b at intersection
const dmp = rA * cosA
Vec3.normalize(atob, atob)
// Create normal to line
normalToLine(n1, atob)
Vec3.normalize(n1, n1)
// Cross together for second normal vector
Vec3.cross(n2, atob, n1)
Vec3.normalize(n2, n2)
// r is radius of circle of intersection
const rInt = Math.sqrt(rA * rA - dmp * dmp)
Vec3.scale(n1, n1, rInt)
Vec3.scale(n2, n2, rInt)
Vec3.scale(atob, atob, dmp)
mid[0] = atob[0] + px[a]
mid[1] = atob[1] + py[a]
mid[2] = atob[2] + pz[a]
lastClip = -1
for (let i = 0; i < probePositions; ++i) {
const cost = cosTable[i]
const sint = sinTable[i]
const px = mid[0] + cost * n1[0] + sint * n2[0]
const py = mid[1] + cost * n1[1] + sint * n2[1]
const pz = mid[2] + cost * n1[2] + sint * n2[2]
if (obscured(px, py, pz, a, b) === -1) {
const iax = Math.floor(scaleFactor * (px - minX))
const iay = Math.floor(scaleFactor * (py - minY))
const iaz = Math.floor(scaleFactor * (pz - minZ))
const begX = Math.max(0, iax - ngTorus)
const begY = Math.max(0, iay - ngTorus)
const begZ = Math.max(0, iaz - ngTorus)
const endX = Math.min(dimX, iax + ngTorus + 2)
const endY = Math.min(dimY, iay + ngTorus + 2)
const endZ = Math.min(dimZ, iaz + ngTorus + 2)
for (let xi = begX; xi < endX; ++xi) {
const dx = px - gridx[xi]
const xIdx = xi * iuv
for (let yi = begY; yi < endY; ++yi) {
const dy = py - gridy[yi]
const dxySq = dx * dx + dy * dy
const xyIdx = yi * iu + xIdx
for (let zi = begZ; zi < endZ; ++zi) {
const dz = pz - gridz[zi]
const dSq = dxySq + dz * dz
const idx = zi + xyIdx
const current = data[idx]
if (current > 0.0 && dSq < (current * current)) {
data[idx] = Math.sqrt(dSq)
// Is this grid point closer to a or b?
// Take dot product of atob and gridpoint->p (dx, dy, dz)
const dp = dx * atob[0] + dy * atob[1] + dz * atob[2]
idData[idx] = OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a)
}
}
}
}
}
} }
} }
}
// async function projectTorii () {
for (let i = 0; i < n; ++i) {
const k = OrderedSet.getAt(indices, i)
lookup3d.find(px[k], py[k], pz[k], radius[k])
for (let j = 0, jl = neighbours.count; j < jl; ++j) {
const l = OrderedSet.getAt(indices, neighbours.indices[j])
if (k < l) projectTorus(k, l)
}
interface MolSurfCalcState { if (i % updateChunk === 0 && ctx.shouldUpdate) {
/** Cached last value for obscured test */ await ctx.update({ message: 'projecting torii', current: i, max: n })
lastClip: number }
/** Neighbours as transient result array from lookup3d */ }
neighbours: Result<number> }
lookup3d: Lookup3D
position: Required<PositionData>
min: Vec3
updateChunk: number
maxRadius: number
n: number
resolution: number
scaleFactor: number
probeRadius: number
/** Angle lookup tables */
cosTable: Float32Array
sinTable: Float32Array
probePositions: number
/** grid lookup tables */
gridx: Float32Array
gridy: Float32Array
gridz: Float32Array
expandedBox: Box3D
space: Tensor.Space
data: Tensor.Data
idData: Tensor.Data
}
async function createState(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, props: MolecularSurfaceCalculationProps): Promise<MolSurfCalcState> { // console.time('MolecularSurface')
// console.time('MolecularSurface createState')
const { resolution, probeRadius, probePositions } = props const { resolution, probeRadius, probePositions } = props
const scaleFactor = 1 / resolution const scaleFactor = 1 / resolution
const ngTorus = Math.max(5, 2 + Math.floor(probeRadius * scaleFactor))
const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius) const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius)
Vec3.scale(cellSize, cellSize, 2) Vec3.scale(cellSize, cellSize, 2)
const lookup3d = GridLookup3D(position, cellSize) const lookup3d = GridLookup3D(position, cellSize)
const neighbours = lookup3d.result
const box = lookup3d.boundary.box const box = lookup3d.boundary.box
const { indices } = position const { indices, x: px, y: py, z: pz, radius } = position
const n = OrderedSet.size(indices) const n = OrderedSet.size(indices)
const pad = maxRadius * 2 + resolution const pad = maxRadius * 2 + resolution
const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad)); const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
const min = expandedBox.min const [ minX, minY, minZ ] = expandedBox.min
const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor) const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor)
const dim = Box3D.size(Vec3(), scaledBox) const dim = Box3D.size(Vec3(), scaledBox)
Vec3.ceil(dim, dim) Vec3.ceil(dim, dim)
const [ dimX, dimY, dimZ ] = dim
const iu = dimZ, iv = dimY, iuv = iu * iv
const { cosTable, sinTable } = getAngleTables(probePositions) const { cosTable, sinTable } = getAngleTables(probePositions)
const space = Tensor.Space(dim, [0, 1, 2], Float32Array) const space = Tensor.Space(dim, [0, 1, 2], Float32Array)
...@@ -367,81 +339,28 @@ async function createState(ctx: RuntimeContext, position: Required<PositionData> ...@@ -367,81 +339,28 @@ async function createState(ctx: RuntimeContext, position: Required<PositionData>
fillUniform(data, -1001.0) fillUniform(data, -1001.0)
fillUniform(idData, -1) fillUniform(idData, -1)
const gridx = fillGridDim(dim[0], min[0], resolution) const gridx = fillGridDim(dimX, minX, resolution)
const gridy = fillGridDim(dim[1], min[1], resolution) const gridy = fillGridDim(dimY, minY, resolution)
const gridz = fillGridDim(dim[2], min[2], resolution) const gridz = fillGridDim(dimZ, minZ, resolution)
const updateChunk = Math.ceil(1000000 / (Math.pow(maxRadius, 3) * resolution))
return {
lastClip: -1,
neighbours: lookup3d.find(0, 0, 0, 0),
lookup3d,
position,
min,
updateChunk,
maxRadius,
n,
resolution,
scaleFactor,
probeRadius,
cosTable,
sinTable,
probePositions,
gridx,
gridy,
gridz,
expandedBox,
space,
data,
idData,
}
}
//
export const MolecularSurfaceCalculationParams = {
resolution: PD.Numeric(0.5, { min: 0.01, max: 10, step: 0.01 }),
probeRadius: PD.Numeric(1.4, { min: 0, max: 10, step: 0.1 }),
probePositions: PD.Numeric(30, { min: 12, max: 90, step: 1 }),
}
export const DefaultMolecularSurfaceCalculationProps = PD.getDefaultValues(MolecularSurfaceCalculationParams)
export type MolecularSurfaceCalculationProps = typeof DefaultMolecularSurfaceCalculationProps
export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, props: MolecularSurfaceCalculationProps) {
// Field generation method adapted from AstexViewer (Mike Hartshorn) by Fred Ludlow.
// Other parts based heavily on NGL (Alexander Rose) EDT Surface class
console.time('MolecularSurface')
console.time('MolecularSurface createState')
const state = await createState(ctx, position, maxRadius, props)
console.timeEnd('MolecularSurface createState')
console.time('MolecularSurface projectPoints') const updateChunk = Math.ceil(100000 / ((Math.pow(Math.pow(maxRadius, 3), 3) * scaleFactor)))
await projectPoints(ctx, state) // console.timeEnd('MolecularSurface createState')
console.timeEnd('MolecularSurface projectPoints')
console.time('MolecularSurface projectTorii') // console.time('MolecularSurface projectPoints')
await projectTorii(ctx, state) await projectPoints()
console.timeEnd('MolecularSurface projectTorii') // console.timeEnd('MolecularSurface projectPoints')
console.timeEnd('MolecularSurface') // console.time('MolecularSurface projectTorii')
await projectTorii()
// console.timeEnd('MolecularSurface projectTorii')
// console.timeEnd('MolecularSurface')
const field = Tensor.create(state.space, state.data) const field = Tensor.create(space, data)
const idField = Tensor.create(state.space, state.idData) const idField = Tensor.create(space, idData)
const { resolution, expandedBox } = state
const transform = Mat4.identity() const transform = Mat4.identity()
Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution)) Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution))
Mat4.setTranslation(transform, expandedBox.min) Mat4.setTranslation(transform, expandedBox.min)
console.log({ field, idField, transform, state }) // console.log({ field, idField, transform, updateChunk })
return { field, idField, transform } return { field, idField, transform }
} }
\ No newline at end of file
...@@ -27,7 +27,6 @@ export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams ...@@ -27,7 +27,6 @@ export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams
// //
async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceCalculationProps, mesh?: Mesh): Promise<Mesh> { async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceCalculationProps, mesh?: Mesh): Promise<Mesh> {
console.log(props)
const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime) const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime)
const params = { const params = {
......
...@@ -11,27 +11,27 @@ import { PositionData, DensityData } from 'mol-math/geometry'; ...@@ -11,27 +11,27 @@ import { PositionData, DensityData } from 'mol-math/geometry';
import { MolecularSurfaceCalculationProps, calcMolecularSurface } from 'mol-math/geometry/molecular-surface'; import { MolecularSurfaceCalculationProps, calcMolecularSurface } from 'mol-math/geometry/molecular-surface';
import { OrderedSet } from 'mol-data/int'; import { OrderedSet } from 'mol-data/int';
export function computeUnitMolecularSurface(unit: Unit, props: MolecularSurfaceCalculationProps) { function getPositionDataAndMaxRadius(unit: Unit, props: MolecularSurfaceCalculationProps) {
const { position, radius } = getUnitConformationAndRadius(unit) const { position, radius } = getUnitConformationAndRadius(unit)
const { indices } = position
const n = OrderedSet.size(indices)
const radii = new Float32Array(OrderedSet.end(indices))
let maxRadius = 0
for (let i = 0; i < n; ++i) {
const j = OrderedSet.getAt(indices, i)
const r = radius(j)
if (maxRadius < r) maxRadius = r
radii[j] = r + props.probeRadius
}
return { position: { ...position, radius: radii }, maxRadius }
}
export function computeUnitMolecularSurface(unit: Unit, props: MolecularSurfaceCalculationProps) {
const { position, maxRadius } = getPositionDataAndMaxRadius(unit, props)
return Task.create('Molecular Surface', async ctx => { return Task.create('Molecular Surface', async ctx => {
const { indices } = position return await MolecularSurface(ctx, position, maxRadius, props);
const n = OrderedSet.size(indices)
const radii = new Float32Array(OrderedSet.max(indices))
let maxRadius = 0
for (let i = 0; i < n; ++i) {
const j = OrderedSet.getAt(indices, i)
const r = radius(j)
if (maxRadius < r) maxRadius = r
radii[j] = r + props.probeRadius
if (i % 100000 === 0 && ctx.shouldUpdate) {
await ctx.update({ message: 'calculating max radius', current: i, max: n })
}
}
return await MolecularSurface(ctx, { ...position, radius: radii }, maxRadius, props);
}); });
} }
......
...@@ -84,43 +84,59 @@ async function init() { ...@@ -84,43 +84,59 @@ async function init() {
console.timeEnd('computeModelDSSP'); console.timeEnd('computeModelDSSP');
(models[0].properties as any).secondaryStructure = secondaryStructure (models[0].properties as any).secondaryStructure = secondaryStructure
const structure = await getStructure(models[0]) const structure = await getStructure(models[0])
const show = {
cartoon: false,
ballAndStick: true,
molecularSurface: true,
gaussianSurface: false,
}
const cartoonRepr = getCartoonRepr() const cartoonRepr = getCartoonRepr()
const ballAndStick = getBallAndStickRepr() const ballAndStickRepr = getBallAndStickRepr()
const molecularSurfaceRepr = getMolecularSurfaceRepr() const molecularSurfaceRepr = getMolecularSurfaceRepr()
const gaussianSurfaceRepr = getGaussianSurfaceRepr() const gaussianSurfaceRepr = getGaussianSurfaceRepr()
// cartoonRepr.setTheme({ if (show.cartoon) {
// color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }), cartoonRepr.setTheme({
// size: reprCtx.sizeThemeRegistry.create('uniform', { structure }) color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }),
// }) size: reprCtx.sizeThemeRegistry.create('uniform', { structure })
// await cartoonRepr.createOrUpdate({ ...CartoonRepresentationProvider.defaultValues, quality: 'auto' }, structure).run() })
await cartoonRepr.createOrUpdate({ ...CartoonRepresentationProvider.defaultValues, quality: 'auto' }, structure).run()
// ballAndStick.setTheme({ }
// color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }),
// size: reprCtx.sizeThemeRegistry.create('uniform', { structure }) if (show.ballAndStick) {
// }) ballAndStickRepr.setTheme({
// await ballAndStick.createOrUpdate({ ...BallAndStickRepresentationProvider.defaultValues, quality: 'auto' }, structure).run() color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }),
size: reprCtx.sizeThemeRegistry.create('uniform', { structure })
molecularSurfaceRepr.setTheme({ })
color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }), await ballAndStickRepr.createOrUpdate({ ...BallAndStickRepresentationProvider.defaultValues, quality: 'auto' }, structure).run()
size: reprCtx.sizeThemeRegistry.create('physical', { structure }) }
})
console.time('molecular surface') if (show.molecularSurface) {
await molecularSurfaceRepr.createOrUpdate({ ...MolecularSurfaceRepresentationProvider.defaultValues, quality: 'custom', alpha: 1.0, flatShaded: true, doubleSided: true, resolution: 0.3 }, structure).run() molecularSurfaceRepr.setTheme({
console.timeEnd('molecular surface') color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }),
size: reprCtx.sizeThemeRegistry.create('physical', { structure })
// gaussianSurfaceRepr.setTheme({ })
// color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }), console.time('molecular surface')
// size: reprCtx.sizeThemeRegistry.create('physical', { structure }) await molecularSurfaceRepr.createOrUpdate({ ...MolecularSurfaceRepresentationProvider.defaultValues, quality: 'custom', alpha: 0.5, flatShaded: true, doubleSided: true, resolution: 0.3 }, structure).run()
// }) console.timeEnd('molecular surface')
// console.time('gaussian surface') }
// await gaussianSurfaceRepr.createOrUpdate({ ...GaussianSurfaceRepresentationProvider.defaultValues, quality: 'custom', alpha: 1.0, flatShaded: true, doubleSided: true, resolution: 0.3 }, structure).run()
// console.timeEnd('gaussian surface') if (show.gaussianSurface) {
gaussianSurfaceRepr.setTheme({
// canvas3d.add(cartoonRepr) color: reprCtx.colorThemeRegistry.create('secondary-structure', { structure }),
// canvas3d.add(ballAndStick) size: reprCtx.sizeThemeRegistry.create('physical', { structure })
canvas3d.add(molecularSurfaceRepr) })
// canvas3d.add(gaussianSurfaceRepr) console.time('gaussian surface')
await gaussianSurfaceRepr.createOrUpdate({ ...GaussianSurfaceRepresentationProvider.defaultValues, quality: 'custom', alpha: 1.0, flatShaded: true, doubleSided: true, resolution: 0.3 }, structure).run()
console.timeEnd('gaussian surface')
}
if (show.cartoon) canvas3d.add(cartoonRepr)
if (show.ballAndStick) canvas3d.add(ballAndStickRepr)
if (show.molecularSurface) canvas3d.add(molecularSurfaceRepr)
if (show.gaussianSurface) canvas3d.add(gaussianSurfaceRepr)
canvas3d.resetCamera() canvas3d.resetCamera()
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment