viewer.ts 8.2 KB

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