viewer.ts 11 KB

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