Browse Source

initial direct volume rendering version

Alexander Rose 6 years ago
parent
commit
4e38407c90

+ 1 - 1
src/apps/canvas/structure-view.ts

@@ -70,7 +70,7 @@ export async function StructureView(app: App, viewer: Viewer, models: ReadonlyAr
     const active: { [k: string]: boolean } = {
         cartoon: true,
         point: false,
-        surface: false,
+        surface: true,
         ballAndStick: false,
         carbohydrate: false,
         spacefill: false,

+ 96 - 0
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { RuntimeContext } from 'mol-task'
+import { ValueCell } from 'mol-util'
+import { Sphere3D } from 'mol-math/geometry'
+import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, TextParam } from 'mol-view/parameter';
+import { DirectVolumeValues } from 'mol-gl/renderable/direct-volume';
+import { TextureImage } from 'mol-gl/renderable/util';
+import { Vec3, Vec2, Mat4 } from 'mol-math/linear-algebra';
+import { Box } from '../../primitive/box';
+import { getControlPointsFromString, createTransferFunctionTexture } from './transfer-function';
+
+export interface DirectVolume {
+    readonly kind: 'direct-volume',
+
+    readonly gridDimension: ValueCell<Vec3>,
+    readonly gridTexture: ValueCell<TextureImage>,
+    readonly gridTextureDim: ValueCell<Vec2>,
+    readonly bboxSize: ValueCell<Vec3>
+    readonly bboxMin: ValueCell<Vec3>
+    readonly bboxMax: ValueCell<Vec3>
+    readonly transform: ValueCell<Mat4>
+
+    /** Bounding sphere of the volume */
+    boundingSphere?: Sphere3D
+}
+
+const VolumeBox = Box()
+
+const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
+
+export namespace DirectVolume {
+    export function createEmpty(directVolume?: DirectVolume): DirectVolume {
+        // TODO
+        return {
+
+        } as DirectVolume
+    }
+
+    //
+
+    export const Params = {
+        alpha: RangeParam('Opacity', '', 1, 0, 1, 0.01),
+        visible: BooleanParam('Visible', '', true),
+        depthMask: BooleanParam('Depth Mask', '', true),
+        useFog: BooleanParam('Use Fog', '', false),
+        isoValue: RangeParam('Iso Value', '', 0.22, 0, 1, 0.01),
+        renderMode: SelectParam('Render Mode', '', 'volume', RenderModeOptions),
+        controlPoints: TextParam('Control Points', '', '0.19:0.1, 0.2:0.5, 0.21:0.1, 0.4:0.3'),
+    }
+    export const DefaultProps = paramDefaultValues(Params)
+    export type Props = typeof DefaultProps
+
+    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume, props: Props): Promise<DirectVolumeValues> {
+        const { bboxSize, bboxMin, bboxMax, gridDimension, gridTexture, gridTextureDim, transform } = directVolume
+
+        const controlPoints = getControlPointsFromString(props.controlPoints)
+        const transferTex = createTransferFunctionTexture(controlPoints)
+
+        return {
+            drawCount: ValueCell.create(VolumeBox.indices.length),
+            instanceCount: ValueCell.create(1),
+
+            aPosition: ValueCell.create(VolumeBox.vertices as Float32Array),
+            elements: ValueCell.create(VolumeBox.indices as Uint32Array),
+
+            uAlpha: ValueCell.create(props.alpha),
+            dUseFog: ValueCell.create(props.useFog),
+
+            uIsoValue: ValueCell.create(props.isoValue),
+            uBboxMin: bboxMin,
+            uBboxMax: bboxMax,
+            uBboxSize: bboxSize,
+            uTransform: transform,
+            uGridDim: gridDimension,
+            uGridTexDim: gridTextureDim,
+            tGridTex: gridTexture,
+            dRenderMode: ValueCell.create(props.renderMode),
+            tTransferTex: transferTex,
+        }
+    }
+
+    export function updateValues(values: DirectVolumeValues, props: Props) {
+        ValueCell.updateIfChanged(values.uIsoValue, props.isoValue)
+        ValueCell.updateIfChanged(values.uAlpha, props.alpha)
+        ValueCell.updateIfChanged(values.dUseFog, props.useFog)
+        ValueCell.updateIfChanged(values.dRenderMode, props.renderMode)
+
+        const controlPoints = getControlPointsFromString(props.controlPoints)
+        createTransferFunctionTexture(controlPoints, values.tTransferTex)
+    }
+}

+ 68 - 0
src/mol-geo/geometry/direct-volume/transfer-function.ts

@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { TextureImage } from 'mol-gl/renderable/util';
+import { spline } from 'mol-math/interpolate';
+import { ColorScale } from 'mol-util/color';
+import { ColorMatplotlib } from 'mol-util/color/tables';
+import { ValueCell } from 'mol-util';
+
+export interface ControlPoint { x: number, alpha: number }
+
+export function getControlPointsFromString(s: string): ControlPoint[] {
+    return s.split(/\s*,\s*/).map(p => {
+        const ps = p.split(/\s*:\s*/)
+        return { x: parseFloat(ps[0]), alpha: parseFloat(ps[1]) }
+    })
+}
+
+export function createTransferFunctionTexture(controlPoints: ControlPoint[], texture?: ValueCell<TextureImage>): ValueCell<TextureImage> {
+    const cp = [
+        { x: 0, alpha: 0 },
+        { x: 0, alpha: 0 },
+        ...controlPoints,
+        { x: 1, alpha: 0 },
+        { x: 1, alpha: 0 },
+    ]
+    const scale = ColorScale.create({
+        domain: [0, 1],
+        colors: ColorMatplotlib.viridis
+    })
+
+    const n = 256
+    const array = texture ? texture.ref.value.array : new Uint8Array(n * 4)
+
+    let k = 0
+    let x1: number, x2: number
+    let a0: number, a1: number, a2: number, a3: number
+
+    const il = controlPoints.length + 1
+    for (let i = 0; i < il; ++i) {
+        x1 = cp[i + 1].x
+        x2 = cp[i + 2].x
+
+        a0 = cp[i].alpha
+        a1 = cp[i + 1].alpha
+        a2 = cp[i + 2].alpha
+        a3 = cp[i + 3].alpha
+
+        const jl = Math.round((x2 - x1) * n)
+        for (let j = 0; j < jl; ++j) {
+            const t = j / jl
+            array[k * 4 + 3] = Math.max(0, spline(a0, a1, a2, a3, t, 0.5) * 255)
+            scale.colorToArray(k / 255, array, k * 4)
+            ++k
+        }
+    }
+
+    const textureImage = { array, width: 256, height: 1 }
+    if (texture) {
+        ValueCell.update(texture, textureImage)
+        return texture
+    } else {
+        return ValueCell.create(textureImage)
+    }
+}

+ 8 - 0
src/mol-geo/representation/structure/index.ts

@@ -14,6 +14,7 @@ import { Mesh } from '../../geometry/mesh/mesh';
 import { Points } from '../../geometry/points/points';
 import { Lines } from '../../geometry/lines/lines';
 import { SelectParam, paramDefaultValues } from 'mol-view/parameter';
+import { DirectVolume } from '../../geometry/direct-volume/direct-volume';
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
@@ -46,6 +47,13 @@ export const StructureLinesParams = {
 export const DefaultStructureLinesProps = paramDefaultValues(StructureLinesParams)
 export type StructureLinesProps = typeof DefaultStructureLinesProps
 
+export const StructureDirectVolumeParams = {
+    ...DirectVolume.Params,
+    ...StructureParams,
+}
+export const DefaultStructureDirectVolumeProps = paramDefaultValues(StructureDirectVolumeParams)
+export type StructureDirectVolumeProps = typeof DefaultStructureDirectVolumeProps
+
 export interface VisualUpdateState {
     updateTransform: boolean
     updateColor: boolean

+ 11 - 3
src/mol-geo/representation/structure/representation/molecular-surface.ts

@@ -14,14 +14,18 @@ import { PickingId } from '../../../geometry/picking';
 import { Task } from 'mol-task';
 import { GaussianWireframeVisual, GaussianWireframeParams } from '../visual/gaussian-surface-wireframe';
 import { getQualityProps } from '../../util';
-import { paramDefaultValues, MultiSelectParam } from 'mol-view/parameter';
+import { paramDefaultValues, MultiSelectParam, SelectParam } from 'mol-view/parameter';
+import { GaussianDensityVolumeParams, GaussianDensityVolumeVisual } from '../visual/gaussian-density-volume';
+import { SizeThemeName, SizeThemeOptions } from 'mol-view/theme/size';
 
-const VisualOptions = [['surface', 'Surface'], ['wireframe', 'Wireframe']] as [string, string][]
+const VisualOptions = [['surface', 'Surface'], ['wireframe', 'Wireframe'], ['volume', 'Volume']] as [string, string][]
 
 export const MolecularSurfaceParams = {
     ...GaussianSurfaceParams,
     ...GaussianWireframeParams,
+    ...GaussianDensityVolumeParams,
 
+    sizeTheme: SelectParam<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
     visuals: MultiSelectParam<string>('Visuals', '', ['surface'], VisualOptions)
 }
 export const DefaultMolecularSurfaceProps = paramDefaultValues(MolecularSurfaceParams)
@@ -34,6 +38,7 @@ export function MolecularSurfaceRepresentation(): MolecularSurfaceRepresentation
     let currentStructure: Structure
     const gaussianSurfaceRepr = UnitsRepresentation('Gaussian surface', GaussianSurfaceVisual)
     const gaussianWireframeRepr = UnitsRepresentation('Gaussian wireframe', GaussianWireframeVisual)
+    const gaussianVolumeRepr = UnitsRepresentation('Gaussian volume', GaussianDensityVolumeVisual)
     return {
         label: 'Molecular Surface',
         params: MolecularSurfaceParams,
@@ -41,10 +46,11 @@ export function MolecularSurfaceRepresentation(): MolecularSurfaceRepresentation
             const renderObjects = []
             if (currentProps.visuals.includes('surface')) renderObjects.push(...gaussianSurfaceRepr.renderObjects)
             if (currentProps.visuals.includes('wireframe')) renderObjects.push(...gaussianWireframeRepr.renderObjects)
+            if (currentProps.visuals.includes('volume')) renderObjects.push(...gaussianVolumeRepr.renderObjects)
             return renderObjects
         },
         get props() {
-            return { ...gaussianSurfaceRepr.props, ...gaussianWireframeRepr.props, visuals: currentProps.visuals }
+            return { ...gaussianSurfaceRepr.props, ...gaussianWireframeRepr.props, ...gaussianVolumeRepr.props, visuals: currentProps.visuals }
         },
         createOrUpdate: (props: Partial<MolecularSurfaceProps> = {}, structure?: Structure) => {
             if (structure) currentStructure = structure
@@ -53,6 +59,7 @@ export function MolecularSurfaceRepresentation(): MolecularSurfaceRepresentation
             return Task.create('Creating MolecularSurfaceRepresentation', async ctx => {
                 if (currentProps.visuals.includes('surface')) await gaussianSurfaceRepr.createOrUpdate(currentProps, currentStructure).runInContext(ctx)
                 if (currentProps.visuals.includes('wireframe')) await gaussianWireframeRepr.createOrUpdate(currentProps, currentStructure).runInContext(ctx)
+                if (currentProps.visuals.includes('volume')) await gaussianVolumeRepr.createOrUpdate(currentProps, currentStructure).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {
@@ -64,6 +71,7 @@ export function MolecularSurfaceRepresentation(): MolecularSurfaceRepresentation
         destroy() {
             gaussianSurfaceRepr.destroy()
             gaussianWireframeRepr.destroy()
+            gaussianVolumeRepr.destroy()
         }
     }
 }

+ 135 - 3
src/mol-geo/representation/structure/units-visual.ts

@@ -8,15 +8,15 @@
 
 import { Unit, Structure } from 'mol-model/structure';
 import { RepresentationProps, Visual } from '../';
-import { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams } from '.';
+import { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams } from '.';
 import { RuntimeContext } from 'mol-task';
 import { PickingId } from '../../geometry/picking';
 import { LocationIterator } from '../../util/location-iterator';
 import { Mesh } from '../../geometry/mesh/mesh';
 import { MarkerAction, applyMarkerAction, createMarkers } from '../../geometry/marker-data';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
-import { MeshRenderObject, PointsRenderObject, LinesRenderObject } from 'mol-gl/render-object';
-import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject } from './visual/util/common';
+import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object';
+import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject } from './visual/util/common';
 import { deepEqual, ValueCell, UUID } from 'mol-util';
 import { Interval } from 'mol-data/int';
 import { Points } from '../../geometry/points/points';
@@ -25,6 +25,7 @@ import { createColors, ColorProps } from '../../geometry/color-data';
 import { createSizes, SizeProps } from '../../geometry/size-data';
 import { Lines } from '../../geometry/lines/lines';
 import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter';
+import { DirectVolume } from '../../geometry/direct-volume/direct-volume';
 
 export const UnitKindInfo = {
     'atomic': {},
@@ -526,4 +527,135 @@ export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesV
             renderObject = undefined
         }
     }
+}
+
+// direct-volume
+
+export const UnitsDirectVolumeParams = {
+    ...StructureDirectVolumeParams,
+    unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions),
+}
+export const DefaultUnitsDirectVolumeProps = paramDefaultValues(UnitsDirectVolumeParams)
+export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps
+
+export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> {
+    defaultProps: P
+    createDirectVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, directVolume?: DirectVolume): Promise<DirectVolume>
+    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
+    setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
+}
+
+export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> {
+    const { defaultProps, createDirectVolume, createLocationIterator, getLoci, setUpdateState } = builder
+    const updateState = VisualUpdateState.create()
+
+    let renderObject: DirectVolumeRenderObject | undefined
+    let currentProps: P
+    let directVolume: DirectVolume
+    let currentGroup: Unit.SymmetryGroup
+    let currentStructure: Structure
+    let locationIt: LocationIterator
+    let currentConformationId: UUID
+
+    async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
+        currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
+        currentGroup = group
+
+        const unit = group.units[0]
+        currentConformationId = Unit.conformationId(unit)
+        directVolume = includesUnitKind(currentProps.unitKinds, unit)
+            ? await createDirectVolume(ctx, unit, currentStructure, currentProps, directVolume)
+            : DirectVolume.createEmpty(directVolume)
+
+        // TODO create empty location iterator when not in unitKinds
+        locationIt = createLocationIterator(group)
+        renderObject = await createUnitsDirectVolumeRenderObject(ctx, group, directVolume, locationIt, currentProps)
+    }
+
+    async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
+        if (!renderObject) return
+
+        const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
+        const unit = currentGroup.units[0]
+
+        locationIt.reset()
+        VisualUpdateState.reset(updateState)
+        setUpdateState(updateState, newProps, currentProps)
+
+        const newConformationId = Unit.conformationId(unit)
+        if (newConformationId !== currentConformationId) {
+            currentConformationId = newConformationId
+            updateState.createGeometry = true
+        }
+
+        if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
+
+        if (sizeChanged(currentProps, newProps)) updateState.createGeometry = true
+        if (colorChanged(currentProps, newProps)) updateState.updateColor = true
+        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
+
+        //
+
+        // if (updateState.updateTransform) {
+        //     locationIt = createLocationIterator(currentGroup)
+        //     const { instanceCount, groupCount } = locationIt
+        //     createUnitsTransform(currentGroup, renderObject.values)
+        //     createMarkers(instanceCount * groupCount, renderObject.values)
+        //     updateState.updateColor = true
+        // }
+
+        if (updateState.createGeometry) {
+            directVolume = includesUnitKind(newProps.unitKinds, unit)
+                ? await createDirectVolume(ctx, unit, currentStructure, newProps, directVolume)
+                : DirectVolume.createEmpty(directVolume)
+            updateState.updateColor = true
+        }
+
+        // if (updateState.updateColor) {
+        //     await createColors(ctx, locationIt, newProps, renderObject.values)
+        // }
+
+        // TODO why do I need to cast here?
+        DirectVolume.updateValues(renderObject.values, newProps as UnitsDirectVolumeProps)
+        updateRenderableState(renderObject.state, newProps as UnitsDirectVolumeProps)
+
+        currentProps = newProps
+    }
+
+    return {
+        get renderObject () { return renderObject },
+        async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
+            if (structureGroup) currentStructure = structureGroup.structure
+            const group = structureGroup ? structureGroup.group : undefined
+            if (!group && !currentGroup) {
+                throw new Error('missing group')
+            } else if (group && (!currentGroup || !renderObject)) {
+                // console.log('unit-visual first create')
+                await create(ctx, group, props)
+            } else if (group && group.hashCode !== currentGroup.hashCode) {
+                // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
+                await create(ctx, group, props)
+            } else {
+                // console.log('unit-visual update')
+                if (group && !sameGroupConformation(group, currentGroup)) {
+                    // console.log('unit-visual new conformation')
+                    currentGroup = group
+                }
+                await update(ctx, props)
+            }
+        },
+        getLoci(pickingId: PickingId) {
+            return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            // TODO
+            return false
+        },
+        destroy() {
+            // TODO
+            renderObject = undefined
+        }
+    }
 }

+ 75 - 0
src/mol-geo/representation/structure/visual/gaussian-density-volume.ts

@@ -0,0 +1,75 @@
+/**
+ * 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, VisualUpdateState } from '..';
+import { RuntimeContext } from 'mol-task'
+import { UnitsDirectVolumeVisual, UnitsDirectVolumeParams } from '../units-visual';
+import { StructureElementIterator, getElementLoci, markElement } from './util/element';
+import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure/structure/unit/gaussian-density';
+import { paramDefaultValues } from 'mol-view/parameter';
+import { DirectVolume } from '../../../geometry/direct-volume/direct-volume';
+import { ValueCell } from 'mol-util';
+import { Vec3, Vec2 } from 'mol-math/linear-algebra';
+
+async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
+    const p = { ...props, useGpu: true, ignoreCache: true }
+    const { transform, renderTarget, bbox, gridDimension } = await unit.computeGaussianDensity(p, ctx)
+    if (!renderTarget || !bbox || !gridDimension) throw new Error('missing renderTarget and/or boundingBox and/or gridDimension')
+
+    if (directVolume) {
+        ValueCell.update(directVolume.gridDimension, gridDimension)
+        ValueCell.update(directVolume.gridTexture, renderTarget.image)
+        ValueCell.update(directVolume.gridTextureDim, Vec2.set(directVolume.gridTextureDim.ref.value, renderTarget.width, renderTarget.height))
+        ValueCell.update(directVolume.bboxMin, bbox.min)
+        ValueCell.update(directVolume.bboxMax, bbox.max)
+        ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
+        ValueCell.update(directVolume.transform, transform)
+    } else {
+        directVolume = {
+            kind: 'direct-volume' as 'direct-volume',
+            gridDimension: ValueCell.create(gridDimension),
+            gridTexture: ValueCell.create(renderTarget.image),
+            gridTextureDim: ValueCell.create(Vec2.create(renderTarget.width, renderTarget.height)),
+            bboxMin: ValueCell.create(bbox.min),
+            bboxMax: ValueCell.create(bbox.max),
+            bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
+            transform: ValueCell.create(transform),
+        }
+    }
+
+    console.log('gridDimension', gridDimension)
+    console.log('gridTextureDim', renderTarget.width, renderTarget.height)
+    console.log('boundingBox', bbox)
+    console.log('transform', transform)
+
+    return directVolume;
+}
+
+export const GaussianDensityVolumeParams = {
+    ...UnitsDirectVolumeParams,
+    ...GaussianDensityParams,
+}
+export const DefaultGaussianDensityVolumeProps = paramDefaultValues(GaussianDensityVolumeParams)
+export type GaussianDensityVolumeProps = typeof DefaultGaussianDensityVolumeProps
+
+export function GaussianDensityVolumeVisual(): UnitsVisual<GaussianDensityVolumeProps> {
+    return UnitsDirectVolumeVisual<GaussianDensityVolumeProps>({
+        defaultProps: DefaultGaussianDensityVolumeProps,
+        createDirectVolume: createGaussianDensityVolume,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement,
+        setUpdateState: (state: VisualUpdateState, newProps: GaussianDensityVolumeProps, currentProps: GaussianDensityVolumeProps) => {
+            if (newProps.resolution !== currentProps.resolution) state.createGeometry = true
+            if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
+            if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true
+            if (newProps.useGpu !== currentProps.useGpu) state.createGeometry = true
+            if (newProps.readSlices !== currentProps.readSlices) state.createGeometry = true
+            if (newProps.ignoreCache !== currentProps.ignoreCache) state.createGeometry = true
+        }
+    })
+}

+ 14 - 1
src/mol-geo/representation/structure/visual/util/common.ts

@@ -8,13 +8,14 @@ import { Unit, Structure } from 'mol-model/structure';
 import { LocationIterator } from '../../../../util/location-iterator';
 import { Mesh } from '../../../../geometry/mesh/mesh';
 import { StructureProps } from '../..';
-import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject } from 'mol-gl/render-object';
+import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject, createDirectVolumeRenderObject } from 'mol-gl/render-object';
 import { RuntimeContext } from 'mol-task';
 import { TransformData, createIdentityTransform, createTransform } from '../../../../geometry/transform-data';
 import { Points } from '../../../../geometry/points/points';
 import { createRenderableState } from '../../../../geometry/geometry';
 import { Mat4 } from 'mol-math/linear-algebra';
 import { Lines } from '../../../../geometry/lines/lines';
+import { DirectVolume } from '../../../../geometry/direct-volume/direct-volume';
 
 export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) {
     const unitCount = units.length
@@ -65,4 +66,16 @@ export async function createUnitsLinesRenderObject(ctx: RuntimeContext, group: U
     console.log('values', values)
     const state = createRenderableState(props)
     return createLinesRenderObject(values, state)
+}
+
+// direct-volume
+
+type StructureDirectVolumeProps = DirectVolume.Props & StructureProps
+
+export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume, locationIt: LocationIterator, props: StructureDirectVolumeProps) {
+    // TODO transform support
+    const transform = createUnitsTransform(group)
+    const values = await DirectVolume.createValues(ctx, directVolume, props)
+    const state = createRenderableState(props)
+    return createDirectVolumeRenderObject(values, state)
 }

+ 7 - 1
src/mol-gl/render-object.ts

@@ -9,6 +9,7 @@ import { RenderableValues } from './renderable/schema';
 import { idFactory } from 'mol-util/id-factory';
 import { Context } from './webgl/context';
 import { GaussianDensityValues, GaussianDensityRenderable } from './renderable/gaussian-density';
+import { DirectVolumeValues, DirectVolumeRenderable } from './renderable/direct-volume';
 
 const getNextId = idFactory(0, 0x7FFFFFFF)
 
@@ -17,7 +18,8 @@ export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', value
 export interface PointsRenderObject extends BaseRenderObject { type: 'points', values: PointsValues }
 export interface LinesRenderObject extends BaseRenderObject { type: 'lines', values: LinesValues }
 export interface GaussianDensityRenderObject extends BaseRenderObject { type: 'gaussian-density', values: GaussianDensityValues }
-export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject
+export interface DirectVolumeRenderObject extends BaseRenderObject { type: 'direct-volume', values: DirectVolumeValues }
+export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject | DirectVolumeRenderObject
 
 export function createMeshRenderObject(values: MeshValues, state: RenderableState): MeshRenderObject {
     return { id: getNextId(), type: 'mesh', values, state }
@@ -31,6 +33,9 @@ export function createLinesRenderObject(values: LinesValues, state: RenderableSt
 export function createGaussianDensityRenderObject(values: GaussianDensityValues, state: RenderableState): GaussianDensityRenderObject {
     return { id: getNextId(), type: 'gaussian-density', values, state }
 }
+export function createDirectVolumeRenderObject(values: DirectVolumeValues, state: RenderableState): DirectVolumeRenderObject {
+    return { id: getNextId(), type: 'direct-volume', values, state }
+}
 
 export function createRenderable(ctx: Context, o: RenderObject): Renderable<any> {
     switch (o.type) {
@@ -38,5 +43,6 @@ export function createRenderable(ctx: Context, o: RenderObject): Renderable<any>
         case 'points': return PointsRenderable(ctx, o.id, o.values, o.state)
         case 'lines': return LinesRenderable(ctx, o.id, o.values, o.state)
         case 'gaussian-density': return GaussianDensityRenderable(ctx, o.id, o.values, o.state)
+        case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values, o.state)
     }
 }

+ 50 - 0
src/mol-gl/renderable/direct-volume.ts

@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Renderable, RenderableState, createRenderable } from '../renderable'
+import { Context } from '../webgl/context';
+import { createRenderItem } from '../webgl/render-item';
+import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema, TextureSpec, ValueSpec, ElementsSpec, DefineSpec } from './schema';
+import { DirectVolumeShaderCode } from '../shader-code';
+import { ValueCell } from 'mol-util';
+
+export const DirectVolumeSchema = {
+    drawCount: ValueSpec('number'),
+    instanceCount: ValueSpec('number'),
+
+    aPosition: AttributeSpec('float32', 3, 0),
+    elements: ElementsSpec('uint32'),
+
+    uAlpha: UniformSpec('f'),
+    dUseFog: DefineSpec('boolean'),
+
+    uIsoValue: UniformSpec('f'),
+    uBboxMin: UniformSpec('v3'),
+    uBboxMax: UniformSpec('v3'),
+    uBboxSize: UniformSpec('v3'),
+    uTransform: UniformSpec('m4'),
+    uGridDim: UniformSpec('v3'),
+    uGridTexDim: UniformSpec('v2'),
+    tGridTex: TextureSpec('rgba', 'ubyte', 'linear'),
+    dRenderMode: DefineSpec('string', ['isosurface', 'volume']),
+    tTransferTex: TextureSpec('rgba', 'ubyte', 'linear'),
+}
+export type DirectVolumeSchema = typeof DirectVolumeSchema
+export type DirectVolumeValues = Values<DirectVolumeSchema>
+
+export function DirectVolumeRenderable(ctx: Context, id: number, values: DirectVolumeValues, state: RenderableState): Renderable<DirectVolumeValues> {
+    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema }
+    const internalValues = {
+        uObjectId: ValueCell.create(id)
+    }
+    const shaderCode = DirectVolumeShaderCode
+    const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues })
+    const renderable = createRenderable(renderItem, values, state);
+
+    Object.defineProperty(renderable, 'opaque', { get: () => false });
+
+    return renderable
+}

+ 12 - 1
src/mol-gl/renderer.ts

@@ -68,9 +68,17 @@ namespace Renderer {
         }
         setClearColor(clearColor)
 
+        const view = Mat4.clone(camera.view)
+        const invView = Mat4.invert(Mat4.identity(), view)
+        const modelView = Mat4.clone(camera.view)
+        const invModelView = Mat4.invert(Mat4.identity(), modelView)
+
         const globalUniforms: GlobalUniformValues = {
             uModel: ValueCell.create(Mat4.identity()),
-            uView: ValueCell.create(Mat4.clone(camera.view)),
+            uView: ValueCell.create(camera.view),
+            uInvView: ValueCell.create(invView),
+            uModelView: ValueCell.create(modelView),
+            uInvModelView: ValueCell.create(invModelView),
             uProjection: ValueCell.create(Mat4.clone(camera.projection)),
 
             uPixelRatio: ValueCell.create(ctx.pixelRatio),
@@ -131,6 +139,9 @@ namespace Renderer {
         const render = (scene: Scene, variant: RenderVariant) => {
             ValueCell.update(globalUniforms.uModel, scene.view)
             ValueCell.update(globalUniforms.uView, camera.view)
+            ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view))
+            ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, scene.view, camera.view))
+            ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView))
             ValueCell.update(globalUniforms.uProjection, camera.projection)
 
             ValueCell.update(globalUniforms.uFogFar, camera.fogFar)

+ 5 - 0
src/mol-gl/shader-code.ts

@@ -44,6 +44,11 @@ export const GaussianDensityShaderCode = ShaderCode(
     require('mol-gl/shader/gaussian-density.frag')
 )
 
+export const DirectVolumeShaderCode = ShaderCode(
+    require('mol-gl/shader/direct-volume.vert'),
+    require('mol-gl/shader/direct-volume.frag')
+)
+
 export type ShaderDefines = {
     [k: string]: ValueCell<DefineType>
 }

+ 270 - 0
src/mol-gl/shader/direct-volume.frag

@@ -0,0 +1,270 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Michael Krone <michael.krone@uni-tuebingen.de>
+ */
+
+precision highp float;
+
+varying vec3 unitCoord;
+varying vec3 origPos;
+
+uniform float uAlpha;
+uniform mat4 uInvView;
+uniform mat4 uModelView;
+uniform mat4 uInvModelView;
+uniform float uIsoValue;
+uniform vec3 uBboxMin;
+uniform vec3 uBboxMax;
+uniform vec3 uBboxSize;
+uniform sampler2D tGridTex;
+uniform vec3 uGridDim;
+uniform vec2 uGridTexDim;
+uniform sampler2D tTransferTex;
+
+// float uIsoValue = exp(-1.5);
+// float uIsoValue = 0.7;
+
+varying vec4 vNearPos;
+varying vec4 vFarPos;
+varying vec3 vPosition;
+
+#pragma glslify: transpose = require(./utils/transpose.glsl)
+
+vec3 extractCameraPos(const in mat4 modelView) {
+    // Get the 3 basis vector planes at the camera origin and transform them into model space.
+    //
+    // NOTE: Planes have to be transformed by the inverse transpose of a matrix
+    //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
+    //
+    //       So for a transform to model space we need to do:
+    //            inverse(transpose(inverse(MV)))
+    //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
+    //
+    // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
+    //
+    mat4 modelViewT = transpose(modelView);
+
+    // Get plane normals
+    vec3 n1 = vec3(modelViewT[0]);
+    vec3 n2 = vec3(modelViewT[1]);
+    vec3 n3 = vec3(modelViewT[2]);
+
+    // Get plane distances
+    float d1 = modelViewT[0].w;
+    float d2 = modelViewT[1].w;
+    float d3 = modelViewT[2].w;
+
+    // Get the intersection of these 3 planes
+    // (uisng math from RealTime Collision Detection by Christer Ericson)
+    vec3 n2n3 = cross(n2, n3);
+    float denom = dot(n1, n2n3);
+
+    vec3 top = (n2n3 * d1) + cross(n1, (d3 * n2) - (d2 * n3));
+    return top / -denom;
+}
+
+// TODO workaround due to some kind of GPU bug
+float myMod(float a, float b) {
+    return a - b * float(int(a) / int(b));
+}
+float myDiv(float a, float b) {
+    return float(int(a) / int(b));
+}
+
+vec3 palette(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
+    return a + b * cos(6.28318 * (c * t + d));
+}
+
+vec3 palette1(in float t) {
+    return palette(t, vec3(0.5,0.5,0.5),vec3(0.5,0.5,0.5),vec3(1.0,1.0,1.0),vec3(0.0,0.10,0.20));
+}
+
+vec4 textureVal(vec3 pos) {
+    float zSlice0 = floor(pos.z * uGridDim.z);
+    float column0 = myMod(zSlice0 * uGridDim.x, uGridTexDim.x) / uGridDim.x;
+    float row0 = floor(myDiv((zSlice0 * uGridDim.x), uGridTexDim.x));
+    vec2 coord0 = (vec2(column0 * uGridDim.x, row0 * uGridDim.y) + (pos.xy * uGridDim.xy)) / uGridTexDim;
+    vec4 color0 = texture2D(tGridTex, coord0);
+
+    float zSlice1 = zSlice0 + 1.0;
+    float column1 = myMod(zSlice1 * uGridDim.x, uGridTexDim.x) / uGridDim.x;
+    float row1 = floor(myDiv((zSlice1 * uGridDim.x), uGridTexDim.x));
+    vec2 coord1 = (vec2(column1 * uGridDim.x, row1 * uGridDim.y) + (pos.xy * uGridDim.xy)) / uGridTexDim;
+    vec4 color1 = texture2D(tGridTex, coord1);
+
+    float delta0 = abs((pos.z * uGridDim.z) - zSlice0);
+    return mix(color0, color1, delta0);
+}
+
+vec4 transferFunction(float value) {
+    return texture2D(tTransferTex, vec2(value, 0.0));
+}
+
+// vec4 textureVal(vec3 pos) {
+//     vec4 color0 = textureVal1(pos);
+//     vec4 color1 = textureVal1(vec3(pos.xy, pos.z + 1.1 / uGridDim.z));
+//     float delta0 = abs((pos.z * uGridDim.z) - floor(pos.z * uGridDim.z));
+//     vec3 tmpCol = vec3(0.45, 0.55, 0.8);
+
+//     // coord = pos.xy;
+//     // coord.x += col1;
+//     // coord.x /= uGridTexDim.x;
+//     // coord.y += row1;
+//     // coord.y /= uGridTexDim.y;
+//     // vec4 color1 = texture2D(tGridTex, coord);
+//     // vec4 color1 = texture2D(tGridTex, unitCoordToGridCoord(pos));
+
+//     return vec4(tmpCol.rgb, mix(color0.a, color1.a, delta0));
+//     // return vec4(tmpCol.rgb, color0.a);
+//     // return vec4(mix(color0, color1, delta0).w, tmpCol.x, tmpCol.y, tmpCol.z);
+//     // return vec4(color0.x, tmpCol.x, tmpCol.y, tmpCol.z);
+//     // return vec4(color0.xyz, 1.0);
+//     // return vec4(color0.xyz, 1.0);
+// }
+
+vec3 scaleVol = vec3(1.0) / uGridDim;
+const float gradOffset = 0.5;
+vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0);
+vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0);
+vec3 dz = vec3(0.0, 0.0, gradOffset * scaleVol.z);
+
+vec3 color = vec3(0.45, 0.55, 0.8);
+
+vec4 raymarch(vec3 cameraPos) {
+    vec3 pos = unitCoord;
+    float prevValue = -1.0;
+    float value = 0.0;
+    float MAX_STEPS_F = max(max(uGridDim.x, uGridDim.y), uGridDim.z);
+    // int MAX_STEPS = 2 * int(length(vec3(imgresx, imgresy, imgresz)));
+    // TODO define this from outside (recompile shader per data set)
+    const int MAX_STEPS = 300;
+    float stepSize = 1.0 / MAX_STEPS_F;
+    vec4 src = vec4(0.0);
+    vec4 dst = vec4(0.0);
+
+    vec3 rayDir = normalize(origPos - cameraPos);
+    // rayDir = normalize(vec3(1.0, 1.0, 0.0));
+    // return vec4(rayDir, 0.5);
+    vec3 isoPos;
+    float tmp;
+    vec3 gradient = vec3(1.0);
+    vec3 step = rayDir * (1.0 / uGridDim) * 0.5;
+
+    // dst = vec4(textureVal(vec3(pos.xy, 0.6)).xyz, 0.5);
+    // vec2 foo = (vec2(5.0 * uGridDim.x, 5.0 * uGridDim.y) + (pos.xy * uGridDim.xy)) / uGridTexDim;
+    // dst = texture2D(tGridTex, foo);
+    // dst = texture2D(tGridTex, unitCoord.xy);
+    // dst.xyz = pos;
+    // return mix(dst, vec4(1.0, 0.0, 0.0, 1.0), 0.5);
+
+    for(int i = 0; i < MAX_STEPS; ++i){
+        if( pos.x <= 1.0 && pos.y <= 1.0 && pos.z <= 1.0 && pos.x >= 0.0 && pos.y >= 0.0 && pos.z >= 0.0) {
+            value = textureVal(pos).a; // current voxel value
+        } else {
+            break;
+        }
+
+        #if defined(dRenderMode_volume)
+            // src = texture1D(transferRGBASampler, scalarData);
+            src = transferFunction(value);
+            // src.rgb = palette1(value);
+            // src.a = 1.0 - pow(1.0 - src.a, 0.5);
+            src.rgb *= src.a;
+            dst = (1.0 - dst.a) * src + dst; // standard blending
+        #endif
+
+        #if defined(dRenderMode_isosurface)
+            if(prevValue > 0.0 && ( // there was a prev Value
+                (prevValue < uIsoValue && value > uIsoValue) || // entering isosurface
+                (prevValue > uIsoValue && value < uIsoValue) // leaving isosurface
+            )) {
+                tmp = ((prevValue - uIsoValue) / ((prevValue - uIsoValue) - (value - uIsoValue)));
+                isoPos = mix(pos - step, pos, tmp);
+
+                // compute gradient by central differences
+                gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
+                gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a;
+                gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a;
+                gradient = normalize(gradient);
+
+                float d = float(dot(gradient, rayDir) > 0.0);
+                gradient = (2.0 * d - 1.0) * gradient;
+
+                src.rgb = color.rgb * abs(dot(gradient, normalize(cameraPos)));
+                src.a = uAlpha;
+
+                // draw interior darker
+                if( (prevValue - uIsoValue) < 0.0 )
+                    src.rgb *= 0.5;
+
+                src.rgb *= src.a;
+                dst = (1.0 - dst.a) * src + dst; // standard blending
+                if(dst.a >= 1.0) {
+                    break;
+                }
+            }
+            prevValue = value;
+        #endif
+
+        pos += step;
+    }
+    return dst;
+}
+
+void main () {
+    // vec3 cameraPos = uInvView[3].xyz / uInvView[3].w;
+    vec3 cameraPos = extractCameraPos(uModelView);
+    // vec3 cameraPos = vec3(10.0, 0.0, 0.0);
+    gl_FragColor = raymarch(cameraPos);
+    if (length(gl_FragColor.rgb) < 0.00001) discard;
+    #if defined(dRenderMode_volume)
+        gl_FragColor.a = uAlpha;
+    #endif
+    // gl_FragColor = vec4(unitCoord, 1.0);
+    // gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
+}
+
+
+// const float relativeStepSize = 1.0;
+// vec3 u_size = uGridDim;
+
+// void main () {
+//     // Normalize clipping plane info
+//     vec3 farPos = vFarPos.xyz / vFarPos.w;
+//     vec3 nearPos = vNearPos.xyz / vNearPos.w;
+//     // Calculate unit vector pointing in the view direction through this fragment.
+//     vec3 viewRay = normalize(nearPos.xyz - farPos.xyz);
+
+//     // Compute the (negative) distance to the front surface or near clipping plane.
+//     // v_position is the back face of the cuboid, so the initial distance calculated in the dot
+//     // product below is the distance from near clip plane to the back of the cuboid
+//     float distance = dot(nearPos - vPosition, viewRay);
+//     distance = max(distance, min((-0.5 - vPosition.x) / viewRay.x, (u_size.x - 0.5 - vPosition.x) / viewRay.x));
+//     distance = max(distance, min((-0.5 - vPosition.y) / viewRay.y, (u_size.y - 0.5 - vPosition.y) / viewRay.y));
+//     distance = max(distance, min((-0.5 - vPosition.z) / viewRay.z, (u_size.z - 0.5 - vPosition.z) / viewRay.z));
+//     // Now we have the starting position on the front surface
+//     vec3 front = vPosition + viewRay * distance;
+//     // Decide how many steps to take
+//     int nsteps = int(-distance / relativeStepSize + 0.5);
+//     // if (nsteps < 1)
+//         // discard;
+//     // Get starting location and step vector in texture coordinates
+//     vec3 step = ((vPosition - front) / u_size) / float(nsteps);
+//     vec3 startLoc = front / u_size;
+//     // For testing: show the number of steps. This helps to establish
+//     // whether the rays are correctly oriented
+//     gl_FragColor = vec4(0.0, float(nsteps) / 1.0 / u_size.x, 1.0, 1.0);
+//     gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+//     return;
+
+//     // if (u_renderstyle == 0)
+//         // cast_mip(startLoc, step, nsteps, viewRay);
+//     // else if (u_renderstyle == 1)
+//     //     cast_iso(start_loc, step, nsteps, view_ray);
+
+
+//     if (gl_FragColor.a < 0.05)
+//         discard;
+// }

+ 55 - 0
src/mol-gl/shader/direct-volume.vert

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Michael Krone <michael.krone@uni-tuebingen.de>
+ */
+
+precision highp float;
+
+attribute vec3 aPosition;
+
+varying vec3 unitCoord;
+varying vec3 origPos;
+
+uniform vec3 uBboxSize;
+uniform vec3 uBboxMin;
+uniform vec3 uBboxMax;
+uniform mat4 uTransform;
+
+uniform mat4 uInvView;
+uniform mat4 uModelView;
+uniform mat4 uInvModelView;
+uniform mat4 uProjection, uView, uModel;
+
+varying vec4 vNearPos;
+varying vec4 vFarPos;
+varying vec3 vPosition;
+
+void main() {
+    unitCoord = aPosition + vec3(0.5);
+    vec4 mvPosition = uView * uModel * vec4(unitCoord * uBboxSize + uBboxMin, 1.0);
+    // vec4 mvPosition = vec4(unitCoord * uBboxSize + uBboxMin, 1.0);
+    // origPos = mvPosition.xyz;
+    origPos = unitCoord * uBboxSize + uBboxMin;
+    gl_Position = uProjection * mvPosition;
+}
+
+// void main() {
+//     // Project local vertex coordinate to camera position. Then do a step
+//     // backward (in cam coords) to the near clipping plane, and project back. Do
+//     // the same for the far clipping plane. This gives us all the information we
+//     // need to calculate the ray and truncate it to the viewing cone.
+//     vec3 position = aPosition * uBboxSize + uBboxMin;
+//     vec4 position4 = vec4(position, 1.0);
+//     vec4 posInCam = uView * position4;
+//     // Intersection of ray and near clipping plane (z = -1 in clip coords)
+//     posInCam.z = -posInCam.w;
+//     vNearPos = uInvView * posInCam;
+//     // Intersection of ray and far clipping plane (z = +1 in clip coords)
+//     posInCam.z = posInCam.w;
+//     vFarPos = uInvView * posInCam;
+//     // Set varyings and output pos
+//     vPosition = position;
+//     gl_Position = uProjection * uModelView * position4;
+// }

+ 13 - 2
src/mol-math/geometry/common.ts

@@ -2,10 +2,13 @@
  * Copyright (c) 2018 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>
  */
 
 import { OrderedSet } from 'mol-data/int'
-import { Mat4, Tensor } from '../linear-algebra';
+import { Mat4, Tensor, Vec3 } from '../linear-algebra';
+import { RenderTarget } from 'mol-gl/webgl/render-target';
+import { Box3D } from '../geometry';
 
 export interface PositionData {
     x: ArrayLike<number>,
@@ -17,4 +20,12 @@ export interface PositionData {
     radius?: ArrayLike<number>
 }
 
-export type DensityData = { transform: Mat4, field: Tensor, idField: Tensor }
+export type DensityData = {
+    transform: Mat4,
+    field: Tensor,
+    idField: Tensor,
+
+    renderTarget?: RenderTarget,
+    bbox?: Box3D,
+    gridDimension?: Vec3
+}

+ 6 - 0
src/mol-math/interpolate.ts

@@ -29,6 +29,7 @@ export function lerp (start: number, stop: number, alpha: number) {
     return start + (stop - start) * alpha
 }
 
+/** Catmul-Rom spline */
 export function spline (p0: number, p1: number, p2: number, p3: number, t: number, tension: number) {
     const v0 = (p2 - p0) * tension
     const v1 = (p3 - p1) * tension
@@ -37,6 +38,11 @@ export function spline (p0: number, p1: number, p2: number, p3: number, t: numbe
     return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1
 }
 
+export function quadraticBezier(p0: number, p1: number, p2: number, t: number) {
+    const k = 1 - t
+    return (k * k * p0) + (2 * k * t * p1) + (t * t * p2)
+}
+
 export function smoothstep (min: number, max: number, x: number) {
     x = saturate(normalize(x, min, max))
     return x * x * (3 - 2 * x)