babylon.glTFFileLoader.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /// <reference path="../../../dist/preview release/babylon.d.ts"/>
  2. module BABYLON {
  3. export enum GLTFLoaderCoordinateSystemMode {
  4. /**
  5. * Automatically convert the glTF right-handed data to the appropriate system based on the current coordinate system mode of the scene.
  6. */
  7. AUTO,
  8. /**
  9. * Sets the useRightHandedSystem flag on the scene.
  10. */
  11. FORCE_RIGHT_HANDED,
  12. }
  13. export enum GLTFLoaderAnimationStartMode {
  14. /**
  15. * No animation will start.
  16. */
  17. NONE,
  18. /**
  19. * The first animation will start.
  20. */
  21. FIRST,
  22. /**
  23. * All animations will start.
  24. */
  25. ALL,
  26. }
  27. export interface IGLTFLoaderData {
  28. json: Object;
  29. bin: Nullable<ArrayBufferView>;
  30. }
  31. export interface IGLTFLoader extends IDisposable {
  32. importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
  33. loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
  34. }
  35. export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
  36. public static CreateGLTFLoaderV1: (parent: GLTFFileLoader) => IGLTFLoader;
  37. public static CreateGLTFLoaderV2: (parent: GLTFFileLoader) => IGLTFLoader;
  38. // #region Common options
  39. /**
  40. * Raised when the asset has been parsed.
  41. * The data.json property stores the glTF JSON.
  42. * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
  43. */
  44. public onParsed: (data: IGLTFLoaderData) => void;
  45. // #endregion
  46. // #region V1 options
  47. public static IncrementalLoading = true;
  48. public static HomogeneousCoordinates = false;
  49. // #endregion
  50. // #region V2 options
  51. /**
  52. * The coordinate system mode (AUTO, FORCE_RIGHT_HANDED).
  53. */
  54. public coordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
  55. /**
  56. * The animation start mode (NONE, FIRST, ALL).
  57. */
  58. public animationStartMode = GLTFLoaderAnimationStartMode.FIRST;
  59. /**
  60. * Set to true to compile materials before raising the success callback.
  61. */
  62. public compileMaterials = false;
  63. /**
  64. * Set to true to also compile materials with clip planes.
  65. */
  66. public useClipPlane = false;
  67. /**
  68. * Set to true to compile shadow generators before raising the success callback.
  69. */
  70. public compileShadowGenerators = false;
  71. /**
  72. * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
  73. */
  74. public onMeshLoaded: (mesh: AbstractMesh) => void;
  75. /**
  76. * Raised when the loader creates a texture after parsing the glTF properties of the texture.
  77. */
  78. public onTextureLoaded: (texture: BaseTexture) => void;
  79. /**
  80. * Raised when the loader creates a material after parsing the glTF properties of the material.
  81. */
  82. public onMaterialLoaded: (material: Material) => void;
  83. /**
  84. * Raised when the asset is completely loaded, immediately before the loader is disposed.
  85. * For assets with LODs, raised when all of the LODs are complete.
  86. * For assets without LODs, raised when the model is complete, immediately after onSuccess.
  87. */
  88. public onComplete: () => void;
  89. // #endregion
  90. private _loader: IGLTFLoader;
  91. public name = "gltf";
  92. public extensions: ISceneLoaderPluginExtensions = {
  93. ".gltf": { isBinary: false },
  94. ".glb": { isBinary: true }
  95. };
  96. /**
  97. * Disposes the loader, releases resources during load, and cancels any outstanding requests.
  98. */
  99. public dispose(): void {
  100. if (this._loader) {
  101. this._loader.dispose();
  102. }
  103. }
  104. public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
  105. try {
  106. const loaderData = GLTFFileLoader._parse(data);
  107. if (this.onParsed) {
  108. this.onParsed(loaderData);
  109. }
  110. this._loader = this._getLoader(loaderData);
  111. this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
  112. }
  113. catch (e) {
  114. if (onError) {
  115. onError(e.message, e);
  116. }
  117. else {
  118. Tools.Error(e.message);
  119. }
  120. }
  121. }
  122. public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
  123. try {
  124. const loaderData = GLTFFileLoader._parse(data);
  125. if (this.onParsed) {
  126. this.onParsed(loaderData);
  127. }
  128. this._loader = this._getLoader(loaderData);
  129. this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
  130. }
  131. catch (e) {
  132. if (onError) {
  133. onError(e.message, e);
  134. }
  135. else {
  136. Tools.Error(e.message);
  137. }
  138. }
  139. }
  140. public canDirectLoad(data: string): boolean {
  141. return ((data.indexOf("scene") !== -1) && (data.indexOf("node") !== -1));
  142. }
  143. public rewriteRootURL: (rootUrl: string, responseURL?: string) => string;
  144. public createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync {
  145. return new GLTFFileLoader();
  146. }
  147. private static _parse(data: string | ArrayBuffer): IGLTFLoaderData {
  148. if (data instanceof ArrayBuffer) {
  149. return GLTFFileLoader._parseBinary(data);
  150. }
  151. return {
  152. json: JSON.parse(data),
  153. bin: null
  154. };
  155. }
  156. private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
  157. const loaderVersion = { major: 2, minor: 0 };
  158. const asset = (<any>loaderData.json).asset || {};
  159. const version = GLTFFileLoader._parseVersion(asset.version);
  160. if (!version) {
  161. throw new Error("Invalid version: " + asset.version);
  162. }
  163. if (asset.minVersion !== undefined) {
  164. const minVersion = GLTFFileLoader._parseVersion(asset.minVersion);
  165. if (!minVersion) {
  166. throw new Error("Invalid minimum version: " + asset.minVersion);
  167. }
  168. if (GLTFFileLoader._compareVersion(minVersion, loaderVersion) > 0) {
  169. throw new Error("Incompatible minimum version: " + asset.minVersion);
  170. }
  171. }
  172. const createLoaders: { [key: number]: (parent: GLTFFileLoader) => IGLTFLoader } = {
  173. 1: GLTFFileLoader.CreateGLTFLoaderV1,
  174. 2: GLTFFileLoader.CreateGLTFLoaderV2
  175. };
  176. const createLoader = createLoaders[version.major];
  177. if (!createLoader) {
  178. throw new Error("Unsupported version: " + asset.version);
  179. }
  180. return createLoader(this);
  181. }
  182. private static _parseBinary(data: ArrayBuffer): IGLTFLoaderData {
  183. const Binary = {
  184. Magic: 0x46546C67
  185. };
  186. const binaryReader = new BinaryReader(data);
  187. const magic = binaryReader.readUint32();
  188. if (magic !== Binary.Magic) {
  189. throw new Error("Unexpected magic: " + magic);
  190. }
  191. const version = binaryReader.readUint32();
  192. switch (version) {
  193. case 1: return GLTFFileLoader._parseV1(binaryReader);
  194. case 2: return GLTFFileLoader._parseV2(binaryReader);
  195. }
  196. throw new Error("Unsupported version: " + version);
  197. }
  198. private static _parseV1(binaryReader: BinaryReader): IGLTFLoaderData {
  199. const ContentFormat = {
  200. JSON: 0
  201. };
  202. const length = binaryReader.readUint32();
  203. if (length != binaryReader.getLength()) {
  204. throw new Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  205. }
  206. const contentLength = binaryReader.readUint32();
  207. const contentFormat = binaryReader.readUint32();
  208. let content: Object;
  209. switch (contentFormat) {
  210. case ContentFormat.JSON: {
  211. content = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(contentLength)));
  212. break;
  213. }
  214. default: {
  215. throw new Error("Unexpected content format: " + contentFormat);
  216. }
  217. }
  218. const bytesRemaining = binaryReader.getLength() - binaryReader.getPosition();
  219. const body = binaryReader.readUint8Array(bytesRemaining);
  220. return {
  221. json: content,
  222. bin: body
  223. };
  224. }
  225. private static _parseV2(binaryReader: BinaryReader): IGLTFLoaderData {
  226. const ChunkFormat = {
  227. JSON: 0x4E4F534A,
  228. BIN: 0x004E4942
  229. };
  230. const length = binaryReader.readUint32();
  231. if (length !== binaryReader.getLength()) {
  232. throw new Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  233. }
  234. // JSON chunk
  235. const chunkLength = binaryReader.readUint32();
  236. const chunkFormat = binaryReader.readUint32();
  237. if (chunkFormat !== ChunkFormat.JSON) {
  238. throw new Error("First chunk format is not JSON");
  239. }
  240. const json = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(chunkLength)));
  241. // Look for BIN chunk
  242. let bin: Nullable<Uint8Array> = null;
  243. while (binaryReader.getPosition() < binaryReader.getLength()) {
  244. const chunkLength = binaryReader.readUint32();
  245. const chunkFormat = binaryReader.readUint32();
  246. switch (chunkFormat) {
  247. case ChunkFormat.JSON: {
  248. throw new Error("Unexpected JSON chunk");
  249. }
  250. case ChunkFormat.BIN: {
  251. bin = binaryReader.readUint8Array(chunkLength);
  252. break;
  253. }
  254. default: {
  255. // ignore unrecognized chunkFormat
  256. binaryReader.skipBytes(chunkLength);
  257. break;
  258. }
  259. }
  260. }
  261. return {
  262. json: json,
  263. bin: bin
  264. };
  265. }
  266. private static _parseVersion(version: string): Nullable<{ major: number, minor: number }> {
  267. if (version === "1.0" || version === "1.0.1") {
  268. return {
  269. major: 1,
  270. minor: 0
  271. };
  272. }
  273. const match = (version + "").match(/^(\d+)\.(\d+)/);
  274. if (!match) {
  275. return null;
  276. }
  277. return {
  278. major: parseInt(match[1]),
  279. minor: parseInt(match[2])
  280. };
  281. }
  282. private static _compareVersion(a: { major: number, minor: number }, b: { major: number, minor: number }) {
  283. if (a.major > b.major) return 1;
  284. if (a.major < b.major) return -1;
  285. if (a.minor > b.minor) return 1;
  286. if (a.minor < b.minor) return -1;
  287. return 0;
  288. }
  289. private static _decodeBufferToText(buffer: Uint8Array): string {
  290. let result = "";
  291. const length = buffer.byteLength;
  292. for (let i = 0; i < length; i++) {
  293. result += String.fromCharCode(buffer[i]);
  294. }
  295. return result;
  296. }
  297. }
  298. class BinaryReader {
  299. private _arrayBuffer: ArrayBuffer;
  300. private _dataView: DataView;
  301. private _byteOffset: number;
  302. constructor(arrayBuffer: ArrayBuffer) {
  303. this._arrayBuffer = arrayBuffer;
  304. this._dataView = new DataView(arrayBuffer);
  305. this._byteOffset = 0;
  306. }
  307. public getPosition(): number {
  308. return this._byteOffset;
  309. }
  310. public getLength(): number {
  311. return this._arrayBuffer.byteLength;
  312. }
  313. public readUint32(): number {
  314. const value = this._dataView.getUint32(this._byteOffset, true);
  315. this._byteOffset += 4;
  316. return value;
  317. }
  318. public readUint8Array(length: number): Uint8Array {
  319. const value = new Uint8Array(this._arrayBuffer, this._byteOffset, length);
  320. this._byteOffset += length;
  321. return value;
  322. }
  323. public skipBytes(length: number): void {
  324. this._byteOffset += length;
  325. }
  326. }
  327. if (BABYLON.SceneLoader) {
  328. BABYLON.SceneLoader.RegisterPlugin(new GLTFFileLoader());
  329. }
  330. }