From 18ec79cd2b2283b518f847e726c9a5550b0003e0 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alexander.rose@weirdbyte.de>
Date: Fri, 8 Jun 2018 18:44:35 +0200
Subject: [PATCH] wip, fog

---
 src/mol-gl/_spec/renderable.spec.ts     | 42 +++++++++++++++
 src/mol-gl/_spec/renderer.spec.ts       |  2 +-
 src/mol-gl/object3d.ts                  | 42 +++++++++++++++
 src/mol-gl/renderable.ts                | 26 +++++++--
 src/mol-gl/renderable/util.ts           | 64 ++++++++++++++++++++++
 src/mol-gl/renderer.ts                  | 28 +++++-----
 src/mol-gl/scene.ts                     | 56 ++++++++++++++++---
 src/mol-gl/shader/chunks/apply-fog.glsl |  4 +-
 src/mol-view/camera/base.ts             | 39 +++++++-------
 src/mol-view/camera/util.ts             |  8 ++-
 src/mol-view/controls/trackball.ts      |  9 +---
 src/mol-view/viewer.ts                  | 71 ++++++++++++++++++-------
 12 files changed, 319 insertions(+), 72 deletions(-)
 create mode 100644 src/mol-gl/_spec/renderable.spec.ts
 create mode 100644 src/mol-gl/object3d.ts

diff --git a/src/mol-gl/_spec/renderable.spec.ts b/src/mol-gl/_spec/renderable.spec.ts
new file mode 100644
index 000000000..a0e486863
--- /dev/null
+++ b/src/mol-gl/_spec/renderable.spec.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 { 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,
+            positionCount: position.length / 3,
+            transform,
+            transformCount: transform.length / 16
+        })
+
+        expect(bs.radius).toBe(1.5)
+        expect(bs.center).toEqual([1.5, 0.0, 0.0])
+    })
+})
diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts
index a08748287..c922e3219 100644
--- a/src/mol-gl/_spec/renderer.spec.ts
+++ b/src/mol-gl/_spec/renderer.spec.ts
@@ -97,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)
     })
diff --git a/src/mol-gl/object3d.ts b/src/mol-gl/object3d.ts
new file mode 100644
index 000000000..d8e4a93ec
--- /dev/null
+++ b/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)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts
index b3a72be16..1e69c6755 100644
--- a/src/mol-gl/renderable.ts
+++ b/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 { calculateBoundingSphere } 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,29 @@ 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> {
+    const position = values.aPosition.ref.value
+    const transform = values.aTransform.ref.value
+    const positionCount = values.drawCount.ref.value / 3 / 3
+    const transformCount = values.instanceCount.ref.value
+
+    let boundingSphere: Sphere3D | undefined
+    
     return {
         get values () { return values },
         get state () { return state },
+        get boundingSphere () {
+            if (boundingSphere) return boundingSphere
+            boundingSphere = calculateBoundingSphere({ position, positionCount, transform, transformCount })
+            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()
     }
 }
diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts
index 3c6048dd8..650831214 100644
--- a/src/mol-gl/renderable/util.ts
+++ b/src/mol-gl/renderable/util.ts
@@ -4,6 +4,9 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { Sphere3D } from 'mol-math/geometry'
+import { Mat4, Vec3 } from 'mol-math/linear-algebra'
+
 export function calculateTextureInfo (n: number, itemSize: number) {
     const sqN = Math.sqrt(n * itemSize)
     let width = Math.ceil(sqN)
@@ -27,4 +30,65 @@ 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 PositionData {
+    position: Float32Array
+    positionCount: number,
+    transform: Float32Array,
+    transformCount: number,
+}
+
+export function calculateBoundingSphere(data: PositionData): Sphere3D {
+    const { position, positionCount, transform, transformCount } = data
+
+    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 };
 }
\ No newline at end of file
diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts
index 885ae9f82..cbc4ffffa 100644
--- a/src/mol-gl/renderer.ts
+++ b/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,7 +49,6 @@ 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)
@@ -67,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)),
 
@@ -86,7 +84,7 @@ namespace Renderer {
         }
 
         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) {
@@ -115,10 +113,16 @@ 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
+            // scene.unsetBoundingSphere()
+            // console.log('scene.boundingSphere', scene.boundingSphere)
 
             gl.depthMask(true)
             gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
@@ -138,10 +142,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
diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts
index 52f098944..0cd225b7b 100644
--- a/src/mol-gl/scene.ts
+++ b/src/mol-gl/scene.ts
@@ -6,29 +6,63 @@
 
 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
+    unsetBoundingSphere: () => void
 }
 
 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,
+            
             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 +72,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)
@@ -60,8 +93,15 @@ namespace Scene {
                     if (o.values.uAlpha.ref.value < 1) callbackFn(r, o)
                 })
             },
+            unsetBoundingSphere: () => boundingSphere = undefined,
             get count() {
                 return renderableMap.size
+            },
+            get boundingSphere() {
+                if (boundingSphere) return boundingSphere
+                // TODO avoid array creation
+                boundingSphere = calculateBoundingSphere(renderableMap)
+                return boundingSphere
             }
         }
     }
diff --git a/src/mol-gl/shader/chunks/apply-fog.glsl b/src/mol-gl/shader/chunks/apply-fog.glsl
index 3b8896f5c..9e176cbaa 100644
--- a/src/mol-gl/shader/chunks/apply-fog.glsl
+++ b/src/mol-gl/shader/chunks/apply-fog.glsl
@@ -1,6 +1,6 @@
 // #ifdef dUseFog
-	// float depth = length(vViewPosition);
-    float depth = gl_FragCoord.z / gl_FragCoord.w;
+	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
\ No newline at end of file
diff --git a/src/mol-view/camera/base.ts b/src/mol-view/camera/base.ts
index f97a19563..891a8ec69 100644
--- a/src/mol-view/camera/base.ts
+++ b/src/mol-view/camera/base.ts
@@ -6,25 +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,
-
-    viewport: Viewport,
-    position: Vec3,
-    direction: Vec3,
-    up: Vec3,
+export interface Camera extends Object3D {
+    readonly projection: Mat4,
+    readonly projectionView: Mat4,
+    readonly inverseProjectionView: Mat4,
+    readonly viewport: Viewport,
 
     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
 }
@@ -36,6 +33,8 @@ export const DefaultCameraProps = {
     viewport: Viewport.create(-1, -1, 1, 1),
     near: 0.1,
     far: 10000,
+    fogNear: 0.1,
+    fogFar: 10000,
 }
 export type CameraProps = Partial<typeof DefaultCameraProps>
 
@@ -43,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()
@@ -94,11 +94,14 @@ export namespace Camera {
             direction,
             up,
 
-            get far() { return p.far },
-            set far(value: number) { p.far = value },
-
             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,
diff --git a/src/mol-view/camera/util.ts b/src/mol-view/camera/util.ts
index a657c6fef..11c77e70d 100644
--- a/src/mol-view/camera/util.ts
+++ b/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()
diff --git a/src/mol-view/controls/trackball.ts b/src/mol-view/controls/trackball.ts
index 4f05c8446..554d81f0a 100644
--- a/src/mol-view/controls/trackball.ts
+++ b/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 }
diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts
index 54163e3a9..e8696d4b1 100644
--- a/src/mol-view/viewer.ts
+++ b/src/mol-view/viewer.ts
@@ -111,10 +111,7 @@ namespace Viewer {
             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 +125,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 +139,53 @@ namespace Viewer {
         let pickDirty = true
         let drawPending = false
         const prevProjectionView = Mat4.zero()
+        const prevSceneView = Mat4.zero()
+
+        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 +197,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 +215,8 @@ namespace Viewer {
         }
 
         function pick() {
-            objectPickTarget.bind()
             render('pickObject', pickDirty)
-
-            instancePickTarget.bind()
             render('pickInstance', pickDirty)
-
-            elementPickTarget.bind()
             render('pickElement', pickDirty)
 
             pickDirty = false
@@ -238,7 +269,7 @@ namespace Viewer {
                 if (oldRO) {
                     SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o))
                     SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o))
-                    scene.update()
+                    // scene.update()
                 } else {
                     repr.renderObjects.forEach(o => scene.add(o))
                 }
@@ -253,7 +284,10 @@ namespace Viewer {
                     reprCount.next(reprMap.size)
                 }
             },
-            update: () => scene.update(),
+            update: () => {
+                scene.forEach((r, o) => r.update())
+                scene.unsetBoundingSphere()
+            },
             clear: () => {
                 reprMap.clear()
                 scene.clear()
@@ -300,10 +334,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)
-- 
GitLab