123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- /**
- * 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
- }
- }
- }
|