viewer.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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. import { BehaviorSubject } from 'rxjs';
  7. import { Vec3, Mat4, EPSILON, Vec4 } from 'mol-math/linear-algebra'
  8. import InputObserver from 'mol-util/input/input-observer'
  9. import * as SetUtils from 'mol-util/set'
  10. import Renderer, { RendererStats } from 'mol-gl/renderer'
  11. import { RenderObject } from 'mol-gl/render-object'
  12. import TrackballControls from './controls/trackball'
  13. import { Viewport } from './camera/util'
  14. import { PerspectiveCamera } from './camera/perspective'
  15. import { resizeCanvas } from './util';
  16. import { createContext } from 'mol-gl/webgl/context';
  17. import { Representation } from 'mol-geo/representation';
  18. import { createRenderTarget } from 'mol-gl/webgl/render-target';
  19. import Scene from 'mol-gl/scene';
  20. import { RenderVariant } from 'mol-gl/webgl/render-item';
  21. interface Viewer {
  22. center: (p: Vec3) => void
  23. hide: (repr: Representation<any>) => void
  24. show: (repr: Representation<any>) => void
  25. add: (repr: Representation<any>) => void
  26. remove: (repr: Representation<any>) => void
  27. update: () => void
  28. clear: () => void
  29. draw: (force?: boolean) => void
  30. requestDraw: () => void
  31. animate: () => void
  32. pick: () => void
  33. identify: (x: number, y: number) => void
  34. reprCount: BehaviorSubject<number>
  35. didDraw: BehaviorSubject<number>
  36. handleResize: () => void
  37. resetCamera: () => void
  38. downloadScreenshot: () => void
  39. getImageData: (variant: RenderVariant) => ImageData
  40. input: InputObserver
  41. stats: RendererStats
  42. dispose: () => void
  43. }
  44. function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes) {
  45. function getContext(contextId: 'webgl' | 'experimental-webgl') {
  46. try {
  47. return canvas.getContext(contextId, contextAttributes)
  48. } catch (e) {
  49. return null
  50. }
  51. }
  52. return getContext('webgl') || getContext('experimental-webgl')
  53. }
  54. namespace Viewer {
  55. export function create(canvas: HTMLCanvasElement, container: Element): Viewer {
  56. const reprMap = new Map<Representation<any>, Set<RenderObject>>()
  57. const reprCount = new BehaviorSubject(0)
  58. const startTime = performance.now()
  59. const didDraw = new BehaviorSubject(0)
  60. const input = InputObserver.create(canvas)
  61. input.resize.subscribe(handleResize)
  62. input.move.subscribe(({x, y}) => identify(x, y))
  63. const camera = PerspectiveCamera.create({
  64. near: 0.1,
  65. far: 10000,
  66. position: Vec3.create(0, 0, 50)
  67. })
  68. const controls = TrackballControls.create(input, camera, {
  69. })
  70. const gl = getWebGLContext(canvas, {
  71. alpha: false,
  72. antialias: true,
  73. depth: true,
  74. preserveDrawingBuffer: true
  75. })
  76. if (gl === null) {
  77. throw new Error('Could not create a WebGL rendering context')
  78. }
  79. const ctx = createContext(gl)
  80. const scene = Scene.create(ctx)
  81. const renderer = Renderer.create(ctx, camera)
  82. const pickScale = 1 // 1 / 4
  83. const pickWidth = Math.round(canvas.width * pickScale)
  84. const pickHeight = Math.round(canvas.height * pickScale)
  85. const objectPickTarget = createRenderTarget(ctx, pickWidth, pickHeight)
  86. const instancePickTarget = createRenderTarget(ctx, pickWidth, pickHeight)
  87. const elementPickTarget = createRenderTarget(ctx, pickWidth, pickHeight)
  88. let drawPending = false
  89. const prevProjectionView = Mat4.zero()
  90. function render(variant: RenderVariant, force?: boolean) {
  91. let didRender = false
  92. controls.update()
  93. camera.update()
  94. if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value)) {
  95. Mat4.copy(prevProjectionView, camera.projectionView)
  96. renderer.render(scene, variant)
  97. didRender = true
  98. }
  99. return didRender
  100. }
  101. function draw(force?: boolean) {
  102. ctx.unbindFramebuffer()
  103. const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height }
  104. renderer.setViewport(viewport)
  105. if (render('draw', force)) {
  106. didDraw.next(performance.now() - startTime)
  107. }
  108. drawPending = false
  109. }
  110. function requestDraw () {
  111. if (drawPending) return
  112. drawPending = true
  113. window.requestAnimationFrame(() => draw(true))
  114. }
  115. function animate () {
  116. draw(false)
  117. window.requestAnimationFrame(() => animate())
  118. }
  119. const decodeFactors = Vec4.create(1, 1/255, 1/65025, 1/16581375)
  120. function decodeFloatRGBA(rgba: Vec4) {
  121. return Vec4.dot(rgba, decodeFactors);
  122. }
  123. function identify (x: number, y: number) {
  124. y = canvas.height - y // flip y
  125. const xp = Math.round(x * pickScale)
  126. const yp = Math.round(y * pickScale)
  127. console.log('position', x, y, xp, yp)
  128. const buffer = new Uint8Array(4)
  129. elementPickTarget.bind()
  130. ctx.readPixels(xp, yp, 1, 1, buffer)
  131. console.log('identify', buffer[0], buffer[1], buffer[2], buffer[3])
  132. const v = Vec4.create(buffer[0], buffer[1], buffer[2], buffer[3])
  133. const d = decodeFloatRGBA(v)
  134. console.log(d)
  135. console.log(d * 16777216)
  136. ctx.unbindFramebuffer()
  137. ctx.readPixels(x, y, 1, 1, buffer)
  138. console.log('color', buffer[0], buffer[1], buffer[2], buffer[3])
  139. }
  140. handleResize()
  141. return {
  142. center: (p: Vec3) => {
  143. Vec3.set(controls.target, p[0], p[1], p[2])
  144. },
  145. hide: (repr: Representation<any>) => {
  146. const renderObjectSet = reprMap.get(repr)
  147. if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = false)
  148. },
  149. show: (repr: Representation<any>) => {
  150. const renderObjectSet = reprMap.get(repr)
  151. if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = true)
  152. },
  153. add: (repr: Representation<any>) => {
  154. const oldRO = reprMap.get(repr)
  155. const newRO = new Set<RenderObject>()
  156. repr.renderObjects.forEach(o => newRO.add(o))
  157. if (oldRO) {
  158. SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o))
  159. SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o))
  160. scene.update()
  161. } else {
  162. repr.renderObjects.forEach(o => scene.add(o))
  163. }
  164. reprMap.set(repr, newRO)
  165. reprCount.next(reprMap.size)
  166. },
  167. remove: (repr: Representation<any>) => {
  168. const renderObjectSet = reprMap.get(repr)
  169. if (renderObjectSet) {
  170. renderObjectSet.forEach(o => scene.remove(o))
  171. reprMap.delete(repr)
  172. reprCount.next(reprMap.size)
  173. }
  174. },
  175. update: () => scene.update(),
  176. clear: () => {
  177. reprMap.clear()
  178. scene.clear()
  179. },
  180. draw,
  181. requestDraw,
  182. animate,
  183. pick: () => {
  184. objectPickTarget.bind()
  185. render('pickObject', true)
  186. instancePickTarget.bind()
  187. render('pickInstance', true)
  188. elementPickTarget.bind()
  189. render('pickElement', true)
  190. },
  191. identify,
  192. handleResize,
  193. resetCamera: () => {
  194. // TODO
  195. },
  196. downloadScreenshot: () => {
  197. // TODO
  198. },
  199. getImageData: (variant: RenderVariant) => {
  200. switch (variant) {
  201. case 'draw': return renderer.getImageData()
  202. case 'pickObject': return objectPickTarget.getImageData()
  203. case 'pickInstance': return instancePickTarget.getImageData()
  204. case 'pickElement': return elementPickTarget.getImageData()
  205. }
  206. },
  207. reprCount,
  208. didDraw,
  209. get input() {
  210. return input
  211. },
  212. get stats() {
  213. return renderer.stats
  214. },
  215. dispose: () => {
  216. scene.clear()
  217. input.dispose()
  218. controls.dispose()
  219. renderer.dispose()
  220. }
  221. }
  222. function handleResize() {
  223. resizeCanvas(canvas, container)
  224. const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height }
  225. renderer.setViewport(viewport)
  226. Viewport.copy(camera.viewport, viewport)
  227. Viewport.copy(controls.viewport, viewport)
  228. const pickWidth = Math.round(canvas.width * pickScale)
  229. const pickHeight = Math.round(canvas.height * pickScale)
  230. objectPickTarget.setSize(pickWidth, pickHeight)
  231. instancePickTarget.setSize(pickWidth, pickHeight)
  232. elementPickTarget.setSize(pickWidth, pickHeight)
  233. }
  234. }
  235. }
  236. export default Viewer