sceneLoader.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. import { Tools } from "../Misc/tools.js";
  2. import { Observable } from "../Misc/observable.js";
  3. import { Scene } from "../scene.js";
  4. import { EngineStore } from "../Engines/engineStore.js";
  5. import { Logger } from "../Misc/logger.js";
  6. import { SceneLoaderFlags } from "./sceneLoaderFlags.js";
  7. import { IsBase64DataUrl } from "../Misc/fileTools.js";
  8. import { RuntimeError, ErrorCodes } from "../Misc/error.js";
  9. import { RandomGUID } from "../Misc/guid.js";
  10. import { Engine } from "../Engines/engine.js";
  11. /**
  12. * Mode that determines how to handle old animation groups before loading new ones.
  13. */
  14. export var SceneLoaderAnimationGroupLoadingMode;
  15. (function (SceneLoaderAnimationGroupLoadingMode) {
  16. /**
  17. * Reset all old animations to initial state then dispose them.
  18. */
  19. SceneLoaderAnimationGroupLoadingMode[SceneLoaderAnimationGroupLoadingMode["Clean"] = 0] = "Clean";
  20. /**
  21. * Stop all old animations.
  22. */
  23. SceneLoaderAnimationGroupLoadingMode[SceneLoaderAnimationGroupLoadingMode["Stop"] = 1] = "Stop";
  24. /**
  25. * Restart old animations from first frame.
  26. */
  27. SceneLoaderAnimationGroupLoadingMode[SceneLoaderAnimationGroupLoadingMode["Sync"] = 2] = "Sync";
  28. /**
  29. * Old animations remains untouched.
  30. */
  31. SceneLoaderAnimationGroupLoadingMode[SceneLoaderAnimationGroupLoadingMode["NoSync"] = 3] = "NoSync";
  32. })(SceneLoaderAnimationGroupLoadingMode || (SceneLoaderAnimationGroupLoadingMode = {}));
  33. /**
  34. * Class used to load scene from various file formats using registered plugins
  35. * @see https://doc.babylonjs.com/features/featuresDeepDive/importers/loadingFileTypes
  36. */
  37. export class SceneLoader {
  38. /**
  39. * Gets or sets a boolean indicating if entire scene must be loaded even if scene contains incremental data
  40. */
  41. static get ForceFullSceneLoadingForIncremental() {
  42. return SceneLoaderFlags.ForceFullSceneLoadingForIncremental;
  43. }
  44. static set ForceFullSceneLoadingForIncremental(value) {
  45. SceneLoaderFlags.ForceFullSceneLoadingForIncremental = value;
  46. }
  47. /**
  48. * Gets or sets a boolean indicating if loading screen must be displayed while loading a scene
  49. */
  50. static get ShowLoadingScreen() {
  51. return SceneLoaderFlags.ShowLoadingScreen;
  52. }
  53. static set ShowLoadingScreen(value) {
  54. SceneLoaderFlags.ShowLoadingScreen = value;
  55. }
  56. /**
  57. * Defines the current logging level (while loading the scene)
  58. * @ignorenaming
  59. */
  60. // eslint-disable-next-line @typescript-eslint/naming-convention
  61. static get loggingLevel() {
  62. return SceneLoaderFlags.loggingLevel;
  63. }
  64. // eslint-disable-next-line @typescript-eslint/naming-convention
  65. static set loggingLevel(value) {
  66. SceneLoaderFlags.loggingLevel = value;
  67. }
  68. /**
  69. * Gets or set a boolean indicating if matrix weights must be cleaned upon loading
  70. */
  71. static get CleanBoneMatrixWeights() {
  72. return SceneLoaderFlags.CleanBoneMatrixWeights;
  73. }
  74. static set CleanBoneMatrixWeights(value) {
  75. SceneLoaderFlags.CleanBoneMatrixWeights = value;
  76. }
  77. /**
  78. * Gets the default plugin (used to load Babylon files)
  79. * @returns the .babylon plugin
  80. */
  81. static GetDefaultPlugin() {
  82. return SceneLoader._RegisteredPlugins[".babylon"];
  83. }
  84. static _GetPluginForExtension(extension) {
  85. const registeredPlugin = SceneLoader._RegisteredPlugins[extension];
  86. if (registeredPlugin) {
  87. return registeredPlugin;
  88. }
  89. Logger.Warn("Unable to find a plugin to load " +
  90. extension +
  91. " files. Trying to use .babylon default plugin. To load from a specific filetype (eg. gltf) see: https://doc.babylonjs.com/features/featuresDeepDive/importers/loadingFileTypes");
  92. return SceneLoader.GetDefaultPlugin();
  93. }
  94. static _GetPluginForDirectLoad(data) {
  95. for (const extension in SceneLoader._RegisteredPlugins) {
  96. const plugin = SceneLoader._RegisteredPlugins[extension].plugin;
  97. if (plugin.canDirectLoad && plugin.canDirectLoad(data)) {
  98. return SceneLoader._RegisteredPlugins[extension];
  99. }
  100. }
  101. return SceneLoader.GetDefaultPlugin();
  102. }
  103. static _GetPluginForFilename(sceneFilename) {
  104. const queryStringPosition = sceneFilename.indexOf("?");
  105. if (queryStringPosition !== -1) {
  106. sceneFilename = sceneFilename.substring(0, queryStringPosition);
  107. }
  108. const dotPosition = sceneFilename.lastIndexOf(".");
  109. const extension = sceneFilename.substring(dotPosition, sceneFilename.length).toLowerCase();
  110. return SceneLoader._GetPluginForExtension(extension);
  111. }
  112. static _GetDirectLoad(sceneFilename) {
  113. if (sceneFilename.substr(0, 5) === "data:") {
  114. return sceneFilename.substr(5);
  115. }
  116. return null;
  117. }
  118. static _FormatErrorMessage(fileInfo, message, exception) {
  119. const fromLoad = fileInfo.rawData ? "binary data" : fileInfo.url;
  120. let errorMessage = "Unable to load from " + fromLoad;
  121. if (message) {
  122. errorMessage += `: ${message}`;
  123. }
  124. else if (exception) {
  125. errorMessage += `: ${exception}`;
  126. }
  127. return errorMessage;
  128. }
  129. static _LoadData(fileInfo, scene, onSuccess, onProgress, onError, onDispose, pluginExtension, name) {
  130. const directLoad = SceneLoader._GetDirectLoad(fileInfo.url);
  131. if (fileInfo.rawData && !pluginExtension) {
  132. // eslint-disable-next-line no-throw-literal
  133. throw "When using ArrayBufferView to load data the file extension must be provided.";
  134. }
  135. const registeredPlugin = pluginExtension
  136. ? SceneLoader._GetPluginForExtension(pluginExtension)
  137. : directLoad
  138. ? SceneLoader._GetPluginForDirectLoad(fileInfo.url)
  139. : SceneLoader._GetPluginForFilename(fileInfo.url);
  140. if (fileInfo.rawData && !registeredPlugin.isBinary) {
  141. // eslint-disable-next-line no-throw-literal
  142. throw "Loading from ArrayBufferView can not be used with plugins that don't support binary loading.";
  143. }
  144. let plugin;
  145. if (registeredPlugin.plugin.createPlugin !== undefined) {
  146. plugin = registeredPlugin.plugin.createPlugin();
  147. }
  148. else {
  149. plugin = registeredPlugin.plugin;
  150. }
  151. if (!plugin) {
  152. // eslint-disable-next-line no-throw-literal
  153. throw "The loader plugin corresponding to the file type you are trying to load has not been found. If using es6, please import the plugin you wish to use before.";
  154. }
  155. SceneLoader.OnPluginActivatedObservable.notifyObservers(plugin);
  156. // Check if we have a direct load url. If the plugin is registered to handle
  157. // it or it's not a base64 data url, then pass it through the direct load path.
  158. if (directLoad && ((plugin.canDirectLoad && plugin.canDirectLoad(fileInfo.url)) || !IsBase64DataUrl(fileInfo.url))) {
  159. if (plugin.directLoad) {
  160. const result = plugin.directLoad(scene, directLoad);
  161. if (result.then) {
  162. result
  163. .then((data) => {
  164. onSuccess(plugin, data);
  165. })
  166. .catch((error) => {
  167. onError("Error in directLoad of _loadData: " + error, error);
  168. });
  169. }
  170. else {
  171. onSuccess(plugin, result);
  172. }
  173. }
  174. else {
  175. onSuccess(plugin, directLoad);
  176. }
  177. return plugin;
  178. }
  179. const useArrayBuffer = registeredPlugin.isBinary;
  180. const dataCallback = (data, responseURL) => {
  181. if (scene.isDisposed) {
  182. onError("Scene has been disposed");
  183. return;
  184. }
  185. onSuccess(plugin, data, responseURL);
  186. };
  187. let request = null;
  188. let pluginDisposed = false;
  189. const onDisposeObservable = plugin.onDisposeObservable;
  190. if (onDisposeObservable) {
  191. onDisposeObservable.add(() => {
  192. pluginDisposed = true;
  193. if (request) {
  194. request.abort();
  195. request = null;
  196. }
  197. onDispose();
  198. });
  199. }
  200. const manifestChecked = () => {
  201. if (pluginDisposed) {
  202. return;
  203. }
  204. const errorCallback = (request, exception) => {
  205. onError(request?.statusText, exception);
  206. };
  207. if (!plugin.loadFile && fileInfo.rawData) {
  208. // eslint-disable-next-line no-throw-literal
  209. throw "Plugin does not support loading ArrayBufferView.";
  210. }
  211. request = plugin.loadFile
  212. ? plugin.loadFile(scene, fileInfo.rawData || fileInfo.file || fileInfo.url, fileInfo.rootUrl, dataCallback, onProgress, useArrayBuffer, errorCallback, name)
  213. : scene._loadFile(fileInfo.file || fileInfo.url, dataCallback, onProgress, true, useArrayBuffer, errorCallback);
  214. };
  215. const engine = scene.getEngine();
  216. let canUseOfflineSupport = engine.enableOfflineSupport;
  217. if (canUseOfflineSupport) {
  218. // Also check for exceptions
  219. let exceptionFound = false;
  220. for (const regex of scene.disableOfflineSupportExceptionRules) {
  221. if (regex.test(fileInfo.url)) {
  222. exceptionFound = true;
  223. break;
  224. }
  225. }
  226. canUseOfflineSupport = !exceptionFound;
  227. }
  228. if (canUseOfflineSupport && Engine.OfflineProviderFactory) {
  229. // Checking if a manifest file has been set for this scene and if offline mode has been requested
  230. scene.offlineProvider = Engine.OfflineProviderFactory(fileInfo.url, manifestChecked, engine.disableManifestCheck);
  231. }
  232. else {
  233. manifestChecked();
  234. }
  235. return plugin;
  236. }
  237. static _GetFileInfo(rootUrl, sceneFilename) {
  238. let url;
  239. let name;
  240. let file = null;
  241. let rawData = null;
  242. if (!sceneFilename) {
  243. url = rootUrl;
  244. name = Tools.GetFilename(rootUrl);
  245. rootUrl = Tools.GetFolderPath(rootUrl);
  246. }
  247. else if (sceneFilename.name) {
  248. const sceneFile = sceneFilename;
  249. url = `file:${sceneFile.name}`;
  250. name = sceneFile.name;
  251. file = sceneFile;
  252. }
  253. else if (ArrayBuffer.isView(sceneFilename)) {
  254. url = "";
  255. name = RandomGUID();
  256. rawData = sceneFilename;
  257. }
  258. else if (typeof sceneFilename === "string" && sceneFilename.startsWith("data:")) {
  259. url = sceneFilename;
  260. name = "";
  261. }
  262. else {
  263. const filename = sceneFilename;
  264. if (filename.substr(0, 1) === "/") {
  265. Tools.Error("Wrong sceneFilename parameter");
  266. return null;
  267. }
  268. url = rootUrl + filename;
  269. name = filename;
  270. }
  271. return {
  272. url: url,
  273. rootUrl: rootUrl,
  274. name: name,
  275. file: file,
  276. rawData,
  277. };
  278. }
  279. // Public functions
  280. /**
  281. * Gets a plugin that can load the given extension
  282. * @param extension defines the extension to load
  283. * @returns a plugin or null if none works
  284. */
  285. static GetPluginForExtension(extension) {
  286. return SceneLoader._GetPluginForExtension(extension).plugin;
  287. }
  288. /**
  289. * Gets a boolean indicating that the given extension can be loaded
  290. * @param extension defines the extension to load
  291. * @returns true if the extension is supported
  292. */
  293. static IsPluginForExtensionAvailable(extension) {
  294. return !!SceneLoader._RegisteredPlugins[extension];
  295. }
  296. /**
  297. * Adds a new plugin to the list of registered plugins
  298. * @param plugin defines the plugin to add
  299. */
  300. static RegisterPlugin(plugin) {
  301. if (typeof plugin.extensions === "string") {
  302. const extension = plugin.extensions;
  303. SceneLoader._RegisteredPlugins[extension.toLowerCase()] = {
  304. plugin: plugin,
  305. isBinary: false,
  306. };
  307. }
  308. else {
  309. const extensions = plugin.extensions;
  310. Object.keys(extensions).forEach((extension) => {
  311. SceneLoader._RegisteredPlugins[extension.toLowerCase()] = {
  312. plugin: plugin,
  313. isBinary: extensions[extension].isBinary,
  314. };
  315. });
  316. }
  317. }
  318. /**
  319. * Import meshes into a scene
  320. * @param meshNames an array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported
  321. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  322. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  323. * @param scene the instance of BABYLON.Scene to append to
  324. * @param onSuccess a callback with a list of imported meshes, particleSystems, skeletons, and animationGroups when import succeeds
  325. * @param onProgress a callback with a progress event for each file being loaded
  326. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  327. * @param pluginExtension the extension used to determine the plugin
  328. * @param name defines the name of the file, if the data is binary
  329. * @returns The loaded plugin
  330. */
  331. static ImportMesh(meshNames, rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, onSuccess = null, onProgress = null, onError = null, pluginExtension = null, name = "") {
  332. if (!scene) {
  333. Logger.Error("No scene available to import mesh to");
  334. return null;
  335. }
  336. const fileInfo = SceneLoader._GetFileInfo(rootUrl, sceneFilename);
  337. if (!fileInfo) {
  338. return null;
  339. }
  340. const loadingToken = {};
  341. scene.addPendingData(loadingToken);
  342. const disposeHandler = () => {
  343. scene.removePendingData(loadingToken);
  344. };
  345. const errorHandler = (message, exception) => {
  346. const errorMessage = SceneLoader._FormatErrorMessage(fileInfo, message, exception);
  347. if (onError) {
  348. onError(scene, errorMessage, new RuntimeError(errorMessage, ErrorCodes.SceneLoaderError, exception));
  349. }
  350. else {
  351. Logger.Error(errorMessage);
  352. // should the exception be thrown?
  353. }
  354. disposeHandler();
  355. };
  356. const progressHandler = onProgress
  357. ? (event) => {
  358. try {
  359. onProgress(event);
  360. }
  361. catch (e) {
  362. errorHandler("Error in onProgress callback: " + e, e);
  363. }
  364. }
  365. : undefined;
  366. const successHandler = (meshes, particleSystems, skeletons, animationGroups, transformNodes, geometries, lights, spriteManagers) => {
  367. scene.importedMeshesFiles.push(fileInfo.url);
  368. if (onSuccess) {
  369. try {
  370. onSuccess(meshes, particleSystems, skeletons, animationGroups, transformNodes, geometries, lights, spriteManagers);
  371. }
  372. catch (e) {
  373. errorHandler("Error in onSuccess callback: " + e, e);
  374. }
  375. }
  376. scene.removePendingData(loadingToken);
  377. };
  378. return SceneLoader._LoadData(fileInfo, scene, (plugin, data, responseURL) => {
  379. if (plugin.rewriteRootURL) {
  380. fileInfo.rootUrl = plugin.rewriteRootURL(fileInfo.rootUrl, responseURL);
  381. }
  382. if (plugin.importMesh) {
  383. const syncedPlugin = plugin;
  384. const meshes = [];
  385. const particleSystems = [];
  386. const skeletons = [];
  387. if (!syncedPlugin.importMesh(meshNames, scene, data, fileInfo.rootUrl, meshes, particleSystems, skeletons, errorHandler)) {
  388. return;
  389. }
  390. scene.loadingPluginName = plugin.name;
  391. successHandler(meshes, particleSystems, skeletons, [], [], [], [], []);
  392. }
  393. else {
  394. const asyncedPlugin = plugin;
  395. asyncedPlugin
  396. .importMeshAsync(meshNames, scene, data, fileInfo.rootUrl, progressHandler, fileInfo.name)
  397. .then((result) => {
  398. scene.loadingPluginName = plugin.name;
  399. successHandler(result.meshes, result.particleSystems, result.skeletons, result.animationGroups, result.transformNodes, result.geometries, result.lights, result.spriteManagers);
  400. })
  401. .catch((error) => {
  402. errorHandler(error.message, error);
  403. });
  404. }
  405. }, progressHandler, errorHandler, disposeHandler, pluginExtension, name);
  406. }
  407. /**
  408. * Import meshes into a scene
  409. * @param meshNames an array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported
  410. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  411. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  412. * @param scene the instance of BABYLON.Scene to append to
  413. * @param onProgress a callback with a progress event for each file being loaded
  414. * @param pluginExtension the extension used to determine the plugin
  415. * @param name defines the name of the file
  416. * @returns The loaded list of imported meshes, particle systems, skeletons, and animation groups
  417. */
  418. static ImportMeshAsync(meshNames, rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, onProgress = null, pluginExtension = null, name = "") {
  419. return new Promise((resolve, reject) => {
  420. SceneLoader.ImportMesh(meshNames, rootUrl, sceneFilename, scene, (meshes, particleSystems, skeletons, animationGroups, transformNodes, geometries, lights, spriteManagers) => {
  421. resolve({
  422. meshes: meshes,
  423. particleSystems: particleSystems,
  424. skeletons: skeletons,
  425. animationGroups: animationGroups,
  426. transformNodes: transformNodes,
  427. geometries: geometries,
  428. lights: lights,
  429. spriteManagers: spriteManagers,
  430. });
  431. }, onProgress, (scene, message, exception) => {
  432. reject(exception || new Error(message));
  433. }, pluginExtension, name);
  434. });
  435. }
  436. /**
  437. * Load a scene
  438. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  439. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  440. * @param engine is the instance of BABYLON.Engine to use to create the scene
  441. * @param onSuccess a callback with the scene when import succeeds
  442. * @param onProgress a callback with a progress event for each file being loaded
  443. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  444. * @param pluginExtension the extension used to determine the plugin
  445. * @param name defines the filename, if the data is binary
  446. * @returns The loaded plugin
  447. */
  448. static Load(rootUrl, sceneFilename = "", engine = EngineStore.LastCreatedEngine, onSuccess = null, onProgress = null, onError = null, pluginExtension = null, name = "") {
  449. if (!engine) {
  450. Tools.Error("No engine available");
  451. return null;
  452. }
  453. return SceneLoader.Append(rootUrl, sceneFilename, new Scene(engine), onSuccess, onProgress, onError, pluginExtension, name);
  454. }
  455. /**
  456. * Load a scene
  457. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  458. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  459. * @param engine is the instance of BABYLON.Engine to use to create the scene
  460. * @param onProgress a callback with a progress event for each file being loaded
  461. * @param pluginExtension the extension used to determine the plugin
  462. * @param name defines the filename, if the data is binary
  463. * @returns The loaded scene
  464. */
  465. static LoadAsync(rootUrl, sceneFilename = "", engine = EngineStore.LastCreatedEngine, onProgress = null, pluginExtension = null, name = "") {
  466. return new Promise((resolve, reject) => {
  467. SceneLoader.Load(rootUrl, sceneFilename, engine, (scene) => {
  468. resolve(scene);
  469. }, onProgress, (scene, message, exception) => {
  470. reject(exception || new Error(message));
  471. }, pluginExtension, name);
  472. });
  473. }
  474. /**
  475. * Append a scene
  476. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  477. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  478. * @param scene is the instance of BABYLON.Scene to append to
  479. * @param onSuccess a callback with the scene when import succeeds
  480. * @param onProgress a callback with a progress event for each file being loaded
  481. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  482. * @param pluginExtension the extension used to determine the plugin
  483. * @param name defines the name of the file, if the data is binary
  484. * @returns The loaded plugin
  485. */
  486. static Append(rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, onSuccess = null, onProgress = null, onError = null, pluginExtension = null, name = "") {
  487. if (!scene) {
  488. Logger.Error("No scene available to append to");
  489. return null;
  490. }
  491. const fileInfo = SceneLoader._GetFileInfo(rootUrl, sceneFilename);
  492. if (!fileInfo) {
  493. return null;
  494. }
  495. const loadingToken = {};
  496. scene.addPendingData(loadingToken);
  497. const disposeHandler = () => {
  498. scene.removePendingData(loadingToken);
  499. };
  500. if (SceneLoader.ShowLoadingScreen && !this._ShowingLoadingScreen) {
  501. this._ShowingLoadingScreen = true;
  502. scene.getEngine().displayLoadingUI();
  503. scene.executeWhenReady(() => {
  504. scene.getEngine().hideLoadingUI();
  505. this._ShowingLoadingScreen = false;
  506. });
  507. }
  508. const errorHandler = (message, exception) => {
  509. const errorMessage = SceneLoader._FormatErrorMessage(fileInfo, message, exception);
  510. if (onError) {
  511. onError(scene, errorMessage, new RuntimeError(errorMessage, ErrorCodes.SceneLoaderError, exception));
  512. }
  513. else {
  514. Logger.Error(errorMessage);
  515. // should the exception be thrown?
  516. }
  517. disposeHandler();
  518. };
  519. const progressHandler = onProgress
  520. ? (event) => {
  521. try {
  522. onProgress(event);
  523. }
  524. catch (e) {
  525. errorHandler("Error in onProgress callback", e);
  526. }
  527. }
  528. : undefined;
  529. const successHandler = () => {
  530. if (onSuccess) {
  531. try {
  532. onSuccess(scene);
  533. }
  534. catch (e) {
  535. errorHandler("Error in onSuccess callback", e);
  536. }
  537. }
  538. scene.removePendingData(loadingToken);
  539. };
  540. return SceneLoader._LoadData(fileInfo, scene, (plugin, data) => {
  541. if (plugin.load) {
  542. const syncedPlugin = plugin;
  543. if (!syncedPlugin.load(scene, data, fileInfo.rootUrl, errorHandler)) {
  544. return;
  545. }
  546. scene.loadingPluginName = plugin.name;
  547. successHandler();
  548. }
  549. else {
  550. const asyncedPlugin = plugin;
  551. asyncedPlugin
  552. .loadAsync(scene, data, fileInfo.rootUrl, progressHandler, fileInfo.name)
  553. .then(() => {
  554. scene.loadingPluginName = plugin.name;
  555. successHandler();
  556. })
  557. .catch((error) => {
  558. errorHandler(error.message, error);
  559. });
  560. }
  561. }, progressHandler, errorHandler, disposeHandler, pluginExtension, name);
  562. }
  563. /**
  564. * Append a scene
  565. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  566. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  567. * @param scene is the instance of BABYLON.Scene to append to
  568. * @param onProgress a callback with a progress event for each file being loaded
  569. * @param pluginExtension the extension used to determine the plugin
  570. * @param name defines the name of the file, if the data is binary
  571. * @returns The given scene
  572. */
  573. static AppendAsync(rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, onProgress = null, pluginExtension = null, name = "") {
  574. return new Promise((resolve, reject) => {
  575. SceneLoader.Append(rootUrl, sceneFilename, scene, (scene) => {
  576. resolve(scene);
  577. }, onProgress, (scene, message, exception) => {
  578. reject(exception || new Error(message));
  579. }, pluginExtension, name);
  580. });
  581. }
  582. /**
  583. * Load a scene into an asset container
  584. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  585. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  586. * @param scene is the instance of BABYLON.Scene to append to (default: last created scene)
  587. * @param onSuccess a callback with the scene when import succeeds
  588. * @param onProgress a callback with a progress event for each file being loaded
  589. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  590. * @param pluginExtension the extension used to determine the plugin
  591. * @param name defines the filename, if the data is binary
  592. * @returns The loaded plugin
  593. */
  594. static LoadAssetContainer(rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, onSuccess = null, onProgress = null, onError = null, pluginExtension = null, name = "") {
  595. if (!scene) {
  596. Logger.Error("No scene available to load asset container to");
  597. return null;
  598. }
  599. const fileInfo = SceneLoader._GetFileInfo(rootUrl, sceneFilename);
  600. if (!fileInfo) {
  601. return null;
  602. }
  603. const loadingToken = {};
  604. scene.addPendingData(loadingToken);
  605. const disposeHandler = () => {
  606. scene.removePendingData(loadingToken);
  607. };
  608. const errorHandler = (message, exception) => {
  609. const errorMessage = SceneLoader._FormatErrorMessage(fileInfo, message, exception);
  610. if (onError) {
  611. onError(scene, errorMessage, new RuntimeError(errorMessage, ErrorCodes.SceneLoaderError, exception));
  612. }
  613. else {
  614. Logger.Error(errorMessage);
  615. // should the exception be thrown?
  616. }
  617. disposeHandler();
  618. };
  619. const progressHandler = onProgress
  620. ? (event) => {
  621. try {
  622. onProgress(event);
  623. }
  624. catch (e) {
  625. errorHandler("Error in onProgress callback", e);
  626. }
  627. }
  628. : undefined;
  629. const successHandler = (assets) => {
  630. if (onSuccess) {
  631. try {
  632. onSuccess(assets);
  633. }
  634. catch (e) {
  635. errorHandler("Error in onSuccess callback", e);
  636. }
  637. }
  638. scene.removePendingData(loadingToken);
  639. };
  640. return SceneLoader._LoadData(fileInfo, scene, (plugin, data) => {
  641. if (plugin.loadAssetContainer) {
  642. const syncedPlugin = plugin;
  643. const assetContainer = syncedPlugin.loadAssetContainer(scene, data, fileInfo.rootUrl, errorHandler);
  644. if (!assetContainer) {
  645. return;
  646. }
  647. assetContainer.populateRootNodes();
  648. scene.loadingPluginName = plugin.name;
  649. successHandler(assetContainer);
  650. }
  651. else if (plugin.loadAssetContainerAsync) {
  652. const asyncedPlugin = plugin;
  653. asyncedPlugin
  654. .loadAssetContainerAsync(scene, data, fileInfo.rootUrl, progressHandler, fileInfo.name)
  655. .then((assetContainer) => {
  656. assetContainer.populateRootNodes();
  657. scene.loadingPluginName = plugin.name;
  658. successHandler(assetContainer);
  659. })
  660. .catch((error) => {
  661. errorHandler(error.message, error);
  662. });
  663. }
  664. else {
  665. errorHandler("LoadAssetContainer is not supported by this plugin. Plugin did not provide a loadAssetContainer or loadAssetContainerAsync method.");
  666. }
  667. }, progressHandler, errorHandler, disposeHandler, pluginExtension, name);
  668. }
  669. /**
  670. * Load a scene into an asset container
  671. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  672. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene (default: empty string)
  673. * @param scene is the instance of Scene to append to
  674. * @param onProgress a callback with a progress event for each file being loaded
  675. * @param pluginExtension the extension used to determine the plugin
  676. * @returns The loaded asset container
  677. */
  678. static LoadAssetContainerAsync(rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, onProgress = null, pluginExtension = null) {
  679. return new Promise((resolve, reject) => {
  680. SceneLoader.LoadAssetContainer(rootUrl, sceneFilename, scene, (assetContainer) => {
  681. resolve(assetContainer);
  682. }, onProgress, (scene, message, exception) => {
  683. reject(exception || new Error(message));
  684. }, pluginExtension);
  685. });
  686. }
  687. /**
  688. * Import animations from a file into a scene
  689. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  690. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  691. * @param scene is the instance of BABYLON.Scene to append to (default: last created scene)
  692. * @param overwriteAnimations when true, animations are cleaned before importing new ones. Animations are appended otherwise
  693. * @param animationGroupLoadingMode defines how to handle old animations groups before importing new ones
  694. * @param targetConverter defines a function used to convert animation targets from loaded scene to current scene (default: search node by name)
  695. * @param onSuccess a callback with the scene when import succeeds
  696. * @param onProgress a callback with a progress event for each file being loaded
  697. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  698. * @param pluginExtension the extension used to determine the plugin
  699. */
  700. static ImportAnimations(rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, overwriteAnimations = true, animationGroupLoadingMode = SceneLoaderAnimationGroupLoadingMode.Clean, targetConverter = null, onSuccess = null, onProgress = null, onError = null, pluginExtension = null) {
  701. if (!scene) {
  702. Logger.Error("No scene available to load animations to");
  703. return;
  704. }
  705. if (overwriteAnimations) {
  706. // Reset, stop and dispose all animations before loading new ones
  707. for (const animatable of scene.animatables) {
  708. animatable.reset();
  709. }
  710. scene.stopAllAnimations();
  711. scene.animationGroups.slice().forEach((animationGroup) => {
  712. animationGroup.dispose();
  713. });
  714. const nodes = scene.getNodes();
  715. nodes.forEach((node) => {
  716. if (node.animations) {
  717. node.animations = [];
  718. }
  719. });
  720. }
  721. else {
  722. switch (animationGroupLoadingMode) {
  723. case SceneLoaderAnimationGroupLoadingMode.Clean:
  724. scene.animationGroups.slice().forEach((animationGroup) => {
  725. animationGroup.dispose();
  726. });
  727. break;
  728. case SceneLoaderAnimationGroupLoadingMode.Stop:
  729. scene.animationGroups.forEach((animationGroup) => {
  730. animationGroup.stop();
  731. });
  732. break;
  733. case SceneLoaderAnimationGroupLoadingMode.Sync:
  734. scene.animationGroups.forEach((animationGroup) => {
  735. animationGroup.reset();
  736. animationGroup.restart();
  737. });
  738. break;
  739. case SceneLoaderAnimationGroupLoadingMode.NoSync:
  740. // nothing to do
  741. break;
  742. default:
  743. Logger.Error("Unknown animation group loading mode value '" + animationGroupLoadingMode + "'");
  744. return;
  745. }
  746. }
  747. const startingIndexForNewAnimatables = scene.animatables.length;
  748. const onAssetContainerLoaded = (container) => {
  749. container.mergeAnimationsTo(scene, scene.animatables.slice(startingIndexForNewAnimatables), targetConverter);
  750. container.dispose();
  751. scene.onAnimationFileImportedObservable.notifyObservers(scene);
  752. if (onSuccess) {
  753. onSuccess(scene);
  754. }
  755. };
  756. this.LoadAssetContainer(rootUrl, sceneFilename, scene, onAssetContainerLoaded, onProgress, onError, pluginExtension);
  757. }
  758. /**
  759. * Import animations from a file into a scene
  760. * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
  761. * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
  762. * @param scene is the instance of BABYLON.Scene to append to (default: last created scene)
  763. * @param overwriteAnimations when true, animations are cleaned before importing new ones. Animations are appended otherwise
  764. * @param animationGroupLoadingMode defines how to handle old animations groups before importing new ones
  765. * @param targetConverter defines a function used to convert animation targets from loaded scene to current scene (default: search node by name)
  766. * @param onSuccess a callback with the scene when import succeeds
  767. * @param onProgress a callback with a progress event for each file being loaded
  768. * @param onError a callback with the scene, a message, and possibly an exception when import fails
  769. * @param pluginExtension the extension used to determine the plugin
  770. * @returns the updated scene with imported animations
  771. */
  772. static ImportAnimationsAsync(rootUrl, sceneFilename = "", scene = EngineStore.LastCreatedScene, overwriteAnimations = true, animationGroupLoadingMode = SceneLoaderAnimationGroupLoadingMode.Clean, targetConverter = null,
  773. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  774. onSuccess = null, onProgress = null,
  775. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  776. onError = null, pluginExtension = null) {
  777. return new Promise((resolve, reject) => {
  778. SceneLoader.ImportAnimations(rootUrl, sceneFilename, scene, overwriteAnimations, animationGroupLoadingMode, targetConverter, (_scene) => {
  779. resolve(_scene);
  780. }, onProgress, (_scene, message, exception) => {
  781. reject(exception || new Error(message));
  782. }, pluginExtension);
  783. });
  784. }
  785. }
  786. /**
  787. * No logging while loading
  788. */
  789. SceneLoader.NO_LOGGING = 0;
  790. /**
  791. * Minimal logging while loading
  792. */
  793. SceneLoader.MINIMAL_LOGGING = 1;
  794. /**
  795. * Summary logging while loading
  796. */
  797. SceneLoader.SUMMARY_LOGGING = 2;
  798. /**
  799. * Detailed logging while loading
  800. */
  801. SceneLoader.DETAILED_LOGGING = 3;
  802. // Members
  803. /**
  804. * Event raised when a plugin is used to load a scene
  805. */
  806. SceneLoader.OnPluginActivatedObservable = new Observable();
  807. SceneLoader._RegisteredPlugins = {};
  808. SceneLoader._ShowingLoadingScreen = false;
  809. //# sourceMappingURL=sceneLoader.js.map