Browse Source

Merge branch 'picking' of https://github.com/mol-star/mol-star-proto into mol-script

David Sehnal 6 years ago
parent
commit
dd9a5eebc2
67 changed files with 1363 additions and 879 deletions
  1. 2 2
      .travis.yml
  2. 237 420
      package-lock.json
  3. 17 14
      package.json
  4. 19 17
      src/mol-app/ui/visualization/image-canvas.tsx
  5. 76 16
      src/mol-app/ui/visualization/viewport.tsx
  6. 2 0
      src/mol-geo/representation/index.ts
  7. 50 2
      src/mol-geo/representation/structure/index.ts
  8. 33 19
      src/mol-geo/representation/structure/point.ts
  9. 39 52
      src/mol-geo/representation/structure/spacefill.ts
  10. 85 3
      src/mol-geo/representation/structure/utils.ts
  11. 6 0
      src/mol-geo/representation/volume/index.ts
  12. 7 1
      src/mol-geo/representation/volume/surface.ts
  13. 5 2
      src/mol-geo/shape/mesh-builder.ts
  14. 22 0
      src/mol-geo/shape/mesh.ts
  15. 14 10
      src/mol-geo/util/color-data.ts
  16. 1 0
      src/mol-geo/util/marching-cubes/algorithm.ts
  17. 27 0
      src/mol-geo/util/picking.ts
  18. 14 10
      src/mol-gl/_spec/renderer.spec.ts
  19. 2 2
      src/mol-gl/render-object.ts
  20. 19 6
      src/mol-gl/renderable.ts
  21. 10 20
      src/mol-gl/renderable/mesh.ts
  22. 10 20
      src/mol-gl/renderable/point.ts
  23. 7 2
      src/mol-gl/renderable/schema.ts
  24. 2 4
      src/mol-gl/renderable/util.ts
  25. 23 47
      src/mol-gl/renderer.ts
  26. 4 0
      src/mol-gl/scene.ts
  27. 3 3
      src/mol-gl/shader/chunks/color-assign-material.glsl
  28. 10 4
      src/mol-gl/shader/chunks/color-assign-varying.glsl
  29. 2 2
      src/mol-gl/shader/chunks/color-frag-params.glsl
  30. 6 5
      src/mol-gl/shader/chunks/color-vert-params.glsl
  31. 5 0
      src/mol-gl/shader/chunks/common-frag-params.glsl
  32. 10 0
      src/mol-gl/shader/chunks/common-vert-params.glsl
  33. 40 29
      src/mol-gl/shader/mesh.frag
  34. 3 6
      src/mol-gl/shader/mesh.vert
  35. 4 2
      src/mol-gl/shader/point.frag
  36. 3 7
      src/mol-gl/shader/point.vert
  37. 1 1
      src/mol-gl/shader/utils/attenuation.glsl
  38. 2 2
      src/mol-gl/shader/utils/decode-float-rgba.glsl
  39. 9 5
      src/mol-gl/shader/utils/encode-float-rgba.glsl
  40. 7 0
      src/mol-gl/shader/utils/encode-id-rgba.glsl
  41. 4 4
      src/mol-gl/shader/utils/inverse.glsl
  42. 1 1
      src/mol-gl/shader/utils/oren-nayar-diffuse.glsl
  43. 1 1
      src/mol-gl/shader/utils/phong-specular.glsl
  44. 3 3
      src/mol-gl/shader/utils/read-from-texture.glsl
  45. 4 4
      src/mol-gl/shader/utils/transpose.glsl
  46. 16 7
      src/mol-gl/webgl/context.ts
  47. 1 4
      src/mol-gl/webgl/framebuffer.ts
  48. 3 3
      src/mol-gl/webgl/program.ts
  49. 71 47
      src/mol-gl/webgl/render-item.ts
  50. 23 16
      src/mol-gl/webgl/render-target.ts
  51. 38 3
      src/mol-gl/webgl/renderbuffer.ts
  52. 17 8
      src/mol-gl/webgl/texture.ts
  53. 1 1
      src/mol-gl/webgl/uniform.ts
  54. 2 2
      src/mol-math/geometry/symmetry-operator.ts
  55. 36 0
      src/mol-math/linear-algebra/3d/vec2.ts
  56. 40 0
      src/mol-math/linear-algebra/3d/vec3.ts
  57. 56 0
      src/mol-math/linear-algebra/3d/vec4.ts
  58. 1 0
      src/mol-model/structure/model/formats/mmcif.ts
  59. 11 1
      src/mol-model/structure/model/formats/mmcif/sequence.ts
  60. 1 0
      src/mol-model/structure/model/model.ts
  61. 30 3
      src/mol-model/structure/model/properties/sequence.ts
  62. 6 4
      src/mol-model/structure/structure/structure.ts
  63. 19 12
      src/mol-model/structure/structure/unit.ts
  64. 14 2
      src/mol-util/input/input-observer.ts
  65. 9 4
      src/mol-view/stage.ts
  66. 116 14
      src/mol-view/viewer.ts
  67. 1 0
      tsconfig.json

+ 2 - 2
.travis.yml

@@ -1,4 +1,4 @@
 language: node_js
 node_js:
-  - "8"
-  - "6"
+  - "10"
+  - "8"

File diff suppressed because it is too large
+ 237 - 420
package-lock.json


+ 17 - 14
package.json

@@ -22,7 +22,10 @@
     "model-server-watch": "nodemon --watch build/node_modules build/node_modules/servers/model/server.js"
   },
   "nodemonConfig": {
-    "ignoreRoot": ["./node_modules", ".git"],
+    "ignoreRoot": [
+      "./node_modules",
+      ".git"
+    ],
     "ignore": [],
     "delay": "2500"
   },
@@ -63,14 +66,14 @@
   "author": "",
   "license": "MIT",
   "devDependencies": {
-    "@types/argparse": "^1.0.33",
+    "@types/argparse": "^1.0.34",
     "@types/benchmark": "^1.0.31",
     "@types/compression": "0.0.36",
     "@types/express": "^4.11.1",
-    "@types/jest": "^22.2.3",
-    "@types/node": "^10.1.2",
-    "@types/node-fetch": "^1.6.9",
-    "@types/react": "^16.3.14",
+    "@types/jest": "^23.0.0",
+    "@types/node": "~10.1.2",
+    "@types/node-fetch": "^2.1.1",
+    "@types/react": "^16.3.16",
     "@types/react-dom": "^16.0.5",
     "benchmark": "^2.1.4",
     "copyfiles": "^2.0.0",
@@ -81,20 +84,20 @@
     "file-loader": "^1.1.11",
     "glslify-import": "^3.1.0",
     "glslify-loader": "^1.0.2",
-    "jest": "^22.4.4",
+    "jest": "^23.1.0",
     "jest-raw-loader": "^1.0.1",
     "node-sass": "^4.9.0",
     "raw-loader": "^0.5.1",
     "resolve-url-loader": "^2.3.0",
-    "sass-loader": "^7.0.1",
+    "sass-loader": "^7.0.2",
     "style-loader": "^0.21.0",
     "ts-jest": "^22.4.6",
     "tslint": "^5.10.0",
-    "typescript": "^2.8.3",
-    "uglify-js": "^3.3.27",
+    "typescript": "^2.9.1",
+    "uglify-js": "^3.4.0",
     "util.promisify": "^1.0.0",
-    "webpack": "^4.8.3",
-    "webpack-cli": "^2.1.3"
+    "webpack": "^4.10.2",
+    "webpack-cli": "^3.0.1"
   },
   "dependencies": {
     "argparse": "^1.0.10",
@@ -102,8 +105,8 @@
     "express": "^4.16.3",
     "immutable": "^4.0.0-rc.9",
     "node-fetch": "^2.1.2",
-    "react": "^16.3.2",
-    "react-dom": "^16.3.2",
+    "react": "^16.4.0",
+    "react-dom": "^16.4.0",
     "rxjs": "^6.2.0"
   }
 }

+ 19 - 17
src/mol-app/ui/visualization/image-canvas.tsx

@@ -22,39 +22,41 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR
     private canvas: HTMLCanvasElement | null = null;
     private ctx: CanvasRenderingContext2D | null = null;
 
-    componentWillMount() {
+    updateStateFromProps() {
         this.setState({
             imageData: this.props.imageData,
             ...getExtend(this.props.aspectRatio, this.props.maxWidth, this.props.maxHeight)
         })
     }
 
-    componentDidMount() {
+    updateImage() {
         if (this.canvas) {
             this.canvas.width = this.state.imageData.width
             this.canvas.height = this.state.imageData.height
-            this.ctx = this.canvas.getContext('2d')
         }
         if (this.ctx) {
             this.ctx.putImageData(this.state.imageData, 0, 0)
         }
     }
 
+    componentWillMount() {
+        this.updateStateFromProps()
+    }
+
+    componentDidMount() {
+        if (this.canvas && !this.ctx) {
+            this.ctx = this.canvas.getContext('2d')
+            if (this.ctx) this.ctx.imageSmoothingEnabled = false
+        }
+        this.updateImage()
+    }
+
     componentWillReceiveProps() {
-        this.setState({
-            imageData: this.props.imageData,
-            ...getExtend(this.props.aspectRatio, this.props.maxWidth, this.props.maxHeight)
-        })
+        this.updateStateFromProps()
     }
 
     componentDidUpdate() {
-        if (this.canvas) {
-            this.canvas.width = this.state.imageData.width
-            this.canvas.height = this.state.imageData.height
-        }
-        if (this.ctx) {
-            this.ctx.putImageData(this.state.imageData, 0, 0)
-        }
+        this.updateImage()
     }
 
     render() {
@@ -63,10 +65,9 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR
             style={{
                 width: this.state.width + 6,
                 height: this.state.height + 6,
-                position: 'absolute',
+                margin: 10,
+                display: 'inline-block',
                 border: '3px white solid',
-                bottom: 10,
-                left: 10,
             }}
         >
             <canvas
@@ -74,6 +75,7 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR
                 style={{
                     width: this.state.width,
                     height: this.state.height,
+                    imageRendering: 'pixelated'
                 }}
             />
         </div>;

+ 76 - 16
src/mol-app/ui/visualization/viewport.tsx

@@ -94,11 +94,39 @@ export const Logo = () =>
     </div>
 
 
-export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean, showLogo?: boolean, imageData?: ImageData, aspectRatio: number }> {
+type ViewportState = {
+    noWebGl: boolean,
+    showLogo: boolean,
+    aspectRatio: number,
+    width: number
+    height: number
+    images: { [k: string]: ImageData }
+    info: string
+}
+
+export class Viewport extends View<ViewportController, ViewportState, { noWebGl?: boolean, showLogo?: boolean, aspectRatio: number, info: string }> {
     private container: HTMLDivElement | null = null;
     private canvas: HTMLCanvasElement | null = null;
     private defaultBg = { r: 1, g: 1, b: 1 }
-    state = { noWebGl: false, showLogo: true, imageData: undefined, aspectRatio: 1 };
+    state: ViewportState = {
+        noWebGl: false,
+        showLogo: true,
+        images: {},
+        aspectRatio: 1,
+        width: 0,
+        height: 0,
+        info: ''
+    };
+
+    handleResize() {
+        if (this.container) {
+            this.setState({
+                aspectRatio: this.container.clientWidth / this.container.clientHeight,
+                width: this.container.clientWidth,
+                height: this.container.clientHeight
+            })
+        }
+    }
 
     componentDidMount() {
         if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) {
@@ -114,12 +142,23 @@ export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean,
             })
         })
 
-        viewer.didDraw.subscribe(() => this.setState({ imageData: viewer.getImageData() }))
-        viewer.didDraw.subscribe(() => this.setState({ imageData: viewer.getImageData() }))
+        viewer.identified.subscribe(info => {
+            this.setState({ info })
+        })
 
-        if (this.container) {
-            this.setState({ aspectRatio: this.container.clientWidth / this.container.clientHeight })
-        }
+        viewer.didDraw.subscribe(() => {
+            // this.setState({ imageData: viewer.getImageData() })
+            this.setState({
+                images: {
+                    'object': viewer.getImageData('pickObject'),
+                    'instance': viewer.getImageData('pickInstance'),
+                    'element': viewer.getImageData('pickElement')
+                }
+            })
+        })
+
+        viewer.input.resize.subscribe(() => this.handleResize())
+        this.handleResize()
     }
 
     componentWillUnmount() {
@@ -140,14 +179,6 @@ export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean,
     render() {
         if (this.state.noWebGl) return this.renderMissing();
 
-        // const imageData = new ImageData(256, 128)
-
-        let image: JSX.Element | undefined
-        const imageData = this.state.imageData
-        if (imageData) {
-            image = <ImageCanvas imageData={imageData} aspectRatio={this.state.aspectRatio} maxWidth={256} maxHeight={256} />
-        }
-
         const color = this.controller.latestState.clearColor! || this.defaultBg;
         return <div className='molstar-viewport' style={{ backgroundColor: `rgb(${255 * color.r}, ${255 * color.g}, ${255 * color.b})` }}>
             <div ref={elm => this.container = elm} className='molstar-viewport-container'>
@@ -155,7 +186,36 @@ export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean,
             </div>
             {this.state.showLogo ? <Logo /> : void 0}
             <ViewportControls controller={this.controller} />
-            {image}
+            <div
+                style={{
+                    position: 'absolute',
+                    top: 10,
+                    left: 10,
+                    padding: 10,
+                    color: 'lightgrey',
+                    background: 'rgba(0, 0, 0, 0.2)'
+                }}
+            >
+                {this.state.info}
+            </div>
+            <div
+                style={{
+                    position: 'absolute',
+                    bottom: 10,
+                    left: 10,
+                }}
+            >
+                {Object.keys(this.state.images).map(k => {
+                    const imageData = this.state.images[k]
+                    return <ImageCanvas
+                        key={k}
+                        imageData={imageData}
+                        aspectRatio={this.state.aspectRatio}
+                        maxWidth={this.state.width / 4}
+                        maxHeight={this.state.height / 4}
+                    />
+                })}
+            </div>
         </div>;
     }
 }

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

@@ -6,6 +6,7 @@
 
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
+import { PickingId, PickingInfo } from '../util/picking';
 
 export interface RepresentationProps {}
 
@@ -13,4 +14,5 @@ export interface Representation<D, P extends RepresentationProps = {}> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (data: D, props?: P) => Task<void>
     update: (props: P) => Task<void>
+    getLabel: (pickingId: PickingId) => PickingInfo | null
 }

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

@@ -5,22 +5,26 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Structure, StructureSymmetry, Unit } from 'mol-model/structure';
+import { Structure, StructureSymmetry, Unit, Element, Queries } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
 import { Representation, RepresentationProps } from '..';
 import { ColorTheme } from '../../theme';
+import { PickingId, PickingInfo } from '../../util/picking';
 
 export interface UnitsRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (group: Unit.SymmetryGroup, props: P) => Task<void>
     update: (props: P) => Task<boolean>
+    getLocation: (pickingId: PickingId) => Element.Location | null
 }
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (structure: Structure, props?: P) => Task<void>
     update: (props: P) => Task<void>
+    getLocation: (pickingId: PickingId) => Element.Location | null
+    getLabel: (pickingId: PickingId) => PickingInfo | null
 }
 
 interface GroupRepresentation<T> {
@@ -28,12 +32,43 @@ interface GroupRepresentation<T> {
     group: Unit.SymmetryGroup
 }
 
+function label(loc: Element.Location) {
+    const model = loc.unit.model.label
+    const instance = loc.unit.label
+    let element = ''
+
+    if (Unit.isAtomic(loc.unit)) {
+        const asym_id = Queries.props.chain.auth_asym_id(loc)
+        const seq_id = Queries.props.residue.auth_seq_id(loc)
+        const comp_id = Queries.props.residue.auth_comp_id(loc)
+        const atom_id = Queries.props.atom.auth_atom_id(loc)
+        element = `[${comp_id}]${seq_id}:${asym_id}.${atom_id}`
+    } else if (Unit.isCoarse(loc.unit)) {
+        const asym_id = Queries.props.coarse.asym_id(loc)
+        const seq_id_begin = Queries.props.coarse.seq_id_begin(loc)
+        const seq_id_end = Queries.props.coarse.seq_id_end(loc)
+        if (seq_id_begin === seq_id_end) {
+            const entityKey = Queries.props.coarse.entityKey(loc)
+            const seq = loc.unit.model.sequence.byEntityKey[entityKey]
+            const comp_id = seq.compId.value(seq_id_begin)
+            element = `[${comp_id}]${seq_id_begin}:${asym_id}`
+        } else {
+            element = `${seq_id_begin}-${seq_id_end}:${asym_id}`
+        }
+    } else {
+        element = 'unknown'
+    }
+
+    return { label: `${model} ${instance} ${element}` }
+}
+
 export const DefaultStructureProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
     alpha: 1,
     visible: true,
     doubleSided: false,
-    depthMask: true
+    depthMask: true,
+    hoverSelection: { objectId: -1, instanceId: -1, elementId: -1 } as PickingId
 }
 export type StructureProps = Partial<typeof DefaultStructureProps>
 
@@ -42,6 +77,14 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () =
     const groupReprs: GroupRepresentation<P>[] = []
     // let currentProps: typeof DefaultStructureProps
 
+    function getLocation(pickingId: PickingId) {
+        for (let i = 0, il = groupReprs.length; i < il; ++i) {
+            const loc = groupReprs[i].repr.getLocation(pickingId)
+            if (loc) return loc
+        }
+        return null
+    }
+
     return {
         renderObjects,
         create(structure: Structure, props: P = {} as P) {
@@ -77,6 +120,11 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () =
                     renderObjects.push(...repr.renderObjects)
                 }
             })
+        },
+        getLocation,
+        getLabel(pickingId: PickingId) {
+            const loc = getLocation(pickingId)
+            return loc ? label(loc) : null
         }
     }
 }

+ 33 - 19
src/mol-geo/representation/structure/point.ts

@@ -11,20 +11,18 @@ import { Unit, Element } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { fillSerial } from 'mol-gl/renderable/util';
 
-import { UnitsRepresentation } from './index';
+import { UnitsRepresentation, DefaultStructureProps } from './index';
 import VertexMap from '../../shape/vertex-map';
-import { ColorTheme, SizeTheme } from '../../theme';
-import { createTransforms, createColors, createSizes } from './utils';
+import { SizeTheme } from '../../theme';
+import { createTransforms, createColors, createSizes, createFlags } from './utils';
 import { deepEqual, defaults } from 'mol-util';
 import { SortedArray } from 'mol-data/int';
 import { RenderableState, PointValues } from 'mol-gl/renderable';
+import { PickingId } from '../../util/picking';
 
 export const DefaultPointProps = {
-    colorTheme: { name: 'instance-index' } as ColorTheme,
-    sizeTheme: { name: 'vdw' } as SizeTheme,
-    alpha: 1,
-    visible: true,
-    depthMask: true
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'vdw' } as SizeTheme
 }
 export type PointProps = Partial<typeof DefaultPointProps>
 
@@ -50,7 +48,8 @@ export function createPointVertices(unit: Unit) {
 export default function Point(): UnitsRepresentation<PointProps> {
     const renderObjects: RenderObject[] = []
     let points: PointRenderObject
-    let curProps = DefaultPointProps
+    let currentProps = DefaultPointProps
+    let currentGroup: Unit.SymmetryGroup
 
     let _units: ReadonlyArray<Unit>
     let _elements: SortedArray
@@ -58,14 +57,16 @@ export default function Point(): UnitsRepresentation<PointProps> {
     return {
         renderObjects,
         create(group: Unit.SymmetryGroup, props: PointProps = {}) {
+            currentProps = Object.assign({}, DefaultPointProps, props)
+
             return Task.create('Point.create', async ctx => {
                 renderObjects.length = 0 // clear
-                curProps = { ...DefaultPointProps, ...props }
+                currentGroup = group
 
                 _units = group.units
                 _elements = group.elements;
 
-                const { colorTheme, sizeTheme } = curProps
+                const { colorTheme, sizeTheme, hoverSelection } = currentProps
                 const elementCount = _elements.length
 
                 const vertexMap = VertexMap.create(
@@ -87,6 +88,9 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 await ctx.update('Computing point sizes');
                 const size = createSizes(group, vertexMap, sizeTheme)
 
+                await ctx.update('Computing spacefill flags');
+                const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId)
+
                 const instanceCount = group.units.length
 
                 const values: PointValues = {
@@ -95,10 +99,10 @@ export default function Point(): UnitsRepresentation<PointProps> {
                     aTransform: transforms,
                     aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                     ...color,
+                    ...flag,
                     ...size,
 
                     uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
-                    uObjectId: ValueCell.create(0),
                     uInstanceCount: ValueCell.create(instanceCount),
                     uElementCount: ValueCell.create(group.elements.length),
 
@@ -120,8 +124,8 @@ export default function Point(): UnitsRepresentation<PointProps> {
             return Task.create('Point.update', async ctx => {
                 if (!points || !_units || !_elements) return false
 
-                const newProps = { ...curProps, ...props }
-                if (deepEqual(curProps, newProps)) {
+                const newProps = { ...currentProps, ...props }
+                if (deepEqual(currentProps, newProps)) {
                     console.log('props identical, nothing to change')
                     return true
                 }
@@ -136,20 +140,30 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 //     fillSerial(new Uint32Array(elementCount + 1))
                 // )
 
-                if (!deepEqual(curProps.colorTheme, newProps.colorTheme)) {
-                    console.log('colorTheme changed', curProps.colorTheme, newProps.colorTheme)
+                if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) {
+                    console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme)
                     // await ctx.update('Computing point colors');
                     // const color = createColors(_units, _elementGroup, vertexMap, newProps.colorTheme)
                     // ValueCell.update(points.props.color, color)
                 }
 
-                if (!deepEqual(curProps.sizeTheme, newProps.sizeTheme)) {
-                    console.log('sizeTheme changed', curProps.sizeTheme, newProps.sizeTheme)
+                if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) {
+                    console.log('sizeTheme changed', currentProps.sizeTheme, newProps.sizeTheme)
                 }
 
-                curProps = newProps
+                currentProps = newProps
                 return false
             })
+        },
+        getLocation(pickingId: PickingId) {
+            const { objectId, instanceId, elementId } = pickingId
+            if (points.id === objectId) {
+                const l = Element.Location()
+                l.unit = currentGroup.units[instanceId]
+                l.element = currentGroup.elements[elementId]
+                return l
+            }
+            return null
         }
     }
 }

+ 39 - 52
src/mol-geo/representation/structure/spacefill.ts

@@ -8,20 +8,30 @@
 import { ValueCell } from 'mol-util/value-cell'
 
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
-// import { createColorTexture } from 'mol-gl/util';
-import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { Unit, Element, Queries } from 'mol-model/structure';
 import { UnitsRepresentation, DefaultStructureProps } from './index';
 import { Task } from 'mol-task'
-import { MeshBuilder } from '../../shape/mesh-builder';
-import { createTransforms, createColors } from './utils';
+import { createTransforms, createColors, createFlags, createEmptyFlags, createSphereMesh } from './utils';
 import VertexMap from '../../shape/vertex-map';
-import { icosahedronVertexCount } from '../../primitive/icosahedron';
 import { deepEqual, defaults } from 'mol-util';
 import { fillSerial } from 'mol-gl/renderable/util';
 import { RenderableState, MeshValues } from 'mol-gl/renderable';
 import { getMeshData } from '../../util/mesh-data';
 import { Mesh } from '../../shape/mesh';
+import { PickingId } from '../../util/picking';
+
+function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) {
+    let radius: Element.Property<number>
+    if (Unit.isAtomic(unit)) {
+        radius = Queries.props.atom.vdw_radius
+    } else if (Unit.isSpheres(unit)) {
+        radius = Queries.props.coarse.sphere_radius
+    } else {
+        console.warn('Unsupported unit type')
+        return Task.constant('Empty mesh', Mesh.createEmpty(mesh))
+    }
+    return createSphereMesh(unit, radius, detail, mesh)
+}
 
 export const DefaultSpacefillProps = {
     ...DefaultStructureProps,
@@ -31,51 +41,6 @@ export const DefaultSpacefillProps = {
 }
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
-function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) {
-    return Task.create('Sphere mesh', async ctx => {
-        const { elements } = unit;
-        const elementCount = elements.length;
-        const vertexCount = elementCount * icosahedronVertexCount(detail)
-        const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
-
-        let radius: Element.Property<number>
-        if (Unit.isAtomic(unit)) {
-            radius = Queries.props.atom.vdw_radius
-        } else if (Unit.isSpheres(unit)) {
-            radius = Queries.props.coarse.sphere_radius
-        } else {
-            console.warn('Unsupported unit type')
-            return meshBuilder.getMesh()
-        }
-
-        const v = Vec3.zero()
-        const m = Mat4.identity()
-
-        const { x, y, z } = unit.conformation
-        const l = Element.Location()
-        l.unit = unit
-
-        for (let i = 0; i < elementCount; i++) {
-            l.element = elements[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: radius(l), detail })
-
-            if (i % 10000 === 0 && ctx.shouldUpdate) {
-                await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount });
-            }
-        }
-
-        const _mesh = meshBuilder.getMesh()
-        console.log(_mesh)
-        return _mesh
-    })
-}
-
 export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
     const renderObjects: RenderObject[] = []
     let spheres: MeshRenderObject
@@ -93,7 +58,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                 renderObjects.length = 0 // clear
                 currentGroup = group
 
-                const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props }
+                const { detail, colorTheme, hoverSelection } = { ...DefaultSpacefillProps, ...props }
 
                 mesh = await createSpacefillMesh(group.units[0], detail).runAsChild(ctx, 'Computing spacefill mesh')
                 // console.log(mesh)
@@ -105,6 +70,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                 await ctx.update('Computing spacefill colors');
                 const color = createColors(group, vertexMap, colorTheme)
 
+                await ctx.update('Computing spacefill flags');
+                const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId)
+
                 const instanceCount = group.units.length
 
                 const values: MeshValues = {
@@ -112,9 +80,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                     aTransform: transforms,
                     aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                     ...color,
+                    ...flag,
 
                     uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
-                    uObjectId: ValueCell.create(0),
                     uInstanceCount: ValueCell.create(instanceCount),
                     uElementCount: ValueCell.create(group.elements.length),
 
@@ -161,6 +129,15 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                     createColors(currentGroup, vertexMap, newProps.colorTheme, spheres.values)
                 }
 
+                if (newProps.hoverSelection !== currentProps.hoverSelection) {
+                    await ctx.update('Computing spacefill flags');
+                    if (newProps.hoverSelection.objectId === spheres.id) {
+                        createFlags(currentGroup, newProps.hoverSelection.instanceId, newProps.hoverSelection.elementId, spheres.values)
+                    } else {
+                        createEmptyFlags(spheres.values)
+                    }
+                }
+
                 ValueCell.updateIfChanged(spheres.values.uAlpha, newProps.alpha)
                 ValueCell.updateIfChanged(spheres.values.dDoubleSided, newProps.doubleSided)
                 ValueCell.updateIfChanged(spheres.values.dFlipSided, newProps.flipSided)
@@ -172,6 +149,16 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                 currentProps = newProps
                 return true
             })
+        },
+        getLocation(pickingId: PickingId) {
+            const { objectId, instanceId, elementId } = pickingId
+            if (spheres.id === objectId) {
+                const l = Element.Location()
+                l.unit = currentGroup.units[instanceId]
+                l.element = currentGroup.elements[elementId]
+                return l
+            }
+            return null
         }
     }
 }

+ 85 - 3
src/mol-geo/representation/structure/utils.ts

@@ -5,8 +5,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Unit } from 'mol-model/structure';
-import { Mat4 } from 'mol-math/linear-algebra'
+import { Unit, Element } from 'mol-model/structure';
+import { Mat4, Vec2, Vec3 } from 'mol-math/linear-algebra'
 
 import { createUniformColor, ColorData } from '../../util/color-data';
 import { createUniformSize } from '../../util/size-data';
@@ -15,6 +15,11 @@ import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
 import { ValueCell } from 'mol-util';
+import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
+import { Mesh } from '../../shape/mesh';
+import { Task } from 'mol-task';
+import { icosahedronVertexCount } from '../../primitive/icosahedron';
+import { MeshBuilder } from '../../shape/mesh-builder';
 
 export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) {
     const unitCount = units.length
@@ -48,4 +53,81 @@ export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, pro
         case 'vdw':
             return elementSizeData({ group, vertexMap })
     }
-}
+}
+
+export type FlagData = {
+    tFlag: ValueCell<TextureImage>
+    uFlagTexSize: ValueCell<Vec2>
+}
+
+export function createFlags(group: Unit.SymmetryGroup, instanceId: number, elementId: number, flagData?: FlagData): FlagData {
+    const instanceCount = group.units.length
+    const elementCount = group.elements.length
+    const count = instanceCount * elementCount
+    const flags = flagData && flagData.tFlag.ref.value.array.length >= count ? flagData.tFlag.ref.value : createTextureImage(count, 1)
+    let flagOffset = 0
+    for (let i = 0; i < instanceCount; i++) {
+        for (let j = 0, jl = elementCount; j < jl; ++j) {
+            flags.array[flagOffset] = (i === instanceId && j === elementId) ? 255 : 0
+            flagOffset += 1
+        }
+    }
+    // console.log(flags, instanceCount, elementCount)
+    if (flagData) {
+        ValueCell.update(flagData.tFlag, flags)
+        ValueCell.update(flagData.uFlagTexSize, Vec2.create(flags.width, flags.height))
+        return flagData
+    } else {
+        return {
+            tFlag: ValueCell.create(flags),
+            uFlagTexSize: ValueCell.create(Vec2.create(flags.width, flags.height)),
+        }
+    }
+}
+
+const emptyFlagTexture = { array: new Uint8Array(1), width: 1, height: 1 }
+export function createEmptyFlags(flagData?: FlagData) {
+    if (flagData) {
+        ValueCell.update(flagData.tFlag, emptyFlagTexture)
+        ValueCell.update(flagData.uFlagTexSize, Vec2.create(1, 1))
+        return flagData
+    } else {
+        return {
+            tFlag: ValueCell.create(emptyFlagTexture),
+            uFlagTexSize: ValueCell.create(Vec2.create(1, 1)),
+        }
+    }
+}
+
+export function createSphereMesh(unit: Unit, radius: Element.Property<number>, detail: number, mesh?: Mesh) {
+    return Task.create('Sphere mesh', async ctx => {
+        const { elements } = unit;
+        const elementCount = elements.length;
+        const vertexCount = elementCount * icosahedronVertexCount(detail)
+        const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
+
+        const v = Vec3.zero()
+        const m = Mat4.identity()
+
+        const { x, y, z } = unit.conformation
+        const l = Element.Location()
+        l.unit = unit
+
+        for (let i = 0; i < elementCount; i++) {
+            l.element = elements[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: radius(l), detail })
+
+            if (i % 10000 === 0 && ctx.shouldUpdate) {
+                await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount });
+            }
+        }
+
+        return meshBuilder.getMesh()
+    })
+}

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

@@ -8,17 +8,20 @@ import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
 import { RepresentationProps, Representation } from '..';
 import { VolumeData } from 'mol-model/volume';
+import { PickingId, PickingInfo } from '../../util/picking';
 
 export interface VolumeElementRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (volumeData: VolumeData, props: P) => Task<void>
     update: (props: P) => Task<boolean>
+    getLabel: (pickingId: PickingId) => PickingInfo | null
 }
 
 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>
+    getLabel: (pickingId: PickingId) => PickingInfo | null
 }
 
 export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentation<P>): VolumeRepresentation<P> {
@@ -35,6 +38,9 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat
         },
         update(props: P) {
             return Task.create('VolumeRepresentation.update', async ctx => {})
+        },
+        getLabel(pickingId: PickingId) {
+            return null
         }
     }
 }

+ 7 - 1
src/mol-geo/representation/volume/surface.ts

@@ -17,6 +17,8 @@ import { Mat4 } from 'mol-math/linear-algebra';
 import { createUniformColor } from '../../util/color-data';
 import { getMeshData } from '../../util/mesh-data';
 import { RenderableState, MeshValues } from 'mol-gl/renderable';
+import { PickingId } from '../../util/picking';
+import { createEmptyFlags } from '../structure/utils';
 
 export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) {
     return Task.create<Mesh>('Volume Surface', async ctx => {
@@ -65,15 +67,16 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
 
                 const instanceCount = 1
                 const color = createUniformColor({ value: 0x7ec0ee })
+                const flag = createEmptyFlags()
 
                 const values: MeshValues = {
                     ...getMeshData(mesh),
                     aTransform: ValueCell.create(new Float32Array(Mat4.identity())),
                     aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                     ...color,
+                    ...flag,
 
                     uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
-                    uObjectId: ValueCell.create(0),
                     uInstanceCount: ValueCell.create(instanceCount),
                     uElementCount: ValueCell.create(mesh.triangleCount),
 
@@ -100,6 +103,9 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
                 // TODO
                 return false
             })
+        },
+        getLabel(pickingId: PickingId) {
+            return null
         }
     }
 }

+ 5 - 2
src/mol-geo/shape/mesh-builder.ts

@@ -5,13 +5,14 @@
  */
 
 import { ValueCell } from 'mol-util/value-cell'
-import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+import { Vec3, Mat4, Mat3 } from 'mol-math/linear-algebra';
 import { ChunkedArray } from 'mol-data/util';
 
 import Box, { BoxProps } from '../primitive/box';
 import Cylinder, { CylinderProps } from '../primitive/cylinder';
 import Icosahedron, { IcosahedronProps } from '../primitive/icosahedron';
 import { Mesh } from './mesh';
+import { getNormalMatrix } from '../util';
 
 type Primitive = {
     vertices: Float32Array
@@ -29,6 +30,7 @@ export interface MeshBuilder {
 }
 
 const tmpV = Vec3.zero()
+const tmpMat3 = Mat3.zero()
 
 // TODO cache primitives based on props
 
@@ -47,6 +49,7 @@ export namespace MeshBuilder {
 
         const add = (t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices: Uint32Array) => {
             const { elementCount, elementSize } = vertices
+            const n = getNormalMatrix(tmpMat3, t)
             for (let i = 0, il = _vertices.length; i < il; i += 3) {
                 // position
                 Vec3.fromArray(tmpV, _vertices, i)
@@ -54,7 +57,7 @@ export namespace MeshBuilder {
                 ChunkedArray.add3(vertices, tmpV[0], tmpV[1], tmpV[2]);
                 // normal
                 Vec3.fromArray(tmpV, _normals, i)
-                // Vec3.transformDirection(tmpV, tmpV, n)  // TODO
+                Vec3.transformMat3(tmpV, tmpV, n)
                 ChunkedArray.add3(normals, tmpV[0], tmpV[1], tmpV[2]);
 
                 ChunkedArray.add(ids, currentId);

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

@@ -15,6 +15,8 @@ export interface Mesh {
     vertexCount: number,
     /** Number of triangles in the mesh */
     triangleCount: number,
+    /** Number of offsets in the mesh */
+    offsetCount: number,
 
     /** Vertex buffer as array of xyz values wrapped in a value cell */
     vertexBuffer: ValueCell<Float32Array>,
@@ -37,6 +39,26 @@ export interface Mesh {
 }
 
 export namespace Mesh {
+    export function createEmpty(mesh?: Mesh): Mesh {
+        const vb = mesh ? mesh.vertexBuffer.ref.value : new Float32Array(0)
+        const ib = mesh ? mesh.indexBuffer.ref.value : new Uint32Array(0)
+        const nb = mesh ? mesh.normalBuffer.ref.value : new Float32Array(0)
+        const idb = mesh ? mesh.idBuffer.ref.value : new Float32Array(0)
+        const ob = mesh ? mesh.offsetBuffer.ref.value : new Uint32Array(0)
+        return {
+            vertexCount: 0,
+            triangleCount: 0,
+            offsetCount: 0,
+            vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb),
+            indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib),
+            normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb),
+            idBuffer: mesh ? ValueCell.update(mesh.idBuffer, idb) : ValueCell.create(idb),
+            offsetBuffer: mesh ? ValueCell.update(mesh.offsetBuffer, ob) : ValueCell.create(ob),
+            normalsComputed: true,
+            offsetsComputed: true,
+        }
+    }
+
     export function computeNormalsImmediate(surface: Mesh) {
         if (surface.normalsComputed) return;
 

+ 14 - 10
src/mol-geo/util/color-data.ts

@@ -5,7 +5,7 @@
  */
 
 import { ValueCell } from 'mol-util';
-import { TextureImage, createColorTexture, emptyTexture } from 'mol-gl/renderable/util';
+import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
 import { Color } from 'mol-util/color';
 import VertexMap from '../shape/vertex-map';
 import { Vec2, Vec3 } from 'mol-math/linear-algebra';
@@ -20,6 +20,14 @@ export type ColorData = {
     dColorType: ValueCell<string>,
 }
 
+const emptyColorTexture = { array: new Uint8Array(3), width: 1, height: 1 }
+function createEmptyColorTexture() {
+    return {
+        tColor: ValueCell.create(emptyColorTexture),
+        uColorTexSize: ValueCell.create(Vec2.create(1, 1))
+    }
+}
+
 export interface UniformColorProps {
     value: Color
 }
@@ -36,8 +44,7 @@ export function createUniformColor(props: UniformColorProps, colorData?: ColorDa
         return {
             uColor: ValueCell.create(Color.toRgbNormalized(props.value) as Vec3),
             aColor: ValueCell.create(new Float32Array(0)),
-            tColor: ValueCell.create(emptyTexture),
-            uColorTexSize: ValueCell.create(Vec2.zero()),
+            ...createEmptyColorTexture(),
             dColorType: ValueCell.create('uniform'),
         }
     }
@@ -62,7 +69,6 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col
         }
     }
     if (colorData) {
-        console.log('update colordata attribute')
         ValueCell.update(colorData.aColor, colors)
         if (colorData.dColorType.ref.value !== 'attribute') {
             ValueCell.update(colorData.dColorType, 'attribute')
@@ -72,8 +78,7 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col
         return {
             uColor: ValueCell.create(Vec3.zero()),
             aColor: ValueCell.create(colors),
-            tColor: ValueCell.create(emptyTexture),
-            uColorTexSize: ValueCell.create(Vec2.zero()),
+            ...createEmptyColorTexture(),
             dColorType: ValueCell.create('attribute'),
         }
     }
@@ -81,7 +86,6 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col
 
 export function createTextureColor(colors: TextureImage, type: ColorType, colorData?: ColorData): ColorData {
     if (colorData) {
-        console.log('update colordata texture')
         ValueCell.update(colorData.tColor, colors)
         ValueCell.update(colorData.uColorTexSize, Vec2.create(colors.width, colors.height))
         if (colorData.dColorType.ref.value !== type) {
@@ -107,7 +111,7 @@ export interface InstanceColorProps {
 /** Creates color texture with color for each instance/unit */
 export function createInstanceColor(props: InstanceColorProps, colorData?: ColorData): ColorData {
     const { colorFn, instanceCount} = props
-    const colors = colorData && colorData.tColor.ref.value.array.length >= instanceCount * 3 ? colorData.tColor.ref.value : createColorTexture(instanceCount)
+    const colors = colorData && colorData.tColor.ref.value.array.length >= instanceCount * 3 ? colorData.tColor.ref.value : createTextureImage(instanceCount, 3)
     for (let i = 0; i < instanceCount; i++) {
         Color.toArray(colorFn(i), colors.array, i * 3)
     }
@@ -123,7 +127,7 @@ export interface ElementColorProps {
 export function createElementColor(props: ElementColorProps, colorData?: ColorData): ColorData {
     const { colorFn, vertexMap } = props
     const elementCount = vertexMap.offsetCount - 1
-    const colors = colorData && colorData.tColor.ref.value.array.length >= elementCount * 3 ? colorData.tColor.ref.value : createColorTexture(elementCount)
+    const colors = colorData && colorData.tColor.ref.value.array.length >= elementCount * 3 ? colorData.tColor.ref.value : createTextureImage(elementCount, 3)
     for (let i = 0, il = elementCount; i < il; ++i) {
         Color.toArray(colorFn(i), colors.array, i * 3)
     }
@@ -141,7 +145,7 @@ export function createElementInstanceColor(props: ElementInstanceColorProps, col
     const { colorFn, instanceCount, vertexMap } = props
     const elementCount = vertexMap.offsetCount - 1
     const count = instanceCount * elementCount
-    const colors = colorData && colorData.tColor.ref.value.array.length >= count * 3 ? colorData.tColor.ref.value : createColorTexture(count)
+    const colors = colorData && colorData.tColor.ref.value.array.length >= count * 3 ? colorData.tColor.ref.value : createTextureImage(count, 3)
     let colorOffset = 0
     for (let i = 0; i < instanceCount; i++) {
         for (let j = 0, jl = elementCount; j < jl; ++j) {

+ 1 - 0
src/mol-geo/util/marching-cubes/algorithm.ts

@@ -76,6 +76,7 @@ class MarchingCubesComputation {
         const ret: Mesh = {
             vertexCount:  this.state.vertexCount,
             triangleCount: this.state.triangleCount,
+            offsetCount: 0,
             vertexBuffer: os ? ValueCell.update(os.vertexBuffer, vb) : ValueCell.create(vb),
             indexBuffer: os ? ValueCell.update(os.indexBuffer, ib) : ValueCell.create(ib),
             normalBuffer: os ? os.normalBuffer : ValueCell.create(new Float32Array(0)),

+ 27 - 0
src/mol-geo/util/picking.ts

@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+function decodeFloatRGBA(r: number, g: number, b: number) {
+    r = Math.floor(r)
+    g = Math.floor(g)
+    b = Math.floor(b)
+    return r * 256 * 256 + g * 256 + b
+}
+
+export function decodeIdRGBA(r: number, g: number, b: number) {
+    return decodeFloatRGBA(r, g, b) - 1
+}
+
+export interface PickingId {
+    objectId: number
+    instanceId: number
+    elementId: number
+}
+
+export interface PickingInfo {
+    label: string
+    data?: any
+}

+ 14 - 10
src/mol-gl/_spec/renderer.spec.ts

@@ -18,6 +18,8 @@ import { createContext } from '../webgl/context';
 import { RenderableState } from '../renderable';
 import { createPointRenderObject } from '../render-object';
 import { PointValues } from '../renderable/point';
+import Scene from '../scene';
+import { createEmptyFlags } from 'mol-geo/representation/structure/utils';
 
 // function writeImage(gl: WebGLRenderingContext, width: number, height: number) {
 //     const pixels = new Uint8Array(width * height * 4)
@@ -47,6 +49,7 @@ function createPoints() {
     const aInstanceId = ValueCell.create(fillSerial(new Float32Array(1)))
     const color = createUniformColor({ value: 0xFF0000 })
     const size = createUniformSize({ value: 1 })
+    const flag = createEmptyFlags()
 
     const aTransform = ValueCell.create(new Float32Array(16))
     const m4 = Mat4.identity()
@@ -58,10 +61,10 @@ function createPoints() {
         aTransform,
         aInstanceId,
         ...color,
+        ...flag,
         ...size,
 
         uAlpha: ValueCell.create(1.0),
-        uObjectId: ValueCell.create(0),
         uInstanceCount: ValueCell.create(1),
         uElementCount: ValueCell.create(3),
 
@@ -101,23 +104,24 @@ describe('renderer', () => {
     it('points', () => {
         const [ width, height ] = [ 32, 32 ]
         const gl = createGl(width, height, { preserveDrawingBuffer: true })
-        const { ctx, renderer } = createRenderer(gl)
+        const { ctx } = createRenderer(gl)
+        const scene = Scene.create(ctx)
 
         const points = createPoints()
 
-        renderer.add(points)
+        scene.add(points)
         expect(ctx.bufferCount).toBe(6);
-        expect(ctx.textureCount).toBe(1);
-        expect(ctx.vaoCount).toBe(1);
-        expect(ctx.programCache.count).toBe(1);
-        expect(ctx.shaderCache.count).toBe(2);
+        expect(ctx.textureCount).toBe(2);
+        expect(ctx.vaoCount).toBe(4);
+        expect(ctx.programCache.count).toBe(4);
+        expect(ctx.shaderCache.count).toBe(8);
 
-        renderer.remove(points)
+        scene.remove(points)
         expect(ctx.bufferCount).toBe(0);
         expect(ctx.textureCount).toBe(0);
         expect(ctx.vaoCount).toBe(0);
-        expect(ctx.programCache.count).toBe(1);
-        expect(ctx.shaderCache.count).toBe(2);
+        expect(ctx.programCache.count).toBe(4);
+        expect(ctx.shaderCache.count).toBe(8);
 
         ctx.programCache.dispose()
         expect(ctx.programCache.count).toBe(0);

+ 2 - 2
src/mol-gl/render-object.ts

@@ -27,7 +27,7 @@ export function createPointRenderObject(values: PointValues, state: RenderableSt
 
 export function createRenderable(ctx: Context, o: RenderObject) {
     switch (o.type) {
-        case 'mesh': return MeshRenderable(ctx, o.values, o.state)
-        case 'point': return PointRenderable(ctx, o.values, o.state)
+        case 'mesh': return MeshRenderable(ctx, o.id, o.values, o.state)
+        case 'point': return PointRenderable(ctx, o.id, o.values, o.state)
     }
 }

+ 19 - 6
src/mol-gl/renderable.ts

@@ -5,7 +5,8 @@
  */
 
 import { Program } from './webgl/program';
-import { RenderableValues } from './renderable/schema';
+import { RenderableValues, Values, RenderableSchema } from './renderable/schema';
+import { RenderVariant, RenderItem } from './webgl/render-item';
 
 export type RenderableState = {
     visible: boolean
@@ -13,14 +14,26 @@ export type RenderableState = {
 }
 
 export interface Renderable<T extends RenderableValues> {
-    draw: () => void
-    values: T
-    state: RenderableState
-    name: string
-    program: Program
+    readonly values: T
+    readonly state: RenderableState
+
+    render: (variant: RenderVariant) => void
+    getProgram: (variant: RenderVariant) => Program
     update: () => void
     dispose: () => void
 }
 
+export function createRenderable<T extends Values<RenderableSchema>>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> {
+    return {
+        get values () { return values },
+        get state () { return state },
+
+        render: (variant: RenderVariant) => renderItem.render(variant),
+        getProgram: (variant: RenderVariant) => renderItem.getProgram(variant),
+        update: () => renderItem.update(),
+        dispose: () => renderItem.destroy()
+    }
+}
+
 export { PointRenderable, PointSchema, PointValues } from './renderable/point'
 export { MeshRenderable, MeshSchema, MeshValues } from './renderable/mesh'

+ 10 - 20
src/mol-gl/renderable/mesh.ts

@@ -4,11 +4,12 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Renderable, RenderableState } from '../renderable'
+import { Renderable, RenderableState, createRenderable } from '../renderable'
 import { Context } from '../webgl/context';
 import { createRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values } from '../renderable/schema';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema } from '../renderable/schema';
 import { MeshShaderCode } from '../shader-code';
+import { ValueCell } from 'mol-util';
 
 export const MeshSchema = {
     ...BaseSchema,
@@ -21,24 +22,13 @@ export const MeshSchema = {
 export type MeshSchema = typeof MeshSchema
 export type MeshValues = Values<MeshSchema>
 
-export function MeshRenderable(ctx: Context, values: MeshValues, state: RenderableState): Renderable<MeshValues> {
-    const schema = { ...GlobalUniformSchema, ...MeshSchema }
+export function MeshRenderable(ctx: Context, id: number, values: MeshValues, state: RenderableState): Renderable<MeshValues> {
+    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...MeshSchema }
+    const internalValues = {
+        uObjectId: ValueCell.create(id)
+    }
     const schaderCode = MeshShaderCode
-    const renderItem = createRenderItem(ctx, 'triangles', schaderCode, schema, values)
+    const renderItem = createRenderItem(ctx, 'triangles', schaderCode, schema, { ...values, ...internalValues })
 
-    return {
-        draw: () => {
-            renderItem.draw()
-        },
-        get values () { return values },
-        get state () { return state },
-        name: 'mesh',
-        get program () { return renderItem.program },
-        update: () => {
-            renderItem.update()
-        },
-        dispose: () => {
-            renderItem.destroy()
-        }
-    }
+    return createRenderable(renderItem, values, state)
 }

+ 10 - 20
src/mol-gl/renderable/point.ts

@@ -4,11 +4,12 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Renderable, RenderableState } from '../renderable'
+import { Renderable, RenderableState, createRenderable } from '../renderable'
 import { Context } from '../webgl/context';
 import { createRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values } from '../renderable/schema';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema } from '../renderable/schema';
 import { PointShaderCode } from '../shader-code';
+import { ValueCell } from 'mol-util';
 
 export const PointSchema = {
     ...BaseSchema,
@@ -20,24 +21,13 @@ export const PointSchema = {
 export type PointSchema = typeof PointSchema
 export type PointValues = Values<PointSchema>
 
-export function PointRenderable(ctx: Context, values: PointValues, state: RenderableState): Renderable<PointValues> {
-    const schema = { ...GlobalUniformSchema, ...PointSchema }
+export function PointRenderable(ctx: Context, id: number, values: PointValues, state: RenderableState): Renderable<PointValues> {
+    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointSchema }
+    const internalValues = {
+        uObjectId: ValueCell.create(id)
+    }
     const schaderCode = PointShaderCode
-    const renderItem = createRenderItem(ctx, 'points', schaderCode, schema, values)
+    const renderItem = createRenderItem(ctx, 'points', schaderCode, schema, { ...values, ...internalValues })
 
-    return {
-        draw: () => {
-            renderItem.draw()
-        },
-        get values () { return values },
-        get state () { return state },
-        name: 'point',
-        get program () { return renderItem.program },
-        update: () => {
-            renderItem.update()
-        },
-        dispose: () => {
-            renderItem.destroy()
-        }
-    }
+    return createRenderable(renderItem, values, state)
 }

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

@@ -130,6 +130,10 @@ export const GlobalUniformSchema = {
 export type GlobalUniformSchema = typeof GlobalUniformSchema
 export type GlobalUniformValues = { [k in keyof GlobalUniformSchema]: ValueCell<any> }
 
+export const InternalSchema = {
+    uObjectId: UniformSpec('i'),
+}
+
 export const BaseSchema = {
     aInstanceId: AttributeSpec('float32', 1, 1),
     aPosition: AttributeSpec('float32', 3, 0),
@@ -138,13 +142,14 @@ export const BaseSchema = {
     aColor: AttributeSpec('float32', 3, 0),
 
     uAlpha: UniformSpec('f'),
-    uObjectId: UniformSpec('i'),
     uInstanceCount: UniformSpec('i'),
     uElementCount: UniformSpec('i'),
-    uColorTexSize: UniformSpec('v2'),
     uColor: UniformSpec('v3'),
+    uColorTexSize: UniformSpec('v2'),
+    uFlagTexSize: UniformSpec('v2'),
 
     tColor: TextureSpec('rgb', 'ubyte'),
+    tFlag: TextureSpec('alpha', 'ubyte'),
 
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),

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

@@ -18,13 +18,11 @@ export interface TextureImage {
     height: number
 }
 
-export function createColorTexture (n: number): TextureImage {
-    const { length, width, height } = calculateTextureInfo(n, 3)
+export function createTextureImage (n: number, itemSize: number): TextureImage {
+    const { length, width, height } = calculateTextureInfo(n, itemSize)
     return { array: new Uint8Array(length), width, height }
 }
 
-export const emptyTexture = { array: new Uint8Array(0), width: 0, height: 0 }
-
 export function fillSerial<T extends Helpers.NumberArray> (array: T) {
     const n = array.length
     for (let i = 0; i < n; ++i) array[ i ] = i

+ 23 - 47
src/mol-gl/renderer.ts

@@ -15,39 +15,30 @@ import { Renderable } from './renderable';
 import { Color } from 'mol-util/color';
 import { ValueCell } from 'mol-util';
 import { RenderableValues, GlobalUniformValues } from './renderable/schema';
-import { RenderObject } from './render-object';
-import { BehaviorSubject } from 'rxjs';
+import { RenderVariant } from './webgl/render-item';
 
 export interface RendererStats {
-    renderableCount: number
     programCount: number
     shaderCount: number
+
     bufferCount: number
+    framebufferCount: number
+    renderbufferCount: number
     textureCount: number
     vaoCount: number
 }
 
 interface Renderer {
-    add: (o: RenderObject) => void
-    remove: (o: RenderObject) => void
-    update: () => void
-    clear: () => void
-    draw: () => void
+    render: (scene: Scene, variant: RenderVariant) => void
 
     setViewport: (viewport: Viewport) => void
     setClearColor: (color: Color) => void
     getImageData: () => ImageData
 
-    didDraw: BehaviorSubject<number>
-
     stats: RendererStats
     dispose: () => void
 }
 
-function getPixelRatio() {
-    return (typeof window !== 'undefined') ? window.devicePixelRatio : 1
-}
-
 export const DefaultRendererProps = {
     clearColor: 0x000000 as Color,
     viewport: Viewport.create(0, 0, 0, 0)
@@ -58,14 +49,9 @@ namespace 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 startTime = performance.now()
-        const didDraw = new BehaviorSubject(0)
 
         const model = Mat4.identity()
         const viewport = Viewport.clone(_viewport)
-        const pixelRatio = getPixelRatio()
 
         // const lightPosition = Vec3.create(0, 0, -100)
         const lightColor = Vec3.create(1.0, 1.0, 1.0)
@@ -82,7 +68,7 @@ namespace Renderer {
             uView: ValueCell.create(Mat4.clone(camera.view)),
             uProjection: ValueCell.create(Mat4.clone(camera.projection)),
 
-            uPixelRatio: ValueCell.create(pixelRatio),
+            uPixelRatio: ValueCell.create(ctx.pixelRatio),
             uViewportHeight: ValueCell.create(viewport.height),
 
             uLightColor: ValueCell.create(Vec3.clone(lightColor)),
@@ -90,12 +76,13 @@ namespace Renderer {
         }
 
         let currentProgramId = -1
-        const drawObject = (r: Renderable<RenderableValues>) => {
+        const renderObject = (r: Renderable<RenderableValues>, variant: RenderVariant) => {
+            const program = r.getProgram(variant)
             if (r.state.visible) {
-                if (currentProgramId !== r.program.id) {
-                    r.program.use()
-                    r.program.setUniforms(globalUniforms)
-                    currentProgramId = r.program.id
+                if (currentProgramId !== program.id) {
+                    program.use()
+                    program.setUniforms(globalUniforms)
+                    currentProgramId = program.id
                 }
                 if (r.values.dDoubleSided.ref.value) {
                     gl.disable(gl.CULL_FACE)
@@ -113,11 +100,11 @@ namespace Renderer {
 
                 gl.depthMask(r.state.depthMask)
 
-                r.draw()
+                r.render(variant)
             }
         }
 
-        const draw = () => {
+        const render = (scene: Scene, variant: RenderVariant) => {
             ValueCell.update(globalUniforms.uView, camera.view)
             ValueCell.update(globalUniforms.uProjection, camera.projection)
 
@@ -128,29 +115,17 @@ namespace Renderer {
 
             gl.disable(gl.BLEND)
             gl.enable(gl.DEPTH_TEST)
-            scene.eachOpaque(drawObject)
+            scene.eachOpaque((r) => renderObject(r, variant))
 
             gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
             gl.enable(gl.BLEND)
-            scene.eachTransparent(drawObject)
+            scene.eachTransparent((r) => renderObject(r, variant))
 
-            didDraw.next(performance.now() - startTime)
+            gl.finish()
         }
 
         return {
-            add: (o: RenderObject) => {
-                scene.add(o)
-            },
-            remove: (o: RenderObject) => {
-                scene.remove(o)
-            },
-            update: () => {
-                scene.forEach((r, o) => r.update())
-            },
-            clear: () => {
-                scene.clear()
-            },
-            draw,
+            render,
 
             setClearColor,
             setViewport: (newViewport: Viewport) => {
@@ -166,20 +141,21 @@ namespace Renderer {
                 return createImageData(buffer, width, height)
             },
 
-            didDraw,
-
             get stats(): RendererStats {
+                console.log(ctx)
                 return {
-                    renderableCount: scene.count,
                     programCount: ctx.programCache.count,
                     shaderCount: ctx.shaderCache.count,
+
                     bufferCount: ctx.bufferCount,
+                    framebufferCount: ctx.framebufferCount,
+                    renderbufferCount: ctx.renderbufferCount,
                     textureCount: ctx.textureCount,
                     vaoCount: ctx.vaoCount,
                 }
             },
             dispose: () => {
-                scene.clear()
+                // TODO
             }
         }
     }

+ 4 - 0
src/mol-gl/scene.ts

@@ -13,6 +13,7 @@ import { RenderObject, createRenderable } from './render-object';
 interface Scene {
     add: (o: RenderObject) => void
     remove: (o: RenderObject) => void
+    update: () => void
     clear: () => void
     forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
     eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
@@ -39,6 +40,9 @@ namespace Scene {
                     renderableMap.delete(o)
                 }
             },
+            update: () => {
+                renderableMap.forEach((r, o) => r.update())
+            },
             clear: () => {
                 renderableMap.forEach(renderable => renderable.dispose())
                 renderableMap.clear()

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

@@ -1,5 +1,5 @@
 #if defined(dColorType_uniform)
-    vec3 material = uColor;
-#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance)
-    vec3 material = vColor;
+    vec4 material = vec4(uColor, 1.0);
+#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance) || defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking)
+    vec4 material = vColor;
 #endif

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

@@ -1,9 +1,15 @@
 #if defined(dColorType_attribute)
-    vColor = aColor;
+    vColor.rgb = aColor;
 #elif defined(dColorType_instance)
-    vColor = read_vec3(tColor, aInstanceId, uColorTexSize);
+    vColor.rgb = readFromTexture(tColor, aInstanceId, uColorTexSize).rgb;
 #elif defined(dColorType_element)
-    vColor = read_vec3(tColor, aElementId, uColorTexSize);
+    vColor.rgb = readFromTexture(tColor, aElementId, uColorTexSize).rgb;
 #elif defined(dColorType_elementInstance)
-    vColor = read_vec3(tColor, aInstanceId * float(uElementCount) + aElementId, uColorTexSize);
+    vColor.rgb = readFromTexture(tColor, aInstanceId * float(uElementCount) + aElementId, uColorTexSize).rgb;
+#elif defined(dColorType_objectPicking)
+    vColor = encodeIdRGBA(float(uObjectId));
+#elif defined(dColorType_instancePicking)
+    vColor = encodeIdRGBA(aInstanceId);
+#elif defined(dColorType_elementPicking)
+    vColor = encodeIdRGBA(aElementId);
 #endif

+ 2 - 2
src/mol-gl/shader/chunks/color-frag-params.glsl

@@ -1,5 +1,5 @@
 #if defined(dColorType_uniform)
     uniform vec3 uColor;
-#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance)
-    varying vec3 vColor;
+#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance) || defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking)
+    varying vec4 vColor;
 #endif

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

@@ -1,12 +1,13 @@
 #if defined(dColorType_uniform)
     uniform vec3 uColor;
 #elif defined(dColorType_attribute)
-    varying vec3 vColor;
+    varying vec4 vColor;
     attribute vec3 aColor;
 #elif defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance)
-    varying vec3 vColor;
+    varying vec4 vColor;
     uniform vec2 uColorTexSize;
     uniform sampler2D tColor;
-#endif
-
-#pragma glslify: read_vec3 = require(../utils/read-from-texture.glsl)
+#elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking)
+    varying vec4 vColor;
+    #pragma glslify: encodeIdRGBA = require(../utils/encode-id-rgba.glsl)
+#endif

+ 5 - 0
src/mol-gl/shader/chunks/common-frag-params.glsl

@@ -0,0 +1,5 @@
+uniform int uObjectId;
+uniform int uInstanceCount;
+uniform int uElementCount;
+
+varying float vFlag;

+ 10 - 0
src/mol-gl/shader/chunks/common-vert-params.glsl

@@ -0,0 +1,10 @@
+uniform mat4 uProjection, uModel, uView;
+
+uniform int uObjectId;
+uniform int uInstanceCount;
+uniform int uElementCount;
+
+uniform vec2 uFlagTexSize;
+uniform sampler2D tFlag;
+varying float vFlag;
+#pragma glslify: readFromTexture = require(../utils/read-from-texture.glsl)

+ 40 - 29
src/mol-gl/shader/mesh.frag

@@ -9,6 +9,10 @@
 #endif
 
 precision highp float;
+precision highp int;
+
+#pragma glslify: import('./chunks/common-frag-params.glsl')
+#pragma glslify: import('./chunks/color-frag-params.glsl')
 
 // uniform vec3 uLightPosition;
 uniform vec3 uLightColor;
@@ -21,8 +25,6 @@ uniform float uAlpha;
 #endif
 varying vec3 vViewPosition;
 
-#pragma glslify: import('./chunks/color-frag-params.glsl')
-
 #pragma glslify: attenuation = require(./utils/attenuation.glsl)
 #pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl)
 #pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl)
@@ -36,37 +38,46 @@ void main() {
     // material color
     #pragma glslify: import('./chunks/color-assign-material.glsl')
 
-    // determine surface to light direction
-    // vec4 viewLightPosition = view * vec4(lightPosition, 1.0);
-    // vec3 lightVector = viewLightPosition.xyz - vViewPosition;
-    vec3 lightVector = vViewPosition;
+    #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking)
+        // gl_FragColor = vec4(material.r, material.g, material.a, 1.0);
+        gl_FragColor = material;
+    #else
+        // determine surface to light direction
+        // vec4 viewLightPosition = view * vec4(lightPosition, 1.0);
+        // vec3 lightVector = viewLightPosition.xyz - vViewPosition;
+        vec3 lightVector = vViewPosition;
 
-    vec3 L = normalize(lightVector); // light direction
-    vec3 V = normalize(vViewPosition); // eye direction
+        vec3 L = normalize(lightVector); // light direction
+        vec3 V = normalize(vViewPosition); // eye direction
 
-    // surface normal
-    #ifdef dFlatShaded
-        vec3 fdx = dFdx(vViewPosition);
-        vec3 fdy = dFdy(vViewPosition);
-        vec3 N = -normalize(cross(fdx, fdy));
-    #else
-        vec3 N = -normalize(vNormal);
-        #ifdef dDoubleSided
-            N = N * (float(gl_FrontFacing) * 2.0 - 1.0);
+        // surface normal
+        #ifdef dFlatShaded
+            vec3 fdx = dFdx(vViewPosition);
+            vec3 fdy = dFdy(vViewPosition);
+            vec3 N = -normalize(cross(fdx, fdy));
+        #else
+            vec3 N = -normalize(vNormal);
+            #ifdef dDoubleSided
+                N = N * (float(gl_FrontFacing) * 2.0 - 1.0);
+            #endif
         #endif
-    #endif
 
-    // 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;
+        // 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 = material * (diffuse + ambient) + specular;
+        // add the lighting
+        vec3 finalColor = material.rgb * (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 = uAlpha;
+        // 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 = uAlpha;
+
+        if (vFlag == 1.0) {
+            gl_FragColor.rgb = mix(vec3(1.0, 0.4, 0.6), gl_FragColor.rgb, 0.3);
+        }
+    #endif
 }

+ 3 - 6
src/mol-gl/shader/mesh.vert

@@ -5,13 +5,9 @@
  */
 
 precision highp float;
+precision highp int;
 
-uniform mat4 uProjection, uModel, uView;
-
-uniform int uObjectId;
-uniform int uInstanceCount;
-uniform int uElementCount;
-
+#pragma glslify: import('./chunks/common-vert-params.glsl')
 #pragma glslify: import('./chunks/color-vert-params.glsl')
 
 attribute vec3 aPosition;
@@ -31,6 +27,7 @@ varying vec3 vViewPosition;
 
 void main(){
     #pragma glslify: import('./chunks/color-assign-varying.glsl')
+    vFlag = readFromTexture(tFlag, aInstanceId * float(uElementCount) + aElementId, uFlagTexSize).a;
 
     mat4 modelView = uView * uModel * aTransform;
     vec4 mvPosition = modelView * vec4(aPosition, 1.0);

+ 4 - 2
src/mol-gl/shader/point.frag

@@ -5,11 +5,13 @@
  */
 
 precision highp float;
+precision highp int;
 
-uniform float uAlpha;
-
+#pragma glslify: import('./chunks/common-frag-params.glsl')
 #pragma glslify: import('./chunks/color-frag-params.glsl')
 
+uniform float uAlpha;
+
 void main(){
     #pragma glslify: import('./chunks/color-assign-material.glsl')
     gl_FragColor = vec4(material, uAlpha);

+ 3 - 7
src/mol-gl/shader/point.vert

@@ -5,18 +5,14 @@
  */
 
 precision highp float;
+precision highp int;
 
-uniform mat4 uProjection, uModel, uView;
-
-uniform int uObjectId;
-uniform int uInstanceCount;
-uniform int uElementCount;
+#pragma glslify: import('./chunks/common-vert-params.glsl')
+#pragma glslify: import('./chunks/color-vert-params.glsl')
 
 uniform float uPixelRatio;
 uniform float uViewportHeight;
 
-#pragma glslify: import('./chunks/color-vert-params.glsl')
-
 #if defined(dSizeType_uniform)
     uniform float uSize;
 #elif defined(dSizeType_attribute)

+ 1 - 1
src/mol-gl/shader/utils/attenuation.glsl

@@ -4,7 +4,7 @@
 //
 // Improved
 // https://imdoingitwrong.wordpress.com/2011/02/10/improved-light-attenuation/
-float attenuation(float r, float f, float d) {
+float attenuation(const in float r, const in float f, const in float d) {
     float denom = d / r + 1.0;
     float attenuation = 1.0 / (denom*denom);
     float t = (attenuation - f) / (1.0 - f);

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

@@ -1,5 +1,5 @@
-float decodeFloatRGBA(vec4 rgba) {
-  return dot(rgba, vec4(1.0, 1/255.0, 1/65025.0, 1/16581375.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)

+ 9 - 5
src/mol-gl/shader/utils/encode-float-rgba.glsl

@@ -1,8 +1,12 @@
-vec4 encodeFloatRGBA(float v) {
-  vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * v;
-  enc = frac(enc);
-  enc -= enc.yzww * float4(1.0/255.0,1.0/255.0,1.0/255.0,0.0);
-  return enc;
+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)

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

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

+ 4 - 4
src/mol-gl/shader/utils/inverse.glsl

@@ -1,16 +1,16 @@
 // (c) 2014 Mikola Lysenko. MIT License
 // https://github.com/glslify/glsl-inverse
 
-float inverse(float m) {
+float inverse(const in float m) {
   return 1.0 / m;
 }
 
-mat2 inverse(mat2 m) {
+mat2 inverse(const in mat2 m) {
   return mat2(m[1][1],-m[0][1],
              -m[1][0], m[0][0]) / (m[0][0]*m[1][1] - m[0][1]*m[1][0]);
 }
 
-mat3 inverse(mat3 m) {
+mat3 inverse(const in mat3 m) {
   float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];
   float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];
   float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];
@@ -26,7 +26,7 @@ mat3 inverse(mat3 m) {
               b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det;
 }
 
-mat4 inverse(mat4 m) {
+mat4 inverse(const in mat4 m) {
   float
       a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
       a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],

+ 1 - 1
src/mol-gl/shader/utils/oren-nayar-diffuse.glsl

@@ -3,7 +3,7 @@
 
 #define PI 3.14159265
 
-float orenNayarDiffuse(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float roughness, float albedo) {
+float orenNayarDiffuse(const in vec3 lightDirection, const in vec3 viewDirection, const in vec3 surfaceNormal, const in float roughness, const in float albedo) {
     float LdotV = dot(lightDirection, viewDirection);
     float NdotL = dot(lightDirection, surfaceNormal);
     float NdotV = dot(surfaceNormal, viewDirection);

+ 1 - 1
src/mol-gl/shader/utils/phong-specular.glsl

@@ -1,7 +1,7 @@
 // (c) 2014 Mikola Lysenko. MIT License
 // https://github.com/glslify/glsl-specular-phong
 
-float phongSpecular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float shininess) {
+float phongSpecular(const in vec3 lightDirection, const in vec3 viewDirection, const in vec3 surfaceNormal, const in float shininess) {
     //Calculate Phong power
     vec3 R = -reflect(lightDirection, surfaceNormal);
     return pow(max(0.0, dot(viewDirection, R)), shininess);

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

@@ -4,10 +4,10 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-vec3 read_vec3 (sampler2D tex, float i, vec2 size) {
+vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 size) {
     float x = mod(i, size.x);
     float y = floor(i / size.x);
     vec2 uv = (vec2(x, y) + 0.5) / size;
-    return texture2D(tex, uv).rgb;
+    return texture2D(tex, uv);
 }
-#pragma glslify: export(read_vec3)
+#pragma glslify: export(readFromTexture)

+ 4 - 4
src/mol-gl/shader/utils/transpose.glsl

@@ -1,22 +1,22 @@
 // (c) 2014 Mikola Lysenko. MIT License
 // https://github.com/glslify/glsl-transpose
 
-float transpose(float m) {
+float transpose(const in float m) {
   return m;
 }
 
-mat2 transpose(mat2 m) {
+mat2 transpose(const in mat2 m) {
   return mat2(m[0][0], m[1][0],
               m[0][1], m[1][1]);
 }
 
-mat3 transpose(mat3 m) {
+mat3 transpose(const in mat3 m) {
   return mat3(m[0][0], m[1][0], m[2][0],
               m[0][1], m[1][1], m[2][1],
               m[0][2], m[1][2], m[2][2]);
 }
 
-mat4 transpose(mat4 m) {
+mat4 transpose(const in mat4 m) {
   return mat4(m[0][0], m[1][0], m[2][0], m[3][0],
               m[0][1], m[1][1], m[2][1], m[3][1],
               m[0][2], m[1][2], m[2][2], m[3][2],

+ 16 - 7
src/mol-gl/webgl/context.ts

@@ -7,6 +7,10 @@
 import { createProgramCache, ProgramCache } from './program'
 import { createShaderCache, ShaderCache } from './shader'
 
+function getPixelRatio() {
+    return (typeof window !== 'undefined') ? window.devicePixelRatio : 1
+}
+
 function unbindResources (gl: WebGLRenderingContext) {
     // bind null to all texture units
     const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
@@ -61,6 +65,7 @@ type Extensions = {
 export interface Context {
     gl: WebGLRenderingContext
     extensions: Extensions
+    pixelRatio: number
 
     shaderCache: ShaderCache
     programCache: ProgramCache
@@ -71,8 +76,8 @@ export interface Context {
     textureCount: number
     vaoCount: number
 
-    readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
     unbindFramebuffer: () => void
+    readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
     destroy: () => void
 }
 
@@ -100,6 +105,7 @@ export function createContext(gl: WebGLRenderingContext): Context {
     return {
         gl,
         extensions: { angleInstancedArrays, standardDerivatives, oesElementIndexUint, oesVertexArrayObject },
+        pixelRatio: getPixelRatio(),
 
         shaderCache,
         programCache,
@@ -110,14 +116,17 @@ export function createContext(gl: WebGLRenderingContext): Context {
         textureCount: 0,
         vaoCount: 0,
 
+        unbindFramebuffer: () => unbindFramebuffer(gl),
         readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
-            if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
-                gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
-            } else {
-                console.error('Reading pixels failed. Framebuffer not complete.')
-            }
+            gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
+            // TODO check is very expensive
+            // if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
+            //     gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
+            // } else {
+            //     console.error('Reading pixels failed. Framebuffer not complete.')
+            // }
         },
-        unbindFramebuffer: () => unbindFramebuffer(gl),
+
         destroy: () => {
             unbindResources(gl)
             programCache.dispose()

+ 1 - 4
src/mol-gl/webgl/framebuffer.ts

@@ -29,10 +29,7 @@ export function createFramebuffer (ctx: Context): Framebuffer {
     return {
         id: getNextFramebufferId(),
 
-        bind: () => {
-            gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer)
-        },
-
+        bind: () => gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer),
         destroy: () => {
             if (destroyed) return
             gl.deleteFramebuffer(_framebuffer)

+ 3 - 3
src/mol-gl/webgl/program.ts

@@ -36,9 +36,9 @@ function getAttributeLocations(ctx: Context, program: WebGLProgram, schema: Rend
         const spec = schema[k]
         if (spec.type === 'attribute') {
             const loc = gl.getAttribLocation(program, k)
-            if (loc === -1) {
-                console.info(`Could not get attribute location for '${k}'`)
-            }
+            // if (loc === -1) {
+            //     console.info(`Could not get attribute location for '${k}'`)
+            // }
             locations[k] = loc
         }
     })

+ 71 - 47
src/mol-gl/webgl/render-item.ts

@@ -9,9 +9,11 @@ import { createTextures } from './texture';
 import { Context } from './context';
 import { ShaderCode, addShaderDefines } from '../shader-code';
 import { Program } from './program';
-import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues } from '../renderable/schema';
+import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues, Values } from '../renderable/schema';
 import { idFactory } from 'mol-util/id-factory';
 import { deleteVertexArray, createVertexArray } from './vertex-array';
+import { ValueCell } from 'mol-util';
+import { ReferenceItem } from 'mol-util/reference-cache';
 
 const getNextRenderItemId = idFactory()
 
@@ -32,14 +34,24 @@ export function getDrawMode(ctx: Context, drawMode: DrawMode) {
 
 export interface RenderItem {
     readonly id: number
-    readonly programId: number
-    readonly program: Program
+    getProgram: (variant: RenderVariant) => Program
 
+    render: (variant: RenderVariant) => void
     update: () => void
-    draw: () => void
     destroy: () => void
 }
 
+const RenderVariantDefines = {
+    'draw': {},
+    'pickObject': { dColorType: ValueCell.create('objectPicking') },
+    'pickInstance': { dColorType: ValueCell.create('instancePicking') },
+    'pickElement': { dColorType: ValueCell.create('elementPicking') }
+}
+export type RenderVariant = keyof typeof RenderVariantDefines
+
+type ProgramVariants = { [k: string]: ReferenceItem<Program> }
+type VertexArrayVariants = { [k: string]: WebGLVertexArrayObjectOES | undefined }
+
 export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues): RenderItem {
     const id = getNextRenderItemId()
     const { programCache } = ctx
@@ -49,9 +61,14 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
     const versions = getValueVersions(values)
 
     const glDrawMode = getDrawMode(ctx, drawMode)
-    let drawProgram = programCache.get(ctx, {
-        shaderCode: addShaderDefines(defineValues, shaderCode),
-        schema
+
+    const programs: ProgramVariants = {}
+    Object.keys(RenderVariantDefines).forEach(k => {
+        const variantDefineValues: Values<RenderableSchema> = (RenderVariantDefines as any)[k]
+        programs[k] = programCache.get(ctx, {
+            shaderCode: addShaderDefines({ ...defineValues, ...variantDefineValues }, shaderCode),
+            schema
+        })
     })
 
     const textures = createTextures(ctx, schema, textureValues)
@@ -63,64 +80,67 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
         elementsBuffer = createElementsBuffer(ctx, elements.ref.value)
     }
 
-    let vertexArray: WebGLVertexArrayObjectOES | undefined = createVertexArray(ctx, drawProgram.value, attributeBuffers, elementsBuffer)
+    const vertexArrays: VertexArrayVariants = {}
+    Object.keys(RenderVariantDefines).forEach(k => {
+        vertexArrays[k] = createVertexArray(ctx, programs[k].value, attributeBuffers, elementsBuffer)
+    })
 
     let drawCount = values.drawCount.ref.value
     let instanceCount = values.instanceCount.ref.value
 
     let destroyed = false
 
-    function render(program: Program) {
-        program.setUniforms(uniformValues)
-        if (oesVertexArrayObject && vertexArray) {
-            oesVertexArrayObject.bindVertexArrayOES(vertexArray)
-        } else {
-            program.bindAttributes(attributeBuffers)
-            if (elementsBuffer) elementsBuffer.bind()
-        }
-        program.bindTextures(textures)
-        if (elementsBuffer) {
-            angleInstancedArrays.drawElementsInstancedANGLE(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount);
-        } else {
-            angleInstancedArrays.drawArraysInstancedANGLE(glDrawMode, 0, drawCount, instanceCount)
-        }
-    }
-
     return {
         id,
-        get programId () { return drawProgram.value.id },
-        get program () { return drawProgram.value },
-
-        draw: () => {
-            render(drawProgram.value)
+        getProgram: (variant: RenderVariant) => programs[variant].value,
+
+        render: (variant: RenderVariant) => {
+            const program = programs[variant].value
+            const vertexArray = vertexArrays[variant]
+            program.setUniforms(uniformValues)
+            if (oesVertexArrayObject && vertexArray) {
+                oesVertexArrayObject.bindVertexArrayOES(vertexArray)
+            } else {
+                program.bindAttributes(attributeBuffers)
+                if (elementsBuffer) elementsBuffer.bind()
+            }
+            program.bindTextures(textures)
+            if (elementsBuffer) {
+                angleInstancedArrays.drawElementsInstancedANGLE(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount);
+            } else {
+                angleInstancedArrays.drawArraysInstancedANGLE(glDrawMode, 0, drawCount, instanceCount)
+            }
         },
         update: () => {
             let defineChange = false
             Object.keys(defineValues).forEach(k => {
                 const value = defineValues[k]
                 if (value.ref.version !== versions[k]) {
-                    console.log('define version changed', k)
+                    // console.log('define version changed', k)
                     defineChange = true
                     versions[k] = value.ref.version
                 }
             })
 
             if (defineChange) {
-                console.log('some defines changed, need to rebuild program')
-                drawProgram.free()
-                drawProgram = programCache.get(ctx, {
-                    shaderCode: addShaderDefines(defineValues, shaderCode),
-                    schema
+                // console.log('some defines changed, need to rebuild programs')
+                Object.keys(RenderVariantDefines).forEach(k => {
+                    const variantDefineValues: Values<RenderableSchema> = (RenderVariantDefines as any)[k]
+                    programs[k].free()
+                    programs[k] = programCache.get(ctx, {
+                        shaderCode: addShaderDefines({ ...defineValues, ...variantDefineValues }, shaderCode),
+                        schema
+                    })
                 })
             }
 
             if (values.drawCount.ref.version !== versions.drawCount) {
-                console.log('drawCount version changed')
+                // console.log('drawCount version changed')
                 drawCount = values.drawCount.ref.value
                 versions.drawCount = values.drawCount.ref.version
             }
             if (values.instanceCount.ref.version !== versions.instanceCount) {
-                console.log('instanceCount version changed')
+                // console.log('instanceCount version changed')
                 instanceCount = values.instanceCount.ref.value
                 versions.instanceCount = values.instanceCount.ref.version
             }
@@ -132,10 +152,10 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
                 if (value.ref.version !== versions[k]) {
                     const buffer = attributeBuffers[k]
                     if (buffer.length >= value.ref.value.length) {
-                        console.log('attribute array large enough to update', k)
+                        // console.log('attribute array large enough to update', k)
                         attributeBuffers[k].updateData(value.ref.value)
                     } else {
-                        console.log('attribute array to small, need to create new attribute', k)
+                        // console.log('attribute array to small, need to create new attribute', k)
                         attributeBuffers[k].destroy()
                         const spec = schema[k] as AttributeSpec<ArrayKind>
                         attributeBuffers[k] = createAttributeBuffer(ctx, value.ref.value, spec.itemSize, spec.divisor)
@@ -147,10 +167,10 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
 
             if (elementsBuffer && values.elements.ref.version !== versions.elements) {
                 if (elementsBuffer.length >= values.elements.ref.value.length) {
-                    console.log('elements array large enough to update')
+                    // console.log('elements array large enough to update')
                     elementsBuffer.updateData(values.elements.ref.value)
                 } else {
-                    console.log('elements array to small, need to create new elements')
+                    // console.log('elements array to small, need to create new elements')
                     elementsBuffer.destroy()
                     elementsBuffer = createElementsBuffer(ctx, values.elements.ref.value)
                     bufferChange = true
@@ -159,15 +179,17 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
             }
 
             if (defineChange || bufferChange) {
-                console.log('program/defines or buffers changed, rebuild vao')
-                deleteVertexArray(ctx, vertexArray)
-                vertexArray = createVertexArray(ctx, drawProgram.value, attributeBuffers, elementsBuffer)
+                // console.log('program/defines or buffers changed, rebuild vaos')
+                Object.keys(RenderVariantDefines).forEach(k => {
+                    deleteVertexArray(ctx, vertexArrays[k])
+                    vertexArrays[k] = createVertexArray(ctx, programs[k].value, attributeBuffers, elementsBuffer)
+                })
             }
 
             Object.keys(textureValues).forEach(k => {
                 const value = textureValues[k]
                 if (value.ref.version !== versions[k]) {
-                    console.log('texture version changed, uploading image', k)
+                    // console.log('texture version changed, uploading image', k)
                     textures[k].load(value.ref.value)
                     versions[k] = value.ref.version
                 }
@@ -175,11 +197,13 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
         },
         destroy: () => {
             if (!destroyed) {
-                drawProgram.free()
+                Object.keys(RenderVariantDefines).forEach(k => {
+                    programs[k].free()
+                    deleteVertexArray(ctx, vertexArrays[k])
+                })
                 Object.keys(textures).forEach(k => textures[k].destroy())
                 Object.keys(attributeBuffers).forEach(k => attributeBuffers[k].destroy())
                 if (elementsBuffer) elementsBuffer.destroy()
-                deleteVertexArray(ctx, vertexArray)
                 destroyed = true
             }
         }

+ 23 - 16
src/mol-gl/webgl/render-target.ts

@@ -4,11 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Context } from './context'
+import { Context, createImageData } from './context'
 import { idFactory } from 'mol-util/id-factory';
 import { createTexture } from './texture';
 import { createFramebuffer } from './framebuffer';
-// import { createRenderbuffer } from './renderbuffer';
+import { createRenderbuffer } from './renderbuffer';
 
 const getNextRenderTargetId = idFactory()
 
@@ -17,27 +17,29 @@ export interface RenderTarget {
 
     bind: () => void
     setSize: (width: number, height: number) => void
-    readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
+    getImageData: () => ImageData
     destroy: () => void
 }
 
 export function createRenderTarget (ctx: Context, _width: number, _height: number): RenderTarget {
     const { gl } = ctx
 
+    const image = {
+        array: new Uint8Array(_width * _height * 4),
+        width: _width,
+        height: _height
+    }
+
     const targetTexture = createTexture(ctx, 'rgba', 'ubyte')
-    targetTexture.setSize(_width, _height)
+    targetTexture.load(image)
 
     const framebuffer = createFramebuffer(ctx)
-    framebuffer.bind()
 
     // attach the texture as the first color attachment
-    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture.texture, 0);
+    targetTexture.attachFramebuffer(framebuffer, 'color0')
 
-    // const depthRenderbuffer = createRenderbuffer(ctx)
-    // depthRenderbuffer.bind()
-    // // make a depth buffer and the same size as the targetTexture
-    // gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTexture.width, targetTexture.height);
-    // gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderbuffer);
+    // make a depth renderbuffer of the same size as the targetTexture
+    const depthRenderbuffer = createRenderbuffer(ctx, 'depth16', 'depth', _width, _height)
 
     let destroyed = false
 
@@ -51,18 +53,23 @@ export function createRenderTarget (ctx: Context, _width: number, _height: numbe
         setSize: (width: number, height: number) => {
             _width = width
             _height = height
-            targetTexture.setSize(_width, _height)
+            image.array = new Uint8Array(_width * _height * 4)
+            image.width = _width
+            image.height = _height
+            targetTexture.load(image)
+
+            depthRenderbuffer.setSize(_width, _height)
         },
-        readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
+        getImageData: () => {
             framebuffer.bind()
-            ctx.readPixels(x, y, width, height, buffer)
-            ctx.unbindFramebuffer()
+            ctx.readPixels(0, 0, _width, _height, image.array)
+            return createImageData(image.array, _width, _height)
         },
         destroy: () => {
             if (destroyed) return
             targetTexture.destroy()
             framebuffer.destroy()
-            // depthRenderbuffer.destroy()
+            depthRenderbuffer.destroy()
             destroyed = true
         }
     }

+ 38 - 3
src/mol-gl/webgl/renderbuffer.ts

@@ -9,28 +9,63 @@ import { idFactory } from 'mol-util/id-factory';
 
 const getNextRenderbufferId = idFactory()
 
+export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil'
+export type RenderbufferAttachment = 'depth' | 'stencil' | 'depth-stencil' | 'color0'
+
+export function getFormat(ctx: Context, format: RenderbufferFormat) {
+    const { gl } = ctx
+    switch (format) {
+        case 'depth16': return gl.DEPTH_COMPONENT16
+        case 'stencil8': return gl.STENCIL_INDEX8
+        case 'rgba4': return gl.RGBA4
+        case 'depth-stencil': return gl.DEPTH_STENCIL
+    }
+}
+
+export function getAttachment(ctx: Context, attachment: RenderbufferAttachment) {
+    const { gl } = ctx
+    switch (attachment) {
+        case 'depth': return gl.DEPTH_ATTACHMENT
+        case 'stencil': return gl.STENCIL_ATTACHMENT
+        case 'depth-stencil': return gl.DEPTH_STENCIL_ATTACHMENT
+        case 'color0': return gl.COLOR_ATTACHMENT0
+    }
+}
+
 export interface Renderbuffer {
     readonly id: number
 
     bind: () => void
+    setSize: (width: number, height: number) => void
+
     destroy: () => void
 }
 
-export function createRenderbuffer (ctx: Context): Renderbuffer {
+export function createRenderbuffer (ctx: Context, format: RenderbufferFormat, attachment: RenderbufferAttachment, _width: number, _height: number): Renderbuffer {
     const { gl } = ctx
     const _renderbuffer = gl.createRenderbuffer()
     if (_renderbuffer === null) {
         throw new Error('Could not create WebGL renderbuffer')
     }
 
+    const bind = () => gl.bindRenderbuffer(gl.RENDERBUFFER, _renderbuffer)
+    const _format = getFormat(ctx, format)
+    const _attachment = getAttachment(ctx, attachment)
+
+    bind()
+    gl.renderbufferStorage(gl.RENDERBUFFER, _format, _width, _height)
+    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, _attachment, gl.RENDERBUFFER, _renderbuffer)
+
     let destroyed = false
     ctx.renderbufferCount += 1
 
     return {
         id: getNextRenderbufferId(),
 
-        bind: () => {
-            gl.bindRenderbuffer(gl.RENDERBUFFER, _renderbuffer)
+        bind,
+        setSize: (_width: number, _height: number) => {
+            bind()
+            gl.renderbufferStorage(gl.RENDERBUFFER, _format, _width, _height)
         },
 
         destroy: () => {

+ 17 - 8
src/mol-gl/webgl/texture.ts

@@ -9,15 +9,18 @@ import { TextureImage } from '../renderable/util';
 import { ValueCell } from 'mol-util';
 import { RenderableSchema } from '../renderable/schema';
 import { idFactory } from 'mol-util/id-factory';
+import { Framebuffer } from './framebuffer';
 
 const getNextTextureId = idFactory()
 
-export type TextureFormat = 'rgb' | 'rgba'
+export type TextureFormat = 'alpha' | 'rgb' | 'rgba'
 export type TextureType = 'ubyte' | 'uint'
+export type TextureAttachment = 'depth' | 'stencil' | 'color0'
 
 export function getFormat(ctx: Context, format: TextureFormat) {
     const { gl } = ctx
     switch (format) {
+        case 'alpha': return gl.ALPHA
         case 'rgb': return gl.RGB
         case 'rgba': return gl.RGBA
     }
@@ -31,9 +34,17 @@ export function getType(ctx: Context, type: TextureType) {
     }
 }
 
+export function getAttachment(ctx: Context, attachment: TextureAttachment) {
+    const { gl } = ctx
+    switch (attachment) {
+        case 'depth': return gl.DEPTH_ATTACHMENT
+        case 'stencil': return gl.STENCIL_ATTACHMENT
+        case 'color0': return gl.COLOR_ATTACHMENT0
+    }
+}
+
 export interface Texture {
     readonly id: number
-    readonly texture: WebGLTexture
     readonly format: number
     readonly type: number
 
@@ -43,7 +54,7 @@ export interface Texture {
     load: (image: TextureImage) => void
     bind: (id: TextureId) => void
     unbind: (id: TextureId) => void
-    setSize: (width: number, height: number) => void
+    attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void
     destroy: () => void
 }
 
@@ -73,7 +84,6 @@ export function createTexture(ctx: Context, _format: TextureFormat, _type: Textu
 
     return {
         id,
-        texture,
         format,
         type,
 
@@ -103,10 +113,9 @@ export function createTexture(ctx: Context, _format: TextureFormat, _type: Textu
             gl.activeTexture(gl.TEXTURE0 + id)
             gl.bindTexture(gl.TEXTURE_2D, null)
         },
-        setSize: (width: number, height: number) => {
-            gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null)
-            _width = width
-            _height = height
+        attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => {
+            framebuffer.bind()
+            gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, texture, 0)
         },
         destroy: () => {
             if (destroyed) return

+ 1 - 1
src/mol-gl/webgl/uniform.ts

@@ -34,7 +34,7 @@ function createUniformSetter(ctx: Context, program: WebGLProgram, name: string,
     const { gl } = ctx
     const location = gl.getUniformLocation(program, name)
     if (location === null) {
-        console.info(`Could not get WebGL uniform location for '${name}'`)
+        // console.info(`Could not get WebGL uniform location for '${name}'`)
     }
     switch (kind) {
         case 'f': return (value: number) => gl.uniform1f(location, value)

+ 2 - 2
src/mol-math/geometry/symmetry-operator.ts

@@ -18,13 +18,13 @@ interface SymmetryOperator {
 }
 
 namespace SymmetryOperator {
-    export const DefaultName = '1_555'
+    export const DefaultName = 'identity'
     export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
 
     const RotationEpsilon = 0.0001;
 
     export function create(name: string, matrix: Mat4, hkl?: Vec3): SymmetryOperator {
-        const _hkl = hkl ? Vec3.copy(Vec3.zero(), hkl) : Vec3.zero();
+        const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
         if (Mat4.isIdentity(matrix)) return { name, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl };
         if (!Mat4.isRotationAndTranslation(matrix, RotationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
         return { name, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl };

+ 36 - 0
src/mol-math/linear-algebra/3d/vec2.ts

@@ -93,6 +93,33 @@ namespace Vec2 {
         return out;
     }
 
+    /**
+     * Math.round the components of a Vec2
+     */
+    export function round(out: Vec2, a: Vec2) {
+        out[0] = Math.round(a[0]);
+        out[1] = Math.round(a[1]);
+        return out;
+    }
+
+    /**
+     * Math.ceil the components of a Vec2
+     */
+    export function ceil(out: Vec2, a: Vec2) {
+        out[0] = Math.ceil(a[0]);
+        out[1] = Math.ceil(a[1]);
+        return out;
+    }
+
+    /**
+     * Math.floor the components of a Vec2
+     */
+    export function floor(out: Vec2, a: Vec2) {
+        out[0] = Math.floor(a[0]);
+        out[1] = Math.floor(a[1]);
+        return out;
+    }
+
     export function distance(a: Vec2, b: Vec2) {
         const x = b[0] - a[0],
             y = b[1] - a[1];
@@ -116,6 +143,15 @@ namespace Vec2 {
             y = a[1];
         return x * x + y * y;
     }
+
+    /**
+     * Returns the inverse of the components of a Vec2
+     */
+    export function inverse(out: Vec2, a: Vec2) {
+        out[0] = 1.0 / a[0];
+        out[1] = 1.0 / a[1];
+        return out;
+    }
 }
 
 export default Vec2

+ 40 - 0
src/mol-math/linear-algebra/3d/vec3.ts

@@ -130,6 +130,36 @@ namespace Vec3 {
         return out;
     }
 
+    /**
+     * Math.round the components of a Vec3
+     */
+    export function round(out: Vec3, a: Vec3) {
+        out[0] = Math.round(a[0]);
+        out[1] = Math.round(a[1]);
+        out[2] = Math.round(a[2]);
+        return out;
+    }
+
+    /**
+     * Math.ceil the components of a Vec3
+     */
+    export function ceil(out: Vec3, a: Vec3) {
+        out[0] = Math.ceil(a[0]);
+        out[1] = Math.ceil(a[1]);
+        out[2] = Math.ceil(a[2]);
+        return out;
+    }
+
+    /**
+     * Math.floor the components of a Vec3
+     */
+    export function floor(out: Vec3, a: Vec3) {
+        out[0] = Math.floor(a[0]);
+        out[1] = Math.floor(a[1]);
+        out[2] = Math.floor(a[2]);
+        return out;
+    }
+
     export function distance(a: Vec3, b: Vec3) {
         const x = b[0] - a[0],
             y = b[1] - a[1],
@@ -162,6 +192,16 @@ namespace Vec3 {
         return Vec3.scale(out, Vec3.normalize(out, a), l)
     }
 
+    /**
+     * Returns the inverse of the components of a Vec3
+     */
+    export function inverse(out: Vec3, a: Vec3) {
+        out[0] = 1.0 / a[0];
+        out[1] = 1.0 / a[1];
+        out[2] = 1.0 / a[2];
+        return out;
+    }
+
     export function normalize(out: Vec3, a: Vec3) {
         const x = a[0],
             y = a[1],

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

@@ -108,6 +108,47 @@ namespace Vec4 {
         return Math.sqrt(x * x + y * y + z * z + w * w);
     }
 
+    export function scale(out: Vec4, a: Vec4, b: number) {
+        out[0] = a[0] * b;
+        out[1] = a[1] * b;
+        out[2] = a[2] * b;
+        out[4] = a[4] * b;
+        return out;
+    }
+
+    /**
+     * Math.round the components of a Vec4
+     */
+    export function round(out: Vec4, a: Vec4) {
+        out[0] = Math.round(a[0]);
+        out[1] = Math.round(a[1]);
+        out[2] = Math.round(a[2]);
+        out[3] = Math.round(a[3]);
+        return out;
+    }
+
+    /**
+     * Math.ceil the components of a Vec4
+     */
+    export function ceil(out: Vec4, a: Vec4) {
+        out[0] = Math.ceil(a[0]);
+        out[1] = Math.ceil(a[1]);
+        out[2] = Math.ceil(a[2]);
+        out[3] = Math.ceil(a[3]);
+        return out;
+    }
+
+    /**
+     * Math.floor the components of a Vec3
+     */
+    export function floor(out: Vec4, a: Vec4) {
+        out[0] = Math.floor(a[0]);
+        out[1] = Math.floor(a[1]);
+        out[2] = Math.floor(a[2]);
+        out[3] = Math.floor(a[3]);
+        return out;
+    }
+
     export function squaredDistance(a: Vec4, b: Vec4) {
         const x = b[0] - a[0],
             y = b[1] - a[1],
@@ -140,6 +181,21 @@ namespace Vec4 {
         out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
         return out;
     }
+
+    export function dot(a: Vec4, b: Vec4) {
+        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+    }
+
+    /**
+     * Returns the inverse of the components of a Vec4
+     */
+    export function inverse(out: Vec4, a: Vec4) {
+        out[0] = 1.0 / a[0];
+        out[1] = 1.0 / a[1];
+        out[2] = 1.0 / a[2];
+        out[3] = 1.0 / a[3];
+        return out;
+    }
 }
 
 export default Vec4

+ 1 - 0
src/mol-model/structure/model/formats/mmcif.ts

@@ -144,6 +144,7 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
 
     return {
         id: UUID.create(),
+        label: format.data.entry.id.value(0),
         sourceData: format,
         modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)),
         entities,

+ 11 - 1
src/mol-model/structure/model/formats/mmcif/sequence.ts

@@ -10,8 +10,18 @@ import { Column } from 'mol-data/db';
 import { AtomicHierarchy } from '../../properties/atomic';
 import { Entities } from '../../properties/common';
 
+// TODO how to handle microheterogeneity
+//    see http://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v50.dic/Categories/entity_poly_seq.html
+//
+// Data items in the ENTITY_POLY_SEQ category specify the sequence
+// of monomers in a polymer. Allowance is made for the possibility
+// of microheterogeneity in a sample by allowing a given sequence
+// number to be correlated with more than one monomer ID. The
+// corresponding ATOM_SITE entries should reflect this
+// heterogeneity.
+
 export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy): Sequence {
-    if (!cif.entity_poly_seq._rowCount) return Sequence.fromAtomicHierarchy(hierarchy);
+    if (!cif.entity_poly_seq._rowCount) return Sequence.fromAtomicHierarchy(entities, hierarchy);
 
     const { entity_id, num, mon_id } = cif.entity_poly_seq;
 

+ 1 - 0
src/mol-model/structure/model/model.ts

@@ -23,6 +23,7 @@ import from_mmCIF from './formats/mmcif'
  */
 interface Model extends Readonly<{
     id: UUID,
+    label: string,
 
     modelNum: number,
 

+ 30 - 3
src/mol-model/structure/model/properties/sequence.ts

@@ -6,6 +6,7 @@
 
 import { Column } from 'mol-data/db'
 import { AtomicHierarchy } from './atomic/hierarchy';
+import { Entities } from './common';
 
 interface Sequence {
     readonly byEntityKey: { [key: number]: Sequence.Entity }
@@ -19,10 +20,36 @@ namespace Sequence {
         readonly compId: Column<string>
     }
 
-    export function fromAtomicHierarchy(hierarchy: AtomicHierarchy): Sequence {
-        // const { label_comp_id } = hierarchy.residues;
+    export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy): Sequence {
+        const { label_entity_id } = hierarchy.chains
+        const { label_comp_id, label_seq_id } = hierarchy.residues
+        const { chainSegments, residueSegments } = hierarchy
 
-        throw 'not implemented';
+        const byEntityKey: Sequence['byEntityKey'] = {};
+
+        const chainCount = hierarchy.chains._rowCount
+        for (let i = 0; i < chainCount; ++i) {
+            const entityId = label_entity_id.value(i)
+            const entityIndex = entities.getEntityIndex(entityId)
+            // TODO only for polymers, mirroring _entity_poly_seq, ok???
+            if (entities.data.type.value(i) !== 'polymer') continue
+
+            const entityKey = hierarchy.entityKey[entityIndex]
+            if (byEntityKey[entityKey] !== undefined) continue
+
+            const start = residueSegments.segmentMap[chainSegments.segments[i]]
+            let end = residueSegments.segmentMap[chainSegments.segments[i + 1]]
+            // TODO better way to handle end???
+            if (end === undefined) end = hierarchy.residues._rowCount
+
+            byEntityKey[entityKey] = {
+                entityId,
+                compId: Column.window(label_comp_id, start, end),
+                num: Column.window(label_seq_id, start, end)
+            }
+        }
+
+        return { byEntityKey }
     }
 }
 

+ 6 - 4
src/mol-model/structure/structure/structure.ts

@@ -92,7 +92,8 @@ namespace Structure {
 
         for (let c = 0; c < chains.count; c++) {
             const elements = SortedArray.ofBounds(chains.segments[c], chains.segments[c + 1]);
-            builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
+            const label = SymmetryOperator.Default.name
+            builder.addUnit(label, Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
         }
 
         const cs = model.coarseHierarchy;
@@ -112,15 +113,16 @@ namespace Structure {
         const { chainSegments } = elements;
         for (let cI = 0; cI < chainSegments.count; cI++) {
             const elements = SortedArray.ofBounds(chainSegments.segments[cI], chainSegments.segments[cI + 1]);
-            builder.addUnit(kind, model, SymmetryOperator.Default, elements);
+            const label = SymmetryOperator.Default.name
+            builder.addUnit(label, kind, model, SymmetryOperator.Default, elements);
         }
     }
 
     export class StructureBuilder {
         private units: Unit[] = [];
 
-        addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
-            const unit = Unit.create(this.units.length, kind, model, operator, elements);
+        addUnit(label: string, kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
+            const unit = Unit.create(this.units.length, label, kind, model, operator, elements);
             this.units.push(unit);
             return unit;
         }

+ 19 - 12
src/mol-model/structure/structure/unit.ts

@@ -25,11 +25,11 @@ namespace Unit {
     export function isSpheres(u: Unit): u is Spheres { return u.kind === Kind.Spheres; }
     export function isGaussians(u: Unit): u is Gaussians { return u.kind === Kind.Gaussians; }
 
-    export function create(id: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
+    export function create(id: number, label: string, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
         switch (kind) {
-            case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), AtomicProperties());
-            case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres));
-            case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians));
+            case Kind.Atomic: return new Atomic(id, unitIdFactory(), label, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), AtomicProperties());
+            case Kind.Spheres: return createCoarse(id, unitIdFactory(), label, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres));
+            case Kind.Gaussians: return createCoarse(id, unitIdFactory(), label, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians));
         }
     }
 
@@ -40,6 +40,7 @@ namespace Unit {
         readonly id: number,
         // invariant ID stays the same even if the Operator/conformation changes.
         readonly invariantId: number,
+        readonly label: string,
         readonly elements: SortedArray,
         readonly model: Model,
         readonly conformation: SymmetryOperator.ArrayMapping,
@@ -64,6 +65,7 @@ namespace Unit {
 
         readonly id: number;
         readonly invariantId: number;
+        readonly label: string;
         readonly elements: SortedArray;
         readonly model: Model;
         readonly conformation: SymmetryOperator.ArrayMapping;
@@ -76,12 +78,13 @@ namespace Unit {
 
         getChild(elements: SortedArray): Unit {
             if (elements.length === this.elements.length) return this;
-            return new Atomic(this.id, this.invariantId, this.model, elements, this.conformation, AtomicProperties());
+            return new Atomic(this.id, this.invariantId, this.label, this.model, elements, this.conformation, AtomicProperties());
         }
 
         applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
             const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
-            return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation), this.props);
+            const label = operator.name
+            return new Atomic(id, this.invariantId, label, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation), this.props);
         }
 
         get lookup3d() {
@@ -97,9 +100,10 @@ namespace Unit {
             return this.props.bonds.ref;
         }
 
-        constructor(id: number, invariantId: number, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
+        constructor(id: number, invariantId: number, label: string, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
             this.id = id;
             this.invariantId = invariantId;
+            this.label = label;
             this.model = model;
             this.elements = elements;
             this.conformation = conformation;
@@ -124,6 +128,7 @@ namespace Unit {
 
         readonly id: number;
         readonly invariantId: number;
+        readonly label: string;
         readonly elements: SortedArray;
         readonly model: Model;
         readonly conformation: SymmetryOperator.ArrayMapping;
@@ -133,12 +138,13 @@ namespace Unit {
 
         getChild(elements: SortedArray): Unit {
             if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */;
-            return createCoarse(this.id, this.invariantId, this.model, this.kind, elements, this.conformation);
+            return createCoarse(this.id, this.invariantId, this.label, this.model, this.kind, elements, this.conformation);
         }
 
         applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
             const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
-            const ret = createCoarse(id, this.invariantId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements()));
+            const label = operator.name
+            const ret = createCoarse(id, this.invariantId, label, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements()));
             (ret as Coarse<K, C>)._lookup3d = this._lookup3d;
             return ret;
         }
@@ -156,10 +162,11 @@ namespace Unit {
             return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians;
         }
 
-        constructor(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
+        constructor(id: number, invariantId: number, label: string, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
             this.kind = kind;
             this.id = id;
             this.invariantId = invariantId;
+            this.label = label;
             this.model = model;
             this.elements = elements;
             this.conformation = conformation;
@@ -168,8 +175,8 @@ namespace Unit {
         }
     }
 
-    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit {
-        return new Coarse(id, invariantId, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */;
+    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, label: string, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit {
+        return new Coarse(id, invariantId, label, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */;
     }
 
     export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { }

+ 14 - 2
src/mol-util/input/input-observer.ts

@@ -93,6 +93,13 @@ export type ClickInput = {
     pageY: number,
 } & BaseInput
 
+export type MoveInput = {
+    x: number,
+    y: number,
+    pageX: number,
+    pageY: number,
+} & BaseInput
+
 export type PinchInput = {
     delta: number,
     distance: number,
@@ -124,6 +131,7 @@ interface InputObserver {
     wheel: Subject<WheelInput>,
     pinch: Subject<PinchInput>,
     click: Subject<ClickInput>,
+    move: Subject<MoveInput>,
     resize: Subject<ResizeInput>,
 
     dispose: () => void
@@ -153,6 +161,7 @@ namespace InputObserver {
 
         const drag = new Subject<DragInput>()
         const click = new Subject<ClickInput>()
+        const move = new Subject<MoveInput>()
         const wheel = new Subject<WheelInput>()
         const pinch = new Subject<PinchInput>()
         const resize = new Subject<ResizeInput>()
@@ -169,6 +178,7 @@ namespace InputObserver {
             wheel,
             pinch,
             click,
+            move,
             resize,
 
             dispose
@@ -343,13 +353,15 @@ namespace InputObserver {
 
         function onPointerMove (ev: PointerEvent) {
             eventOffset(pointerEnd, ev)
+            const { pageX, pageY } = ev
+            const [ x, y ] = pointerEnd
+            move.next({ x, y, pageX, pageY, buttons, modifiers })
+
             if (dragging === DraggingState.Stopped) return
 
             Vec2.div(pointerDelta, Vec2.sub(pointerDelta, pointerEnd, pointerStart), getClientSize(rectSize))
 
             const isStart = dragging === DraggingState.Started
-            const { pageX, pageY } = ev
-            const [ x, y ] = pointerEnd
             const [ dx, dy ] = pointerDelta
             drag.next({ x, y, dx, dy, pageX, pageY, buttons, modifiers, isStart })
 

+ 9 - 4
src/mol-view/stage.ts

@@ -34,18 +34,23 @@ export class Stage {
 
     }
 
-    async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) {
+    initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) {
         this.viewer = Viewer.create(canvas, container)
         this.viewer.animate()
         this.ctx.viewer = this.viewer
-        this.loadPdbid('1crn')
+        // this.loadPdbid('1crn')
+        this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
     }
 
-    async loadPdbid (pdbid: string) {
-        const urlEntity = UrlEntity.ofUrl(this.ctx, `https://files.rcsb.org/download/${pdbid}.cif`)
+    loadMmcifUrl (url: string) {
+        const urlEntity = UrlEntity.ofUrl(this.ctx, url)
         MmcifUrlToSpacefill.apply(this.ctx, urlEntity, spacefillProps)
     }
 
+    loadPdbid (pdbid: string) {
+        return this.loadMmcifUrl(`https://files.rcsb.org/download/${pdbid}.cif`)
+    }
+
     dispose () {
         // TODO
     }

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

@@ -18,6 +18,10 @@ import { PerspectiveCamera } from './camera/perspective'
 import { resizeCanvas } from './util';
 import { createContext } from 'mol-gl/webgl/context';
 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/util/picking';
 
 interface Viewer {
     center: (p: Vec3) => void
@@ -33,13 +37,17 @@ interface Viewer {
     draw: (force?: boolean) => void
     requestDraw: () => void
     animate: () => void
+    pick: () => void
+    identify: (x: number, y: number) => void
+
     reprCount: BehaviorSubject<number>
+    identified: BehaviorSubject<string>
     didDraw: BehaviorSubject<number>
 
     handleResize: () => void
     resetCamera: () => void
     downloadScreenshot: () => void
-    getImageData: () => ImageData
+    getImageData: (variant: RenderVariant) => ImageData
 
     input: InputObserver
     stats: RendererStats
@@ -61,9 +69,26 @@ namespace Viewer {
     export function create(canvas: HTMLCanvasElement, container: Element): Viewer {
         const reprMap = new Map<Representation<any>, Set<RenderObject>>()
         const reprCount = new BehaviorSubject(0)
+        const identified = new BehaviorSubject('')
+
+        const startTime = performance.now()
+        const didDraw = new BehaviorSubject(0)
 
         const input = InputObserver.create(canvas)
         input.resize.subscribe(handleResize)
+        input.move.subscribe(({x, y}) => {
+            const p = identify(x, y)
+            let label = ''
+            reprMap.forEach((roSet, repr) => {
+                const info = repr.getLabel(p)
+                if (info) label = info.label
+                repr.update({ hoverSelection: p }).run().then(() => {
+                    scene.update()
+                    requestDraw()
+                })
+            })
+            identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`)
+        })
 
         const camera = PerspectiveCamera.create({
             near: 0.1,
@@ -86,17 +111,42 @@ namespace Viewer {
         }
         const ctx = createContext(gl)
 
+        const scene = Scene.create(ctx)
         const renderer = Renderer.create(ctx, camera)
 
+        const pickScale = 1 / 4
+        const pickWidth = Math.round(canvas.width * pickScale)
+        const pickHeight = Math.round(canvas.height * pickScale)
+        const objectPickTarget = createRenderTarget(ctx, pickWidth, pickHeight)
+        const instancePickTarget = createRenderTarget(ctx, pickWidth, pickHeight)
+        const elementPickTarget = createRenderTarget(ctx, pickWidth, pickHeight)
+
+        let pickDirty = true
         let drawPending = false
         const prevProjectionView = Mat4.zero()
 
-        function draw (force?: boolean) {
+        function render(variant: RenderVariant, force?: boolean) {
+            let didRender = false
             controls.update()
             camera.update()
             if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value)) {
                 Mat4.copy(prevProjectionView, camera.projectionView)
-                renderer.draw()
+                renderer.render(scene, variant)
+                if (variant === 'draw') {
+                    pickDirty = true
+                    pick()
+                }
+                didRender = true
+            }
+            return didRender
+        }
+
+        function draw(force?: boolean) {
+            ctx.unbindFramebuffer()
+            const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height }
+            renderer.setViewport(viewport)
+            if (render('draw', force)) {
+                didDraw.next(performance.now() - startTime)
             }
             drawPending = false
         }
@@ -108,10 +158,47 @@ namespace Viewer {
         }
 
         function animate () {
-            draw()
+            draw(false)
             window.requestAnimationFrame(() => animate())
         }
 
+        function pick() {
+            objectPickTarget.bind()
+            render('pickObject', pickDirty)
+
+            instancePickTarget.bind()
+            render('pickInstance', pickDirty)
+
+            elementPickTarget.bind()
+            render('pickElement', pickDirty)
+
+            pickDirty = false
+        }
+
+        function identify (x: number, y: number): PickingId {
+            x *= ctx.pixelRatio
+            y *= ctx.pixelRatio
+            y = canvas.height - y // flip y
+
+            const buffer = new Uint8Array(4)
+            const xp = Math.round(x * pickScale)
+            const yp = Math.round(y * pickScale)
+
+            objectPickTarget.bind()
+            ctx.readPixels(xp, yp, 1, 1, buffer)
+            const objectId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+
+            instancePickTarget.bind()
+            ctx.readPixels(xp, yp, 1, 1, buffer)
+            const instanceId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+
+            elementPickTarget.bind()
+            ctx.readPixels(xp, yp, 1, 1, buffer)
+            const elementId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+
+            return { objectId, instanceId, elementId }
+        }
+
         handleResize()
 
         return {
@@ -133,11 +220,11 @@ namespace Viewer {
                 const newRO = new Set<RenderObject>()
                 repr.renderObjects.forEach(o => newRO.add(o))
                 if (oldRO) {
-                    SetUtils.difference(newRO, oldRO).forEach(o => renderer.add(o))
-                    SetUtils.difference(oldRO, newRO).forEach(o => renderer.remove(o))
-                    renderer.update()
+                    SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o))
+                    SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o))
+                    scene.update()
                 } else {
-                    repr.renderObjects.forEach(o => renderer.add(o))
+                    repr.renderObjects.forEach(o => scene.add(o))
                 }
                 reprMap.set(repr, newRO)
                 reprCount.next(reprMap.size)
@@ -145,20 +232,22 @@ namespace Viewer {
             remove: (repr: Representation<any>) => {
                 const renderObjectSet = reprMap.get(repr)
                 if (renderObjectSet) {
-                    renderObjectSet.forEach(o => renderer.remove(o))
+                    renderObjectSet.forEach(o => scene.remove(o))
                     reprMap.delete(repr)
                     reprCount.next(reprMap.size)
                 }
             },
-            update: () => renderer.update(),
+            update: () => scene.update(),
             clear: () => {
                 reprMap.clear()
-                renderer.clear()
+                scene.clear()
             },
 
             draw,
             requestDraw,
             animate,
+            pick,
+            identify,
 
             handleResize,
             resetCamera: () => {
@@ -167,11 +256,17 @@ namespace Viewer {
             downloadScreenshot: () => {
                 // TODO
             },
-            getImageData: () => {
-                return renderer.getImageData()
+            getImageData: (variant: RenderVariant) => {
+                switch (variant) {
+                    case 'draw': return renderer.getImageData()
+                    case 'pickObject': return objectPickTarget.getImageData()
+                    case 'pickInstance': return instancePickTarget.getImageData()
+                    case 'pickElement': return elementPickTarget.getImageData()
+                }
             },
             reprCount,
-            didDraw: renderer.didDraw,
+            identified,
+            didDraw,
 
             get input() {
                 return input
@@ -180,6 +275,7 @@ namespace Viewer {
                 return renderer.stats
             },
             dispose: () => {
+                scene.clear()
                 input.dispose()
                 controls.dispose()
                 renderer.dispose()
@@ -192,6 +288,12 @@ namespace Viewer {
             renderer.setViewport(viewport)
             Viewport.copy(camera.viewport, viewport)
             Viewport.copy(controls.viewport, viewport)
+
+            const pickWidth = Math.round(canvas.width * pickScale)
+            const pickHeight = Math.round(canvas.height * pickScale)
+            objectPickTarget.setSize(pickWidth, pickHeight)
+            instancePickTarget.setSize(pickWidth, pickHeight)
+            elementPickTarget.setSize(pickWidth, pickHeight)
         }
     }
 }

+ 1 - 0
tsconfig.json

@@ -8,6 +8,7 @@
         "noUnusedLocals": true,
         "strictNullChecks": true,
         "strictFunctionTypes": true,
+        "keyofStringsOnly": true,
         //"downlevelIteration": true,
         "jsx": "react",
         "lib": [ "es6", "dom", "esnext.asynciterable", "es2016" ],

Some files were not shown because too many files changed in this diff