viewer.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { viewerManager } from './viewerManager';
  2. import { TemplateManager } from './../templateManager';
  3. import configurationLoader from './../configuration/loader';
  4. import { Observable, Engine, Scene, ArcRotateCamera, Vector3, SceneLoader, AbstractMesh, Mesh, HemisphericLight, Database } from 'babylonjs';
  5. import { ViewerConfiguration } from '../configuration/configuration';
  6. import { PromiseObservable } from '../util/promiseObservable';
  7. export abstract class AbstractViewer {
  8. public templateManager: TemplateManager;
  9. public engine: Engine;
  10. public scene: Scene;
  11. public baseId: string;
  12. protected configuration: ViewerConfiguration;
  13. // observables
  14. public onSceneInitObservable: PromiseObservable<Scene>;
  15. public onEngineInitObservable: PromiseObservable<Engine>;
  16. public onModelLoadedObservable: PromiseObservable<AbstractMesh[]>;
  17. private canvas: HTMLCanvasElement;
  18. constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = {}) {
  19. // if exists, use the container id. otherwise, generate a random string.
  20. if (containerElement.id) {
  21. this.baseId = containerElement.id;
  22. } else {
  23. this.baseId = containerElement.id = 'bjs' + Math.random().toString(32).substr(2, 8);
  24. }
  25. this.onSceneInitObservable = new PromiseObservable();
  26. this.onEngineInitObservable = new PromiseObservable();
  27. this.onModelLoadedObservable = new PromiseObservable();
  28. // add this viewer to the viewer manager
  29. viewerManager.addViewer(this);
  30. // create a new template manager. TODO - singleton?
  31. this.templateManager = new TemplateManager(containerElement);
  32. this.prepareContainerElement();
  33. // extend the configuration
  34. configurationLoader.loadConfiguration(initialConfiguration).then((configuration) => {
  35. this.configuration = configuration;
  36. // adding preconfigured functions
  37. if (this.configuration.observers) {
  38. if (this.configuration.observers.onEngineInit) {
  39. this.onEngineInitObservable.add(window[this.configuration.observers.onEngineInit]);
  40. }
  41. if (this.configuration.observers.onSceneInit) {
  42. this.onSceneInitObservable.add(window[this.configuration.observers.onSceneInit]);
  43. }
  44. if (this.configuration.observers.onModelLoaded) {
  45. this.onModelLoadedObservable.add(window[this.configuration.observers.onModelLoaded]);
  46. }
  47. }
  48. // initialize the templates
  49. let templateConfiguration = this.configuration.templates || {};
  50. this.templateManager.initTemplate(templateConfiguration);
  51. // when done, execute onTemplatesLoaded()
  52. this.templateManager.onAllLoaded.add(() => {
  53. let canvas = this.templateManager.getCanvas();
  54. if (canvas) {
  55. this.canvas = canvas;
  56. }
  57. this.onTemplatesLoaded();
  58. });
  59. });
  60. }
  61. public getBaseId(): string {
  62. return this.baseId;
  63. }
  64. public isCanvasInDOM(): boolean {
  65. return !!this.canvas && !!this.canvas.parentElement;
  66. }
  67. protected resize = (): void => {
  68. // Only resize if Canvas is in the DOM
  69. if (!this.isCanvasInDOM()) {
  70. return;
  71. }
  72. if (this.canvas.clientWidth <= 0 || this.canvas.clientHeight <= 0) {
  73. return;
  74. }
  75. this.engine.resize();
  76. }
  77. protected render = (): void => {
  78. this.scene && this.scene.render();
  79. }
  80. public dispose() {
  81. window.removeEventListener('resize', this.resize);
  82. }
  83. protected abstract prepareContainerElement();
  84. /**
  85. * This function will execute when the HTML templates finished initializing.
  86. * It should initialize the engine and continue execution.
  87. *
  88. * @protected
  89. * @returns {Promise<AbstractViewer>} The viewer object will be returned after the object was loaded.
  90. * @memberof AbstractViewer
  91. */
  92. protected onTemplatesLoaded(): Promise<AbstractViewer> {
  93. let autoLoadModel = !!this.configuration.model;
  94. return this.initEngine().then(() => {
  95. if (autoLoadModel) {
  96. return this.loadModel();
  97. } else {
  98. return this.scene || this.initScene();
  99. }
  100. }).then(() => {
  101. return this;
  102. });
  103. }
  104. /**
  105. * Initialize the engine. Retruns a promise in case async calls are needed.
  106. *
  107. * @protected
  108. * @returns {Promise<Engine>}
  109. * @memberof Viewer
  110. */
  111. protected initEngine(): Promise<Engine> {
  112. let canvasElement = this.templateManager.getCanvas();
  113. if (!canvasElement) {
  114. return Promise.reject('Canvas element not found!');
  115. }
  116. let config = this.configuration.engine || {};
  117. // TDO enable further configuration
  118. this.engine = new Engine(canvasElement, !!config.antialiasing, config.engineOptions);
  119. // Disable manifest checking
  120. Database.IDBStorageEnabled = false;
  121. if (!config.disableResize) {
  122. window.addEventListener('resize', this.resize);
  123. }
  124. this.engine.runRenderLoop(this.render);
  125. if (this.configuration.engine && this.configuration.engine.adaptiveQuality) {
  126. var scale = Math.max(0.5, 1 / (window.devicePixelRatio || 2));
  127. this.engine.setHardwareScalingLevel(scale);
  128. }
  129. return this.onEngineInitObservable.notifyWithPromise(this.engine).then(() => {
  130. return this.engine;
  131. });
  132. }
  133. protected initScene(): Promise<Scene> {
  134. // if the scen exists, dispose it.
  135. if (this.scene) {
  136. this.scene.dispose();
  137. }
  138. // create a new scene
  139. this.scene = new Scene(this.engine);
  140. // make sure there is a default camera and light.
  141. this.scene.createDefaultCameraOrLight(true, true, true);
  142. if (this.configuration.scene && this.configuration.scene.debug) {
  143. this.scene.debugLayer.show();
  144. }
  145. return this.onSceneInitObservable.notifyWithPromise(this.scene).then(() => {
  146. return this.scene!;
  147. });
  148. }
  149. public loadModel(model: any = this.configuration.model, clearScene: boolean = true): Promise<Scene> {
  150. this.configuration.model = model;
  151. let modelUrl = (typeof model === 'string') ? model : model.url;
  152. let parts = modelUrl.split('/');
  153. let filename = parts.pop();
  154. let base = parts.join('/') + '/';
  155. let plugin = (typeof model === 'string') ? undefined : model.loader;
  156. return Promise.resolve(this.scene).then((scene) => {
  157. if (!scene || clearScene) return this.initScene();
  158. else return this.scene!;
  159. }).then(() => {
  160. return new Promise<Array<AbstractMesh>>((resolve, reject) => {
  161. SceneLoader.ImportMesh(undefined, base, filename, this.scene, (meshes) => {
  162. resolve(meshes);
  163. }, undefined, (e, m, exception) => {
  164. console.log(m, exception);
  165. reject(m);
  166. }, plugin);
  167. });
  168. }).then((meshes: Array<AbstractMesh>) => {
  169. return this.onModelLoadedObservable.notifyWithPromise(meshes).then(() => {
  170. return this.scene;
  171. });
  172. });
  173. }
  174. }