camera.ts 6.1 KB

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