Browse Source

Merge pull request #1427 from kickserve/feature/binary_extension

Add glTF binary extension support to glTF loader
David Catuhe 8 năm trước cách đây
mục cha
commit
9949801966

+ 18 - 18
loaders/OBJ/babylon.objFileLoader.ts

@@ -152,7 +152,7 @@ module BABYLON {
             //At the end of the file, add the last material
             this.materials.push(material);
         }
-        
+
         /**
          * Gets the texture for the material.
          * 
@@ -163,7 +163,7 @@ module BABYLON {
          * @param value The value stored in the mtl
          * @return The Texture
          */
-        private static _getTexture(rootUrl:string, value: string, scene: Scene): Texture {
+        private static _getTexture(rootUrl: string, value: string, scene: Scene): Texture {
             var url = rootUrl;
             // Load from input file.
             if (rootUrl === "file:") {
@@ -171,9 +171,9 @@ module BABYLON {
                 if (lastDelimiter === -1) {
                     lastDelimiter = value.lastIndexOf("/");
                 }
-                
+
                 if (lastDelimiter > -1) {
-                    url += value.substr(lastDelimiter + 1); 
+                    url += value.substr(lastDelimiter + 1);
                 }
                 else {
                     url += value;
@@ -183,7 +183,7 @@ module BABYLON {
             else {
                 url += value;
             }
-            
+
             return new BABYLON.Texture(url, scene);
         }
     }
@@ -227,14 +227,14 @@ module BABYLON {
         private _loadMTL(url: string, rootUrl: string, onSuccess: (response: string) => any) {
             //The complete path to the mtl file
             var pathOfFile = BABYLON.Tools.BaseUrl + rootUrl + url;
-            
+
             // Loads through the babylon tools to allow fileInput search.
             BABYLON.Tools.LoadFile(pathOfFile, 
                 onSuccess, 
                 null, 
                 null, 
                 false, 
-                () => { console.warn("Error - Unable to load " + pathOfFile); });
+                () => { console.warn("Error - Unable to load " + pathOfFile); });
         }
 
         public importMesh(meshesNames: any, scene: Scene, data: any, rootUrl: string, meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]): boolean {
@@ -300,8 +300,8 @@ module BABYLON {
              * @param obj Array<number>
              * @returns {boolean}
              */
-            var isInArray = (arr: Array<{ normals: Array<number>; idx: Array<number>}>, obj: Array<number>) => {
-                if (!arr[obj[0]]) arr[obj[0]] = { normals: [], idx: []};
+            var isInArray = (arr: Array<{ normals: Array<number>; idx: Array<number> }>, obj: Array<number>) => {
+                if (!arr[obj[0]]) arr[obj[0]] = { normals: [], idx: [] };
                 var idx = arr[obj[0]].normals.indexOf(obj[1]);
 
                 return idx === -1 ? -1 : arr[obj[0]].idx[idx];
@@ -310,7 +310,7 @@ module BABYLON {
                 if (!arr[obj[0]]) arr[obj[0]] = { normals: [], idx: [], uv: [] };
                 var idx = arr[obj[0]].normals.indexOf(obj[1]);
 
-                if(idx != 1 && (obj[2] == arr[obj[0]].uv[idx])) {
+                if (idx != 1 && (obj[2] == arr[obj[0]].uv[idx])) {
                     return arr[obj[0]].idx[idx];
                 }
                 return -1;
@@ -331,8 +331,8 @@ module BABYLON {
              */
             var setData = (indicePositionFromObj: number, indiceUvsFromObj: number, indiceNormalFromObj: number, positionVectorFromOBJ: BABYLON.Vector3, textureVectorFromOBJ: BABYLON.Vector2, normalsVectorFromOBJ: BABYLON.Vector3) => {
                 //Check if this tuple already exists in the list of tuples
-                var _index : number;
-                if(OBJFileLoader.OPTIMIZE_WITH_UV) {
+                var _index: number;
+                if (OBJFileLoader.OPTIMIZE_WITH_UV) {
                     _index = isInArrayUV(
                         tuplePosNorm,
                         [
@@ -370,7 +370,7 @@ module BABYLON {
                     //Add the tuple in the comparison list
                     tuplePosNorm[indicePositionFromObj].normals.push(indiceNormalFromObj);
                     tuplePosNorm[indicePositionFromObj].idx.push(curPositionInIndices++);
-                    if(OBJFileLoader.OPTIMIZE_WITH_UV) tuplePosNorm[indicePositionFromObj].uv.push(indiceUvsFromObj);
+                    if (OBJFileLoader.OPTIMIZE_WITH_UV) tuplePosNorm[indicePositionFromObj].uv.push(indiceUvsFromObj);
                 } else {
                     //The tuple already exists
                     //Add the index of the already existing tuple
@@ -389,8 +389,8 @@ module BABYLON {
                     unwrappedPositionsForBabylon.push(wrappedPositionForBabylon[l].x, wrappedPositionForBabylon[l].y, wrappedPositionForBabylon[l].z);
                     unwrappedNormalsForBabylon.push(wrappedNormalsForBabylon[l].x, wrappedNormalsForBabylon[l].y, wrappedNormalsForBabylon[l].z);
                     unwrappedUVForBabylon.push(wrappedUvsForBabylon[l].x, wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
-                }				
-				// Reset arrays for the next new meshes
+                }
+                // Reset arrays for the next new meshes
                 wrappedPositionForBabylon = [];
                 wrappedNormalsForBabylon = [];
                 wrappedUvsForBabylon = [];
@@ -421,7 +421,7 @@ module BABYLON {
                     //Recursion
                     getTriangles(face, v);
                 }
-                
+
                 //Result obtained after 2 iterations:
                 //Pattern1 => triangle = ["1","2","3","1","3","4"];
                 //Pattern2 => triangle = ["1/1","2/2","3/3","1/1","3/3","4/4"];
@@ -726,7 +726,7 @@ module BABYLON {
                                 uvs: undefined,
                                 materialName: materialNameFromObj
                             };
-                        increment ++;
+                        increment++;
                         //If meshes are already defined
                         meshesFromObj.push(objMesh);
                     }
@@ -816,7 +816,7 @@ module BABYLON {
                 //This is indispensable for the importMesh function
                 materialToUse.push(meshesFromObj[j].materialName);
 
-				var vertexData: VertexData = new BABYLON.VertexData(); //The container for the values
+                var vertexData: VertexData = new BABYLON.VertexData(); //The container for the values
                 //Set the data for the babylonMesh
                 vertexData.positions = handledMesh.positions;
                 vertexData.normals = handledMesh.normals;

+ 4 - 2
loaders/config.json

@@ -16,7 +16,9 @@
       "files": [
         "glTF/babylon.glTFFileLoaderInterfaces.ts",
         "glTF/babylon.glTFFileLoader.ts",
-        "glTF/babylon.glTFFileLoaderUtils.ts"
+        "glTF/babylon.glTFFileLoaderUtils.ts",
+        "glTF/babylon.glTFFileLoaderExtension.ts",
+        "glTF/babylon.glTFBinaryExtension.ts"
       ],
       "output": "./glTF",
       "filename": "babylon.glTFFileLoader.js"
@@ -24,6 +26,6 @@
   },
 
   "defines": [
-    "../dist/*.d.ts"
+    "../dist/preview release/*.d.ts"
   ]
 }

+ 175 - 0
loaders/glTF/babylon.glTFBinaryExtension.ts

@@ -0,0 +1,175 @@
+module BABYLON {
+    const BinaryExtensionBufferName = "binary_glTF";
+
+    enum EContentFormat {
+        JSON = 0
+    };
+
+    interface IGLTFBinaryExtension {
+        content: Object;
+        body: Uint8Array;
+    };
+
+    interface IGLTFBinaryExtensionShader {
+        bufferView: string;
+    };
+
+    interface IGLTFBinaryExtensionImage {
+        bufferView: string;
+        mimeType: string;
+        height: number;
+        width: number;
+    };
+
+    export class GLTFBinaryExtension extends GLTFFileLoaderExtension {
+        private _binary: IGLTFBinaryExtension;
+
+        public constructor() {
+            super("KHR_binary_glTF");
+        }
+
+        public loadRuntimeAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: (gltfRuntime: IGLTFRuntime) => void, onError: () => void): boolean {
+            if (!(data instanceof ArrayBuffer)) {
+                return false;
+            }
+
+            setTimeout(() => {
+                this._binary = this._parseBinary(<ArrayBuffer>data);
+                if (!this._binary) {
+                    onError();
+                    return true;
+                }
+
+                var gltfRuntime = GLTFFileLoaderBase.CreateRuntime(this._binary.content, scene, rootUrl);
+
+                if (gltfRuntime.extensionsUsed.indexOf(this.name) === -1) {
+                    Tools.Warn("glTF binary file does not have " + this.name + " specified in extensionsUsed");
+                    gltfRuntime.extensionsUsed.push(this.name);
+                }
+
+                onSuccess(gltfRuntime);
+            });
+
+            return true;
+        }
+
+        public loadBufferAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (buffer: ArrayBufferView) => void, onError: () => void): boolean {
+            if (gltfRuntime.extensionsUsed.indexOf(this.name) === -1) {
+                return false;
+            }
+
+            if (id !== BinaryExtensionBufferName) {
+                return false;
+            }
+
+            onSuccess(this._binary.body);
+            return true;
+        }
+
+        public loadTextureBufferAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (buffer: ArrayBufferView) => void, onError: () => void): boolean {
+            var texture: IGLTFTexture = gltfRuntime.textures[id];
+            var source: IGLTFImage = gltfRuntime.images[texture.source];
+            if (!source.extensions || !(this.name in source.extensions)) {
+                return false;
+            }
+
+            var sourceExt: IGLTFBinaryExtensionImage = source.extensions[this.name];
+            var bufferView: IGLTFBufferView = gltfRuntime.bufferViews[sourceExt.bufferView];
+            var buffer = GLTFUtils.GetBufferFromBufferView(gltfRuntime, bufferView, 0, bufferView.byteLength, EComponentType.UNSIGNED_BYTE);
+            onSuccess(buffer);
+            return true;
+        }
+
+        public loadShaderStringAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (shaderString: string) => void, onError: () => void): boolean {
+            var shader: IGLTFShader = gltfRuntime.shaders[id];
+            if (!shader.extensions || !(this.name in shader.extensions)) {
+                return false;
+            }
+
+            var binaryExtensionShader: IGLTFBinaryExtensionShader = shader.extensions[this.name];
+            var bufferView: IGLTFBufferView = gltfRuntime.bufferViews[binaryExtensionShader.bufferView];
+            var shaderBytes = GLTFUtils.GetBufferFromBufferView(gltfRuntime, bufferView, 0, bufferView.byteLength, EComponentType.UNSIGNED_BYTE);
+
+            setTimeout(() => {
+                var shaderString = GLTFUtils.DecodeBufferToText(shaderBytes);
+                onSuccess(shaderString);
+            });
+
+            return true;
+        }
+
+        // Parses a glTF binary array buffer into its content and body
+        private _parseBinary(data: ArrayBuffer): IGLTFBinaryExtension {
+            var binaryReader = new BinaryReader(data);
+
+            var magic = GLTFUtils.DecodeBufferToText(binaryReader.getUint8Array(4));
+            if (magic != "glTF") {
+                Tools.Error("Unexpected magic: " + magic);
+                return null;
+            }
+
+            var version = binaryReader.getUint32();
+            if (version != 1) {
+                Tools.Error("Unsupported version: " + version);
+                return null;
+            }
+
+            var length = binaryReader.getUint32();
+            if (length != data.byteLength) {
+                Tools.Error("Length in header does not match actual data length: " + length + " != " + data.byteLength);
+                return null;
+            }
+
+            var contentLength = binaryReader.getUint32();
+            var contentFormat = <EContentFormat>binaryReader.getUint32();
+
+            var content: Object;
+            switch (contentFormat) {
+                case EContentFormat.JSON:
+                    var jsonText = GLTFUtils.DecodeBufferToText(binaryReader.getUint8Array(contentLength));
+                    content = JSON.parse(jsonText);
+                    break;
+                default:
+                    Tools.Error("Unexpected content format: " + contentFormat);
+                    return null;
+            }
+
+            var body = binaryReader.getUint8Array();
+
+            return {
+                content: content,
+                body: body
+            };
+        };
+    }
+
+    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 getUint32(): number {
+            var value = this._dataView.getUint32(this._byteOffset, true);
+            this._byteOffset += 4;
+            return value;
+        }
+
+        public getUint8Array(length?: number): Uint8Array {
+            if (!length) {
+                length = this._arrayBuffer.byteLength - this._byteOffset;
+            }
+
+            var value = new Uint8Array(this._arrayBuffer, this._byteOffset, length);
+            this._byteOffset += length;
+            return value;
+        }
+    }
+
+    GLTFFileLoader.RegisterExtension(new GLTFBinaryExtension());
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 618 - 398
loaders/glTF/babylon.glTFFileLoader.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 370 - 432
loaders/glTF/babylon.glTFFileLoader.ts


+ 132 - 0
loaders/glTF/babylon.glTFFileLoaderExtension.ts

@@ -0,0 +1,132 @@
+module BABYLON {
+    export abstract class GLTFFileLoaderExtension {
+        private _name: string;
+
+        public constructor(name: string) {
+            this._name = name;
+        }
+
+        public get name(): string {
+            return this._name;
+        }
+
+        /**
+        * Defines an override for loading the runtime
+        * Return true to stop further extensions from loading the runtime
+        */
+        public loadRuntimeAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: (gltfRuntime: IGLTFRuntime) => void, onError: () => void): boolean {
+            return false;
+        }
+
+        /**
+        * Defines an override for loading buffers
+        * Return true to stop further extensions from loading this buffer
+        */
+        public loadBufferAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (buffer: ArrayBufferView) => void, onError: () => void): boolean {
+            return false;
+        }
+
+        /**
+        * Defines an override for loading texture buffers
+        * Return true to stop further extensions from loading this texture data
+        */
+        public loadTextureBufferAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (buffer: ArrayBufferView) => void, onError: () => void): boolean {
+            return false;
+        }
+
+        /**
+        * Defines an override for creating textures
+        * Return true to stop further extensions from loading this texture
+        */
+        public createTextureAsync(gltfRuntime: IGLTFRuntime, id: string, buffer: ArrayBufferView, onSuccess: (texture: Texture) => void, onError: () => void): boolean {
+            return false;
+        }
+
+        /**
+        * Defines an override for loading shader strings
+        * Return true to stop further extensions from loading this shader data
+        */
+        public loadShaderStringAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (shaderString: string) => void, onError: () => void): boolean {
+            return false;
+        }
+
+        /**
+        * Defines an override for loading materials
+        * Return true to stop further extensions from loading this material
+        */
+        public loadMaterialAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (material: Material) => void, onError: () => void): boolean {
+            return false;
+        }
+
+        // ---------
+        // Utilities
+        // ---------
+
+        public static LoadRuntimeAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: (gltfRuntime: IGLTFRuntime) => void, onError: () => void): void {
+            GLTFFileLoaderExtension.ApplyExtensions(loaderExtension => {
+                return loaderExtension.loadRuntimeAsync(scene, data, rootUrl, onSuccess, onError);
+            }, () => {
+                setTimeout(() => {
+                    onSuccess(GLTFFileLoaderBase.CreateRuntime(JSON.parse(<string>data), scene, rootUrl));
+                });
+            });
+        }
+
+        public static LoadBufferAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (bufferView: ArrayBufferView) => void, onError: () => void): void {
+            GLTFFileLoaderExtension.ApplyExtensions(loaderExtension => {
+                return loaderExtension.loadBufferAsync(gltfRuntime, id, onSuccess, onError);
+            }, () => {
+                GLTFFileLoaderBase.LoadBufferAsync(gltfRuntime, id, onSuccess, onError);
+            });
+        }
+
+        public static LoadTextureAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (texture: Texture) => void, onError: () => void): void {
+            GLTFFileLoaderExtension.LoadTextureBufferAsync(gltfRuntime, id,
+                buffer => GLTFFileLoaderExtension.CreateTextureAsync(gltfRuntime, id, buffer, onSuccess, onError),
+                onError);
+        }
+
+        public static LoadShaderStringAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (shaderData: string) => void, onError: () => void): void {
+            GLTFFileLoaderExtension.ApplyExtensions(loaderExtension => {
+                return loaderExtension.loadShaderStringAsync(gltfRuntime, id, onSuccess, onError);
+            }, () => {
+                GLTFFileLoaderBase.LoadShaderStringAsync(gltfRuntime, id, onSuccess, onError);
+            });
+        }
+
+        public static LoadMaterialAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (material: Material) => void, onError: () => void): void {
+            GLTFFileLoaderExtension.ApplyExtensions(loaderExtension => {
+                return loaderExtension.loadMaterialAsync(gltfRuntime, id, onSuccess, onError);
+            }, () => {
+                GLTFFileLoaderBase.LoadMaterialAsync(gltfRuntime, id, onSuccess, onError);
+            });
+        }
+
+        private static LoadTextureBufferAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (buffer: ArrayBufferView) => void, onError: () => void): void {
+            GLTFFileLoaderExtension.ApplyExtensions(loaderExtension => {
+                return loaderExtension.loadTextureBufferAsync(gltfRuntime, id, onSuccess, onError);
+            }, () => {
+                GLTFFileLoaderBase.LoadTextureBufferAsync(gltfRuntime, id, onSuccess, onError);
+            });
+        }
+
+        private static CreateTextureAsync(gltfRuntime: IGLTFRuntime, id: string, buffer: ArrayBufferView, onSuccess: (texture: Texture) => void, onError: () => void): void {
+            GLTFFileLoaderExtension.ApplyExtensions(loaderExtension => {
+                return loaderExtension.createTextureAsync(gltfRuntime, id, buffer, onSuccess, onError);
+            }, () => {
+                GLTFFileLoaderBase.CreateTextureAsync(gltfRuntime, id, buffer, onSuccess, onError);
+            });
+        }
+
+        private static ApplyExtensions(func: (loaderExtension: GLTFFileLoaderExtension) => boolean, defaultFunc: () => void): void {
+            for (var extensionName in GLTFFileLoader.Extensions) {
+                var loaderExtension = GLTFFileLoader.Extensions[extensionName];
+                if (func(loaderExtension)) {
+                    return;
+                }
+            }
+
+            defaultFunc();
+        }
+    }
+}

+ 6 - 28
loaders/glTF/babylon.glTFFileLoaderInterfaces.ts

@@ -351,9 +351,11 @@
 
         scene: Scene;
         rootUrl: string;
-        loadedBuffers: number;
-        loadedShaders: number;
-        arrayBuffers: Object;
+
+        loadedBufferCount: number;
+        loadedBufferViews: { [name: string]: ArrayBufferView };
+
+        loadedShaderCount: number;
 
         importOnlyMeshes: boolean;
         importMeshesNames?: string[];
@@ -374,28 +376,4 @@
         node: IGLTFNode;
         id: string;
     }
-
-    /**
-    * Extensions
-    */
-    export interface IGLTFExtension {
-        /**
-        * The name of the extension (example: "KHR_materials_pbr" cf. https://github.com/tsturm/glTF/tree/master/extensions/Vendor/FRAUNHOFER_materials_pbr)
-        */
-        extensionName: string;
-    }
-
-    export interface IGLTFLoaderExtension<ExtensionType extends Object, ExtensionObject extends Object> extends IGLTFExtension {
-        /**
-        * If the extensions needs the loader to skip its default behavior
-        * Example, when loading materials, if the loader should use only the extension
-        * or load the shader material and call the extension to customize the shader material
-        */
-        needToSkipDefaultLoaderBehavior(id: string, extension: ExtensionType): boolean;
-
-        /**
-        * Apply extension method
-        */
-        apply(gltfRuntime: IGLTFRuntime, id: string, name: string, extension: ExtensionType, object: ExtensionObject): ExtensionObject;
-    }
-}
+}

+ 50 - 51
loaders/glTF/babylon.glTFFileLoaderUtils.ts

@@ -88,6 +88,22 @@
         }
 
         /**
+        * Decode the base64 uri
+        * @param uri: the uri to decode
+        */
+        public static DecodeBase64(uri: string): ArrayBuffer {
+            var decodedString = atob(uri.split(",")[1]);
+            var bufferLength = decodedString.length;
+            var bufferView = new Uint8Array(new ArrayBuffer(bufferLength));
+
+            for (var i = 0; i < bufferLength; i++) {
+                bufferView[i] = decodedString.charCodeAt(i);
+            }
+
+            return bufferView.buffer;
+        }
+
+        /**
         * Returns the wrap mode of the texture
         * @param mode: the mode value
         */
@@ -101,48 +117,6 @@
         }
 
         /**
-         * Loads a texture from its name
-         * @param gltfRuntime: the gltf runtime
-         * @param name: the name of the texture
-         */
-        public static LoadTexture(gltfRuntime: IGLTFRuntime, name: string): Texture {
-            var texture: IGLTFTexture = gltfRuntime.textures[name];
-
-            if (!texture || !texture.source) {
-                return null;
-            }
-
-            if (texture.babylonTexture) {
-                return texture.babylonTexture;
-            }
-
-            var sampler: IGLTFSampler = gltfRuntime.samplers[texture.sampler];
-            var source: IGLTFImage = gltfRuntime.images[texture.source];
-            var newTexture: Texture = null;
-
-            var createMipMaps = (sampler.minFilter === ETextureFilterType.NEAREST_MIPMAP_NEAREST) ||
-                (sampler.minFilter === ETextureFilterType.NEAREST_MIPMAP_LINEAR) ||
-                (sampler.minFilter === ETextureFilterType.LINEAR_MIPMAP_NEAREST) ||
-                (sampler.minFilter === ETextureFilterType.LINEAR_MIPMAP_LINEAR);
-
-            var samplingMode = Texture.BILINEAR_SAMPLINGMODE;
-
-            if (GLTFUtils.IsBase64(source.uri)) {
-                newTexture = new Texture(source.uri, gltfRuntime.scene, !createMipMaps, true, samplingMode, null, null, source.uri, true);
-            }
-            else {
-                newTexture = new Texture(gltfRuntime.rootUrl + source.uri, gltfRuntime.scene, !createMipMaps, true, samplingMode);
-            }
-
-            newTexture.wrapU = GLTFUtils.GetWrapMode(sampler.wrapS);
-            newTexture.wrapV = GLTFUtils.GetWrapMode(sampler.wrapT);
-            newTexture.name = name;
-
-            texture.babylonTexture = newTexture;
-            return newTexture;
-        }
-
-        /**
          * Returns the byte stride giving an accessor
          * @param accessor: the GLTF accessor objet
          */
@@ -176,6 +150,26 @@
             }
         }
 
+        public static GetBufferFromBufferView(gltfRuntime: IGLTFRuntime, bufferView: IGLTFBufferView, byteOffset: number, byteLength: number, componentType: EComponentType): ArrayBufferView {
+            var byteOffset = bufferView.byteOffset + byteOffset;
+
+            var loadedBufferView = gltfRuntime.loadedBufferViews[bufferView.buffer];
+            if (byteOffset + byteLength > loadedBufferView.byteLength) {
+                throw new Error("Buffer access is out of range");
+            }
+
+            var buffer = loadedBufferView.buffer;
+            byteOffset += loadedBufferView.byteOffset;
+
+            switch (componentType) {
+                case EComponentType.BYTE: return new Int8Array(buffer, byteOffset, byteLength);
+                case EComponentType.UNSIGNED_BYTE: return new Uint8Array(buffer, byteOffset, byteLength);
+                case EComponentType.SHORT: return new Int16Array(buffer, byteOffset, byteLength);
+                case EComponentType.UNSIGNED_SHORT: return new Uint16Array(buffer, byteOffset, byteLength);
+                default: return new Float32Array(buffer, byteOffset, byteLength);
+            }
+        }
+
         /**
          * Returns a buffer from its accessor
          * @param gltfRuntime: the GLTF runtime
@@ -183,18 +177,23 @@
          */
         public static GetBufferFromAccessor(gltfRuntime: IGLTFRuntime, accessor: IGLTFAccessor): any {
             var bufferView: IGLTFBufferView = gltfRuntime.bufferViews[accessor.bufferView];
-            var arrayBuffer: ArrayBuffer = gltfRuntime.arrayBuffers[bufferView.buffer];
+            var byteLength = accessor.count * GLTFUtils.GetByteStrideFromType(accessor);
+            return GLTFUtils.GetBufferFromBufferView(gltfRuntime, bufferView, accessor.byteOffset, byteLength, accessor.componentType);
+        }
 
-            var byteOffset = accessor.byteOffset + bufferView.byteOffset;
-            var count = accessor.count * GLTFUtils.GetByteStrideFromType(accessor);
+        /**
+         * Decodes a buffer view into a string
+         * @param view: the buffer view
+         */
+        public static DecodeBufferToText(view: ArrayBufferView): string {
+            var result = "";
+            var length = view.byteLength;
 
-            switch (accessor.componentType) {
-                case EComponentType.BYTE: return new Int8Array(arrayBuffer, byteOffset, count);
-                case EComponentType.UNSIGNED_BYTE: return new Uint8Array(arrayBuffer, byteOffset, count);
-                case EComponentType.SHORT: return new Int16Array(arrayBuffer, byteOffset, count);
-                case EComponentType.UNSIGNED_SHORT: return new Uint16Array(arrayBuffer, byteOffset, count);
-                default: return new Float32Array(arrayBuffer, byteOffset, count);
+            for (var i = 0; i < length; ++i) {
+                result += String.fromCharCode(view[i]);
             }
+
+            return result;
         }
     }
 }