From 84e6ebf57e80afe280bfc33b58e5cc78020384fe Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sun, 9 Sep 2018 19:26:05 -0700 Subject: [PATCH] wip, gaussian surface --- src/apps/canvas/structure-view.ts | 3 + .../structure/representation/surface.ts | 47 ++++++ .../representation/structure/units-visual.ts | 6 +- .../structure/visual/gaussian-surface-mesh.ts | 153 ++++++++++++++++++ .../visual/intra-unit-link-cylinder.ts | 4 +- .../structure/visual/nucleotide-block-mesh.ts | 4 +- .../visual/polymer-backbone-cylinder.ts | 4 +- .../visual/polymer-direction-wedge.ts | 4 +- .../structure/visual/polymer-gap-cylinder.ts | 4 +- .../structure/visual/polymer-trace-mesh.ts | 4 +- .../structure/visual/util/element.ts | 4 +- 11 files changed, 220 insertions(+), 17 deletions(-) create mode 100644 src/mol-geo/representation/structure/representation/surface.ts create mode 100644 src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts index b381ab2f0..c5674578f 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 000000000..7e95466e6 --- /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 534764f3d..a98620a7f 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 000000000..6b894f5a5 --- /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 9b9452a41..a3d6dd868 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 8b5d893ef..dcf176ba3 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 d975ae940..cd9ca682c 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 1e6b8e42a..bf6afd3f8 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 24038dfc3..21ebafc2d 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 1a37e4d98..685b0d3c4 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 0de1861ee..6887addab 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; -- GitLab