modelLoader.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, SceneLoader, Tags, Tools } from 'babylonjs';
  2. import { GLTFFileLoader, GLTFLoaderAnimationStartMode } from 'babylonjs-loaders';
  3. import { ConfigurationContainer } from '../configuration/configurationContainer';
  4. import { IModelConfiguration } from '../configuration/interfaces/modelConfiguration';
  5. import { ObservablesManager } from '../managers/observablesManager';
  6. import { ModelState, ViewerModel } from '../model/viewerModel';
  7. import { getLoaderPluginByName, ILoaderPlugin } from './plugins/';
  8. /**
  9. * An instance of the class is in charge of loading the model correctly.
  10. * This class will continously be expended with tasks required from the specific loaders Babylon has.
  11. *
  12. * A Model loader is unique per (Abstract)Viewer. It is being generated by the viewer
  13. */
  14. export class ModelLoader {
  15. private _loadId: number;
  16. private _disposed = false;
  17. private _loaders: Array<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
  18. private _plugins: Array<ILoaderPlugin>;
  19. private _baseUrl: string;
  20. public get baseUrl(): string {
  21. return this._baseUrl;
  22. }
  23. /**
  24. * Create a new Model loader
  25. * @param _viewer the viewer using this model loader
  26. */
  27. constructor(private _observablesManager: ObservablesManager, private _configurationContainer?: ConfigurationContainer) {
  28. this._loaders = [];
  29. this._loadId = 0;
  30. this._plugins = [];
  31. }
  32. /**
  33. * Adds a new plugin to the loader process.
  34. *
  35. * @param plugin the plugin name or the plugin itself
  36. */
  37. public addPlugin(plugin: ILoaderPlugin | string) {
  38. let actualPlugin: ILoaderPlugin = {};
  39. if (typeof plugin === 'string') {
  40. let loadedPlugin = getLoaderPluginByName(plugin);
  41. if (loadedPlugin) {
  42. actualPlugin = loadedPlugin;
  43. }
  44. } else {
  45. actualPlugin = plugin;
  46. }
  47. if (actualPlugin && this._plugins.indexOf(actualPlugin) === -1) {
  48. this._plugins.push(actualPlugin);
  49. }
  50. }
  51. /**
  52. * Load a model using predefined configuration
  53. * @param modelConfiguration the modelConfiguration to use to load the model
  54. */
  55. public load(modelConfiguration: IModelConfiguration): ViewerModel {
  56. const model = new ViewerModel(this._observablesManager, modelConfiguration, this._configurationContainer);
  57. model.loadId = this._loadId++;
  58. let filename: any;
  59. if (modelConfiguration.file) {
  60. this._baseUrl = "file:";
  61. filename = modelConfiguration.file;
  62. }
  63. else if (modelConfiguration.url) {
  64. filename = Tools.GetFilename(modelConfiguration.url) || modelConfiguration.url;
  65. this._baseUrl = modelConfiguration.root || Tools.GetFolderPath(modelConfiguration.url);
  66. }
  67. if (!filename || !this._baseUrl) {
  68. model.state = ModelState.ERROR;
  69. Tools.Error("No URL provided");
  70. return model;
  71. }
  72. let plugin = modelConfiguration.loader;
  73. let scene = model.rootMesh.getScene();
  74. model.loader = SceneLoader.ImportMesh(undefined, this._baseUrl, filename, scene, (meshes, particleSystems, skeletons, animationGroups) => {
  75. meshes.forEach(mesh => {
  76. Tags.AddTagsTo(mesh, "viewerMesh");
  77. model.addMesh(mesh);
  78. });
  79. model.particleSystems = particleSystems;
  80. model.skeletons = skeletons;
  81. for (const animationGroup of animationGroups) {
  82. model.addAnimationGroup(animationGroup);
  83. }
  84. this._checkAndRun("onLoaded", model);
  85. scene.executeWhenReady(() => {
  86. model.onLoadedObservable.notifyObservers(model);
  87. });
  88. }, (progressEvent) => {
  89. this._checkAndRun("onProgress", progressEvent);
  90. model.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
  91. }, (scene, m, exception) => {
  92. model.state = ModelState.ERROR;
  93. Tools.Error("Load Error: There was an error loading the model. " + m);
  94. this._checkAndRun("onError", m, exception);
  95. model.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
  96. }, plugin)!;
  97. if (model.loader.name === "gltf") {
  98. let gltfLoader = (<GLTFFileLoader>model.loader);
  99. gltfLoader.animationStartMode = GLTFLoaderAnimationStartMode.NONE;
  100. gltfLoader.compileMaterials = true;
  101. if (!modelConfiguration.file) {
  102. gltfLoader.rewriteRootURL = (rootURL, responseURL) => {
  103. return modelConfiguration.root || Tools.GetFolderPath(responseURL || modelConfiguration.url || '');
  104. };
  105. }
  106. // if ground is set to "mirror":
  107. if (this._configurationContainer
  108. && this._configurationContainer.configuration
  109. && this._configurationContainer.configuration.ground
  110. && typeof this._configurationContainer.configuration.ground === 'object'
  111. && this._configurationContainer.configuration.ground.mirror) {
  112. gltfLoader.useClipPlane = true;
  113. }
  114. Object.keys(gltfLoader).filter(name => name.indexOf('on') === 0 && name.indexOf('Observable') !== -1).forEach(functionName => {
  115. gltfLoader[functionName].add((payload) => {
  116. this._checkAndRun(functionName.replace("Observable", ''), payload);
  117. });
  118. });
  119. gltfLoader.onParsedObservable.add((data) => {
  120. if (data && data.json && data.json['asset']) {
  121. model.loadInfo = data.json['asset'];
  122. }
  123. });
  124. gltfLoader.onCompleteObservable.add(() => {
  125. model.loaderDone = true;
  126. });
  127. } else {
  128. model.loaderDone = true;
  129. }
  130. this._checkAndRun("onInit", model.loader, model);
  131. this._loaders.push(model.loader);
  132. return model;
  133. }
  134. public cancelLoad(model: ViewerModel) {
  135. const loader = model.loader || this._loaders[model.loadId];
  136. // ATM only available in the GLTF Loader
  137. if (loader && loader.name === "gltf") {
  138. let gltfLoader = (<GLTFFileLoader>loader);
  139. gltfLoader.dispose();
  140. model.state = ModelState.CANCELED;
  141. } else {
  142. Tools.Warn("This type of loader cannot cancel the request");
  143. }
  144. }
  145. /**
  146. * dispose the model loader.
  147. * If loaders are registered and are in the middle of loading, they will be disposed and the request(s) will be cancelled.
  148. */
  149. public dispose() {
  150. this._loaders.forEach(loader => {
  151. if (loader.name === "gltf") {
  152. (<GLTFFileLoader>loader).dispose();
  153. }
  154. });
  155. this._loaders.length = 0;
  156. this._disposed = true;
  157. }
  158. private _checkAndRun(functionName: string, ...payload: Array<any>) {
  159. if (this._disposed) return;
  160. this._plugins.filter(p => p[functionName]).forEach(plugin => {
  161. try {
  162. plugin[functionName].apply(this, payload);
  163. } catch (e) { }
  164. })
  165. }
  166. }