babylon.glTFFileLoader.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /// <reference path="../../../dist/preview release/babylon.d.ts"/>
  2. module BABYLON {
  3. export interface IGLTFLoaderData {
  4. json: Object;
  5. bin: ArrayBufferView;
  6. }
  7. export interface IGLTFLoader {
  8. importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onsuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onerror: () => void) => void;
  9. loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onsuccess: () => void, onerror: () => void) => void;
  10. }
  11. export class GLTFFileLoader implements ISceneLoaderPluginAsync {
  12. public static GLTFLoaderV1: IGLTFLoader = null;
  13. public static GLTFLoaderV2: IGLTFLoader = null;
  14. public static HomogeneousCoordinates: boolean = false;
  15. public static IncrementalLoading: boolean = true;
  16. public extensions: ISceneLoaderPluginExtensions = {
  17. ".gltf": { isBinary: false },
  18. ".glb": { isBinary: true }
  19. };
  20. public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onError: () => void): void {
  21. var loaderData = GLTFFileLoader._parse(data);
  22. var loader = this._getLoader(loaderData);
  23. if (!loader) {
  24. onError();
  25. return;
  26. }
  27. loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onError);
  28. }
  29. public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: () => void, onError: () => void): void {
  30. var loaderData = GLTFFileLoader._parse(data);
  31. var loader = this._getLoader(loaderData);
  32. if (!loader) {
  33. onError();
  34. return;
  35. }
  36. return loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onError);
  37. }
  38. public canDirectLoad(data: string): boolean {
  39. return ((data.indexOf("scene") !== -1) && (data.indexOf("node") !== -1));
  40. }
  41. private static _parse(data: string | ArrayBuffer): IGLTFLoaderData {
  42. if (data instanceof ArrayBuffer) {
  43. return GLTFFileLoader._parseBinary(data);
  44. }
  45. return {
  46. json: JSON.parse(data),
  47. bin: null
  48. };
  49. }
  50. private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
  51. const loaderVersion = { major: 2, minor: 0 };
  52. var asset = (<any>loaderData.json).asset || {};
  53. var version = GLTFFileLoader._parseVersion(asset.version);
  54. if (!version) {
  55. Tools.Error("Invalid version");
  56. return null;
  57. }
  58. var minVersion = GLTFFileLoader._parseVersion(asset.minVersion);
  59. if (minVersion) {
  60. if (GLTFFileLoader._compareVersion(minVersion, loaderVersion) > 0) {
  61. Tools.Error("Incompatible version");
  62. return null;
  63. }
  64. }
  65. var loaders = {
  66. 1: GLTFFileLoader.GLTFLoaderV1,
  67. 2: GLTFFileLoader.GLTFLoaderV2
  68. };
  69. var loader = loaders[version.major];
  70. if (loader === undefined) {
  71. Tools.Error("Unsupported version");
  72. return null;
  73. }
  74. if (loader === null) {
  75. Tools.Error("v" + version.major + " loader is not available");
  76. return null;
  77. }
  78. return loader;
  79. }
  80. private static _parseBinary(data: ArrayBuffer): IGLTFLoaderData {
  81. const Binary = {
  82. Magic: 0x46546C67
  83. };
  84. var binaryReader = new BinaryReader(data);
  85. var magic = binaryReader.readUint32();
  86. if (magic !== Binary.Magic) {
  87. Tools.Error("Unexpected magic: " + magic);
  88. return null;
  89. }
  90. var version = binaryReader.readUint32();
  91. switch (version) {
  92. case 1: return GLTFFileLoader._parseV1(binaryReader);
  93. case 2: return GLTFFileLoader._parseV2(binaryReader);
  94. }
  95. Tools.Error("Unsupported version: " + version);
  96. return null;
  97. }
  98. private static _parseV1(binaryReader: BinaryReader): IGLTFLoaderData {
  99. const ContentFormat = {
  100. JSON: 0
  101. };
  102. var length = binaryReader.readUint32();
  103. if (length != binaryReader.getLength()) {
  104. Tools.Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  105. return null;
  106. }
  107. var contentLength = binaryReader.readUint32();
  108. var contentFormat = binaryReader.readUint32();
  109. var content: Object;
  110. switch (contentFormat) {
  111. case ContentFormat.JSON:
  112. content = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(contentLength)));
  113. break;
  114. default:
  115. Tools.Error("Unexpected content format: " + contentFormat);
  116. return null;
  117. }
  118. var bytesRemaining = binaryReader.getLength() - binaryReader.getPosition();
  119. var body = binaryReader.readUint8Array(bytesRemaining);
  120. return {
  121. json: content,
  122. bin: body
  123. };
  124. }
  125. private static _parseV2(binaryReader: BinaryReader): IGLTFLoaderData {
  126. const ChunkFormat = {
  127. JSON: 0x4E4F534A,
  128. BIN: 0x004E4942
  129. };
  130. var length = binaryReader.readUint32();
  131. if (length !== binaryReader.getLength()) {
  132. Tools.Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  133. return null;
  134. }
  135. // JSON chunk
  136. var chunkLength = binaryReader.readUint32();
  137. var chunkFormat = binaryReader.readUint32();
  138. if (chunkFormat !== ChunkFormat.JSON) {
  139. Tools.Error("First chunk format is not JSON");
  140. return null;
  141. }
  142. var json = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(chunkLength)));
  143. // Look for BIN chunk
  144. var bin: Uint8Array = null;
  145. while (binaryReader.getPosition() < binaryReader.getLength()) {
  146. chunkLength = binaryReader.readUint32();
  147. chunkFormat = binaryReader.readUint32();
  148. switch (chunkFormat) {
  149. case ChunkFormat.JSON:
  150. Tools.Error("Unexpected JSON chunk");
  151. return null;
  152. case ChunkFormat.BIN:
  153. bin = binaryReader.readUint8Array(chunkLength);
  154. break;
  155. default:
  156. // ignore unrecognized chunkFormat
  157. binaryReader.skipBytes(chunkLength);
  158. break;
  159. }
  160. }
  161. return {
  162. json: json,
  163. bin: bin
  164. };
  165. }
  166. private static _parseVersion(version: string): { major: number, minor: number } {
  167. if (!version) {
  168. return null;
  169. }
  170. var parts = version.split(".");
  171. if (parts.length === 0) {
  172. return null;
  173. }
  174. var major = parseInt(parts[0]);
  175. if (major > 1 && parts.length != 2) {
  176. return null;
  177. }
  178. var minor = parseInt(parts[1]);
  179. return {
  180. major: major,
  181. minor: parseInt(parts[0])
  182. };
  183. }
  184. private static _compareVersion(a: { major: number, minor: number }, b: { major: number, minor: number }) {
  185. if (a.major > b.major) return 1;
  186. if (a.major < b.major) return -1;
  187. if (a.minor > b.minor) return 1;
  188. if (a.minor < b.minor) return -1;
  189. return 0;
  190. }
  191. private static _decodeBufferToText(view: ArrayBufferView): string {
  192. var result = "";
  193. var length = view.byteLength;
  194. for (var i = 0; i < length; ++i) {
  195. result += String.fromCharCode(view[i]);
  196. }
  197. return result;
  198. }
  199. }
  200. class BinaryReader {
  201. private _arrayBuffer: ArrayBuffer;
  202. private _dataView: DataView;
  203. private _byteOffset: number;
  204. constructor(arrayBuffer: ArrayBuffer) {
  205. this._arrayBuffer = arrayBuffer;
  206. this._dataView = new DataView(arrayBuffer);
  207. this._byteOffset = 0;
  208. }
  209. public getPosition(): number {
  210. return this._byteOffset;
  211. }
  212. public getLength(): number {
  213. return this._arrayBuffer.byteLength;
  214. }
  215. public readUint32(): number {
  216. var value = this._dataView.getUint32(this._byteOffset, true);
  217. this._byteOffset += 4;
  218. return value;
  219. }
  220. public readUint8Array(length: number): Uint8Array {
  221. var value = new Uint8Array(this._arrayBuffer, this._byteOffset, length);
  222. this._byteOffset += length;
  223. return value;
  224. }
  225. public skipBytes(length: number): void {
  226. this._byteOffset += length;
  227. }
  228. }
  229. BABYLON.SceneLoader.RegisterPlugin(new GLTFFileLoader());
  230. }