babylon.glTFFileLoader.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. /// <reference path="../../../dist/preview release/babylon.d.ts"/>
  2. module BABYLON {
  3. export enum GLTFLoaderCoordinateSystemMode {
  4. // Automatically convert the glTF right-handed data to the appropriate system based on the current coordinate system mode of the scene (scene.useRightHandedSystem).
  5. // NOTE: When scene.useRightHandedSystem is false, an additional transform will be added to the root to transform the data from right-handed to left-handed.
  6. AUTO,
  7. // The glTF right-handed data is not transformed in any form and is loaded directly.
  8. PASS_THROUGH,
  9. // Sets the useRightHandedSystem flag on the scene.
  10. FORCE_RIGHT_HANDED,
  11. }
  12. export interface IGLTFLoaderData {
  13. json: Object;
  14. bin: ArrayBufferView;
  15. }
  16. export interface IGLTFLoader {
  17. importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void) => void;
  18. loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void) => void;
  19. }
  20. export class GLTFFileLoader implements ISceneLoaderPluginAsync {
  21. public static CreateGLTFLoaderV1: (parent: GLTFFileLoader) => IGLTFLoader;
  22. public static CreateGLTFLoaderV2: (parent: GLTFFileLoader) => IGLTFLoader;
  23. // Common options
  24. public onParsed: (data: IGLTFLoaderData) => void;
  25. // V1 options
  26. public static HomogeneousCoordinates: boolean = false;
  27. public static IncrementalLoading: boolean = true;
  28. // V2 options
  29. public coordinateSystemMode: GLTFLoaderCoordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
  30. public onTextureLoaded: (texture: BaseTexture) => void;
  31. public onMaterialLoaded: (material: Material) => void;
  32. /**
  33. * Let the user decides if he needs to process the material (like precompilation) before affecting it to meshes
  34. */
  35. public onBeforeMaterialReadyAsync: (material: Material, targetMesh: AbstractMesh, isLOD: boolean, callback: () => void) => void;
  36. /**
  37. * Raised when the visible components (geometry, materials, textures, etc.) are first ready to be rendered.
  38. * For assets with LODs, raised when the first LOD is complete.
  39. * For assets without LODs, raised when the model is complete just before onComplete.
  40. */
  41. public onReady: () => void;
  42. /**
  43. * Raised when the asset is completely loaded, just before the loader is disposed.
  44. * For assets with LODs, raised when all of the LODs are complete.
  45. * For assets without LODs, raised when the model is complete just after onReady.
  46. */
  47. public onComplete: () => void;
  48. public name = "gltf";
  49. public extensions: ISceneLoaderPluginExtensions = {
  50. ".gltf": { isBinary: false },
  51. ".glb": { isBinary: true }
  52. };
  53. 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) => void): void {
  54. var loaderData = GLTFFileLoader._parse(data, onError);
  55. if (!loaderData) {
  56. return;
  57. }
  58. if (this.onParsed) {
  59. this.onParsed(loaderData);
  60. }
  61. var loader = this._getLoader(loaderData, onError);
  62. if (!loader) {
  63. return;
  64. }
  65. loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
  66. }
  67. public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): void {
  68. var loaderData = GLTFFileLoader._parse(data, onError);
  69. if (!loaderData) {
  70. return;
  71. }
  72. if (this.onParsed) {
  73. this.onParsed(loaderData);
  74. }
  75. var loader = this._getLoader(loaderData, onError);
  76. if (!loader) {
  77. return;
  78. }
  79. return loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
  80. }
  81. public canDirectLoad(data: string): boolean {
  82. return ((data.indexOf("scene") !== -1) && (data.indexOf("node") !== -1));
  83. }
  84. private static _parse(data: string | ArrayBuffer, onError: (message: string) => void): IGLTFLoaderData {
  85. if (data instanceof ArrayBuffer) {
  86. return GLTFFileLoader._parseBinary(data, onError);
  87. }
  88. try {
  89. return {
  90. json: JSON.parse(data),
  91. bin: null
  92. };
  93. }
  94. catch (e) {
  95. onError(e.message);
  96. return null;
  97. }
  98. }
  99. private _getLoader(loaderData: IGLTFLoaderData, onError: (message: string) => void): IGLTFLoader {
  100. const loaderVersion = { major: 2, minor: 0 };
  101. var asset = (<any>loaderData.json).asset || {};
  102. var version = GLTFFileLoader._parseVersion(asset.version);
  103. if (!version) {
  104. onError("Invalid version: " + asset.version);
  105. return null;
  106. }
  107. if (asset.minVersion !== undefined) {
  108. var minVersion = GLTFFileLoader._parseVersion(asset.minVersion);
  109. if (!minVersion) {
  110. onError("Invalid minimum version: " + asset.minVersion);
  111. return null;
  112. }
  113. if (GLTFFileLoader._compareVersion(minVersion, loaderVersion) > 0) {
  114. onError("Incompatible minimum version: " + asset.minVersion);
  115. return null;
  116. }
  117. }
  118. var createLoaders = {
  119. 1: GLTFFileLoader.CreateGLTFLoaderV1,
  120. 2: GLTFFileLoader.CreateGLTFLoaderV2
  121. };
  122. var createLoader = createLoaders[version.major];
  123. if (!createLoader) {
  124. onError("Unsupported version: " + asset.version);
  125. return null;
  126. }
  127. return createLoader(this);
  128. }
  129. private static _parseBinary(data: ArrayBuffer, onError: (message: string) => void): IGLTFLoaderData {
  130. const Binary = {
  131. Magic: 0x46546C67
  132. };
  133. var binaryReader = new BinaryReader(data);
  134. var magic = binaryReader.readUint32();
  135. if (magic !== Binary.Magic) {
  136. onError("Unexpected magic: " + magic);
  137. return null;
  138. }
  139. var version = binaryReader.readUint32();
  140. switch (version) {
  141. case 1: return GLTFFileLoader._parseV1(binaryReader, onError);
  142. case 2: return GLTFFileLoader._parseV2(binaryReader, onError);
  143. }
  144. onError("Unsupported version: " + version);
  145. return null;
  146. }
  147. private static _parseV1(binaryReader: BinaryReader, onError: (message: string) => void): IGLTFLoaderData {
  148. const ContentFormat = {
  149. JSON: 0
  150. };
  151. var length = binaryReader.readUint32();
  152. if (length != binaryReader.getLength()) {
  153. onError("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  154. return null;
  155. }
  156. var contentLength = binaryReader.readUint32();
  157. var contentFormat = binaryReader.readUint32();
  158. var content: Object;
  159. switch (contentFormat) {
  160. case ContentFormat.JSON:
  161. content = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(contentLength)));
  162. break;
  163. default:
  164. onError("Unexpected content format: " + contentFormat);
  165. return null;
  166. }
  167. var bytesRemaining = binaryReader.getLength() - binaryReader.getPosition();
  168. var body = binaryReader.readUint8Array(bytesRemaining);
  169. return {
  170. json: content,
  171. bin: body
  172. };
  173. }
  174. private static _parseV2(binaryReader: BinaryReader, onError: (message: string) => void): IGLTFLoaderData {
  175. const ChunkFormat = {
  176. JSON: 0x4E4F534A,
  177. BIN: 0x004E4942
  178. };
  179. var length = binaryReader.readUint32();
  180. if (length !== binaryReader.getLength()) {
  181. onError("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  182. return null;
  183. }
  184. // JSON chunk
  185. var chunkLength = binaryReader.readUint32();
  186. var chunkFormat = binaryReader.readUint32();
  187. if (chunkFormat !== ChunkFormat.JSON) {
  188. onError("First chunk format is not JSON");
  189. return null;
  190. }
  191. var json = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(chunkLength)));
  192. // Look for BIN chunk
  193. var bin: Uint8Array = null;
  194. while (binaryReader.getPosition() < binaryReader.getLength()) {
  195. chunkLength = binaryReader.readUint32();
  196. chunkFormat = binaryReader.readUint32();
  197. switch (chunkFormat) {
  198. case ChunkFormat.JSON:
  199. onError("Unexpected JSON chunk");
  200. return null;
  201. case ChunkFormat.BIN:
  202. bin = binaryReader.readUint8Array(chunkLength);
  203. break;
  204. default:
  205. // ignore unrecognized chunkFormat
  206. binaryReader.skipBytes(chunkLength);
  207. break;
  208. }
  209. }
  210. return {
  211. json: json,
  212. bin: bin
  213. };
  214. }
  215. private static _parseVersion(version: string): { major: number, minor: number } {
  216. if (!version) {
  217. return null;
  218. }
  219. var parts = version.split(".");
  220. if (parts.length != 2) {
  221. return null;
  222. }
  223. var major = +parts[0];
  224. if (isNaN(major)) {
  225. return null;
  226. }
  227. var minor = +parts[1];
  228. if (isNaN(minor)) {
  229. return null;
  230. }
  231. return {
  232. major: major,
  233. minor: minor
  234. };
  235. }
  236. private static _compareVersion(a: { major: number, minor: number }, b: { major: number, minor: number }) {
  237. if (a.major > b.major) return 1;
  238. if (a.major < b.major) return -1;
  239. if (a.minor > b.minor) return 1;
  240. if (a.minor < b.minor) return -1;
  241. return 0;
  242. }
  243. private static _decodeBufferToText(view: ArrayBufferView): string {
  244. var result = "";
  245. var length = view.byteLength;
  246. for (var i = 0; i < length; ++i) {
  247. result += String.fromCharCode(view[i]);
  248. }
  249. return result;
  250. }
  251. }
  252. class BinaryReader {
  253. private _arrayBuffer: ArrayBuffer;
  254. private _dataView: DataView;
  255. private _byteOffset: number;
  256. constructor(arrayBuffer: ArrayBuffer) {
  257. this._arrayBuffer = arrayBuffer;
  258. this._dataView = new DataView(arrayBuffer);
  259. this._byteOffset = 0;
  260. }
  261. public getPosition(): number {
  262. return this._byteOffset;
  263. }
  264. public getLength(): number {
  265. return this._arrayBuffer.byteLength;
  266. }
  267. public readUint32(): number {
  268. var value = this._dataView.getUint32(this._byteOffset, true);
  269. this._byteOffset += 4;
  270. return value;
  271. }
  272. public readUint8Array(length: number): Uint8Array {
  273. var value = new Uint8Array(this._arrayBuffer, this._byteOffset, length);
  274. this._byteOffset += length;
  275. return value;
  276. }
  277. public skipBytes(length: number): void {
  278. this._byteOffset += length;
  279. }
  280. }
  281. if (BABYLON.SceneLoader) {
  282. BABYLON.SceneLoader.RegisterPlugin(new GLTFFileLoader());
  283. }
  284. }