camera.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. /*
  7. * This code has been modified from https://github.com/regl-project/regl-camera,
  8. * copyright (c) 2016 Mikola Lysenko. MIT License
  9. */
  10. const isBrowser = typeof window !== 'undefined'
  11. import REGL = require('regl');
  12. import mouseChange, { MouseModifiers } from 'mol-util/mouse-change'
  13. import mouseWheel from 'mol-util/mouse-wheel'
  14. import { defaults } from 'mol-util'
  15. import { Mat4, Vec3 } from 'mol-math/linear-algebra/3d'
  16. import { clamp, damp } from 'mol-math/interpolate'
  17. export interface CameraUniforms {
  18. projection: Mat4,
  19. }
  20. export interface CameraState {
  21. center: Vec3,
  22. theta: number,
  23. phi: number,
  24. distance: number,
  25. eye: Vec3,
  26. up: Vec3,
  27. fovy: number,
  28. near: number,
  29. far: number,
  30. noScroll: boolean,
  31. flipY: boolean,
  32. dtheta: number,
  33. dphi: number,
  34. rotationSpeed: number,
  35. zoomSpeed: number,
  36. renderOnDirty: boolean,
  37. damping: number,
  38. minDistance: number,
  39. maxDistance: number,
  40. }
  41. export interface Camera {
  42. update: (props: any, block: any) => void,
  43. setState: (newState: CameraState) => void,
  44. getState: () => CameraState,
  45. dirty: boolean
  46. }
  47. export namespace Camera {
  48. export function create (regl: REGL.Regl, element: HTMLElement, initialState: Partial<CameraState> = {}): Camera {
  49. const state: CameraState = {
  50. center: defaults(initialState.center, Vec3.zero()),
  51. theta: defaults(initialState.theta, 0),
  52. phi: defaults(initialState.phi, 0),
  53. distance: Math.log(defaults(initialState.distance, 10.0)),
  54. eye: Vec3.zero(),
  55. up: defaults(initialState.up, Vec3.create(0, 1, 0)),
  56. fovy: defaults(initialState.fovy, Math.PI / 4.0),
  57. near: defaults(initialState.near, 0.01),
  58. far: defaults(initialState.far, 1000.0),
  59. noScroll: defaults(initialState.noScroll, false),
  60. flipY: defaults(initialState.flipY, false),
  61. dtheta: 0,
  62. dphi: 0,
  63. rotationSpeed: defaults(initialState.rotationSpeed, 1),
  64. zoomSpeed: defaults(initialState.zoomSpeed, 1),
  65. renderOnDirty: defaults(initialState.renderOnDirty, false),
  66. damping: defaults(initialState.damping, 0.9),
  67. minDistance: Math.log(defaults(initialState.minDistance, 0.1)),
  68. maxDistance: Math.log(defaults(initialState.maxDistance, 1000))
  69. }
  70. const view = Mat4.identity()
  71. const projection = Mat4.identity()
  72. const right = Vec3.create(1, 0, 0)
  73. const front = Vec3.create(0, 0, 1)
  74. let dirty = true
  75. let ddistance = 0
  76. let prevX = 0
  77. let prevY = 0
  78. if (isBrowser) {
  79. const source = element || regl._gl.canvas
  80. const getWidth = function () {
  81. return element ? element.offsetWidth : window.innerWidth
  82. }
  83. const getHeight = function () {
  84. return element ? element.offsetHeight : window.innerHeight
  85. }
  86. mouseChange(source, function (buttons: number, x: number, y: number, mods: MouseModifiers) {
  87. if (buttons & 1) {
  88. const dx = (x - prevX) / getWidth()
  89. const dy = (y - prevY) / getHeight()
  90. state.dtheta += state.rotationSpeed * 4.0 * dx
  91. state.dphi += state.rotationSpeed * 4.0 * dy
  92. dirty = true;
  93. }
  94. prevX = x
  95. prevY = y
  96. })
  97. mouseWheel(source, function (dx: number, dy: number) {
  98. ddistance += dy / getHeight() * state.zoomSpeed
  99. dirty = true;
  100. }, state.noScroll)
  101. }
  102. function dampAndMarkDirty (x: number) {
  103. const xd = damp(x, state.damping)
  104. if (Math.abs(xd) < 0.1) return 0
  105. dirty = true;
  106. return xd
  107. }
  108. function setState (newState: Partial<CameraState> = {}) {
  109. Object.assign(state, newState)
  110. const { center, eye, up, dtheta, dphi } = state
  111. state.theta += dtheta
  112. state.phi = clamp(state.phi + dphi, -Math.PI / 2.0, Math.PI / 2.0)
  113. state.distance = clamp(state.distance + ddistance, state.minDistance, state.maxDistance)
  114. state.dtheta = dampAndMarkDirty(dtheta)
  115. state.dphi = dampAndMarkDirty(dphi)
  116. ddistance = dampAndMarkDirty(ddistance)
  117. const theta = state.theta
  118. const phi = state.phi
  119. const r = Math.exp(state.distance)
  120. const vf = r * Math.sin(theta) * Math.cos(phi)
  121. const vr = r * Math.cos(theta) * Math.cos(phi)
  122. const vu = r * Math.sin(phi)
  123. for (let i = 0; i < 3; ++i) {
  124. eye[i] = center[i] + vf * front[i] + vr * right[i] + vu * up[i]
  125. }
  126. Mat4.lookAt(view, eye, center, up)
  127. }
  128. const injectContext = regl({
  129. context: {
  130. view: () => view,
  131. dirty: () => dirty,
  132. projection: (context: REGL.DefaultContext) => {
  133. Mat4.perspective(
  134. projection,
  135. state.fovy,
  136. context.viewportWidth / context.viewportHeight,
  137. state.near,
  138. state.far
  139. )
  140. if (state.flipY) { projection[5] *= -1 }
  141. return projection
  142. }
  143. },
  144. uniforms: { // TODO
  145. view: regl.context('view' as any),
  146. projection: regl.context('projection' as any)
  147. }
  148. })
  149. function update (props: any, block: any) {
  150. setState()
  151. injectContext(props, block)
  152. dirty = false
  153. }
  154. return {
  155. update,
  156. setState,
  157. getState: () => Object.assign({}, state),
  158. get dirty() { return dirty },
  159. set dirty(value: boolean) { dirty = value }
  160. }
  161. }
  162. }