glTFFileLoader.ts 33 KB

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