glTFFileLoader.ts 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. import * as GLTF2 from "babylonjs-gltf2interface";
  2. import { Nullable } from "babylonjs/types";
  3. import { Observable, Observer } from "babylonjs/Misc/observable";
  4. import { Tools } from "babylonjs/Misc/tools";
  5. import { Camera } from "babylonjs/Cameras/camera";
  6. import { AnimationGroup } from "babylonjs/Animations/animationGroup";
  7. import { Skeleton } from "babylonjs/Bones/skeleton";
  8. import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
  9. import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
  10. import { Material } from "babylonjs/Materials/material";
  11. import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
  12. import { SceneLoader, ISceneLoaderPluginFactory, ISceneLoaderPlugin, ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginExtensions } from "babylonjs/Loading/sceneLoader";
  13. import { AssetContainer } from "babylonjs/assetContainer";
  14. import { Scene, IDisposable } from "babylonjs/scene";
  15. import { WebRequest } from "babylonjs/Misc/webRequest";
  16. import { IFileRequest } from "babylonjs/Misc/fileRequest";
  17. import { Logger } from 'babylonjs/Misc/logger';
  18. import { DataReader, IDataBuffer } from 'babylonjs/Misc/dataReader';
  19. /**
  20. * glTF validator object
  21. */
  22. declare var GLTFValidator: GLTF2.IGLTFValidator;
  23. /**
  24. * Mode that determines the coordinate system to use.
  25. */
  26. export enum GLTFLoaderCoordinateSystemMode {
  27. /**
  28. * Automatically convert the glTF right-handed data to the appropriate system based on the current coordinate system mode of the scene.
  29. */
  30. AUTO,
  31. /**
  32. * Sets the useRightHandedSystem flag on the scene.
  33. */
  34. FORCE_RIGHT_HANDED,
  35. }
  36. /**
  37. * Mode that determines what animations will start.
  38. */
  39. export enum GLTFLoaderAnimationStartMode {
  40. /**
  41. * No animation will start.
  42. */
  43. NONE,
  44. /**
  45. * The first animation will start.
  46. */
  47. FIRST,
  48. /**
  49. * All animations will start.
  50. */
  51. ALL,
  52. }
  53. /**
  54. * Interface that contains the data for the glTF asset.
  55. */
  56. export interface IGLTFLoaderData {
  57. /**
  58. * The object that represents the glTF JSON.
  59. */
  60. json: Object;
  61. /**
  62. * The BIN chunk of a binary glTF.
  63. */
  64. bin: Nullable<IDataBuffer>;
  65. }
  66. /**
  67. * Interface for extending the loader.
  68. */
  69. export interface IGLTFLoaderExtension {
  70. /**
  71. * The name of this extension.
  72. */
  73. readonly name: string;
  74. /**
  75. * Defines whether this extension is enabled.
  76. */
  77. enabled: boolean;
  78. /**
  79. * Defines the order of this extension.
  80. * The loader sorts the extensions using these values when loading.
  81. */
  82. order?: number;
  83. }
  84. /**
  85. * Loader state.
  86. */
  87. export enum GLTFLoaderState {
  88. /**
  89. * The asset is loading.
  90. */
  91. LOADING,
  92. /**
  93. * The asset is ready for rendering.
  94. */
  95. READY,
  96. /**
  97. * The asset is completely loaded.
  98. */
  99. COMPLETE
  100. }
  101. /** @hidden */
  102. export interface IGLTFLoader extends IDisposable {
  103. readonly state: Nullable<GLTFLoaderState>;
  104. importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string) => Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }>;
  105. loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string) => Promise<void>;
  106. }
  107. /**
  108. * File loader for loading glTF files into a scene.
  109. */
  110. export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
  111. /** @hidden */
  112. public static _CreateGLTF1Loader: (parent: GLTFFileLoader) => IGLTFLoader;
  113. /** @hidden */
  114. public static _CreateGLTF2Loader: (parent: GLTFFileLoader) => IGLTFLoader;
  115. // --------------
  116. // Common options
  117. // --------------
  118. /**
  119. * Raised when the asset has been parsed
  120. */
  121. public onParsedObservable = new Observable<IGLTFLoaderData>();
  122. private _onParsedObserver: Nullable<Observer<IGLTFLoaderData>>;
  123. /**
  124. * Raised when the asset has been parsed
  125. */
  126. public set onParsed(callback: (loaderData: IGLTFLoaderData) => void) {
  127. if (this._onParsedObserver) {
  128. this.onParsedObservable.remove(this._onParsedObserver);
  129. }
  130. this._onParsedObserver = this.onParsedObservable.add(callback);
  131. }
  132. // ----------
  133. // V1 options
  134. // ----------
  135. /**
  136. * Set this property to false to disable incremental loading which delays the loader from calling the success callback until after loading the meshes and shaders.
  137. * Textures always loads asynchronously. For example, the success callback can compute the bounding information of the loaded meshes when incremental loading is disabled.
  138. * Defaults to true.
  139. * @hidden
  140. */
  141. public static IncrementalLoading = true;
  142. /**
  143. * Set this property to true in order to work with homogeneous coordinates, available with some converters and exporters.
  144. * Defaults to false. See https://en.wikipedia.org/wiki/Homogeneous_coordinates.
  145. * @hidden
  146. */
  147. public static HomogeneousCoordinates = false;
  148. // ----------
  149. // V2 options
  150. // ----------
  151. /**
  152. * The coordinate system mode. Defaults to AUTO.
  153. */
  154. public coordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
  155. /**
  156. * The animation start mode. Defaults to FIRST.
  157. */
  158. public animationStartMode = GLTFLoaderAnimationStartMode.FIRST;
  159. /**
  160. * Defines if the loader should compile materials before raising the success callback. Defaults to false.
  161. */
  162. public compileMaterials = false;
  163. /**
  164. * Defines if the loader should also compile materials with clip planes. Defaults to false.
  165. */
  166. public useClipPlane = false;
  167. /**
  168. * Defines if the loader should compile shadow generators before raising the success callback. Defaults to false.
  169. */
  170. public compileShadowGenerators = false;
  171. /**
  172. * Defines if the Alpha blended materials are only applied as coverage.
  173. * If false, (default) The luminance of each pixel will reduce its opacity to simulate the behaviour of most physical materials.
  174. * If true, no extra effects are applied to transparent pixels.
  175. */
  176. public transparencyAsCoverage = false;
  177. /**
  178. * Defines if the loader should use range requests when load binary glTF files from HTTP.
  179. * Enabling will disable offline support and glTF validator.
  180. * Defaults to false.
  181. */
  182. public useRangeRequests = false;
  183. /**
  184. * Defines if the loader should create instances when multiple glTF nodes point to the same glTF mesh. Defaults to true.
  185. */
  186. public createInstances = true;
  187. /**
  188. * Function called before loading a url referenced by the asset.
  189. */
  190. public preprocessUrlAsync = (url: string) => Promise.resolve(url);
  191. /**
  192. * Observable raised when the loader creates a mesh after parsing the glTF properties of the mesh.
  193. */
  194. public readonly onMeshLoadedObservable = new Observable<AbstractMesh>();
  195. private _onMeshLoadedObserver: Nullable<Observer<AbstractMesh>>;
  196. /**
  197. * Callback raised when the loader creates a mesh after parsing the glTF properties of the mesh.
  198. */
  199. public set onMeshLoaded(callback: (mesh: AbstractMesh) => void) {
  200. if (this._onMeshLoadedObserver) {
  201. this.onMeshLoadedObservable.remove(this._onMeshLoadedObserver);
  202. }
  203. this._onMeshLoadedObserver = this.onMeshLoadedObservable.add(callback);
  204. }
  205. /**
  206. * Observable raised when the loader creates a texture after parsing the glTF properties of the texture.
  207. */
  208. public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
  209. private _onTextureLoadedObserver: Nullable<Observer<BaseTexture>>;
  210. /**
  211. * Callback raised when the loader creates a texture after parsing the glTF properties of the texture.
  212. */
  213. public set onTextureLoaded(callback: (texture: BaseTexture) => void) {
  214. if (this._onTextureLoadedObserver) {
  215. this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
  216. }
  217. this._onTextureLoadedObserver = this.onTextureLoadedObservable.add(callback);
  218. }
  219. /**
  220. * Observable raised when the loader creates a material after parsing the glTF properties of the material.
  221. */
  222. public readonly onMaterialLoadedObservable = new Observable<Material>();
  223. private _onMaterialLoadedObserver: Nullable<Observer<Material>>;
  224. /**
  225. * Callback raised when the loader creates a material after parsing the glTF properties of the material.
  226. */
  227. public set onMaterialLoaded(callback: (material: Material) => void) {
  228. if (this._onMaterialLoadedObserver) {
  229. this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
  230. }
  231. this._onMaterialLoadedObserver = this.onMaterialLoadedObservable.add(callback);
  232. }
  233. /**
  234. * Observable raised when the loader creates a camera after parsing the glTF properties of the camera.
  235. */
  236. public readonly onCameraLoadedObservable = new Observable<Camera>();
  237. private _onCameraLoadedObserver: Nullable<Observer<Camera>>;
  238. /**
  239. * Callback raised when the loader creates a camera after parsing the glTF properties of the camera.
  240. */
  241. public set onCameraLoaded(callback: (camera: Camera) => void) {
  242. if (this._onCameraLoadedObserver) {
  243. this.onCameraLoadedObservable.remove(this._onCameraLoadedObserver);
  244. }
  245. this._onCameraLoadedObserver = this.onCameraLoadedObservable.add(callback);
  246. }
  247. /**
  248. * Observable raised when the asset is completely loaded, immediately before the loader is disposed.
  249. * For assets with LODs, raised when all of the LODs are complete.
  250. * For assets without LODs, raised when the model is complete, immediately after the loader resolves the returned promise.
  251. */
  252. public readonly onCompleteObservable = new Observable<void>();
  253. private _onCompleteObserver: Nullable<Observer<void>>;
  254. /**
  255. * Callback raised when the asset is completely loaded, immediately before the loader is disposed.
  256. * For assets with LODs, raised when all of the LODs are complete.
  257. * For assets without LODs, raised when the model is complete, immediately after the loader resolves the returned promise.
  258. */
  259. public set onComplete(callback: () => void) {
  260. if (this._onCompleteObserver) {
  261. this.onCompleteObservable.remove(this._onCompleteObserver);
  262. }
  263. this._onCompleteObserver = this.onCompleteObservable.add(callback);
  264. }
  265. /**
  266. * Observable raised when an error occurs.
  267. */
  268. public readonly onErrorObservable = new Observable<any>();
  269. private _onErrorObserver: Nullable<Observer<any>>;
  270. /**
  271. * Callback raised when an error occurs.
  272. */
  273. public set onError(callback: (reason: any) => void) {
  274. if (this._onErrorObserver) {
  275. this.onErrorObservable.remove(this._onErrorObserver);
  276. }
  277. this._onErrorObserver = this.onErrorObservable.add(callback);
  278. }
  279. /**
  280. * Observable raised after the loader is disposed.
  281. */
  282. public readonly onDisposeObservable = new Observable<void>();
  283. private _onDisposeObserver: Nullable<Observer<void>>;
  284. /**
  285. * Callback raised after the loader is disposed.
  286. */
  287. public set onDispose(callback: () => void) {
  288. if (this._onDisposeObserver) {
  289. this.onDisposeObservable.remove(this._onDisposeObserver);
  290. }
  291. this._onDisposeObserver = this.onDisposeObservable.add(callback);
  292. }
  293. /**
  294. * Observable raised after a loader extension is created.
  295. * Set additional options for a loader extension in this event.
  296. */
  297. public readonly onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
  298. private _onExtensionLoadedObserver: Nullable<Observer<IGLTFLoaderExtension>>;
  299. /**
  300. * Callback raised after a loader extension is created.
  301. */
  302. public set onExtensionLoaded(callback: (extension: IGLTFLoaderExtension) => void) {
  303. if (this._onExtensionLoadedObserver) {
  304. this.onExtensionLoadedObservable.remove(this._onExtensionLoadedObserver);
  305. }
  306. this._onExtensionLoadedObserver = this.onExtensionLoadedObservable.add(callback);
  307. }
  308. /**
  309. * Defines if the loader logging is enabled.
  310. */
  311. public get loggingEnabled(): boolean {
  312. return this._loggingEnabled;
  313. }
  314. public set loggingEnabled(value: boolean) {
  315. if (this._loggingEnabled === value) {
  316. return;
  317. }
  318. this._loggingEnabled = value;
  319. if (this._loggingEnabled) {
  320. this._log = this._logEnabled;
  321. }
  322. else {
  323. this._log = this._logDisabled;
  324. }
  325. }
  326. /**
  327. * Defines if the loader should capture performance counters.
  328. */
  329. public get capturePerformanceCounters(): boolean {
  330. return this._capturePerformanceCounters;
  331. }
  332. public set capturePerformanceCounters(value: boolean) {
  333. if (this._capturePerformanceCounters === value) {
  334. return;
  335. }
  336. this._capturePerformanceCounters = value;
  337. if (this._capturePerformanceCounters) {
  338. this._startPerformanceCounter = this._startPerformanceCounterEnabled;
  339. this._endPerformanceCounter = this._endPerformanceCounterEnabled;
  340. }
  341. else {
  342. this._startPerformanceCounter = this._startPerformanceCounterDisabled;
  343. this._endPerformanceCounter = this._endPerformanceCounterDisabled;
  344. }
  345. }
  346. /**
  347. * Defines if the loader should validate the asset.
  348. */
  349. public validate = false;
  350. /**
  351. * Observable raised after validation when validate is set to true. The event data is the result of the validation.
  352. */
  353. public readonly onValidatedObservable = new Observable<GLTF2.IGLTFValidationResults>();
  354. private _onValidatedObserver: Nullable<Observer<GLTF2.IGLTFValidationResults>>;
  355. /**
  356. * Callback raised after a loader extension is created.
  357. */
  358. public set onValidated(callback: (results: GLTF2.IGLTFValidationResults) => void) {
  359. if (this._onValidatedObserver) {
  360. this.onValidatedObservable.remove(this._onValidatedObserver);
  361. }
  362. this._onValidatedObserver = this.onValidatedObservable.add(callback);
  363. }
  364. private _loader: Nullable<IGLTFLoader> = null;
  365. /**
  366. * Name of the loader ("gltf")
  367. */
  368. public name = "gltf";
  369. /** @hidden */
  370. public extensions: ISceneLoaderPluginExtensions = {
  371. ".gltf": { isBinary: false },
  372. ".glb": { isBinary: true }
  373. };
  374. /**
  375. * Disposes the loader, releases resources during load, and cancels any outstanding requests.
  376. */
  377. public dispose(): void {
  378. if (this._loader) {
  379. this._loader.dispose();
  380. this._loader = null;
  381. }
  382. this._clear();
  383. this.onDisposeObservable.notifyObservers(undefined);
  384. this.onDisposeObservable.clear();
  385. }
  386. /** @hidden */
  387. public _clear(): void {
  388. this.preprocessUrlAsync = (url) => Promise.resolve(url);
  389. this.onMeshLoadedObservable.clear();
  390. this.onTextureLoadedObservable.clear();
  391. this.onMaterialLoadedObservable.clear();
  392. this.onCameraLoadedObservable.clear();
  393. this.onCompleteObservable.clear();
  394. this.onExtensionLoadedObservable.clear();
  395. }
  396. /** @hidden */
  397. public requestFile(scene: Scene, url: string, onSuccess: (data: any, request?: WebRequest) => void, onProgress?: (ev: ProgressEvent) => void, useArrayBuffer?: boolean, onError?: (error: any) => void): IFileRequest {
  398. if (useArrayBuffer) {
  399. if (this.useRangeRequests) {
  400. if (this.validate) {
  401. Logger.Warn("glTF validation is not supported when range requests are enabled");
  402. }
  403. const fileRequests = new Array<IFileRequest>();
  404. const aggregatedFileRequest: IFileRequest = {
  405. abort: () => fileRequests.forEach((fileRequest) => fileRequest.abort()),
  406. onCompleteObservable: new Observable<IFileRequest>()
  407. };
  408. const dataBuffer = {
  409. readAsync: (byteOffset: number, byteLength: number) => {
  410. return new Promise<ArrayBufferView>((resolve, reject) => {
  411. fileRequests.push(scene._requestFile(url, (data, webRequest) => {
  412. dataBuffer.byteLength = Number(webRequest!.getResponseHeader("Content-Range")!.split("/")[1]);
  413. resolve(new Uint8Array(data as ArrayBuffer));
  414. }, onProgress, true, true, (error) => {
  415. reject(error);
  416. }, (webRequest) => {
  417. webRequest.setRequestHeader("Range", `bytes=${byteOffset}-${byteOffset + byteLength - 1}`);
  418. }));
  419. });
  420. },
  421. byteLength: 0
  422. };
  423. this._unpackBinaryAsync(new DataReader(dataBuffer)).then((loaderData) => {
  424. aggregatedFileRequest.onCompleteObservable.notifyObservers(aggregatedFileRequest);
  425. onSuccess(loaderData);
  426. }, onError);
  427. return aggregatedFileRequest;
  428. }
  429. return scene._requestFile(url, (data, request) => {
  430. const arrayBuffer = data as ArrayBuffer;
  431. this._unpackBinaryAsync(new DataReader({
  432. readAsync: (byteOffset, byteLength) => Promise.resolve(new Uint8Array(arrayBuffer, byteOffset, byteLength)),
  433. byteLength: arrayBuffer.byteLength
  434. })).then((loaderData) => {
  435. onSuccess(loaderData, request);
  436. }, onError);
  437. }, onProgress, true, true, onError);
  438. }
  439. return scene._requestFile(url, (data, response) => {
  440. this._validateAsync(scene, data, Tools.GetFolderPath(url), Tools.GetFilename(url));
  441. onSuccess({ json: this._parseJson(data as string) }, response);
  442. }, onProgress, true, false, onError);
  443. }
  444. /** @hidden */
  445. public readFile(scene: Scene, file: File, onSuccess: (data: any) => void, onProgress?: (ev: ProgressEvent) => any, useArrayBuffer?: boolean, onError?: (error: any) => void): IFileRequest {
  446. return scene._readFile(file, (data) => {
  447. this._validateAsync(scene, data, "file:", file.name);
  448. if (useArrayBuffer) {
  449. const arrayBuffer = data as ArrayBuffer;
  450. this._unpackBinaryAsync(new DataReader({
  451. readAsync: (byteOffset, byteLength) => Promise.resolve(new Uint8Array(arrayBuffer, byteOffset, byteLength)),
  452. byteLength: arrayBuffer.byteLength
  453. })).then(onSuccess, onError);
  454. }
  455. else {
  456. onSuccess({ json: this._parseJson(data as string) });
  457. }
  458. }, onProgress, useArrayBuffer, onError);
  459. }
  460. /** @hidden */
  461. public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }> {
  462. return Promise.resolve().then(() => {
  463. this.onParsedObservable.notifyObservers(data);
  464. this.onParsedObservable.clear();
  465. this._log(`Loading ${fileName || ""}`);
  466. this._loader = this._getLoader(data);
  467. return this._loader.importMeshAsync(meshesNames, scene, data, rootUrl, onProgress, fileName);
  468. });
  469. }
  470. /** @hidden */
  471. public loadAsync(scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
  472. return Promise.resolve().then(() => {
  473. this.onParsedObservable.notifyObservers(data);
  474. this.onParsedObservable.clear();
  475. this._log(`Loading ${fileName || ""}`);
  476. this._loader = this._getLoader(data);
  477. return this._loader.loadAsync(scene, data, rootUrl, onProgress, fileName);
  478. });
  479. }
  480. /** @hidden */
  481. public loadAssetContainerAsync(scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<AssetContainer> {
  482. return Promise.resolve().then(() => {
  483. this.onParsedObservable.notifyObservers(data);
  484. this.onParsedObservable.clear();
  485. this._log(`Loading ${fileName || ""}`);
  486. this._loader = this._getLoader(data);
  487. // Get materials/textures when loading to add to container
  488. const materials: Array<Material> = [];
  489. this.onMaterialLoadedObservable.add((material) => {
  490. materials.push(material);
  491. });
  492. const textures: Array<BaseTexture> = [];
  493. this.onTextureLoadedObservable.add((texture) => {
  494. textures.push(texture);
  495. });
  496. return this._loader.importMeshAsync(null, scene, data, rootUrl, onProgress, fileName).then((result) => {
  497. const container = new AssetContainer(scene);
  498. Array.prototype.push.apply(container.meshes, result.meshes);
  499. Array.prototype.push.apply(container.particleSystems, result.particleSystems);
  500. Array.prototype.push.apply(container.skeletons, result.skeletons);
  501. Array.prototype.push.apply(container.animationGroups, result.animationGroups);
  502. Array.prototype.push.apply(container.materials, materials);
  503. Array.prototype.push.apply(container.textures, textures);
  504. container.removeAllFromScene();
  505. return container;
  506. });
  507. });
  508. }
  509. /** @hidden */
  510. public canDirectLoad(data: string): boolean {
  511. return data.indexOf("asset") !== -1 && data.indexOf("version") !== -1;
  512. }
  513. /** @hidden */
  514. public directLoad(scene: Scene, data: string): any {
  515. this._validateAsync(scene, data);
  516. return { json: this._parseJson(data) };
  517. }
  518. /**
  519. * The callback that allows custom handling of the root url based on the response url.
  520. * @param rootUrl the original root url
  521. * @param responseURL the response url if available
  522. * @returns the new root url
  523. */
  524. public rewriteRootURL?(rootUrl: string, responseURL?: string): string;
  525. /** @hidden */
  526. public createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync {
  527. return new GLTFFileLoader();
  528. }
  529. /**
  530. * The loader state or null if the loader is not active.
  531. */
  532. public get loaderState(): Nullable<GLTFLoaderState> {
  533. return this._loader ? this._loader.state : null;
  534. }
  535. /**
  536. * Returns a promise that resolves when the asset is completely loaded.
  537. * @returns a promise that resolves when the asset is completely loaded.
  538. */
  539. public whenCompleteAsync(): Promise<void> {
  540. return new Promise((resolve, reject) => {
  541. this.onCompleteObservable.addOnce(() => {
  542. resolve();
  543. });
  544. this.onErrorObservable.addOnce((reason) => {
  545. reject(reason);
  546. });
  547. });
  548. }
  549. private _validateAsync(scene: Scene, data: string | ArrayBuffer, rootUrl = "", fileName?: string): Promise<void> {
  550. if (!this.validate || typeof GLTFValidator === "undefined") {
  551. return Promise.resolve();
  552. }
  553. this._startPerformanceCounter("Validate JSON");
  554. const options: GLTF2.IGLTFValidationOptions = {
  555. externalResourceFunction: (uri) => {
  556. return this.preprocessUrlAsync(rootUrl + uri)
  557. .then((url) => scene._loadFileAsync(url, undefined, true, true))
  558. .then((data) => new Uint8Array(data as ArrayBuffer));
  559. }
  560. };
  561. if (fileName) {
  562. options.uri = (rootUrl === "file:" ? fileName : rootUrl + fileName);
  563. }
  564. const promise = (data instanceof ArrayBuffer)
  565. ? GLTFValidator.validateBytes(new Uint8Array(data), options)
  566. : GLTFValidator.validateString(data, options);
  567. return promise.then((result) => {
  568. this._endPerformanceCounter("Validate JSON");
  569. this.onValidatedObservable.notifyObservers(result);
  570. this.onValidatedObservable.clear();
  571. }, (reason) => {
  572. this._endPerformanceCounter("Validate JSON");
  573. Tools.Warn(`Failed to validate: ${reason}`);
  574. this.onValidatedObservable.clear();
  575. });
  576. }
  577. private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
  578. const asset = (<any>loaderData.json).asset || {};
  579. this._log(`Asset version: ${asset.version}`);
  580. asset.minVersion && this._log(`Asset minimum version: ${asset.minVersion}`);
  581. asset.generator && this._log(`Asset generator: ${asset.generator}`);
  582. const version = GLTFFileLoader._parseVersion(asset.version);
  583. if (!version) {
  584. throw new Error("Invalid version: " + asset.version);
  585. }
  586. if (asset.minVersion !== undefined) {
  587. const minVersion = GLTFFileLoader._parseVersion(asset.minVersion);
  588. if (!minVersion) {
  589. throw new Error("Invalid minimum version: " + asset.minVersion);
  590. }
  591. if (GLTFFileLoader._compareVersion(minVersion, { major: 2, minor: 0 }) > 0) {
  592. throw new Error("Incompatible minimum version: " + asset.minVersion);
  593. }
  594. }
  595. const createLoaders: { [key: number]: (parent: GLTFFileLoader) => IGLTFLoader } = {
  596. 1: GLTFFileLoader._CreateGLTF1Loader,
  597. 2: GLTFFileLoader._CreateGLTF2Loader
  598. };
  599. const createLoader = createLoaders[version.major];
  600. if (!createLoader) {
  601. throw new Error("Unsupported version: " + asset.version);
  602. }
  603. return createLoader(this);
  604. }
  605. private _parseJson(json: string): Object {
  606. this._startPerformanceCounter("Parse JSON");
  607. this._log(`JSON length: ${json.length}`);
  608. const parsed = JSON.parse(json);
  609. this._endPerformanceCounter("Parse JSON");
  610. return parsed;
  611. }
  612. private _unpackBinaryAsync(dataReader: DataReader): Promise<IGLTFLoaderData> {
  613. this._startPerformanceCounter("Unpack Binary");
  614. // Read magic + version + length + json length + json format
  615. return dataReader.loadAsync(20).then(() => {
  616. const Binary = {
  617. Magic: 0x46546C67
  618. };
  619. const magic = dataReader.readUint32();
  620. if (magic !== Binary.Magic) {
  621. throw new Error("Unexpected magic: " + magic);
  622. }
  623. const version = dataReader.readUint32();
  624. if (this.loggingEnabled) {
  625. this._log(`Binary version: ${version}`);
  626. }
  627. const length = dataReader.readUint32();
  628. if (length !== dataReader.buffer.byteLength) {
  629. throw new Error(`Length in header does not match actual data length: ${length} != ${dataReader.buffer.byteLength}`);
  630. }
  631. let unpacked: Promise<IGLTFLoaderData>;
  632. switch (version) {
  633. case 1: {
  634. unpacked = this._unpackBinaryV1Async(dataReader);
  635. break;
  636. }
  637. case 2: {
  638. unpacked = this._unpackBinaryV2Async(dataReader);
  639. break;
  640. }
  641. default: {
  642. throw new Error("Unsupported version: " + version);
  643. }
  644. }
  645. this._endPerformanceCounter("Unpack Binary");
  646. return unpacked;
  647. });
  648. }
  649. private _unpackBinaryV1Async(dataReader: DataReader): Promise<IGLTFLoaderData> {
  650. const ContentFormat = {
  651. JSON: 0
  652. };
  653. const contentLength = dataReader.readUint32();
  654. const contentFormat = dataReader.readUint32();
  655. if (contentFormat !== ContentFormat.JSON) {
  656. throw new Error(`Unexpected content format: ${contentFormat}`);
  657. }
  658. const bodyLength = dataReader.buffer.byteLength - dataReader.byteOffset;
  659. const data: IGLTFLoaderData = { json: this._parseJson(dataReader.readString(contentLength)), bin: null };
  660. if (bodyLength !== 0) {
  661. const startByteOffset = dataReader.byteOffset;
  662. data.bin = {
  663. readAsync: (byteOffset, byteLength) => dataReader.buffer.readAsync(startByteOffset + byteOffset, byteLength),
  664. byteLength: bodyLength
  665. };
  666. }
  667. return Promise.resolve(data);
  668. }
  669. private _unpackBinaryV2Async(dataReader: DataReader): Promise<IGLTFLoaderData> {
  670. const ChunkFormat = {
  671. JSON: 0x4E4F534A,
  672. BIN: 0x004E4942
  673. };
  674. // Read the JSON chunk header.
  675. const chunkLength = dataReader.readUint32();
  676. const chunkFormat = dataReader.readUint32();
  677. if (chunkFormat !== ChunkFormat.JSON) {
  678. throw new Error("First chunk format is not JSON");
  679. }
  680. // Bail if there are no other chunks.
  681. if (dataReader.byteOffset + chunkLength === dataReader.buffer.byteLength) {
  682. return dataReader.loadAsync(chunkLength).then(() => {
  683. return { json: this._parseJson(dataReader.readString(chunkLength)), bin: null };
  684. });
  685. }
  686. // Read the JSON chunk and the length and type of the next chunk.
  687. return dataReader.loadAsync(chunkLength + 8).then(() => {
  688. const data: IGLTFLoaderData = { json: this._parseJson(dataReader.readString(chunkLength)), bin: null };
  689. const readAsync = (): Promise<IGLTFLoaderData> => {
  690. const chunkLength = dataReader.readUint32();
  691. const chunkFormat = dataReader.readUint32();
  692. switch (chunkFormat) {
  693. case ChunkFormat.JSON: {
  694. throw new Error("Unexpected JSON chunk");
  695. }
  696. case ChunkFormat.BIN: {
  697. const startByteOffset = dataReader.byteOffset;
  698. data.bin = {
  699. readAsync: (byteOffset, byteLength) => dataReader.buffer.readAsync(startByteOffset + byteOffset, byteLength),
  700. byteLength: chunkLength
  701. };
  702. dataReader.skipBytes(chunkLength);
  703. break;
  704. }
  705. default: {
  706. // ignore unrecognized chunkFormat
  707. dataReader.skipBytes(chunkLength);
  708. break;
  709. }
  710. }
  711. if (dataReader.byteOffset !== dataReader.buffer.byteLength) {
  712. return dataReader.loadAsync(8).then(readAsync);
  713. }
  714. return Promise.resolve(data);
  715. };
  716. return readAsync();
  717. });
  718. }
  719. private static _parseVersion(version: string): Nullable<{ major: number, minor: number }> {
  720. if (version === "1.0" || version === "1.0.1") {
  721. return {
  722. major: 1,
  723. minor: 0
  724. };
  725. }
  726. const match = (version + "").match(/^(\d+)\.(\d+)/);
  727. if (!match) {
  728. return null;
  729. }
  730. return {
  731. major: parseInt(match[1]),
  732. minor: parseInt(match[2])
  733. };
  734. }
  735. private static _compareVersion(a: { major: number, minor: number }, b: { major: number, minor: number }): number {
  736. if (a.major > b.major) { return 1; }
  737. if (a.major < b.major) { return -1; }
  738. if (a.minor > b.minor) { return 1; }
  739. if (a.minor < b.minor) { return -1; }
  740. return 0;
  741. }
  742. private static readonly _logSpaces = " ";
  743. private _logIndentLevel = 0;
  744. private _loggingEnabled = false;
  745. /** @hidden */
  746. public _log = this._logDisabled;
  747. /** @hidden */
  748. public _logOpen(message: string): void {
  749. this._log(message);
  750. this._logIndentLevel++;
  751. }
  752. /** @hidden */
  753. public _logClose(): void {
  754. --this._logIndentLevel;
  755. }
  756. private _logEnabled(message: string): void {
  757. const spaces = GLTFFileLoader._logSpaces.substr(0, this._logIndentLevel * 2);
  758. Logger.Log(`${spaces}${message}`);
  759. }
  760. private _logDisabled(message: string): void {
  761. }
  762. private _capturePerformanceCounters = false;
  763. /** @hidden */
  764. public _startPerformanceCounter = this._startPerformanceCounterDisabled;
  765. /** @hidden */
  766. public _endPerformanceCounter = this._endPerformanceCounterDisabled;
  767. private _startPerformanceCounterEnabled(counterName: string): void {
  768. Tools.StartPerformanceCounter(counterName);
  769. }
  770. private _startPerformanceCounterDisabled(counterName: string): void {
  771. }
  772. private _endPerformanceCounterEnabled(counterName: string): void {
  773. Tools.EndPerformanceCounter(counterName);
  774. }
  775. private _endPerformanceCounterDisabled(counterName: string): void {
  776. }
  777. }
  778. if (SceneLoader) {
  779. SceneLoader.RegisterPlugin(new GLTFFileLoader());
  780. }