viewer.ts 8.5 KB

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