scene.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import { Renderable } from './renderable'
  8. import { WebGLContext } from './webgl/context';
  9. import { RenderableValues, BaseValues } from './renderable/schema';
  10. import { GraphicsRenderObject, createRenderable } from './render-object';
  11. import { Object3D } from './object3d';
  12. import { Sphere3D } from '../mol-math/geometry';
  13. import { CommitQueue } from './commit-queue';
  14. import { now } from '../mol-util/now';
  15. import { arraySetRemove } from '../mol-util/array';
  16. import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
  17. const eposHelper = new BoundaryHelper('98')
  18. function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
  19. eposHelper.reset();
  20. for (let i = 0, il = renderables.length; i < il; ++i) {
  21. const boundingSphere = renderables[i].values.boundingSphere.ref.value
  22. if (!boundingSphere.radius) continue;
  23. for (const b of Sphere3D.getList(boundingSphere)) {
  24. eposHelper.includeSphereStep(b.center, b.radius);
  25. }
  26. }
  27. eposHelper.finishedIncludeStep();
  28. for (let i = 0, il = renderables.length; i < il; ++i) {
  29. const boundingSphere = renderables[i].values.boundingSphere.ref.value
  30. if (!boundingSphere.radius) continue;
  31. for (const b of Sphere3D.getList(boundingSphere)) {
  32. eposHelper.radiusSphereStep(b.center, b.radius);
  33. }
  34. }
  35. return eposHelper.getSphere(boundingSphere);
  36. }
  37. function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Renderable<RenderableValues & BaseValues>) {
  38. const drawProgramIdA = a.getProgram('color').id
  39. const drawProgramIdB = b.getProgram('color').id
  40. const materialIdA = a.materialId
  41. const materialIdB = b.materialId
  42. if (drawProgramIdA !== drawProgramIdB) {
  43. return drawProgramIdA - drawProgramIdB // sort by program id to minimize gl state changes
  44. } else if (materialIdA !== materialIdB) {
  45. return materialIdA - materialIdB // sort by material id to minimize gl state changes
  46. } else {
  47. return a.id - b.id;
  48. }
  49. }
  50. interface Scene extends Object3D {
  51. readonly count: number
  52. readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
  53. readonly boundingSphere: Sphere3D
  54. update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean, isRemoving?: boolean) => void
  55. add: (o: GraphicsRenderObject) => void // Renderable<any>
  56. remove: (o: GraphicsRenderObject) => void
  57. commit: (maxTimeMs?: number) => boolean
  58. readonly needsCommit: boolean
  59. has: (o: GraphicsRenderObject) => boolean
  60. clear: () => void
  61. forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
  62. }
  63. namespace Scene {
  64. export function create(ctx: WebGLContext): Scene {
  65. const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
  66. const renderables: Renderable<RenderableValues & BaseValues>[] = []
  67. const boundingSphere = Sphere3D.zero()
  68. let boundingSphereDirty = true
  69. const object3d = Object3D.create()
  70. function add(o: GraphicsRenderObject) {
  71. if (!renderableMap.has(o)) {
  72. const renderable = createRenderable(ctx, o)
  73. renderables.push(renderable)
  74. renderableMap.set(o, renderable)
  75. boundingSphereDirty = true
  76. return renderable;
  77. } else {
  78. console.warn(`RenderObject with id '${o.id}' already present`)
  79. return renderableMap.get(o)!
  80. }
  81. }
  82. function remove(o: GraphicsRenderObject) {
  83. const renderable = renderableMap.get(o)
  84. if (renderable) {
  85. renderable.dispose()
  86. arraySetRemove(renderables, renderable);
  87. renderableMap.delete(o)
  88. boundingSphereDirty = true
  89. }
  90. }
  91. const commitBulkSize = 100;
  92. function commit(maxTimeMs: number) {
  93. const start = now();
  94. let i = 0;
  95. while (true) {
  96. const o = commitQueue.tryGetRemove();
  97. if (!o) break;
  98. remove(o);
  99. if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false;
  100. }
  101. while (true) {
  102. const o = commitQueue.tryGetAdd();
  103. if (!o) break;
  104. add(o);
  105. if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false;
  106. }
  107. renderables.sort(renderableSort)
  108. return true;
  109. }
  110. const commitQueue = new CommitQueue();
  111. return {
  112. get view () { return object3d.view },
  113. get position () { return object3d.position },
  114. get direction () { return object3d.direction },
  115. get up () { return object3d.up },
  116. // get isCommiting () { return commitQueue.length > 0 },
  117. update(objects, keepBoundingSphere, isRemoving) {
  118. Object3D.update(object3d)
  119. if (objects) {
  120. for (let i = 0, il = objects.length; i < il; ++i) {
  121. const o = renderableMap.get(objects[i]);
  122. if (!o) continue;
  123. o.update();
  124. }
  125. } else if (!isRemoving) {
  126. for (let i = 0, il = renderables.length; i < il; ++i) {
  127. renderables[i].update()
  128. }
  129. }
  130. if (!keepBoundingSphere) boundingSphereDirty = true
  131. },
  132. add: (o: GraphicsRenderObject) => commitQueue.add(o),
  133. remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
  134. commit: (maxTime = Number.MAX_VALUE) => commit(maxTime),
  135. get needsCommit() { return !commitQueue.isEmpty; },
  136. has: (o: GraphicsRenderObject) => {
  137. return renderableMap.has(o)
  138. },
  139. clear: () => {
  140. for (let i = 0, il = renderables.length; i < il; ++i) {
  141. renderables[i].dispose()
  142. }
  143. renderables.length = 0
  144. renderableMap.clear()
  145. boundingSphereDirty = true
  146. },
  147. forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => {
  148. renderableMap.forEach(callbackFn)
  149. },
  150. get count() {
  151. return renderables.length
  152. },
  153. renderables,
  154. get boundingSphere() {
  155. if (boundingSphereDirty) calculateBoundingSphere(renderables, boundingSphere)
  156. boundingSphereDirty = false
  157. return boundingSphere
  158. }
  159. }
  160. }
  161. }
  162. export default Scene