scene.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. import { hash1 } from '../mol-data/util';
  18. const boundaryHelper = new BoundaryHelper('98')
  19. function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D, onlyVisible: boolean): Sphere3D {
  20. boundaryHelper.reset();
  21. for (let i = 0, il = renderables.length; i < il; ++i) {
  22. if (onlyVisible && !renderables[i].state.visible) continue;
  23. const boundingSphere = renderables[i].values.boundingSphere.ref.value
  24. if (!boundingSphere.radius) continue;
  25. if (Sphere3D.hasExtrema(boundingSphere)) {
  26. for (const e of boundingSphere.extrema) {
  27. boundaryHelper.includeStep(e)
  28. }
  29. } else {
  30. boundaryHelper.includeSphereStep(boundingSphere.center, boundingSphere.radius);
  31. }
  32. }
  33. boundaryHelper.finishedIncludeStep();
  34. for (let i = 0, il = renderables.length; i < il; ++i) {
  35. if (onlyVisible && !renderables[i].state.visible) continue;
  36. const boundingSphere = renderables[i].values.boundingSphere.ref.value
  37. if (!boundingSphere.radius) continue;
  38. if (Sphere3D.hasExtrema(boundingSphere)) {
  39. for (const e of boundingSphere.extrema) {
  40. boundaryHelper.radiusStep(e)
  41. }
  42. } else {
  43. boundaryHelper.radiusSphereStep(boundingSphere.center, boundingSphere.radius);
  44. }
  45. }
  46. return boundaryHelper.getSphere(boundingSphere);
  47. }
  48. function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Renderable<RenderableValues & BaseValues>) {
  49. const drawProgramIdA = a.getProgram('color').id
  50. const drawProgramIdB = b.getProgram('color').id
  51. const materialIdA = a.materialId
  52. const materialIdB = b.materialId
  53. if (drawProgramIdA !== drawProgramIdB) {
  54. return drawProgramIdA - drawProgramIdB // sort by program id to minimize gl state changes
  55. } else if (materialIdA !== materialIdB) {
  56. return materialIdA - materialIdB // sort by material id to minimize gl state changes
  57. } else {
  58. return a.id - b.id;
  59. }
  60. }
  61. interface Scene extends Object3D {
  62. readonly count: number
  63. readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
  64. readonly boundingSphere: Sphere3D
  65. readonly boundingSphereVisible: Sphere3D
  66. /** Returns `true` if some visibility has changed, `false` otherwise. */
  67. syncVisibility: () => boolean
  68. update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean, isRemoving?: boolean) => void
  69. add: (o: GraphicsRenderObject) => void // Renderable<any>
  70. remove: (o: GraphicsRenderObject) => void
  71. commit: (maxTimeMs?: number) => boolean
  72. readonly needsCommit: boolean
  73. has: (o: GraphicsRenderObject) => boolean
  74. clear: () => void
  75. forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
  76. }
  77. namespace Scene {
  78. export function create(ctx: WebGLContext): Scene {
  79. const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
  80. const renderables: Renderable<RenderableValues & BaseValues>[] = []
  81. const boundingSphere = Sphere3D()
  82. const boundingSphereVisible = Sphere3D()
  83. let boundingSphereDirty = true
  84. let boundingSphereVisibleDirty = true
  85. const object3d = Object3D.create()
  86. function add(o: GraphicsRenderObject) {
  87. if (!renderableMap.has(o)) {
  88. const renderable = createRenderable(ctx, o)
  89. renderables.push(renderable)
  90. renderableMap.set(o, renderable)
  91. boundingSphereDirty = true
  92. boundingSphereVisibleDirty = true
  93. return renderable;
  94. } else {
  95. console.warn(`RenderObject with id '${o.id}' already present`)
  96. return renderableMap.get(o)!
  97. }
  98. }
  99. function remove(o: GraphicsRenderObject) {
  100. const renderable = renderableMap.get(o)
  101. if (renderable) {
  102. renderable.dispose()
  103. arraySetRemove(renderables, renderable);
  104. renderableMap.delete(o)
  105. boundingSphereDirty = true
  106. boundingSphereVisibleDirty = true
  107. }
  108. }
  109. const commitBulkSize = 100;
  110. function commit(maxTimeMs: number) {
  111. const start = now();
  112. let i = 0;
  113. while (true) {
  114. const o = commitQueue.tryGetRemove();
  115. if (!o) break;
  116. remove(o);
  117. if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false;
  118. }
  119. while (true) {
  120. const o = commitQueue.tryGetAdd();
  121. if (!o) break;
  122. add(o);
  123. if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false;
  124. }
  125. renderables.sort(renderableSort)
  126. return true;
  127. }
  128. const commitQueue = new CommitQueue();
  129. let visibleHash = -1
  130. function computeVisibleHash() {
  131. let hash = 23
  132. for (let i = 0, il = renderables.length; i < il; ++i) {
  133. if (!renderables[i].state.visible) continue;
  134. hash = (31 * hash + renderables[i].id) | 0;
  135. }
  136. hash = hash1(hash);
  137. if (hash === -1) hash = 0;
  138. return hash
  139. }
  140. function syncVisibility() {
  141. const newVisibleHash = computeVisibleHash()
  142. if (newVisibleHash !== visibleHash) {
  143. boundingSphereVisibleDirty = true
  144. return true
  145. } else {
  146. return false
  147. }
  148. }
  149. return {
  150. get view () { return object3d.view },
  151. get position () { return object3d.position },
  152. get direction () { return object3d.direction },
  153. get up () { return object3d.up },
  154. syncVisibility,
  155. update(objects, keepBoundingSphere, isRemoving) {
  156. Object3D.update(object3d)
  157. if (objects) {
  158. for (let i = 0, il = objects.length; i < il; ++i) {
  159. const o = renderableMap.get(objects[i]);
  160. if (!o) continue;
  161. o.update();
  162. }
  163. } else if (!isRemoving) {
  164. for (let i = 0, il = renderables.length; i < il; ++i) {
  165. renderables[i].update()
  166. }
  167. }
  168. if (!keepBoundingSphere) {
  169. boundingSphereDirty = true
  170. boundingSphereVisibleDirty = true
  171. } else {
  172. syncVisibility()
  173. }
  174. },
  175. add: (o: GraphicsRenderObject) => commitQueue.add(o),
  176. remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
  177. commit: (maxTime = Number.MAX_VALUE) => commit(maxTime),
  178. get needsCommit() { return !commitQueue.isEmpty; },
  179. has: (o: GraphicsRenderObject) => {
  180. return renderableMap.has(o)
  181. },
  182. clear: () => {
  183. for (let i = 0, il = renderables.length; i < il; ++i) {
  184. renderables[i].dispose()
  185. }
  186. renderables.length = 0
  187. renderableMap.clear()
  188. boundingSphereDirty = true
  189. boundingSphereVisibleDirty = true
  190. },
  191. forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => {
  192. renderableMap.forEach(callbackFn)
  193. },
  194. get count() {
  195. return renderables.length
  196. },
  197. renderables,
  198. get boundingSphere() {
  199. if (boundingSphereDirty) {
  200. calculateBoundingSphere(renderables, boundingSphere, false)
  201. boundingSphereDirty = false
  202. }
  203. return boundingSphere
  204. },
  205. get boundingSphereVisible() {
  206. if (boundingSphereVisibleDirty) {
  207. calculateBoundingSphere(renderables, boundingSphereVisible, true)
  208. boundingSphereVisibleDirty = false
  209. visibleHash = computeVisibleHash()
  210. }
  211. return boundingSphereVisible
  212. }
  213. }
  214. }
  215. }
  216. export default Scene