diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts index b381ab2f082e2918092e7e695c1de8b82a8ffd45..c5674578f27d99dae9056db73f63464279da2c44 100644 --- a/src/apps/canvas/structure-view.ts +++ b/src/apps/canvas/structure-view.ts @@ -24,6 +24,7 @@ import { StructureRepresentation } from 'mol-geo/representation/structure'; import { BehaviorSubject } from 'rxjs'; import { SpacefillRepresentation } from 'mol-geo/representation/structure/representation/spacefill'; import { DistanceRestraintRepresentation } from 'mol-geo/representation/structure/representation/distance-restraint'; +import { SurfaceRepresentation } from 'mol-geo/representation/structure/representation/surface'; export interface StructureView { readonly viewer: Viewer @@ -66,6 +67,7 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> const active: { [k: string]: boolean } = { cartoon: true, point: false, + surface: true, ballAndStick: false, carbohydrate: false, spacefill: false, @@ -76,6 +78,7 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> const structureRepresentations: { [k: string]: StructureRepresentation<any> } = { cartoon: CartoonRepresentation(), + surface: SurfaceRepresentation(), point: PointRepresentation(), ballAndStick: BallAndStickRepresentation(), carbohydrate: CarbohydrateRepresentation(), diff --git a/src/mol-geo/representation/structure/representation/surface.ts b/src/mol-geo/representation/structure/representation/surface.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e95466e6ffa51421b7973b8436b2a5ef1e9a6d8 --- /dev/null +++ b/src/mol-geo/representation/structure/representation/surface.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { UnitsRepresentation } from '..'; +import { GaussianSurfaceVisual, DefaultGaussianSurfaceProps } from '../visual/gaussian-surface-mesh'; +import { StructureRepresentation } from '../units-representation'; +import { Structure } from 'mol-model/structure'; +import { MarkerAction } from '../../../util/marker-data'; +import { Loci } from 'mol-model/loci'; +import { PickingId } from '../../../util/picking'; + +export const DefaultSurfaceProps = { + ...DefaultGaussianSurfaceProps, +} +export type SurfaceProps = typeof DefaultSurfaceProps + +export type SurfaceRepresentation = StructureRepresentation<SurfaceProps> + +export function SurfaceRepresentation(): SurfaceRepresentation { + let currentProps: SurfaceProps + const gaussianRepr = UnitsRepresentation('Gaussian surface', GaussianSurfaceVisual) + return { + label: 'Surface', + get renderObjects() { + return [ ...gaussianRepr.renderObjects ] + }, + get props() { + return { ...gaussianRepr.props } + }, + createOrUpdate: (props: Partial<SurfaceProps> = {}, structure?: Structure) => { + currentProps = Object.assign({}, DefaultSurfaceProps, currentProps, props) + return gaussianRepr.createOrUpdate(currentProps, structure) + }, + getLoci: (pickingId: PickingId) => { + return gaussianRepr.getLoci(pickingId) + }, + mark: (loci: Loci, action: MarkerAction) => { + return gaussianRepr.mark(loci, action) + }, + destroy() { + gaussianRepr.destroy() + } + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/units-visual.ts b/src/mol-geo/representation/structure/units-visual.ts index 534764f3d57028e1ad33103d4e90125d3c69d4fe..a98620a7f37577c8370be896af55bbbc0213c6a0 100644 --- a/src/mol-geo/representation/structure/units-visual.ts +++ b/src/mol-geo/representation/structure/units-visual.ts @@ -32,7 +32,7 @@ export type UnitsMeshProps = typeof DefaultUnitsMeshProps export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> { defaultProps: P - createMesh(ctx: RuntimeContext, unit: Unit, props: P, mesh?: Mesh): Promise<Mesh> + createMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, mesh?: Mesh): Promise<Mesh> createLocationIterator(group: Unit.SymmetryGroup): LocationIterator getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean @@ -59,7 +59,7 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu const unit = group.units[0] currentConformationId = Unit.conformationId(unit) mesh = currentProps.unitKinds.includes(unit.kind) - ? await createMesh(ctx, unit, currentProps, mesh) + ? await createMesh(ctx, unit, currentStructure, currentProps, mesh) : Mesh.createEmpty(mesh) // TODO create empty location iterator when not in unitKinds @@ -102,7 +102,7 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu if (updateState.createMesh) { mesh = newProps.unitKinds.includes(unit.kind) - ? await createMesh(ctx, unit, newProps, mesh) + ? await createMesh(ctx, unit, currentStructure, newProps, mesh) : Mesh.createEmpty(mesh) ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) updateState.updateColor = true diff --git a/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts b/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b894f5a573b51e7f975fa8e5f64cf4340c04caf --- /dev/null +++ b/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2018 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, MeshUpdateState } from '..'; +import { RuntimeContext } from 'mol-task' +import { Mesh } from '../../../mesh/mesh'; +import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; +import { StructureElementIterator, getElementLoci, markElement } from './util/element'; +import { computeMarchingCubes } from '../../../util/marching-cubes/algorithm'; +import { Tensor, Vec3, Mat4 } from 'mol-math/linear-algebra'; +import { Box3D } from 'mol-math/geometry'; +import { ValueCell } from 'mol-util'; +import { smoothstep } from 'mol-math/interpolate'; + +export interface GaussianSurfaceMeshProps { + +} + +function getDelta(box: Box3D) { + const extent = Vec3.sub(Vec3.zero(), box.max, box.min) + + const n = Math.pow(128, 3) + const f = (extent[0] * extent[1] * extent[2]) / n + const s = Math.pow(f, 1 / 3) + const size = Vec3.zero() + // Vec3.scale(size, extent, s) + Vec3.ceil(size, Vec3.scale(size, extent, s)) + const delta = Vec3.div(Vec3.zero(), extent, size) + return delta +} + +async function createGaussianSurfaceMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianSurfaceMeshProps, mesh?: Mesh): Promise<Mesh> { + + const { elements } = unit; + const elementCount = elements.length; + + const r = 2.5; + + const v = Vec3.zero() + const p = Vec3.zero() + const pos = unit.conformation.invariantPosition + const box = unit.lookup3d.boundary.box + const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(r*3, r*3, r*3)); + const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min) + const min = expandedBox.min + + // const n = Math.pow(128, 3) + // const f = (extent[0] * extent[1] * extent[2]) / n + // const s = Math.pow(f, 1 / 3) + // const size = Vec3.zero() + // // Vec3.scale(size, extent, s) + // Vec3.ceil(size, Vec3.scale(size, extent, s)) + // const delta = Vec3.div(Vec3.zero(), extent, size) + + // console.log('extent', extent) + // console.log('n', n) + // console.log('f', f) + // console.log('s', s) + // console.log('size', size) + // console.log('delta', delta) + const delta = getDelta(Box3D.expand(Box3D.empty(), structure.boundary.box, Vec3.create(r*3, r*3, r*3))) + const dim = Vec3.zero() + Vec3.ceil(dim, Vec3.mul(dim, extent, delta)) + // console.log('dim', dim, dim[0] * dim[1] * dim[2]) + + const space = Tensor.Space(dim, [0, 1, 2], Float32Array) + const data = space.create() + const field = Tensor.create(space, data) + + for (let i = 0; i < elementCount; i++) { + pos(elements[i], v) + + Vec3.mul(v, Vec3.sub(v, v, min), delta) + + const size = r + const radius = size * delta[0] + + const minX = Math.floor(v[0] - radius) + const minY = Math.floor(v[1] - radius) + const minZ = Math.floor(v[2] - radius) + const maxX = Math.floor(v[0] + radius) + const maxY = Math.floor(v[1] + radius) + const maxZ = Math.floor(v[2] + radius) + + for (let x = minX; x <= maxX; ++x) { + for (let y = minY; y <= maxY; ++y) { + for (let z = minZ; z <= maxZ; ++z) { + const dist = Vec3.distance(Vec3.set(p, x, y, z), v) + if (dist <= radius) { + const density = 1.0 - smoothstep(0.0, radius * 1.0, dist) + space.set(data, x, y, z, space.get(data, x, y, z) + density) + } + } + } + } + + if (i % 10000 === 0 && ctx.shouldUpdate) { + await ctx.update({ message: 'Gaussian surface', current: i, max: elementCount }); + } + } + + // console.log('data', data) + + const surface = await computeMarchingCubes({ + isoLevel: 0.1, + scalarField: field, + oldSurface: mesh + + }).runAsChild(ctx); + + const t = Mat4.identity() + Mat4.fromUniformScaling(t, 1 / delta[0]) + Mat4.setTranslation(t, expandedBox.min) + + ValueCell.update(surface.groupBuffer, new Float32Array(surface.vertexCount)) + Mesh.transformImmediate(surface, t) + await Mesh.computeNormals(surface).runAsChild(ctx) + + // console.log('surface', surface) + + // const transform = VolumeData.getGridToCartesianTransform(volume); + // ctx.update({ message: 'Transforming mesh...' }); + // Mesh.transformImmediate(surface, transform); + + return surface; +} + +export const DefaultGaussianSurfaceProps = { + ...DefaultUnitsMeshProps, + linearSegments: 8, + radialSegments: 12, + aspectRatio: 5, + arrowFactor: 1.5, + + flipSided: true, + // flatShaded: true, +} +export type GaussianSurfaceProps = typeof DefaultGaussianSurfaceProps + +export function GaussianSurfaceVisual(): UnitsVisual<GaussianSurfaceProps> { + return UnitsMeshVisual<GaussianSurfaceProps>({ + defaultProps: DefaultGaussianSurfaceProps, + createMesh: createGaussianSurfaceMesh, + createLocationIterator: StructureElementIterator.fromGroup, + getLoci: getElementLoci, + mark: markElement, + setUpdateState: (state: MeshUpdateState, newProps: GaussianSurfaceProps, currentProps: GaussianSurfaceProps) => {} + }) +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts index 9b9452a4191d579a07837137b34ccb22082261ae..a3d6dd8687bc1624fd75fb2b45c2a96e383af1c8 100644 --- a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts @@ -5,7 +5,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Unit, Link, StructureElement } from 'mol-model/structure'; +import { Unit, Link, StructureElement, Structure } from 'mol-model/structure'; import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { DefaultLinkCylinderProps, LinkCylinderProps, createLinkCylinderMesh, LinkIterator } from './util/link'; @@ -18,7 +18,7 @@ import { Interval } from 'mol-data/int'; import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; import { BitFlags } from 'mol-util'; -async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) { +async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) const sizeTheme = SizeTheme(props.sizeTheme) 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 8b5d893ef85dc1b6ae8b18ea8a651c3427e8872d..dcf176ba3d1fef8188b3b2886be11e7d46beb4c6 100644 --- a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts +++ b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from 'mol-model/structure'; +import { Unit, Structure } from 'mol-model/structure'; import { UnitsVisual } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; @@ -33,7 +33,7 @@ const sVec = Vec3.zero() const box = Box() // TODO define props, should be scalable -async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) { +async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: {}, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) // TODO better vertex count estimate diff --git a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts index d975ae940228601a3fd1a833979d28dc2c0dc96e..cd9ca682c0cab90544d44ab88e7958a4858ef956 100644 --- a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from 'mol-model/structure'; +import { Unit, Structure } from 'mol-model/structure'; import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; @@ -23,7 +23,7 @@ export interface PolymerBackboneCylinderProps { radialSegments: number } -async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerBackboneCylinderProps, mesh?: Mesh) { +async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: PolymerBackboneCylinderProps, mesh?: Mesh) { const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) diff --git a/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts b/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts index 1e6b8e42a198d66249becde9ff3219b723a659ee..bf6afd3f8f2c21565851435caa2f10bbe60fa8cb 100644 --- a/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts +++ b/src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from 'mol-model/structure'; +import { Unit, Structure } from 'mol-model/structure'; import { UnitsVisual } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; @@ -32,7 +32,7 @@ export interface PolymerDirectionWedgeProps { sizeTheme: SizeThemeProps } -async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, props: PolymerDirectionWedgeProps, mesh?: Mesh) { +async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: PolymerDirectionWedgeProps, mesh?: Mesh) { const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) diff --git a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts index 24038dfc3182746c74d2ae0336a5556ff00cfcde..21ebafc2d3ffd91d4160defa3e9412568df14d86 100644 --- a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from 'mol-model/structure'; +import { Unit, Structure } from 'mol-model/structure'; import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; @@ -24,7 +24,7 @@ export interface PolymerGapCylinderProps { radialSegments: number } -async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerGapCylinderProps, mesh?: Mesh) { +async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: PolymerGapCylinderProps, mesh?: Mesh) { const polymerGapCount = unit.gapElements.length if (!polymerGapCount) return Mesh.createEmpty(mesh) diff --git a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts index 1a37e4d9809fa189a779c2dcb2b969ccc382dcba..685b0d3c4f6889f3d55586505053c00cb5aba739 100644 --- a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts +++ b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from 'mol-model/structure'; +import { Unit, Structure } from 'mol-model/structure'; import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; @@ -26,7 +26,7 @@ export interface PolymerTraceMeshProps { // TODO handle polymer ends properly -async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: PolymerTraceMeshProps, mesh?: Mesh) { +async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: PolymerTraceMeshProps, mesh?: Mesh) { const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) diff --git a/src/mol-geo/representation/structure/visual/util/element.ts b/src/mol-geo/representation/structure/visual/util/element.ts index 0de1861eefddaeac2d28b8d6e8f20790f4eca963..6887addab337f1956adcb4c1645e2586c9bbd4ab 100644 --- a/src/mol-geo/representation/structure/visual/util/element.ts +++ b/src/mol-geo/representation/structure/visual/util/element.ts @@ -5,7 +5,7 @@ */ import { Vec3 } from 'mol-math/linear-algebra'; -import { Unit, StructureElement } from 'mol-model/structure'; +import { Unit, StructureElement, Structure } from 'mol-model/structure'; import { RuntimeContext } from 'mol-task'; import { sphereVertexCount } from '../../../../primitive/sphere'; import { Mesh } from '../../../../mesh/mesh'; @@ -22,7 +22,7 @@ export interface ElementSphereMeshProps { detail: number, } -export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, props: ElementSphereMeshProps, mesh?: Mesh) { +export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: ElementSphereMeshProps, mesh?: Mesh) { const { detail } = props const { elements } = unit;