babylon.assetsManager.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. module BABYLON {
  2. /**
  3. * Defines the list of states available for a task inside a {BABYLON.AssetsManager}
  4. */
  5. export enum AssetTaskState {
  6. /**
  7. * Initialization
  8. */
  9. INIT,
  10. /**
  11. * Running
  12. */
  13. RUNNING,
  14. /**
  15. * Done
  16. */
  17. DONE,
  18. /**
  19. * Error
  20. */
  21. ERROR
  22. }
  23. /**
  24. * Define an abstract asset task used with a {BABYLON.AssetsManager} class to load assets into a scene
  25. */
  26. export abstract class AbstractAssetTask {
  27. /**
  28. * Callback called when the task is successful
  29. * @param task contains the successful task
  30. */
  31. public onSuccess: (task: any) => void;
  32. /**
  33. * Callback called when the task is successful
  34. * @param task contains the successful task
  35. * @param message contains the error message
  36. * @param exception can contains the inner exception
  37. */
  38. public onError: (task: any, message?: string, exception?: any) => void;
  39. /**
  40. * Creates a new {BABYLON.AssetsManager}
  41. * @param name define the name of the task
  42. */
  43. constructor(public name: string) {
  44. }
  45. private _isCompleted = false;
  46. private _taskState = AssetTaskState.INIT;
  47. private _errorObject: { message?: string; exception?: any; };
  48. /**
  49. * Get if the task is completed
  50. */
  51. public get isCompleted(): boolean {
  52. return this._isCompleted;
  53. }
  54. /**
  55. * Gets the current state of the task
  56. */
  57. public get taskState(): AssetTaskState {
  58. return this._taskState;
  59. }
  60. /**
  61. * Gets the current error object (if task is in error)
  62. */
  63. public get errorObject(): { message?: string; exception?: any; } {
  64. return this._errorObject;
  65. }
  66. /**
  67. * Internal only
  68. * @ignore
  69. */
  70. public _setErrorObject(message?: string, exception?: any) {
  71. if (this._errorObject) {
  72. return;
  73. }
  74. this._errorObject = {
  75. message: message,
  76. exception: exception
  77. };
  78. }
  79. /**
  80. * Execute the current task
  81. * @param scene defines the scene where you want your assets to be loaded
  82. * @param onSuccess is a callback called when the task is successfully executed
  83. * @param onError is a callback called if an error occurs
  84. */
  85. public run(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  86. this._taskState = AssetTaskState.RUNNING;
  87. this.runTask(scene, () => {
  88. this.onDoneCallback(onSuccess, onError);
  89. }, (msg, exception) => {
  90. this.onErrorCallback(onError, msg, exception);
  91. });
  92. }
  93. /**
  94. * Execute the current task
  95. * @param scene defines the scene where you want your assets to be loaded
  96. * @param onSuccess is a callback called when the task is successfully executed
  97. * @param onError is a callback called if an error occurs
  98. */
  99. public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  100. throw new Error("runTask is not implemented");
  101. }
  102. private onErrorCallback(onError: (message?: string, exception?: any) => void, message?: string, exception?: any) {
  103. this._taskState = AssetTaskState.ERROR;
  104. this._errorObject = {
  105. message: message,
  106. exception: exception
  107. }
  108. if (this.onError) {
  109. this.onError(this, message, exception);
  110. }
  111. onError();
  112. }
  113. private onDoneCallback(onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  114. try {
  115. this._taskState = AssetTaskState.DONE;
  116. this._isCompleted = true;
  117. if (this.onSuccess) {
  118. this.onSuccess(this);
  119. }
  120. onSuccess();
  121. } catch (e) {
  122. this.onErrorCallback(onError, "Task is done, error executing success callback(s)", e);
  123. }
  124. }
  125. }
  126. /**
  127. * Define the interface used by progress events raised during assets loading
  128. */
  129. export interface IAssetsProgressEvent {
  130. /**
  131. * Defines the number of remaining tasks to process
  132. */
  133. remainingCount: number;
  134. /**
  135. * Defines the total number of tasks
  136. */
  137. totalCount: number;
  138. /**
  139. * Defines the task that was just processed
  140. */
  141. task: AbstractAssetTask;
  142. }
  143. /**
  144. * Class used to share progress information about assets loading
  145. */
  146. export class AssetsProgressEvent implements IAssetsProgressEvent {
  147. /**
  148. * Defines the number of remaining tasks to process
  149. */
  150. public remainingCount: number;
  151. /**
  152. * Defines the total number of tasks
  153. */
  154. public totalCount: number;
  155. /**
  156. * Defines the task that was just processed
  157. */
  158. public task: AbstractAssetTask;
  159. /**
  160. * Creates a {BABYLON.AssetsProgressEvent}
  161. * @param remainingCount defines the number of remaining tasks to process
  162. * @param totalCount defines the total number of tasks
  163. * @param task defines the task that was just processed
  164. */
  165. constructor(remainingCount: number, totalCount: number, task: AbstractAssetTask) {
  166. this.remainingCount = remainingCount;
  167. this.totalCount = totalCount;
  168. this.task = task;
  169. }
  170. }
  171. /**
  172. * Define a task used by {BABYLON.AssetsManager} to load meshes
  173. */
  174. export class MeshAssetTask extends AbstractAssetTask {
  175. public loadedMeshes: Array<AbstractMesh>;
  176. public loadedParticleSystems: Array<ParticleSystem>;
  177. public loadedSkeletons: Array<Skeleton>;
  178. public onSuccess: (task: MeshAssetTask) => void;
  179. public onError: (task: MeshAssetTask, message?: string, exception?: any) => void;
  180. /**
  181. * Creates a new {BABYLON.MeshAssetTask}
  182. * @param name defines the name of the task
  183. * @param meshesNames
  184. * @param rootUrl
  185. * @param sceneFilename
  186. */
  187. constructor(public name: string, public meshesNames: any, public rootUrl: string, public sceneFilename: string) {
  188. super(name);
  189. }
  190. public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  191. SceneLoader.ImportMesh(this.meshesNames, this.rootUrl, this.sceneFilename, scene,
  192. (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => {
  193. this.loadedMeshes = meshes;
  194. this.loadedParticleSystems = particleSystems;
  195. this.loadedSkeletons = skeletons;
  196. onSuccess();
  197. }, null, (scene, message, exception) => {
  198. onError(message, exception);
  199. }
  200. );
  201. }
  202. }
  203. /**
  204. * Define a task used by {BABYLON.AssetsManager} to load text content
  205. */
  206. export class TextFileAssetTask extends AbstractAssetTask {
  207. public text: string;
  208. public onSuccess: (task: TextFileAssetTask) => void;
  209. public onError: (task: TextFileAssetTask, message?: string, exception?: any) => void;
  210. constructor(public name: string, public url: string) {
  211. super(name);
  212. }
  213. public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  214. scene._loadFile(this.url, (data) => {
  215. this.text = data as string;
  216. onSuccess();
  217. }, undefined, false, true, (request, exception) => {
  218. if (request) {
  219. onError(request.status + " " + request.statusText, exception);
  220. }
  221. });
  222. }
  223. }
  224. /**
  225. * Define a task used by {BABYLON.AssetsManager} to load binary data
  226. */
  227. export class BinaryFileAssetTask extends AbstractAssetTask {
  228. public data: ArrayBuffer;
  229. public onSuccess: (task: BinaryFileAssetTask) => void;
  230. public onError: (task: BinaryFileAssetTask, message?: string, exception?: any) => void;
  231. constructor(public name: string, public url: string) {
  232. super(name);
  233. }
  234. public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  235. scene._loadFile(this.url, (data) => {
  236. this.data = data as ArrayBuffer;
  237. onSuccess();
  238. }, undefined, true, true, (request, exception) => {
  239. if (request) {
  240. onError(request.status + " " + request.statusText, exception);
  241. }
  242. });
  243. }
  244. }
  245. /**
  246. * Define a task used by {BABYLON.AssetsManager} to load images
  247. */
  248. export class ImageAssetTask extends AbstractAssetTask {
  249. public image: HTMLImageElement;
  250. public onSuccess: (task: ImageAssetTask) => void;
  251. public onError: (task: ImageAssetTask, message?: string, exception?: any) => void;
  252. constructor(public name: string, public url: string) {
  253. super(name);
  254. }
  255. public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  256. var img = new Image();
  257. Tools.SetCorsBehavior(this.url, img);
  258. img.onload = () => {
  259. this.image = img;
  260. onSuccess();
  261. };
  262. img.onerror = (err: ErrorEvent): any => {
  263. onError("Error loading image", err);
  264. };
  265. img.src = this.url;
  266. }
  267. }
  268. export interface ITextureAssetTask<TEX extends BaseTexture> {
  269. texture: TEX;
  270. }
  271. /**
  272. * Define a task used by {BABYLON.AssetsManager} to load 2D textures
  273. */
  274. export class TextureAssetTask extends AbstractAssetTask implements ITextureAssetTask<Texture> {
  275. public texture: Texture;
  276. public onSuccess: (task: TextureAssetTask) => void;
  277. public onError: (task: TextureAssetTask, message?: string, exception?: any) => void;
  278. constructor(public name: string, public url: string, public noMipmap?: boolean, public invertY?: boolean, public samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
  279. super(name);
  280. }
  281. public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  282. var onload = () => {
  283. onSuccess();
  284. };
  285. var onerror = (message?: string, exception?: any) => {
  286. onError(message, exception);
  287. };
  288. this.texture = new Texture(this.url, scene, this.noMipmap, this.invertY, this.samplingMode, onload, onerror);
  289. }
  290. }
  291. /**
  292. * Define a task used by {BABYLON.AssetsManager} to load cube textures
  293. */
  294. export class CubeTextureAssetTask extends AbstractAssetTask implements ITextureAssetTask<CubeTexture> {
  295. public texture: CubeTexture;
  296. public onSuccess: (task: CubeTextureAssetTask) => void;
  297. public onError: (task: CubeTextureAssetTask, message?: string, exception?: any) => void;
  298. constructor(public name: string, public url: string, public extensions?: string[], public noMipmap?: boolean, public files?: string[]) {
  299. super(name);
  300. }
  301. public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  302. var onload = () => {
  303. onSuccess();
  304. };
  305. var onerror = (message?: string, exception?: any) => {
  306. onError(message, exception);
  307. };
  308. this.texture = new CubeTexture(this.url, scene, this.extensions, this.noMipmap, this.files, onload, onerror);
  309. }
  310. }
  311. /**
  312. * Define a task used by {BABYLON.AssetsManager} to load HDR cube textures
  313. */
  314. export class HDRCubeTextureAssetTask extends AbstractAssetTask implements ITextureAssetTask<HDRCubeTexture> {
  315. public texture: HDRCubeTexture;
  316. public onSuccess: (task: HDRCubeTextureAssetTask) => void;
  317. public onError: (task: HDRCubeTextureAssetTask, message?: string, exception?: any) => void;
  318. constructor(public name: string, public url: string, public size?: number, public noMipmap = false, public generateHarmonics = true, public useInGammaSpace = false, public usePMREMGenerator = false) {
  319. super(name);
  320. }
  321. public run(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
  322. var onload = () => {
  323. onSuccess();
  324. };
  325. var onerror = (message?: string, exception?: any) => {
  326. onError(message, exception);
  327. };
  328. this.texture = new HDRCubeTexture(this.url, scene, this.size, this.noMipmap, this.generateHarmonics, this.useInGammaSpace, this.usePMREMGenerator, onload, onerror);
  329. }
  330. }
  331. /**
  332. * This class can be used to easily import assets into a scene
  333. * @see http://doc.babylonjs.com/how_to/how_to_use_assetsmanager
  334. */
  335. export class AssetsManager {
  336. private _scene: Scene;
  337. private _isLoading = false;
  338. protected tasks = new Array<AbstractAssetTask>();
  339. protected waitingTasksCount = 0;
  340. protected totalTasksCount = 0;
  341. /**
  342. * Callback called when all tasks are processed
  343. * @param tasks will contains all remaining tasks (ie. all tasks which were not successful)
  344. */
  345. public onFinish: (tasks: AbstractAssetTask[]) => void;
  346. /**
  347. * Callback called when a task is successful
  348. * @param task defines the loaded task
  349. */
  350. public onTaskSuccess: (task: AbstractAssetTask) => void;
  351. /**
  352. * Callback called when a task had an error
  353. * @param task defines failed task
  354. */
  355. public onTaskError: (task: AbstractAssetTask) => void;
  356. /**
  357. * Callback called when a task is done (whatever the result is)
  358. * @param remainingCount defines the number of remaining tasks to process
  359. * @param totalCount defines the total number of tasks
  360. * @param task defines the task that was just processed
  361. */
  362. public onProgress: (remainingCount: number, totalCount: number, task: AbstractAssetTask) => void;
  363. //Observables
  364. /**
  365. * Observable called when all tasks are processed
  366. */
  367. public onTaskSuccessObservable = new Observable<AbstractAssetTask>();
  368. /**
  369. * Observable called when a task had an error
  370. */
  371. public onTaskErrorObservable = new Observable<AbstractAssetTask>();
  372. /**
  373. * Observable called when a task is successful
  374. */
  375. public onTasksDoneObservable = new Observable<AbstractAssetTask[]>();
  376. /**
  377. * Observable called when a task is done (whatever the result is)
  378. */
  379. public onProgressObservable = new Observable<IAssetsProgressEvent>();
  380. /**
  381. * Gets or sets a boolean defining if the {BABYLON.AssetsManager} should use the default loading screen
  382. * @see http://doc.babylonjs.com/how_to/creating_a_custom_loading_screen
  383. */
  384. public useDefaultLoadingScreen = true;
  385. constructor(scene: Scene) {
  386. this._scene = scene;
  387. }
  388. /**
  389. * Add a {BABYLON.MeshAssetTask} to the list of active tasks
  390. * @param taskName defines the name of the new task
  391. * @param meshesNames defines the name of meshes to load
  392. * @param rootUrl defines the root url to use to locate files
  393. * @param sceneFilename defines the filename of the scene file
  394. */
  395. public addMeshTask(taskName: string, meshesNames: any, rootUrl: string, sceneFilename: string): MeshAssetTask {
  396. var task = new MeshAssetTask(taskName, meshesNames, rootUrl, sceneFilename);
  397. this.tasks.push(task);
  398. return task;
  399. }
  400. /**
  401. * Add a {BABYLON.TextFileAssetTask} to the list of active tasks
  402. * @param taskName defines the name of the new task
  403. * @param url defines the url of the file to load
  404. */
  405. public addTextFileTask(taskName: string, url: string): TextFileAssetTask {
  406. var task = new TextFileAssetTask(taskName, url);
  407. this.tasks.push(task);
  408. return task;
  409. }
  410. /**
  411. * Add a {BABYLON.BinaryFileAssetTask} to the list of active tasks
  412. * @param taskName defines the name of the new task
  413. * @param url defines the url of the file to load
  414. */
  415. public addBinaryFileTask(taskName: string, url: string): BinaryFileAssetTask {
  416. var task = new BinaryFileAssetTask(taskName, url);
  417. this.tasks.push(task);
  418. return task;
  419. }
  420. /**
  421. * Add a {BABYLON.ImageAssetTask} to the list of active tasks
  422. * @param taskName defines the name of the new task
  423. * @param url defines the url of the file to load
  424. */
  425. public addImageTask(taskName: string, url: string): ImageAssetTask {
  426. var task = new ImageAssetTask(taskName, url);
  427. this.tasks.push(task);
  428. return task;
  429. }
  430. /**
  431. * Add a {BABYLON.TextureAssetTask} to the list of active tasks
  432. * @param taskName defines the name of the new task
  433. * @param url defines the url of the file to load
  434. * @param noMipmap defines if the texture must not receive mipmaps (false by default)
  435. * @param invertY defines if you want to invert Y axis of the loaded texture (false by default)
  436. * @param samplingMode defines the sampling mode to use (BABYLON.Texture.TRILINEAR_SAMPLINGMODE by default)
  437. */
  438. public addTextureTask(taskName: string, url: string, noMipmap?: boolean, invertY?: boolean, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE): TextureAssetTask {
  439. var task = new TextureAssetTask(taskName, url, noMipmap, invertY, samplingMode);
  440. this.tasks.push(task);
  441. return task;
  442. }
  443. /**
  444. * Add a {BABYLON.CubeTextureAssetTask} to the list of active tasks
  445. * @param taskName defines the name of the new task
  446. * @param url defines the url of the file to load
  447. * @param extensions defines the extension to use to load the cube map (can be null)
  448. * @param noMipmap defines if the texture must not receive mipmaps (false by default)
  449. * @param files defines the list of files to load (can be null)
  450. */
  451. public addCubeTextureTask(taskName: string, url: string, extensions?: string[], noMipmap?: boolean, files?: string[]): CubeTextureAssetTask {
  452. var task = new CubeTextureAssetTask(taskName, url, extensions, noMipmap, files);
  453. this.tasks.push(task);
  454. return task;
  455. }
  456. /**
  457. *
  458. * Add a {BABYLON.HDRCubeTextureAssetTask} to the list of active tasks
  459. * @param taskName defines the name of the new task
  460. * @param url defines the url of the file to load
  461. * @param size defines the size you want for the cubemap (can be null)
  462. * @param noMipmap defines if the texture must not receive mipmaps (false by default)
  463. * @param generateHarmonics defines if you want to automatically generate (true by default)
  464. * @param useInGammaSpace defines if the texture must be considered in gamma space (false by default)
  465. * @param usePMREMGenerator is a reserved parameter and must be set to false or ignored
  466. */
  467. public addHDRCubeTextureTask(taskName: string, url: string, size?: number, noMipmap = false, generateHarmonics = true, useInGammaSpace = false, usePMREMGenerator = false): HDRCubeTextureAssetTask {
  468. var task = new HDRCubeTextureAssetTask(taskName, url, size, noMipmap, generateHarmonics, useInGammaSpace, usePMREMGenerator);
  469. this.tasks.push(task);
  470. return task;
  471. }
  472. private _decreaseWaitingTasksCount(task: AbstractAssetTask): void {
  473. this.waitingTasksCount--;
  474. try {
  475. if (task.taskState === AssetTaskState.DONE) {
  476. // Let's remove successfull tasks
  477. Tools.SetImmediate(() => {
  478. let index = this.tasks.indexOf(task);
  479. if (index > -1) {
  480. this.tasks.splice(index, 1);
  481. }
  482. });
  483. }
  484. if (this.onProgress) {
  485. this.onProgress(
  486. this.waitingTasksCount,
  487. this.totalTasksCount,
  488. task
  489. );
  490. }
  491. this.onProgressObservable.notifyObservers(
  492. new AssetsProgressEvent(
  493. this.waitingTasksCount,
  494. this.totalTasksCount,
  495. task
  496. )
  497. );
  498. } catch (e) {
  499. Tools.Error("Error running progress callbacks.");
  500. console.log(e);
  501. }
  502. if (this.waitingTasksCount === 0) {
  503. try {
  504. if (this.onFinish) {
  505. this.onFinish(this.tasks);
  506. }
  507. this.onTasksDoneObservable.notifyObservers(this.tasks);
  508. } catch (e) {
  509. Tools.Error("Error running tasks-done callbacks.");
  510. console.log(e);
  511. }
  512. this._isLoading = false;
  513. this._scene.getEngine().hideLoadingUI();
  514. }
  515. }
  516. private _runTask(task: AbstractAssetTask): void {
  517. let done = () => {
  518. try {
  519. if (this.onTaskSuccess) {
  520. this.onTaskSuccess(task);
  521. }
  522. this.onTaskSuccessObservable.notifyObservers(task);
  523. this._decreaseWaitingTasksCount(task);
  524. } catch (e) {
  525. error("Error executing task success callbacks", e);
  526. }
  527. }
  528. let error = (message?: string, exception?: any) => {
  529. task._setErrorObject(message, exception);
  530. if (this.onTaskError) {
  531. this.onTaskError(task);
  532. }
  533. this.onTaskErrorObservable.notifyObservers(task);
  534. this._decreaseWaitingTasksCount(task);
  535. }
  536. task.run(this._scene, done, error);
  537. }
  538. /**
  539. * Reset the {BABYLON.AssetsManager} and remove all tasks
  540. * @return the current instance of the {BABYLON.AssetsManager}
  541. */
  542. public reset(): AssetsManager {
  543. this._isLoading = false;
  544. this.tasks = new Array<AbstractAssetTask>();
  545. return this;
  546. }
  547. /**
  548. * Start the loading process
  549. * @return the current instance of the {BABYLON.AssetsManager}
  550. */
  551. public load(): AssetsManager {
  552. if (this._isLoading) {
  553. return this;
  554. }
  555. this._isLoading = true;
  556. this.waitingTasksCount = this.tasks.length;
  557. this.totalTasksCount = this.tasks.length;
  558. if (this.waitingTasksCount === 0) {
  559. if (this.onFinish) {
  560. this.onFinish(this.tasks);
  561. }
  562. this.onTasksDoneObservable.notifyObservers(this.tasks);
  563. return this;
  564. }
  565. if (this.useDefaultLoadingScreen) {
  566. this._scene.getEngine().displayLoadingUI();
  567. }
  568. for (var index = 0; index < this.tasks.length; index++) {
  569. var task = this.tasks[index];
  570. this._runTask(task);
  571. }
  572. return this;
  573. }
  574. }
  575. }