renderer.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 REGL = require('regl');
  7. import * as glContext from './context'
  8. import { PerspectiveCamera } from './camera/perspective'
  9. import { PointRenderable, MeshRenderable, Renderable } from './renderable'
  10. import Stats from './stats'
  11. import { Vec3, Mat4 } from 'mol-math/linear-algebra'
  12. import { ValueCell } from 'mol-util';
  13. import { isNull } from 'util';
  14. import OrbitControls from './controls/orbit';
  15. let _renderObjectId = 0;
  16. function getNextId() {
  17. return _renderObjectId++ % 0x7FFFFFFF;
  18. }
  19. export interface RenderUpdateInfo {
  20. }
  21. export type RenderData = { [k: string]: ValueCell<Helpers.TypedArray> }
  22. export interface RenderObject {
  23. id: number
  24. type: 'mesh' | 'point'
  25. data: PointRenderable.Data | MeshRenderable.Data
  26. uniforms: { [k: string]: REGL.Uniform }
  27. }
  28. export function createRenderObject(type: 'mesh' | 'point', data: PointRenderable.Data | MeshRenderable.Data, uniforms: { [k: string]: REGL.Uniform }) {
  29. return { id: getNextId(), type, data, uniforms }
  30. }
  31. export function createRenderable(regl: REGL.Regl, o: RenderObject) {
  32. switch (o.type) {
  33. case 'mesh': return MeshRenderable.create(regl, o.data as MeshRenderable.Data, o.uniforms || {})
  34. case 'point': return PointRenderable.create(regl, o.data as PointRenderable.Data)
  35. }
  36. }
  37. interface Renderer {
  38. camera: PerspectiveCamera
  39. controls: any // OrbitControls
  40. add: (o: RenderObject) => void
  41. remove: (o: RenderObject) => void
  42. clear: () => void
  43. draw: () => void
  44. frame: () => void
  45. }
  46. function resizeCanvas (canvas: HTMLCanvasElement, element: HTMLElement) {
  47. let w = window.innerWidth
  48. let h = window.innerHeight
  49. if (element !== document.body) {
  50. let bounds = element.getBoundingClientRect()
  51. w = bounds.right - bounds.left
  52. h = bounds.bottom - bounds.top
  53. }
  54. canvas.width = window.devicePixelRatio * w
  55. canvas.height = window.devicePixelRatio * h
  56. Object.assign(canvas.style, { width: w + 'px', height: h + 'px' })
  57. }
  58. namespace Renderer {
  59. export function fromElement(element: HTMLElement, contexAttributes?: WebGLContextAttributes) {
  60. const canvas = document.createElement('canvas')
  61. Object.assign(canvas.style, { border: 0, margin: 0, padding: 0, top: 0, left: 0 })
  62. element.appendChild(canvas)
  63. if (element === document.body) {
  64. canvas.style.position = 'absolute'
  65. Object.assign(element.style, { margin: 0, padding: 0 })
  66. }
  67. function resize () {
  68. resizeCanvas(canvas, element)
  69. }
  70. window.addEventListener('resize', resize, false)
  71. // function onDestroy () {
  72. // window.removeEventListener('resize', resize)
  73. // element.removeChild(canvas)
  74. // }
  75. resize()
  76. return fromCanvas(canvas, contexAttributes)
  77. }
  78. export function fromCanvas(canvas: HTMLCanvasElement, contexAttributes?: WebGLContextAttributes) {
  79. function get (name: 'webgl' | 'experimental-webgl') {
  80. try {
  81. return canvas.getContext(name, contexAttributes)
  82. } catch (e) {
  83. return null
  84. }
  85. }
  86. const gl = get('webgl') || get('experimental-webgl')
  87. if (isNull(gl)) throw new Error('unable to create webgl context')
  88. return create(gl, canvas)
  89. }
  90. export function create(gl: WebGLRenderingContext, element: Element): Renderer {
  91. const renderableList: Renderable[] = []
  92. const objectIdRenderableMap: { [k: number]: Renderable } = {}
  93. const camera = PerspectiveCamera.create({
  94. near: 0.01,
  95. far: 1000,
  96. position: Vec3.create(0, 0, 50)
  97. })
  98. const controls = OrbitControls.create(element, {
  99. position: Vec3.create(0, 0, 50)
  100. })
  101. const extensions = [
  102. 'OES_texture_float',
  103. 'OES_texture_float_linear',
  104. 'OES_element_index_uint',
  105. 'EXT_blend_minmax',
  106. 'ANGLE_instanced_arrays'
  107. ]
  108. if (gl.getExtension('EXT_disjoint_timer_query') !== null) {
  109. extensions.push('EXT_disjoint_timer_query')
  110. }
  111. const regl = glContext.create({ gl, extensions, profile: true })
  112. const baseContext = regl({
  113. context: {
  114. model: Mat4.identity(),
  115. transform: Mat4.identity(),
  116. view: camera.view,
  117. projection: camera.projection
  118. },
  119. uniforms: {
  120. model: regl.context('model' as any),
  121. transform: regl.context('transform' as any),
  122. view: regl.context('view' as any),
  123. projection: regl.context('projection' as any),
  124. 'light.position': Vec3.create(0, 0, -100),
  125. 'light.color': Vec3.create(1.0, 1.0, 1.0),
  126. 'light.ambient': Vec3.create(0.5, 0.5, 0.5),
  127. 'light.falloff': 0,
  128. 'light.radius': 500
  129. }
  130. })
  131. const stats = Stats([])
  132. let prevTime = regl.now()
  133. const draw = () => {
  134. controls.update()
  135. controls.copyInto(camera.position, camera.direction, camera.up)
  136. camera.update()
  137. baseContext(state => {
  138. regl.clear({ color: [0, 0, 0, 1] })
  139. // TODO painters sort, filter visible, filter picking, visibility culling?
  140. renderableList.forEach(r => {
  141. r.draw()
  142. })
  143. stats.update(state.time - prevTime)
  144. prevTime = state.time
  145. })
  146. }
  147. // TODO animate, draw, requestDraw
  148. return {
  149. camera,
  150. controls,
  151. add: (o: RenderObject) => {
  152. const renderable = createRenderable(regl, o)
  153. renderableList.push(renderable)
  154. objectIdRenderableMap[o.id] = renderable
  155. stats.add(renderable)
  156. draw()
  157. },
  158. remove: (o: RenderObject) => {
  159. if (o.id in objectIdRenderableMap) {
  160. // TODO
  161. // objectIdRenderableMap[o.id].destroy()
  162. delete objectIdRenderableMap[o.id]
  163. draw()
  164. }
  165. },
  166. clear: () => {
  167. for (const id in objectIdRenderableMap) {
  168. // TODO
  169. // objectIdRenderableMap[id].destroy()
  170. delete objectIdRenderableMap[id]
  171. }
  172. renderableList.length = 0
  173. draw()
  174. },
  175. draw,
  176. frame: () => {
  177. regl.frame((ctx) => draw())
  178. }
  179. }
  180. }
  181. }
  182. export default Renderer