scene.ts 7.0 KB

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