Browse Source

Merge branch 'master' into mol-model

David Sehnal 6 years ago
parent
commit
81e4cb3865
42 changed files with 597 additions and 265 deletions
  1. 16 0
      src/apps/viewer/index.tsx
  2. 4 2
      src/mol-app/event/basic.ts
  3. 4 3
      src/mol-app/ui/visualization/sequence-view.tsx
  4. 20 5
      src/mol-app/ui/visualization/viewport.tsx
  5. 3 3
      src/mol-geo/representation/index.ts
  6. 15 12
      src/mol-geo/representation/structure/bond.ts
  7. 9 8
      src/mol-geo/representation/structure/index.ts
  8. 11 10
      src/mol-geo/representation/structure/point.ts
  9. 10 9
      src/mol-geo/representation/structure/spacefill.ts
  10. 6 6
      src/mol-geo/representation/structure/utils.ts
  11. 6 6
      src/mol-geo/representation/volume/index.ts
  12. 9 7
      src/mol-geo/representation/volume/surface.ts
  13. 28 28
      src/mol-geo/util/marker-data.ts
  14. 40 0
      src/mol-gl/_spec/renderable.spec.ts
  15. 6 5
      src/mol-gl/_spec/renderer.spec.ts
  16. 42 0
      src/mol-gl/object3d.ts
  17. 17 4
      src/mol-gl/renderable.ts
  18. 10 2
      src/mol-gl/renderable/schema.ts
  19. 78 0
      src/mol-gl/renderable/util.ts
  20. 25 13
      src/mol-gl/renderer.ts
  21. 52 8
      src/mol-gl/scene.ts
  22. 6 0
      src/mol-gl/shader/chunks/apply-fog.glsl
  23. 8 0
      src/mol-gl/shader/chunks/apply-marker-color.glsl
  24. 0 0
      src/mol-gl/shader/chunks/assign-color-varying.glsl
  25. 1 0
      src/mol-gl/shader/chunks/assign-marker-varying.glsl
  26. 0 0
      src/mol-gl/shader/chunks/assign-material-color.glsl
  27. 4 0
      src/mol-gl/shader/chunks/assign-position.glsl
  28. 7 1
      src/mol-gl/shader/chunks/common-frag-params.glsl
  29. 3 3
      src/mol-gl/shader/chunks/common-vert-params.glsl
  30. 3 16
      src/mol-gl/shader/mesh.frag
  31. 3 7
      src/mol-gl/shader/mesh.vert
  32. 5 1
      src/mol-gl/shader/point.frag
  33. 2 4
      src/mol-gl/shader/point.vert
  34. 8 1
      src/mol-model/loci.ts
  35. 3 1
      src/mol-util/input/input-observer.ts
  36. 29 15
      src/mol-view/camera/base.ts
  37. 1 5
      src/mol-view/camera/orthographic.ts
  38. 0 8
      src/mol-view/camera/perspective.ts
  39. 7 1
      src/mol-view/camera/util.ts
  40. 2 7
      src/mol-view/controls/trackball.ts
  41. 22 15
      src/mol-view/label.ts
  42. 72 49
      src/mol-view/viewer.ts

+ 16 - 0
src/apps/viewer/index.tsx

@@ -23,6 +23,9 @@ import { EntityTreeController } from 'mol-app/controller/entity/tree';
 import { TransformListController } from 'mol-app/controller/transform/list';
 import { TransformList } from 'mol-app/ui/transform/list';
 import { SequenceView } from 'mol-app/ui/visualization/sequence-view';
+import { InteractivityEvents } from 'mol-app/event/basic';
+import { MarkerAction } from 'mol-geo/util/marker-data';
+import { EveryLoci } from 'mol-model/loci';
 
 const elm = document.getElementById('app')
 if (!elm) throw new Error('Can not find element with id "app".')
@@ -94,4 +97,17 @@ ctx.layout.setState({
 })
 // ctx.viewport.setState()
 
+ctx.dispatcher.getStream(InteractivityEvents.HighlightLoci).subscribe(event => {
+    ctx.stage.viewer.mark(EveryLoci, MarkerAction.RemoveHighlight)
+    if (event && event.data) {
+        ctx.stage.viewer.mark(event.data, MarkerAction.Highlight)
+    }
+})
+
+ctx.dispatcher.getStream(InteractivityEvents.SelectLoci).subscribe(event => {
+    if (event && event.data) {
+        ctx.stage.viewer.mark(event.data, MarkerAction.ToggleSelect)
+    }
+})
+
 ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm);

+ 4 - 2
src/mol-app/event/basic.ts

@@ -11,7 +11,7 @@ import { Dispatcher } from '../service/dispatcher'
 import { LayoutState } from '../controller/layout';
 import { ViewportOptions } from '../controller/visualization/viewport';
 import { Job } from '../service/job';
-import { Element } from 'mol-model/structure'
+import { Loci } from 'mol-model/loci';
 
 const Lane = Dispatcher.Lane;
 
@@ -34,5 +34,7 @@ export namespace LayoutEvents {
 }
 
 export namespace InteractivityEvents {
-    export const HighlightElementLoci = Event.create<Element.Loci | undefined>('bs.Interactivity.HighlightElementLoci', Lane.Slow);
+    export const HighlightLoci = Event.create<Loci>('bs.Interactivity.HighlightLoci', Lane.Slow);
+    export const SelectLoci = Event.create<Loci>('bs.Interactivity.SelectLoci', Lane.Slow);
+    export const LabelLoci = Event.create<Loci>('bs.Interactivity.LabelLoci', Lane.Slow);
 }

+ 4 - 3
src/mol-app/ui/visualization/sequence-view.tsx

@@ -11,6 +11,7 @@ import { Structure, StructureSequence, Queries, Selection } from 'mol-model/stru
 import { Context } from '../../context/context';
 import { InteractivityEvents } from '../../event/basic';
 import { SyncRuntimeContext } from 'mol-task/execution/synchronous';
+import { EmptyLoci } from 'mol-model/loci';
 
 export class SequenceView extends View<SequenceViewController, {}, {}> {
     render() {
@@ -36,14 +37,14 @@ class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSeque
 
     async raiseInteractityEvent(seqId?: number) {
         if (typeof seqId === 'undefined') {
-            InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0);
+            InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
             return;
         }
 
         const query = createQuery(this.props.seq.entityId, seqId);
         const loci = Selection.toLoci(await query(this.props.structure, SyncRuntimeContext));
-        if (loci.elements.length === 0) InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0);
-        else InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, loci);
+        if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
+        else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci);
     }
 
 

+ 20 - 5
src/mol-app/ui/visualization/viewport.tsx

@@ -14,6 +14,8 @@ import { View } from '../view';
 import { HelpBox, Toggle, Button } from '../controls/common'
 import { Slider } from '../controls/slider'
 import { ImageCanvas } from './image-canvas';
+import { InteractivityEvents } from '../../event/basic';
+import { labelFirst } from 'mol-view/label';
 
 export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> {
     state = { showSceneOptions: false, showHelp: false };
@@ -132,6 +134,7 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
         if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) {
             this.setState({ noWebGl: true });
         }
+        this.handleResize()
 
         const viewer = this.controller.context.stage.viewer
 
@@ -142,10 +145,6 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
             })
         })
 
-        viewer.identified.subscribe(info => {
-            this.setState({ info })
-        })
-
         viewer.didDraw.subscribe(() => {
             // this.setState({ imageData: viewer.getImageData() })
             this.setState({
@@ -158,7 +157,23 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
         })
 
         viewer.input.resize.subscribe(() => this.handleResize())
-        this.handleResize()
+
+        viewer.input.move.subscribe(({x, y, inside}) => {
+            if (!inside) return
+            const p = viewer.identify(x, y)
+            const loci = viewer.getLoci(p)
+            InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci);
+            
+            // TODO use LabelLoci event and make configurable
+            const label = labelFirst(loci)
+            const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`
+            this.setState({ info })
+        })
+
+        viewer.input.click.subscribe(({x, y}) => {
+            const loci = viewer.getLoci(viewer.identify(x, y))
+            InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci);
+        })
     }
 
     componentWillUnmount() {

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

@@ -8,7 +8,7 @@ import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object'
 import { PickingId } from '../util/picking';
 import { Loci } from 'mol-model/loci';
-import { FlagAction } from '../util/flag-data';
+import { MarkerAction } from '../util/marker-data';
 
 export interface RepresentationProps {}
 
@@ -16,6 +16,6 @@ export interface Representation<D, P extends RepresentationProps = {}> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (data: D, props?: P) => Task<void>
     update: (props: P) => Task<void>
-    getLoci: (pickingId: PickingId) => Loci | null
-    applyFlags: (loci: Loci, action: FlagAction) => void
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => void
 }

+ 15 - 12
src/mol-geo/representation/structure/bond.ts

@@ -5,6 +5,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+// TODO multiple cylinders for higher bond orders
+
 import { ValueCell } from 'mol-util/value-cell'
 
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
@@ -21,8 +23,8 @@ import { MeshBuilder } from '../../shape/mesh-builder';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { createUniformColor } from '../../util/color-data';
 import { defaults } from 'mol-util';
-import { Loci, isEveryLoci } from 'mol-model/loci';
-import { FlagAction, applyFlagAction, createFlags } from '../../util/flag-data';
+import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction, applyMarkerAction, createMarkers } from '../../util/marker-data';
 
 function createBondMesh(unit: Unit, mesh?: Mesh) {
     return Task.create('Cylinder mesh', async ctx => {
@@ -110,15 +112,15 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
                 await ctx.update('Computing bond colors');
                 const color = createUniformColor({ value: 0xFF0000 })
 
-                await ctx.update('Computing bond flags');
-                const flag = createFlags(instanceCount * elementCount)
+                await ctx.update('Computing bond marks');
+                const marker = createMarkers(instanceCount * elementCount)
 
                 const values: MeshValues = {
                     ...getMeshData(mesh),
                     aTransform: transforms,
                     aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                     ...color,
-                    ...flag,
+                    ...marker,
 
                     uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
                     uInstanceCount: ValueCell.create(instanceCount),
@@ -132,6 +134,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
                     dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
                     dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)),
                     dFlipSided: ValueCell.create(defaults(props.flipSided, false)),
+                    dUseFog: ValueCell.create(defaults(props.useFog, true)),
                 }
                 const state: RenderableState = {
                     depthMask: defaults(props.depthMask, true),
@@ -171,11 +174,11 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
                     bIndex: unit.links.b[elementId]
                 }])
             }
-            return null
+            return EmptyLoci
         },
-        applyFlags(loci: Loci, action: FlagAction) {
+        mark(loci: Loci, action: MarkerAction) {
             const group = currentGroup
-            const tFlag = cylinders.values.tFlag
+            const tMarker = cylinders.values.tMarker
             const unit = group.units[0]
             if (!Unit.isAtomic(unit)) return
 
@@ -183,9 +186,9 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
             const instanceCount = group.units.length
 
             let changed = false
-            const array = tFlag.ref.value.array
+            const array = tMarker.ref.value.array
             if (isEveryLoci(loci)) {
-                applyFlagAction(array, 0, elementCount * instanceCount, action)
+                applyMarkerAction(array, 0, elementCount * instanceCount, action)
                 changed = true
             } else if (Link.isLoci(loci)) {
                 for (const b of loci.links) {
@@ -194,7 +197,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
                         const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex)
                         if (_idx !== -1) {
                             const idx = _idx
-                            if (applyFlagAction(array, idx, idx + 1, action) && !changed) {
+                            if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
                                 changed = true
                             }
                         }
@@ -204,7 +207,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
                 return
             }
             if (changed) {
-                ValueCell.update(tFlag, tFlag.ref.value)
+                ValueCell.update(tMarker, tMarker.ref.value)
             }
         }
     }

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

@@ -11,15 +11,15 @@ import { RenderObject } from 'mol-gl/render-object';
 import { Representation, RepresentationProps } from '..';
 import { ColorTheme } from '../../theme';
 import { PickingId } from '../../util/picking';
-import { Loci } from 'mol-model/loci';
-import { FlagAction } from '../../util/flag-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
 
 export interface UnitsRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (group: Unit.SymmetryGroup, props: P) => Task<void>
     update: (props: P) => Task<boolean>
-    getLoci: (pickingId: PickingId) => Loci | null
-    applyFlags: (loci: Loci, action: FlagAction) => void
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => void
 }
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
@@ -34,7 +34,8 @@ export const DefaultStructureProps = {
     alpha: 1,
     visible: true,
     doubleSided: false,
-    depthMask: true
+    depthMask: true,
+    useFog: true,
 }
 export type StructureProps = Partial<typeof DefaultStructureProps>
 
@@ -48,7 +49,7 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () =
             const loc = groupReprs[i].repr.getLoci(pickingId)
             if (loc) return loc
         }
-        return null
+        return EmptyLoci
     }
 
     return {
@@ -88,9 +89,9 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () =
             })
         },
         getLoci,
-        applyFlags(loci: Loci, action: FlagAction) {
+        mark(loci: Loci, action: MarkerAction) {
             for (let i = 0, il = groupReprs.length; i < il; ++i) {
-                groupReprs[i].repr.applyFlags(loci, action)
+                groupReprs[i].repr.mark(loci, action)
             }
         }
     }

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

@@ -14,13 +14,13 @@ import { fillSerial } from 'mol-gl/renderable/util';
 import { UnitsRepresentation, DefaultStructureProps } from './index';
 import VertexMap from '../../shape/vertex-map';
 import { SizeTheme } from '../../theme';
-import { createTransforms, createColors, createSizes, applyElementFlags } from './utils';
+import { createTransforms, createColors, createSizes, markElement } 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';
-import { Loci } from 'mol-model/loci';
-import { FlagAction, createFlags } from '../../util/flag-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction, createMarkers } from '../../util/marker-data';
 
 export const DefaultPointProps = {
     ...DefaultStructureProps,
@@ -91,8 +91,8 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro
                 await ctx.update('Computing point sizes');
                 const size = createSizes(group, vertexMap, sizeTheme)
 
-                await ctx.update('Computing spacefill flags');
-                const flag = createFlags(instanceCount * elementCount)
+                await ctx.update('Computing spacefill marks');
+                const marker = createMarkers(instanceCount * elementCount)
 
                 const values: PointValues = {
                     aPosition: ValueCell.create(vertices),
@@ -100,7 +100,7 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro
                     aTransform: transforms,
                     aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                     ...color,
-                    ...flag,
+                    ...marker,
                     ...size,
 
                     uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
@@ -110,7 +110,8 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro
                     drawCount: ValueCell.create(vertices.length / 3),
                     instanceCount: ValueCell.create(instanceCount),
 
-                    dPointSizeAttenuation: ValueCell.create(true)
+                    dPointSizeAttenuation: ValueCell.create(true),
+                    dUseFog: ValueCell.create(defaults(props.useFog, true)),
                 }
                 const state: RenderableState = {
                     depthMask: defaults(props.depthMask, true),
@@ -163,10 +164,10 @@ export default function PointUnitsRepresentation(): UnitsRepresentation<PointPro
                 const indices = SortedArray.ofSingleton(elementId)
                 return Element.Loci([{ unit, indices }])
             }
-            return null
+            return EmptyLoci
         },
-        applyFlags(loci: Loci, action: FlagAction) {
-            applyElementFlags(points.values.tFlag, currentGroup, loci, action)
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(points.values.tMarker, currentGroup, loci, action)
         }
     }
 }

+ 10 - 9
src/mol-geo/representation/structure/spacefill.ts

@@ -11,7 +11,7 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/r
 import { Unit, Element, Queries } from 'mol-model/structure';
 import { UnitsRepresentation, DefaultStructureProps } from './index';
 import { Task } from 'mol-task'
-import { createTransforms, createColors, createSphereMesh, applyElementFlags } from './utils';
+import { createTransforms, createColors, createSphereMesh, markElement } from './utils';
 import VertexMap from '../../shape/vertex-map';
 import { deepEqual, defaults } from 'mol-util';
 import { fillSerial } from 'mol-gl/renderable/util';
@@ -20,8 +20,8 @@ import { getMeshData } from '../../util/mesh-data';
 import { Mesh } from '../../shape/mesh';
 import { PickingId } from '../../util/picking';
 import { SortedArray } from 'mol-data/int';
-import { createFlags, FlagAction } from '../../util/flag-data';
-import { Loci } from 'mol-model/loci';
+import { createMarkers, MarkerAction } from '../../util/marker-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
 
 function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) {
     let radius: Element.Property<number>
@@ -75,15 +75,15 @@ export default function SpacefillUnitsRepresentation(): UnitsRepresentation<Spac
                 await ctx.update('Computing spacefill colors');
                 const color = createColors(group, vertexMap, colorTheme)
 
-                await ctx.update('Computing spacefill flags');
-                const flag = createFlags(instanceCount * elementCount)
+                await ctx.update('Computing spacefill marks');
+                const marker = createMarkers(instanceCount * elementCount)
 
                 const values: MeshValues = {
                     ...getMeshData(mesh),
                     aTransform: transforms,
                     aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                     ...color,
-                    ...flag,
+                    ...marker,
 
                     uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
                     uInstanceCount: ValueCell.create(instanceCount),
@@ -97,6 +97,7 @@ export default function SpacefillUnitsRepresentation(): UnitsRepresentation<Spac
                     dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
                     dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)),
                     dFlipSided: ValueCell.create(defaults(props.flipSided, false)),
+                    dUseFog: ValueCell.create(defaults(props.useFog, true)),
                 }
                 const state: RenderableState = {
                     depthMask: defaults(props.depthMask, true),
@@ -151,10 +152,10 @@ export default function SpacefillUnitsRepresentation(): UnitsRepresentation<Spac
                 const indices = SortedArray.ofSingleton(elementId);
                 return Element.Loci([{ unit, indices }])
             }
-            return null
+            return EmptyLoci
         },
-        applyFlags(loci: Loci, action: FlagAction) {
-            applyElementFlags(spheres.values.tFlag, currentGroup, loci, action)
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(spheres.values.tMarker, currentGroup, loci, action)
         }
     }
 }

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

@@ -20,7 +20,7 @@ import { Task } from 'mol-task';
 import { icosahedronVertexCount } from '../../primitive/icosahedron';
 import { MeshBuilder } from '../../shape/mesh-builder';
 import { TextureImage } from 'mol-gl/renderable/util';
-import { applyFlagAction, FlagAction } from '../../util/flag-data';
+import { applyMarkerAction, MarkerAction } from '../../util/marker-data';
 import { Loci, isEveryLoci } from 'mol-model/loci';
 
 export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) {
@@ -89,13 +89,13 @@ export function createSphereMesh(unit: Unit, radius: Element.Property<number>, d
 }
 
 
-export function applyElementFlags(tFlag: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: FlagAction) {
+export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: MarkerAction) {
     let changed = false
     const elementCount = group.elements.length
     const instanceCount = group.units.length
-    const array = tFlag.ref.value.array
+    const array = tMarker.ref.value.array
     if (isEveryLoci(loci)) {
-        applyFlagAction(array, 0, elementCount * instanceCount, action)
+        applyMarkerAction(array, 0, elementCount * instanceCount, action)
         changed = true
     } else if (Element.isLoci(loci)) {
         for (const e of loci.elements) {
@@ -103,7 +103,7 @@ export function applyElementFlags(tFlag: ValueCell<TextureImage>, group: Unit.Sy
             if (unitIdx !== -1) {
                 for (let i = 0, il = e.indices.length; i < il; ++i) {
                     const idx = unitIdx * elementCount + e.indices[i]
-                    if (applyFlagAction(array, idx, idx + 1, action) && !changed) {
+                    if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
                         changed = true
                     }
                 }
@@ -113,6 +113,6 @@ export function applyElementFlags(tFlag: ValueCell<TextureImage>, group: Unit.Sy
         return
     }
     if (changed) {
-        ValueCell.update(tFlag, tFlag.ref.value)
+        ValueCell.update(tMarker, tMarker.ref.value)
     }
 }

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

@@ -9,15 +9,15 @@ import { RenderObject } from 'mol-gl/render-object';
 import { RepresentationProps, Representation } from '..';
 import { VolumeData } from 'mol-model/volume';
 import { PickingId } from '../../util/picking';
-import { Loci } from 'mol-model/loci';
-import { FlagAction } from '../../util/flag-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
 
 export interface VolumeElementRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
     create: (volumeData: VolumeData, props: P) => Task<void>
     update: (props: P) => Task<boolean>
-    getLoci: (pickingId: PickingId) => Loci | null
-    applyFlags: (loci: Loci, action: FlagAction) => void
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => void
 }
 
 export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { }
@@ -39,9 +39,9 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat
         },
         getLoci(pickingId: PickingId) {
             // TODO
-            return null
+            return EmptyLoci
         },
-        applyFlags(loci: Loci, action: FlagAction) {
+        mark(loci: Loci, action: MarkerAction) {
             // TODO
         }
     }

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

@@ -18,8 +18,8 @@ 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, FlagAction } from '../../util/flag-data';
-import { Loci } from 'mol-model/loci';
+import { createEmptyMarkers, MarkerAction } from '../../util/marker-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
 
 export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) {
     return Task.create<Mesh>('Volume Surface', async ctx => {
@@ -45,7 +45,8 @@ export const DefaultSurfaceProps = {
     flatShaded: true,
     flipSided: true,
     doubleSided: true,
-    depthMask: true
+    depthMask: true,
+    useFog: true
 }
 export type SurfaceProps = Partial<typeof DefaultSurfaceProps>
 
@@ -68,14 +69,14 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
 
                 const instanceCount = 1
                 const color = createUniformColor({ value: 0x7ec0ee })
-                const flag = createEmptyFlags()
+                const marker = createEmptyMarkers()
 
                 const values: MeshValues = {
                     ...getMeshData(mesh),
                     aTransform: ValueCell.create(new Float32Array(Mat4.identity())),
                     aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
                     ...color,
-                    ...flag,
+                    ...marker,
 
                     uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
                     uInstanceCount: ValueCell.create(instanceCount),
@@ -89,6 +90,7 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
                     dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
                     dFlatShaded: ValueCell.create(defaults(props.flatShaded, true)),
                     dFlipSided: ValueCell.create(false),
+                    dUseFog: ValueCell.create(defaults(props.useFog, true)),
                 }
                 const state: RenderableState = {
                     depthMask: defaults(props.depthMask, true),
@@ -107,9 +109,9 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
         },
         getLoci(pickingId: PickingId) {
             // TODO
-            return null
+            return EmptyLoci
         },
-        applyFlags(loci: Loci, action: FlagAction) {
+        mark(loci: Loci, action: MarkerAction) {
             // TODO
         }
     }

+ 28 - 28
src/mol-geo/util/flag-data.ts → src/mol-geo/util/marker-data.ts

@@ -8,12 +8,12 @@ import { ValueCell } from 'mol-util/value-cell'
 import { Vec2 } from 'mol-math/linear-algebra'
 import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
 
-export type FlagData = {
-    tFlag: ValueCell<TextureImage>
-    uFlagTexSize: ValueCell<Vec2>
+export type MarkerData = {
+    tMarker: ValueCell<TextureImage>
+    uMarkerTexSize: ValueCell<Vec2>
 }
 
-export enum FlagAction {
+export enum MarkerAction {
     Highlight,
     RemoveHighlight,
     Select,
@@ -22,34 +22,34 @@ export enum FlagAction {
     Clear
 }
 
-export function applyFlagAction(array: Uint8Array, start: number, end: number, action: FlagAction) {
+export function applyMarkerAction(array: Uint8Array, start: number, end: number, action: MarkerAction) {
     let changed = false
     for (let i = start; i < end; ++i) {
         let v = array[i]
         switch (action) {
-            case FlagAction.Highlight:
+            case MarkerAction.Highlight:
                 if (v % 2 === 0) {
                     v += 1
                     changed = true
                 }
                 break
-            case FlagAction.RemoveHighlight:
+            case MarkerAction.RemoveHighlight:
                 if (v % 2 !== 0) {
                     v -= 1
                     changed = true
                 } 
                 break
-            case FlagAction.Select:
+            case MarkerAction.Select:
                 v += 2
                 changed = true
                 break
-            case FlagAction.Deselect:
+            case MarkerAction.Deselect:
                 if (v >= 2) {
                     v -= 2
                     changed = true
                 }
                 break
-            case FlagAction.ToggleSelect:
+            case MarkerAction.ToggleSelect:
                 if (v === 0) {
                     v = 2
                 } else if (v === 1) {
@@ -61,7 +61,7 @@ export function applyFlagAction(array: Uint8Array, start: number, end: number, a
                 }
                 changed = true
                 break
-            case FlagAction.Clear:
+            case MarkerAction.Clear:
                 v = 0
                 changed = true
                 break
@@ -71,32 +71,32 @@ export function applyFlagAction(array: Uint8Array, start: number, end: number, a
     return changed
 }
 
-export function createFlags(count: number, flagData?: FlagData): FlagData {
-    const flags = flagData && flagData.tFlag.ref.value.array.length >= count
-        ? flagData.tFlag.ref.value
+export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
+    const markers = markerData && markerData.tMarker.ref.value.array.length >= count
+        ? markerData.tMarker.ref.value
         : createTextureImage(count, 1)
-    if (flagData) {
-        ValueCell.update(flagData.tFlag, flags)
-        ValueCell.update(flagData.uFlagTexSize, Vec2.create(flags.width, flags.height))
-        return flagData
+    if (markerData) {
+        ValueCell.update(markerData.tMarker, markers)
+        ValueCell.update(markerData.uMarkerTexSize, Vec2.create(markers.width, markers.height))
+        return markerData
     } else {
         return {
-            tFlag: ValueCell.create(flags),
-            uFlagTexSize: ValueCell.create(Vec2.create(flags.width, flags.height)),
+            tMarker: ValueCell.create(markers),
+            uMarkerTexSize: ValueCell.create(Vec2.create(markers.width, markers.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
+const emptyMarkerTexture = { array: new Uint8Array(1), width: 1, height: 1 }
+export function createEmptyMarkers(markerData?: MarkerData) {
+    if (markerData) {
+        ValueCell.update(markerData.tMarker, emptyMarkerTexture)
+        ValueCell.update(markerData.uMarkerTexSize, Vec2.create(1, 1))
+        return markerData
     } else {
         return {
-            tFlag: ValueCell.create(emptyFlagTexture),
-            uFlagTexSize: ValueCell.create(Vec2.create(1, 1)),
+            tMarker: ValueCell.create(emptyMarkerTexture),
+            uMarkerTexSize: ValueCell.create(Vec2.create(1, 1)),
         }
     }
 }

+ 40 - 0
src/mol-gl/_spec/renderable.spec.ts

@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { calculateBoundingSphere } from '../renderable/util';
+
+describe('renderable', () => {
+    it('calculateBoundingSphere', () => {
+        const position = new Float32Array([
+            0, 0, 0,
+            1, 0, 0
+        ])
+        const transform = new Float32Array([
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            0, 0, 0, 0,
+
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            1, 0, 0, 0,
+
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            2, 0, 0, 0
+        ])
+
+        const bs = calculateBoundingSphere(
+            position, position.length / 3,
+            transform, transform.length / 16
+        )
+
+        expect(bs.radius).toBe(1.5)
+        expect(bs.center).toEqual([1.5, 0.0, 0.0])
+    })
+})

+ 6 - 5
src/mol-gl/_spec/renderer.spec.ts

@@ -19,7 +19,7 @@ import { RenderableState } from '../renderable';
 import { createPointRenderObject } from '../render-object';
 import { PointValues } from '../renderable/point';
 import Scene from '../scene';
-import { createEmptyFlags } from 'mol-geo/util/flag-data';
+import { createEmptyMarkers } from 'mol-geo/util/marker-data';
 
 // function writeImage(gl: WebGLRenderingContext, width: number, height: number) {
 //     const pixels = new Uint8Array(width * height * 4)
@@ -49,7 +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 marker = createEmptyMarkers()
 
     const aTransform = ValueCell.create(new Float32Array(16))
     const m4 = Mat4.identity()
@@ -61,7 +61,7 @@ function createPoints() {
         aTransform,
         aInstanceId,
         ...color,
-        ...flag,
+        ...marker,
         ...size,
 
         uAlpha: ValueCell.create(1.0),
@@ -71,7 +71,8 @@ function createPoints() {
         drawCount: ValueCell.create(3),
         instanceCount: ValueCell.create(1),
 
-        dPointSizeAttenuation: ValueCell.create(true)
+        dPointSizeAttenuation: ValueCell.create(true),
+        dUseFog: ValueCell.create(true),
     }
     const state: RenderableState = {
         visible: true,
@@ -96,7 +97,7 @@ describe('renderer', () => {
         expect(ctx.programCache.count).toBe(0);
         expect(ctx.shaderCache.count).toBe(0);
 
-        renderer.setViewport({ x: 0, y: 0, width: 64, height: 48 })
+        renderer.setViewport(0, 0, 64, 48)
         expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[2]).toBe(64)
         expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[3]).toBe(48)
     })

+ 42 - 0
src/mol-gl/object3d.ts

@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+
+export interface Object3D {
+    readonly view: Mat4,
+    readonly position: Vec3,
+    readonly direction: Vec3,
+    readonly up: Vec3,
+
+    update: () => void
+}
+
+export function createObject3D(): Object3D {
+    const view = Mat4.identity()
+    const position = Vec3.create(0, 0, 0)
+    const direction = Vec3.create(0, 0, -1)
+    const up = Vec3.create(0, 1, 0)
+
+    const center = Vec3.zero()
+
+    return {
+        view,
+        position,
+        direction,
+        up,
+
+        update() {
+            // console.log('position', position)
+            // console.log('direction', direction)
+            // console.log('up', up)
+            Vec3.add(center, position, direction)
+            Mat4.lookAt(view, position, center, up)
+            // Mat4.lookAt(view, center, position, up)
+            // console.log('view', view)
+        }
+    }
+}

+ 17 - 4
src/mol-gl/renderable.ts

@@ -5,17 +5,20 @@
  */
 
 import { Program } from './webgl/program';
-import { RenderableValues, Values, RenderableSchema } from './renderable/schema';
+import { RenderableValues, Values, RenderableSchema, BaseValues } from './renderable/schema';
 import { RenderVariant, RenderItem } from './webgl/render-item';
+import { Sphere3D } from 'mol-math/geometry';
+import { calculateBoundingSphereFromValues } from './renderable/util';
 
 export type RenderableState = {
     visible: boolean
     depthMask: boolean
 }
 
-export interface Renderable<T extends RenderableValues> {
+export interface Renderable<T extends RenderableValues & BaseValues> {
     readonly values: T
     readonly state: RenderableState
+    readonly boundingSphere: Sphere3D
 
     render: (variant: RenderVariant) => void
     getProgram: (variant: RenderVariant) => Program
@@ -23,14 +26,24 @@ export interface Renderable<T extends RenderableValues> {
     dispose: () => void
 }
 
-export function createRenderable<T extends Values<RenderableSchema>>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> {
+export function createRenderable<T extends Values<RenderableSchema> & BaseValues>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> {
+    let boundingSphere: Sphere3D | undefined
+    
     return {
         get values () { return values },
         get state () { return state },
+        get boundingSphere () {
+            if (boundingSphere) return boundingSphere
+            boundingSphere = calculateBoundingSphereFromValues(values)
+            return boundingSphere
+        },
 
         render: (variant: RenderVariant) => renderItem.render(variant),
         getProgram: (variant: RenderVariant) => renderItem.getProgram(variant),
-        update: () => renderItem.update(),
+        update: () => {
+            renderItem.update()
+            boundingSphere = undefined
+        },
         dispose: () => renderItem.destroy()
     }
 }

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

@@ -126,6 +126,13 @@ export const GlobalUniformSchema = {
 
     uPixelRatio: UniformSpec('f'),
     uViewportHeight: UniformSpec('f'),
+
+    uHighlightColor: UniformSpec('v3'),
+    uSelectColor: UniformSpec('v3'),
+
+    uFogNear: UniformSpec('f'),
+    uFogFar: UniformSpec('f'),
+    uFogColor: UniformSpec('v3'),
 }
 export type GlobalUniformSchema = typeof GlobalUniformSchema
 export type GlobalUniformValues = { [k in keyof GlobalUniformSchema]: ValueCell<any> }
@@ -146,15 +153,16 @@ export const BaseSchema = {
     uElementCount: UniformSpec('i'),
     uColor: UniformSpec('v3'),
     uColorTexSize: UniformSpec('v2'),
-    uFlagTexSize: UniformSpec('v2'),
+    uMarkerTexSize: UniformSpec('v2'),
 
     tColor: TextureSpec('rgb', 'ubyte'),
-    tFlag: TextureSpec('alpha', 'ubyte'),
+    tMarker: TextureSpec('alpha', 'ubyte'),
 
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
 
     dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'element', 'element_instance']),
+    dUseFog: DefineSpec('boolean'),
 }
 export type BaseSchema = typeof BaseSchema
 export type BaseValues = Values<BaseSchema>

+ 78 - 0
src/mol-gl/renderable/util.ts

@@ -4,6 +4,10 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { Sphere3D } from 'mol-math/geometry'
+import { Mat4, Vec3 } from 'mol-math/linear-algebra'
+import { ValueCell } from 'mol-util';
+
 export function calculateTextureInfo (n: number, itemSize: number) {
     const sqN = Math.sqrt(n * itemSize)
     let width = Math.ceil(sqN)
@@ -27,4 +31,78 @@ export function fillSerial<T extends Helpers.NumberArray> (array: T) {
     const n = array.length
     for (let i = 0; i < n; ++i) array[ i ] = i
     return array
+}
+
+export interface PositionValues {
+    aPosition: ValueCell<Float32Array>
+    drawCount: ValueCell<number>,
+    aTransform: ValueCell<Float32Array>,
+    instanceCount: ValueCell<number>,
+}
+
+function getPositionDataFromValues(values: PositionValues) {
+    return {
+        position: values.aPosition.ref.value,
+        positionCount: values.drawCount.ref.value / 3 / 3,
+        transform: values.aTransform.ref.value,
+        transformCount: values.instanceCount.ref.value
+    }
+}
+
+export function calculateBoundingSphereFromValues(values: PositionValues){
+    const { position, positionCount, transform, transformCount } = getPositionDataFromValues(values)
+    return calculateBoundingSphere(position, positionCount, transform, transformCount)
+}
+
+export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number): Sphere3D {
+
+    const m = Mat4.zero()
+
+    let cx = 0, cy = 0, cz = 0;
+    let radiusSq = 0;
+
+    for (let i = 0, _i = positionCount * 3; i < _i; i += 3) {
+        cx += position[i];
+        cy += position[i + 1];
+        cz += position[i + 2];
+    }
+
+    if (positionCount > 0) {
+        cx /= positionCount;
+        cy /= positionCount;
+        cz /= positionCount;
+    }
+
+    for (let i = 0, _i = positionCount * 3; i < _i; i += 3) {
+        const dx = position[i] - cx
+        const dy = position[i + 1] - cy
+        const dz = position[i + 2] - cz;
+        const d = dx * dx + dy * dy + dz * dz;
+        if (d > radiusSq) radiusSq = d;
+    }
+
+    const c = Vec3.create(cx, cy, cz)
+    const ct = Vec3.zero()
+
+    const center = Vec3.zero()
+    const centers = new Float32Array(3 * transformCount)
+
+    for (let i = 0, _i = transformCount; i < _i; ++i) {
+        Mat4.fromArray(m, transform, i * 16)
+        Vec3.transformMat4(ct, c, m)
+        Vec3.add(center, center, ct)
+        Vec3.toArray(ct, centers, i * 3)
+    }
+
+    Vec3.scale(center, center, 1 / transformCount)
+
+    let r = Math.sqrt(radiusSq)
+    let radius = r
+
+    for (let i = 0, _i = transformCount; i < _i; ++i) {
+        Vec3.fromArray(ct, centers, i * 3)
+        radius = Math.max(radius, Vec3.distance(center, ct) + r)
+    }
+
+    return { center, radius };
 }

+ 25 - 13
src/mol-gl/renderer.ts

@@ -14,7 +14,7 @@ import { Mat4, Vec3 } from 'mol-math/linear-algebra';
 import { Renderable } from './renderable';
 import { Color } from 'mol-util/color';
 import { ValueCell } from 'mol-util';
-import { RenderableValues, GlobalUniformValues } from './renderable/schema';
+import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
 import { RenderVariant } from './webgl/render-item';
 
 export interface RendererStats {
@@ -29,13 +29,12 @@ export interface RendererStats {
 }
 
 interface Renderer {
-    render: (scene: Scene, variant: RenderVariant) => void
+    readonly stats: RendererStats
 
-    setViewport: (viewport: Viewport) => void
+    render: (scene: Scene, variant: RenderVariant) => void
+    setViewport: (x: number, y: number, width: number, height: number) => void
     setClearColor: (color: Color) => void
     getImageData: () => ImageData
-
-    stats: RendererStats
     dispose: () => void
 }
 
@@ -50,12 +49,14 @@ namespace Renderer {
         const { gl } = ctx
         let { clearColor, viewport: _viewport } = { ...DefaultRendererProps, ...props }
 
-        const model = Mat4.identity()
         const viewport = Viewport.clone(_viewport)
 
         // const lightPosition = Vec3.create(0, 0, -100)
         const lightColor = Vec3.create(1.0, 1.0, 1.0)
         const lightAmbient = Vec3.create(0.5, 0.5, 0.5)
+        const highlightColor = Vec3.create(1.0, 0.4, 0.6)
+        const selectColor = Vec3.create(0.2, 1.0, 0.1)
+        const fogColor = Vec3.create(0.0, 0.0, 0.0)
 
         function setClearColor(color: Color) {
             const [ r, g, b ] = Color.toRgbNormalized(color)
@@ -64,7 +65,7 @@ namespace Renderer {
         setClearColor(clearColor)
 
         const globalUniforms: GlobalUniformValues = {
-            uModel: ValueCell.create(Mat4.clone(model)),
+            uModel: ValueCell.create(Mat4.identity()),
             uView: ValueCell.create(Mat4.clone(camera.view)),
             uProjection: ValueCell.create(Mat4.clone(camera.projection)),
 
@@ -72,11 +73,18 @@ namespace Renderer {
             uViewportHeight: ValueCell.create(viewport.height),
 
             uLightColor: ValueCell.create(Vec3.clone(lightColor)),
-            uLightAmbient: ValueCell.create(Vec3.clone(lightAmbient))
+            uLightAmbient: ValueCell.create(Vec3.clone(lightAmbient)),
+
+            uHighlightColor: ValueCell.create(Vec3.clone(highlightColor)),
+            uSelectColor: ValueCell.create(Vec3.clone(selectColor)),
+
+            uFogNear: ValueCell.create(camera.near),
+            uFogFar: ValueCell.create(camera.far / 50),
+            uFogColor: ValueCell.create(Vec3.clone(fogColor)),
         }
 
         let currentProgramId = -1
-        const renderObject = (r: Renderable<RenderableValues>, variant: RenderVariant) => {
+        const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: RenderVariant) => {
             const program = r.getProgram(variant)
             if (r.state.visible) {
                 if (currentProgramId !== program.id) {
@@ -105,9 +113,13 @@ namespace Renderer {
         }
 
         const render = (scene: Scene, variant: RenderVariant) => {
+            ValueCell.update(globalUniforms.uModel, scene.view)
             ValueCell.update(globalUniforms.uView, camera.view)
             ValueCell.update(globalUniforms.uProjection, camera.projection)
 
+            ValueCell.update(globalUniforms.uFogFar, camera.fogFar)
+            ValueCell.update(globalUniforms.uFogNear, camera.fogNear)
+
             currentProgramId = -1
 
             gl.depthMask(true)
@@ -128,10 +140,10 @@ namespace Renderer {
             render,
 
             setClearColor,
-            setViewport: (newViewport: Viewport) => {
-                Viewport.copy(viewport, newViewport)
-                gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height)
-                ValueCell.update(globalUniforms.uViewportHeight, viewport.height)
+            setViewport: (x: number, y: number, width: number, height: number) => {
+                Viewport.set(viewport, x, y, width, height)
+                gl.viewport(x, y, width, height)
+                ValueCell.update(globalUniforms.uViewportHeight, height)
             },
             getImageData: () => {
                 const { width, height } = viewport

+ 52 - 8
src/mol-gl/scene.ts

@@ -6,29 +6,68 @@
 
 import { Renderable } from './renderable'
 import { Context } from './webgl/context';
-import { RenderableValues } from './renderable/schema';
+import { RenderableValues, BaseValues } from './renderable/schema';
 import { RenderObject, createRenderable } from './render-object';
+import { Object3D, createObject3D } from './object3d';
+import { Sphere3D } from 'mol-math/geometry';
+import { Vec3 } from 'mol-math/linear-algebra';
 
+function calculateBoundingSphere(renderableMap: Map<RenderObject, Renderable<RenderableValues & BaseValues>>): Sphere3D {
+    let count = 0
+    const center = Vec3.zero()
+    renderableMap.forEach((r, o) => {
+        if (r.boundingSphere.radius) {
+            Vec3.add(center, center, r.boundingSphere.center)
+            ++count
+        }
+    })
+    if (count > 0) {
+        Vec3.scale(center, center, 1 / count)
+    }
+
+    let radius = 0
+    renderableMap.forEach((r, o) => {
+        if (r.boundingSphere.radius) {
+            radius = Math.max(radius, Vec3.distance(center, r.boundingSphere.center) + r.boundingSphere.radius)
+        }
+    })
+
+    return { center, radius };
+}
+
+interface Scene extends Object3D {
+    readonly count: number
+    readonly boundingSphere: Sphere3D
 
-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
     eachTransparent: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
-    count: number
 }
 
 namespace Scene {
     export function create(ctx: Context): Scene {
-        const renderableMap = new Map<RenderObject, Renderable<RenderableValues>>()
+        const renderableMap = new Map<RenderObject, Renderable<RenderableValues & BaseValues>>()
+        let boundingSphere: Sphere3D | undefined
+
+        const { view, position, up, direction, update } = createObject3D()
 
         return {
+            // ...createObject3D(), // TODO does not work in conjunction with getter
+            view, position, up, direction,
+
+            update: () => {
+                update()
+                renderableMap.forEach((o, r) => o.update())
+                boundingSphere = undefined
+            },
+            
             add: (o: RenderObject) => {
                 if (!renderableMap.has(o)) {
                     renderableMap.set(o, createRenderable(ctx, o))
+                    boundingSphere = undefined
                 } else {
                     console.warn(`RenderObject with id '${o.id}' already present`)
                 }
@@ -38,14 +77,13 @@ namespace Scene {
                 if (renderable) {
                     renderable.dispose()
                     renderableMap.delete(o)
+                    boundingSphere = undefined
                 }
             },
-            update: () => {
-                renderableMap.forEach((r, o) => r.update())
-            },
             clear: () => {
                 renderableMap.forEach(renderable => renderable.dispose())
                 renderableMap.clear()
+                boundingSphere = undefined
             },
             forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => {
                 renderableMap.forEach(callbackFn)
@@ -62,6 +100,12 @@ namespace Scene {
             },
             get count() {
                 return renderableMap.size
+            },
+            get boundingSphere() {
+                if (boundingSphere) return boundingSphere
+                // TODO avoid array creation
+                boundingSphere = calculateBoundingSphere(renderableMap)
+                return boundingSphere
             }
         }
     }

+ 6 - 0
src/mol-gl/shader/chunks/apply-fog.glsl

@@ -0,0 +1,6 @@
+// #ifdef dUseFog
+	float depth = length(vViewPosition);
+    // float depth = gl_FragCoord.z / gl_FragCoord.w;
+    float fogFactor = smoothstep(uFogNear, uFogFar, depth);
+	gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
+// #endif

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

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

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


+ 1 - 0
src/mol-gl/shader/chunks/assign-marker-varying.glsl

@@ -0,0 +1 @@
+vMarker = readFromTexture(tMarker, aInstanceId * float(uElementCount) + aElementId, uMarkerTexSize).a;

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


+ 4 - 0
src/mol-gl/shader/chunks/assign-position.glsl

@@ -0,0 +1,4 @@
+mat4 modelView = uView * uModel * aTransform;
+vec4 mvPosition = modelView * vec4(aPosition, 1.0);
+vViewPosition = mvPosition.xyz;
+gl_Position = uProjection * mvPosition;

+ 7 - 1
src/mol-gl/shader/chunks/common-frag-params.glsl

@@ -2,4 +2,10 @@ uniform int uObjectId;
 uniform int uInstanceCount;
 uniform int uElementCount;
 
-varying float vFlag;
+uniform vec3 uHighlightColor;
+uniform vec3 uSelectColor;
+varying float vMarker;
+
+uniform float uFogNear;
+uniform float uFogFar;
+uniform vec3 uFogColor;

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

@@ -4,7 +4,7 @@ uniform int uObjectId;
 uniform int uInstanceCount;
 uniform int uElementCount;
 
-uniform vec2 uFlagTexSize;
-uniform sampler2D tFlag;
-varying float vFlag;
+uniform vec2 uMarkerTexSize;
+uniform sampler2D tMarker;
+varying float vMarker;
 #pragma glslify: readFromTexture = require(../utils/read-from-texture.glsl)

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

@@ -36,7 +36,7 @@ const float albedo = 0.95;
 
 void main() {
     // material color
-    #pragma glslify: import('./chunks/color-assign-material.glsl')
+    #pragma glslify: import('./chunks/assign-material-color.glsl')
 
     #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking)
         // gl_FragColor = vec4(material.r, material.g, material.a, 1.0);
@@ -76,20 +76,7 @@ void main() {
         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);
-        // }
-
-        float flag = floor(vFlag * 255.0);
-
-        if (flag == 0.0) {
-            // diffuseColor = vec4( vColor, opacity );
-        } else if (mod(flag, 2.0) == 0.0) {
-            // diffuseColor = vec4(highlightColor, opacity);
-            gl_FragColor.rgb = mix(vec3(1.0, 0.4, 0.6), gl_FragColor.rgb, 0.3);
-        } else {
-            // diffuseColor = vec4(selectionColor, opacity);
-            gl_FragColor.rgb = mix(vec3(0.2, 1.0, 0.1), gl_FragColor.rgb, 0.3);
-        }
+        #pragma glslify: import('./chunks/apply-marker-color.glsl')
+        #pragma glslify: import('./chunks/apply-fog.glsl')
     #endif
 }

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

@@ -26,13 +26,9 @@ varying vec3 vViewPosition;
 #pragma glslify: transpose = require(./utils/transpose.glsl)
 
 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);
-    vViewPosition = mvPosition.xyz;
-    gl_Position = uProjection * mvPosition;
+    #pragma glslify: import('./chunks/assign-color-varying.glsl')
+    #pragma glslify: import('./chunks/assign-marker-varying.glsl')
+    #pragma glslify: import('./chunks/assign-position.glsl')
 
     #ifndef dFlatShaded
         mat3 normalMatrix = transpose(inverse(mat3(modelView)));

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

@@ -13,6 +13,10 @@ precision highp int;
 uniform float uAlpha;
 
 void main(){
-    #pragma glslify: import('./chunks/color-assign-material.glsl')
+    #pragma glslify: import('./chunks/assign-material-color.glsl')
+    
     gl_FragColor = vec4(material, uAlpha);
+
+    #pragma glslify: import('./chunks/apply-marker-color.glsl')
+    #pragma glslify: import('./chunks/apply-fog.glsl')
 }

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

@@ -25,10 +25,8 @@ attribute float aInstanceId;
 attribute float aElementId;
 
 void main(){
-    #pragma glslify: import('./chunks/color-assign-varying.glsl')
-
-    mat4 modelView = uView * uModel * aTransform;
-    vec4 mvPosition = modelView * vec4(aPosition, 1.0);
+    #pragma glslify: import('./chunks/assign-color-varying.glsl')
+    #pragma glslify: import('./chunks/assign-position.glsl')
 
     #if defined(dSizeType_uniform)
         float size = uSize;

+ 8 - 1
src/mol-model/loci.ts

@@ -14,4 +14,11 @@ export function isEveryLoci(x: any): x is EveryLoci {
     return !!x && x.kind === 'every-loci';
 }
 
-export type Loci =  Element.Loci | Link.Loci | EveryLoci
+/** A Loci that that is empty */
+export const EmptyLoci = { kind: 'empty-loci' as 'empty-loci' }
+export type EmptyLoci = typeof EmptyLoci
+export function isEmptyLoci(x: any): x is EmptyLoci {
+    return !!x && x.kind === 'empty-loci';
+}
+
+export type Loci =  Element.Loci | Link.Loci | EveryLoci | EmptyLoci

+ 3 - 1
src/mol-util/input/input-observer.ts

@@ -98,6 +98,7 @@ export type MoveInput = {
     y: number,
     pageX: number,
     pageY: number,
+    inside: boolean,
 } & BaseInput
 
 export type PinchInput = {
@@ -355,7 +356,8 @@ namespace InputObserver {
             eventOffset(pointerEnd, ev)
             const { pageX, pageY } = ev
             const [ x, y ] = pointerEnd
-            move.next({ x, y, pageX, pageY, buttons, modifiers })
+            const inside = insideBounds(pointerEnd)
+            move.next({ x, y, pageX, pageY, buttons, modifiers, inside })
 
             if (dragging === DraggingState.Stopped) return
 

+ 29 - 15
src/mol-view/camera/base.ts

@@ -6,22 +6,22 @@
 
 import { Mat4, Vec3, Vec4 } from 'mol-math/linear-algebra'
 import { cameraProject, cameraUnproject, cameraLookAt, Viewport } from './util';
+import { Object3D, createObject3D } from 'mol-gl/object3d';
 
-export interface Camera {
-    view: Mat4,
-    projection: Mat4,
-    projectionView: Mat4,
-    inverseProjectionView: Mat4,
+export interface Camera extends Object3D {
+    readonly projection: Mat4,
+    readonly projectionView: Mat4,
+    readonly inverseProjectionView: Mat4,
+    readonly viewport: Viewport,
 
-    viewport: Viewport,
-    position: Vec3,
-    direction: Vec3,
-    up: Vec3,
+    near: number,
+    far: number,
+    fogNear: number,
+    fogFar: number,
 
     translate: (v: Vec3) => void,
     reset: () => void,
     lookAt: (target: Vec3) => void,
-    update: () => void,
     project: (out: Vec4, point: Vec3) => Vec4,
     unproject: (out: Vec3, point: Vec3) => Vec3
 }
@@ -30,7 +30,11 @@ export const DefaultCameraProps = {
     position: Vec3.zero(),
     direction: Vec3.create(0, 0, -1),
     up: Vec3.create(0, 1, 0),
-    viewport: Viewport.create(-1, -1, 1, 1)
+    viewport: Viewport.create(-1, -1, 1, 1),
+    near: 0.1,
+    far: 10000,
+    fogNear: 0.1,
+    fogFar: 10000,
 }
 export type CameraProps = Partial<typeof DefaultCameraProps>
 
@@ -38,11 +42,12 @@ export namespace Camera {
     export function create(props?: CameraProps): Camera {
         const p = { ...DefaultCameraProps, ...props };
 
+        const { view, position, direction, up } = createObject3D()
+        Vec3.copy(position, p.position)
+        Vec3.copy(direction, p.direction)
+        Vec3.copy(up, p.up)
+
         const projection = Mat4.identity()
-        const view = Mat4.identity()
-        const position = Vec3.clone(p.position)
-        const direction = Vec3.clone(p.direction)
-        const up = Vec3.clone(p.up)
         const viewport = Viewport.clone(p.viewport)
         const projectionView = Mat4.identity()
         const inverseProjectionView = Mat4.identity()
@@ -89,6 +94,15 @@ export namespace Camera {
             direction,
             up,
 
+            get near() { return p.near },
+            set near(value: number) { p.near = value },
+            get far() { return p.far },
+            set far(value: number) { p.far = value },
+            get fogNear() { return p.fogNear },
+            set fogNear(value: number) { p.fogNear = value },
+            get fogFar() { return p.fogFar },
+            set fogFar(value: number) { p.fogFar = value },
+
             translate,
             reset,
             lookAt,

+ 1 - 5
src/mol-view/camera/orthographic.ts

@@ -1,5 +1 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
+// TODO

+ 0 - 8
src/mol-view/camera/perspective.ts

@@ -15,8 +15,6 @@ export interface PerspectiveCamera extends Camera {
 
 export const DefaultPerspectiveCameraProps = {
     fov: Math.PI / 4,
-    near: 0.1,
-    far: 10000,
     ...DefaultCameraProps
 }
 export type PerspectiveCameraProps = Partial<typeof DefaultPerspectiveCameraProps>
@@ -48,12 +46,6 @@ export namespace PerspectiveCamera {
             ...camera,
             update,
 
-            get far() { return far },
-            set far(value: number) { far = value },
-
-            get near() { return near },
-            set near(value: number) { near = value },
-
             get fov() { return fov },
             set fov(value: number) { fov = value },
         }

+ 7 - 1
src/mol-view/camera/util.ts

@@ -20,10 +20,16 @@ export namespace Viewport {
     export function clone(viewport: Viewport): Viewport {
         return { ...viewport }
     }
-
     export function copy(target: Viewport, source: Viewport): Viewport {
         return Object.assign(target, source)
     }
+    export function set(viewport: Viewport, x: number, y: number, width: number, height: number): Viewport {
+        viewport.x = x
+        viewport.y = y
+        viewport.width = width
+        viewport.height = height
+        return viewport
+    }
 }
 
 const tmpVec3 = Vec3.zero()

+ 2 - 7
src/mol-view/controls/trackball.ts

@@ -12,6 +12,7 @@
 import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra';
 import { cameraLookAt, Viewport } from '../camera/util';
 import InputObserver, { DragInput, WheelInput, ButtonsFlag, PinchInput } from 'mol-util/input/input-observer';
+import { Object3D } from 'mol-gl/object3d';
 
 export const DefaultTrackballControlsProps = {
     noScroll: true,
@@ -29,12 +30,6 @@ export const DefaultTrackballControlsProps = {
 }
 export type TrackballControlsProps = Partial<typeof DefaultTrackballControlsProps>
 
-interface Object {
-    position: Vec3,
-    direction: Vec3,
-    up: Vec3,
-}
-
 interface TrackballControls {
     viewport: Viewport
     target: Vec3
@@ -50,7 +45,7 @@ interface TrackballControls {
 }
 
 namespace TrackballControls {
-    export function create (input: InputObserver, object: Object, props: TrackballControlsProps = {}): TrackballControls {
+    export function create (input: InputObserver, object: Object3D, props: TrackballControlsProps = {}): TrackballControls {
         const p = { ...DefaultTrackballControlsProps, ...props }
 
         const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }

+ 22 - 15
src/mol-view/label.ts

@@ -6,7 +6,6 @@
  */
 
 import { Unit, Element, Queries } from 'mol-model/structure';
-import { Link } from 'mol-model/structure/structure/unit/links';
 import { Loci } from 'mol-model/loci';
 
 const elementLocA = Element.Location()
@@ -17,21 +16,29 @@ function setElementLocation(loc: Element.Location, unit: Unit, index: number) {
     loc.element = unit.elements[index]
 }
 
-export function labelFirst(loci: Loci) {
-    if(Element.isLoci(loci)) {
-        const e = loci.elements[0]
-        if (e && e.indices[0] !== undefined) {
-            return elementLabel(Element.Location(e.unit, e.indices[0]))
-        }
-    } else if (Link.isLoci(loci)) {
-        const bond = loci.links[0]
-        if (bond) {
-            setElementLocation(elementLocA, bond.aUnit, bond.aIndex)
-            setElementLocation(elementLocB, bond.bUnit, bond.bIndex)
-            return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}`
-        }
+export function labelFirst(loci: Loci): string {
+    switch (loci.kind) {
+        case 'element-loci':
+            const e = loci.elements[0]
+            if (e && e.indices[0] !== undefined) {
+                return elementLabel(Element.Location(e.unit, e.indices[0]))
+            } else {
+                return 'Nothing'
+            }
+        case 'link-loci':
+            const bond = loci.links[0]
+            if (bond) {
+                setElementLocation(elementLocA, bond.aUnit, bond.aIndex)
+                setElementLocation(elementLocB, bond.bUnit, bond.bIndex)
+                return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}`
+            } else {
+                return 'Nothing'
+            }
+        case 'every-loci':
+            return 'Evertything'
+        case 'empty-loci':
+            return 'Nothing'
     }
-    return ''
 }
 
 export function elementLabel(loc: Element.Location) {

+ 72 - 49
src/mol-view/viewer.ts

@@ -22,9 +22,8 @@ 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';
-import { labelFirst } from './label';
-import { FlagAction } from 'mol-geo/util/flag-data';
-import { EveryLoci } from 'mol-model/loci';
+import { MarkerAction } from 'mol-geo/util/marker-data';
+import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
 
 interface Viewer {
     center: (p: Vec3) => void
@@ -41,7 +40,9 @@ interface Viewer {
     requestDraw: () => void
     animate: () => void
     pick: () => void
-    identify: (x: number, y: number) => void
+    identify: (x: number, y: number) => PickingId
+    mark: (loci: Loci, action: MarkerAction) => void
+    getLoci: (pickingId: PickingId) => Loci
 
     reprCount: BehaviorSubject<number>
     identified: BehaviorSubject<string>
@@ -76,45 +77,14 @@ namespace Viewer {
 
         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) => {
-                repr.applyFlags(EveryLoci, FlagAction.RemoveHighlight)
-                const loci = repr.getLoci(p)
-                if (loci) {
-                    label = labelFirst(loci)
-                    repr.applyFlags(loci, FlagAction.Highlight)
-                }
-                scene.update()
-                requestDraw()
-            })
-            identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`)
-        })
-        input.click.subscribe(({x, y}) => {
-            const p = identify(x, y)
-            reprMap.forEach((roSet, repr) => {
-                const loci = repr.getLoci(p)
-                if (loci) {
-                    repr.applyFlags(loci, FlagAction.ToggleSelect)
-                    scene.update()
-                    requestDraw()
-                }
-            })
-        })
 
         const camera = PerspectiveCamera.create({
             near: 0.1,
             far: 10000,
             position: Vec3.create(0, 0, 50)
         })
-
-        const controls = TrackballControls.create(input, camera, {
-
-        })
+        // camera.lookAt(Vec3.create(0, 0, 0))
 
         const gl = getWebGLContext(canvas, {
             alpha: false,
@@ -128,6 +98,8 @@ namespace Viewer {
         const ctx = createContext(gl)
 
         const scene = Scene.create(ctx)
+        // const controls = TrackballControls.create(input, scene, {})
+        const controls = TrackballControls.create(input, camera, {})
         const renderer = Renderer.create(ctx, camera)
 
         const pickScale = 1 / 4
@@ -140,13 +112,71 @@ namespace Viewer {
         let pickDirty = true
         let drawPending = false
         const prevProjectionView = Mat4.zero()
+        const prevSceneView = Mat4.zero()
+
+        function getLoci(pickingId: PickingId) {
+            let loci: Loci = EmptyLoci
+            reprMap.forEach((_, repr) => {
+                const _loci = repr.getLoci(pickingId)
+                if (!isEmptyLoci(_loci)) {
+                    if (!isEmptyLoci(loci)) console.warn('found another loci')
+                    loci = _loci
+                }
+            })
+            return loci
+        }
+
+        function mark(loci: Loci, action: MarkerAction) {
+            reprMap.forEach((roSet, repr) => repr.mark(loci, action))
+            scene.update()
+            requestDraw()
+        }
+
+        let nearPlaneDelta = 0
+        function computeNearDistance() {
+            const focusRadius = scene.boundingSphere.radius
+            let dist = Vec3.distance(controls.target, camera.position)
+            if (dist > focusRadius) return dist - focusRadius
+            return 0
+        }
 
         function render(variant: RenderVariant, force?: boolean) {
+            // const p = scene.boundingSphere.center
+            // console.log(p[0], p[1], p[2])
+            // Vec3.set(controls.target, p[0], p[1], p[2])
+
+            const focusRadius = scene.boundingSphere.radius
+            const targetDistance = Vec3.distance(controls.target, camera.position)
+            // console.log(targetDistance, controls.target, camera.position)
+            let near = computeNearDistance() + nearPlaneDelta
+            camera.near = Math.max(0.01, Math.min(near, targetDistance - 0.5))
+
+            let fogNear = targetDistance - camera.near + 1 * focusRadius - nearPlaneDelta;
+            let fogFar = targetDistance - camera.near + 2 * focusRadius - nearPlaneDelta; 
+                                        
+            //console.log(fogNear, fogFar); 
+            camera.fogNear = Math.max(fogNear, 0.1);
+            camera.fogFar = Math.max(fogFar, 0.2);
+            
+            // console.log(camera.fogNear, camera.fogFar, targetDistance)
+
+            switch (variant) {
+                case 'pickObject': objectPickTarget.bind(); break;
+                case 'pickInstance': instancePickTarget.bind(); break;
+                case 'pickElement': elementPickTarget.bind(); break;
+                case 'draw':
+                    ctx.unbindFramebuffer();
+                    renderer.setViewport(0, 0, canvas.width, canvas.height);
+                    break;
+            }
             let didRender = false
             controls.update()
             camera.update()
-            if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value)) {
+            scene.update()
+            if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value) || !Mat4.areEqual(scene.view, prevSceneView, EPSILON.Value)) {
+                // console.log('foo', force, prevSceneView, scene.view)
                 Mat4.copy(prevProjectionView, camera.projectionView)
+                Mat4.copy(prevSceneView, scene.view)
                 renderer.render(scene, variant)
                 if (variant === 'draw') {
                     pickDirty = true
@@ -158,9 +188,6 @@ namespace Viewer {
         }
 
         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)
             }
@@ -179,13 +206,8 @@ namespace Viewer {
         }
 
         function pick() {
-            objectPickTarget.bind()
             render('pickObject', pickDirty)
-
-            instancePickTarget.bind()
             render('pickInstance', pickDirty)
-
-            elementPickTarget.bind()
             render('pickElement', pickDirty)
 
             pickDirty = false
@@ -264,6 +286,8 @@ namespace Viewer {
             animate,
             pick,
             identify,
+            mark,
+            getLoci,
 
             handleResize,
             resetCamera: () => {
@@ -300,10 +324,9 @@ namespace Viewer {
 
         function handleResize() {
             resizeCanvas(canvas, container)
-            const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height }
-            renderer.setViewport(viewport)
-            Viewport.copy(camera.viewport, viewport)
-            Viewport.copy(controls.viewport, viewport)
+            renderer.setViewport(0, 0, canvas.width, canvas.height)
+            Viewport.set(camera.viewport, 0, 0, canvas.width, canvas.height)
+            Viewport.set(controls.viewport, 0, 0, canvas.width, canvas.height)
 
             const pickWidth = Math.round(canvas.width * pickScale)
             const pickHeight = Math.round(canvas.height * pickScale)