Browse Source

Merge branch 'master' into state_representation

David Sehnal 6 years ago
parent
commit
d47c2ebeb0
56 changed files with 1025 additions and 1103 deletions
  1. 24 3
      src/apps/canvas/app.ts
  2. 22 4
      src/apps/canvas/component/app.tsx
  3. 10 1
      src/apps/canvas/index.ts
  4. 1 1
      src/apps/canvas/structure-view.ts
  5. 8 1
      src/apps/canvas/util.ts
  6. 64 128
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  7. 3 5
      src/mol-geo/geometry/geometry.ts
  8. 22 0
      src/mol-geo/geometry/mesh/mesh.ts
  9. 1 1
      src/mol-geo/geometry/picking.ts
  10. 65 26
      src/mol-geo/representation/structure/complex-visual.ts
  11. 2 3
      src/mol-geo/representation/structure/index.ts
  12. 1 1
      src/mol-geo/representation/structure/representation/ball-and-stick.ts
  13. 17 3
      src/mol-geo/representation/structure/representation/molecular-surface.ts
  14. 89 454
      src/mol-geo/representation/structure/units-visual.ts
  15. 1 1
      src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts
  16. 1 1
      src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts
  17. 1 1
      src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts
  18. 3 7
      src/mol-geo/representation/structure/visual/gaussian-density-volume.ts
  19. 2 44
      src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts
  20. 1 1
      src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts
  21. 42 10
      src/mol-geo/representation/structure/visual/util/common.ts
  22. 21 22
      src/mol-geo/representation/volume/direct-volume.ts
  23. 6 11
      src/mol-gl/render-object.ts
  24. 26 41
      src/mol-gl/renderable/direct-volume.ts
  25. 6 2
      src/mol-gl/renderable/gaussian-density.ts
  26. 2 1
      src/mol-gl/renderable/schema.ts
  27. 41 14
      src/mol-gl/shader-code.ts
  28. 2 2
      src/mol-gl/shader/chunks/apply-marker-color.glsl
  29. 3 3
      src/mol-gl/shader/chunks/assign-color-varying.glsl
  30. 1 1
      src/mol-gl/shader/chunks/color-vert-params.glsl
  31. 111 52
      src/mol-gl/shader/direct-volume.frag
  32. 4 0
      src/mol-gl/shader/direct-volume.vert
  33. 47 32
      src/mol-gl/shader/gaussian-density.frag
  34. 14 6
      src/mol-gl/shader/gaussian-density.vert
  35. 5 5
      src/mol-gl/shader/mesh.frag
  36. 11 0
      src/mol-gl/shader/utils/decode-float-rgb.glsl
  37. 0 5
      src/mol-gl/shader/utils/decode-float-rgba.glsl
  38. 13 0
      src/mol-gl/shader/utils/decode-id-rgb.glsl
  39. 20 0
      src/mol-gl/shader/utils/encode-float-rgb.glsl
  40. 0 12
      src/mol-gl/shader/utils/encode-float-rgba.glsl
  41. 13 0
      src/mol-gl/shader/utils/encode-id-rgb.glsl
  42. 0 7
      src/mol-gl/shader/utils/encode-id-rgba.glsl
  43. 5 2
      src/mol-gl/shader/utils/read-from-texture.glsl
  44. 28 0
      src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl
  45. 19 0
      src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl
  46. 22 0
      src/mol-gl/webgl/compat.ts
  47. 21 4
      src/mol-gl/webgl/context.ts
  48. 4 4
      src/mol-gl/webgl/render-item.ts
  49. 2 5
      src/mol-gl/webgl/texture.ts
  50. 1 1
      src/mol-io/reader/_spec/ccp4.spec.ts
  51. 10 12
      src/mol-io/reader/ccp4/parser.ts
  52. 8 8
      src/mol-io/reader/ccp4/schema.ts
  53. 168 147
      src/mol-math/geometry/gaussian-density/gpu.ts
  54. 1 1
      src/mol-model/volume/data.ts
  55. 6 3
      src/mol-model/volume/formats/density-server.ts
  56. 4 4
      src/mol-view/viewer.ts

+ 24 - 3
src/apps/canvas/app.ts

@@ -5,7 +5,7 @@
  */
 
 import Viewer from 'mol-view/viewer';
-import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile } from './util';
+import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile, getVolumeFromVolcif } from './util';
 import { StructureView } from './structure-view';
 import { BehaviorSubject } from 'rxjs';
 import { CifBlock } from 'mol-io/reader/cif';
@@ -71,14 +71,14 @@ export class App {
         if (this.structureView) this.structureView.destroy();
         const url = idOrUrl.length <= 4 ? `https://files.rcsb.org/download/${idOrUrl}.cif` : idOrUrl;
         const cif = await this.runTask(getCifFromUrl(url, options ? !!options.binary : false), 'Load mmCIF from URL')
-        this.loadMmcif(cif, options ? options.assemblyId : void 0)
+        this.loadMmcif(cif.blocks[0], options ? options.assemblyId : void 0)
     }
 
     async loadMmcifFile(file: File) {
         if (this.structureView) this.structureView.destroy();
         const binary = /\.bcif$/.test(file.name);
         const cif = await this.runTask(getCifFromFile(file, binary), 'Load mmCIF from file')
-        this.loadMmcif(cif)
+        this.loadMmcif(cif.blocks[0])
     }
 
     //
@@ -100,4 +100,25 @@ export class App {
         const ccp4 = await this.runTask(getCcp4FromUrl(url), 'Load CCP4 from URL')
         this.loadCcp4(ccp4)
     }
+
+    //
+
+    async loadVolcif(cif: CifBlock) {
+        const volume = await this.runTask(getVolumeFromVolcif(cif), 'Get Volume')
+        this.volumeView = await this.runTask(VolumeView(this, this.viewer, volume), 'Init volume view')
+        this.volumeLoaded.next(this.volumeView)
+    }
+
+    async loadVolcifFile(file: File) {
+        if (this.volumeView) this.volumeView.destroy();
+        const binary = /\.bcif$/.test(file.name);
+        const cif = await this.runTask(getCifFromFile(file, binary), 'Load volCif from file')
+        this.loadVolcif(cif.blocks[1])
+    }
+
+    async loadVolcifUrl(url: string, binary?: boolean) {
+        if (this.volumeView) this.volumeView.destroy();
+        const cif = await this.runTask(getCifFromUrl(url, binary), 'Load volCif from URL')
+        this.loadVolcif(cif.blocks[1])
+    }
 }

+ 22 - 4
src/apps/canvas/component/app.tsx

@@ -20,14 +20,16 @@ export interface AppProps {
 export interface AppState {
     structureView: StructureView | null,
     volumeView: VolumeView | null,
-    binary: boolean
+    mmcifBinary: boolean,
+    volcifBinary: boolean
 }
 
 export class AppComponent extends React.Component<AppProps, AppState> {
     state = {
         structureView: this.props.app.structureView,
         volumeView: this.props.app.volumeView,
-        binary: false
+        mmcifBinary: false,
+        volcifBinary: true
     }
 
     componentDidMount() {
@@ -50,7 +52,7 @@ export class AppComponent extends React.Component<AppProps, AppState> {
             <div style={{width: '330px', paddingLeft: '10px', paddingRight: '10px', right: '0px', height: '100%', position: 'absolute', overflow: 'auto'}}>
                 <div style={{marginTop: '10px'}}>
                     <span>Load PDB ID or URL</span>&nbsp;&nbsp;
-                    <input type='checkbox' checked={this.state.binary} onChange={e => this.setState({ binary: e.target.checked })} /> Binary<br />
+                    <input type='checkbox' checked={this.state.mmcifBinary} onChange={e => this.setState({ mmcifBinary: e.target.checked })} /> Binary<br />
                     <input
                         style={{ width: '100%' }}
                         type='text'
@@ -58,7 +60,7 @@ export class AppComponent extends React.Component<AppProps, AppState> {
                             if (e.keyCode === 13) {
                                 const value = e.currentTarget.value.trim()
                                 if (value) {
-                                    this.props.app.loadPdbIdOrMmcifUrl(value, { binary: this.state.binary })
+                                    this.props.app.loadPdbIdOrMmcifUrl(value, { binary: this.state.mmcifBinary })
                                 }
                             }
                         }}
@@ -84,6 +86,22 @@ export class AppComponent extends React.Component<AppProps, AppState> {
                         }}
                     />
                 </div>
+                <div style={{marginTop: '10px'}}>
+                    <span>Load DensityServer URL</span>&nbsp;&nbsp;
+                    <input type='checkbox' checked={this.state.volcifBinary} onChange={e => this.setState({ volcifBinary: e.target.checked })} /> Binary<br />
+                    <input
+                        style={{ width: '100%' }}
+                        type='text'
+                        onKeyDown={e => {
+                            if (e.keyCode === 13) {
+                                const value = e.currentTarget.value.trim()
+                                if (value) {
+                                    this.props.app.loadVolcifUrl(value, this.state.volcifBinary)
+                                }
+                            }
+                        }}
+                    />
+                </div>
                 <div>
                     <span>Load example </span>
                     <select

+ 10 - 1
src/apps/canvas/index.ts

@@ -23,4 +23,13 @@ const assemblyId = urlQueryParameter('assembly')
 const pdbId = urlQueryParameter('pdb')
 if (pdbId) app.loadPdbIdOrMmcifUrl(pdbId, { assemblyId })
 
-// app.loadCcp4Url('http://localhost:8091/ngl/data/betaGal.mrc')
+// app.loadCcp4Url('http://localhost:8091/ngl/data/betaGal.mrc')
+
+// app.loadPdbIdOrMmcifUrl('3pqr')
+// app.loadVolCifUrl('https://webchem.ncbr.muni.cz/DensityServer/x-ray/3pqr/cell?space=fractional', true)
+
+// app.loadPdbIdOrMmcifUrl('5ire')
+// app.loadVolcifUrl('https://webchem.ncbr.muni.cz/DensityServer/em/emd-8116/cell?space=cartesian&detail=6', true)
+
+// app.loadPdbIdOrMmcifUrl('5gag')
+// app.loadVolcifUrl('https://webchem.ncbr.muni.cz/DensityServer/em/emd-8003/cell?detail=3', true)

+ 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: true,
+        surface: false,
         ballAndStick: false,
         carbohydrate: false,
         spacefill: false,

+ 8 - 1
src/apps/canvas/util.ts

@@ -11,6 +11,7 @@ import CCP4 from 'mol-io/reader/ccp4/parser'
 import { FileHandle } from 'mol-io/common/file-handle';
 import { Ccp4File } from 'mol-io/reader/ccp4/schema';
 import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4';
+import { parseDensityServerData } from 'mol-model/volume';
 // import { parse as parseObj } from 'mol-io/reader/obj/parser'
 
 // export async function getObjFromUrl(url: string) {
@@ -25,7 +26,7 @@ export async function getCifFromData(data: string | Uint8Array) {
     const comp = CIF.parse(data)
     const parsed = await comp.run()
     if (parsed.isError) throw parsed
-    return parsed.result.blocks[0]
+    return parsed.result
 }
 
 export async function getCifFromUrl(url: string, binary = false) {
@@ -68,4 +69,10 @@ export async function getCcp4FromData(data: Uint8Array) {
 
 export async function getVolumeFromCcp4(ccp4: Ccp4File) {
     return await volumeFromCcp4(ccp4).run()
+}
+
+//
+
+export async function getVolumeFromVolcif(cif: CifBlock) {
+    return await parseDensityServerData(CIF.schema.densityServer(cif)).run()
 }

+ 64 - 128
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -7,17 +7,25 @@
 import { RuntimeContext } from 'mol-task'
 import { ValueCell } from 'mol-util'
 import { Sphere3D, Box3D } from 'mol-math/geometry'
-import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, TextParam } from 'mol-view/parameter';
-import { DirectVolume2dValues, DirectVolumeBaseValues, DirectVolume3dValues } from 'mol-gl/renderable/direct-volume';
-import { Vec3, Vec2, Mat4 } from 'mol-math/linear-algebra';
+import { paramDefaultValues, RangeParam, SelectParam, TextParam } from 'mol-view/parameter';
+import { DirectVolumeValues } from 'mol-gl/renderable/direct-volume';
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Box } from '../../primitive/box';
 import { getControlPointsFromString, createTransferFunctionTexture } from './transfer-function';
 import { Texture } from 'mol-gl/webgl/texture';
+import { LocationIterator } from 'mol-geo/util/location-iterator';
+import { TransformData } from '../transform-data';
+import { createColors } from '../color-data';
+import { createMarkers } from '../marker-data';
+import { Geometry } from '../geometry';
 
 const VolumeBox = Box()
 const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
 
-interface DirectVolumeBase {
+export interface DirectVolume {
+    readonly kind: 'direct-volume',
+    readonly gridTexture: ValueCell<Texture>,
+    readonly gridTextureDim: ValueCell<Vec3>,
     readonly gridDimension: ValueCell<Vec3>,
     readonly bboxSize: ValueCell<Vec3>
     readonly bboxMin: ValueCell<Vec3>
@@ -28,74 +36,12 @@ interface DirectVolumeBase {
     boundingSphere?: Sphere3D
 }
 
-const BaseParams = {
-    alpha: RangeParam('Opacity', '', 1, 0, 1, 0.01),
-    visible: BooleanParam('Visible', '', true),
-    depthMask: BooleanParam('Depth Mask', '', true),
-    useFog: BooleanParam('Use Fog', '', false),
-    isoValueAbsolute: RangeParam('Iso Value Absolute', '', 0.22, -1, 1, 0.01),
-    isoValueRelative: RangeParam('Iso Value Relative', '', 2, -10, 10, 0.1),
-    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'),
-}
-const DefaultBaseProps = paramDefaultValues(BaseParams)
-type BaseProps = typeof DefaultBaseProps
-
-async function createBaseValues(ctx: RuntimeContext, directVolume: DirectVolumeBase, props: BaseProps): Promise<DirectVolumeBaseValues> {
-    const { bboxSize, bboxMin, bboxMax, gridDimension, transform } = directVolume
-
-    const controlPoints = getControlPointsFromString(props.controlPoints)
-    const transferTex = createTransferFunctionTexture(controlPoints)
-
-    const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2
-    console.log('maxSteps', maxSteps)
-
-    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.isoValueAbsolute),
-        uBboxMin: bboxMin,
-        uBboxMax: bboxMax,
-        uBboxSize: bboxSize,
-        dMaxSteps: ValueCell.create(maxSteps),
-        uTransform: transform,
-        uGridDim: gridDimension,
-        dRenderMode: ValueCell.create(props.renderMode),
-        tTransferTex: transferTex,
-    }
-}
-
-function updateBaseValues(values: DirectVolumeBaseValues, props: BaseProps) {
-    console.log('DirectVolumeBaseValues', props, values)
-    ValueCell.updateIfChanged(values.uIsoValue, props.isoValueAbsolute)
-    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)
-}
-
-// 2d
-
-export interface DirectVolume2d extends DirectVolumeBase {
-    readonly kind: 'direct-volume-2d',
-    readonly gridTexture: ValueCell<Texture>,
-    readonly gridTextureDim: ValueCell<Vec2>,
-}
-
-export namespace DirectVolume2d {
-    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume2d): DirectVolume2d {
+export namespace DirectVolume {
+    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume): DirectVolume {
+        const { width, height, depth } = texture
         if (directVolume) {
             ValueCell.update(directVolume.gridDimension, gridDimension)
-            ValueCell.update(directVolume.gridTextureDim, Vec2.set(directVolume.gridTextureDim.ref.value, texture.width, texture.height))
+            ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth))
             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))
@@ -103,10 +49,10 @@ export namespace DirectVolume2d {
             return directVolume
         } else {
             return {
-                kind: 'direct-volume-2d' as 'direct-volume-2d',
+                kind: 'direct-volume',
                 gridDimension: ValueCell.create(gridDimension),
                 gridTexture: ValueCell.create(texture),
-                gridTextureDim: ValueCell.create(Vec2.create(texture.width, texture.height)),
+                gridTextureDim: ValueCell.create(Vec3.create(width, height, depth)),
                 bboxMin: ValueCell.create(bbox.min),
                 bboxMax: ValueCell.create(bbox.max),
                 bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
@@ -115,78 +61,68 @@ export namespace DirectVolume2d {
         }
     }
 
-    export function createEmpty(directVolume?: DirectVolume2d): DirectVolume2d {
-        return {} as DirectVolume2d // TODO
+    export function createEmpty(directVolume?: DirectVolume): DirectVolume {
+        return {} as DirectVolume // TODO
     }
 
-    export const Params = BaseParams
+    export const Params = {
+        ...Geometry.Params,
+        isoValueAbsolute: RangeParam('Iso Value Absolute', '', 0.22, -1, 1, 0.01),
+        isoValueRelative: RangeParam('Iso Value Relative', '', 2, -10, 10, 0.1),
+        renderMode: SelectParam('Render Mode', '', 'isosurface', 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: DirectVolume2d, props: Props): Promise<DirectVolume2dValues> {
+    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<DirectVolumeValues> {
         const { gridTexture, gridTextureDim } = directVolume
 
-        return {
-            ...await createBaseValues(ctx, directVolume, props),
-            dGridTexType: ValueCell.create('2d'),
-            uGridTexDim: gridTextureDim,
-            tGridTex: gridTexture,
-        }
-    }
+        const { instanceCount, groupCount } = locationIt
+        const color = await createColors(ctx, locationIt, props)
+        const marker = createMarkers(instanceCount * groupCount)
 
-    export function updateValues(values: DirectVolume2dValues, props: Props) {
-        updateBaseValues(values, props)
-    }
-}
+        const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount }
 
-// 3d
+        const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume
 
-export interface DirectVolume3d extends DirectVolumeBase {
-    readonly kind: 'direct-volume-3d',
-    readonly gridTexture: ValueCell<Texture>,
-}
+        const controlPoints = getControlPointsFromString(props.controlPoints)
+        const transferTex = createTransferFunctionTexture(controlPoints)
 
-export namespace DirectVolume3d {
-    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume3d): DirectVolume3d {
-        if (directVolume) {
-            ValueCell.update(directVolume.gridDimension, gridDimension)
-            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)
-            return directVolume
-        } else {
-            return {
-                kind: 'direct-volume-3d' as 'direct-volume-3d',
-                gridDimension: ValueCell.create(gridDimension),
-                gridTexture: ValueCell.create(texture),
-                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),
-            }
-        }
-    }
-
-    export function createEmpty(directVolume?: DirectVolume3d): DirectVolume3d {
-        return {} as DirectVolume3d // TODO
-    }
-
-    export const Params = BaseParams
-    export const DefaultProps = paramDefaultValues(Params)
-    export type Props = typeof DefaultProps
-
-    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume3d, props: Props): Promise<DirectVolume3dValues> {
-        const { gridTexture } = directVolume
+        const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2
 
         return {
-            ...await createBaseValues(ctx, directVolume, props),
-            dGridTexType: ValueCell.create('3d'),
+            ...color,
+            ...marker,
+            ...transform,
+            ...Geometry.createValues(props, counts),
+
+            aPosition: ValueCell.create(VolumeBox.vertices as Float32Array),
+            elements: ValueCell.create(VolumeBox.indices as Uint32Array),
+
+            uIsoValue: ValueCell.create(props.isoValueAbsolute),
+            uBboxMin: bboxMin,
+            uBboxMax: bboxMax,
+            uBboxSize: bboxSize,
+            dMaxSteps: ValueCell.create(maxSteps),
+            uTransform: gridTransform,
+            uGridDim: gridDimension,
+            dRenderMode: ValueCell.create(props.renderMode),
+            tTransferTex: transferTex,
+
+            dGridTexType: ValueCell.create(gridTexture.ref.value.depth > 0 ? '3d' : '2d'),
+            uGridTexDim: gridTextureDim,
             tGridTex: gridTexture,
         }
     }
 
-    export function updateValues(values: DirectVolume3dValues, props: Props) {
-        updateBaseValues(values, props)
+    export function updateValues(values: DirectVolumeValues, props: Props) {
+        ValueCell.updateIfChanged(values.uIsoValue, props.isoValueAbsolute)
+        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)
     }
 }

+ 3 - 5
src/mol-geo/geometry/geometry.ts

@@ -17,7 +17,7 @@ import { SizeType } from './size-data';
 import { Lines } from './lines/lines';
 import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, ColorParam, StructureParam, ValueParam } from 'mol-view/parameter'
 import { Structure } from 'mol-model/structure';
-import { DirectVolume2d, DirectVolume3d } from './direct-volume/direct-volume';
+import { DirectVolume } from './direct-volume/direct-volume';
 import { Context } from 'mol-gl/webgl/context';
 
 //
@@ -43,8 +43,7 @@ export type GeometryKindType = {
     'mesh': Mesh,
     'points': Points,
     'lines': Lines,
-    'direct-volume-2d': DirectVolume2d,
-    'direct-volume-3d': DirectVolume3d
+    'direct-volume': DirectVolume,
 }
 export type GeometryKind = keyof GeometryKindType
 export type Geometry = Helpers.ValueOf<GeometryKindType>
@@ -55,8 +54,7 @@ export namespace Geometry {
             case 'mesh': return geometry.triangleCount * 3
             case 'points': return geometry.pointCount
             case 'lines': return geometry.lineCount * 2 * 3
-            case 'direct-volume-2d': return 12 * 3
-            case 'direct-volume-3d': return 12 * 3
+            case 'direct-volume': return 12 * 3
         }
     }
 

+ 22 - 0
src/mol-geo/geometry/mesh/mesh.ts

@@ -103,6 +103,28 @@ export namespace Mesh {
         mesh.normalsComputed = true;
     }
 
+    export function checkForDuplicateVertices(mesh: Mesh, fractionDigits = 3) {
+        const v = mesh.vertexBuffer.ref.value
+
+        const map = new Map<string, number>()
+        const hash = (v: Vec3, d: number) => `${v[0].toFixed(d)}|${v[1].toFixed(d)}|${v[2].toFixed(d)}`
+        let duplicates = 0
+
+        const a = Vec3.zero()
+        for (let i = 0, il = mesh.vertexCount; i < il; ++i) {
+            Vec3.fromArray(a, v, i * 3)
+            const k = hash(a, fractionDigits)
+            const count = map.get(k)
+            if (count !== undefined) {
+                duplicates += 1
+                map.set(k, count + 1)
+            } else {
+                map.set(k, 1)
+            }
+        }
+        return duplicates
+    }
+
     export function computeNormals(surface: Mesh): Task<Mesh> {
         return Task.create<Mesh>('Surface (Compute Normals)', async ctx => {
             if (surface.normalsComputed) return surface;

+ 1 - 1
src/mol-geo/geometry/picking.ts

@@ -11,7 +11,7 @@ function decodeFloatRGBA(r: number, g: number, b: number) {
     return r * 256 * 256 + g * 256 + b
 }
 
-export function decodeIdRGBA(r: number, g: number, b: number) {
+export function decodeIdRGB(r: number, g: number, b: number) {
     return decodeFloatRGBA(r, g, b) - 1
 }
 

+ 65 - 26
src/mol-geo/representation/structure/complex-visual.ts

@@ -6,47 +6,57 @@
 
 import { Structure } from 'mol-model/structure';
 import { Visual } from '..';
-import { MeshRenderObject } from 'mol-gl/render-object';
+import { MeshRenderObject, LinesRenderObject, PointsRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object';
 import { Mesh } from '../../geometry/mesh/mesh';
 import { RuntimeContext } from 'mol-task';
 import { LocationIterator } from '../../util/location-iterator';
-import { createComplexMeshRenderObject } from './visual/util/common';
-import { StructureProps, VisualUpdateState, StructureMeshParams } from '.';
+import { createComplexMeshRenderObject, sizeChanged, colorChanged, UnitKind, UnitKindOptions } from './visual/util/common';
+import { StructureProps, VisualUpdateState, StructureMeshParams, StructureParams } from '.';
 import { deepEqual, ValueCell } from 'mol-util';
 import { PickingId } from '../../geometry/picking';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction, applyMarkerAction } from '../../geometry/marker-data';
 import { Interval } from 'mol-data/int';
-import { updateRenderableState } from '../../geometry/geometry';
+import { updateRenderableState, Geometry } from '../../geometry/geometry';
 import { createColors } from '../../geometry/color-data';
-import { UnitKindOptions, UnitKind } from './units-visual';
 import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter';
+import { RenderableValues } from 'mol-gl/renderable/schema';
+import { createSizes } from 'mol-geo/geometry/size-data';
 
 export interface  ComplexVisual<P extends StructureProps> extends Visual<Structure, P> { }
 
-export const ComplexMeshParams = {
-    ...StructureMeshParams,
-    unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions),
+const ComplexParams = {
+    ...StructureParams,
+    unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
 }
-export const DefaultComplexMeshProps = paramDefaultValues(ComplexMeshParams)
-export type ComplexMeshProps = typeof DefaultComplexMeshProps
+const DefaultComplexProps = paramDefaultValues(ComplexParams)
+type ComplexProps = typeof DefaultComplexProps
+
+type ComplexRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
 
-export interface ComplexMeshVisualBuilder<P extends ComplexMeshProps> {
+interface ComplexVisualBuilder<P extends ComplexProps, G extends Geometry> {
     defaultProps: P
-    createMesh(ctx: RuntimeContext, structure: Structure, props: P, mesh?: Mesh): Promise<Mesh>
+    createGeometry(ctx: RuntimeContext, structure: Structure, props: P, geometry?: G): Promise<G>
     createLocationIterator(structure: Structure): LocationIterator
     getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
     mark(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean): boolean,
     setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
 }
 
-export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMeshVisualBuilder<P>): ComplexVisual<P> {
-    const { defaultProps, createMesh, createLocationIterator, getLoci, mark, setUpdateState } = builder
+interface ComplexVisualGeometryBuilder<P extends ComplexProps, G extends Geometry> extends ComplexVisualBuilder<P, G> {
+    createEmptyGeometry(geometry?: G): G
+    createRenderObject(ctx: RuntimeContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, currentProps: P): Promise<ComplexRenderObject>
+    updateValues(values: RenderableValues, newProps: P): void
+}
+
+export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisualGeometryBuilder<P, Geometry>): ComplexVisual<P> {
+    const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
+    const { createRenderObject, updateValues } = builder
     const updateState = VisualUpdateState.create()
 
-    let renderObject: MeshRenderObject | undefined
+    let renderObject: ComplexRenderObject | undefined
     let currentProps: P
-    let mesh: Mesh
+    let geometry: Geometry
     let currentStructure: Structure
     let locationIt: LocationIterator
     let conformationHash: number
@@ -56,10 +66,10 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe
         currentStructure = structure
 
         conformationHash = Structure.conformationHash(currentStructure)
-        mesh = await createMesh(ctx, currentStructure, currentProps, mesh)
+        geometry = await createGeometry(ctx, currentStructure, currentProps, geometry)
 
         locationIt = createLocationIterator(structure)
-        renderObject = await createComplexMeshRenderObject(ctx, structure, mesh, locationIt, currentProps)
+        renderObject = await createRenderObject(ctx, structure, geometry, locationIt, currentProps)
     }
 
     async function update(ctx: RuntimeContext, props: Partial<P>) {
@@ -77,25 +87,30 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe
             updateState.createGeometry = true
         }
 
-        if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.createGeometry = true
-        if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true
-        // if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createMesh = true // TODO
+        if (colorChanged(currentProps, newProps)) updateState.updateColor = true
+        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
 
         //
 
         if (updateState.createGeometry) {
-            mesh = await createMesh(ctx, currentStructure, newProps, mesh)
-            ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+            geometry = await createGeometry(ctx, currentStructure, newProps, geometry)
+            ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
             updateState.updateColor = true
         }
 
+        if (updateState.updateSize) {
+            // not all geometries have size data, so check here
+            if ('uSize' in renderObject.values) {
+                await createSizes(ctx, locationIt, newProps, renderObject.values)
+            }
+        }
+
         if (updateState.updateColor) {
             await createColors(ctx, locationIt, newProps, renderObject.values)
         }
 
-        // TODO why do I need to cast here?
-        Mesh.updateValues(renderObject.values, newProps as ComplexMeshProps)
-        updateRenderableState(renderObject.state, newProps as ComplexMeshProps)
+        updateValues(renderObject.values, newProps)
+        updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps
         return true
@@ -147,4 +162,28 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe
             renderObject = undefined
         }
     }
+}
+
+// mesh
+
+export const ComplexMeshParams = {
+    ...StructureMeshParams,
+    unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions),
+}
+export const DefaultComplexMeshProps = paramDefaultValues(ComplexMeshParams)
+export type ComplexMeshProps = typeof DefaultComplexMeshProps
+
+export interface ComplexMeshVisualBuilder<P extends ComplexMeshProps> extends ComplexVisualBuilder<P, Mesh> { }
+
+export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMeshVisualBuilder<P>): ComplexVisual<P> {
+    return ComplexVisual({
+        ...builder,
+        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
+            builder.setUpdateState(state, newProps, currentProps)
+            if (sizeChanged(currentProps, newProps)) state.createGeometry = true
+        },
+        createEmptyGeometry: Mesh.createEmpty,
+        createRenderObject: createComplexMeshRenderObject,
+        updateValues: Mesh.updateValues
+    })
 }

+ 2 - 3
src/mol-geo/representation/structure/index.ts

@@ -14,7 +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 { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume';
+import { DirectVolume } from '../../geometry/direct-volume/direct-volume';
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
@@ -48,8 +48,7 @@ export const DefaultStructureLinesProps = paramDefaultValues(StructureLinesParam
 export type StructureLinesProps = typeof DefaultStructureLinesProps
 
 export const StructureDirectVolumeParams = {
-    ...DirectVolume2d.Params,
-    ...DirectVolume3d.Params,
+    ...DirectVolume.Params,
     ...StructureParams,
 }
 export const DefaultStructureDirectVolumeProps = paramDefaultValues(StructureDirectVolumeParams)

+ 1 - 1
src/mol-geo/representation/structure/representation/ball-and-stick.ts

@@ -16,7 +16,7 @@ import { InterUnitLinkVisual } from '../visual/inter-unit-link-cylinder';
 import { SizeThemeName, SizeThemeOptions } from 'mol-view/theme/size';
 import { getQualityProps } from '../../util';
 import { paramDefaultValues, SelectParam, NumberParam, MultiSelectParam } from 'mol-view/parameter';
-import { UnitKind, UnitKindOptions } from '../units-visual';
+import { UnitKind, UnitKindOptions } from '../visual/util/common';
 
 export const BallAndStickParams = {
     ...ElementSphereParams,

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

@@ -9,7 +9,7 @@ import { GaussianSurfaceVisual, GaussianSurfaceParams } from '../visual/gaussian
 import { StructureRepresentation } from '../units-representation';
 import { Structure } from 'mol-model/structure';
 import { MarkerAction } from '../../../geometry/marker-data';
-import { Loci } from 'mol-model/loci';
+import { Loci, isEmptyLoci } from 'mol-model/loci';
 import { PickingId } from '../../../geometry/picking';
 import { Task } from 'mol-task';
 import { GaussianWireframeVisual, GaussianWireframeParams } from '../visual/gaussian-surface-wireframe';
@@ -63,10 +63,24 @@ export function MolecularSurfaceRepresentation(): MolecularSurfaceRepresentation
             })
         },
         getLoci: (pickingId: PickingId) => {
-            return gaussianSurfaceRepr.getLoci(pickingId)
+            const surfaceLoci = gaussianSurfaceRepr.getLoci(pickingId)
+            const wireframeLoci = gaussianWireframeRepr.getLoci(pickingId)
+            const volumeLoci = gaussianVolumeRepr.getLoci(pickingId)
+            if (isEmptyLoci(surfaceLoci)) {
+                if (isEmptyLoci(wireframeLoci)) {
+                    return volumeLoci
+                } else {
+                    return wireframeLoci
+                }
+            } else {
+                return surfaceLoci
+            }
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            return gaussianSurfaceRepr.mark(loci, action)
+            const markSurfaceElement = gaussianSurfaceRepr.mark(loci, action)
+            const markWireframeElement = gaussianWireframeRepr.mark(loci, action)
+            const markVolumeElement = gaussianVolumeRepr.mark(loci, action)
+            return markSurfaceElement || markWireframeElement || markVolumeElement
         },
         destroy() {
             gaussianSurfaceRepr.destroy()

+ 89 - 454
src/mol-geo/representation/structure/units-visual.ts

@@ -4,37 +4,27 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-// TODO refactor to make DRY
-
 import { Unit, Structure } from 'mol-model/structure';
 import { RepresentationProps, Visual } from '../';
-import { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams, StructureProps } from '.';
+import { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams, StructureParams } 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, DirectVolume2dRenderObject, DirectVolume3dRenderObject } from 'mol-gl/render-object';
-import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject } from './visual/util/common';
+import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object';
+import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject, UnitKind, UnitKindOptions, includesUnitKind, colorChanged, sizeChanged } from './visual/util/common';
 import { deepEqual, ValueCell, UUID } from 'mol-util';
 import { Interval } from 'mol-data/int';
 import { Points } from '../../geometry/points/points';
 import { updateRenderableState, Geometry } from '../../geometry/geometry';
-import { createColors, ColorProps } from '../../geometry/color-data';
-import { createSizes, SizeProps } from '../../geometry/size-data';
+import { createColors } from '../../geometry/color-data';
+import { createSizes } from '../../geometry/size-data';
 import { Lines } from '../../geometry/lines/lines';
 import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter';
-import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume';
-
-export const UnitKindInfo = {
-    'atomic': {},
-    'spheres': {},
-    'gaussians': {},
-}
-export type UnitKind = keyof typeof UnitKindInfo
-export const UnitKindNames = Object.keys(UnitKindInfo)
-export const UnitKindOptions = UnitKindNames.map(n => [n, n] as [UnitKind, string])
+import { DirectVolume } from '../../geometry/direct-volume/direct-volume';
+import { RenderableValues } from 'mol-gl/renderable/schema';
 
 export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 
@@ -47,35 +37,16 @@ function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.Symmetry
     )
 }
 
-function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
-    for (let i = 0, il = unitKinds.length; i < il; ++i) {
-        if (Unit.isAtomic(unit) && unitKinds[i] === 'atomic') return true
-        if (Unit.isSpheres(unit) && unitKinds[i] === 'spheres') return true
-        if (Unit.isGaussians(unit) && unitKinds[i] === 'gaussians') return true
-    }
-    return false
-}
-
-function sizeChanged(oldProps: SizeProps, newProps: SizeProps) {
-    return (
-        oldProps.sizeTheme !== newProps.sizeTheme ||
-        oldProps.sizeValue !== newProps.sizeValue ||
-        oldProps.sizeFactor !== newProps.sizeFactor
-    )
-}
-
-function colorChanged(oldProps: ColorProps, newProps: ColorProps) {
-    return (
-        oldProps.colorTheme !== newProps.colorTheme ||
-        oldProps.colorValue !== newProps.colorValue
-    )
-}
-
 const UnitsParams = {
+    ...StructureParams,
     unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
 }
+const DefaultUnitsProps = paramDefaultValues(UnitsParams)
+type UnitsProps = typeof DefaultUnitsProps
+
+type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
 
-interface UnitsVisualBuilder<P extends StructureProps, G extends Geometry> {
+interface UnitsVisualBuilder<P extends UnitsProps, G extends Geometry> {
     defaultProps: P
     createGeometry(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, geometry?: G): Promise<G>
     createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
@@ -84,23 +55,20 @@ interface UnitsVisualBuilder<P extends StructureProps, G extends Geometry> {
     setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
 }
 
-// mesh
-
-export const UnitsMeshParams = {
-    ...StructureMeshParams,
-    ...UnitsParams,
+interface UnitsVisualGeometryBuilder<P extends UnitsProps, G extends Geometry> extends UnitsVisualBuilder<P, G> {
+    createEmptyGeometry(geometry?: G): G
+    createRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, currentProps: P): Promise<UnitsRenderObject>
+    updateValues(values: RenderableValues, newProps: P): void
 }
-export const DefaultUnitsMeshProps = paramDefaultValues(UnitsMeshParams)
-export type UnitsMeshProps = typeof DefaultUnitsMeshProps
-export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> extends UnitsVisualBuilder<P, Mesh> { }
 
-export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
+export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBuilder<P, Geometry>): UnitsVisual<P> {
     const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
+    const { createEmptyGeometry, createRenderObject, updateValues } = builder
     const updateState = VisualUpdateState.create()
 
-    let renderObject: MeshRenderObject | undefined
+    let renderObject: UnitsRenderObject | undefined
     let currentProps: P
-    let mesh: Mesh
+    let geometry: Geometry
     let currentGroup: Unit.SymmetryGroup
     let currentStructure: Structure
     let locationIt: LocationIterator
@@ -112,13 +80,13 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu
 
         const unit = group.units[0]
         currentConformationId = Unit.conformationId(unit)
-        mesh = includesUnitKind(currentProps.unitKinds, unit)
-            ? await createGeometry(ctx, unit, currentStructure, currentProps, mesh)
-            : Mesh.createEmpty(mesh)
+        geometry = includesUnitKind(currentProps.unitKinds, unit)
+            ? await createGeometry(ctx, unit, currentStructure, currentProps, geometry)
+            : createEmptyGeometry(geometry)
 
         // TODO create empty location iterator when not in unitKinds
         locationIt = createLocationIterator(group)
-        renderObject = await createUnitsMeshRenderObject(ctx, group, mesh, locationIt, currentProps)
+        renderObject = await createRenderObject(ctx, group, geometry, locationIt, currentProps)
     }
 
     async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
@@ -139,7 +107,6 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu
 
         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
 
@@ -154,20 +121,26 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu
         }
 
         if (updateState.createGeometry) {
-            mesh = includesUnitKind(newProps.unitKinds, unit)
-                ? await createGeometry(ctx, unit, currentStructure, newProps, mesh)
-                : Mesh.createEmpty(mesh)
-            ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+            geometry = includesUnitKind(newProps.unitKinds, unit)
+                ? await createGeometry(ctx, unit, currentStructure, newProps, geometry)
+                : createEmptyGeometry(geometry)
+            ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
             updateState.updateColor = true
         }
 
+        if (updateState.updateSize) {
+            // not all geometries have size data, so check here
+            if ('uSize' in renderObject.values) {
+                await createSizes(ctx, locationIt, newProps, renderObject.values)
+            }
+        }
+
         if (updateState.updateColor) {
             await createColors(ctx, locationIt, newProps, renderObject.values)
         }
 
-        // TODO why do I need to cast here?
-        Mesh.updateValues(renderObject.values, newProps as UnitsMeshProps)
-        updateRenderableState(renderObject.state, newProps as UnitsMeshProps)
+        updateValues(renderObject.values, newProps)
+        updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps
     }
@@ -226,6 +199,29 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu
     }
 }
 
+// mesh
+
+export const UnitsMeshParams = {
+    ...StructureMeshParams,
+    ...UnitsParams,
+}
+export const DefaultUnitsMeshProps = paramDefaultValues(UnitsMeshParams)
+export type UnitsMeshProps = typeof DefaultUnitsMeshProps
+export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> extends UnitsVisualBuilder<P, Mesh> { }
+
+export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
+    return UnitsVisual({
+        ...builder,
+        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
+            builder.setUpdateState(state, newProps, currentProps)
+            if (sizeChanged(currentProps, newProps)) state.createGeometry = true
+        },
+        createEmptyGeometry: Mesh.createEmpty,
+        createRenderObject: createUnitsMeshRenderObject,
+        updateValues: Mesh.updateValues
+    })
+}
+
 // points
 
 export const UnitsPointsParams = {
@@ -237,139 +233,16 @@ export type UnitsPointsProps = typeof DefaultUnitsPointsProps
 export interface UnitsPointVisualBuilder<P extends UnitsPointsProps> extends UnitsVisualBuilder<P, Points> { }
 
 export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
-    const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
-    const updateState = VisualUpdateState.create()
-
-    let renderObject: PointsRenderObject | undefined
-    let currentProps: P
-    let points: Points
-    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)
-        points = includesUnitKind(currentProps.unitKinds, unit)
-            ? await createGeometry(ctx, unit, currentStructure, currentProps, points)
-            : Points.createEmpty(points)
-
-        // TODO create empty location iterator when not in unitKinds
-        locationIt = createLocationIterator(group)
-        renderObject = await createUnitsPointsRenderObject(ctx, group, points, 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.updateSize = 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) {
-            points = includesUnitKind(newProps.unitKinds, unit)
-                ? await createGeometry(ctx, unit, currentStructure, newProps, points)
-                : Points.createEmpty(points)
-            ValueCell.update(renderObject.values.drawCount, points.pointCount)
-            updateState.updateColor = true
-        }
-
-        if (updateState.updateSize) {
-            await createSizes(ctx, locationIt, newProps, renderObject.values)
-        }
-
-        if (updateState.updateColor) {
-            await createColors(ctx, locationIt, newProps, renderObject.values)
-        }
-
-        // TODO why do I need to cast here?
-        Points.updateValues(renderObject.values, newProps as UnitsPointsProps)
-        updateRenderableState(renderObject.state, newProps as UnitsPointsProps)
-
-        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
+    return UnitsVisual({
+        ...builder,
+        createEmptyGeometry: Points.createEmpty,
+        createRenderObject: createUnitsPointsRenderObject,
+        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
+            builder.setUpdateState(state, newProps, currentProps)
+            if (sizeChanged(currentProps, newProps)) state.updateSize = true
         },
-        mark(loci: Loci, action: MarkerAction) {
-            if (!renderObject) return false
-            const { tMarker } = renderObject.values
-            const { groupCount, instanceCount } = locationIt
-
-            function apply(interval: Interval) {
-                const start = Interval.start(interval)
-                const end = Interval.end(interval)
-                return applyMarkerAction(tMarker.ref.value.array, start, end, action)
-            }
-
-            let changed = false
-            if (isEveryLoci(loci)) {
-                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
-            } else {
-                changed = mark(loci, currentGroup, apply)
-            }
-            if (changed) {
-                ValueCell.update(tMarker, tMarker.ref.value)
-            }
-            return changed
-        },
-        destroy() {
-            // TODO
-            renderObject = undefined
-        }
-    }
+        updateValues: Points.updateValues
+    })
 }
 
 // lines
@@ -383,139 +256,16 @@ export type UnitsLinesProps = typeof DefaultUnitsLinesProps
 export interface UnitsLinesVisualBuilder<P extends UnitsLinesProps> extends UnitsVisualBuilder<P, Lines> { }
 
 export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> {
-    const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
-    const updateState = VisualUpdateState.create()
-
-    let renderObject: LinesRenderObject | undefined
-    let currentProps: P
-    let lines: Lines
-    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)
-        lines = includesUnitKind(currentProps.unitKinds, unit)
-            ? await createGeometry(ctx, unit, currentStructure, currentProps, lines)
-            : Lines.createEmpty(lines)
-
-        // TODO create empty location iterator when not in unitKinds
-        locationIt = createLocationIterator(group)
-        renderObject = await createUnitsLinesRenderObject(ctx, group, lines, 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.updateSize = 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) {
-            lines = includesUnitKind(newProps.unitKinds, unit)
-                ? await createGeometry(ctx, unit, currentStructure, newProps, lines)
-                : Lines.createEmpty(lines)
-            ValueCell.update(renderObject.values.drawCount, lines.lineCount * 2 * 3)
-            updateState.updateColor = true
-        }
-
-        if (updateState.updateSize) {
-            await createSizes(ctx, locationIt, newProps, renderObject.values)
-        }
-
-        if (updateState.updateColor) {
-            await createColors(ctx, locationIt, newProps, renderObject.values)
-        }
-
-        // TODO why do I need to cast here?
-        Lines.updateValues(renderObject.values, newProps as UnitsLinesProps)
-        updateRenderableState(renderObject.state, newProps as UnitsLinesProps)
-
-        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) {
-            if (!renderObject) return false
-            const { tMarker } = renderObject.values
-            const { groupCount, instanceCount } = locationIt
-
-            function apply(interval: Interval) {
-                const start = Interval.start(interval)
-                const end = Interval.end(interval)
-                return applyMarkerAction(tMarker.ref.value.array, start, end, action)
-            }
-
-            let changed = false
-            if (isEveryLoci(loci)) {
-                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
-            } else {
-                changed = mark(loci, currentGroup, apply)
-            }
-            if (changed) {
-                ValueCell.update(tMarker, tMarker.ref.value)
-            }
-            return changed
+    return UnitsVisual({
+        ...builder,
+        createEmptyGeometry: Lines.createEmpty,
+        createRenderObject: createUnitsLinesRenderObject,
+        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
+            builder.setUpdateState(state, newProps, currentProps)
+            if (sizeChanged(currentProps, newProps)) state.updateSize = true
         },
-        destroy() {
-            // TODO
-            renderObject = undefined
-        }
-    }
+        updateValues: Lines.updateValues
+    })
 }
 
 // direct-volume
@@ -526,132 +276,17 @@ export const UnitsDirectVolumeParams = {
 }
 export const DefaultUnitsDirectVolumeProps = paramDefaultValues(UnitsDirectVolumeParams)
 export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps
-export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume2d | DirectVolume3d> { }
+export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume> { }
 
 export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> {
-    const { defaultProps, createGeometry, createLocationIterator, getLoci, setUpdateState } = builder
-    const updateState = VisualUpdateState.create()
-
-    let renderObject: DirectVolume2dRenderObject | DirectVolume3dRenderObject | undefined
-    let currentProps: P
-    let directVolume: DirectVolume2d | DirectVolume3d
-    let currentGroup: Unit.SymmetryGroup
-    let currentStructure: Structure
-    let locationIt: LocationIterator
-    let currentConformationId: UUID
-
-    async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
-        const { webgl } = props
-        if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props')
-
-        currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
-        currentGroup = group
-
-        const unit = group.units[0]
-        currentConformationId = Unit.conformationId(unit)
-        directVolume = includesUnitKind(currentProps.unitKinds, unit)
-            ? await createGeometry(ctx, unit, currentStructure, currentProps, directVolume)
-            : (webgl.isWebGL2 ?
-                DirectVolume2d.createEmpty(directVolume as DirectVolume2d) :
-                DirectVolume3d.createEmpty(directVolume as DirectVolume3d))
-
-        console.log('directVolume', 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> = {}) {
-        const { webgl } = props
-        if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props')
-
-        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 createGeometry(ctx, unit, currentStructure, newProps, directVolume)
-                : (webgl.isWebGL2 ?
-                    DirectVolume2d.createEmpty(directVolume as DirectVolume2d) :
-                    DirectVolume3d.createEmpty(directVolume as DirectVolume3d))
-            updateState.updateColor = true
-        }
-
-        // if (updateState.updateColor) {
-        //     await createColors(ctx, locationIt, newProps, renderObject.values)
-        // }
-
-        if (renderObject.type === 'direct-volume-2d') {
-            DirectVolume2d.updateValues(renderObject.values, newProps)
-        } else {
-            DirectVolume3d.updateValues(renderObject.values, newProps)
-        }
-        updateRenderableState(renderObject.state, newProps)
-
-        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)
-            }
+    return UnitsVisual({
+        ...builder,
+        createEmptyGeometry: DirectVolume.createEmpty,
+        createRenderObject: createUnitsDirectVolumeRenderObject,
+        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
+            builder.setUpdateState(state, newProps, currentProps)
+            if (sizeChanged(currentProps, newProps)) state.createGeometry = true
         },
-        getLoci(pickingId: PickingId) {
-            return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            // TODO
-            return false
-        },
-        destroy() {
-            // TODO
-            renderObject = undefined
-        }
-    }
+        updateValues: DirectVolume.updateValues
+    })
 }

+ 1 - 1
src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts

@@ -74,7 +74,7 @@ export type CarbohydrateLinkProps = typeof DefaultCarbohydrateLinkProps
 export function CarbohydrateLinkVisual(): ComplexVisual<CarbohydrateLinkProps> {
     return ComplexMeshVisual<CarbohydrateLinkProps>({
         defaultProps: DefaultCarbohydrateLinkProps,
-        createMesh: createCarbohydrateLinkCylinderMesh,
+        createGeometry: createCarbohydrateLinkCylinderMesh,
         createLocationIterator: CarbohydrateLinkIterator,
         getLoci: getLinkLoci,
         mark: markLink,

+ 1 - 1
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -157,7 +157,7 @@ export type CarbohydrateSymbolProps = typeof DefaultCarbohydrateSymbolProps
 export function CarbohydrateSymbolVisual(): ComplexVisual<CarbohydrateSymbolProps> {
     return ComplexMeshVisual<CarbohydrateSymbolProps>({
         defaultProps: DefaultCarbohydrateSymbolProps,
-        createMesh: createCarbohydrateSymbolMesh,
+        createGeometry: createCarbohydrateSymbolMesh,
         createLocationIterator: CarbohydrateElementIterator,
         getLoci: getCarbohydrateLoci,
         mark: markCarbohydrate,

+ 1 - 1
src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts

@@ -62,7 +62,7 @@ export type CrossLinkRestraintProps = typeof DefaultCrossLinkRestraintProps
 export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintProps> {
     return ComplexMeshVisual<CrossLinkRestraintProps>({
         defaultProps: DefaultCrossLinkRestraintProps,
-        createMesh: createCrossLinkRestraintCylinderMesh,
+        createGeometry: createCrossLinkRestraintCylinderMesh,
         createLocationIterator: CrossLinkRestraintIterator,
         getLoci: getLinkLoci,
         mark: markLink,

+ 3 - 7
src/mol-geo/representation/structure/visual/gaussian-density-volume.ts

@@ -11,9 +11,9 @@ import { UnitsDirectVolumeVisual, UnitsDirectVolumeParams } from '../units-visua
 import { StructureElementIterator, getElementLoci, markElement } from './util/element';
 import { GaussianDensityProps, GaussianDensityParams, computeUnitGaussianDensityTexture } from 'mol-model/structure/structure/unit/gaussian-density';
 import { paramDefaultValues } from 'mol-view/parameter';
-import { DirectVolume2d, DirectVolume3d } from '../../../geometry/direct-volume/direct-volume';
+import { DirectVolume } from '../../../geometry/direct-volume/direct-volume';
 
-async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume2d | DirectVolume3d): Promise<DirectVolume2d | DirectVolume3d> {
+async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
     const { webgl } = props
     if (webgl === undefined) throw new Error('createGaussianDensityVolume requires `webgl` in props')
 
@@ -22,11 +22,7 @@ async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, stru
     const densityTextureData = await computeUnitGaussianDensityTexture(unit, p, oldTexture).runInContext(ctx)
     const { transform, texture, bbox, gridDimension } = densityTextureData
 
-    directVolume = texture.depth === 0 ?
-        DirectVolume2d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume2d) :
-        DirectVolume3d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume3d)
-
-    return directVolume;
+    return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume)
 }
 
 export const GaussianDensityVolumeParams = {

+ 2 - 44
src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, Structure, StructureElement, ElementIndex } from 'mol-model/structure';
+import { Unit, Structure } from 'mol-model/structure';
 import { UnitsVisual, VisualUpdateState } from '..';
 import { RuntimeContext } from 'mol-task'
 import { Mesh } from '../../../geometry/mesh/mesh';
@@ -13,11 +13,9 @@ import { StructureElementIterator, getElementLoci, markElement } from './util/el
 import { computeMarchingCubesMesh } from '../../../util/marching-cubes/algorithm';
 import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure/structure/unit/gaussian-density';
 import { paramDefaultValues } from 'mol-view/parameter';
-import { SizeTheme } from 'mol-view/theme/size';
-import { OrderedSet } from 'mol-data/int';
 
 async function createGaussianSurfaceMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
-    const { smoothness, radiusOffset } = props
+    const { smoothness } = props
     const { transform, field, idField } = await unit.computeGaussianDensity(props, ctx)
 
     const params = {
@@ -28,46 +26,6 @@ async function createGaussianSurfaceMesh(ctx: RuntimeContext, unit: Unit, struct
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx)
 
     Mesh.transformImmediate(surface, transform)
-
-    if (props.useGpu) {
-        console.time('find max element radius')
-        const { elements } = unit
-        const n = OrderedSet.size(elements)
-        const l = StructureElement.create(unit)
-        const sizeTheme = SizeTheme({ name: 'physical' })
-        const radius = (index: number) => {
-            l.element = index as ElementIndex
-            return sizeTheme.size(l)
-        }
-        let maxRadius = 0
-        for (let i = 0; i < n; ++i) {
-            const r = radius(OrderedSet.getAt(elements, i)) + radiusOffset
-            if (maxRadius < r) maxRadius = r
-        }
-        console.timeEnd('find max element radius')
-
-        console.time('find closest element for vertices')
-        const { lookup3d } = unit
-
-        const { vertexCount, vertexBuffer, groupBuffer } = surface
-        const vertices = vertexBuffer.ref.value
-        const groups = groupBuffer.ref.value
-        for (let i = 0; i < vertexCount; ++i) {
-            const r = lookup3d.find(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2], maxRadius * 2)
-            let minDsq = Infinity
-            let group = 0
-            for (let j = 0, jl = r.count; j < jl; ++j) {
-                const dSq = r.squaredDistances[j]
-                if (dSq < minDsq) {
-                    minDsq = dSq
-                    group = r.indices[j]
-                }
-            }
-            groups[i] = group
-        }
-        console.timeEnd('find closest element for vertices')
-    }
-
     Mesh.computeNormalsImmediate(surface)
     Mesh.uniformTriangleGroup(surface)
 

+ 1 - 1
src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts

@@ -62,7 +62,7 @@ export type InterUnitLinkProps = typeof DefaultInterUnitLinkProps
 export function InterUnitLinkVisual(): ComplexVisual<InterUnitLinkProps> {
     return ComplexMeshVisual<InterUnitLinkProps>({
         defaultProps: DefaultInterUnitLinkProps,
-        createMesh: createInterUnitLinkCylinderMesh,
+        createGeometry: createInterUnitLinkCylinderMesh,
         createLocationIterator: LinkIterator.fromStructure,
         getLoci: getLinkLoci,
         mark: markLink,

+ 42 - 10
src/mol-geo/representation/structure/visual/util/common.ts

@@ -8,14 +8,16 @@ 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, createDirectVolume2dRenderObject, createDirectVolume3dRenderObject } 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 { DirectVolume2d, DirectVolume3d } from '../../../../geometry/direct-volume/direct-volume';
+import { DirectVolume } from '../../../../geometry/direct-volume/direct-volume';
+import { SizeProps } from 'mol-geo/geometry/size-data';
+import { ColorProps } from 'mol-geo/geometry/color-data';
 
 export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) {
     const unitCount = units.length
@@ -27,6 +29,39 @@ export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformDat
     return createTransform(array, unitCount, transformData)
 }
 
+export const UnitKindInfo = {
+    'atomic': {},
+    'spheres': {},
+    'gaussians': {},
+}
+export type UnitKind = keyof typeof UnitKindInfo
+export const UnitKindNames = Object.keys(UnitKindInfo)
+export const UnitKindOptions = UnitKindNames.map(n => [n, n] as [UnitKind, string])
+
+export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
+    for (let i = 0, il = unitKinds.length; i < il; ++i) {
+        if (Unit.isAtomic(unit) && unitKinds[i] === 'atomic') return true
+        if (Unit.isSpheres(unit) && unitKinds[i] === 'spheres') return true
+        if (Unit.isGaussians(unit) && unitKinds[i] === 'gaussians') return true
+    }
+    return false
+}
+
+export function sizeChanged(oldProps: SizeProps, newProps: SizeProps) {
+    return (
+        oldProps.sizeTheme !== newProps.sizeTheme ||
+        oldProps.sizeValue !== newProps.sizeValue ||
+        oldProps.sizeFactor !== newProps.sizeFactor
+    )
+}
+
+export function colorChanged(oldProps: ColorProps, newProps: ColorProps) {
+    return (
+        oldProps.colorTheme !== newProps.colorTheme ||
+        oldProps.colorValue !== newProps.colorValue
+    )
+}
+
 // mesh
 
 type StructureMeshProps = Mesh.Props & StructureProps
@@ -63,20 +98,17 @@ type StructureLinesProps = Lines.Props & StructureProps
 export async function createUnitsLinesRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, lines: Lines, locationIt: LocationIterator, props: StructureLinesProps) {
     const transform = createUnitsTransform(group)
     const values = await Lines.createValues(ctx, lines, transform, locationIt, props)
-    console.log('values', values)
     const state = createRenderableState(props)
     return createLinesRenderObject(values, state)
 }
 
 // direct-volume
 
-type StructureDirectVolumeProps = DirectVolume2d.Props & DirectVolume3d.Props & StructureProps
+type StructureDirectVolumeProps = DirectVolume.Props & StructureProps
 
-export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume2d | DirectVolume3d, locationIt: LocationIterator, props: StructureDirectVolumeProps) {
-    // TODO transform support
-    // const transform = createUnitsTransform(group)
+export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume, locationIt: LocationIterator, props: StructureDirectVolumeProps) {
+    const transform = createUnitsTransform(group)
+    const values = await DirectVolume.createValues(ctx, directVolume, transform, locationIt, props)
     const state = createRenderableState(props)
-    return directVolume.kind === 'direct-volume-2d' ?
-        createDirectVolume2dRenderObject(await DirectVolume2d.createValues(ctx, directVolume, props), state) :
-        createDirectVolume3dRenderObject(await DirectVolume3d.createValues(ctx, directVolume, props), state)
+    return createDirectVolumeRenderObject(values, state)
 }

+ 21 - 22
src/mol-geo/representation/volume/direct-volume.ts

@@ -7,18 +7,20 @@
 import { VolumeData } from 'mol-model/volume'
 import { RuntimeContext } from 'mol-task'
 import { VolumeVisual, VolumeRepresentation } from '.';
-import { DirectVolume2dRenderObject, createDirectVolume2dRenderObject, DirectVolume3dRenderObject, createDirectVolume3dRenderObject } from 'mol-gl/render-object';
+import { DirectVolumeRenderObject, createDirectVolumeRenderObject } from 'mol-gl/render-object';
 import { PickingId } from '../../geometry/picking';
 import { MarkerAction } from '../../geometry/marker-data';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { createRenderableState, updateRenderableState, Geometry } from '../../geometry/geometry';
 import { paramDefaultValues } from 'mol-view/parameter';
-import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume';
+import { DirectVolume } from '../../geometry/direct-volume/direct-volume';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Box3D } from 'mol-math/geometry';
 import { Context } from 'mol-gl/webgl/context';
-import { DirectVolume3dValues, DirectVolume2dValues } from 'mol-gl/renderable/direct-volume';
 import { createTexture } from 'mol-gl/webgl/texture';
+import { LocationIterator } from 'mol-geo/util/location-iterator';
+import { NullLocation } from 'mol-model/location';
+import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
 
 function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
     const bbox = Box3D.empty()
@@ -83,7 +85,7 @@ function createVolumeTexture2d(volume: VolumeData, maxTextureSize: number) {
     return textureImage
 }
 
-export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume2d) {
+export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume) {
     const gridDimension = volume.data.space.dimensions as Vec3
     const textureImage = createVolumeTexture2d(volume, webgl.maxTextureSize)
     // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
@@ -96,7 +98,7 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume
     const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
     texture.load(textureImage)
 
-    return DirectVolume2d.create(bbox, dim, transform, texture, directVolume)
+    return DirectVolume.create(bbox, dim, transform, texture, directVolume)
 }
 
 // 3d volume texture
@@ -123,7 +125,7 @@ function createVolumeTexture3d(volume: VolumeData) {
     return textureVolume
 }
 
-export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume3d) {
+export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume) {
     const gridDimension = volume.data.space.dimensions as Vec3
     const textureVolume = createVolumeTexture3d(volume)
     const transform = VolumeData.getGridToCartesianTransform(volume)
@@ -132,23 +134,23 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume
     const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
     texture.load(textureVolume)
 
-    return DirectVolume3d.create(bbox, gridDimension, transform, texture, directVolume)
+    return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume)
 }
 
 //
 
 export const DirectVolumeParams = {
     ...Geometry.Params,
-    ...DirectVolume2d.Params
+    ...DirectVolume.Params
 }
 export const DefaultDirectVolumeProps = paramDefaultValues(DirectVolumeParams)
 export type DirectVolumeProps = typeof DefaultDirectVolumeProps
 
 export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
     let currentProps = DefaultDirectVolumeProps
-    let renderObject: DirectVolume2dRenderObject | DirectVolume3dRenderObject
+    let renderObject: DirectVolumeRenderObject
     let currentVolume: VolumeData
-    let directVolume: DirectVolume2d | DirectVolume3d
+    let directVolume: DirectVolume
 
     async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<DirectVolumeProps> = {}) {
         const { webgl } = props
@@ -160,16 +162,17 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
         }
 
         const state = createRenderableState(currentProps)
+        const locationIt = LocationIterator(1, 1, () => NullLocation)
+        const transform = createIdentityTransform()
 
         if (webgl.isWebGL2) {
-            console.log('creating 3d volume')
-            directVolume = await createDirectVolume3d(ctx, webgl, volume, directVolume as DirectVolume3d)
-            const values = await DirectVolume3d.createValues(ctx, directVolume as DirectVolume3d, currentProps)
-            renderObject = createDirectVolume3dRenderObject(values, state)
+            directVolume = await createDirectVolume3d(ctx, webgl, volume, directVolume)
+            const values = await DirectVolume.createValues(ctx, directVolume, transform, locationIt, currentProps)
+            renderObject = createDirectVolumeRenderObject(values, state)
         } else {
-            directVolume = await createDirectVolume2d(ctx, webgl, volume, directVolume as DirectVolume2d)
-            const values = await DirectVolume2d.createValues(ctx, directVolume as DirectVolume2d, currentProps)
-            renderObject = createDirectVolume2dRenderObject(values, state)
+            directVolume = await createDirectVolume2d(ctx, webgl, volume, directVolume)
+            const values = await DirectVolume.createValues(ctx, directVolume, transform, locationIt, currentProps)
+            renderObject = createDirectVolumeRenderObject(values, state)
         }
     }
 
@@ -182,11 +185,7 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
             // newProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative)
         }
 
-        if (webgl.isWebGL2) {
-            DirectVolume3d.updateValues(renderObject.values as DirectVolume3dValues, newProps)
-        } else {
-            DirectVolume2d.updateValues(renderObject.values as DirectVolume2dValues, newProps)
-        }
+        DirectVolume.updateValues(renderObject.values, newProps)
         updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps

+ 6 - 11
src/mol-gl/render-object.ts

@@ -9,7 +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 { DirectVolume2dValues, DirectVolume2dRenderable, DirectVolume3dValues, DirectVolume3dRenderable } from './renderable/direct-volume';
+import { DirectVolumeValues, DirectVolumeRenderable } from './renderable/direct-volume';
 
 const getNextId = idFactory(0, 0x7FFFFFFF)
 
@@ -18,10 +18,9 @@ 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 interface DirectVolume2dRenderObject extends BaseRenderObject { type: 'direct-volume-2d', values: DirectVolume2dValues }
-export interface DirectVolume3dRenderObject extends BaseRenderObject { type: 'direct-volume-3d', values: DirectVolume3dValues }
+export interface DirectVolumeRenderObject extends BaseRenderObject { type: 'direct-volume', values: DirectVolumeValues }
 
-export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject | DirectVolume2dRenderObject | DirectVolume3dRenderObject
+export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject | DirectVolumeRenderObject
 
 //
 
@@ -37,11 +36,8 @@ 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 createDirectVolume2dRenderObject(values: DirectVolume2dValues, state: RenderableState): DirectVolume2dRenderObject {
-    return { id: getNextId(), type: 'direct-volume-2d', values, state }
-}
-export function createDirectVolume3dRenderObject(values: DirectVolume3dValues, state: RenderableState): DirectVolume3dRenderObject {
-    return { id: getNextId(), type: 'direct-volume-3d', 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> {
@@ -50,7 +46,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-2d': return DirectVolume2dRenderable(ctx, o.id, o.values, o.state)
-        case 'direct-volume-3d': return DirectVolume3dRenderable(ctx, o.id, o.values, o.state)
+        case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values, o.state)
     }
 }

+ 26 - 41
src/mol-gl/renderable/direct-volume.ts

@@ -11,7 +11,22 @@ import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema
 import { DirectVolumeShaderCode } from '../shader-code';
 import { ValueCell } from 'mol-util';
 
-export const DirectVolumeBaseSchema = {
+export const DirectVolumeSchema = {
+    aColor: AttributeSpec('float32', 3, 0), // TODO not used, just for type checking
+    uColor: UniformSpec('v3'),
+    uColorTexDim: UniformSpec('v2'),
+    tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
+    dColorType: DefineSpec('string', ['uniform', 'instance', 'group', 'group_instance']),
+
+    uMarkerTexDim: UniformSpec('v2'),
+    tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
+
+    uInstanceCount: UniformSpec('i'),
+    uGroupCount: UniformSpec('i'),
+
+    aInstance: AttributeSpec('float32', 1, 1),
+    aTransform: AttributeSpec('float32', 16, 1),
+
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
 
@@ -30,54 +45,24 @@ export const DirectVolumeBaseSchema = {
     uGridDim: UniformSpec('v3'),
     dRenderMode: DefineSpec('string', ['isosurface', 'volume']),
     tTransferTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'),
+
+    dGridTexType: DefineSpec('string', ['2d', '3d']),
+    uGridTexDim: UniformSpec('v3'),
+    tGridTex: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
 }
-export type DirectVolumeBaseSchema = typeof DirectVolumeBaseSchema
-export type DirectVolumeBaseValues = Values<DirectVolumeBaseSchema>
+export type DirectVolumeSchema = typeof DirectVolumeSchema
+export type DirectVolumeValues = Values<DirectVolumeSchema>
 
-function getInternalValues(ctx: Context, id: number): InternalValues {
-    return {
+export function DirectVolumeRenderable(ctx: Context, id: number, values: DirectVolumeValues, state: RenderableState): Renderable<DirectVolumeValues> {
+    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema }
+    const internalValues: InternalValues = {
         uObjectId: ValueCell.create(id)
     }
-}
-
-function DirectVolumeRenderable<T extends DirectVolumeBaseValues, S extends DirectVolumeBaseSchema>(ctx: Context, id: number, values: T, state: RenderableState, schema: S): Renderable<T> {
-    const fullSchema = Object.assign({}, GlobalUniformSchema, InternalSchema, schema)
-    const internalValues = getInternalValues(ctx, id)
-    const fullValues = Object.assign({}, values, internalValues)
     const shaderCode = DirectVolumeShaderCode
-    const renderItem = createRenderItem(ctx, 'triangles', shaderCode, fullSchema, fullValues)
+    const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues })
     const renderable = createRenderable(renderItem, values, state);
 
     Object.defineProperty(renderable, 'opaque', { get: () => false });
 
     return renderable
-}
-
-// via 2d texture
-
-export const DirectVolume2dSchema = {
-    ...DirectVolumeBaseSchema,
-    dGridTexType: DefineSpec('string', ['2d']),
-    uGridTexDim: UniformSpec('v2'),
-    tGridTex: TextureSpec('texture2d', 'rgba', 'ubyte', 'linear'),
-}
-export type DirectVolume2dSchema = typeof DirectVolume2dSchema
-export type DirectVolume2dValues = Values<DirectVolume2dSchema>
-
-export function DirectVolume2dRenderable(ctx: Context, id: number, values: DirectVolume2dValues, state: RenderableState): Renderable<DirectVolume2dValues> {
-    return DirectVolumeRenderable(ctx, id, values, state, DirectVolume2dSchema)
-}
-
-// via 3d texture
-
-export const DirectVolume3dSchema = {
-    ...DirectVolumeBaseSchema,
-    dGridTexType: DefineSpec('string', ['3d']),
-    tGridTex: TextureSpec('texture3d', 'rgba', 'ubyte', 'linear'),
-}
-export type DirectVolume3dSchema = typeof DirectVolume3dSchema
-export type DirectVolume3dValues = Values<DirectVolume3dSchema>
-
-export function DirectVolume3dRenderable(ctx: Context, id: number, values: DirectVolume3dValues, state: RenderableState): Renderable<DirectVolume3dValues> {
-    return DirectVolumeRenderable(ctx, id, values, state, DirectVolume3dSchema)
 }

+ 6 - 2
src/mol-gl/renderable/gaussian-density.ts

@@ -7,7 +7,7 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable'
 import { Context } from '../webgl/context';
 import { createRenderItem } from '../webgl/render-item';
-import { AttributeSpec, Values, UniformSpec, ValueSpec, DefineSpec } from './schema';
+import { AttributeSpec, Values, UniformSpec, ValueSpec, DefineSpec, TextureSpec } from './schema';
 import { GaussianDensityShaderCode } from '../shader-code';
 
 export const GaussianDensitySchema = {
@@ -16,6 +16,7 @@ export const GaussianDensitySchema = {
 
     aRadius: AttributeSpec('float32', 1, 0),
     aPosition: AttributeSpec('float32', 3, 0),
+    aGroup: AttributeSpec('float32', 1, 0),
 
     uCurrentSlice: UniformSpec('f'),
     uCurrentX: UniformSpec('f'),
@@ -24,9 +25,12 @@ export const GaussianDensitySchema = {
     uBboxMax: UniformSpec('v3'),
     uBboxSize: UniformSpec('v3'),
     uGridDim: UniformSpec('v3'),
+    uGridTexDim: UniformSpec('v3'),
     uAlpha: UniformSpec('f'),
+    tMinDistanceTex: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
 
-    dDrawBuffers: DefineSpec('number'),
+    dGridTexType: DefineSpec('string', ['2d', '3d']),
+    dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']),
 }
 export type GaussianDensitySchema = typeof GaussianDensitySchema
 export type GaussianDensityValues = Values<GaussianDensitySchema>

+ 2 - 1
src/mol-gl/renderable/schema.ts

@@ -43,6 +43,7 @@ export type KindValue = {
     'image-float32': TextureImage<Float32Array>
     'volume-uint8': TextureVolume<Uint8Array>
     'volume-float32': TextureVolume<Float32Array>
+    'texture': Texture
     'texture2d': Texture
     'texture3d': Texture
 
@@ -59,7 +60,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
     const defineValues: DefineValues = {}
     const textureValues: TextureValues = {}
     const uniformValues: UniformValues = {}
-    Object.keys(values).forEach(k => {
+    Object.keys(schema).forEach(k => {
         if (schema[k].type === 'attribute') attributeValues[k] = values[k]
         if (schema[k].type === 'define') defineValues[k] = values[k]
         if (schema[k].type === 'texture') textureValues[k] = values[k]

+ 41 - 14
src/mol-gl/shader-code.ts

@@ -14,39 +14,50 @@ export type DefineValues = { [k: string]: ValueCell<DefineType> }
 
 const shaderCodeId = idFactory()
 
+export interface ShaderExtensions {
+    readonly standardDerivatives: boolean
+    readonly fragDepth: boolean
+}
+
 export interface ShaderCode {
-    id: number
-    vert: string
-    frag: string
+    readonly id: number
+    readonly vert: string
+    readonly frag: string
+    readonly extensions: ShaderExtensions
 }
 
-export function ShaderCode(vert: string, frag: string): ShaderCode {
-    return { id: shaderCodeId(), vert, frag }
+export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensions): ShaderCode {
+    return { id: shaderCodeId(), vert, frag, extensions }
 }
 
 export const PointsShaderCode = ShaderCode(
     require('mol-gl/shader/points.vert'),
-    require('mol-gl/shader/points.frag')
+    require('mol-gl/shader/points.frag'),
+    { standardDerivatives: false, fragDepth: false }
 )
 
 export const LinesShaderCode = ShaderCode(
     require('mol-gl/shader/lines.vert'),
-    require('mol-gl/shader/lines.frag')
+    require('mol-gl/shader/lines.frag'),
+    { standardDerivatives: false, fragDepth: false }
 )
 
 export const MeshShaderCode = ShaderCode(
     require('mol-gl/shader/mesh.vert'),
-    require('mol-gl/shader/mesh.frag')
+    require('mol-gl/shader/mesh.frag'),
+    { standardDerivatives: true, fragDepth: false }
 )
 
 export const GaussianDensityShaderCode = ShaderCode(
     require('mol-gl/shader/gaussian-density.vert'),
-    require('mol-gl/shader/gaussian-density.frag')
+    require('mol-gl/shader/gaussian-density.frag'),
+    { standardDerivatives: false, fragDepth: false }
 )
 
 export const DirectVolumeShaderCode = ShaderCode(
     require('mol-gl/shader/direct-volume.vert'),
-    require('mol-gl/shader/direct-volume.frag')
+    require('mol-gl/shader/direct-volume.frag'),
+    { standardDerivatives: false, fragDepth: true }
 )
 
 export type ShaderDefines = {
@@ -74,8 +85,20 @@ function getDefinesCode (defines: ShaderDefines) {
     return lines.join('\n') + '\n'
 }
 
-const glsl100FragPrefix = `#extension GL_OES_standard_derivatives : enable
-`
+function getGlsl100FragPrefix(ctx: Context, extensions: ShaderExtensions) {
+    const prefix: string[] = []
+    if (extensions.standardDerivatives) {
+        prefix.push('#extension GL_OES_standard_derivatives : enable')
+        prefix.push('#define enabledStandardDerivatives')
+    }
+    if (extensions.fragDepth) {
+        if (ctx.extensions.fragDepth) {
+            prefix.push('#extension GL_EXT_frag_depth : enable')
+            prefix.push('#define enabledFragDepth')
+        }
+    }
+    return prefix.join('\n') + '\n'
+}
 
 const glsl300VertPrefix = `#version 300 es
 #define attribute in
@@ -89,16 +112,20 @@ layout(location = 0) out highp vec4 out_FragColor;
 #define gl_FragColor out_FragColor
 #define gl_FragDepthEXT gl_FragDepth
 #define texture2D texture
+
+#define enabledStandardDerivatives
+#define enabledFragDepth
 `
 
 export function addShaderDefines(ctx: Context, defines: ShaderDefines, shaders: ShaderCode): ShaderCode {
     const { isWebGL2 } = ctx
     const header = getDefinesCode(defines)
     const vertPrefix = isWebGL2 ? glsl300VertPrefix : ''
-    const fragPrefix = isWebGL2 ? glsl300FragPrefix : glsl100FragPrefix
+    const fragPrefix = isWebGL2 ? glsl300FragPrefix : getGlsl100FragPrefix(ctx, shaders.extensions)
     return {
         id: shaderCodeId(),
         vert: `${vertPrefix}${header}${shaders.vert}`,
-        frag: `${fragPrefix}${header}${shaders.frag}`
+        frag: `${fragPrefix}${header}${shaders.frag}`,
+        extensions: shaders.extensions
     }
 }

+ 2 - 2
src/mol-gl/shader/chunks/apply-marker-color.glsl

@@ -1,6 +1,6 @@
-float marker = vMarker * 256.0;
+float marker = vMarker * 255.0;
 if (marker > 0.1) {
-    if (mod(marker, 2.0) < 0.1) {
+    if (mod(marker, 2.0) > 0.1) {
         gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3);
     } else {
         gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3);

+ 3 - 3
src/mol-gl/shader/chunks/assign-color-varying.glsl

@@ -7,9 +7,9 @@
 #elif defined(dColorType_groupInstance)
     vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + aGroup, uColorTexDim).rgb;
 #elif defined(dColorType_objectPicking)
-    vColor = encodeIdRGBA(float(uObjectId));
+    vColor = vec4(encodeIdRGB(float(uObjectId)), 1.0);
 #elif defined(dColorType_instancePicking)
-    vColor = encodeIdRGBA(aInstance);
+    vColor = vec4(encodeIdRGB(aInstance), 1.0);
 #elif defined(dColorType_groupPicking)
-    vColor = encodeIdRGBA(aGroup);
+    vColor = vec4(encodeIdRGB(aGroup), 1.0);
 #endif

+ 1 - 1
src/mol-gl/shader/chunks/color-vert-params.glsl

@@ -9,5 +9,5 @@
     uniform sampler2D tColor;
 #elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
     varying vec4 vColor;
-    #pragma glslify: encodeIdRGBA = require(../utils/encode-id-rgba.glsl)
+    #pragma glslify: encodeIdRGB = require(../utils/encode-id-rgb.glsl)
 #endif

+ 111 - 52
src/mol-gl/shader/direct-volume.frag

@@ -5,15 +5,11 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-#if defined(dGridTexType_2d)
-    precision mediump sampler2D;
-#elif defined(dGridTexType_3d)
-    precision mediump sampler3D;
-#endif
 precision highp float;
 
 varying vec3 unitCoord;
 varying vec3 origPos;
+varying float instance;
 
 uniform float uAlpha;
 uniform mat4 uInvView;
@@ -21,42 +17,66 @@ uniform float uIsoValue;
 uniform vec3 uGridDim;
 uniform sampler2D tTransferTex;
 
+uniform int uObjectId;
+uniform int uInstanceCount;
+uniform int uGroupCount;
+
+uniform vec3 uHighlightColor;
+uniform vec3 uSelectColor;
+uniform vec2 uMarkerTexDim;
+uniform sampler2D tMarker;
+
 #if defined(dGridTexType_2d)
+    precision mediump sampler2D;
     uniform sampler2D tGridTex;
-    uniform vec2 uGridTexDim;
+    uniform vec3 uGridTexDim;
 #elif defined(dGridTexType_3d)
+    precision mediump sampler3D;
     uniform sampler3D tGridTex;
 #endif
 
-#if defined(dGridTexType_2d)
-    // 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));
-    }
+#if defined(dColorType_uniform)
+    uniform vec3 uColor;
+#elif defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
+    uniform vec2 uColorTexDim;
+    uniform sampler2D tColor;
+#endif
+
+#pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl)
+#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
+#pragma glslify: decodeIdRGB = require(./utils/decode-id-rgb.glsl)
+#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl)
+#pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl)
+
+// uniform vec3 uLightPosition;
+uniform vec3 uLightColor;
+uniform vec3 uLightAmbient;
+uniform mat4 uView;
 
+#pragma glslify: attenuation = require(./utils/attenuation.glsl)
+#pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl)
+#pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl)
+
+const float specularScale = 0.15;
+const float shininess = 200.0;
+const float roughness = 100.0;
+const float albedo = 0.95;
+
+#if defined(dGridTexType_2d)
     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);
+        return texture3dFrom2dLinear(tGridTex, pos, uGridDim, uGridTexDim.xy);
+    }
+    vec4 textureGroup(vec3 pos) {
+        vec3 nearestPos = floor(pos * uGridDim + 0.5) / uGridDim + 0.5 / uGridDim;
+        return texture3dFrom2dNearest(tGridTex, nearestPos, uGridDim, uGridTexDim.xy);
     }
 #elif defined(dGridTexType_3d)
     vec4 textureVal(vec3 pos) {
         return texture(tGridTex, pos);
     }
+    vec4 textureGroup(vec3 pos) {
+        return texelFetch(tGridTex, ivec3(pos * uGridDim), 0);
+    }
 #endif
 
 vec4 transferFunction(float value) {
@@ -64,7 +84,6 @@ vec4 transferFunction(float value) {
 }
 
 const float gradOffset = 0.5;
-const vec3 color = vec3(0.45, 0.55, 0.8);
 
 vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
     vec3 scaleVol = vec3(1.0) / uGridDim;
@@ -78,6 +97,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
         vec3 isoPos;
         float tmp;
 
+        vec3 color = vec3(0.45, 0.55, 0.8);
         vec3 gradient = vec3(1.0);
         vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0);
         vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0);
@@ -103,28 +123,67 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
                 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, viewDir) > 0.0);
-                gradient = (2.0 * d - 1.0) * gradient;
-
-                src.rgb = color.rgb * abs(dot(gradient, viewDir));
-                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;
-                }
+                #if defined(dColorType_objectPicking)
+                    return vec4(encodeIdRGB(float(uObjectId)), 1.0);
+                #elif defined(dColorType_instancePicking)
+                    return vec4(encodeIdRGB(instance), 1.0);
+                #elif defined(dColorType_groupPicking)
+                    float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5);
+                    return vec4(encodeIdRGB(group), 1.0);
+                #else
+                    // 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, viewDir) > 0.0);
+                    gradient = (2.0 * d - 1.0) * gradient;
+
+                    float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5);
+
+                    #if defined(dColorType_instance)
+                        color = readFromTexture(tColor, instance, uColorTexDim).rgb;
+                    #elif defined(dColorType_group)
+                        color = readFromTexture(tColor, group, uColorTexDim).rgb;
+                    #elif defined(dColorType_groupInstance)
+                        color = readFromTexture(tColor, instance * float(uGroupCount) + group, uColorTexDim).rgb;
+                    #endif
+
+                    vec3 L = normalize(viewDir); // light direction
+                    vec3 V = normalize(viewDir); // eye direction
+                    vec3 N = normalize(gradient); // surface normal
+
+                    // compute our diffuse & specular terms
+                    float specular = calculateSpecular(L, V, N, shininess) * specularScale;
+                    vec3 diffuse = uLightColor * calculateDiffuse(L, V, N, roughness, albedo);
+                    vec3 ambient = uLightAmbient;
+
+                    // add the lighting
+                    vec3 finalColor = color.rgb * (diffuse + ambient) + specular;
+
+                    src.rgb = finalColor;
+                    src.a = uAlpha;
+
+                    float marker = readFromTexture(tMarker, instance * float(uGroupCount) + group, uMarkerTexDim).a * 256.0;
+                    if (marker > 0.1) {
+                        if (mod(marker, 2.0) < 0.1) {
+                            src.rgb = mix(uHighlightColor, src.rgb, 0.3);
+                        } else {
+                            src.rgb = mix(uSelectColor, src.rgb, 0.3);
+                        }
+                    }
+
+                    // 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;
+                    }
+                #endif
             }
             prevValue = value;
         #endif
@@ -144,6 +203,6 @@ void main () {
     gl_FragColor = raymarch(startLoc, step, normalize(cameraPos));
     if (length(gl_FragColor.rgb) < 0.00001) discard;
     #if defined(dRenderMode_volume)
-        gl_FragColor.a = uAlpha;
+        gl_FragColor.a *= uAlpha;
     #endif
 }

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

@@ -8,9 +8,12 @@
 precision highp float;
 
 attribute vec3 aPosition;
+attribute mat4 aTransform;
+attribute float aInstance;
 
 varying vec3 unitCoord;
 varying vec3 origPos;
+varying float instance;
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -23,5 +26,6 @@ void main() {
     unitCoord = aPosition + vec3(0.5);
     vec4 mvPosition = uModelView * vec4(unitCoord * uBboxSize + uBboxMin, 1.0);
     origPos = unitCoord * uBboxSize + uBboxMin;
+    instance = aInstance;
     gl_Position = uProjection * mvPosition;
 }

+ 47 - 32
src/mol-gl/shader/gaussian-density.frag

@@ -7,8 +7,22 @@
 
 precision highp float;
 
-varying vec3 position;
-varying float radius;
+varying vec3 vPosition;
+varying float vRadius;
+#if defined(dCalcType_groupId)
+    #if defined(dGridTexType_2d)
+        precision mediump sampler2D;
+        uniform sampler2D tMinDistanceTex;
+        uniform vec3 uGridTexDim;
+    #elif defined(dGridTexType_3d)
+        precision highp sampler3D;
+        uniform sampler3D tMinDistanceTex;
+    #endif
+    varying float vGroup;
+#endif
+
+#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
+#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl)
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -19,40 +33,41 @@ uniform float uCurrentX;
 uniform float uCurrentY;
 uniform float uAlpha;
 
-#if dDrawBuffers >= 4
-    layout(location = 1) out vec4 out1;
-    layout(location = 2) out vec4 out2;
-    layout(location = 3) out vec4 out3;
-#endif
-#if dDrawBuffers >= 8
-    layout(location = 4) out vec4 out4;
-    layout(location = 5) out vec4 out5;
-    layout(location = 6) out vec4 out6;
-    layout(location = 7) out vec4 out7;
+#if defined(dCalcType_groupId)
+    #if defined(dGridTexType_2d)
+        vec4 textureMinDist(vec3 pos) {
+            return texture3dFrom2dNearest(tMinDistanceTex, pos, uGridDim, uGridTexDim.xy);
+        }
+    #elif defined(dGridTexType_3d)
+        vec4 textureMinDist(vec3 pos) {
+            return texture(tMinDistanceTex, pos);
+        }
+    #endif
 #endif
 
-float calcDensity(float x, float y, float z, float radiusSq) {
-    vec3 fragPos = vec3(x, y, z) / uGridDim;
-    float dist = distance(fragPos * uBboxSize, position * uBboxSize);
-    float density = exp(-uAlpha * ((dist * dist) / radiusSq));
-    return density;
-}
-
-const vec3 color = vec3(1.0, 1.0, 1.0);
+// encode distance logarithmically with given maxDistance
+const float maxDistance = 10000.0;
+const float distLogFactor = log(maxDistance + 1.0);
+float encodeDistLog(float dist) { return log(dist + 1.0) / distLogFactor; }
+float decodeDistLog(float logDist) { return exp(logDist * distLogFactor) - 1.0; }
 
 void main() {
-    float radiusSq = radius * radius;
     vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
-    gl_FragColor = vec4(color, calcDensity(v.x, v.y, uCurrentSlice, radiusSq));
-    #if dDrawBuffers >= 4
-        out1 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 1.0, radiusSq));
-        out2 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 2.0, radiusSq));
-        out3 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 3.0, radiusSq));
-    #endif
-    #if dDrawBuffers >= 8
-        out4 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 4.0, radiusSq));
-        out5 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 5.0, radiusSq));
-        out6 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 6.0, radiusSq));
-        out7 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 7.0, radiusSq));
+    vec3 fragPos = vec3(v.x, v.y, uCurrentSlice) / uGridDim;
+    float dist = distance(fragPos * uBboxSize, vPosition * uBboxSize);
+
+    #if defined(dCalcType_density)
+        float radiusSq = vRadius * vRadius;
+        float density = exp(-uAlpha * ((dist * dist) / radiusSq));
+        gl_FragColor = vec4(density);
+    #elif defined(dCalcType_minDistance)
+        gl_FragColor.a = 1.0 - encodeDistLog(dist);
+    #elif defined(dCalcType_groupId)
+        float minDistance = decodeDistLog(1.0 - textureMinDist(fragPos).a);
+        // TODO verify `length(uBboxSize / uGridDim) * 2.0`
+        //      on some machines `* 2.0` is needed while on others `* 0.5` works
+        if (dist > minDistance + length(uBboxSize / uGridDim) * 0.5)
+            discard;
+        gl_FragColor.rgb = encodeIdRGB(vGroup);
     #endif
 }

+ 14 - 6
src/mol-gl/shader/gaussian-density.vert

@@ -10,8 +10,13 @@ precision highp float;
 attribute vec3 aPosition;
 attribute float aRadius;
 
-varying vec3 position;
-varying float radius;
+varying vec3 vPosition;
+varying float vRadius;
+
+#if defined(dCalcType_groupId)
+    attribute float aGroup;
+    varying float vGroup;
+#endif
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -20,9 +25,12 @@ uniform vec3 uGridDim;
 uniform float uCurrentSlice;
 
 void main() {
-    radius = aRadius;
+    vRadius = aRadius;
+    #if defined(dCalcType_groupId)
+        vGroup = aGroup;
+    #endif
     float scale = max(uBboxSize.z, max(uBboxSize.x, uBboxSize.y));
-    gl_PointSize = (radius / scale) * max(uGridDim.x, uGridDim.y) * 6.0;
-    position = (aPosition - uBboxMin) / uBboxSize;
-    gl_Position = vec4(position * 2.0 - 1.0, 1.0);
+    gl_PointSize = (vRadius / scale) * max(uGridDim.x, uGridDim.y) * 6.0;
+    vPosition = (aPosition - uBboxMin) / uBboxSize;
+    gl_Position = vec4(vPosition * 2.0 - 1.0, 1.0);
 }

+ 5 - 5
src/mol-gl/shader/mesh.frag

@@ -15,7 +15,7 @@ uniform vec3 uLightColor;
 uniform vec3 uLightAmbient;
 uniform mat4 uView;
 
-#ifndef dFlatShaded
+#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives)
     varying vec3 vNormal;
 #endif
 
@@ -23,9 +23,9 @@ uniform mat4 uView;
 #pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl)
 #pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl)
 
-const float specularScale = 0.65;
-const float shininess = 100.0;
-const float roughness = 5.0;
+const float specularScale = 0.15;
+const float shininess = 200.0;
+const float roughness = 100.0;
 const float albedo = 0.95;
 
 void main() {
@@ -45,7 +45,7 @@ void main() {
         vec3 V = normalize(vViewPosition); // eye direction
 
         // surface normal
-        #ifdef dFlatShaded
+        #if defined(dFlatShaded) && defined(enabledStandardDerivatives)
             vec3 fdx = dFdx(vViewPosition);
             vec3 fdy = dFdy(vViewPosition);
             vec3 N = -normalize(cross(fdx, fdy));

+ 11 - 0
src/mol-gl/shader/utils/decode-float-rgb.glsl

@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+float decodeFloatRGB(const in vec3 rgb) {
+    return rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0;
+}
+
+#pragma glslify: export(decodeFloatRGB)

+ 0 - 5
src/mol-gl/shader/utils/decode-float-rgba.glsl

@@ -1,5 +0,0 @@
-float decodeFloatRGBA(const in vec4 rgba) {
-    return dot(rgba, vec4(1.0, 1/255.0, 1/65025.0, 1/16581375.0));
-}
-
-#pragma glslify: export(decodeFloatRGBA)

+ 13 - 0
src/mol-gl/shader/utils/decode-id-rgb.glsl

@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+#pragma glslify: decodeFloatRGB = require(../utils/decode-float-rgb.glsl)
+
+float decodeIdRGB(const in vec3 v) {
+	return decodeFloatRGB(v) - 1.0;
+}
+
+#pragma glslify: export(decodeIdRGB)

+ 20 - 0
src/mol-gl/shader/utils/encode-float-rgb.glsl

@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// TODO use myMod and myDiv to fix issues with picking?
+
+vec3 encodeFloatRGB(in float value) {
+    value = clamp(value, 0.0, 16777216.0);
+    vec3 c = vec3(0.0);
+    c.b = mod(value, 256.0);
+    value = floor(value / 256.0);
+    c.g = mod(value, 256.0);
+    value = floor(value / 256.0);
+    c.r = mod(value, 256.0);
+    return c / 255.0;
+}
+
+#pragma glslify: export(encodeFloatRGB)

+ 0 - 12
src/mol-gl/shader/utils/encode-float-rgba.glsl

@@ -1,12 +0,0 @@
-vec4 encodeFloatRGBA(in float value) {
-    value = clamp(value, 0., 16777216.);
-    vec3 c = vec3(0.);
-    c.b = mod(value, 256.);
-    value = floor(value/256.);
-    c.g = mod(value, 256.);
-    value = floor(value/256.);
-    c.r = mod(value, 256.);
-    return vec4(c/255., 1.);
-}
-
-#pragma glslify: export(encodeFloatRGBA)

+ 13 - 0
src/mol-gl/shader/utils/encode-id-rgb.glsl

@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+#pragma glslify: encodeFloatRGB = require(../utils/encode-float-rgb.glsl)
+
+vec3 encodeIdRGB(const in float v) {
+	return encodeFloatRGB(v + 1.0);
+}
+
+#pragma glslify: export(encodeIdRGB)

+ 0 - 7
src/mol-gl/shader/utils/encode-id-rgba.glsl

@@ -1,7 +0,0 @@
-#pragma glslify: encodeFloatRGBA = require(../utils/encode-float-rgba.glsl)
-
-vec4 encodeIdRGBA(const in float v) {
-	return encodeFloatRGBA(v + 1.0);
-}
-
-#pragma glslify: export(encodeIdRGBA)

+ 5 - 2
src/mol-gl/shader/utils/read-from-texture.glsl

@@ -4,9 +4,12 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+float intDiv(float a, float b) { return float(int(a) / int(b)); }
+float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
+
 vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 dim) {
-    float x = mod(i, dim.x);
-    float y = floor(i / dim.x);
+    float x = intMod(i, dim.x);
+    float y = floor(intDiv(i, dim.x));
     vec2 uv = (vec2(x, y) + 0.5) / dim;
     return texture2D(tex, uv);
 }

+ 28 - 0
src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl

@@ -0,0 +1,28 @@
+/**
+ * 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>
+ */
+
+float intDiv(float a, float b) { return float(int(a) / int(b)); }
+float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
+
+vec4 texture3dFrom2dLinear(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
+    float zSlice0 = floor(pos.z * gridDim.z);
+    float column0 = intMod(zSlice0 * gridDim.x, texDim.x) / gridDim.x;
+    float row0 = floor(intDiv(zSlice0 * gridDim.x, texDim.x));
+    vec2 coord0 = (vec2(column0 * gridDim.x, row0 * gridDim.y) + (pos.xy * gridDim.xy)) / texDim;
+    vec4 color0 = texture2D(tex, coord0);
+
+    float zSlice1 = zSlice0 + 1.0;
+    float column1 = intMod(zSlice1 * gridDim.x, texDim.x) / gridDim.x;
+    float row1 = floor(intDiv(zSlice1 * gridDim.x, texDim.x));
+    vec2 coord1 = (vec2(column1 * gridDim.x, row1 * gridDim.y) + (pos.xy * gridDim.xy)) / texDim;
+    vec4 color1 = texture2D(tex, coord1);
+
+    float delta0 = abs((pos.z * gridDim.z) - zSlice0);
+    return mix(color0, color1, delta0);
+}
+
+#pragma glslify: export(texture3dFrom2dLinear)

+ 19 - 0
src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl

@@ -0,0 +1,19 @@
+/**
+ * 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>
+ */
+
+float intDiv(float a, float b) { return float(int(a) / int(b)); }
+float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
+
+vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
+    float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice
+    float column = intMod(zSlice * gridDim.x, texDim.x) / gridDim.x;
+    float row = floor(intDiv(zSlice * gridDim.x, texDim.x));
+    vec2 coord = (vec2(column * gridDim.x, row * gridDim.y) + (pos.xy * gridDim.xy)) / texDim;
+    return texture2D(tex, coord);
+}
+
+#pragma glslify: export(texture3dFrom2dNearest)

+ 22 - 0
src/mol-gl/webgl/compat.ts

@@ -106,4 +106,26 @@ export interface COMPAT_texture_float_linear {
 
 export function getTextureFloatLinear(gl: GLRenderingContext): COMPAT_texture_float_linear | null {
     return gl.getExtension('OES_texture_float_linear')
+}
+
+export interface COMPAT_blend_minmax {
+    readonly MIN: number
+    readonly MAX: number
+}
+
+export function getBlendMinMax(gl: GLRenderingContext): COMPAT_blend_minmax | null {
+    if (isWebGL2(gl)) {
+        return { MIN: gl.MIN, MAX: gl.MAX }
+    } else {
+        const ext = gl.getExtension('EXT_blend_minmax')
+        if (ext === null) return null
+        return { MIN: ext.MIN_EXT, MAX: ext.MAX_EXT }
+    }
+}
+
+export interface COMPAT_frag_depth {
+}
+
+export function getFragDepth(gl: GLRenderingContext): COMPAT_frag_depth | null {
+    return isWebGL2(gl) ? {} : gl.getExtension('EXT_frag_depth')
 }

+ 21 - 4
src/mol-gl/webgl/context.ts

@@ -6,7 +6,7 @@
 
 import { createProgramCache, ProgramCache } from './program'
 import { createShaderCache, ShaderCache } from './shader'
-import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear } from './compat';
+import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth } from './compat';
 
 export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null {
     function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') {
@@ -100,10 +100,12 @@ export function createImageData(buffer: ArrayLike<number>, width: number, height
 type Extensions = {
     instancedArrays: COMPAT_instanced_arrays
     standardDerivatives: COMPAT_standard_derivatives
-    textureFloat: COMPAT_texture_float,
-    textureFloatLinear: COMPAT_texture_float_linear,
+    blendMinMax: COMPAT_blend_minmax
+    textureFloat: COMPAT_texture_float
+    textureFloatLinear: COMPAT_texture_float_linear
     elementIndexUint: COMPAT_element_index_uint | null
     vertexArrayObject: COMPAT_vertex_array_object | null
+    fragDepth: COMPAT_frag_depth | null
 }
 
 /** A WebGL context object, including the rendering context, resource caches and counts */
@@ -144,6 +146,10 @@ export function createContext(gl: GLRenderingContext): Context {
     if (standardDerivatives === null) {
         throw new Error('Could not find support for "standard_derivatives"')
     }
+    const blendMinMax = getBlendMinMax(gl)
+    if (blendMinMax === null) {
+        throw new Error('Could not find support for "blend_minmax"')
+    }
     const textureFloat = getTextureFloat(gl)
     if (textureFloat === null) {
         throw new Error('Could not find support for "texture_float"')
@@ -160,6 +166,10 @@ export function createContext(gl: GLRenderingContext): Context {
     if (vertexArrayObject === null) {
         console.log('Could not find support for "vertex_array_object"')
     }
+    const fragDepth = getFragDepth(gl)
+    if (fragDepth === null) {
+        console.log('Could not find support for "frag_depth"')
+    }
 
     const shaderCache = createShaderCache()
     const programCache = createProgramCache()
@@ -167,6 +177,11 @@ export function createContext(gl: GLRenderingContext): Context {
     const parameters = {
         maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
         maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) : 0,
+        maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
+    }
+
+    if (parameters.maxVertexTextureImageUnits < 4) {
+        throw new Error('Need "MAX_VERTEX_TEXTURE_IMAGE_UNITS" >= 4')
     }
 
     return {
@@ -175,10 +190,12 @@ export function createContext(gl: GLRenderingContext): Context {
         extensions: {
             instancedArrays,
             standardDerivatives,
+            blendMinMax,
             textureFloat,
             textureFloatLinear,
             elementIndexUint,
-            vertexArrayObject
+            vertexArrayObject,
+            fragDepth
         },
         pixelRatio: getPixelRatio(),
 

+ 4 - 4
src/mol-gl/webgl/render-item.ts

@@ -227,8 +227,8 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
             Object.keys(textureValues).forEach(k => {
                 const value = textureValues[k]
                 if (value.ref.version !== versions[k]) {
-                    // update of textures with kind 'texture2d' or 'texture3d' is done externally
-                    if (schema[k].kind !== 'texture2d' && schema[k].kind !== 'texture3d') {
+                    // update of textures with kind 'texture' is done externally
+                    if (schema[k].kind !== 'texture') {
                         // console.log('texture version changed, uploading image', k)
                         textures[k].load(value.ref.value as TextureImage<any> | TextureVolume<any>)
                         versions[k] = value.ref.version
@@ -246,8 +246,8 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
                     deleteVertexArray(ctx, vertexArrays[k])
                 })
                 Object.keys(textures).forEach(k => {
-                    // lifetime of textures with kind 'texture2d' or 'texture3d' is defined externally
-                    if (schema[k].kind !== 'texture2d' && schema[k].kind !== 'texture3d') {
+                    // lifetime of textures with kind 'texture' is defined externally
+                    if (schema[k].kind !== 'texture') {
                         textures[k].destroy()
                     }
                 })

+ 2 - 5
src/mol-gl/webgl/texture.ts

@@ -19,8 +19,7 @@ export type TextureKindValue = {
     'image-float32': TextureImage<Float32Array>
     'volume-uint8': TextureVolume<Uint8Array>
     'volume-float32': TextureVolume<Float32Array>
-    'texture2d': Texture
-    'texture3d': Texture
+    'texture': Texture
 }
 export type TextureValueType = Helpers.ValueOf<TextureKindValue>
 export type TextureKind = keyof TextureKindValue
@@ -35,13 +34,11 @@ export function getTarget(ctx: Context, kind: TextureKind): number {
     switch (kind) {
         case 'image-uint8': return gl.TEXTURE_2D
         case 'image-float32': return gl.TEXTURE_2D
-        case 'texture2d': return gl.TEXTURE_2D
     }
     if (isWebGL2(gl)) {
         switch (kind) {
             case 'volume-uint8': return gl.TEXTURE_3D
             case 'volume-float32': return gl.TEXTURE_3D
-            case 'texture3d': return gl.TEXTURE_3D
         }
     }
     throw new Error('unknown texture kind')
@@ -252,7 +249,7 @@ export function createTextures(ctx: Context, schema: RenderableSchema, values: T
     Object.keys(schema).forEach((k, i) => {
         const spec = schema[k]
         if (spec.type === 'texture') {
-            if (spec.kind === 'texture2d' || spec.kind === 'texture3d') {
+            if (spec.kind === 'texture') {
                 textures[k] = values[k].ref.value as Texture
             } else {
                 const texture = createTexture(ctx, spec.kind, spec.format, spec.dataType, spec.filter)

+ 1 - 1
src/mol-io/reader/_spec/ccp4.spec.ts

@@ -7,7 +7,7 @@
 import CCP4 from '../ccp4/parser'
 import { FileHandle } from '../../common/file-handle';
 
-const ccp4Buffer = new Uint8Array([])
+const ccp4Buffer = new Uint8Array(4 * 64)
 
 describe('ccp4 reader', () => {
     it('basic', async () => {

+ 10 - 12
src/mol-io/reader/ccp4/parser.ts

@@ -8,7 +8,6 @@ import { Task, RuntimeContext } from 'mol-task';
 import * as Schema from './schema'
 import Result from '../result'
 import { FileHandle } from '../../common/file-handle';
-import { flipByteOrder } from '../../common/binary';
 
 async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Schema.Ccp4File>> {
     await ctx.update({ message: 'Parsing CCP4 file...' });
@@ -32,9 +31,12 @@ async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Res
     // 54  MACHST      Machine stamp indicating machine type which wrote file
     //                 17 and 17 for big-endian or 68 and 65 for little-endian
     const MACHST = [ dv.getUint8(53 * 4), dv.getUint8(53 * 4 + 1) ]
-
-    if (MACHST[ 0 ] === 17 && MACHST[ 1 ] === 17) {
-        flipByteOrder(buffer, buffer.length)
+    // found MRC files that don't have the MACHST stamp set and are big-endian
+    if (MACHST[0] !== 68 && MACHST[1] !== 65) {
+        // flip byte order in-place
+        for (let i = 0, il = bin.byteLength; i < il; i += 4) {
+            dv.setFloat32(i, dv.getFloat32(i), true)
+        }
     }
 
     const header: Schema.Ccp4Header = {
@@ -91,17 +93,13 @@ async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Res
         // TODO bytes 57-256 LABEL
     }
 
+    const offset = 256 * 4 + header.NSYMBT
+    const count = header.NC * header.NR * header.NS
     let values
     if (header.MODE === 2) {
-        values = new Float32Array(
-            bin, 256 * 4 + header.NSYMBT,
-            header.NX * header.NY * header.NZ
-        )
+        values = new Float32Array(bin, offset, count)
     } else if (header.MODE === 0) {
-        values = new Float32Array(new Int8Array(
-            bin, 256 * 4 + header.NSYMBT,
-            header.NX * header.NY * header.NZ
-        ))
+        values = new Int8Array(bin, offset, count)
     } else {
         return Result.error(`ccp4 mode '${header.MODE}' unsupported`);
     }

+ 8 - 8
src/mol-io/reader/ccp4/schema.ts

@@ -6,11 +6,11 @@
  */
 
 export interface Ccp4Header {
-    /** columns (fastest changing) */
+    /** number of columns (fastest changing) */
     NC: number
-    /** rows */
+    /** number of rows */
     NR: number
-    /** sections (slowest changing) */
+    /** number of sections (slowest changing) */
     NS: number
     /**
      * 0 image : signed 8-bit bytes range -128 to 127
@@ -49,11 +49,11 @@ export interface Ccp4Header {
     beta: number
     /** gamma cell angle (Degrees) */
     gamma: number
-    /** axis corresponds for columns (1,2,3 for X,Y,Z) */
+    /** axis corresponds to columns (1,2,3 for X,Y,Z) */
     MAPC: number
-    /** axis corresponds for rows (1,2,3 for X,Y,Z) */
+    /** axis corresponds to rows (1,2,3 for X,Y,Z) */
     MAPR: number
-    /** axis corresponds for sections (1,2,3 for X,Y,Z) */
+    /** axis corresponds to sections (1,2,3 for X,Y,Z) */
     MAPS: number
     /** minimum density value */
     AMIN: number
@@ -67,8 +67,8 @@ export interface Ccp4Header {
     NSYMBT: number
     /** flag for skew transformation, =0 none, =1 if foll */
     LSKFLG: number
-    /** Skew matrix S (in order S11, S12, S13, S21 etc) if LSKFLG .ne. 0
-     *
+    /**
+     * Skew matrix S (in order S11, S12, S13, S21 etc) if LSKFLG .ne. 0
      * May be used in CCP4 but not in MRC
      */
     SKWMAT: number[]

+ 168 - 147
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -13,34 +13,36 @@ import { OrderedSet } from 'mol-data/int'
 import { Vec3, Tensor, Mat4 } from '../../linear-algebra'
 import { GaussianDensityValues } from 'mol-gl/renderable/gaussian-density'
 import { ValueCell, defaults } from 'mol-util'
-import { RenderableState } from 'mol-gl/renderable'
+import { RenderableState, Renderable } from 'mol-gl/renderable'
 import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object'
 import { Context, createContext, getGLContext } from 'mol-gl/webgl/context';
 import { createFramebuffer } from 'mol-gl/webgl/framebuffer';
-import { createTexture, Texture, TextureAttachment } from 'mol-gl/webgl/texture';
+import { createTexture, Texture } from 'mol-gl/webgl/texture';
 import { GLRenderingContext } from 'mol-gl/webgl/compat';
+import { decodeIdRGB } from 'mol-geo/geometry/picking';
 
 export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
     const webgl = defaults(props.webgl, getWebGLContext())
+    // always use texture2d when the gaussian density needs to be downloaded from the GPU,
+    // it's faster than texture3d
+    console.time('GaussianDensityTexture2d')
+    const { scale, bbox, texture, dim } = await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props)
+    console.timeEnd('GaussianDensityTexture2d')
+    const { field, idField } = fieldFromTexture2d(webgl, texture, dim)
 
-    const { transform, texture, gridDimension } = await GaussianDensityTexture(ctx, webgl, position, box, radius, props)
-
-    const field = webgl.maxDrawBuffers > 0 ?
-        fieldFromTexture3d(webgl, texture, gridDimension) :
-        fieldFromTexture2d(webgl, texture, gridDimension)
-
-    const idData = field.space.create()
-    const idField = Tensor.create(field.space, idData)
+    const transform = Mat4.identity()
+    Mat4.fromScaling(transform, scale)
+    Mat4.setTranslation(transform, bbox.min)
 
     return { field, idField, transform }
 }
 
 export async function GaussianDensityTexture(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): Promise<DensityTextureData> {
-    console.time(`GaussianDensityTexture, ${webgl.maxDrawBuffers > 0 ? 'multi' : 'single'}`)
-    const { texture, scale, bbox, dim } = webgl.maxDrawBuffers > 0 ?
-        await GaussianDensityMultiDrawBuffer(ctx, webgl, position, box, radius, props, oldTexture) :
-        await GaussianDensitySingleDrawBuffer(ctx, webgl, position, box, radius, props, oldTexture)
-    console.timeEnd(`GaussianDensityTexture, ${webgl.maxDrawBuffers > 0 ? 'multi' : 'single'}`)
+    console.time(`GaussianDensityTexture, ${webgl.isWebGL2 ? '3d' : '2d'}`)
+    const { texture, scale, bbox, dim } = webgl.isWebGL2 ?
+        await GaussianDensityTexture3d(ctx, webgl, position, box, radius, props, oldTexture) :
+        await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props, oldTexture)
+    console.timeEnd(`GaussianDensityTexture, ${webgl.isWebGL2 ? '3d' : '2d'}`)
 
     const transform = Mat4.identity()
     Mat4.fromScaling(transform, scale)
@@ -51,29 +53,18 @@ export async function GaussianDensityTexture(ctx: RuntimeContext, webgl: Context
 
 //
 
-async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
+async function GaussianDensityTexture2d(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
     const { smoothness } = props
 
-    const { drawCount, positions, radii, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
+    const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
     const [ dx, dy, dz ] = dim
-    const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, expandedBox, dim, smoothness)
-    const renderable = createRenderable(webgl, renderObject)
+    const { texDimX, texDimY, texCols } = getTexture2dSize(webgl.maxTextureSize, dim)
 
-    //
+    const minDistanceTexture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest')
+    minDistanceTexture.define(texDimX, texDimY)
 
-    const maxTexSize = webgl.maxTextureSize
-    let fboTexDimX = 0
-    let fboTexDimY = dim[1]
-    let fboTexRows = 1
-    let fboTexCols = dim[0]
-    if (maxTexSize < dim[0] * dim[2]) {
-        fboTexCols =  Math.floor(maxTexSize / dim[0])
-        fboTexRows = Math.ceil(dim[2] / fboTexCols)
-        fboTexDimX = fboTexCols * dim[0]
-        fboTexDimY *= fboTexRows
-    } else {
-        fboTexDimX = dim[0] * dim[2]
-    }
+    const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness)
+    const renderable = createRenderable(webgl, renderObject)
 
     //
 
@@ -82,35 +73,41 @@ async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Conte
 
     const framebuffer = createFramebuffer(webgl)
     framebuffer.bind()
-
-    if (!texture) {
-        texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
-    }
-    texture.define(fboTexDimX, fboTexDimY)
-
-    const program = renderable.getProgram('draw')
-    program.use()
     setRenderingDefaults(gl)
-    texture.attachFramebuffer(framebuffer, 0)
 
-    let currCol = 0
-    let currY = 0
-    let currX = 0
-    for (let i = 0; i < dz; ++i) {
-        if (currCol >= fboTexCols) {
-            currCol -= fboTexCols
-            currY += dy
-            currX = 0
+    if (!texture) texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
+    texture.define(texDimX, texDimY)
+
+    function render(fbTex: Texture) {
+        fbTex.attachFramebuffer(framebuffer, 0)
+        let currCol = 0
+        let currY = 0
+        let currX = 0
+        for (let i = 0; i < dz; ++i) {
+            if (currCol >= texCols) {
+                currCol -= texCols
+                currY += dy
+                currX = 0
+            }
+            gl.viewport(currX, currY, dx, dy)
+            ValueCell.update(uCurrentSlice, i)
+            ValueCell.update(uCurrentX, currX)
+            ValueCell.update(uCurrentY, currY)
+            renderable.render('draw')
+            ++currCol
+            currX += dx
         }
-        gl.viewport(currX, currY, dx, dy)
-        ValueCell.update(uCurrentSlice, i)
-        ValueCell.update(uCurrentX, currX)
-        ValueCell.update(uCurrentY, currY)
-        renderable.render('draw')
-        ++currCol
-        currX += dx
     }
 
+    setupMinDistanceRendering(webgl, renderable)
+    render(minDistanceTexture)
+
+    setupDensityRendering(webgl, renderable)
+    render(texture)
+
+    setupGroupIdRendering(webgl, renderable)
+    render(texture)
+
     framebuffer.destroy() // clean up
 
     await ctx.update({ message: 'gpu gaussian density calculation' });
@@ -119,61 +116,46 @@ async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Conte
     return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
 }
 
-async function GaussianDensityMultiDrawBuffer(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
+async function GaussianDensityTexture3d(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
     const { smoothness } = props
 
-    const { drawCount, positions, radii, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
+    const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
     const [ dx, dy, dz ] = dim
-    const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, expandedBox, dim, smoothness)
+    const minDistanceTexture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest')
+    minDistanceTexture.define(dx, dy, dz)
+
+    const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness)
     const renderable = createRenderable(webgl, renderObject)
-    const drawBuffers = Math.min(8, webgl.maxDrawBuffers)
 
     //
 
-    const gl = webgl.gl as WebGL2RenderingContext
+    const { gl } = webgl
     const { uCurrentSlice } = renderObject.values
 
     const framebuffer = createFramebuffer(webgl)
     framebuffer.bind()
-
-    setDrawBuffers(gl, drawBuffers)
-    gl.viewport(0, 0, dx, dy)
     setRenderingDefaults(gl)
+    gl.viewport(0, 0, dx, dy)
 
-    if (!texture) {
-        texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
-    }
+    if (!texture) texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
     texture.define(dx, dy, dz)
 
-    // z-slices to be render with multi render targets
-    const dzMulti = Math.floor(dz / drawBuffers) * drawBuffers
-
-    // render multi target
-    const programMulti = renderable.getProgram('draw')
-    programMulti.use()
-    for (let i = 0; i < dzMulti; i += drawBuffers) {
-        ValueCell.update(uCurrentSlice, i)
-        for (let k = 0; k < drawBuffers; ++k) {
-            texture.attachFramebuffer(framebuffer, k as TextureAttachment, i + k)
+    function render(fbTex: Texture) {
+        for (let i = 0; i < dz; ++i) {
+            ValueCell.update(uCurrentSlice, i)
+            fbTex.attachFramebuffer(framebuffer, 0, i)
+            renderable.render('draw')
         }
-        renderable.render('draw');
     }
 
-    // render single target
-    ValueCell.updateIfChanged(renderable.values.dDrawBuffers, 1)
-    renderable.update()
-    const programSingle = renderable.getProgram('draw')
-    programSingle.use()
-    for (let i = dzMulti; i < dz; ++i) {
-        ValueCell.update(uCurrentSlice, i)
-        texture.attachFramebuffer(framebuffer, 0, i)
-        renderable.render('draw')
-    }
+    setupMinDistanceRendering(webgl, renderable)
+    render(minDistanceTexture)
 
-    // must detach framebuffer attachments before reading is possible
-    for (let k = 0; k < drawBuffers; ++k) {
-        texture.detachFramebuffer(framebuffer, k as TextureAttachment)
-    }
+    setupDensityRendering(webgl, renderable)
+    render(texture)
+
+    setupGroupIdRendering(webgl, renderable)
+    render(texture)
 
     framebuffer.destroy() // clean up
 
@@ -208,6 +190,7 @@ async function prepareGaussianDensityData(ctx: RuntimeContext, position: Positio
 
     const positions = new Float32Array(n * 3)
     const radii = new Float32Array(n)
+    const groups = new Float32Array(n)
 
     let maxRadius = 0
 
@@ -220,13 +203,13 @@ async function prepareGaussianDensityData(ctx: RuntimeContext, position: Positio
         const r = radius(j) + radiusOffset
         if (maxRadius < r) maxRadius = r
         radii[i] = r
+        groups[i] = i
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {
             await ctx.update({ message: 'preparing density data', current: i, max: n })
         }
     }
 
-
     const pad = maxRadius * 2 + resolution
     const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad));
     const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min)
@@ -236,11 +219,12 @@ async function prepareGaussianDensityData(ctx: RuntimeContext, position: Positio
     Vec3.ceil(dim, Vec3.mul(dim, extent, delta))
     console.log('grid dim gpu', dim)
 
-    return { drawCount: n, positions, radii, delta, expandedBox, dim }
+    return { drawCount: n, positions, radii, groups, delta, expandedBox, dim }
 }
 
-function getGaussianDensityRenderObject(webgl: Context, drawCount: number, positions: Float32Array, radii: Float32Array, box: Box3D, dimensions: Vec3, smoothness: number) {
+function getGaussianDensityRenderObject(webgl: Context, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, dimensions: Vec3, smoothness: number) {
     const extent = Vec3.sub(Vec3.zero(), box.max, box.min)
+    const { texDimX, texDimY } = getTexture2dSize(webgl.maxTextureSize, dimensions)
 
     const values: GaussianDensityValues = {
         drawCount: ValueCell.create(drawCount),
@@ -248,6 +232,7 @@ function getGaussianDensityRenderObject(webgl: Context, drawCount: number, posit
 
         aRadius: ValueCell.create(radii),
         aPosition: ValueCell.create(positions),
+        aGroup: ValueCell.create(groups),
 
         uCurrentSlice: ValueCell.create(0),
         uCurrentX: ValueCell.create(0),
@@ -256,9 +241,12 @@ function getGaussianDensityRenderObject(webgl: Context, drawCount: number, posit
         uBboxMax: ValueCell.create(box.max),
         uBboxSize: ValueCell.create(extent),
         uGridDim: ValueCell.create(dimensions),
+        uGridTexDim: ValueCell.create(Vec3.create(texDimX, texDimY, 0)),
         uAlpha: ValueCell.create(smoothness),
+        tMinDistanceTex: ValueCell.create(minDistanceTexture),
 
-        dDrawBuffers: ValueCell.create(Math.min(8, webgl.maxDrawBuffers)),
+        dGridTexType: ValueCell.create(minDistanceTexture.depth > 0 ? '3d' : '2d'),
+        dCalcType: ValueCell.create('density'),
     }
     const state: RenderableState = {
         visible: true,
@@ -274,27 +262,52 @@ function setRenderingDefaults(gl: GLRenderingContext) {
     gl.disable(gl.CULL_FACE)
     gl.frontFace(gl.CCW)
     gl.cullFace(gl.BACK)
+    gl.enable(gl.BLEND)
+}
 
+function setupMinDistanceRendering(webgl: Context, renderable: Renderable<any>) {
+    const { gl } = webgl
+    ValueCell.update(renderable.values.dCalcType, 'minDistance')
+    renderable.update()
+    renderable.getProgram('draw').use()
+    gl.blendFunc(gl.ONE, gl.ONE)
+    // the shader writes 1 - dist so we set blending to MAX
+    gl.blendEquation(webgl.extensions.blendMinMax.MAX)
+}
+
+function setupDensityRendering(webgl: Context, renderable: Renderable<any>) {
+    const { gl } = webgl
+    ValueCell.update(renderable.values.dCalcType, 'density')
+    renderable.update()
+    renderable.getProgram('draw').use()
     gl.blendFunc(gl.ONE, gl.ONE)
     gl.blendEquation(gl.FUNC_ADD)
-    gl.enable(gl.BLEND)
 }
 
-function setDrawBuffers(gl: WebGL2RenderingContext, drawBuffers: number) {
-    if (drawBuffers === 1) {
-        gl.drawBuffers([
-            gl.COLOR_ATTACHMENT0,
-        ]);
-    } else if (drawBuffers === 4) {
-        gl.drawBuffers([
-            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
-        ]);
-    } else if (drawBuffers === 8) {
-        gl.drawBuffers([
-            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
-            gl.COLOR_ATTACHMENT4, gl.COLOR_ATTACHMENT5, gl.COLOR_ATTACHMENT6, gl.COLOR_ATTACHMENT7,
-        ]);
+function setupGroupIdRendering(webgl: Context, renderable: Renderable<any>) {
+    const { gl } = webgl
+    ValueCell.update(renderable.values.dCalcType, 'groupId')
+    renderable.update()
+    renderable.getProgram('draw').use()
+    // overwrite color, don't change alpha
+    gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ZERO, gl.ONE)
+    gl.blendEquation(gl.FUNC_ADD)
+}
+
+function getTexture2dSize(maxTexSize: number, gridDim: Vec3) {
+    let texDimX = 0
+    let texDimY = gridDim[1]
+    let texRows = 1
+    let texCols = gridDim[0]
+    if (maxTexSize < gridDim[0] * gridDim[2]) {
+        texCols =  Math.floor(maxTexSize / gridDim[0])
+        texRows = Math.ceil(gridDim[2] / texCols)
+        texDimX = texCols * gridDim[0]
+        texDimY *= texRows
+    } else {
+        texDimX = gridDim[0] * gridDim[2]
     }
+    return { texDimX, texDimY, texRows, texCols }
 }
 
 function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
@@ -307,6 +320,8 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
     const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
     const data = space.create()
     const field = Tensor.create(space, data)
+    const idData = space.create()
+    const idField = Tensor.create(space, idData)
 
     const image = new Uint8Array(width * height * 4)
 
@@ -316,7 +331,7 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
     texture.attachFramebuffer(framebuffer, 0)
     gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image)
 
-    let idx = 0
+    let j = 0
     let tmpCol = 0
     let tmpRow = 0
     for (let iz = 0; iz < dz; ++iz) {
@@ -326,8 +341,10 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
         }
         for (let iy = 0; iy < dy; ++iy) {
             for (let ix = 0; ix < dx; ++ix) {
-                data[idx] = image[4 * (tmpCol * dx + (iy + tmpRow) * width + ix) + 3] / 255
-                idx++
+                const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix)
+                data[j] = image[idx + 3] / 255
+                idData[j] = decodeIdRGB(image[idx], image[idx + 1], image[idx + 2])
+                j++
             }
         }
         tmpCol++
@@ -336,37 +353,41 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
     framebuffer.destroy()
     console.timeEnd('fieldFromTexture2d')
 
-    return field
+    return { field, idField }
 }
 
-function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) {
-    console.time('fieldFromTexture3d')
-    const { gl } = ctx
-    const { width, height, depth } = texture
-
-    const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
-    const data = space.create()
-    const field = Tensor.create(space, data)
-
-    const slice = new Uint8Array(width * height * 4)
-
-    const framebuffer = createFramebuffer(ctx)
-    framebuffer.bind()
-
-    let j = 0
-    for (let i = 0; i < depth; ++i) {
-        texture.attachFramebuffer(framebuffer, 0, i)
-        gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice)
-        for (let iy = 0; iy < height; ++iy) {
-            for (let ix = 0; ix < width; ++ix) {
-                data[j] = slice[4 * (iy * width + ix) + 3] / 255
-                ++j
-            }
-        }
-    }
-
-    framebuffer.destroy()
-    console.timeEnd('fieldFromTexture3d')
-
-    return field
-}
+// function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) {
+//     console.time('fieldFromTexture3d')
+//     const { gl } = ctx
+//     const { width, height, depth } = texture
+
+//     const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
+//     const data = space.create()
+//     const field = Tensor.create(space, data)
+//     const idData = space.create()
+//     const idField = Tensor.create(space, idData)
+
+//     const slice = new Uint8Array(width * height * 4)
+
+//     const framebuffer = createFramebuffer(ctx)
+//     framebuffer.bind()
+
+//     let j = 0
+//     for (let i = 0; i < depth; ++i) {
+//         texture.attachFramebuffer(framebuffer, 0, i)
+//         gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice)
+//         for (let iy = 0; iy < height; ++iy) {
+//             for (let ix = 0; ix < width; ++ix) {
+//                 const idx = 4 * (iy * width + ix)
+//                 data[j] = slice[idx + 3] / 255
+//                 idData[j] = decodeIdRGB(slice[idx], slice[idx + 1], slice[idx + 2])
+//                 ++j
+//             }
+//         }
+//     }
+
+//     framebuffer.destroy()
+//     console.timeEnd('fieldFromTexture3d')
+
+//     return { field, idField }
+// }

+ 1 - 1
src/mol-model/volume/data.ts

@@ -7,8 +7,8 @@
 import { SpacegroupCell, Box3D } from 'mol-math/geometry'
 import { Tensor, Mat4, Vec3 } from 'mol-math/linear-algebra'
 
+/** The basic unit cell that contains the data. */
 interface VolumeData {
-    // The basic unit cell that contains the data.
     readonly cell: SpacegroupCell,
     readonly fractionalBox: Box3D,
     readonly data: Tensor,

+ 6 - 3
src/mol-model/volume/formats/density-server.ts

@@ -13,14 +13,17 @@ import { Tensor, Vec3 } from 'mol-math/linear-algebra';
 function parseDensityServerData(source: DensityServer_Data_Database): Task<VolumeData> {
     return Task.create<VolumeData>('Parse Volume Data', async ctx => {
         const { volume_data_3d_info: info, volume_data_3d: values } = source;
-        const cell = SpacegroupCell.create(info.spacegroup_number.value(0),
+        const cell = SpacegroupCell.create(
+            info.spacegroup_number.value(0),
             Vec3.ofArray(info.spacegroup_cell_size.value(0)),
-            Vec3.scale(Vec3.zero(), Vec3.ofArray(info.spacegroup_cell_angles.value(0)), Math.PI / 180));
+            Vec3.scale(Vec3.zero(), Vec3.ofArray(info.spacegroup_cell_angles.value(0)), Math.PI / 180)
+        );
 
         const tensorSpace = Tensor.Space(info.sample_count.value(0), info.axis_order.value(0), Float32Array);
         const data = Tensor.create(tensorSpace, Tensor.Data1(values.values.toArray()));
 
-        const origin = Vec3.ofArray(info.origin.value(0)), dimensions = Vec3.ofArray(info.dimensions.value(0));
+        const origin = Vec3.ofArray(info.origin.value(0))
+        const dimensions = Vec3.ofArray(info.dimensions.value(0));
 
         return {
             cell,

+ 4 - 4
src/mol-view/viewer.ts

@@ -21,7 +21,7 @@ import { Representation } from 'mol-geo/representation';
 import { createRenderTarget } from 'mol-gl/webgl/render-target';
 import Scene from 'mol-gl/scene';
 import { RenderVariant } from 'mol-gl/webgl/render-item';
-import { PickingId, decodeIdRGBA } from 'mol-geo/geometry/picking';
+import { PickingId, decodeIdRGB } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
 import { Color } from 'mol-util/color';
@@ -231,15 +231,15 @@ namespace Viewer {
 
             objectPickTarget.bind()
             ctx.readPixels(xp, yp, 1, 1, buffer)
-            const objectId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+            const objectId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
 
             instancePickTarget.bind()
             ctx.readPixels(xp, yp, 1, 1, buffer)
-            const instanceId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+            const instanceId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
 
             groupPickTarget.bind()
             ctx.readPixels(xp, yp, 1, 1, buffer)
-            const groupId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+            const groupId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
 
             isPicking = false