babylon.glTFFileLoader.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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: Nullable<ArrayBufferView>;
  15. }
  16. export interface IGLTFLoader extends IDisposable {
  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 IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
  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 = false;
  27. public static IncrementalLoading = true;
  28. // V2 options
  29. public coordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
  30. public compileMaterials = false;
  31. public compileShadowGenerators = false;
  32. public useClipPlane = false;
  33. public onMeshLoaded: (mesh: AbstractMesh) => void;
  34. public onTextureLoaded: (texture: BaseTexture) => void;
  35. public onMaterialLoaded: (material: Material) => void;
  36. /**
  37. * Raised when the asset is completely loaded, just before the loader is disposed.
  38. * For assets with LODs, raised when all of the LODs are complete.
  39. * For assets without LODs, raised when the model is complete just after onSuccess.
  40. */
  41. public onComplete: () => void;
  42. private _loader: IGLTFLoader;
  43. public name = "gltf";
  44. public extensions: ISceneLoaderPluginExtensions = {
  45. ".gltf": { isBinary: false },
  46. ".glb": { isBinary: true }
  47. };
  48. public dispose(): void {
  49. if (this._loader) {
  50. this._loader.dispose();
  51. }
  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. try {
  55. const loaderData = GLTFFileLoader._parse(data);
  56. if (this.onParsed) {
  57. this.onParsed(loaderData);
  58. }
  59. this._loader = this._getLoader(loaderData);
  60. this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
  61. }
  62. catch (e) {
  63. onError(e.message);
  64. }
  65. }
  66. public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): void {
  67. try {
  68. const loaderData = GLTFFileLoader._parse(data);
  69. if (this.onParsed) {
  70. this.onParsed(loaderData);
  71. }
  72. this._loader = this._getLoader(loaderData);
  73. this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
  74. }
  75. catch (e) {
  76. onError(e.message);
  77. }
  78. }
  79. public canDirectLoad(data: string): boolean {
  80. return ((data.indexOf("scene") !== -1) && (data.indexOf("node") !== -1));
  81. }
  82. public rewriteRootURL: (rootUrl: string, responseURL?: string) => string;
  83. public createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync {
  84. return new GLTFFileLoader();
  85. }
  86. private static _parse(data: string | ArrayBuffer): IGLTFLoaderData {
  87. if (data instanceof ArrayBuffer) {
  88. return GLTFFileLoader._parseBinary(data);
  89. }
  90. return {
  91. json: JSON.parse(data),
  92. bin: null
  93. };
  94. }
  95. private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
  96. const loaderVersion = { major: 2, minor: 0 };
  97. const asset = (<any>loaderData.json).asset || {};
  98. const version = GLTFFileLoader._parseVersion(asset.version);
  99. if (!version) {
  100. throw new Error("Invalid version: " + asset.version);
  101. }
  102. if (asset.minVersion !== undefined) {
  103. const minVersion = GLTFFileLoader._parseVersion(asset.minVersion);
  104. if (!minVersion) {
  105. throw new Error("Invalid minimum version: " + asset.minVersion);
  106. }
  107. if (GLTFFileLoader._compareVersion(minVersion, loaderVersion) > 0) {
  108. throw new Error("Incompatible minimum version: " + asset.minVersion);
  109. }
  110. }
  111. const createLoaders: { [key: number]: (parent: GLTFFileLoader) => IGLTFLoader } = {
  112. 1: GLTFFileLoader.CreateGLTFLoaderV1,
  113. 2: GLTFFileLoader.CreateGLTFLoaderV2
  114. };
  115. const createLoader = createLoaders[version.major];
  116. if (!createLoader) {
  117. throw new Error("Unsupported version: " + asset.version);
  118. }
  119. return createLoader(this);
  120. }
  121. private static _parseBinary(data: ArrayBuffer): IGLTFLoaderData {
  122. const Binary = {
  123. Magic: 0x46546C67
  124. };
  125. const binaryReader = new BinaryReader(data);
  126. const magic = binaryReader.readUint32();
  127. if (magic !== Binary.Magic) {
  128. throw new Error("Unexpected magic: " + magic);
  129. }
  130. const version = binaryReader.readUint32();
  131. switch (version) {
  132. case 1: return GLTFFileLoader._parseV1(binaryReader);
  133. case 2: return GLTFFileLoader._parseV2(binaryReader);
  134. }
  135. throw new Error("Unsupported version: " + version);
  136. }
  137. private static _parseV1(binaryReader: BinaryReader): IGLTFLoaderData {
  138. const ContentFormat = {
  139. JSON: 0
  140. };
  141. const length = binaryReader.readUint32();
  142. if (length != binaryReader.getLength()) {
  143. throw new Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  144. }
  145. const contentLength = binaryReader.readUint32();
  146. const contentFormat = binaryReader.readUint32();
  147. let content: Object;
  148. switch (contentFormat) {
  149. case ContentFormat.JSON: {
  150. content = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(contentLength)));
  151. break;
  152. }
  153. default: {
  154. throw new Error("Unexpected content format: " + contentFormat);
  155. }
  156. }
  157. const bytesRemaining = binaryReader.getLength() - binaryReader.getPosition();
  158. const body = binaryReader.readUint8Array(bytesRemaining);
  159. return {
  160. json: content,
  161. bin: body
  162. };
  163. }
  164. private static _parseV2(binaryReader: BinaryReader): IGLTFLoaderData {
  165. const ChunkFormat = {
  166. JSON: 0x4E4F534A,
  167. BIN: 0x004E4942
  168. };
  169. const length = binaryReader.readUint32();
  170. if (length !== binaryReader.getLength()) {
  171. throw new Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength());
  172. }
  173. // JSON chunk
  174. const chunkLength = binaryReader.readUint32();
  175. const chunkFormat = binaryReader.readUint32();
  176. if (chunkFormat !== ChunkFormat.JSON) {
  177. throw new Error("First chunk format is not JSON");
  178. }
  179. const json = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(chunkLength)));
  180. // Look for BIN chunk
  181. let bin: Nullable<Uint8Array> = null;
  182. while (binaryReader.getPosition() < binaryReader.getLength()) {
  183. const chunkLength = binaryReader.readUint32();
  184. const chunkFormat = binaryReader.readUint32();
  185. switch (chunkFormat) {
  186. case ChunkFormat.JSON: {
  187. throw new Error("Unexpected JSON chunk");
  188. }
  189. case ChunkFormat.BIN: {
  190. bin = binaryReader.readUint8Array(chunkLength);
  191. break;
  192. }
  193. default: {
  194. // ignore unrecognized chunkFormat
  195. binaryReader.skipBytes(chunkLength);
  196. break;
  197. }
  198. }
  199. }
  200. return {
  201. json: json,
  202. bin: bin
  203. };
  204. }
  205. private static _parseVersion(version: string): Nullable<{ major: number, minor: number }> {
  206. const match = (version + "").match(/^(\d+)\.(\d+)$/);
  207. if (!match) {
  208. return null;
  209. }
  210. return {
  211. major: parseInt(match[1]),
  212. minor: parseInt(match[2])
  213. };
  214. }
  215. private static _compareVersion(a: { major: number, minor: number }, b: { major: number, minor: number }) {
  216. if (a.major > b.major) return 1;
  217. if (a.major < b.major) return -1;
  218. if (a.minor > b.minor) return 1;
  219. if (a.minor < b.minor) return -1;
  220. return 0;
  221. }
  222. private static _decodeBufferToText(buffer: Uint8Array): string {
  223. let result = "";
  224. const length = buffer.byteLength;
  225. for (let i = 0; i < length; i++) {
  226. result += String.fromCharCode(buffer[i]);
  227. }
  228. return result;
  229. }
  230. }
  231. class BinaryReader {
  232. private _arrayBuffer: ArrayBuffer;
  233. private _dataView: DataView;
  234. private _byteOffset: number;
  235. constructor(arrayBuffer: ArrayBuffer) {
  236. this._arrayBuffer = arrayBuffer;
  237. this._dataView = new DataView(arrayBuffer);
  238. this._byteOffset = 0;
  239. }
  240. public getPosition(): number {
  241. return this._byteOffset;
  242. }
  243. public getLength(): number {
  244. return this._arrayBuffer.byteLength;
  245. }
  246. public readUint32(): number {
  247. const value = this._dataView.getUint32(this._byteOffset, true);
  248. this._byteOffset += 4;
  249. return value;
  250. }
  251. public readUint8Array(length: number): Uint8Array {
  252. const value = new Uint8Array(this._arrayBuffer, this._byteOffset, length);
  253. this._byteOffset += length;
  254. return value;
  255. }
  256. public skipBytes(length: number): void {
  257. this._byteOffset += length;
  258. }
  259. }
  260. if (BABYLON.SceneLoader) {
  261. BABYLON.SceneLoader.RegisterPlugin(new GLTFFileLoader());
  262. }
  263. }