babylon.sceneLoader.ts 19 KB

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