babylon.sceneLoader.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. module BABYLON {
  2. export interface ISceneLoaderPluginExtensions {
  3. [extension: string]: {
  4. isBinary: boolean;
  5. };
  6. }
  7. export interface ISceneLoaderPlugin {
  8. name: string;
  9. extensions: string | ISceneLoaderPluginExtensions;
  10. importMesh: (meshesNames: any, scene: Scene, data: any, rootUrl: string, meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[], onError?: (message: string, exception?: any) => void) => boolean;
  11. load: (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void) => boolean;
  12. canDirectLoad?: (data: string) => boolean;
  13. }
  14. export interface ISceneLoaderPluginAsync {
  15. name: string;
  16. extensions: string | ISceneLoaderPluginExtensions;
  17. importMeshAsync: (meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void) => void;
  18. loadAsync: (scene: Scene, data: string, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string, exception?: any) => void) => void;
  19. canDirectLoad?: (data: string) => boolean;
  20. }
  21. interface IRegisteredPlugin {
  22. plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
  23. isBinary: boolean;
  24. }
  25. export class SceneLoader {
  26. // Flags
  27. private static _ForceFullSceneLoadingForIncremental = false;
  28. private static _ShowLoadingScreen = true;
  29. private static _CleanBoneMatrixWeights = false;
  30. public static get NO_LOGGING(): number {
  31. return 0;
  32. }
  33. public static get MINIMAL_LOGGING(): number {
  34. return 1;
  35. }
  36. public static get SUMMARY_LOGGING(): number {
  37. return 2;
  38. }
  39. public static get DETAILED_LOGGING(): number {
  40. return 3;
  41. }
  42. private static _loggingLevel = SceneLoader.NO_LOGGING;
  43. public static get ForceFullSceneLoadingForIncremental() {
  44. return SceneLoader._ForceFullSceneLoadingForIncremental;
  45. }
  46. public static set ForceFullSceneLoadingForIncremental(value: boolean) {
  47. SceneLoader._ForceFullSceneLoadingForIncremental = value;
  48. }
  49. public static get ShowLoadingScreen(): boolean {
  50. return SceneLoader._ShowLoadingScreen;
  51. }
  52. public static set ShowLoadingScreen(value: boolean) {
  53. SceneLoader._ShowLoadingScreen = value;
  54. }
  55. public static get loggingLevel(): number {
  56. return SceneLoader._loggingLevel;
  57. }
  58. public static set loggingLevel(value: number) {
  59. SceneLoader._loggingLevel = value;
  60. }
  61. public static get CleanBoneMatrixWeights(): boolean {
  62. return SceneLoader._CleanBoneMatrixWeights;
  63. }
  64. public static set CleanBoneMatrixWeights(value: boolean) {
  65. SceneLoader._CleanBoneMatrixWeights = value;
  66. }
  67. // Members
  68. public static OnPluginActivatedObservable = new Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>();
  69. private static _registeredPlugins: { [extension: string]: IRegisteredPlugin } = {};
  70. private static _getDefaultPlugin(): IRegisteredPlugin {
  71. return SceneLoader._registeredPlugins[".babylon"];
  72. }
  73. private static _getPluginForExtension(extension: string): IRegisteredPlugin {
  74. var registeredPlugin = SceneLoader._registeredPlugins[extension];
  75. if (registeredPlugin) {
  76. return registeredPlugin;
  77. }
  78. return SceneLoader._getDefaultPlugin();
  79. }
  80. private static _getPluginForDirectLoad(data: string): IRegisteredPlugin {
  81. for (var extension in SceneLoader._registeredPlugins) {
  82. var plugin = SceneLoader._registeredPlugins[extension].plugin;
  83. if (plugin.canDirectLoad && plugin.canDirectLoad(data)) {
  84. return SceneLoader._registeredPlugins[extension];
  85. }
  86. }
  87. return SceneLoader._getDefaultPlugin();
  88. }
  89. private static _getPluginForFilename(sceneFilename: any): IRegisteredPlugin {
  90. if (sceneFilename.name) {
  91. sceneFilename = sceneFilename.name;
  92. }
  93. var dotPosition = sceneFilename.lastIndexOf(".");
  94. var queryStringPosition = sceneFilename.indexOf("?");
  95. if (queryStringPosition === -1) {
  96. queryStringPosition = sceneFilename.length;
  97. }
  98. var extension = sceneFilename.substring(dotPosition, queryStringPosition).toLowerCase();
  99. return SceneLoader._getPluginForExtension(extension);
  100. }
  101. // use babylon file loader directly if sceneFilename is prefixed with "data:"
  102. private static _getDirectLoad(sceneFilename: string): Nullable<string> {
  103. if (sceneFilename.substr && sceneFilename.substr(0, 5) === "data:") {
  104. return sceneFilename.substr(5);
  105. }
  106. return null;
  107. }
  108. private static _loadData(rootUrl: string, sceneFilename: string, scene: Scene, onSuccess: (plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync, data: any) => void, onProgress: (event: ProgressEvent) => void, onError: (message: Nullable<string>, exception?: any) => void, pluginExtension?: string): void {
  109. var directLoad = SceneLoader._getDirectLoad(sceneFilename);
  110. var registeredPlugin = pluginExtension ? SceneLoader._getPluginForExtension(pluginExtension) : (directLoad ? SceneLoader._getPluginForDirectLoad(sceneFilename) : SceneLoader._getPluginForFilename(sceneFilename));
  111. var plugin = registeredPlugin.plugin;
  112. var useArrayBuffer = registeredPlugin.isBinary;
  113. var database: Database;
  114. SceneLoader.OnPluginActivatedObservable.notifyObservers(registeredPlugin.plugin);
  115. var dataCallback = (data: any) => {
  116. if (scene.isDisposed) {
  117. onError("Scene has been disposed");
  118. return;
  119. }
  120. scene.database = database;
  121. try {
  122. onSuccess(plugin, data);
  123. }
  124. catch (e) {
  125. onError(null, e);
  126. }
  127. };
  128. var manifestChecked = (success: any) => {
  129. Tools.LoadFile(rootUrl + sceneFilename, dataCallback, onProgress, database, useArrayBuffer, request => {
  130. if (request) {
  131. onError(request.status + " " + request.statusText);
  132. }
  133. });
  134. };
  135. if (directLoad) {
  136. dataCallback(directLoad);
  137. return;
  138. }
  139. if (rootUrl.indexOf("file:") === -1) {
  140. if (scene.getEngine().enableOfflineSupport) {
  141. // Checking if a manifest file has been set for this scene and if offline mode has been requested
  142. database = new Database(rootUrl + sceneFilename, manifestChecked);
  143. }
  144. else {
  145. manifestChecked(true);
  146. }
  147. }
  148. // Loading file from disk via input file or drag'n'drop
  149. else {
  150. var fileOrString = <any>sceneFilename;
  151. if (fileOrString.name) { // File
  152. Tools.ReadFile(fileOrString, dataCallback, onProgress, useArrayBuffer);
  153. } else if (FilesInput.FilesToLoad[sceneFilename]) {
  154. Tools.ReadFile(FilesInput.FilesToLoad[sceneFilename], dataCallback, onProgress, useArrayBuffer);
  155. } else {
  156. onError("Unable to find file named " + sceneFilename);
  157. return;
  158. }
  159. }
  160. }
  161. // Public functions
  162. public static GetPluginForExtension(extension: string): ISceneLoaderPlugin | ISceneLoaderPluginAsync {
  163. return SceneLoader._getPluginForExtension(extension).plugin;
  164. }
  165. public static RegisterPlugin(plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync): void {
  166. if (typeof plugin.extensions === "string") {
  167. var extension = <string>plugin.extensions;
  168. SceneLoader._registeredPlugins[extension.toLowerCase()] = {
  169. plugin: plugin,
  170. isBinary: false
  171. };
  172. }
  173. else {
  174. var extensions = <ISceneLoaderPluginExtensions>plugin.extensions;
  175. Object.keys(extensions).forEach(extension => {
  176. SceneLoader._registeredPlugins[extension.toLowerCase()] = {
  177. plugin: plugin,
  178. isBinary: extensions[extension].isBinary
  179. };
  180. });
  181. }
  182. }
  183. /**
  184. * Import meshes into a scene
  185. * @param meshNames an array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported
  186. * @param rootUrl a string that defines the root url for scene and resources
  187. * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
  188. * @param scene the instance of BABYLON.Scene to append to
  189. * @param onSuccess a callback with a list of imported meshes, particleSystems, and skeletons when import succeeds
  190. * @param onProgress a callback with a progress event for each file being loaded
  191. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  192. */
  193. public static ImportMesh(meshNames: any, rootUrl: string, sceneFilename: string, scene: Scene, onSuccess: Nullable<(meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void> = null, onProgress: Nullable<(event: ProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension?: string): void {
  194. if (sceneFilename.substr && sceneFilename.substr(0, 1) === "/") {
  195. Tools.Error("Wrong sceneFilename parameter");
  196. return;
  197. }
  198. var loadingToken = {};
  199. scene._addPendingData(loadingToken);
  200. var errorHandler = (message: Nullable<string>, exception?: any) => {
  201. let errorMessage = "Unable to import meshes from " + rootUrl + sceneFilename + (message ? ": " + message : "");
  202. if (onError) {
  203. onError(scene, errorMessage, exception);
  204. } else {
  205. Tools.Error(errorMessage);
  206. // should the exception be thrown?
  207. }
  208. scene._removePendingData(loadingToken);
  209. };
  210. var progressHandler = (event: ProgressEvent) => {
  211. if (onProgress) {
  212. onProgress(event);
  213. }
  214. };
  215. SceneLoader._loadData(rootUrl, sceneFilename, scene, (plugin, data) => {
  216. if ((<any>plugin).importMesh) {
  217. var syncedPlugin = <ISceneLoaderPlugin>plugin;
  218. var meshes = new Array<AbstractMesh>();
  219. var particleSystems = new Array<ParticleSystem>();
  220. var skeletons = new Array<Skeleton>();
  221. if (!syncedPlugin.importMesh(meshNames, scene, data, rootUrl, meshes, particleSystems, skeletons, errorHandler)) {
  222. return;
  223. }
  224. if (onSuccess) {
  225. // wrap onSuccess with try-catch to know if something went wrong.
  226. try {
  227. scene.importedMeshesFiles.push(rootUrl + sceneFilename);
  228. onSuccess(meshes, particleSystems, skeletons);
  229. scene._removePendingData(loadingToken);
  230. } catch (e) {
  231. let message = 'Error in onSuccess callback.';
  232. errorHandler(message, e);
  233. }
  234. }
  235. }
  236. else {
  237. var asyncedPlugin = <ISceneLoaderPluginAsync>plugin;
  238. asyncedPlugin.importMeshAsync(meshNames, scene, data, rootUrl, (meshes, particleSystems, skeletons) => {
  239. if (onSuccess) {
  240. try {
  241. scene.importedMeshesFiles.push(rootUrl + sceneFilename);
  242. onSuccess(meshes, particleSystems, skeletons);
  243. scene._removePendingData(loadingToken);
  244. } catch (e) {
  245. let message = 'Error in onSuccess callback.';
  246. errorHandler(message, e);
  247. }
  248. }
  249. }, progressHandler, errorHandler);
  250. }
  251. }, progressHandler, errorHandler, pluginExtension);
  252. }
  253. /**
  254. * Load a scene
  255. * @param rootUrl a string that defines the root url for scene and resources
  256. * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
  257. * @param engine is the instance of BABYLON.Engine to use to create the scene
  258. * @param onSuccess a callback with the scene when import succeeds
  259. * @param onProgress a callback with a progress event for each file being loaded
  260. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  261. */
  262. public static Load(rootUrl: string, sceneFilename: any, engine: Engine, onSuccess?: (scene: Scene) => void, onProgress?: (event: ProgressEvent) => void, onError?: (scene: Scene, message: string, exception?: any) => void, pluginExtension?: string): void {
  263. SceneLoader.Append(rootUrl, sceneFilename, new Scene(engine), onSuccess, onProgress, onError, pluginExtension);
  264. }
  265. /**
  266. * Append a scene
  267. * @param rootUrl a string that defines the root url for scene and resources
  268. * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
  269. * @param scene is the instance of BABYLON.Scene to append to
  270. * @param onSuccess a callback with the scene when import succeeds
  271. * @param onProgress a callback with a progress event for each file being loaded
  272. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  273. */
  274. public static Append(rootUrl: string, sceneFilename: any, scene: Scene, onSuccess?: (scene: Scene) => void, onProgress?: (event: ProgressEvent) => void, onError?: (scene: Scene, message: string, exception?: any) => void, pluginExtension?: string): void {
  275. if (sceneFilename.substr && sceneFilename.substr(0, 1) === "/") {
  276. Tools.Error("Wrong sceneFilename parameter");
  277. return;
  278. }
  279. if (SceneLoader.ShowLoadingScreen) {
  280. scene.getEngine().displayLoadingUI();
  281. }
  282. var loadingToken = {};
  283. scene._addPendingData(loadingToken);
  284. var errorHandler = (message: Nullable<string>, exception?: any) => {
  285. let errorMessage = "Unable to load from " + rootUrl + sceneFilename + (message ? ": " + message : "");
  286. if (onError) {
  287. onError(scene, errorMessage, exception);
  288. } else {
  289. Tools.Error(errorMessage);
  290. // should the exception be thrown?
  291. }
  292. scene._removePendingData(loadingToken);
  293. scene.getEngine().hideLoadingUI();
  294. };
  295. var progressHandler = (event: ProgressEvent) => {
  296. if (onProgress) {
  297. onProgress(event);
  298. }
  299. };
  300. SceneLoader._loadData(rootUrl, sceneFilename, scene, (plugin, data) => {
  301. if ((<any>plugin).load) {
  302. var syncedPlugin = <ISceneLoaderPlugin>plugin;
  303. if (!syncedPlugin.load(scene, data, rootUrl, errorHandler)) {
  304. return;
  305. }
  306. if (onSuccess) {
  307. try {
  308. onSuccess(scene);
  309. } catch (e) {
  310. errorHandler("Error in onSuccess callback", e);
  311. }
  312. }
  313. scene.loadingPluginName = plugin.name;
  314. scene._removePendingData(loadingToken);
  315. } else {
  316. var asyncedPlugin = <ISceneLoaderPluginAsync>plugin;
  317. asyncedPlugin.loadAsync(scene, data, rootUrl, () => {
  318. if (onSuccess) {
  319. onSuccess(scene);
  320. }
  321. scene.loadingPluginName = plugin.name;
  322. scene._removePendingData(loadingToken);
  323. }, progressHandler, errorHandler);
  324. }
  325. if (SceneLoader.ShowLoadingScreen) {
  326. scene.executeWhenReady(() => {
  327. scene.getEngine().hideLoadingUI();
  328. });
  329. }
  330. }, progressHandler, errorHandler, pluginExtension);
  331. }
  332. };
  333. }