scene.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Renderable } from './renderable'
  7. import { WebGLContext } from './webgl/context';
  8. import { RenderableValues, BaseValues } from './renderable/schema';
  9. import { GraphicsRenderObject, createRenderable } from './render-object';
  10. import { Object3D } from './object3d';
  11. import { Sphere3D } from '../mol-math/geometry';
  12. import { Vec3 } from '../mol-math/linear-algebra';
  13. import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
  14. import { RuntimeContext, Task } from '../mol-task';
  15. import { AsyncQueue } from '../mol-util/async-queue';
  16. const boundaryHelper = new BoundaryHelper();
  17. function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
  18. boundaryHelper.reset(0.1);
  19. for (let i = 0, il = renderables.length; i < il; ++i) {
  20. const boundingSphere = renderables[i].values.boundingSphere.ref.value
  21. if (!boundingSphere.radius) continue;
  22. boundaryHelper.boundaryStep(boundingSphere.center, boundingSphere.radius);
  23. }
  24. boundaryHelper.finishBoundaryStep();
  25. for (let i = 0, il = renderables.length; i < il; ++i) {
  26. const boundingSphere = renderables[i].values.boundingSphere.ref.value
  27. if (!boundingSphere.radius) continue;
  28. boundaryHelper.extendStep(boundingSphere.center, boundingSphere.radius);
  29. }
  30. Vec3.copy(boundingSphere.center, boundaryHelper.center);
  31. boundingSphere.radius = boundaryHelper.radius;
  32. return boundingSphere;
  33. }
  34. function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Renderable<RenderableValues & BaseValues>) {
  35. const drawProgramIdA = a.getProgram('color').id
  36. const drawProgramIdB = b.getProgram('color').id
  37. const materialIdA = a.materialId
  38. const materialIdB = b.materialId
  39. if (drawProgramIdA !== drawProgramIdB) {
  40. return drawProgramIdA - drawProgramIdB // sort by program id to minimize gl state changes
  41. } else if (materialIdA !== materialIdB) {
  42. return materialIdA - materialIdB // sort by material id to minimize gl state changes
  43. } else {
  44. return a.id - b.id;
  45. }
  46. }
  47. interface Scene extends Object3D {
  48. readonly count: number
  49. readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
  50. readonly boundingSphere: Sphere3D
  51. readonly isCommiting: boolean
  52. update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void
  53. add: (o: GraphicsRenderObject) => void // Renderable<any>
  54. remove: (o: GraphicsRenderObject) => void
  55. syncCommit: () => void
  56. commit: () => Task<void>
  57. has: (o: GraphicsRenderObject) => boolean
  58. clear: () => void
  59. forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
  60. }
  61. namespace Scene {
  62. export function create(ctx: WebGLContext): Scene {
  63. const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
  64. const renderables: Renderable<RenderableValues & BaseValues>[] = []
  65. const boundingSphere = Sphere3D.zero()
  66. let boundingSphereDirty = true
  67. const object3d = Object3D.create()
  68. const add = (o: GraphicsRenderObject) => {
  69. if (!renderableMap.has(o)) {
  70. const renderable = createRenderable(ctx, o)
  71. renderables.push(renderable)
  72. renderableMap.set(o, renderable)
  73. boundingSphereDirty = true
  74. return renderable;
  75. } else {
  76. console.warn(`RenderObject with id '${o.id}' already present`)
  77. return renderableMap.get(o)!
  78. }
  79. }
  80. const remove = (o: GraphicsRenderObject) => {
  81. const renderable = renderableMap.get(o)
  82. if (renderable) {
  83. renderable.dispose()
  84. renderables.splice(renderables.indexOf(renderable), 1)
  85. renderableMap.delete(o)
  86. boundingSphereDirty = true
  87. }
  88. }
  89. const commitQueue = new AsyncQueue<any>();
  90. const toAdd: GraphicsRenderObject[] = []
  91. const toRemove: GraphicsRenderObject[] = []
  92. type CommitParams = { toAdd: GraphicsRenderObject[], toRemove: GraphicsRenderObject[] }
  93. const step = 100
  94. const handle = async (ctx: RuntimeContext, arr: GraphicsRenderObject[], fn: (o: GraphicsRenderObject) => void, message: string) => {
  95. for (let i = 0, il = arr.length; i < il; i += step) {
  96. if (ctx.shouldUpdate) await ctx.update({ message, current: i, max: il })
  97. for (let j = i, jl = Math.min(i + step, il); j < jl; ++j) {
  98. fn(arr[j])
  99. }
  100. }
  101. }
  102. const commit = async (ctx: RuntimeContext, p: CommitParams) => {
  103. await handle(ctx, p.toRemove, remove, 'Removing GraphicsRenderObjects')
  104. await handle(ctx, p.toAdd, add, 'Adding GraphicsRenderObjects')
  105. if (ctx.shouldUpdate) await ctx.update({ message: 'Sorting GraphicsRenderObjects' })
  106. renderables.sort(renderableSort)
  107. }
  108. return {
  109. get view () { return object3d.view },
  110. get position () { return object3d.position },
  111. get direction () { return object3d.direction },
  112. get up () { return object3d.up },
  113. get isCommiting () { return commitQueue.length > 0 },
  114. update(objects, keepBoundingSphere) {
  115. Object3D.update(object3d)
  116. if (objects) {
  117. for (let i = 0, il = objects.length; i < il; ++i) {
  118. const o = renderableMap.get(objects[i]);
  119. if (!o) continue;
  120. o.update();
  121. }
  122. } else {
  123. for (let i = 0, il = renderables.length; i < il; ++i) {
  124. renderables[i].update()
  125. }
  126. }
  127. if (!keepBoundingSphere) boundingSphereDirty = true
  128. },
  129. add: (o: GraphicsRenderObject) => {
  130. toAdd.push(o)
  131. },
  132. remove: (o: GraphicsRenderObject) => {
  133. toRemove.push(o)
  134. },
  135. syncCommit: () => {
  136. for (let i = 0, il = toRemove.length; i < il; ++i) remove(toRemove[i])
  137. toRemove.length = 0
  138. for (let i = 0, il = toAdd.length; i < il; ++i) add(toAdd[i])
  139. toAdd.length = 0
  140. renderables.sort(renderableSort)
  141. },
  142. commit: () => {
  143. const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] }
  144. toAdd.length = 0
  145. toRemove.length = 0
  146. return Task.create('Commiting GraphicsRenderObjects', async ctx => {
  147. const removed = await commitQueue.enqueue(params);
  148. if (!removed) return;
  149. try {
  150. await commit(ctx, params);
  151. } finally {
  152. commitQueue.handled(params);
  153. }
  154. }, () => {
  155. commitQueue.remove(params);
  156. })
  157. },
  158. has: (o: GraphicsRenderObject) => {
  159. return renderableMap.has(o)
  160. },
  161. clear: () => {
  162. for (let i = 0, il = renderables.length; i < il; ++i) {
  163. renderables[i].dispose()
  164. }
  165. renderables.length = 0
  166. renderableMap.clear()
  167. boundingSphereDirty = true
  168. },
  169. forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => {
  170. renderableMap.forEach(callbackFn)
  171. },
  172. get count() {
  173. return renderables.length
  174. },
  175. renderables,
  176. get boundingSphere() {
  177. if (boundingSphereDirty) calculateBoundingSphere(renderables, boundingSphere)
  178. boundingSphereDirty = false
  179. return boundingSphere
  180. }
  181. }
  182. }
  183. }
  184. export default Scene