/// module BABYLON { export interface IGLTFLoaderData { json: Object; bin: ArrayBufferView; } export interface IGLTFLoader { importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onsuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onerror: () => void) => void; loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onsuccess: () => void, onerror: () => void) => void; } export class GLTFFileLoader implements ISceneLoaderPluginAsync { public static GLTFLoaderV1: IGLTFLoader = null; public static GLTFLoaderV2: IGLTFLoader = null; public static HomogeneousCoordinates: boolean = false; public static IncrementalLoading: boolean = true; public extensions: ISceneLoaderPluginExtensions = { ".gltf": { isBinary: false }, ".glb": { isBinary: true } }; public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onError: () => void): void { var loaderData = GLTFFileLoader._parse(data); var loader = this._getLoader(loaderData); if (!loader) { onError(); return; } loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onError); } public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: () => void, onError: () => void): void { var loaderData = GLTFFileLoader._parse(data); var loader = this._getLoader(loaderData); if (!loader) { onError(); return; } return loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onError); } public canDirectLoad(data: string): boolean { return ((data.indexOf("scene") !== -1) && (data.indexOf("node") !== -1)); } private static _parse(data: string | ArrayBuffer): IGLTFLoaderData { if (data instanceof ArrayBuffer) { return GLTFFileLoader._parseBinary(data); } return { json: JSON.parse(data), bin: null }; } private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader { const loaderVersion = { major: 2, minor: 0 }; var asset = (loaderData.json).asset || {}; var version = GLTFFileLoader._parseVersion(asset.version); if (!version) { Tools.Error("Invalid version"); return null; } var minVersion = GLTFFileLoader._parseVersion(asset.minVersion); if (minVersion) { if (GLTFFileLoader._compareVersion(minVersion, loaderVersion) > 0) { Tools.Error("Incompatible version"); return null; } } var loaders = { 1: GLTFFileLoader.GLTFLoaderV1, 2: GLTFFileLoader.GLTFLoaderV2 }; var loader = loaders[version.major]; if (loader === undefined) { Tools.Error("Unsupported version"); return null; } if (loader === null) { Tools.Error("v" + version.major + " loader is not available"); return null; } return loader; } private static _parseBinary(data: ArrayBuffer): IGLTFLoaderData { const Binary = { Magic: 0x46546C67 }; var binaryReader = new BinaryReader(data); var magic = binaryReader.readUint32(); if (magic !== Binary.Magic) { Tools.Error("Unexpected magic: " + magic); return null; } var version = binaryReader.readUint32(); switch (version) { case 1: return GLTFFileLoader._parseV1(binaryReader); case 2: return GLTFFileLoader._parseV2(binaryReader); } Tools.Error("Unsupported version: " + version); return null; } private static _parseV1(binaryReader: BinaryReader): IGLTFLoaderData { const ContentFormat = { JSON: 0 }; var length = binaryReader.readUint32(); if (length != binaryReader.getLength()) { Tools.Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength()); return null; } var contentLength = binaryReader.readUint32(); var contentFormat = binaryReader.readUint32(); var content: Object; switch (contentFormat) { case ContentFormat.JSON: content = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(contentLength))); break; default: Tools.Error("Unexpected content format: " + contentFormat); return null; } var bytesRemaining = binaryReader.getLength() - binaryReader.getPosition(); var body = binaryReader.readUint8Array(bytesRemaining); return { json: content, bin: body }; } private static _parseV2(binaryReader: BinaryReader): IGLTFLoaderData { const ChunkFormat = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; var length = binaryReader.readUint32(); if (length !== binaryReader.getLength()) { Tools.Error("Length in header does not match actual data length: " + length + " != " + binaryReader.getLength()); return null; } // JSON chunk var chunkLength = binaryReader.readUint32(); var chunkFormat = binaryReader.readUint32(); if (chunkFormat !== ChunkFormat.JSON) { Tools.Error("First chunk format is not JSON"); return null; } var json = JSON.parse(GLTFFileLoader._decodeBufferToText(binaryReader.readUint8Array(chunkLength))); // Look for BIN chunk var bin: Uint8Array = null; while (binaryReader.getPosition() < binaryReader.getLength()) { chunkLength = binaryReader.readUint32(); chunkFormat = binaryReader.readUint32(); switch (chunkFormat) { case ChunkFormat.JSON: Tools.Error("Unexpected JSON chunk"); return null; case ChunkFormat.BIN: bin = binaryReader.readUint8Array(chunkLength); break; default: // ignore unrecognized chunkFormat binaryReader.skipBytes(chunkLength); break; } } return { json: json, bin: bin }; } private static _parseVersion(version: string): { major: number, minor: number } { if (!version) { return null; } var parts = version.split("."); if (parts.length === 0) { return null; } var major = parseInt(parts[0]); if (major > 1 && parts.length != 2) { return null; } var minor = parseInt(parts[1]); return { major: major, minor: parseInt(parts[0]) }; } private static _compareVersion(a: { major: number, minor: number }, b: { major: number, minor: number }) { if (a.major > b.major) return 1; if (a.major < b.major) return -1; if (a.minor > b.minor) return 1; if (a.minor < b.minor) return -1; return 0; } private static _decodeBufferToText(view: ArrayBufferView): string { var result = ""; var length = view.byteLength; for (var i = 0; i < length; ++i) { result += String.fromCharCode(view[i]); } return result; } } class BinaryReader { private _arrayBuffer: ArrayBuffer; private _dataView: DataView; private _byteOffset: number; constructor(arrayBuffer: ArrayBuffer) { this._arrayBuffer = arrayBuffer; this._dataView = new DataView(arrayBuffer); this._byteOffset = 0; } public getPosition(): number { return this._byteOffset; } public getLength(): number { return this._arrayBuffer.byteLength; } public readUint32(): number { var value = this._dataView.getUint32(this._byteOffset, true); this._byteOffset += 4; return value; } public readUint8Array(length: number): Uint8Array { var value = new Uint8Array(this._arrayBuffer, this._byteOffset, length); this._byteOffset += length; return value; } public skipBytes(length: number): void { this._byteOffset += length; } } BABYLON.SceneLoader.RegisterPlugin(new GLTFFileLoader()); }