diff --git a/src/mol-math/geometry/molecular-surface.ts b/src/mol-math/geometry/molecular-surface.ts new file mode 100644 index 0000000000000000000000000000000000000000..cfd5346804c729ab1e41f8818581576559d0091a --- /dev/null +++ b/src/mol-math/geometry/molecular-surface.ts @@ -0,0 +1,568 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Fred Ludlow <fred.ludlow@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * + * ported from NGL (https://github.com/arose/ngl), licensed under MIT + */ + +import { fillUniform } from 'mol-util/array'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { NumberArray } from 'mol-util/type-helpers'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { number } from 'prop-types'; +import { Lookup3D, Result } from './lookup3d/common'; +import { RuntimeContext } from 'mol-task'; +import { OrderedSet } from 'mol-data/int'; +import { PositionData } from './common'; + +function normalToLine (out: Vec3, p: Vec3) { + out[0] = out[1] = out[2] = 1.0 + if (p[0] !== 0) { + out[0] = (p[1] + p[2]) / -p[0] + } else if (p[1] !== 0) { + out[1] = (p[0] + p[2]) / -p[1] + } else if (p[2] !== 0) { + out[2] = (p[0] + p[1]) / -p[2] + } + return out +} + +function fillGridDim (a: Float32Array, start: number, step: number) { + for (let i = 0; i < a.length; i++) { + a[i] = start + (step * i) + } +} + +type AnglesTables = { cosTable: Float32Array, sinTable: Float32Array } +function getAngleTables (probePositions: number): AnglesTables { + let theta = 0.0 + const step = 2 * Math.PI / probePositions + + const cosTable = new Float32Array(probePositions) + const sinTable = new Float32Array(probePositions) + for (let i = 0; i < probePositions; i++) { + cosTable[ i ] = Math.cos(theta) + sinTable[ i ] = Math.sin(theta) + theta += step + } + 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) + */ +function obscured (state: MolSurfCalcState, x: number, y: number, z: number, a: number, b: number) { + let ai: number + + if (state.lastClip !== -1) { + ai = state.lastClip + if (ai !== a && ai !== b && singleAtomObscures(state, ai, x, y, z)) { + return ai + } else { + state.lastClip = -1 + } + } + + let ni = 0 + ai = state.neighbours[ni] + while (ai >= 0) { + if (ai !== a && ai !== b && singleAtomObscures(state, ai, x, y, z)) { + state.lastClip = ai + return ai + } + ai = state.neighbours[++ni] + } + + state.lastClip = -1 + + return -1 +} + +function singleAtomObscures (state: MolSurfCalcState, ai: number, x: number, y: number, z: number) { + const ra2 = state.radiusSq[ai] + const dx = state.xCoord[ai] - x + const dy = state.yCoord[ai] - y + const dz = state.zCoord[ai] - z + const d2 = dx * dx + dy * dy + dz * dz + return d2 < ra2 +} + +/** + * 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, radius, scaleFactor, lookup3D, min, delta } = state + + const { indices, x, y, z } = position + const n = OrderedSet.size(indices) + + const v = Vec3() + const p = Vec3() + const c = Vec3() + + const beg = Vec3() + const end = Vec3() + + const gridPad = 1 / Math.max(...delta) + + for (let i = 0; i < n; ++i) { + const j = OrderedSet.getAt(indices, i) + + Vec3.set(v, x[j], y[j], z[j]) + + Vec3.sub(v, v, min) + Vec3.mul(c, v, delta) + + const rad = radius(j) + const rSq = rad * rad + + const r2 = rad * 2 + gridPad + const rad2 = Vec3.create(r2, r2, r2) + Vec3.mul(rad2, rad2, delta) + const r2sq = r2 * r2 + + const [ begX, begY, begZ ] = Vec3.floor(beg, Vec3.sub(beg, c, rad2)) + const [ endX, endY, endZ ] = Vec3.ceil(end, Vec3.add(end, c, rad2)) + + for (let xi = begX; xi < endX; ++xi) { + for (let yi = begY; yi < endY; ++yi) { + for (let zi = begZ; zi < endZ; ++zi) { + Vec3.set(p, xi, yi, zi) + Vec3.div(p, p, delta) + const distSq = Vec3.squaredDistance(p, v) + if (distSq <= r2sq) { + const dens = Math.exp(-alpha * (distSq / rSq)) + space.add(data, xi, yi, zi, dens) + if (dens > space.get(densData, xi, yi, zi)) { + space.set(densData, xi, yi, zi, dens) + space.set(idData, xi, yi, zi, i) + } + } + } + } + } + + if (i % 10000 === 0 && ctx.shouldUpdate) { + await ctx.update({ message: 'projecting points', current: i, max: n }) + } + } + + for (let i = 0; i < nAtoms; i++) { + const ax = xCoord[ i ] + const ay = yCoord[ i ] + const az = zCoord[ i ] + const ar = radius[ i ] + const ar2 = radiusSq[ i ] + + state.neighbours = lookup3D.find(ax, ay, az, ar) + + // Number of grid points, round this up... + const ng = Math.ceil(ar * scaleFactor) + + // Center of the atom, mapped to grid points (take floor) + const iax = Math.floor(scaleFactor * (ax - min[ 0 ])) + const iay = Math.floor(scaleFactor * (ay - min[ 1 ])) + const iaz = Math.floor(scaleFactor * (az - min[ 2 ])) + + // Extents of grid to consider for this atom + const minx = Math.max(0, iax - ng) + const miny = Math.max(0, iay - ng) + const minz = 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 maxx = Math.min(dim[ 0 ], iax + ng + 2) + const maxy = Math.min(dim[ 1 ], iay + ng + 2) + const maxz = Math.min(dim[ 2 ], iaz + ng + 2) + + for (let ix = minx; ix < maxx; ix++) { + const dx = gridx[ ix ] - ax + const xoffset = dim[ 1 ] * dim[ 2 ] * ix + + for (let iy = miny; iy < maxy; iy++) { + const dy = gridy[ iy ] - ay + const dxy2 = dx * dx + dy * dy + const xyoffset = xoffset + dim[ 2 ] * iy + + for (let iz = minz; iz < maxz; iz++) { + const dz = gridz[ iz ] - az + const d2 = dxy2 + dz * dz + + if (d2 < ar2) { + const idx = iz + xyoffset + + if (grid[idx] < 0.0) { + // Unvisited, make positive + grid[ idx ] = -grid[ idx ] + } + // Project on to the surface of the sphere + // sp is the projected point ( dx, dy, dz ) * ( ra / d ) + const d = Math.sqrt(d2) + const ap = ar / d + let spx = dx * ap + let spy = dy * ap + let spz = dz * ap + + spx += ax + spy += ay + spz += az + + if (obscured(state, spx, spy, spz, i, -1) === -1) { + const dd = ar - d + if (dd < grid[ idx ]) { + grid[ idx ] = dd + atomIndex[ idx ] = i + } + } + } + } + } + } + } +} + +// Vectors for Torus Projection +const atob = Vec3() +const mid = Vec3() +const n1 = Vec3() +const n2 = Vec3() +function projectTorus (state: MolSurfCalcState, a: number, b: number) { + const r1 = state.radius[a] + const r2 = state.radius[b] + const dx = atob[0] = state.xCoord[b] - state.xCoord[a] + const dy = atob[1] = state.yCoord[b] - state.yCoord[a] + const dz = atob[2] = state.zCoord[b] - state.zCoord[a] + const d2 = dx * dx + dy * dy + dz * dz + + // This check now redundant as already done in AVHash.withinRadii + // if (d2 > ((r1 + r2) * (r1 + r2))){ return; } + + const d = Math.sqrt(d2) + + // Find angle between a->b vector and the circle + // of their intersection by cosine rule + const cosA = (r1 * r1 + d * d - r2 * r2) / (2.0 * r1 * d) + + // distance along a->b at intersection + const dmp = r1 * 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(r1 * r1 - dmp * dmp) + + Vec3.scale(n1, n1, rInt) + Vec3.scale(n2, n2, rInt) + Vec3.scale(atob, atob, dmp) + + mid[0] = atob[0] + state.xCoord[a] + mid[1] = atob[1] + state.yCoord[a] + mid[2] = atob[2] + state.zCoord[a] + + state.lastClip = -1 + + const { ngTorus, cosTable, sinTable, scaleFactor } = state + + for (let i = 0; i < state.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(state, px, py, pz, a, b) === -1) { + // As above, iterate over our grid... + // px, py, pz in grid coords + const iax = Math.floor(scaleFactor * (px - min[0])) + const iay = Math.floor(scaleFactor * (py - min[1])) + const iaz = Math.floor(scaleFactor * (pz - min[2])) + + const minx = Math.max(0, iax - ngTorus) + const miny = Math.max(0, iay - ngTorus) + const minz = Math.max(0, iaz - ngTorus) + + const maxx = Math.min(dim[0], iax + ngTorus + 2) + const maxy = Math.min(dim[1], iay + ngTorus + 2) + const maxz = Math.min(dim[2], iaz + ngTorus + 2) + + for (let ix = minx; ix < maxx; ix++) { + const dx = px - gridx[ ix ] + const xoffset = dim[1] * dim[2] * ix + + for (let iy = miny; iy < maxy; iy++) { + const dy = py - gridy[iy] + const dxy2 = dx * dx + dy * dy + const xyoffset = xoffset + dim[2] * iy + + for (let iz = minz; iz < maxz; iz++) { + const dz = pz - gridz[iz] + const d2 = dxy2 + dz * dz + const idx = iz + xyoffset + const current = grid[idx] + + if (current > 0.0 && d2 < (current * current)) { + grid[idx] = Math.sqrt(d2) + // 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] + atomIndex[idx] = dp < 0.0 ? b : a + } + } + } + } + } + } +} + +function projectTorii (state: MolSurfCalcState) { + const { n: nAtoms, neighbours, hash, xCoord, yCoord, zCoord, radius } = state + for (let i = 0; i < nAtoms; i++) { + hash.withinRadii(xCoord[i], yCoord[i], zCoord[i], radius[i], neighbours) + let ia = 0 + let ni = neighbours[ ia ] + while (ni >= 0) { + if (i < ni) { + projectTorus(state, i, ni) + } + ni = neighbours[ ++ia ] + } + } +} + +function fixNegatives (grid: NumberArray) { + for (let i = 0; i < grid.length; i++) { + if (grid[i] < 0) grid[i] = 0 + } +} + +function fixAtomIDs (atomIndex: NumberArray, indexList: NumberArray) { + for (let i = 0; i < atomIndex.length; i++) { + atomIndex[i] = indexList[atomIndex[i]] + } +} + +// + +interface MolSurfCalcState { + /** Cached last value for obscured test */ + lastClip: number + /** Neighbours as transient result array from lookup3d */ + neighbours: Result<number> + + lookup3D: Lookup3D + position: PositionData + radius: (index: number) => number + delta: Vec3 + min: Vec3 + + maxRadius: number + + n: number + scaleFactor: number + + /** Angle lookup tables */ + cosTable: Float32Array + sinTable: Float32Array + + probePositions: number + ngTorus: number +} + + + +export const MolecularSurfaceCalculationParams = { + scaleFactor: PD.Numeric(2, { min: 0.1, max: 10, step: 0.1 }), + 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 + +function createState(nAtoms: number, props: MolecularSurfaceCalculationProps): MolSurfCalcState { + const { scaleFactor, probeRadius, probePositions } = props + const { cosTable, sinTable } = getAngleTables(probePositions) + const ngTorus = Math.max(5, 2 + Math.floor(probeRadius * scaleFactor)) + + + return { + lastClip: -1, + neighbours: Int32Array, + + xCoord: new Float32Array(nAtoms), + yCoord: new Float32Array(nAtoms), + zCoord: new Float32Array(nAtoms), + radius: new Float32Array(nAtoms), + radiusSq: new Float32Array(nAtoms), + maxRadius: 0, + + n: nAtoms, + scaleFactor, + + cosTable, + sinTable, + + probePositions, + ngTorus, + } +} + +// + +export function MolecularSurface(coordList: Float32Array, radiusList: Float32Array, indexList: Uint16Array|Uint32Array) { + // Field generation method adapted from AstexViewer (Mike Hartshorn) + // by Fred Ludlow. + // Other parts based heavily on NGL (Alexander Rose) EDT Surface class + // + // Should work as a drop-in alternative to EDTSurface (though some of + // the EDT paramters are not relevant in this method). + + const nAtoms = radiusList.length + + const x = new Float32Array(nAtoms) + const y = new Float32Array(nAtoms) + const z = new Float32Array(nAtoms) + + for (let i = 0; i < nAtoms; i++) { + const ci = 3 * i + x[ i ] = coordList[ ci ] + y[ i ] = coordList[ ci + 1 ] + z[ i ] = coordList[ ci + 2 ] + } + + let bbox = computeBoundingBox(coordList) + if (coordList.length === 0) { + bbox[ 0 ].set([ 0, 0, 0 ]) + bbox[ 1 ].set([ 0, 0, 0 ]) + } + const min = bbox[0] + const max = bbox[1] + + let r: Float32Array, r2: Float32Array // Atom positions, expanded radii (squared) + let maxRadius: number + + // Parameters + let probeRadius: number, scaleFactor: number, setAtomID: boolean, probePositions: number + + // Grid params + let dim: Float32Array, matrix: Float32Array, grid: NumberArray, atomIndex: Int32Array + + // grid indices -> xyz coords + let gridx: Float32Array, gridy: Float32Array, gridz: Float32Array + + // Spatial Hash + let hash: iAVHash + + // Neighbour array to be filled by hash + let neighbours: Int32Array + + let ngTorus: number + + function init (_probeRadius?: number, _scaleFactor?: number, _setAtomID?: boolean, _probePositions?: number) { + probeRadius = defaults(_probeRadius, 1.4) + scaleFactor = defaults(_scaleFactor, 2.0) + setAtomID = defaults(_setAtomID, true) + probePositions = defaults(_probePositions, 30) + + r = new Float32Array(nAtoms) + r2 = new Float32Array(nAtoms) + + for (let i = 0; i < r.length; ++i) { + var rExt = radiusList[ i ] + probeRadius + r[ i ] = rExt + r2[ i ] = rExt * rExt + } + + maxRadius = 0 + for (let j = 0; j < r.length; ++j) { + if (r[ j ] > maxRadius) maxRadius = r[ j ] + } + + initializeGrid() + getAngleTables(probePositions) + initializeHash() + + lastClip = -1 + } + + function initializeGrid () { + const surfGrid = getSurfaceGrid( + min, max, maxRadius, scaleFactor, 0.0 + ) + + scaleFactor = surfGrid.scaleFactor + dim = surfGrid.dim + matrix = surfGrid.matrix + + ngTorus = Math.max(5, 2 + Math.floor(probeRadius * scaleFactor)) + + grid = fillUniform(new Float32Array(dim[0] * dim[1] * dim[2]), -1001.0) + + atomIndex = new Int32Array(grid.length) + + gridx = new Float32Array(dim[0]) + gridy = new Float32Array(dim[1]) + gridz = new Float32Array(dim[2]) + + fillGridDim(gridx, min[0], 1 / scaleFactor) + fillGridDim(gridy, min[1], 1 / scaleFactor) + fillGridDim(gridz, min[2], 1 / scaleFactor) + } + + + + function initializeHash () { + hash = makeAVHash(x, y, z, r, min, max, 2.01 * maxRadius) + neighbours = new Int32Array(hash.neighbourListLength) + } + + + + + + function getVolume (probeRadius: number, scaleFactor: number, setAtomID: boolean) { + // Basic steps are: + // 1) Initialize + // 2) Project points + // 3) Project torii + + console.time('AVSurface.getVolume') + + console.time('AVSurface.init') + init(probeRadius, scaleFactor, setAtomID) + console.timeEnd('AVSurface.init') + + console.time('AVSurface.projectPoints') + projectPoints() + console.timeEnd('AVSurface.projectPoints') + + console.time('AVSurface.projectTorii') + projectTorii() + console.timeEnd('AVSurface.projectTorii') + fixNegatives() + fixAtomIDs() + + console.timeEnd('AVSurface.getVolume') + } +} \ No newline at end of file diff --git a/src/mol-repr/structure/registry.ts b/src/mol-repr/structure/registry.ts index a9f9d8c8681c4286120755ccfdb681c451c91d3c..d21925c6b02fb0c27cfb6c0e21b3855dd8c0aa5d 100644 --- a/src/mol-repr/structure/registry.ts +++ b/src/mol-repr/structure/registry.ts @@ -16,6 +16,7 @@ import { DistanceRestraintRepresentationProvider } from './representation/distan import { PointRepresentationProvider } from './representation/point'; import { StructureRepresentationState } from './representation'; import { PuttyRepresentationProvider } from './representation/putty'; +import { MolecularSurfaceRepresentationProvider } from './representation/molecular-surface'; export class StructureRepresentationRegistry extends RepresentationRegistry<Structure, StructureRepresentationState> { constructor() { @@ -34,6 +35,7 @@ export const BuiltInStructureRepresentations = { 'distance-restraint': DistanceRestraintRepresentationProvider, 'gaussian-surface': GaussianSurfaceRepresentationProvider, 'gaussian-volume': GaussianVolumeRepresentationProvider, + 'molecular-surface': MolecularSurfaceRepresentationProvider, 'point': PointRepresentationProvider, 'putty': PuttyRepresentationProvider, 'spacefill': SpacefillRepresentationProvider, diff --git a/src/mol-repr/structure/representation/molecular-surface.ts b/src/mol-repr/structure/representation/molecular-surface.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a5f0c0510c8d7d3302c5c458d8f3d94a35d4b40 --- /dev/null +++ b/src/mol-repr/structure/representation/molecular-surface.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { MolecularSurfaceMeshVisual, MolecularSurfaceMeshParams } from '../visual/molecular-surface-mesh'; +import { UnitsRepresentation } from '../units-representation'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder } from '../representation'; +import { Representation, RepresentationParamsGetter, RepresentationContext } from 'mol-repr/representation'; +import { ThemeRegistryContext } from 'mol-theme/theme'; +import { Structure } from 'mol-model/structure'; + +const MolecularSurfaceVisuals = { + 'molecular-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceMeshParams>) => UnitsRepresentation('Molecular surface', ctx, getParams, MolecularSurfaceMeshVisual), +} +type MolecularSurfaceVisualName = keyof typeof MolecularSurfaceVisuals +const MolecularSurfaceVisualOptions = Object.keys(MolecularSurfaceVisuals).map(name => [name, name] as [MolecularSurfaceVisualName, string]) + +export const MolecularSurfaceParams = { + ...MolecularSurfaceMeshParams, + visuals: PD.MultiSelect<MolecularSurfaceVisualName>(['molecular-surface-mesh'], MolecularSurfaceVisualOptions), +} +export type MolecularSurfaceParams = typeof MolecularSurfaceParams +export function getMolecularSurfaceParams(ctx: ThemeRegistryContext, structure: Structure) { + return PD.clone(MolecularSurfaceParams) +} + +export type MolecularSurfaceRepresentation = StructureRepresentation<MolecularSurfaceParams> +export function MolecularSurfaceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceParams>): MolecularSurfaceRepresentation { + return Representation.createMulti('Molecular Surface', ctx, getParams, StructureRepresentationStateBuilder, MolecularSurfaceVisuals as unknown as Representation.Def<Structure, MolecularSurfaceParams>) +} + +export const MolecularSurfaceRepresentationProvider: StructureRepresentationProvider<MolecularSurfaceParams> = { + label: 'Molecular Surface', + description: 'Displays a molecular surface.', + factory: MolecularSurfaceRepresentation, + getParams: getMolecularSurfaceParams, + defaultValues: PD.getDefaultValues(MolecularSurfaceParams), + defaultColorTheme: 'polymer-id', + defaultSizeTheme: 'uniform' +} \ No newline at end of file diff --git a/src/mol-repr/structure/visual/molecular-surface-mesh.ts b/src/mol-repr/structure/visual/molecular-surface-mesh.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9f317ee66f4cded3fdf64ab33bb13f3f55ce2e0 --- /dev/null +++ b/src/mol-repr/structure/visual/molecular-surface-mesh.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, Structure } from 'mol-model/structure'; +import { UnitsVisual } from '../representation'; +import { VisualUpdateState } from '../../util'; +import { UnitsMeshVisual, UnitsMeshParams, UnitsTextureMeshParams } from '../units-visual'; +import { StructureElementIterator, getElementLoci, eachElement } from './util/element'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { Mesh } from 'mol-geo/geometry/mesh/mesh'; +import { computeMarchingCubesMesh } from 'mol-geo/util/marching-cubes/algorithm'; +import { VisualContext } from 'mol-repr/visual'; +import { Theme } from 'mol-theme/theme'; +import { MolecularSurfaceCalculationParams, MolecularSurfaceCalculationProps, computeUnitMolecularSurface } from './util/molecular-surface'; + +export const MolecularSurfaceMeshParams = { + ...UnitsMeshParams, + ...UnitsTextureMeshParams, + ...MolecularSurfaceCalculationParams, +} +export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams + +// + +async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceCalculationProps, mesh?: Mesh): Promise<Mesh> { + const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime) + + const params = { + isoLevel: 1, + scalarField: field, + idField + } + const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime) + + Mesh.transformImmediate(surface, transform) + Mesh.computeNormalsImmediate(surface) + Mesh.uniformTriangleGroup(surface) + + return surface +} + +export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<MolecularSurfaceMeshParams> { + return UnitsMeshVisual<MolecularSurfaceMeshParams>({ + defaultProps: PD.getDefaultValues(MolecularSurfaceMeshParams), + createGeometry: createMolecularSurfaceMesh, + createLocationIterator: StructureElementIterator.fromGroup, + getLoci: getElementLoci, + eachLocation: eachElement, + setUpdateState: (state: VisualUpdateState, newProps: PD.Values<MolecularSurfaceMeshParams>, currentProps: PD.Values<MolecularSurfaceMeshParams>) => { + if (newProps.resolution !== currentProps.resolution) state.createGeometry = true + if (newProps.probeRadius !== currentProps.probeRadius) state.createGeometry = true + } + }, materialId) +} \ No newline at end of file diff --git a/src/mol-repr/structure/visual/util/molecular-surface.ts b/src/mol-repr/structure/visual/util/molecular-surface.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1e592db6d6939bea295e9cab2da90e9ad157f35 --- /dev/null +++ b/src/mol-repr/structure/visual/util/molecular-surface.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { Unit } from 'mol-model/structure'; +import { Task, RuntimeContext } from 'mol-task'; +import { getUnitConformationAndRadius } from './common'; +import { PositionData, Box3D, DensityData } from 'mol-math/geometry'; + +export const MolecularSurfaceCalculationParams = { + resolution: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }), + probeRadius: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }), +} +export const DefaultMolecularSurfaceCalculationProps = PD.getDefaultValues(MolecularSurfaceCalculationParams) +export type MolecularSurfaceCalculationProps = typeof DefaultMolecularSurfaceCalculationProps + +export function computeUnitMolecularSurface(unit: Unit, props: MolecularSurfaceCalculationProps) { + const { position, radius } = getUnitConformationAndRadius(unit) + return Task.create('Molecular Surface', async ctx => { + return await MolecularSurface(ctx, position, unit.lookup3d.boundary.box, radius, props); + }); +} + +// + +async function MolecularSurface(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: MolecularSurfaceCalculationProps): Promise<DensityData> { + return { + transform: Mat4, + field: Tensor, + idField: Tensor, + } +} \ No newline at end of file