ソースを参照

wip, regl camera

Alexander Rose 7 年 前
コミット
920b48b25e
1 ファイル変更191 行追加0 行削除
  1. 191 0
      src/mol-gl/camera.ts

+ 191 - 0
src/mol-gl/camera.ts

@@ -0,0 +1,191 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+/*
+ * This code has been modified from https://github.com/regl-project/regl-camera,
+ * copyright (c) 2016 Mikola Lysenko. MIT License
+ */
+
+const isBrowser = typeof window !== 'undefined'
+
+import REGL = require('regl');
+
+import mouseChange, { MouseModifiers } from 'mol-util/mouse-change'
+import mouseWheel from 'mol-util/mouse-wheel'
+import { Mat4, Vec3 } from 'mol-math/linear-algebra/3d'
+import { clamp, damp } from 'mol-math/interpolate'
+
+export interface CameraUniforms {
+    projection: Mat4,
+}
+
+export interface CameraState {
+    center: Vec3,
+    theta: number,
+    phi: number,
+    distance: number,
+    eye: Vec3,
+    up: Vec3,
+    fovy: number,
+    near: number,
+    far: number,
+    noScroll: boolean,
+    flipY: boolean,
+    dtheta: number,
+    dphi: number,
+    rotationSpeed: number,
+    zoomSpeed: number,
+    renderOnDirty: boolean,
+    damping: number,
+    minDistance: number,
+    maxDistance: number,
+}
+
+export interface Camera {
+    update: (props: any, block: any) => void,
+    setState: (newState: CameraState) => void,
+    isDirty: () => boolean
+}
+
+export namespace Camera {
+    export function create (regl: REGL.Regl, element: HTMLElement, props: Partial<CameraState> = {}): Camera {
+        const state: CameraState = {
+            center: props.center || Vec3.zero(),
+            theta: props.theta || 0,
+            phi: props.phi || 0,
+            distance: Math.log(props.distance || 10.0),
+            eye: Vec3.zero(),
+            up: props.up || Vec3.create(0, 1, 0),
+            fovy: props.fovy || Math.PI / 4.0,
+            near: typeof props.near !== 'undefined' ? props.near : 0.01,
+            far: typeof props.far !== 'undefined' ? props.far : 1000.0,
+            noScroll: typeof props.noScroll !== 'undefined' ? props.noScroll : false,
+            flipY: !!props.flipY,
+            dtheta: 0,
+            dphi: 0,
+            rotationSpeed: typeof props.rotationSpeed !== 'undefined' ? props.rotationSpeed : 1,
+            zoomSpeed: typeof props.zoomSpeed !== 'undefined' ? props.zoomSpeed : 1,
+            renderOnDirty: typeof props.renderOnDirty !== undefined ? !!props.renderOnDirty : false,
+            damping: typeof props.damping !== 'undefined' ? props.damping : 0.9,
+            minDistance: Math.log(typeof props.minDistance !== 'undefined' ? props.minDistance : 0.1),
+            maxDistance: Math.log(typeof props.maxDistance !== 'undefined' ? props.maxDistance : 1000)
+        }
+
+        const view = Mat4.identity()
+        const projection = Mat4.identity()
+
+        const right = Vec3.create(1, 0, 0)
+        const front = Vec3.create(0, 0, 1)
+
+        let dirty = false
+        let ddistance = 0
+
+        let prevX = 0
+        let prevY = 0
+
+        if (isBrowser) {
+            const source = element || regl._gl.canvas
+
+            const getWidth = function () {
+                return element ? element.offsetWidth : window.innerWidth
+            }
+
+            const getHeight = function () {
+                return element ? element.offsetHeight : window.innerHeight
+            }
+
+            mouseChange(source, function (buttons: number, x: number, y: number, mods: MouseModifiers) {
+                if (buttons & 1) {
+                    const dx = (x - prevX) / getWidth()
+                    const dy = (y - prevY) / getHeight()
+
+                    state.dtheta += state.rotationSpeed * 4.0 * dx
+                    state.dphi += state.rotationSpeed * 4.0 * dy
+                    dirty = true;
+                }
+                prevX = x
+                prevY = y
+            })
+
+            mouseWheel(source, function (dx: number, dy: number) {
+                ddistance += dy / getHeight() * state.zoomSpeed
+                dirty = true;
+            }, state.noScroll)
+        }
+
+        function dampAndMarkDirty (x: number) {
+            const xd = damp(x, state.damping)
+            if (Math.abs(xd) < 0.1) return 0
+            dirty = true;
+            return xd
+        }
+
+        function setState (newState: Partial<CameraState> = {}) {
+            Object.assign(state, newState)
+
+            const { center, eye, up, dtheta, dphi } = state
+
+            state.theta += dtheta
+            state.phi = clamp(state.phi + dphi, -Math.PI / 2.0, Math.PI / 2.0)
+            state.distance = clamp(state.distance + ddistance, state.minDistance, state.maxDistance)
+
+            state.dtheta = dampAndMarkDirty(dtheta)
+            state.dphi = dampAndMarkDirty(dphi)
+            ddistance = dampAndMarkDirty(ddistance)
+
+            const theta = state.theta
+            const phi = state.phi
+            const r = Math.exp(state.distance)
+
+            const vf = r * Math.sin(theta) * Math.cos(phi)
+            const vr = r * Math.cos(theta) * Math.cos(phi)
+            const vu = r * Math.sin(phi)
+
+            for (let i = 0; i < 3; ++i) {
+                eye[i] = center[i] + vf * front[i] + vr * right[i] + vu * up[i]
+            }
+
+            Mat4.lookAt(view, eye, center, up)
+        }
+
+        const injectContext = regl({
+            context: {
+                view: () => view,
+                dirty: () => dirty,
+                projection: (context: REGL.DefaultContext) => {
+                    Mat4.perspective(
+                        projection,
+                        state.fovy,
+                        context.viewportWidth / context.viewportHeight,
+                        state.near,
+                        state.far
+                    )
+                    if (state.flipY) { projection[5] *= -1 }
+                    return projection
+                }
+            },
+            uniforms: {  // TODO
+                view: regl.context('view' as any),
+                projection: regl.context('projection' as any)
+            }
+        })
+
+        function update (props: any, block: any) {
+            setState()
+            injectContext(props, block)
+            if (dirty) {
+                console.log(view)
+            }
+            dirty = false
+        }
+
+        return {
+            update,
+            setState,
+            isDirty: () => dirty
+        }
+    }
+}