scene.ts 8.7 KB

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