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

refactored volume representations

parent e3ce7c36
No related branches found
No related tags found
No related merge requests found
......@@ -15,7 +15,8 @@ import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-se
import { Table } from 'mol-data/db';
import { StringBuilder } from 'mol-util';
import { Task } from 'mol-task';
import { createVolumeIsosurface } from 'mol-repr/volume/isosurface-mesh';
import { createVolumeIsosurfaceMesh } from 'mol-repr/volume/isosurface-mesh';
import { createEmptyTheme } from 'mol-theme/theme';
require('util.promisify').shim();
const writeFileAsync = util.promisify(fs.writeFile);
......@@ -38,7 +39,7 @@ function print(data: Volume) {
}
async function doMesh(data: Volume, filename: string) {
const mesh = await Task.create('', runtime => createVolumeIsosurface({ runtime }, data.volume, { isoValue: VolumeIsoValue.calcAbsolute(data.volume.dataStats, 1.5) } )).run();
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, data.volume, createEmptyTheme(), { isoValue: VolumeIsoValue.calcAbsolute(data.volume.dataStats, 1.5) } )).run();
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
// Export the mesh in OBJ format.
......
......@@ -35,6 +35,10 @@ namespace VolumeData {
const translate = Mat4.fromTranslation(_translate, volume.fractionalBox.min);
return Mat4.mul3(Mat4.zero(), volume.cell.fromFractional, translate, scale);
}
export function areEquivalent(volA: VolumeData, volB: VolumeData) {
return volA === volB
}
}
type VolumeIsoValue = VolumeIsoValue.Absolute | VolumeIsoValue.Relative
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
......@@ -7,7 +7,6 @@
import { VolumeData } from 'mol-model/volume'
import { RuntimeContext } from 'mol-task'
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
import { createRenderObject } from 'mol-gl/render-object';
import { EmptyLoci } from 'mol-model/loci';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { Vec3, Mat4 } from 'mol-math/linear-algebra';
......@@ -15,13 +14,13 @@ import { Box3D } from 'mol-math/geometry';
import { WebGLContext } from 'mol-gl/webgl/context';
import { createTexture } from 'mol-gl/webgl/texture';
import { LocationIterator } from 'mol-geo/util/location-iterator';
import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
import { BaseGeometry } from 'mol-geo/geometry/base';
import { VisualUpdateState } from 'mol-repr/util';
import { RepresentationContext, RepresentationParamsGetter } from 'mol-repr/representation';
import { Theme, ThemeRegistryContext } from 'mol-theme/theme';
import { VisualContext } from 'mol-repr/visual';
import { NullLocation } from 'mol-model/location';
function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
const bbox = Box3D.empty()
......@@ -144,7 +143,7 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
//
export async function createDirectVolume(ctx: VisualContext, volume: VolumeData, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
export async function createDirectVolume(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
const { runtime, webgl } = ctx
if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props')
......@@ -166,22 +165,15 @@ export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: VolumeD
}
export function DirectVolumeVisual(): VolumeVisual<DirectVolumeParams> {
return VolumeVisual<DirectVolumeParams>({
return VolumeVisual<DirectVolume, DirectVolumeParams>({
defaultProps: PD.getDefaultValues(DirectVolumeParams),
createGeometry: createDirectVolume,
createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
getLoci: () => EmptyLoci,
mark: () => false,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
},
createRenderObject: (geometry: DirectVolume, locationIt: LocationIterator, theme: Theme, props: PD.Values<DirectVolumeParams>) => {
const transform = createIdentityTransform()
const values = DirectVolume.Utils.createValues(geometry, transform, locationIt, theme, props)
const state = DirectVolume.Utils.createRenderableState(props)
return createRenderObject('direct-volume', values, state)
},
updateValues: DirectVolume.Utils.updateValues,
updateBoundingSphere: DirectVolume.Utils.updateBoundingSphere,
updateRenderableState: DirectVolume.Utils.updateRenderableState
geometryUtils: DirectVolume.Utils
})
}
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
......@@ -7,23 +7,22 @@
import { VolumeData } from 'mol-model/volume'
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
import { createRenderObject } from 'mol-gl/render-object';
import { EmptyLoci } from 'mol-model/loci';
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 { LocationIterator } from 'mol-geo/util/location-iterator';
import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
import { VisualUpdateState } from 'mol-repr/util';
import { RepresentationContext, RepresentationParamsGetter } from 'mol-repr/representation';
import { Theme, ThemeRegistryContext } from 'mol-theme/theme';
import { VisualContext } from 'mol-repr/visual';
import { NullLocation } from 'mol-model/location';
interface VolumeIsosurfaceProps {
isoValue: number
}
export async function createVolumeIsosurface(ctx: VisualContext, volume: VolumeData, props: VolumeIsosurfaceProps, mesh?: Mesh) {
export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: VolumeData, theme: Theme, props: VolumeIsosurfaceProps, mesh?: Mesh) {
ctx.runtime.update({ message: 'Marching cubes...' });
const surface = await computeMarchingCubesMesh({
......@@ -39,46 +38,39 @@ export async function createVolumeIsosurface(ctx: VisualContext, volume: VolumeD
return surface;
}
export const IsosurfaceParams = {
export const IsosurfaceMeshParams = {
...Mesh.Params,
isoValue: PD.Numeric(0.22, { min: -1, max: 1, step: 0.01 }),
}
export type IsosurfaceParams = typeof IsosurfaceParams
export type IsosurfaceMeshParams = typeof IsosurfaceMeshParams
export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeData) {
return PD.clone(IsosurfaceParams)
return PD.clone(IsosurfaceMeshParams)
}
export function IsosurfaceVisual(): VolumeVisual<IsosurfaceParams> {
return VolumeVisual<IsosurfaceParams>({
defaultProps: PD.getDefaultValues(IsosurfaceParams),
createGeometry: createVolumeIsosurface,
export function IsosurfaceVisual(): VolumeVisual<IsosurfaceMeshParams> {
return VolumeVisual<Mesh, IsosurfaceMeshParams>({
defaultProps: PD.getDefaultValues(IsosurfaceMeshParams),
createGeometry: createVolumeIsosurfaceMesh,
createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
getLoci: () => EmptyLoci,
mark: () => false,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IsosurfaceParams>, currentProps: PD.Values<IsosurfaceParams>) => {
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
if (newProps.isoValue !== currentProps.isoValue) state.createGeometry = true
},
createRenderObject: (geometry: Mesh, locationIt: LocationIterator, theme: Theme, props: PD.Values<IsosurfaceParams>) => {
const transform = createIdentityTransform()
const values = Mesh.Utils.createValues(geometry, transform, locationIt, theme, props)
const state = Mesh.Utils.createRenderableState(props)
return createRenderObject('mesh', values, state)
},
updateValues: Mesh.Utils.updateValues,
updateBoundingSphere: Mesh.Utils.updateBoundingSphere,
updateRenderableState: Mesh.Utils.updateRenderableState
geometryUtils: Mesh.Utils
})
}
export function IsosurfaceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceParams>): VolumeRepresentation<IsosurfaceParams> {
export function IsosurfaceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceMeshParams>): VolumeRepresentation<IsosurfaceMeshParams> {
return VolumeRepresentation('Isosurface ', ctx, getParams, IsosurfaceVisual)
}
export const IsosurfaceRepresentationProvider: VolumeRepresentationProvider<IsosurfaceParams> = {
export const IsosurfaceRepresentationProvider: VolumeRepresentationProvider<IsosurfaceMeshParams> = {
label: 'Isosurface',
description: 'Displays an isosurface of volumetric data.',
factory: IsosurfaceRepresentation,
getParams: getIsosurfaceParams,
defaultValues: PD.getDefaultValues(IsosurfaceParams),
defaultValues: PD.getDefaultValues(IsosurfaceMeshParams),
defaultColorTheme: 'uniform',
defaultSizeTheme: 'uniform'
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
......@@ -9,94 +9,152 @@ import { Representation, RepresentationContext, RepresentationProvider, Represen
import { Visual, VisualContext } from '../visual';
import { VolumeData } from 'mol-model/volume';
import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
import { Geometry } from 'mol-geo/geometry/geometry';
import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { PickingId } from 'mol-geo/geometry/picking';
import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
import { GraphicsRenderObject } from 'mol-gl/render-object';
import { GraphicsRenderObject, createRenderObject } from 'mol-gl/render-object';
import { Interval } from 'mol-data/int';
import { RenderableValues } from 'mol-gl/renderable/schema';
import { LocationIterator } from 'mol-geo/util/location-iterator';
import { NullLocation } from 'mol-model/location';
import { VisualUpdateState } from 'mol-repr/util';
import { ValueCell } from 'mol-util';
import { Theme, createEmptyTheme } from 'mol-theme/theme';
import { Subject } from 'rxjs';
import { RenderableState } from 'mol-gl/renderable';
import { Mat4 } from 'mol-math/linear-algebra';
import { BaseGeometry } from 'mol-geo/geometry/base';
import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
import { ColorTheme } from 'mol-theme/color';
import { createColors } from 'mol-geo/geometry/color-data';
import { createSizes } from 'mol-geo/geometry/size-data';
export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeData, P> { }
function createVolumeRenderObject<G extends Geometry>(volume: VolumeData, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<Geometry.Params<G>>) {
const { createValues, createRenderableState } = Geometry.getUtils(geometry)
const transform = createIdentityTransform()
const values = createValues(geometry, transform, locationIt, theme, props)
const state = createRenderableState(props)
return createRenderObject(geometry.kind, values, state)
}
interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
defaultProps: PD.Values<P>
createGeometry(ctx: VisualContext, volumeData: VolumeData, props: PD.Values<P>, geometry?: G): Promise<G>
createGeometry(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
createLocationIterator(volume: VolumeData): LocationIterator
getLoci(pickingId: PickingId, id: number): Loci
mark(loci: Loci, apply: (interval: Interval) => boolean): boolean
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>): void
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme): void
}
interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry> extends VolumeVisualBuilder<P, G> {
createRenderObject(geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): GraphicsRenderObject
updateValues(values: RenderableValues, newProps: PD.Values<P>): void,
updateBoundingSphere(values: RenderableValues, geometry: G): void
updateRenderableState(state: RenderableState, props: PD.Values<P>): void
geometryUtils: GeometryUtils<G>
}
export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeometryBuilder<P, Geometry>): VolumeVisual<P> {
const { defaultProps, createGeometry, getLoci, mark, setUpdateState } = builder
const { createRenderObject, updateValues, updateBoundingSphere, updateRenderableState } = builder
export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geometry.Params<G>>(builder: VolumeVisualGeometryBuilder<P, G>): VolumeVisual<P> {
const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
const { updateValues, updateBoundingSphere, updateRenderableState } = builder.geometryUtils
const updateState = VisualUpdateState.create()
let currentProps: PD.Values<P>
let renderObject: GraphicsRenderObject | undefined
let newProps: PD.Values<P>
let newTheme: Theme
let newVolume: VolumeData
let currentProps: PD.Values<P> = Object.assign({}, defaultProps)
let currentTheme: Theme = createEmptyTheme()
let currentVolume: VolumeData
let geometry: Geometry
let geometry: G
let locationIt: LocationIterator
async function create(ctx: VisualContext, volume: VolumeData, theme: Theme, props: Partial<PD.Values<P>> = {}) {
currentProps = Object.assign({}, defaultProps, props)
geometry = await createGeometry(ctx, volume, currentProps, geometry)
locationIt = LocationIterator(1, 1, () => NullLocation)
renderObject = createRenderObject(geometry, locationIt, theme, currentProps)
function prepareUpdate(theme: Theme, props: Partial<PD.Values<P>>, volume: VolumeData) {
if (!volume && !currentVolume) {
throw new Error('missing volume')
}
async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}) {
if (!renderObject) return
const newProps = Object.assign({}, currentProps, props)
newProps = Object.assign({}, currentProps, props)
newTheme = theme
newVolume = volume
VisualUpdateState.reset(updateState)
setUpdateState(updateState, newProps, currentProps)
if (!renderObject) {
updateState.createNew = true
} else if (!currentVolume || !VolumeData.areEquivalent(newVolume, currentVolume)) {
updateState.createNew = true
}
if (updateState.createNew) {
updateState.createGeometry = true
return
}
setUpdateState(updateState, newProps, currentProps, newTheme, currentTheme)
if (!ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
if (updateState.createGeometry) {
geometry = await createGeometry(ctx, currentVolume, currentProps, geometry)
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
updateBoundingSphere(renderObject.values, geometry)
updateState.updateColor = true
}
}
function update(newGeometry?: G) {
if (updateState.createNew) {
locationIt = createLocationIterator(newVolume)
if (newGeometry) {
renderObject = createVolumeRenderObject(newVolume, newGeometry, locationIt, newTheme, newProps)
} else {
throw new Error('expected geometry to be given')
}
} else {
if (!renderObject) {
throw new Error('expected renderObject to be available')
}
locationIt.reset()
if (updateState.createGeometry) {
if (newGeometry) {
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(newGeometry))
updateBoundingSphere(renderObject.values, newGeometry)
} else {
throw new Error('expected geometry to be given')
}
}
if (updateState.updateSize) {
// not all geometries have size data, so check here
if ('uSize' in renderObject.values) {
createSizes(locationIt, newTheme.size, renderObject.values)
}
}
if (updateState.updateColor) {
createColors(locationIt, newTheme.color, renderObject.values)
}
updateValues(renderObject.values, newProps)
updateRenderableState(renderObject.state, newProps)
}
currentProps = newProps
currentTheme = newTheme
currentVolume = newVolume
if (newGeometry) geometry = newGeometry
}
return {
get groupCount() { return locationIt ? locationIt.count : 0 },
get renderObject () { return renderObject },
async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, volume?: VolumeData) {
if (!volume && !currentVolume) {
throw new Error('missing volume')
} else if (volume && (!currentVolume || !renderObject)) {
currentVolume = volume
await create(ctx, volume, theme, props)
} else if (volume && volume !== currentVolume) {
currentVolume = volume
await create(ctx, volume, theme, props)
prepareUpdate(theme, props, volume || currentVolume)
if (updateState.createGeometry) {
const newGeometry = createGeometry(ctx, newVolume, newTheme, newProps, geometry)
return newGeometry instanceof Promise ? newGeometry.then(update) : update(newGeometry)
} else {
await update(ctx, theme, props)
update()
}
currentProps = Object.assign({}, defaultProps, props)
},
getLoci(pickingId: PickingId) {
return renderObject ? getLoci(pickingId, renderObject.id) : EmptyLoci
......@@ -151,18 +209,17 @@ export const VolumeParams = {
}
export type VolumeParams = typeof VolumeParams
export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: (volume: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: () => VolumeVisual<P>): VolumeRepresentation<P> {
let version = 0
const updated = new Subject<number>()
const renderObjects: GraphicsRenderObject[] = []
const _state = Representation.createState()
let visual: VolumeVisual<P>
let visual: VolumeVisual<P> | undefined
let _volume: VolumeData
let _props: PD.Values<P>
let _params: P
let _props: PD.Values<P>
let _theme = createEmptyTheme()
let busy = false
function createOrUpdate(props: Partial<PD.Values<P>> = {}, volume?: VolumeData) {
if (volume && volume !== _volume) {
......@@ -172,22 +229,10 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
}
_props = Object.assign({}, _props, props)
return Task.create('VolumeRepresentation.create', async runtime => {
// TODO queue it somehow
if (busy) return
if (!visual && !volume) {
throw new Error('volume data missing')
} else if (volume && !visual) {
busy = true
visual = visualCtor(volume)
await visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, volume)
busy = false
} else {
busy = true
await visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, volume)
busy = false
}
return Task.create('Creating or updating VolumeRepresentation', async runtime => {
if (!visual) visual = visualCtor()
const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, volume)
if (promise) await promise
// update list of renderObjects
renderObjects.length = 0
if (visual && visual.renderObject) renderObjects.push(visual.renderObject)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment