|
@@ -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
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|