OV.GltfComponentType =
{
	BYTE : 5120,
	UNSIGNED_BYTE : 5121,
	SHORT : 5122,
	UNSIGNED_SHORT : 5123,
	UNSIGNED_INT : 5125,
	FLOAT  : 5126
};

OV.GltfDataType =
{
	SCALAR : 0,
	VEC2 : 1,
	VEC3 : 2,
	VEC4 : 3,
	MAT2 : 4,
	MAT3  : 5,
    MAT4  : 6
};

OV.GltfRenderMode =
{
	POINTS : 0,
	LINES : 1,
	LINE_LOOP : 2,
	LINE_STRIP : 3,
	TRIANGLES : 4,
	TRIANGLE_STRIP  : 5,
    TRIANGLE_FAN : 6
};

OV.GltfConstants =
{
	GLTF_STRING : 0x46546C67,
	JSON_CHUNK_TYPE : 0x4E4F534A,
	BINARY_CHUNK_TYPE : 0x004E4942
};

OV.GltfNodeTree = class
{
    constructor ()
    {
        this.nodes = [];
        this.nodeToParent = {};
        this.nodeMatrices = {};
    }

    AddMeshNode (nodeIndex)
    {
        this.nodes.push (nodeIndex);
        return this.nodes.length - 1;
    }

    AddNodeParent (nodeIndex, parentIndex)
    {
        this.nodeToParent[nodeIndex] = parentIndex;
    }

    GetNodeParent (nodeIndex)
    {
        let parentIndex = this.nodeToParent[nodeIndex];
        if (parentIndex === undefined || parentIndex === -1) {
            return null;
        }
        return parentIndex;
    }

    AddNodeMatrix (nodeIndex, matrix)
    {
        this.nodeMatrices[nodeIndex] = matrix;
    }

    GetNodeMatrix (nodeIndex)
    {
        let matrix = this.nodeMatrices[nodeIndex];
        if (matrix === undefined) {
            return null;
        }
        return matrix;
    }    
};

OV.GltfBufferReader = class
{
    constructor (buffer)
    {
        this.reader = new OV.BinaryReader (buffer, true);
        this.componentType = null;
        this.dataType = null;
        this.byteStride = null;
        this.dataCount = null;
        this.sparseReader = null;
    }

    SetComponentType (componentType)
    {
        this.componentType = componentType;
    }

    SetDataType (dataType)
    {
        if (dataType === 'SCALAR') {
            this.dataType = OV.GltfDataType.SCALAR;
        } else if (dataType === 'VEC2') {
            this.dataType = OV.GltfDataType.VEC2;
        } else if (dataType === 'VEC3') {
            this.dataType = OV.GltfDataType.VEC3;
        } else if (dataType === 'VEC4') {
            this.dataType = OV.GltfDataType.VEC4;
        } else if (dataType === 'MAT2') {
            this.dataType = OV.GltfDataType.MAT2;
        } else if (dataType === 'MAT3') {
            this.dataType = OV.GltfDataType.MAT3;
        } else if (dataType === 'MAT4') {
            this.dataType = OV.GltfDataType.MAT4;
        }
    }

    SetByteStride (byteStride)
    {
        this.byteStride = byteStride;
    }

    SetDataCount (dataCount)
    {
        this.dataCount = dataCount;
    }

    SetSparseReader (indexReader, valueReader)
    {
        this.sparseReader = {
            indexReader : indexReader,
            valueReader : valueReader
        };
    }

    ReadArrayBuffer (byteLength)
    {
        return this.reader.ReadArrayBuffer (byteLength);
    }
    
    GetDataCount ()
    {
        return this.dataCount;
    }

    ReadData ()
    {
        if (this.dataType === null) {
            return null;
        }
        if (this.dataType === OV.GltfDataType.SCALAR) {
            let data = this.ReadComponent ();
            this.SkipBytesByStride (1);
            return data;
        } else if (this.dataType === OV.GltfDataType.VEC2) {
            let x = this.ReadComponent ();
            let y = this.ReadComponent ();
            this.SkipBytesByStride (2);
            return new OV.Coord2D (x, y);
        } else if (this.dataType === OV.GltfDataType.VEC3) {
            let x = this.ReadComponent ();
            let y = this.ReadComponent ();
            let z = this.ReadComponent ();
            this.SkipBytesByStride (3);
            return new OV.Coord3D (x, y, z);
        }
        return null;
    }

    EnumerateData (onData)
    {
        if (this.sparseReader === null) {
            for (let i = 0; i < this.dataCount; i++) {
                onData (this.ReadData ());
            }
        } else {
            let sparseData = [];
            for (let i = 0; i < this.sparseReader.indexReader.GetDataCount (); i++) {
                let index = this.sparseReader.indexReader.ReadData ();
                let value = this.sparseReader.valueReader.ReadData ();
                sparseData.push ({
                    index : index,
                    value : value
                });
            }
            let sparseIndex = 0;
            for (let i = 0; i < this.dataCount; i++) {
                let data = this.ReadData ();
                if (sparseIndex < sparseData.length && sparseData[sparseIndex].index === i) {
                    onData (sparseData[sparseIndex].value);
                    sparseIndex += 1;
                } else {
                    onData (data);
                }
            }
        }
    }

    SkipBytes (bytes)
    {
        this.reader.Skip (bytes);
    }

    ReadComponent ()
    {
        if (this.componentType === null) {
            return null;
        }
        if (this.componentType === OV.GltfComponentType.BYTE) {
            return this.reader.ReadCharacter8 ();
        } else if (this.componentType === OV.GltfComponentType.UNSIGNED_BYTE) {
            return this.reader.ReadUnsignedCharacter8 ();
        } else if (this.componentType === OV.GltfComponentType.SHORT) {
            return this.reader.ReadInteger16 ();
        } else if (this.componentType === OV.GltfComponentType.UNSIGNED_SHORT) {
            return this.reader.ReadUnsignedInteger16 ();
        } else if (this.componentType === OV.GltfComponentType.UNSIGNED_INT) {
            return this.reader.ReadInteger32 ();
        } else if (this.componentType === OV.GltfComponentType.FLOAT) {
            return this.reader.ReadFloat32 ();
        }
        return null;
    }

    SkipBytesByStride (componentCount)
    {
        if (this.byteStride === null) {
            return;
        }
        let readBytes = componentCount * this.GetComponentSize ();
        this.reader.Skip (this.byteStride - readBytes);
    }

    GetComponentSize ()
    {
        if (this.componentType === OV.GltfComponentType.BYTE) {
            return 1;
        } else if (this.componentType === OV.GltfComponentType.UNSIGNED_BYTE) {
            return 1;
        } else if (this.componentType === OV.GltfComponentType.SHORT) {
            return 2;
        } else if (this.componentType === OV.GltfComponentType.UNSIGNED_SHORT) {
            return 2;
        } else if (this.componentType === OV.GltfComponentType.UNSIGNED_INT) {
            return 4;
        } else if (this.componentType === OV.GltfComponentType.FLOAT) {
            return 4;
        }
        return 0;
    }    
};

OV.GltfExtensions = class
{
    constructor ()
    {
        this.supportedExtensions = [
            'KHR_materials_pbrSpecularGlossiness',
            'KHR_texture_transform',
        ];
    }

    GetUnsupportedExtensions (extensionsRequired)
    {
        let unsupportedExtensions = [];
        if (extensionsRequired === undefined) {
            return unsupportedExtensions;
        }
        for (let i = 0; i < extensionsRequired.length; i++) {
            let requiredExtension = extensionsRequired[i];
            if (this.supportedExtensions.indexOf (requiredExtension) === -1) {
                unsupportedExtensions.push (requiredExtension);
            }
        }
        return unsupportedExtensions;
    }

    ProcessMaterial (gltfMaterial, material, imporTextureFn)
    {
        function GetMaterialComponent (component)
        {
            return parseInt (Math.round (OV.LinearToSRGB (component) * 255.0), 10);
        }

        if (gltfMaterial.extensions === undefined) {
            return;
        }
        let khrSpecularGlossiness = gltfMaterial.extensions.KHR_materials_pbrSpecularGlossiness;
        if (khrSpecularGlossiness !== undefined) {
            let diffuseColor = khrSpecularGlossiness.diffuseFactor;
            if (diffuseColor !== undefined) {
                material.diffuse = new OV.Color (
                    GetMaterialComponent (diffuseColor[0]),
                    GetMaterialComponent (diffuseColor[1]),
                    GetMaterialComponent (diffuseColor[2])
                );
                material.opacity = diffuseColor[3];
            }
            let diffuseTexture = khrSpecularGlossiness.diffuseTexture;
            if (diffuseTexture !== undefined) {
                material.diffuseMap = imporTextureFn (diffuseTexture);
            }
            let specularColor = khrSpecularGlossiness.specularFactor;
            if (specularColor !== undefined) {
                material.specular = new OV.Color (
                    GetMaterialComponent (specularColor[0]),
                    GetMaterialComponent (specularColor[1]),
                    GetMaterialComponent (specularColor[2])
                );
            }
            let specularTexture = khrSpecularGlossiness.specularGlossinessTexture;
            if (specularTexture !== undefined) {
                material.specularMap = imporTextureFn (specularTexture);
            }            
            let glossiness = khrSpecularGlossiness.glossinessFactor;
            if (glossiness !== undefined) {
                material.shininess = glossiness;
            }
        }
    }

    ProcessTexture (gltfTexture, texture)
    {
        if (gltfTexture.extensions === undefined) {
            return;
        }
        let khrTextureTransform = gltfTexture.extensions.KHR_texture_transform;
        if (khrTextureTransform !== undefined) {
            if (khrTextureTransform.offset !== undefined) {
                texture.offset.x = khrTextureTransform.offset[0];
                texture.offset.y = -khrTextureTransform.offset[1];
            }
            if (khrTextureTransform.scale !== undefined) {
                texture.scale.x = khrTextureTransform.scale[0];
                texture.scale.y = khrTextureTransform.scale[1];
            }
            if (khrTextureTransform.rotation !== undefined) {
                texture.rotation = -khrTextureTransform.rotation;
            }
        }
    }
};

OV.ImporterGltf = class extends OV.ImporterBase
{
	constructor ()
	{
		super ();
        
        this.bufferContents = null;
        this.gltfExtensions = null;
	}
	
	ResetState ()
	{
        this.bufferContents = [];
        this.gltfExtensions = new OV.GltfExtensions ();
	}

	CanImportExtension (extension)
	{
		return extension === 'gltf' || extension === 'glb';
	}

	GetKnownFileFormats ()
	{
		return {
			'gltf' : OV.FileFormat.Text,
            'glb' : OV.FileFormat.Binary,
            'bin' : OV.FileFormat.Binary
		};
	}

    GetUpDirection ()
    {
        return OV.Direction.Y;
    }
    
	ImportContent (fileContent)
	{
        if (this.extension === 'gltf') {
            this.ProcessGltf (fileContent);
        } else if (this.extension === 'glb') {
            this.ProcessBinaryGltf (fileContent);
        }
	}

    ProcessGltf (fileContent)
    {
        let gltf = JSON.parse (fileContent);
        if (gltf.asset.version !== '2.0') {
            this.SetError ();
            this.SetMessage ('Invalid glTF version.');
            return;
        }

        for (let i = 0; i < gltf.buffers.length; i++) {
            let buffer = null;
            let gltfBuffer = gltf.buffers[i];
            let base64Buffer = OV.Base64DataURIToArrayBuffer (gltfBuffer.uri);
            if (base64Buffer === null) {
                let fileBuffer = this.callbacks.getFileBuffer (gltfBuffer.uri);
                if (fileBuffer !== null) {
                    buffer = fileBuffer.buffer;
                }
            } else {
                buffer = base64Buffer.buffer;
            }
            if (buffer === null) {
                this.SetError ();
                this.SetMessage ('One  of the requested buffers is missing.');
                return;
            }
            this.bufferContents.push (buffer);
        }

        this.ProcessMainFile (gltf);
    }

    ProcessBinaryGltf (fileContent)
    {
        function ReadChunk (reader)
        {
            let length = reader.ReadUnsignedInteger32 ();
            let type = reader.ReadUnsignedInteger32 ();
            let buffer = reader.ReadArrayBuffer (length);
            return {
                type : type,
                buffer : buffer
            };
        }

        let reader = new OV.BinaryReader (fileContent, true);
        let magic = reader.ReadUnsignedInteger32 ();
        if (magic !== OV.GltfConstants.GLTF_STRING) {
            this.SetError ();
            this.SetMessage ('Invalid glTF file.');
            return;
        }
        let version = reader.ReadUnsignedInteger32 ();
        if (version !== 2) {
            this.SetError ();
            this.SetMessage ('Invalid glTF version.');
            return;
        }
        let length = reader.ReadUnsignedInteger32 ();
        if (length !== reader.GetByteLength ()) {
            this.SetError ();
            this.SetMessage ('Invalid glTF file.');
            return;
        }

        let gltfTextContent = null;
        while (!reader.End ()) {
            let chunk = ReadChunk (reader);
            if (chunk.type === OV.GltfConstants.JSON_CHUNK_TYPE) {
                gltfTextContent = OV.ArrayBufferToUtf8String (chunk.buffer);
            } else if (chunk.type === OV.GltfConstants.BINARY_CHUNK_TYPE) {
                this.bufferContents.push (chunk.buffer);
            }
        }

        if (gltfTextContent !== null) {
            let gltf = JSON.parse (gltfTextContent);
            this.ProcessMainFile (gltf);
        }
    }

    ProcessMainFile (gltf)
    {
        let unsupportedExtensions = this.gltfExtensions.GetUnsupportedExtensions (gltf.extensionsRequired);
        if (unsupportedExtensions.length > 0) {
            this.SetError ();
            this.SetMessage ('Unsupported extension: ' + unsupportedExtensions.join (', ') + '.');
            return;            
        }

        let defaultScene = this.GetDefaultScene (gltf);
        if (defaultScene === null) {
            this.SetError ();
            this.SetMessage ('No default scene found.');
            return;
        }

        let materials = gltf.materials;
        if (materials !== undefined) {
            for (let i = 0; i < materials.length; i++) {
                this.ImportMaterial (gltf, i);
            }          
        }

        let nodeTree = this.CollectMeshNodesForScene (gltf, defaultScene);
        for (let i = 0; i < nodeTree.nodes.length; i++) {
            let nodeIndex = nodeTree.nodes[i];
            this.ImportMeshNode (gltf, nodeIndex, nodeTree);
        }
    }

    GetDefaultScene (gltf)
    {
        let defaultSceneIndex = gltf.scene || 0;
        if (defaultSceneIndex >= gltf.scenes.length) {
            return null;
        }
        return gltf.scenes[defaultSceneIndex];
    }

    CollectMeshNodesForScene (gltf, scene)
    {
        function CollectMeshNodeIndices (gltf, parentIndex, nodeIndex, nodeTree, meshNodes)
        {
            let node = gltf.nodes[nodeIndex];
            if (node.mesh !== undefined) {
                nodeTree.AddMeshNode (nodeIndex);
                meshNodes.push (nodeIndex);
            }
            nodeTree.AddNodeParent (nodeIndex, parentIndex);
            if (node.children !== undefined) {
                for (let i = 0; i < node.children.length; i++) {
                    let childNodeIndex = node.children[i];
                    CollectMeshNodeIndices (gltf, nodeIndex, childNodeIndex, nodeTree, meshNodes);
                }
            }
        }

        let nodeTree = new OV.GltfNodeTree ();
        let meshNodes = [];
        for (let i = 0; i < scene.nodes.length; i++) {
            let nodeIndex = scene.nodes[i];
            CollectMeshNodeIndices (gltf, -1, nodeIndex, nodeTree, meshNodes);
        }
        return nodeTree;
    }

    ImportMaterial (gltf, materialIndex)
    {
        function GetMaterialComponent (component)
        {
            return parseInt (Math.round (OV.LinearToSRGB (component) * 255.0), 10);
        }

        let gltfMaterial = gltf.materials[materialIndex];
        let material = new OV.Material ();
        if (gltfMaterial.name !== undefined) {
            material.name = gltfMaterial.name;
        }

        material.diffuse = new OV.Color (
            GetMaterialComponent (1.0),
            GetMaterialComponent (1.0),
            GetMaterialComponent (1.0)
        );
        if (gltfMaterial.pbrMetallicRoughness !== undefined) {
            let baseColor = gltfMaterial.pbrMetallicRoughness.baseColorFactor;
            if (baseColor !== undefined) {
                material.diffuse = new OV.Color (
                    GetMaterialComponent (baseColor[0]),
                    GetMaterialComponent (baseColor[1]),
                    GetMaterialComponent (baseColor[2])
                );
                material.opacity = baseColor[3];
            }
            let emissiveColor = gltfMaterial.emissiveFactor;
            if (emissiveColor !== undefined) {
                material.emissive = new OV.Color (
                    GetMaterialComponent (emissiveColor[0]),
                    GetMaterialComponent (emissiveColor[1]),
                    GetMaterialComponent (emissiveColor[2])
                );
            }

            material.diffuseMap = this.ImportTexture (gltf, gltfMaterial.pbrMetallicRoughness.baseColorTexture);
            material.normalMap = this.ImportTexture (gltf, gltfMaterial.normalTexture);
            material.emissiveMap = this.ImportTexture (gltf, gltfMaterial.emissiveTexture);
            if (material.diffuseMap !== null) {
                material.multiplyDiffuseMap = true;
            }

            let alphaMode = gltfMaterial.alphaMode;
            if (alphaMode !== undefined) {
                if (alphaMode === 'BLEND') {
                    material.transparent = true;
                } else if (alphaMode === 'MASK') {
                    material.transparent = true;
                    material.alphaTest = gltfMaterial.alphaCutoff || 0.5;
                }
            }
        }

        let obj = this;
        this.gltfExtensions.ProcessMaterial (gltfMaterial, material, function (textureRef) {
            return obj.ImportTexture (gltf, textureRef);
        });
        this.model.AddMaterial (material);
    }

    ImportTexture (gltf, gltfTextureRef)
    {
        function GetTextureFileExtension (mimeType)
        {
            if (mimeType === undefined || mimeType === null) {
                return '';
            }
            let mimeParts = mimeType.split ('/');
            if (mimeParts.length === 0) {
                return '';
            }
            return '.' + mimeParts[mimeParts.length - 1];
        }

        if (gltfTextureRef === undefined || gltfTextureRef === null) {
            return null;
        }

        let texture = new OV.TextureMap ();
        let gltfTexture = gltf.textures[gltfTextureRef.index];
        let gltfImage = gltf.images[gltfTexture.source];

        let textureIndexString = gltfTexture.source.toString ();
        if (gltfImage.uri !== undefined) {
            let base64Buffer = OV.Base64DataURIToArrayBuffer (gltfImage.uri);
            if (base64Buffer !== null) {
                let blob = new Blob ([base64Buffer.buffer], { type : base64Buffer.mimeType });
                texture.name = 'Embedded_' + textureIndexString + GetTextureFileExtension (base64Buffer.mimeType);
                texture.url = URL.createObjectURL (blob);
                texture.buffer = base64Buffer.buffer;
            } else {
                let textureBuffer = this.callbacks.getFileBuffer (gltfImage.uri);
                texture.name = gltfImage.uri;
                if (textureBuffer !== null) {
                    texture.url = textureBuffer.url;
                    texture.buffer = textureBuffer.buffer;
                }
            }
        } else if (gltfImage.bufferView !== undefined) {
            let bufferView = gltf.bufferViews[gltfImage.bufferView];
            let reader = this.GetReaderFromBufferView (bufferView);
            if (reader !== null) {
                let buffer = reader.ReadArrayBuffer (bufferView.byteLength);
                let blob = new Blob ([buffer], { type: gltfImage.mimeType });
                texture.name = 'Binary_' + textureIndexString + GetTextureFileExtension (gltfImage.mimeType);
                texture.url = URL.createObjectURL (blob);
                texture.buffer = buffer;
            }
        }

        this.gltfExtensions.ProcessTexture (gltfTextureRef, texture);
        return texture;
    }

    ImportMeshNode (gltf, nodeIndex, nodeTree)
    {
        function GetNodeTransformation (gltf, nodeIndex, nodeTree)
        {
            let node = gltf.nodes[nodeIndex];
            let matrix = nodeTree.GetNodeMatrix (nodeIndex);
            if (matrix !== null) {
                return matrix;
            }

            matrix = new OV.Matrix ().CreateIdentity ();
            if (node.matrix !== undefined) {
                matrix.Set (node.matrix);
            } else {
                let hasTransformation = false;
                let translation = [0.0, 0.0, 0.0];
                let rotation = [0.0, 0.0, 0.0, 1.0];
                let scale = [1.0, 1.0, 1.0];
                if (node.translation !== undefined) {
                    translation = node.translation;
                    hasTransformation = true;
                }
                if (node.rotation !== undefined) {
                    rotation = node.rotation;
                    hasTransformation = true;
                }
                if (node.scale !== undefined) {
                    scale = node.scale;
                    hasTransformation = true;
                }
                
                if (hasTransformation) {
                    matrix.ComposeTRS (translation, rotation, scale);
                }
            }

            let parentNodeIndex = nodeTree.GetNodeParent (nodeIndex);
            if (parentNodeIndex !== null) {
                let parentMatrix = GetNodeTransformation (gltf, parentNodeIndex, nodeTree);
                matrix = matrix.MultiplyMatrix (parentMatrix);
            }

            nodeTree.AddNodeMatrix (nodeIndex, matrix);
            return matrix;            
        }

        let gltfNode = gltf.nodes[nodeIndex];
        let gltfMeshIndex = gltfNode.mesh;
        let gltfMesh = gltf.meshes[gltfMeshIndex];

        let mesh = new OV.Mesh ();
        this.model.AddMesh (mesh);
        if (gltfMesh.name !== undefined) {
            mesh.SetName (gltfMesh.name);
        }

        for (let i = 0; i < gltfMesh.primitives.length; i++) {
            let primitive = gltfMesh.primitives[i];
            this.ImportPrimitive (gltf, primitive, mesh);
        }

        let matrix = GetNodeTransformation (gltf, nodeIndex, nodeTree);
        let transformation = new OV.Transformation (matrix);
        OV.TransformMesh (mesh, transformation);
    }    

    ImportPrimitive (gltf, primitive, mesh)
    {
        function AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset)
        {
            let triangle = new OV.Triangle (vertexOffset + v0, vertexOffset + v1, vertexOffset + v2);
            if (hasNormals) {
                triangle.SetNormals (
                    normalOffset + v0,
                    normalOffset + v1,
                    normalOffset + v2
                );
            }
            if (hasUVs) {
                triangle.SetTextureUVs (
                    uvOffset + v0,
                    uvOffset + v1,
                    uvOffset + v2
                );
            }
            if (primitive.material !== undefined) {
                triangle.mat = primitive.material;
            }
            mesh.AddTriangle (triangle);
        }

        if (primitive.attributes === undefined) {
            return;
        }

        let hasVertices = (primitive.attributes.POSITION !== undefined);
        let hasNormals = (primitive.attributes.NORMAL !== undefined);
        let hasUVs = (primitive.attributes.TEXCOORD_0 !== undefined);
        let hasIndices = (primitive.indices !== undefined);

        let mode = OV.GltfRenderMode.TRIANGLES;
        if (primitive.mode !== undefined) {
            mode = primitive.mode;
        }
        if (mode !== OV.GltfRenderMode.TRIANGLES && mode !== OV.GltfRenderMode.TRIANGLE_STRIP && mode !== OV.GltfRenderMode.TRIANGLE_FAN) {
            return;
        }

        let vertexOffset = mesh.VertexCount ();
        let normalOffset = mesh.NormalCount ();
        let uvOffset = mesh.TextureUVCount ();

        if (hasVertices) {
            let accessor = gltf.accessors[primitive.attributes.POSITION];
            let reader = this.GetReaderFromAccessor (gltf, accessor);
            if (reader === null) {
                return;
            }
            reader.EnumerateData (function (data) {
                mesh.AddVertex (data);
            });
        } else {
            return;
        }

        if (hasNormals) {
            let accessor = gltf.accessors[primitive.attributes.NORMAL];
            let reader = this.GetReaderFromAccessor (gltf, accessor);
            if (reader === null) {
                return;
            }
            reader.EnumerateData (function (data) {
                mesh.AddNormal (data);
            });
        }

        if (hasUVs) {
            let accessor = gltf.accessors[primitive.attributes.TEXCOORD_0];
            let reader = this.GetReaderFromAccessor (gltf, accessor);
            if (reader === null) {
                return;
            }
            reader.EnumerateData (function (data) {
                data.y = -data.y;
                mesh.AddTextureUV (data);
            });
        }

        let vertexIndices = [];
        if (hasIndices) {
            let accessor = gltf.accessors[primitive.indices];
            let reader = this.GetReaderFromAccessor (gltf, accessor);
            if (reader === null) {
                return;
            }
            reader.EnumerateData (function (data) {
                vertexIndices.push (data);
            });          
        } else {
            for (let i = 0; i < mesh.VertexCount (); i++) {
                vertexIndices.push (i);
            }
        }

        if (mode === OV.GltfRenderMode.TRIANGLES) {
            for (let i = 0; i < vertexIndices.length; i += 3) {
                let v0 = vertexIndices[i];
                let v1 = vertexIndices[i + 1];
                let v2 = vertexIndices[i + 2];
                AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset);
            }
        } else if (mode === OV.GltfRenderMode.TRIANGLE_STRIP) {
            for (let i = 0; i < vertexIndices.length - 2; i++) {
                let v0 = vertexIndices[i];
                let v1 = vertexIndices[i + 1];
                let v2 = vertexIndices[i + 2];
                if (i % 2 === 1) {
                    let tmp = v1;
                    v1 = v2;
                    v2 = tmp;
                }
                AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset);
            }
        } else if (mode === OV.GltfRenderMode.TRIANGLE_FAN) {
            for (let i = 1; i < vertexIndices.length - 1; i++) {
                let v0 = vertexIndices[0];
                let v1 = vertexIndices[i];
                let v2 = vertexIndices[i + 1];
                AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset);
            }
        }
    }

    GetReaderFromBufferView (bufferView)
    {
        let bufferIndex = bufferView.buffer || 0;
        let buffer = this.bufferContents[bufferIndex];
        if (buffer === null) {
            return null;
        }

        let reader = new OV.GltfBufferReader (buffer);
        reader.SkipBytes (bufferView.byteOffset || 0);
        let byteStride = bufferView.byteStride;
        if (byteStride !== undefined) {
            reader.SetByteStride (byteStride);
        }

        return reader;
    }

    GetReaderFromAccessor (gltf, accessor)
    {
        let bufferViewIndex = accessor.bufferView || 0;
        let bufferView = gltf.bufferViews[bufferViewIndex];
        let reader = this.GetReaderFromBufferView (bufferView);
        if (reader === null) {
            return null;
        }

        reader.SetComponentType (accessor.componentType);
        reader.SetDataType (accessor.type);
        reader.SetDataCount (accessor.count);
        reader.SkipBytes (accessor.byteOffset || 0);

        if (accessor.sparse !== undefined) {
            let indexReader = this.GetReaderFromSparseAccessor (gltf, accessor.sparse.indices, accessor.sparse.indices.componentType, 'SCALAR', accessor.sparse.count);
            let valueReader = this.GetReaderFromSparseAccessor (gltf, accessor.sparse.values, accessor.componentType, accessor.type, accessor.sparse.count);
            if (indexReader !== null && valueReader !== null) {
                reader.SetSparseReader (indexReader, valueReader);
            }
        }
        return reader;
    }

    GetReaderFromSparseAccessor (gltf, sparseAccessor, componentType, type, count)
    {
        if (sparseAccessor.bufferView === undefined) {
            return null;
        }

        let bufferView = gltf.bufferViews[sparseAccessor.bufferView];
        let reader = this.GetReaderFromBufferView (bufferView);
        if (reader === null) {
            return null;
        }
        
        reader.SetComponentType (componentType);
        reader.SetDataType (type);
        reader.SetDataCount (count);
        reader.SkipBytes (sparseAccessor.byteOffset || 0);
        return reader;
    }
};
