Browse Source

Merged master

David Sehnal 7 years ago
parent
commit
0f8e1416f9
42 changed files with 863 additions and 332 deletions
  1. 65 0
      src/apps/render-test/components/assemblies.tsx
  2. 1 1
      src/apps/render-test/components/color-theme.tsx
  3. 8 8
      src/apps/render-test/components/sphere-detail.tsx
  4. 1 1
      src/apps/render-test/components/visibility.tsx
  5. 90 104
      src/apps/render-test/state.ts
  6. 4 2
      src/apps/render-test/ui.tsx
  7. 24 10
      src/apps/render-test/utils/index.ts
  8. 2 2
      src/apps/structure-info/volume.ts
  9. 4 0
      src/mol-geo/primitive/icosahedron.ts
  10. 16 0
      src/mol-geo/representation/index.ts
  11. 13 15
      src/mol-geo/representation/structure/index.ts
  12. 18 10
      src/mol-geo/representation/structure/point.ts
  13. 36 19
      src/mol-geo/representation/structure/spacefill.ts
  14. 4 4
      src/mol-geo/representation/structure/utils.ts
  15. 0 29
      src/mol-geo/representation/volume/Mesh.ts
  16. 41 0
      src/mol-geo/representation/volume/index.ts
  17. 95 0
      src/mol-geo/representation/volume/surface.ts
  18. 7 2
      src/mol-geo/shape/mesh.ts
  19. 31 15
      src/mol-geo/theme/structure/color/chain-id.ts
  20. 1 1
      src/mol-geo/theme/structure/color/element-index.ts
  21. 1 1
      src/mol-geo/theme/structure/color/index.ts
  22. 31 0
      src/mol-geo/theme/structure/size/element.ts
  23. 1 1
      src/mol-geo/theme/structure/size/index.ts
  24. 0 23
      src/mol-geo/theme/structure/size/vdw.ts
  25. 17 6
      src/mol-geo/util.ts
  26. 2 0
      src/mol-gl/_spec/renderer.spec.ts
  27. 10 0
      src/mol-gl/renderable.ts
  28. 9 6
      src/mol-gl/renderable/mesh.ts
  29. 2 4
      src/mol-gl/renderable/point.ts
  30. 7 5
      src/mol-gl/renderable/util.ts
  31. 68 26
      src/mol-gl/renderer.ts
  32. 15 3
      src/mol-gl/scene.ts
  33. 2 1
      src/mol-gl/shader-code.ts
  34. 23 3
      src/mol-gl/shader/mesh.frag
  35. 12 4
      src/mol-gl/shader/mesh.vert
  36. 3 1
      src/mol-gl/shader/point.frag
  37. 6 1
      src/mol-gl/webgl/context.ts
  38. 141 6
      src/mol-math/linear-algebra/3d/mat3.ts
  39. 13 2
      src/mol-math/linear-algebra/3d/vec3.ts
  40. 14 0
      src/mol-math/linear-algebra/3d/vec4.ts
  41. 6 2
      src/mol-model/structure/query/properties.ts
  42. 19 14
      src/mol-view/viewer.ts

+ 65 - 0
src/apps/render-test/components/assemblies.tsx

@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+import { WithStyles } from 'material-ui/styles';
+import { MenuItem } from 'material-ui/Menu';
+import { InputLabel } from 'material-ui/Input';
+import { FormControl } from 'material-ui/Form';
+import Select from 'material-ui/Select';
+
+import State from '../state'
+import Observer from './observer';
+import { Assembly } from 'mol-model/structure/model/properties/symmetry';
+
+interface AssemblyState {
+    loading: boolean
+    assemblies: ReadonlyArray<Assembly>
+    value: string
+}
+
+export default class Assemblies extends Observer<{ state: State } & WithStyles, AssemblyState> {
+    state: AssemblyState = { loading: false, assemblies: [], value: '' }
+
+    componentDidMount() {
+        this.subscribe(this.props.state.loading, value => {
+           this.setState({ loading: value });
+        });
+        this.subscribe(this.props.state.model, value => {
+            this.setState({ assemblies: value ? value.symmetry.assemblies : [] });
+        });
+        this.subscribe(this.props.state.assembly, value => {
+            this.setState({ value });
+        });
+    }
+
+    handleValueChange = (event: React.ChangeEvent<any>) => {
+        this.props.state.assembly.next(event.target.value)
+    }
+
+    render() {
+        const { classes } = this.props;
+
+        const items = this.state.assemblies.map((value, idx) => {
+            return <MenuItem key={idx} value={value.id}>{value.details}</MenuItem>
+        })
+
+        return <FormControl className={classes.formControl}>
+            <InputLabel htmlFor='assembly-value'>Assembly</InputLabel>
+            <Select
+                className={classes.selectField}
+                value={this.state.value}
+                onChange={this.handleValueChange}
+                inputProps={{
+                    name: 'value',
+                    id: 'assembly-value',
+                }}
+            >
+                {items}
+            </Select>
+        </FormControl>
+    }
+}

+ 1 - 1
src/apps/render-test/components/color-theme.tsx

@@ -22,7 +22,7 @@ interface ColorThemeState {
 }
 
 export default class ColorTheme extends Observer<{ state: State } & WithStyles, ColorThemeState> {
-    state = { loading: false, name: 'element-symbol' as _ColorTheme, value: 0xFF0000 }
+    state: ColorThemeState = { loading: false, name: 'element-symbol' as _ColorTheme, value: 0xFF0000 }
 
     componentDidMount() {
         this.subscribe(this.props.state.loading, value => {

+ 8 - 8
src/apps/render-test/components/detail.tsx → src/apps/render-test/components/sphere-detail.tsx

@@ -14,43 +14,43 @@ import Select from 'material-ui/Select';
 import State from '../state'
 import Observer from './observer';
 
-interface DetailState {
+interface SphereDetailState {
     loading: boolean
     value: number
 }
 
-export default class Detail extends Observer<{ state: State } & WithStyles, DetailState> {
-    state = { loading: false, value: 2 }
+export default class SphereDetail extends Observer<{ state: State } & WithStyles, SphereDetailState> {
+    state: SphereDetailState = { loading: false, value: 2 }
 
     componentDidMount() {
         this.subscribe(this.props.state.loading, value => {
            this.setState({ loading: value });
         });
-        this.subscribe(this.props.state.detail, value => {
+        this.subscribe(this.props.state.sphereDetail, value => {
             this.setState({ value });
          });
     }
 
     handleValueChange = (event: React.ChangeEvent<any>) => {
-        this.props.state.detail.next(event.target.value)
+        this.props.state.sphereDetail.next(event.target.value)
     }
 
     render() {
         const { classes } = this.props;
 
-        const items = [0, 1, 2].map((value, idx) => {
+        const items = [0, 1, 2, 3].map((value, idx) => {
             return <MenuItem key={idx} value={value}>{value.toString()}</MenuItem>
         })
 
         return <FormControl className={classes.formControl}>
-            <InputLabel htmlFor='detail-value'>Detail</InputLabel>
+            <InputLabel htmlFor='sphere-detail-value'>Sphere Detail</InputLabel>
             <Select
                 className={classes.selectField}
                 value={this.state.value}
                 onChange={this.handleValueChange}
                 inputProps={{
                     name: 'value',
-                    id: 'detail-value',
+                    id: 'sphere-detail-value',
                 }}
             >
                 {items}

+ 1 - 1
src/apps/render-test/components/visibility.tsx

@@ -19,7 +19,7 @@ interface VisibilityState {
 }
 
 export default class Visibility extends Observer<{ state: State } & WithStyles, VisibilityState> {
-    state = { loading: false, spacefill: true, point: true }
+    state: VisibilityState = { loading: false, spacefill: true, point: true }
 
     componentDidMount() {
         this.subscribe(this.props.state.loading, value => {

+ 90 - 104
src/apps/render-test/state.ts

@@ -17,12 +17,15 @@ import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spac
 import Point, { PointProps } from 'mol-geo/representation/structure/point'
 
 import { Run } from 'mol-task'
-import { Symmetry, Structure } from 'mol-model/structure'
+import { Symmetry, Structure, Model } from 'mol-model/structure'
 
 // import mcubes from './utils/mcubes'
-import { getStructuresFromPdbId, getStructuresFromFile, log } from './utils'
+import { getModelFromPdbId, getModelFromFile, log, Volume, getVolumeFromEmdId } from './utils'
 import { StructureRepresentation } from 'mol-geo/representation/structure';
 import { Color } from 'mol-util/color';
+import Surface, { SurfaceProps } from 'mol-geo/representation/volume/surface';
+import { VolumeIsoValue } from 'mol-model/volume';
+import { VolumeRepresentation } from 'mol-geo/representation/volume';
 // import Cylinder from 'mol-geo/primitive/cylinder';
 
 
@@ -37,24 +40,35 @@ export type ColorTheme = keyof typeof ColorTheme
 
 export default class State {
     viewer: Viewer
-    pdbId = '4cup'
+    pdbId = '1crn'
+    // pdbId = '5ire'
+    emdId = '8116'
+    // pdbId = '6G1K'
+    // emdId = '4339'
+    // pdbId = '4cup'
+    // emdId = ''
+    model = new BehaviorSubject<Model | undefined>(undefined)
+    volume = new BehaviorSubject<Volume | undefined>(undefined)
     initialized = new BehaviorSubject<boolean>(false)
     loading = new BehaviorSubject<boolean>(false)
 
     colorTheme = new BehaviorSubject<ColorTheme>('element-symbol')
     colorValue = new BehaviorSubject<Color>(0xFF4411)
-    detail = new BehaviorSubject<number>(0)
+    sphereDetail = new BehaviorSubject<number>(0)
+    assembly = new BehaviorSubject<string>('')
 
     pointVisibility = new BehaviorSubject<boolean>(true)
     spacefillVisibility = new BehaviorSubject<boolean>(true)
 
     pointRepr: StructureRepresentation<PointProps>
     spacefillRepr: StructureRepresentation<SpacefillProps>
+    surfaceRepr: VolumeRepresentation<SurfaceProps>
 
     constructor() {
         this.colorTheme.subscribe(() => this.update())
         this.colorValue.subscribe(() => this.update())
-        this.detail.subscribe(() => this.update())
+        this.sphereDetail.subscribe(() => this.update())
+        this.assembly.subscribe(() => this.initStructure())
 
         this.pointVisibility.subscribe(() => this.updateVisibility())
         this.spacefillVisibility.subscribe(() => this.updateVisibility())
@@ -63,7 +77,8 @@ export default class State {
     getSpacefillProps (): SpacefillProps {
         const colorThemeName = this.colorTheme.getValue()
         return {
-            detail: this.detail.getValue(),
+            doubleSided: true,
+            detail: this.sphereDetail.getValue(),
             colorTheme: colorThemeName === 'uniform' ?
                 { name: colorThemeName, value: this.colorValue.getValue() } :
                 { name: colorThemeName }
@@ -84,51 +99,105 @@ export default class State {
         this.viewer = Viewer.create(canvas, container)
         this.initialized.next(true)
         this.loadPdbId()
+        this.loadEmdId()
         this.viewer.animate()
     }
 
-    async initStructure (structure: Structure) {
-        const { viewer, loading } = this
-        viewer.clear()
+    async getStructure () {
+        const model = this.model.getValue()
+        if (!model) return
+        const assembly = this.assembly.getValue()
+        let structure: Structure
+        const assemblies = model.symmetry.assemblies
+        if (assemblies.length) {
+            structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), log, 500)
+        } else {
+            structure = Structure.ofModel(model)
+        }
+        return structure
+    }
+
+    async initStructure () {
+        const { viewer } = this
+        if (!viewer || !this.model.getValue()) return
 
-        const struct = await Run(Symmetry.buildAssembly(structure, '1'), log, 100)
+        if (this.pointRepr) this.viewer.remove(this.pointRepr)
+        if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr)
+
+        const structure = await this.getStructure()
+        if (!structure) return
 
         this.pointRepr = StructureRepresentation(Point)
-        await Run(this.pointRepr.create(struct, this.getPointProps()), log, 100)
+        await Run(this.pointRepr.create(structure, this.getPointProps()), log, 500)
         viewer.add(this.pointRepr)
 
         this.spacefillRepr = StructureRepresentation(Spacefill)
-        await Run(this.spacefillRepr.create(struct, this.getSpacefillProps()), log, 100)
+        await Run(this.spacefillRepr.create(structure, this.getSpacefillProps()), log, 500)
         viewer.add(this.spacefillRepr)
 
         this.updateVisibility()
         viewer.requestDraw()
         console.log(viewer.stats)
+    }
 
-        loading.next(false)
+    setModel(model: Model) {
+        this.model.next(model)
+        this.initStructure()
+        this.loading.next(false)
     }
 
     async loadFile (file: File) {
         this.viewer.clear()
         this.loading.next(true)
+        this.setModel((await getModelFromFile(file))[0])
+    }
+
+    async initVolume () {
+        const { viewer } = this
+        const v = this.volume.getValue()
+        if (!viewer || !v) return
+
+        if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr)
 
-        const structures = await getStructuresFromFile(file)
-        this.initStructure(structures[0])
+        this.surfaceRepr = VolumeRepresentation(Surface)
+        await Run(this.surfaceRepr.create(v.volume, {
+            isoValue: VolumeIsoValue.relative(v.volume.dataStats, 3.0),
+            alpha: 0.5,
+            flatShaded: false,
+            flipSided: true,
+            doubleSided: true
+        }), log, 500)
+        viewer.add(this.surfaceRepr)
+
+        viewer.requestDraw()
+        console.log(viewer.stats)
     }
 
     async loadPdbId () {
-        this.viewer.clear()
+        if (this.pointRepr) this.viewer.remove(this.pointRepr)
+        if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr)
         if (this.pdbId.length !== 4) return
         this.loading.next(true)
+        this.setModel((await getModelFromPdbId(this.pdbId))[0])
+    }
+
+    setVolume(volume: Volume) {
+        this.volume.next(volume)
+        this.initVolume()
+        this.loading.next(false)
+    }
 
-        const structures = await getStructuresFromPdbId(this.pdbId)
-        this.initStructure(structures[0])
+    async loadEmdId () {
+        if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr)
+        if (this.emdId.length !== 4) return
+        this.loading.next(true)
+        this.setVolume(await getVolumeFromEmdId(this.emdId))
     }
 
     async update () {
         if (!this.spacefillRepr) return
-        await Run(this.spacefillRepr.update(this.getSpacefillProps()), log, 100)
-        await Run(this.pointRepr.update(this.getPointProps()), log, 100)
+        await Run(this.spacefillRepr.update(this.getSpacefillProps()), log, 500)
+        await Run(this.pointRepr.update(this.getPointProps()), log, 500)
         this.viewer.add(this.spacefillRepr)
         this.viewer.add(this.pointRepr)
         this.viewer.update()
@@ -154,87 +223,4 @@ export default class State {
         }
         this.viewer.requestDraw()
     }
-}
-
-
-
-// async foo () {
-//     const p1 = Vec3.create(0, 4, 0)
-//     const p2 = Vec3.create(-3, 0, 0)
-
-//     // const position = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]))
-//     // const normal = ValueCell.create(new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]))
-
-//     const transformArray1 = ValueCell.create(new Float32Array(16))
-//     const transformArray2 = ValueCell.create(new Float32Array(16 * 3))
-//     const m4 = Mat4.identity()
-//     Mat4.toArray(m4, transformArray1.ref.value, 0)
-//     Mat4.toArray(m4, transformArray2.ref.value, 0)
-//     Mat4.setTranslation(m4, p1)
-//     Mat4.toArray(m4, transformArray2.ref.value, 16)
-//     Mat4.setTranslation(m4, p2)
-//     Mat4.toArray(m4, transformArray2.ref.value, 32)
-
-//     const color = ValueCell.create(createColorTexture(3))
-//     color.ref.value.set([
-//         0, 0, 255,
-//         0, 255, 0,
-//         255, 0, 0
-//     ])
-
-//     // const points = createRenderObject('point', {
-//     //     position,
-//     //     transform: transformArray1
-//     // })
-//     // // renderer.add(points)
-
-//     // const mesh = createRenderObject('mesh', {
-//     //     position,
-//     //     normal,
-//     //     color,
-//     //     transform: transformArray2
-//     // })
-//     // renderer.add(mesh)
-
-//     // const cylinder = Cylinder({ height: 3, radiusBottom: 0.5, radiusTop: 0.5 })
-//     // console.log(cylinder)
-//     // const cylinderMesh = createRenderObject('mesh', {
-//     //     position: ValueCell.create(cylinder.vertices),
-//     //     normal: ValueCell.create(cylinder.normals),
-//     //     color,
-//     //     transform: transformArray2
-//     // }, cylinder.indices)
-//     // renderer.add(cylinderMesh)
-
-//     // const sphere = Icosahedron()
-//     // console.log(sphere)
-
-//     // const box = Box()
-//     // console.log(box)
-
-//     // const points2 = createRenderObject('point', {
-//     //     position: ValueCell.create(new Float32Array(box.vertices)),
-//     //     transform: transformArray1
-//     // })
-//     // renderer.add(points2)
-
-//     // let rr = 0.7;
-//     // function cubesF(x: number, y: number, z: number) {
-//     //     return x * x + y * y + z * z - rr * rr;
-//     // }
-//     // let cubes = await mcubes(cubesF);
-
-//     // const makeCubesMesh = () => createRenderObject('mesh', {
-//     //     position: cubes.surface.vertexBuffer,
-//     //     normal: cubes.surface.normalBuffer,
-//     //     color,
-//     //     transform: transformArray2,
-//     //     elements: cubes.surface.indexBuffer,
-
-//     //     instanceCount: transformArray2.ref.value.length / 16,
-//     //     elementCount: cubes.surface.triangleCount,
-//     //     positionCount: cubes.surface.vertexCount
-//     // }, {});
-//     // const mesh2 = makeCubesMesh();
-//     // renderer.add(mesh2)
-// }
+}

+ 4 - 2
src/apps/render-test/ui.tsx

@@ -16,8 +16,9 @@ import State from './state'
 import Viewport from './components/viewport'
 import FileInput from './components/file-input'
 import ColorTheme from './components/color-theme'
-import Detail from './components/detail'
+import SphereDetail from './components/sphere-detail'
 import Visibility from './components/visibility'
+import Assemblies from './components/assemblies'
 
 
 const styles: StyleRulesCallback = (theme: Theme) => ({
@@ -83,8 +84,9 @@ class UI extends React.Component<{ state: State } & WithStyles, {  }> {
                     <FileInput state={state} classes={classes}></FileInput>
                     <form className={classes.root} autoComplete='off'>
                         <div>
+                            <Assemblies state={state} classes={classes}></Assemblies>
                             <ColorTheme state={state} classes={classes}></ColorTheme>
-                            <Detail state={state} classes={classes}></Detail>
+                            <SphereDetail state={state} classes={classes}></SphereDetail>
                             <Visibility state={state} classes={classes}></Visibility>
                         </div>
                     </form>

+ 24 - 10
src/apps/render-test/utils/index.ts

@@ -6,24 +6,30 @@
 
 import CIF from 'mol-io/reader/cif'
 import { Run, Progress } from 'mol-task'
-import { Structure } from 'mol-model/structure'
+import { Model } from 'mol-model/structure'
+import { VolumeData, parseDensityServerData } from 'mol-model/volume'
+import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server';
 
 export function log(progress: Progress) {
     const p = progress.root.progress
     console.log(`${p.message} ${(p.current/p.max*100).toFixed(2)}%`)
 }
 
+export async function downloadCif(url: string, isBinary: boolean) {
+    const data = await fetch(url);
+    return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
+}
+
 export async function parseCif(data: string|Uint8Array) {
     const comp = CIF.parse(data)
-    const parsed = await Run(comp, log, 100);
+    const parsed = await Run(comp, log, 500);
     if (parsed.isError) throw parsed;
-    return parsed
+    return parsed.result
 }
 
-export async function getStructuresFromPdbId(pdbid: string) {
-    const data = await fetch(`https://files.rcsb.org/download/${pdbid}.cif`)
-    const parsed = await parseCif(await data.text())
-    return Structure.ofData({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) })
+export async function getModelFromPdbId(pdbid: string) {
+    const cif = await downloadCif(`https://files.rcsb.org/download/${pdbid}.cif`, false)
+    return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) })
 }
 
 const readFileAsText = (file: File) => {
@@ -38,7 +44,15 @@ const readFileAsText = (file: File) => {
     })
 }
 
-export async function getStructuresFromFile(file: File) {
-    const parsed = await parseCif(await readFileAsText(file))
-    return Structure.ofData({ kind: 'mmCIF', data: CIF.schema.mmCIF(parsed.result.blocks[0]) })
+export async function getModelFromFile(file: File) {
+    const cif = await parseCif(await readFileAsText(file))
+    return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) })
+}
+
+export type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
+
+export async function getVolumeFromEmdId(emdid: string): Promise<Volume> {
+    const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, true)
+    const data = CIF.schema.densityServer(cif.blocks[1])
+    return { source: data, volume: await Run(parseDensityServerData(data)) }
 }

+ 2 - 2
src/apps/structure-info/volume.ts

@@ -14,7 +14,7 @@ import CIF from 'mol-io/reader/cif'
 import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server';
 import { Run } from 'mol-task';
 import { Table } from 'mol-data/db';
-import { computeVolumeMesh } from 'mol-geo/representation/volume/Mesh';
+import { computeVolumeSurface } from 'mol-geo/representation/volume/surface';
 import { StringBuilder } from 'mol-util';
 
 require('util.promisify').shim();
@@ -38,7 +38,7 @@ function print(data: Volume) {
 }
 
 async function doMesh(data: Volume, filename: string) {
-    const mesh = await Run(computeVolumeMesh(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5)));
+    const mesh = await Run(computeVolumeSurface(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5)));
     console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
 
     // Export the mesh in OBJ format.

+ 4 - 0
src/mol-geo/primitive/icosahedron.ts

@@ -23,6 +23,10 @@ const indices = [
      4, 9, 5, 	2, 4, 11,	6, 2, 10,	8, 6, 7,	9, 8, 1
 ];
 
+export function icosahedronVertexCount(detail: number) {
+    return 10 * Math.pow(Math.pow(2, detail), 2) + 2
+}
+
 export const DefaultIcosahedronProps = {
     radius: 1,
     detail: 0

+ 16 - 0
src/mol-geo/representation/index.ts

@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Task } from 'mol-task'
+import { RenderObject } from 'mol-gl/scene';
+
+export interface RepresentationProps {}
+
+export interface Representation<D, P extends RepresentationProps = {}> {
+    renderObjects: ReadonlyArray<RenderObject>
+    create: (data: D, props?: P) => Task<void>
+    update: (props: P) => Task<void>
+}

+ 13 - 15
src/mol-geo/representation/structure/index.ts

@@ -9,22 +9,20 @@ import { EquivalenceClasses } from 'mol-data/util';
 import { OrderedSet } from 'mol-data/int'
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/scene';
+import { Representation, RepresentationProps } from '..';
 // import { Mat4, EPSILON } from 'mol-math/linear-algebra';
 
-export interface RepresentationProps {
 
-}
-
-export interface UnitsRepresentation<Props = {}> {
+export interface UnitsRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
-    create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: Props) => Task<void>
-    update: (props: RepresentationProps) => Task<boolean>
+    create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: P) => Task<void>
+    update: (props: P) => Task<boolean>
 }
 
-export interface StructureRepresentation<Props = {}> {
+export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> {
     renderObjects: ReadonlyArray<RenderObject>
-    create: (structure: Structure, props?: Props) => Task<void>
-    update: (props: Props) => Task<void>
+    create: (structure: Structure, props?: P) => Task<void>
+    update: (props: P) => Task<void>
 }
 
 interface GroupRepresentation<T> {
@@ -33,13 +31,13 @@ interface GroupRepresentation<T> {
     elementGroup: ElementGroup
 }
 
-export function StructureRepresentation<Props>(reprCtor: () => UnitsRepresentation<Props>): StructureRepresentation<Props> {
+export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> {
     const renderObjects: RenderObject[] = []
-    const groupReprs: GroupRepresentation<Props>[] = []
+    const groupReprs: GroupRepresentation<P>[] = []
 
     return {
         renderObjects,
-        create(structure: Structure, props: Props = {} as Props) {
+        create(structure: Structure, props: P = {} as P) {
             return Task.create('StructureRepresentation.create', async ctx => {
                 const { elements, units } = structure;
                 const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>(
@@ -73,20 +71,20 @@ export function StructureRepresentation<Props>(reprCtor: () => UnitsRepresentati
                     const elementGroup = ElementSet.groupFromUnitIndex(elements, group[0])
                     const repr = reprCtor()
                     groupReprs.push({ repr, units: groupUnits, elementGroup })
-                    await ctx.update({ message: 'Building units...', current: i, max: il });
+                    await ctx.update({ message: 'Building structure unit representations...', current: i, max: il });
                     await ctx.runChild(repr.create(groupUnits, elementGroup, props));
                     renderObjects.push(...repr.renderObjects)
                 }
             });
         },
-        update(props: Props) {
+        update(props: P) {
             return Task.create('StructureRepresentation.update', async ctx => {
                 // TODO check model.id, conformation.id, unit.id, elementGroup(.hashCode/.areEqual)
                 renderObjects.length = 0 // clear
                 for (let i = 0, il = groupReprs.length; i < il; ++i) {
                     const groupRepr = groupReprs[i]
                     const { repr, units, elementGroup } = groupRepr
-                    await ctx.update({ message: 'Updating units...', current: i, max: il });
+                    await ctx.update({ message: 'Updating structure unit representations...', current: i, max: il });
                     if (!await ctx.runChild(repr.update(props))) {
                         await ctx.runChild(repr.create(units, elementGroup, props))
                     }

+ 18 - 10
src/mol-geo/representation/structure/point.ts

@@ -7,11 +7,11 @@
 import { ValueCell } from 'mol-util/value-cell'
 import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene'
 import { OrderedSet } from 'mol-data/int'
-import { Unit, ElementGroup } from 'mol-model/structure';
+import { Unit, ElementGroup, Element } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { fillSerial } from 'mol-gl/renderable/util';
 
-import { RepresentationProps, UnitsRepresentation } from './index';
+import { UnitsRepresentation } from './index';
 import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { createTransforms, createColors, createSizes } from './utils';
@@ -19,20 +19,26 @@ import { deepEqual } from 'mol-util';
 
 export const DefaultPointProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
-    sizeTheme: { name: 'vdw' } as SizeTheme
+    sizeTheme: { name: 'vdw' } as SizeTheme,
+    alpha: 1,
+    visible: true
 }
 export type PointProps = Partial<typeof DefaultPointProps>
 
 export function createPointVertices(unit: Unit, elementGroup: ElementGroup) {
     const elementCount = OrderedSet.size(elementGroup.elements)
     const vertices = new Float32Array(elementCount * 3)
-    const { x, y, z } = unit.model.atomSiteConformation
+
+    const { x, y, z } = unit
+    const l = Element.Location()
+    l.unit = unit
+
     for (let i = 0; i < elementCount; i++) {
-        const e = OrderedSet.getAt(elementGroup.elements, i)
+        l.element = ElementGroup.getAt(elementGroup, i)
         const i3 = i * 3
-        vertices[i3] = x[e]
-        vertices[i3 + 1] = y[e]
-        vertices[i3 + 2] = z[e]
+        vertices[i3] = x(l.element)
+        vertices[i3 + 1] = y(l.element)
+        vertices[i3 + 2] = z(l.element)
     }
     return vertices
 }
@@ -55,7 +61,7 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 _units = units
                 _elementGroup = elementGroup
 
-                const { colorTheme, sizeTheme } = curProps
+                const { colorTheme, sizeTheme, alpha, visible } = curProps
                 const elementCount = OrderedSet.size(elementGroup.elements)
                 const unitCount = units.length
 
@@ -80,6 +86,8 @@ export default function Point(): UnitsRepresentation<PointProps> {
 
                 points = createPointRenderObject({
                     objectId: 0,
+                    alpha,
+                    visible,
 
                     position: ValueCell.create(vertices),
                     id: ValueCell.create(fillSerial(new Float32Array(elementCount))),
@@ -96,7 +104,7 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 renderObjects.push(points)
             })
         },
-        update(props: RepresentationProps) {
+        update(props: PointProps) {
             return Task.create('Point.update', async ctx => {
                 if (!points || !_units || !_elementGroup) return false
 

+ 36 - 19
src/mol-geo/representation/structure/spacefill.ts

@@ -10,46 +10,60 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/s
 // import { createColorTexture } from 'mol-gl/util';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { OrderedSet } from 'mol-data/int'
-import { Unit, ElementGroup } from 'mol-model/structure';
-import { RepresentationProps, UnitsRepresentation } from './index';
+import { Unit, ElementGroup, Element, Queries } from 'mol-model/structure';
+import { UnitsRepresentation } from './index';
 import { Task } from 'mol-task'
 import { MeshBuilder } from '../../shape/mesh-builder';
-import { VdwRadius } from 'mol-model/structure/model/properties/atomic';
 import { createTransforms, createColors } from './utils';
 import { ColorTheme } from '../../theme';
 import VertexMap from '../../shape/vertex-map';
+import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
+import { icosahedronVertexCount } from '../../primitive/icosahedron';
 
 export const DefaultSpacefillProps = {
     detail: 0,
     colorTheme: { name: 'instance-index' } as ColorTheme,
+    alpha: 1,
+    visible: true,
+    doubleSided: false
 }
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
 function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: number) {
-    return Task.create('Spacefill', async ctx => {
-        const meshBuilder = MeshBuilder.create()
+    return Task.create('Sphere mesh', async ctx => {
+        const elementCount = OrderedSet.size(elementGroup.elements)
+        const vertexCount = elementCount * icosahedronVertexCount(detail)
+        const meshBuilder = MeshBuilder.create(vertexCount)
+
+        let radius: Element.Property<number>
+        if (Unit.isAtomic(unit)) {
+            radius = Queries.props.atom.vdw_radius
+        } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) {
+            radius = Queries.props.coarse_grained.sphere_radius
+        } else {
+            console.warn('Unsupported unit type')
+            return meshBuilder.getMesh()
+        }
 
         const v = Vec3.zero()
         const m = Mat4.identity()
 
-        const { x, y, z } = unit.model.atomSiteConformation
-        const { type_symbol } = unit.model.hierarchy.atoms
-        const elementCount = OrderedSet.size(elementGroup.elements)
+        const { x, y, z } = unit
+        const l = Element.Location()
+        l.unit = unit
+
         for (let i = 0; i < elementCount; i++) {
-            const e = OrderedSet.getAt(elementGroup.elements, i)
-            v[0] = x[e]
-            v[1] = y[e]
-            v[2] = z[e]
+            l.element = ElementGroup.getAt(elementGroup, i)
+            v[0] = x(l.element)
+            v[1] = y(l.element)
+            v[2] = z(l.element)
             Mat4.setTranslation(m, v)
 
             meshBuilder.setId(i)
-            meshBuilder.addIcosahedron(m, {
-                radius: VdwRadius(type_symbol.value(e)),
-                detail
-            })
+            meshBuilder.addIcosahedron(m, { radius: radius(l), detail })
 
             if (i % 10000 === 0 && ctx.shouldUpdate) {
-                await ctx.update({ message: 'Spacefill', current: i, max: elementCount });
+                await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount });
             }
         }
 
@@ -67,7 +81,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
             return Task.create('Spacefill.create', async ctx => {
                 renderObjects.length = 0 // clear
 
-                const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props }
+                const { detail, colorTheme, alpha, visible, doubleSided } = { ...DefaultSpacefillProps, ...props }
 
                 await ctx.update('Computing spacefill mesh');
                 const mesh = await ctx.runChild(createSpacefillMesh(units[0], elementGroup, detail))
@@ -83,6 +97,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
 
                 spheres = createMeshRenderObject({
                     objectId: 0,
+                    alpha,
+                    visible,
+                    doubleSided,
 
                     position: mesh.vertexBuffer,
                     normal: mesh.normalBuffer as ValueCell<Float32Array>,
@@ -99,7 +116,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                 renderObjects.push(spheres)
             })
         },
-        update(props: RepresentationProps) {
+        update(props: SpacefillProps) {
             return Task.create('Spacefill.update', async ctx => {
                 if (!spheres) return false
 

+ 4 - 4
src/mol-geo/representation/structure/utils.ts

@@ -9,10 +9,10 @@ import { Mat4 } from 'mol-math/linear-algebra'
 
 import { createUniformColor } from '../../util/color-data';
 import { createUniformSize } from '../../util/size-data';
-import { vdwSizeData } from '../../theme/structure/size/vdw';
+import { elementSizeData } from '../../theme/structure/size/element';
 import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
-import { atomIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
+import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
 
 export function createTransforms(units: ReadonlyArray<Unit>) {
     const unitCount = units.length
@@ -26,7 +26,7 @@ export function createTransforms(units: ReadonlyArray<Unit>) {
 export function createColors(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, vertexMap: VertexMap, props: ColorTheme) {
     switch (props.name) {
         case 'atom-index':
-            return atomIndexColorData({ units, elementGroup, vertexMap })
+            return elementIndexColorData({ units, elementGroup, vertexMap })
         case 'chain-id':
             return chainIdColorData({ units, elementGroup, vertexMap })
         case 'element-symbol':
@@ -43,6 +43,6 @@ export function createSizes(units: ReadonlyArray<Unit>, elementGroup: ElementGro
         case 'uniform':
             return createUniformSize(props)
         case 'vdw':
-            return vdwSizeData({ units, elementGroup, vertexMap })
+            return elementSizeData({ units, elementGroup, vertexMap })
     }
 }

+ 0 - 29
src/mol-geo/representation/volume/Mesh.ts

@@ -1,29 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { VolumeData, VolumeIsoValue } from 'mol-model/volume'
-import { Task } from 'mol-task'
-import { Mesh } from '../../shape/mesh';
-import { computeMarchingCubes } from '../../util/marching-cubes/algorithm';
-
-function computeVolumeMesh(volume: VolumeData, isoValue: VolumeIsoValue) {
-    return Task.create<Mesh>('Volume Surface', async ctx => {
-        ctx.update({ message: 'Marching cubes...' });
-
-        const mesh = await ctx.runChild(computeMarchingCubes({
-            isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue,
-            scalarField: volume.data
-        }));
-
-        const transform = VolumeData.getGridToCartesianTransform(volume);
-        ctx.update({ message: 'Transforming mesh...' });
-        Mesh.transformImmediate(mesh, transform);
-
-        return mesh;
-    });
-}
-
-export { computeVolumeMesh }

+ 41 - 0
src/mol-geo/representation/volume/index.ts

@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Task } from 'mol-task'
+import { RenderObject } from 'mol-gl/scene';
+import { RepresentationProps, Representation } from '..';
+import { VolumeData } from 'mol-model/volume';
+
+export interface VolumeElementRepresentation<P> {
+    renderObjects: ReadonlyArray<RenderObject>
+    create: (volumeData: VolumeData, props: P) => Task<void>
+    update: (props: P) => Task<boolean>
+}
+
+export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> {
+    renderObjects: ReadonlyArray<RenderObject>
+    create: (volumeData: VolumeData, props?: P) => Task<void>
+    update: (props: P) => Task<void>
+}
+
+export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentation<P>): VolumeRepresentation<P> {
+    const renderObjects: RenderObject[] = []
+
+    return {
+        renderObjects,
+        create(volumeData: VolumeData, props: P = {} as P) {
+            return Task.create('VolumeRepresentation.create', async ctx => {
+                const repr = reprCtor()
+                await ctx.update({ message: 'Building volume representation...', current: 0, max: 1 });
+                await ctx.runChild(repr.create(volumeData, props));
+                renderObjects.push(...repr.renderObjects)
+            });
+        },
+        update(props: P) {
+            return Task.create('VolumeRepresentation.update', async ctx => {})
+        }
+    }
+}

+ 95 - 0
src/mol-geo/representation/volume/surface.ts

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { VolumeData, VolumeIsoValue } from 'mol-model/volume'
+import { Task } from 'mol-task'
+import { computeMarchingCubes } from '../../util/marching-cubes/algorithm';
+import { Mesh } from '../../shape/mesh';
+import { VolumeElementRepresentation } from '.';
+import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/scene';
+import { fillSerial } from 'mol-gl/renderable/util';
+import { ValueCell } from 'mol-util';
+import { Mat4 } from 'mol-math/linear-algebra';
+import { createUniformColor } from '../../util/color-data';
+
+export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) {
+    return Task.create<Mesh>('Volume Surface', async ctx => {
+        ctx.update({ message: 'Marching cubes...' });
+
+        const mesh = await ctx.runChild(computeMarchingCubes({
+            isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue,
+            scalarField: volume.data
+        }));
+
+        const transform = VolumeData.getGridToCartesianTransform(volume);
+        ctx.update({ message: 'Transforming mesh...' });
+        Mesh.transformImmediate(mesh, transform);
+
+        return mesh;
+    });
+}
+
+export const DefaultSurfaceProps = {
+    isoValue: VolumeIsoValue.relative({ min: 0, max: 0, mean: 0, sigma: 0 }, 0),
+    alpha: 0.5,
+    visible: true,
+    flatShaded: true,
+    flipSided: true,
+    doubleSided: true
+}
+export type SurfaceProps = Partial<typeof DefaultSurfaceProps>
+
+export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
+    const renderObjects: RenderObject[] = []
+    let surface: MeshRenderObject
+    let curProps = DefaultSurfaceProps
+
+    return {
+        renderObjects,
+        create(volume: VolumeData, props: SurfaceProps = {}) {
+            return Task.create('Point.create', async ctx => {
+                renderObjects.length = 0 // clear
+                curProps = { ...DefaultSurfaceProps, ...props }
+                const { alpha, visible, flatShaded, flipSided, doubleSided } = curProps
+
+                const mesh = await ctx.runChild(computeVolumeSurface(volume, curProps.isoValue))
+                if (!flatShaded) {
+                    Mesh.computeNormalsImmediate(mesh)
+                }
+
+                surface = createMeshRenderObject({
+                    objectId: 0,
+                    alpha,
+                    visible,
+
+                    position: mesh.vertexBuffer,
+                    normal: mesh.normalBuffer,
+                    id: ValueCell.create(fillSerial(new Float32Array(mesh.vertexCount / 3))),
+                    color: createUniformColor({ value: 0x7ec0ee }),
+                    transform: ValueCell.create(new Float32Array(Mat4.identity())),
+                    index: mesh.indexBuffer,
+
+                    instanceCount: 1,
+                    indexCount: mesh.triangleCount,
+                    elementCount: mesh.triangleCount,
+                    positionCount: mesh.vertexCount / 3,
+
+                    flatShaded,
+                    doubleSided,
+                    flipSided
+                })
+                renderObjects.push(surface)
+            })
+        },
+        update(props: SurfaceProps) {
+            return Task.create('Surface.update', async ctx => {
+                // TODO
+                return false
+            })
+        }
+    }
+}

+ 7 - 2
src/mol-geo/shape/mesh.ts

@@ -8,7 +8,7 @@ import { Task } from 'mol-task'
 import { ValueCell } from 'mol-util'
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { Sphere3D } from 'mol-math/geometry'
-import { transformPositionArray } from '../util';
+import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../util';
 
 export interface Mesh {
     /** Number of vertices in the mesh */
@@ -86,7 +86,12 @@ export namespace Mesh {
 
     export function transformRangeImmediate(mesh: Mesh, t: Mat4, offset: number, count: number) {
         transformPositionArray(t, mesh.vertexBuffer.ref.value, offset, count)
-        // transformDirectionArray(n, mesh.normalBuffer.ref.value, offset, count)  // TODO
+        // TODO normals transformation does not work for an unknown reason, ASR
+        // if (mesh.normalBuffer.ref.value) {
+        //     const n = getNormalMatrix(Mat3.zero(), t)
+        //     transformDirectionArray(n, mesh.normalBuffer.ref.value, offset, count)
+        //     mesh.normalsComputed = true;
+        // }
         mesh.normalsComputed = false;
         // mesh.boundingSphere = void 0;
     }

+ 31 - 15
src/mol-geo/theme/structure/color/chain-id.ts

@@ -4,21 +4,32 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Model } from 'mol-model/structure';
+import { ElementGroup, Unit, Queries, Element } from 'mol-model/structure';
 
 import { StructureColorDataProps } from '.';
 import { createAttributeOrElementColor } from '../../../util/color-data';
 import { ColorScale } from 'mol-util/color';
+import { Column } from 'mol-data/db';
 
-function createChainIdMap(model: Model) {
-    const { chains } = model.hierarchy
-    const { label_asym_id } = chains
-
+function createChainIdMap(unit: Unit) {
     const map = new Map<string, number>()
     let index = 0
 
-    for (let i = 0, il = chains._rowCount; i < il; ++i) {
-        const chainId = label_asym_id.value(i)
+    let count: number
+    let asym_id: Column<string>
+    if (Unit.isAtomic(unit)) {
+        asym_id = unit.hierarchy.chains.label_asym_id
+        count = unit.hierarchy.chains._rowCount
+    } else if (Unit.isCoarse(unit)) {
+        asym_id = unit.siteBases.asym_id
+        count = unit.siteBases.count
+    } else {
+        console.warn('Unknown unit type')
+        return { map, count: index }
+    }
+
+    for (let i = 0; i < count; ++i) {
+        const chainId = asym_id.value(i)
         if (map.get(chainId) === undefined) {
             map.set(chainId, index)
             index += 1
@@ -31,20 +42,25 @@ export function chainIdColorData(props: StructureColorDataProps) {
     const { units, elementGroup, vertexMap } = props
     const unit = units[0]
 
-    const { chains, chainSegments } = unit.model.hierarchy
-    const { label_asym_id } = chains
-    const { map, count } = createChainIdMap(unit.model)
+    const { map, count } = createChainIdMap(unit)
 
     const domain = [ 0, count - 1 ]
     const scale = ColorScale.create({ domain })
 
+    let asym_id: Element.Property<string>
+    if (Unit.isAtomic(unit)) {
+        asym_id = Queries.props.chain.label_asym_id
+    } else if (Unit.isCoarse(unit)) {
+        asym_id = Queries.props.coarse_grained.asym_id
+    }
+
+    const l = Element.Location()
+    l.unit = unit
+
     return createAttributeOrElementColor(vertexMap, {
         colorFn: (elementIdx: number) => {
-            const aI = ElementGroup.getAt(elementGroup, elementIdx);
-            const cI = chainSegments.segmentMap[aI]
-            const chainId = label_asym_id.value(cI)
-
-            return scale.color(map.get(chainId) || 0)
+            l.element = ElementGroup.getAt(elementGroup, elementIdx)
+            return scale.color(map.get(asym_id(l)) || 0)
         },
         vertexMap
     })

+ 1 - 1
src/mol-geo/theme/structure/color/atom-index.ts → src/mol-geo/theme/structure/color/element-index.ts

@@ -9,7 +9,7 @@ import { StructureColorDataProps } from '.';
 import { OrderedSet } from 'mol-data/int';
 import { createElementInstanceColor } from '../../../util/color-data';
 
-export function atomIndexColorData(props: StructureColorDataProps) {
+export function elementIndexColorData(props: StructureColorDataProps) {
     const { units, elementGroup, vertexMap } = props
     const instanceCount = units.length
     const elementCount = OrderedSet.size(elementGroup.elements)

+ 1 - 1
src/mol-geo/theme/structure/color/index.ts

@@ -13,7 +13,7 @@ export interface StructureColorDataProps {
     vertexMap: VertexMap
 }
 
-export { atomIndexColorData } from './atom-index'
+export { elementIndexColorData } from './element-index'
 export { chainIdColorData } from './chain-id'
 export { elementSymbolColorData } from './element-symbol'
 export { instanceIndexColorData } from './instance-index'

+ 31 - 0
src/mol-geo/theme/structure/size/element.ts

@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ElementGroup, Element, Unit, Queries } from 'mol-model/structure';
+import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
+import { StructureSizeDataProps } from '.';
+import { createAttributeSize } from '../../../util/size-data';
+
+/** Create attribute data with the size of an element, i.e. vdw for atoms and radius for coarse spheres */
+export function elementSizeData(props: StructureSizeDataProps) {
+    const { units, elementGroup, vertexMap } = props
+    const unit = units[0]
+    let radius: Element.Property<number>
+    if (Unit.isAtomic(unit)) {
+        radius = Queries.props.atom.vdw_radius
+    } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) {
+        radius = Queries.props.coarse_grained.sphere_radius
+    }
+    const l = Element.Location()
+    l.unit = unit
+    return createAttributeSize({
+        sizeFn: (elementIdx: number) => {
+            l.element = ElementGroup.getAt(elementGroup, elementIdx)
+            return radius(l)
+        },
+        vertexMap
+    })
+}

+ 1 - 1
src/mol-geo/theme/structure/size/index.ts

@@ -13,4 +13,4 @@ export interface StructureSizeDataProps {
     vertexMap: VertexMap
 }
 
-export { vdwSizeData } from './vdw'
+export { elementSizeData } from './element'

+ 0 - 23
src/mol-geo/theme/structure/size/vdw.ts

@@ -1,23 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { OrderedSet } from 'mol-data/int';
-import { VdwRadius } from 'mol-model/structure/model/properties/atomic';
-import { StructureSizeDataProps } from '.';
-import { createAttributeSize } from '../../../util/size-data';
-
-
-export function vdwSizeData(props: StructureSizeDataProps) {
-    const { units, elementGroup, vertexMap } = props
-    const { type_symbol } = units[0].model.hierarchy.atoms
-    return createAttributeSize({
-        sizeFn: (elementIdx: number) => {
-            const e = OrderedSet.getAt(elementGroup.elements, elementIdx)
-            return VdwRadius(type_symbol.value(e))
-        },
-        vertexMap
-    })
-}

+ 17 - 6
src/mol-geo/util.ts

@@ -19,18 +19,29 @@ export function normalizeVec3Array<T extends Helpers.NumberArray> (a: T) {
     }
 }
 
-const tmpV = Vec3.zero()
+export function getNormalMatrix(out: Mat3, t: Mat4) {
+    Mat3.fromMat4(out, t)
+    Mat3.invert(out, out)
+    Mat3.transpose(out, out)
+    return out
+}
+
+const tmpV3 = Vec3.zero()
 
 export function transformPositionArray (t: Mat4, array: Helpers.NumberArray, offset: number, count: number) {
     for (let i = 0, il = count * 3; i < il; i += 3) {
-        Vec3.fromArray(tmpV, array, offset + i)
-        Vec3.transformMat4(tmpV, tmpV, t)
-        Vec3.toArray(tmpV, array, offset + i)
+        Vec3.fromArray(tmpV3, array, offset + i)
+        Vec3.transformMat4(tmpV3, tmpV3, t)
+        Vec3.toArray(tmpV3, array, offset + i)
     }
 }
 
-export function transformDirectionArray (t: Mat3, array: Helpers.NumberArray, offset: number, count: number) {
-    // TODO
+export function transformDirectionArray (n: Mat3, array: Helpers.NumberArray, offset: number, count: number) {
+    for (let i = 0, il = count * 3; i < il; i += 3) {
+        Vec3.fromArray(tmpV3, array, offset + i)
+        Vec3.transformMat3(tmpV3, tmpV3, n)
+        Vec3.toArray(tmpV3, array, offset + i)
+    }
 }
 
 export function setArrayZero(array: Helpers.NumberArray) {

+ 2 - 0
src/mol-gl/_spec/renderer.spec.ts

@@ -50,6 +50,8 @@ function createPoints() {
 
     return createPointRenderObject({
         objectId: 0,
+        alpha: 1.0,
+        visible: true,
 
         position,
         id,

+ 10 - 0
src/mol-gl/renderable.ts

@@ -8,6 +8,16 @@ import PointRenderable from './renderable/point'
 import MeshRenderable from './renderable/mesh'
 import { Program } from './webgl/program';
 
+export type BaseProps = {
+    objectId: number
+    alpha: number
+    visible: boolean
+
+    flatShaded?: boolean
+    doubleSided?: boolean
+    flipSided?: boolean
+}
+
 export interface Renderable<T> {
     draw: () => void
     name: string

+ 9 - 6
src/mol-gl/renderable/mesh.ts

@@ -7,7 +7,7 @@
 import { ValueCell } from 'mol-util/value-cell'
 import { ColorData } from 'mol-geo/util/color-data';
 
-import { Renderable } from '../renderable'
+import { Renderable, BaseProps } from '../renderable'
 import { getBaseDefs, getBaseValues, getBaseDefines } from './util'
 import { MeshShaderCode, addShaderDefines } from '../shader-code'
 import { Context } from '../webgl/context';
@@ -17,10 +17,8 @@ type Mesh = 'mesh'
 
 namespace Mesh {
     export type Props = {
-        objectId: number
-
         position: ValueCell<Float32Array>
-        normal?: ValueCell<Float32Array>
+        normal: ValueCell<Float32Array | undefined>
         id: ValueCell<Float32Array>
 
         color: ColorData
@@ -31,12 +29,17 @@ namespace Mesh {
         instanceCount: number
         elementCount: number
         positionCount: number
-    }
+    } & BaseProps
 
     export function create(ctx: Context, props: Props): Renderable<Props> {
+        const defines = getBaseDefines(props)
+        if (props.flatShaded) defines.FLAT_SHADED = ''
+        if (props.doubleSided) defines.DOUBLE_SIDED = ''
+        if (props.flipSided) defines.FLIP_SIDED = ''
+
         const defs: RenderItemProps = {
             ...getBaseDefs(props),
-            shaderCode: addShaderDefines(getBaseDefines(props), MeshShaderCode),
+            shaderCode: addShaderDefines(defines, MeshShaderCode),
             drawMode: 'triangles',
             elementsKind: 'uint32'
         }

+ 2 - 4
src/mol-gl/renderable/point.ts

@@ -6,7 +6,7 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 
-import { Renderable } from '../renderable'
+import { Renderable, BaseProps } from '../renderable'
 import { getBaseValues, getBaseDefs, getBaseDefines } from './util'
 import { PointShaderCode, addShaderDefines } from '../shader-code'
 import { ColorData } from 'mol-geo/util/color-data';
@@ -18,8 +18,6 @@ type Point = 'point'
 
 namespace Point {
     export type Props = {
-        objectId: number
-
         position: ValueCell<Float32Array>
         id: ValueCell<Float32Array>
 
@@ -32,7 +30,7 @@ namespace Point {
         positionCount: number,
 
         usePointSizeAttenuation?: boolean
-    }
+    } & BaseProps
 
     export function create<T = Props>(ctx: Context, props: Props): Renderable<Props> {
         const defines = getBaseDefines(props)

+ 7 - 5
src/mol-gl/renderable/util.ts

@@ -65,9 +65,10 @@ interface BaseProps {
     instanceCount: number,
     elementCount: number,
     positionCount: number,
+    alpha: number,
 
     position: ValueCell<Float32Array>
-    normal?: ValueCell<Float32Array>
+    normal?: ValueCell<Float32Array | undefined>
     id: ValueCell<Float32Array>
     transform: ValueCell<Float32Array>
 
@@ -87,6 +88,7 @@ export function getBaseUniformDefs(props: BaseProps) {
         // light_position: 'v3',
         light_color: 'v3',
         light_ambient: 'v3',
+        alpha: 'f',
 
         objectId: 'i',
         instanceCount: 'i',
@@ -107,9 +109,9 @@ export function getBaseUniformDefs(props: BaseProps) {
 }
 
 export function getBaseUniformValues(props: BaseProps) {
-    const { objectId, instanceCount, elementCount } = props
+    const { objectId, instanceCount, elementCount, alpha } = props
     const uniformValues: UniformValues = {
-        objectId, instanceCount, elementCount
+        objectId, instanceCount, elementCount, alpha
     }
     const color = props.color
     if (color.type === 'instance' || color.type === 'element' || color.type === 'element-instance') {
@@ -133,7 +135,7 @@ export function getBaseAttributeDefs(props: BaseProps) {
         elementId: { kind: 'float32', itemSize: 1, divisor: 0 },
         transform: { kind: 'float32', itemSize: 16, divisor: 1 },
     }
-    if (props.normal) {
+    if (props.normal && props.normal.ref.value) {
         attributeDefs.normal = { kind: 'float32', itemSize: 3, divisor: 0 }
     }
     const color = props.color
@@ -156,7 +158,7 @@ export function getBaseAttributeValues(props: BaseProps) {
         elementId: id.ref.value,
         transform: transform.ref.value
     }
-    if (normal) {
+    if (normal && normal.ref.value) {
         attributeValues.normal = normal.ref.value
     }
     const color = props.color

+ 68 - 26
src/mol-gl/renderer.ts

@@ -11,6 +11,8 @@ import { Camera } from 'mol-view/camera/base';
 import Scene, { RenderObject } from './scene';
 import { Context } from './webgl/context';
 import { Mat4, Vec3 } from 'mol-math/linear-algebra';
+import { Renderable } from './renderable';
+import { Color } from 'mol-util/color';
 
 export interface RendererStats {
     renderableCount: number
@@ -29,6 +31,7 @@ interface Renderer {
     draw: () => void
 
     setViewport: (viewport: Viewport) => void
+    setClearColor: (color: Color) => void
 
     stats: RendererStats
     dispose: () => void
@@ -38,47 +41,83 @@ function getPixelRatio() {
     return (typeof window !== 'undefined') ? window.devicePixelRatio : 1
 }
 
+export const DefaultRendererProps = {
+    clearColor: 0x000000 as Color,
+    viewport: Viewport.create(0, 0, 0, 0)
+}
+export type RendererProps = Partial<typeof DefaultRendererProps>
+
 namespace Renderer {
-    export function create(ctx: Context, camera: Camera): Renderer {
+    export function create(ctx: Context, camera: Camera, props: RendererProps = {}): Renderer {
         const { gl } = ctx
+        let { clearColor, viewport: _viewport } = { ...DefaultRendererProps, ...props }
         const scene = Scene.create(ctx)
 
         const model = Mat4.identity()
-        const viewport = Viewport.create(0, 0, 0, 0)
+        const viewport = Viewport.clone(_viewport)
         const pixelRatio = getPixelRatio()
 
         // const light_position = Vec3.create(0, 0, -100)
         const light_color = Vec3.create(1.0, 1.0, 1.0)
         const light_ambient = Vec3.create(0.5, 0.5, 0.5)
 
+        function setClearColor(color: Color) {
+            const [ r, g, b ] = Color.toRgbNormalized(color)
+            gl.clearColor(r, g, b, 1.0)
+        }
+        setClearColor(clearColor)
+
+        let currentProgramId = -1
+        const drawObject = (r: Renderable<any>, o: RenderObject) => {
+            if (o.props.visible) {
+                if (currentProgramId !== r.program.id) {
+                    if (o.props.doubleSided) {
+                        gl.disable(gl.CULL_FACE)
+                    } else {
+                        gl.enable(gl.CULL_FACE)
+                    }
+
+                    if (o.props.flipSided) {
+                        gl.frontFace(gl.CW)
+                        gl.cullFace(gl.FRONT)
+                    } else {
+                        gl.frontFace(gl.CCW)
+                        gl.cullFace(gl.BACK)
+                    }
+
+                    r.program.use()
+                    r.program.setUniforms({
+                        model,
+                        view: camera.view,
+                        projection: camera.projection,
+
+                        pixelRatio,
+                        viewportHeight: viewport.height,
+
+                        // light_position,
+                        light_color,
+                        light_ambient,
+                    })
+                    currentProgramId = r.program.id
+                }
+                r.draw()
+            }
+        }
+
         const draw = () => {
-            // TODO clear color
+            currentProgramId = -1
+
+            gl.depthMask(true)
             gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
+
+            gl.disable(gl.BLEND)
             gl.enable(gl.DEPTH_TEST)
+            scene.eachOpaque(drawObject)
 
-            // TODO painters sort, filter visible, filter picking, visibility culling?
-            let currentProgramId = -1
-            scene.forEach((r, o) => {
-                if (o.visible) {
-                    if (currentProgramId !== r.program.id) {
-                        r.program.use()
-                        r.program.setUniforms({
-                            model,
-                            view: camera.view,
-                            projection: camera.projection,
-
-                            pixelRatio,
-                            viewportHeight: viewport.height,
-
-                            // light_position,
-                            light_color,
-                            light_ambient,
-                        })
-                        currentProgramId = r.program.id
-                    }
-                    r.draw()
-                }
-            })
+            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
+            gl.enable(gl.BLEND)
+            gl.depthMask(false)
+            scene.eachTransparent(drawObject)
         }
 
         return {
@@ -95,10 +134,13 @@ namespace Renderer {
                 scene.clear()
             },
             draw,
+
+            setClearColor,
             setViewport: (newViewport: Viewport) => {
                 Viewport.copy(viewport, newViewport)
                 gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height)
             },
+
             get stats(): RendererStats {
                 return {
                     renderableCount: scene.count,

+ 15 - 3
src/mol-gl/scene.ts

@@ -16,16 +16,16 @@ function getNextId() {
 
 export type RenderData = { [k: string]: ValueCell<Helpers.TypedArray> }
 
-export interface BaseRenderObject { id: number, type: string, props: {}, visible: boolean }
+export interface BaseRenderObject { id: number, type: string, props: {} }
 export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', props: MeshRenderable.Props }
 export interface PointRenderObject extends BaseRenderObject { type: 'point', props: PointRenderable.Props }
 export type RenderObject = MeshRenderObject | PointRenderObject
 
 export function createMeshRenderObject(props: MeshRenderable.Props): MeshRenderObject {
-    return { id: getNextId(), type: 'mesh', props, visible: true }
+    return { id: getNextId(), type: 'mesh', props }
 }
 export function createPointRenderObject(props: PointRenderable.Props): PointRenderObject {
-    return { id: getNextId(), type: 'point', props, visible: true }
+    return { id: getNextId(), type: 'point', props }
 }
 
 export function createRenderable(ctx: Context, o: RenderObject) {
@@ -40,6 +40,8 @@ interface Scene {
     remove: (o: RenderObject) => void
     clear: () => void
     forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
+    eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
+    eachTransparent: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
     count: number
 }
 
@@ -69,6 +71,16 @@ namespace Scene {
             forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => {
                 renderableMap.forEach(callbackFn)
             },
+            eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => {
+                renderableMap.forEach((r, o) => {
+                    if (o.props.alpha === 1) callbackFn(r, o)
+                })
+            },
+            eachTransparent: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => {
+                renderableMap.forEach((r, o) => {
+                    if (o.props.alpha < 1) callbackFn(r, o)
+                })
+            },
             get count() {
                 return renderableMap.size
             }

+ 2 - 1
src/mol-gl/shader-code.ts

@@ -23,7 +23,8 @@ export const MeshShaderCode: ShaderCode = {
 type ShaderDefine = (
     'UNIFORM_COLOR' | 'ATTRIBUTE_COLOR' | 'INSTANCE_COLOR' | 'ELEMENT_COLOR' | 'ELEMENT_INSTANCE_COLOR' |
     'UNIFORM_SIZE' | 'ATTRIBUTE_SIZE' |
-    'POINT_SIZE_ATTENUATION'
+    'POINT_SIZE_ATTENUATION' |
+    'FLAT_SHADED' | 'DOUBLE_SIDED' | 'FLIP_SIDED'
 )
 export type ShaderDefines = {
     [k in ShaderDefine]?: number|string

+ 23 - 3
src/mol-gl/shader/mesh.frag

@@ -4,14 +4,22 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+#ifdef FLAT_SHADED
+    #extension GL_OES_standard_derivatives : enable
+#endif
+
 precision highp float;
 
 // uniform vec3 light_position;
 uniform vec3 light_color;
 uniform vec3 light_ambient;
 uniform mat4 view;
+uniform float alpha;
 
-varying vec3 vNormal, vViewPosition;
+#ifndef FLAT_SHADED
+    varying vec3 vNormal;
+#endif
+varying vec3 vViewPosition;
 
 #pragma glslify: import('./chunks/color-frag-params.glsl')
 
@@ -35,7 +43,18 @@ void main() {
 
     vec3 L = normalize(lightVector); // light direction
     vec3 V = normalize(vViewPosition); // eye direction
-    vec3 N = normalize(-vNormal); // surface normal
+
+    // surface normal
+    #ifdef FLAT_SHADED
+        vec3 fdx = dFdx(vViewPosition);
+        vec3 fdy = dFdy(vViewPosition);
+        vec3 N = -normalize(cross(fdx, fdy));
+    #else
+        vec3 N = -normalize(vNormal);
+        #ifdef DOUBLE_SIDED
+            N = N * (float(gl_FrontFacing) * 2.0 - 1.0);
+        #endif
+    #endif
 
     // compute our diffuse & specular terms
     float specular = calculateSpecular(L, V, N, shininess) * specularScale;
@@ -46,7 +65,8 @@ void main() {
     vec3 finalColor = material * (diffuse + ambient) + specular;
 
     // gl_FragColor.rgb = N;
+    // gl_FragColor.a = 1.0;
     // gl_FragColor.rgb = vec3(1.0, 0.0, 0.0);
     gl_FragColor.rgb = finalColor;
-    gl_FragColor.a = 1.0;
+    gl_FragColor.a = alpha;
 }

+ 12 - 4
src/mol-gl/shader/mesh.vert

@@ -19,9 +19,11 @@ attribute mat4 transform;
 attribute float instanceId;
 attribute float elementId;
 
-attribute vec3 normal;
+#ifndef FLAT_SHADED
+    attribute vec3 normal;
+    varying vec3 vNormal;
+#endif
 
-varying vec3 vNormal;
 varying vec3 vViewPosition;
 
 #pragma glslify: inverse = require(./utils/inverse.glsl)
@@ -35,6 +37,12 @@ void main(){
     vViewPosition = mvPosition.xyz;
     gl_Position = projection * mvPosition;
 
-    mat3 normalMatrix = transpose(inverse(mat3(modelView)));
-    vNormal = normalize(normalMatrix * normal);
+    #ifndef FLAT_SHADED
+        mat3 normalMatrix = transpose(inverse(mat3(modelView)));
+        vec3 transformedNormal = normalize(normalMatrix * normalize(normal));
+        #if defined(FLIP_SIDED) && !defined(DOUBLE_SIDED) // TODO checking DOUBLE_SIDED should not be required, ASR
+            transformedNormal = -transformedNormal;
+        #endif
+        vNormal = transformedNormal;
+    #endif
 }

+ 3 - 1
src/mol-gl/shader/point.frag

@@ -6,9 +6,11 @@
 
 precision highp float;
 
+uniform float alpha;
+
 #pragma glslify: import('./chunks/color-frag-params.glsl')
 
 void main(){
     #pragma glslify: import('./chunks/color-assign-material.glsl')
-    gl_FragColor = vec4(material, 1.0);
+    gl_FragColor = vec4(material, alpha);
 }

+ 6 - 1
src/mol-gl/webgl/context.ts

@@ -33,6 +33,7 @@ function unbindResources (gl: WebGLRenderingContext) {
 
 type Extensions = {
     angleInstancedArrays: ANGLE_instanced_arrays
+    standardDerivatives: OES_standard_derivatives
     oesElementIndexUint: OES_element_index_uint | null
     oesVertexArrayObject: OES_vertex_array_object | null
 }
@@ -53,6 +54,10 @@ export function createContext(gl: WebGLRenderingContext): Context {
     if (angleInstancedArrays === null) {
         throw new Error('Could not get "ANGLE_instanced_arrays" extension')
     }
+    const standardDerivatives = gl.getExtension('OES_standard_derivatives')
+    if (standardDerivatives === null) {
+        throw new Error('Could not get "OES_standard_derivatives" extension')
+    }
     const oesElementIndexUint = gl.getExtension('OES_element_index_uint')
     if (oesElementIndexUint === null) {
         console.warn('Could not get "OES_element_index_uint" extension')
@@ -67,7 +72,7 @@ export function createContext(gl: WebGLRenderingContext): Context {
 
     return {
         gl,
-        extensions: { angleInstancedArrays, oesElementIndexUint, oesVertexArrayObject },
+        extensions: { angleInstancedArrays, standardDerivatives, oesElementIndexUint, oesVertexArrayObject },
         shaderCache,
         programCache,
         bufferCount: 0,

+ 141 - 6
src/mol-math/linear-algebra/3d/mat3.ts

@@ -17,6 +17,8 @@
  * furnished to do so, subject to the following conditions:
  */
 
+import { Mat4 } from '../3d'
+
 interface Mat3 extends Array<number> { [d: number]: number, '@type': 'mat3', length: 9 }
 
 namespace Mat3 {
@@ -27,6 +29,33 @@ namespace Mat3 {
         return ret as any;
     }
 
+    export function identity(): Mat3 {
+        const out = zero();
+        out[0] = 1;
+        out[1] = 0;
+        out[2] = 0;
+        out[3] = 0;
+        out[4] = 1;
+        out[5] = 0;
+        out[6] = 0;
+        out[7] = 0;
+        out[8] = 1;
+        return out;
+    }
+
+    export function setIdentity(mat: Mat3): Mat3 {
+        mat[0] = 1;
+        mat[1] = 0;
+        mat[2] = 0;
+        mat[3] = 0;
+        mat[4] = 1;
+        mat[5] = 0;
+        mat[6] = 0;
+        mat[7] = 0;
+        mat[8] = 1;
+        return mat;
+    }
+
     export function toArray(a: Mat3, out: Helpers.NumberArray, offset: number) {
         out[offset + 0] = a[0];
         out[offset + 1] = a[1];
@@ -37,9 +66,6 @@ namespace Mat3 {
         out[offset + 6] = a[6];
         out[offset + 7] = a[7];
         out[offset + 8] = a[8];
-        out[offset + 9] = a[9];
-        out[offset + 10] = a[10];
-        out[offset + 11] = a[11];
     }
 
     export function fromArray(a: Mat3, array: Helpers.NumberArray, offset: number) {
@@ -52,11 +78,120 @@ namespace Mat3 {
         a[6] = array[offset + 6]
         a[7] = array[offset + 7]
         a[8] = array[offset + 8]
-        a[9] = array[offset + 9]
-        a[10] = array[offset + 10]
-        a[11] = array[offset + 11]
         return a
     }
+
+    /**
+     * Copies the upper-left 3x3 values into the given mat3.
+     */
+    export function fromMat4(out: Mat3, a: Mat4) {
+        out[0] = a[0];
+        out[1] = a[1];
+        out[2] = a[2];
+        out[3] = a[4];
+        out[4] = a[5];
+        out[5] = a[6];
+        out[6] = a[8];
+        out[7] = a[9];
+        out[8] = a[10];
+        return out;
+    }
+
+    /**
+     * Creates a new Mat3 initialized with values from an existing matrix
+     */
+    export function clone(a: Mat3) {
+        return Mat3.copy(Mat3.zero(), a);
+    }
+
+    /**
+     * Copy the values from one Mat3 to another
+     */
+    export function copy(out: Mat3, a: Mat3) {
+        out[0] = a[0];
+        out[1] = a[1];
+        out[2] = a[2];
+        out[3] = a[3];
+        out[4] = a[4];
+        out[5] = a[5];
+        out[6] = a[6];
+        out[7] = a[7];
+        out[8] = a[8];
+        return out;
+    }
+
+    /**
+     * Transpose the values of a Mat3
+     */
+    export function transpose(out: Mat3, a: Mat3) {
+        // If we are transposing ourselves we can skip a few steps but have to cache some values
+        if (out === a) {
+            const a01 = a[1], a02 = a[2], a12 = a[5];
+            out[1] = a[3];
+            out[2] = a[6];
+            out[3] = a01;
+            out[5] = a[7];
+            out[6] = a02;
+            out[7] = a12;
+        } else {
+            out[0] = a[0];
+            out[1] = a[3];
+            out[2] = a[6];
+            out[3] = a[1];
+            out[4] = a[4];
+            out[5] = a[7];
+            out[6] = a[2];
+            out[7] = a[5];
+            out[8] = a[8];
+        }
+        return out;
+    }
+
+    /**
+     * Inverts a Mat3
+     */
+    export function invert(out: Mat3, a: Mat3): Mat3 {
+        const a00 = a[0], a01 = a[1], a02 = a[2];
+        const a10 = a[3], a11 = a[4], a12 = a[5];
+        const a20 = a[6], a21 = a[7], a22 = a[8];
+
+        const b01 = a22 * a11 - a12 * a21;
+        const b11 = -a22 * a10 + a12 * a20;
+        const b21 = a21 * a10 - a11 * a20;
+
+        // Calculate the determinant
+        let det = a00 * b01 + a01 * b11 + a02 * b21;
+
+        if (!det) {
+            console.warn('non-invertible matrix.', a);
+            return out;
+        }
+        det = 1.0 / det;
+
+        out[0] = b01 * det;
+        out[1] = (-a22 * a01 + a02 * a21) * det;
+        out[2] = (a12 * a01 - a02 * a11) * det;
+        out[3] = b11 * det;
+        out[4] = (a22 * a00 - a02 * a20) * det;
+        out[5] = (-a12 * a00 + a02 * a10) * det;
+        out[6] = b21 * det;
+        out[7] = (-a21 * a00 + a01 * a20) * det;
+        out[8] = (a11 * a00 - a01 * a10) * det;
+        return out;
+    }
+
+    export function determinant(a: Mat3) {
+        const a00 = a[0], a01 = a[1], a02 = a[2];
+        const a10 = a[3], a11 = a[4], a12 = a[5];
+        const a20 = a[6], a21 = a[7], a22 = a[8];
+
+        const b01 = a22 * a11 - a12 * a21;
+        const b11 = -a22 * a10 + a12 * a20;
+        const b21 = a21 * a10 - a11 * a20;
+
+        // Calculate the determinant
+        return a00 * b01 + a01 * b11 + a02 * b21;
+    }
 }
 
 export default Mat3

+ 13 - 2
src/mol-math/linear-algebra/3d/vec3.ts

@@ -18,7 +18,7 @@
  */
 
 import Mat4 from './mat4';
-import { Quat } from '../3d';
+import { Quat, Mat3 } from '../3d';
 
 interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 }
 
@@ -265,7 +265,18 @@ namespace Vec3 {
         return out;
     }
 
-    /** Transforms the vec3 with a quat */
+    /**
+     * Transforms the Vec3 with a Mat3.
+     */
+    export function transformMat3(out: Vec3, a: Vec3, m: Mat3) {
+        const x = a[0], y = a[1], z = a[2];
+        out[0] = x * m[0] + y * m[3] + z * m[6];
+        out[1] = x * m[1] + y * m[4] + z * m[7];
+        out[2] = x * m[2] + y * m[5] + z * m[8];
+        return out;
+    }
+
+    /** Transforms the Vec3 with a quat */
     export function transformQuat(out: Vec3, a: Vec3, q: Quat) {
         // benchmarks: http://jsperf.com/quaternion-transform-vec3-implementations
 

+ 14 - 0
src/mol-math/linear-algebra/3d/vec4.ts

@@ -62,6 +62,20 @@ namespace Vec4 {
         return a
     }
 
+    export function toVec3Array(a: Vec4, out: Helpers.NumberArray, offset: number) {
+        out[offset + 0] = a[0];
+        out[offset + 1] = a[1];
+        out[offset + 2] = a[2];
+    }
+
+    export function fromVec3Array(a: Vec4, array: Helpers.NumberArray, offset: number) {
+        a[0] = array[offset + 0]
+        a[1] = array[offset + 1]
+        a[2] = array[offset + 2]
+        a[3] = 0
+        return a
+    }
+
     export function copy(out: Vec4, a: Vec4) {
         out[0] = a[0];
         out[1] = a[1];

+ 6 - 2
src/mol-model/structure/query/properties.ts

@@ -1,10 +1,11 @@
 /**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { Element, Unit } from '../structure'
+import { VdwRadius } from '../model/properties/atomic';
 
 const constant = {
     true: Element.property(l => true),
@@ -37,7 +38,10 @@ const atom = {
     label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_atom_id.value(l.element)),
     auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.auth_atom_id.value(l.element)),
     label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_alt_id.value(l.element)),
-    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.pdbx_formal_charge.value(l.element))
+    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.pdbx_formal_charge.value(l.element)),
+
+    // Derived
+    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.hierarchy.atoms.type_symbol.value(l.element))),
 }
 
 const residue = {

+ 19 - 14
src/mol-view/viewer.ts

@@ -9,20 +9,20 @@ import InputObserver from 'mol-util/input/input-observer'
 import * as SetUtils from 'mol-util/set'
 import Renderer, { RendererStats } from 'mol-gl/renderer'
 import { RenderObject } from 'mol-gl/scene'
-import { StructureRepresentation } from 'mol-geo/representation/structure';
 
 import TrackballControls from './controls/trackball'
 import { Viewport } from './camera/util'
 import { PerspectiveCamera } from './camera/perspective'
 import { resizeCanvas } from './util';
 import { createContext } from 'mol-gl/webgl/context';
+import { Representation } from 'mol-geo/representation';
 
 interface Viewer {
-    hide: (repr: StructureRepresentation) => void
-    show: (repr: StructureRepresentation) => void
+    hide: (repr: Representation<any>) => void
+    show: (repr: Representation<any>) => void
 
-    add: (repr: StructureRepresentation) => void
-    remove: (repr: StructureRepresentation) => void
+    add: (repr: Representation<any>) => void
+    remove: (repr: Representation<any>) => void
     update: () => void
     clear: () => void
 
@@ -49,13 +49,13 @@ function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLCon
 
 namespace Viewer {
     export function create(canvas: HTMLCanvasElement, container: Element): Viewer {
-        const reprMap = new Map<StructureRepresentation, Set<RenderObject>>()
+        const reprMap = new Map<Representation<any>, Set<RenderObject>>()
 
         const input = InputObserver.create(canvas)
         input.resize.subscribe(handleResize)
 
         const camera = PerspectiveCamera.create({
-            near: 0.01,
+            near: 0.1,
             far: 10000,
             position: Vec3.create(0, 0, 50)
         })
@@ -64,7 +64,12 @@ namespace Viewer {
 
         })
 
-        const gl = getWebGLContext(canvas)
+        const gl = getWebGLContext(canvas, {
+            alpha: false,
+            antialias: true,
+            depth: true,
+            preserveDrawingBuffer: true
+        })
         if (gl === null) {
             throw new Error('Could not create a WebGL rendering context')
         }
@@ -99,16 +104,16 @@ namespace Viewer {
         handleResize()
 
         return {
-            hide: (repr: StructureRepresentation) => {
+            hide: (repr: Representation<any>) => {
                 const renderObjectSet = reprMap.get(repr)
-                if (renderObjectSet) renderObjectSet.forEach(o => o.visible = false)
+                if (renderObjectSet) renderObjectSet.forEach(o => o.props.visible = false)
             },
-            show: (repr: StructureRepresentation) => {
+            show: (repr: Representation<any>) => {
                 const renderObjectSet = reprMap.get(repr)
-                if (renderObjectSet) renderObjectSet.forEach(o => o.visible = true)
+                if (renderObjectSet) renderObjectSet.forEach(o => o.props.visible = true)
             },
 
-            add: (repr: StructureRepresentation) => {
+            add: (repr: Representation<any>) => {
                 const oldRO = reprMap.get(repr)
                 const newRO = new Set<RenderObject>()
                 repr.renderObjects.forEach(o => newRO.add(o))
@@ -120,7 +125,7 @@ namespace Viewer {
                 }
                 reprMap.set(repr, newRO)
             },
-            remove: (repr: StructureRepresentation) => {
+            remove: (repr: Representation<any>) => {
                 const renderObjectSet = reprMap.get(repr)
                 if (renderObjectSet) renderObjectSet.forEach(o => renderer.remove(o))
             },