viewer.ts 8.9 KB

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