示例#1
0
文件: Mesh.cs 项目: dak0ta101/ps2ls
            public void ReadHalf2s(ICollection <Vector2> half2s, int offset, int startVertexIndex, int vertexCount)
            {
                Vector2 half2;

                for (int vertexIndex = startVertexIndex; vertexIndex < startVertexIndex + vertexCount; ++vertexIndex)
                {
                    half2.X = Half.FromBytes(Data, (vertexIndex * BytesPerVertex) + offset + 0).ToSingle();
                    half2.Y = Half.FromBytes(Data, (vertexIndex * BytesPerVertex) + offset + 2).ToSingle();
                    half2s.Add(half2);
                }
            }
示例#2
0
        public void ExportModelToDirectoryWithExportOptions(Model model, String directory, ExportOptions exportOptions)
        {
            //TODO: Figure out what to do with non-version 4 models.
            if (model != null && model.Version != 4)
            {
                return;
            }

            NumberFormatInfo format = new NumberFormatInfo();

            format.NumberDecimalSeparator = ".";

            if (exportOptions.Package)
            {
                try
                {
                    DirectoryInfo directoryInfo = Directory.CreateDirectory(directory + @"\" + Path.GetFileNameWithoutExtension(model.Name));
                    directory = directoryInfo.FullName;
                }
                catch (Exception) { }
            }

            if (exportOptions.Textures)
            {
                ImageImporter imageImporter = new ImageImporter();
                ImageExporter imageExporter = new ImageExporter();

                foreach (String textureString in model.TextureStrings)
                {
                    MemoryStream textureMemoryStream = AssetManager.Instance.CreateAssetMemoryStreamByName(textureString);

                    if (textureMemoryStream == null)
                    {
                        continue;
                    }

                    Image textureImage = imageImporter.LoadImageFromStream(textureMemoryStream);

                    if (textureImage == null)
                    {
                        continue;
                    }

                    imageExporter.SaveImage(textureImage, exportOptions.TextureFormat.ImageType, directory + @"\" + Path.GetFileNameWithoutExtension(textureString) + @"." + exportOptions.TextureFormat.Extension);
                }
            }

            String path = directory + @"\" + Path.GetFileNameWithoutExtension(model.Name) + ".obj";

            FileStream   fileStream   = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write);
            StreamWriter streamWriter = new StreamWriter(fileStream);

            for (Int32 i = 0; i < model.Meshes.Length; ++i)
            {
                Mesh mesh = model.Meshes[i];

                MaterialDefinition materialDefinition = MaterialDefinitionManager.Instance.MaterialDefinitions[model.Materials[(Int32)mesh.MaterialIndex].MaterialDefinitionHash];
                VertexLayout       vertexLayout       = MaterialDefinitionManager.Instance.VertexLayouts[materialDefinition.DrawStyles[0].VertexLayoutNameHash];

                //position
                VertexLayout.Entry.DataTypes positionDataType;
                Int32 positionOffset;
                Int32 positionStreamIndex;

                vertexLayout.GetEntryInfoFromDataUsageAndUsageIndex(VertexLayout.Entry.DataUsages.Position, 0, out positionDataType, out positionStreamIndex, out positionOffset);

                Mesh.VertexStream positionStream = mesh.VertexStreams[positionStreamIndex];

                for (Int32 j = 0; j < mesh.VertexCount; ++j)
                {
                    Vector3 position = readVector3(exportOptions, positionOffset, positionStream, j);

                    position.X *= exportOptions.Scale.X;
                    position.Y *= exportOptions.Scale.Y;
                    position.Z *= exportOptions.Scale.Z;

                    streamWriter.WriteLine("v " + position.X.ToString(format) + " " + position.Y.ToString(format) + " " + position.Z.ToString(format));
                }

                //texture coordinates
                if (exportOptions.TextureCoordinates)
                {
                    VertexLayout.Entry.DataTypes texCoord0DataType;
                    Int32 texCoord0Offset      = 0;
                    Int32 texCoord0StreamIndex = 0;

                    Boolean texCoord0Present = vertexLayout.GetEntryInfoFromDataUsageAndUsageIndex(VertexLayout.Entry.DataUsages.Texcoord, 0, out texCoord0DataType, out texCoord0StreamIndex, out texCoord0Offset);

                    if (texCoord0Present)
                    {
                        Mesh.VertexStream texCoord0Stream = mesh.VertexStreams[texCoord0StreamIndex];

                        for (Int32 j = 0; j < mesh.VertexCount; ++j)
                        {
                            Vector2 texCoord;

                            switch (texCoord0DataType)
                            {
                            case VertexLayout.Entry.DataTypes.Float2:
                                texCoord.X = BitConverter.ToSingle(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + 0);
                                texCoord.Y = 1.0f - BitConverter.ToSingle(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + 4);
                                break;

                            case VertexLayout.Entry.DataTypes.float16_2:
                                texCoord.X = Half.FromBytes(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + texCoord0Offset + 0).ToSingle();
                                texCoord.Y = 1.0f - Half.FromBytes(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + texCoord0Offset + 2).ToSingle();
                                break;

                            default:
                                texCoord.X = 0;
                                texCoord.Y = 0;
                                break;
                            }

                            streamWriter.WriteLine("vt " + texCoord.X.ToString(format) + " " + texCoord.Y.ToString(format));
                        }
                    }
                }
            }

            //faces
            UInt32 vertexCount = 0;

            for (Int32 i = 0; i < model.Meshes.Length; ++i)
            {
                Mesh mesh = model.Meshes[i];

                streamWriter.WriteLine("g Mesh" + i);

                for (Int32 j = 0; j < mesh.IndexCount; j += 3)
                {
                    UInt32 index0, index1, index2;

                    switch (mesh.IndexSize)
                    {
                    case 2:
                        index0 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 0) + 1;
                        index1 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 2) + 1;
                        index2 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 4) + 1;
                        break;

                    case 4:
                        index0 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 0) + 1;
                        index1 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 4) + 1;
                        index2 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 8) + 1;
                        break;

                    default:
                        index0 = 0;
                        index1 = 0;
                        index2 = 0;
                        break;
                    }

                    if (exportOptions.Normals && exportOptions.TextureCoordinates)
                    {
                        streamWriter.WriteLine("f " + index2 + "/" + index2 + "/" + index2 + " " + index1 + "/" + index1 + "/" + index1 + " " + index0 + "/" + index0 + "/" + index0);
                    }
                    else if (exportOptions.Normals)
                    {
                        streamWriter.WriteLine("f " + index2 + "//" + index2 + " " + index1 + "//" + index1 + " " + index0 + "//" + index0);
                    }
                    else if (exportOptions.TextureCoordinates)
                    {
                        streamWriter.WriteLine("f " + index2 + "/" + index2 + " " + index1 + "/" + index1 + " " + index0 + "/" + index0);
                    }
                    else
                    {
                        streamWriter.WriteLine("f " + index2 + " " + index1 + " " + index0);
                    }
                }

                vertexCount += (UInt32)mesh.VertexCount;
            }

            streamWriter.Close();
        }
示例#3
0
        private static void TransformRgba16161616F(byte[] buffer, BinaryReader br, uint width, uint height)
        {
            // I have no idea how this works. It's just converted straight from VTFLib.
            // I think the half format is slightly different to what it should be, which causes the result to be different to VTFLib.
            // Fortunately Sledge does not need to care about cubemaps, which is what this format seems to be used for...
            const int a     = 6;
            const int r     = 0;
            const int g     = 2;
            const int b     = 4;
            var       bytes = br.ReadBytes((int)(width * height * 8));

            var log = 0d;

            for (int i = 0, j = 0; i < bytes.Length; i += 8, j += 4)
            {
                var hb  = Half.FromBytes(bytes, i + b).ToSingle();
                var hg  = Half.FromBytes(bytes, i + g).ToSingle();
                var hr  = Half.FromBytes(bytes, i + r).ToSingle();
                var lum = hr * 0.299f + hg * 0.587f + hb * 0.114f;
                log += Math.Log(0.0000000001d + lum);
            }
            log = Math.Exp(log / (width * height));

            for (int i = 0, j = 0; i < bytes.Length; i += 8, j += 4)
            {
                var hb = Half.FromBytes(bytes, i + b).ToSingle();
                var hg = Half.FromBytes(bytes, i + g).ToSingle();
                var hr = Half.FromBytes(bytes, i + r).ToSingle();
                var ha = Half.FromBytes(bytes, i + a).ToSingle();

                var y = hr * 0.299f + hg * 0.587f + hb * 0.114f;
                var u = (hb - y) * 0.565f;
                var v = (hr - y) * 0.713f;

                var mul = 4 * y / log;
                mul  = mul / (1 + mul);
                mul /= y;

                hr = (float)Math.Pow((y + 1.403f * v) * mul, 2.25f);
                hg = (float)Math.Pow((y - 0.344f * u - 0.714f * v) * mul, 2.25f);
                hb = (float)Math.Pow((y + 1.770f * u) * mul, 2.25f);

                if (hr < 0)
                {
                    hr = 0;
                }
                if (hr > 1)
                {
                    hr = 1;
                }
                if (hg < 0)
                {
                    hg = 0;
                }
                if (hg > 1)
                {
                    hg = 1;
                }
                if (hb < 0)
                {
                    hb = 0;
                }
                if (hb > 1)
                {
                    hb = 1;
                }

                buffer[j + 0] = (byte)(hb * 255); // b
                buffer[j + 1] = (byte)(hg * 255); // g
                buffer[j + 2] = (byte)(hr * 255); // r
                buffer[j + 3] = (byte)(ha * 255); // a
            }
        }
示例#4
0
        MeshDrawer ReadMeshChunk(int streamOffset)
        {
            //mesh name
            reader.BaseStream.Position = streamOffset + 0x7;
            int chunkSize   = reader.ReadByte();
            int meshNamePos = (int)reader.BaseStream.Position + reader.ReadInt32();

            reader.BaseStream.Position = meshNamePos;
            string meshName = reader.readString();

            //material reference
            reader.BaseStream.Position = streamOffset + 0x14;
            int matNamePos = (int)reader.BaseStream.Position + reader.ReadInt32();

            reader.BaseStream.Position = matNamePos + 0x8;
            string matName = reader.readString();

            //bones reference
            reader.BaseStream.Position = streamOffset + 0x58;
            int weightBoneNameTableStart = (int)reader.BaseStream.Position + reader.ReadInt32();

            reader.BaseStream.Position = streamOffset + 0x5c;
            int WeightBoneTableStart = (int)reader.BaseStream.Position + reader.ReadInt32();

            reader.BaseStream.Position = streamOffset + 0x78;
            int facesCount = reader.ReadInt32();

            reader.BaseStream.Position = streamOffset + 0x84;
            int  verticesCount = reader.ReadInt32();
            byte size          = 4;
            int  SizeTest      = verticesCount * chunkSize;

            if (SizeTest < 0x100)
            {
                size = 1;
            }
            else if (SizeTest < 0x10000)
            {
                size = 2;
            }

            reader.BaseStream.Position += 8;
            int vertSize   = reader.readVal(size);
            int VertOffset = (int)reader.BaseStream.Position;

            List <VertexRigged3D> vertices = new List <VertexRigged3D>();

            for (int i = 0; i < verticesCount; i++)
            {
                VertexRigged3D vertice = new VertexRigged3D();
                vertice.Position = reader.readVector3();
                //vertex color unsupported
                reader.BaseStream.Position += 4;
                //if (chunkSize == 0x24 || chunkSize == 0x28)
                if (chunkSize == 0x24)
                {
                    reader.BaseStream.Position += 4;
                }
                else if (chunkSize == 0x30)
                {
                    reader.BaseStream.Position += 4;
                    reader.BaseStream.Position += 0xc;
                }
                vertice.UV   = new Vector2();
                vertice.UV.X = Half.FromBytes(reader.ReadBytes(2), 0);
                vertice.UV.Y = Half.FromBytes(reader.ReadBytes(2), 0);

                /*
                 * if (chunkSize == 0x28)
                 *  file.BaseStream.Position += 4;
                 */
                vertice.BoneIndices = new IVector4(new int[] { reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte() });
                if (chunkSize == 0x28)
                {
                    vertice.BoneWeigths = reader.readVector4();
                }
                else
                {
                    vertice.BoneWeigths  = new Vector4(reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt16());
                    vertice.BoneWeigths /= 65535f;
                }
                vertices.Add(vertice);
            }

            int unknownSize = 4;

            if (size == 1)
            {
                unknownSize = 2;
            }

            reader.BaseStream.Position = VertOffset + vertSize + size + unknownSize;
            int unknownCount = reader.ReadInt32();

            reader.BaseStream.Position += 0x10 * unknownCount;
            reader.ReadInt32();
            //Read Faces
            size = 4;
            if (facesCount < 0x100)
            {
                size = 1;
            }
            else if (facesCount < 0x10000)
            {
                size = 2;
            }

            int  faceSize       = reader.readVal(size);
            byte indexValueSize = 4;

            if (verticesCount < 0x100)
            {
                indexValueSize = 1;
            }
            else if (verticesCount < 0x10000)
            {
                indexValueSize = 2;
            }

            int faceOffset = (int)reader.BaseStream.Position;

            int[] indexes = new int[facesCount];
            for (int i = 0; i < facesCount; i++)
            {
                indexes[i] = reader.readVal(indexValueSize);
            }

            //Read Weigth Bones dictionary
            reader.BaseStream.Position = weightBoneNameTableStart;
            int weightBoneCount = reader.ReadInt32();

            int[] boneIdDict = new int[weightBoneCount];
            for (int i = 0; i < weightBoneCount; i++)
            {
                reader.BaseStream.Position  = weightBoneNameTableStart + i * 4 + 4;
                reader.BaseStream.Position += reader.ReadInt32();
                string weightBoneName = reader.readString();
                boneIdDict[i] = Array.FindIndex(bones, (b) => b.Name == weightBoneName);
            }

            var vv = vertices.ToArray();

            //set bone id to global
            for (int i = 0; i < vv.Length; i++)
            {
                IVector4 boneIndexes = vv[i].BoneIndices;
                boneIndexes.bone1 = boneIdDict[boneIndexes.bone1];
                boneIndexes.bone2 = boneIdDict[boneIndexes.bone2];
                boneIndexes.bone3 = boneIdDict[boneIndexes.bone3];
                boneIndexes.bone4 = boneIdDict[boneIndexes.bone4];
                vv[i].BoneIndices = boneIndexes;
            }

            CalculateVertNormals.CalculateNormals(vv, indexes);
            var      mesh = new Mesh(vv, indexes);
            Material mat;

            if (materialTable.TryGetValue(matName, out mat))
            {
                return(new MeshDrawerRigged(mesh, new Material[] { mat }, boneControll));
            }
            else
            {
                return(new MeshDrawerRigged(mesh, boneControll));
            }
        }
示例#5
0
        /// <summary>
        /// Exports a model to the given directory.
        /// </summary>
        private void ExportModel(Model model, StringBuilder stringBuilder, ref byte[] textureBuffer)
        {
            //TODO: Figure out what to do with non-version 4 models.
            if (model == null || model.Version != 4)
            {
                return;
            }

            string directory = ResourceDir + "/Models";
            string path      = directory + @"\" + Path.GetFileNameWithoutExtension(model.Name) + ".obj";

            if (File.Exists(path))
            {
                return;
            }

            // Validate meshes attached to the model
            foreach (Mesh mesh in model.Meshes)
            {
                if (!ForgelightGame.MaterialDefinitionManager.MaterialDefinitions.ContainsKey(model.Materials[(int)mesh.MaterialIndex].MaterialDefinitionHash))
                {
                    return;
                }
            }

            // The texture directory may not exist yet.
            Directory.CreateDirectory(directory + @"\Textures");

            // We reset the string builder so we don't have any previous buffer left over.
            stringBuilder.Length = 0;

            // Materials and Textures
            foreach (Mesh mesh in model.Meshes)
            {
                if (mesh.BaseDiffuse != null)
                {
                    ExportTexture(mesh.BaseDiffuse, directory, ref textureBuffer);
                    ExportMaterial(mesh, directory);
                    stringBuilder.AppendLine("mtllib " + Path.GetFileNameWithoutExtension(mesh.BaseDiffuse) + ".mtl");
                }

                if (mesh.SpecMap != null)
                {
                    ExportTexture(mesh.SpecMap, directory, ref textureBuffer);
                }

                if (mesh.BumpMap != null)
                {
                    ExportTexture(mesh.BumpMap, directory, ref textureBuffer);
                }
            }

            // Meshes
            foreach (Mesh mesh in model.Meshes)
            {
                MaterialDefinition materialDefinition = ForgelightGame.MaterialDefinitionManager.MaterialDefinitions[model.Materials[(int)mesh.MaterialIndex].MaterialDefinitionHash];
                VertexLayout       vertexLayout       = ForgelightGame.MaterialDefinitionManager.VertexLayouts[materialDefinition.DrawStyles[0].VertexLayoutNameHash];

                //position
                VertexLayout.Entry.DataTypes positionDataType;
                int positionOffset;
                int positionStreamIndex;

                vertexLayout.GetEntryInfoFromDataUsageAndUsageIndex(VertexLayout.Entry.DataUsages.Position, 0, out positionDataType, out positionStreamIndex, out positionOffset);

                Mesh.VertexStream positionStream = mesh.VertexStreams[positionStreamIndex];

                for (int j = 0; j < mesh.VertexCount; ++j)
                {
                    Vector3 position = ReadVector3(positionOffset, positionStream, j);

                    stringBuilder.AppendLine("v " + position.x.ToString(format) + " " +
                                             position.y.ToString(format) + " " + position.z.ToString(format));
                }

                //texture coordinates
                VertexLayout.Entry.DataTypes texCoord0DataType;
                int texCoord0Offset;
                int texCoord0StreamIndex;

                bool texCoord0Present = vertexLayout.GetEntryInfoFromDataUsageAndUsageIndex(
                    VertexLayout.Entry.DataUsages.Texcoord, 0, out texCoord0DataType, out texCoord0StreamIndex,
                    out texCoord0Offset);

                if (texCoord0Present)
                {
                    Mesh.VertexStream texCoord0Stream = mesh.VertexStreams[texCoord0StreamIndex];

                    for (int j = 0; j < mesh.VertexCount; ++j)
                    {
                        Vector2 texCoord;

                        switch (texCoord0DataType)
                        {
                        case VertexLayout.Entry.DataTypes.Float2:
                        {
                            texCoord.x = BitConverter.ToSingle(texCoord0Stream.Data,
                                                               (j * texCoord0Stream.BytesPerVertex) + 0);
                            texCoord.y = 1.0f - BitConverter.ToSingle(texCoord0Stream.Data,
                                                                      (j * texCoord0Stream.BytesPerVertex) + 4);
                            break;
                        }

                        case VertexLayout.Entry.DataTypes.float16_2:
                        {
                            texCoord.x = Half.FromBytes(texCoord0Stream.Data,
                                                        (j * texCoord0Stream.BytesPerVertex) + texCoord0Offset + 0);
                            texCoord.y = 1.0f - Half.FromBytes(texCoord0Stream.Data,
                                                               (j * texCoord0Stream.BytesPerVertex) + texCoord0Offset + 2);
                            break;
                        }

                        default:
                            texCoord.x = 0;
                            texCoord.y = 0;
                            break;
                        }

                        stringBuilder.AppendLine("vt " + texCoord.x.ToString(format) + " " +
                                                 texCoord.y.ToString(format));
                    }
                }
            }

            // Faces
            uint vertexCount = 0;

            for (int i = 0; i < model.Meshes.Count; ++i)
            {
                Mesh mesh = model.Meshes[i];
                stringBuilder.AppendLine("g Mesh" + i);

                // Specify Material
                if (mesh.BaseDiffuse != null)
                {
                    stringBuilder.AppendLine("usemtl " + Path.GetFileNameWithoutExtension(mesh.BaseDiffuse));
                }

                for (int j = 0; j < mesh.IndexCount; j += 3)
                {
                    uint index0, index1, index2;

                    switch (mesh.IndexSize)
                    {
                    case 2:
                        index0 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 0) + 1;
                        index1 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 2) + 1;
                        index2 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 4) + 1;
                        break;

                    case 4:
                        index0 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 0) + 1;
                        index1 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 4) + 1;
                        index2 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 8) + 1;
                        break;

                    default:
                        index0 = 0;
                        index1 = 0;
                        index2 = 0;
                        break;
                    }

                    stringBuilder.AppendLine("f " + index2 + "/" + index2 + "/" + index2 + " " + index1 + "/" +
                                             index1 + "/" + index1 + " " + index0 + "/" + index0 + "/" +
                                             index0);
                }

                vertexCount += mesh.VertexCount;

                File.WriteAllText(path, stringBuilder.ToString());
            }
        }
示例#6
0
        public static void ExportModel(ForgelightGame forgelightGame, Model model, string directory)
        {
            //TODO: Figure out what to do with non-version 4 models.
            if (model == null || model.Version != 4)
            {
                return;
            }

            //Validate this mesh.
            for (int i = 0; i < model.Meshes.Count; ++i)
            {
                Mesh mesh = model.Meshes[i];

                if (!forgelightGame.MaterialDefinitionManager.MaterialDefinitions.ContainsKey(model.Materials[(int)mesh.MaterialIndex].MaterialDefinitionHash))
                {
                    return;
                }
            }

            NumberFormatInfo format = new NumberFormatInfo();

            format.NumberDecimalSeparator = ".";

            Directory.CreateDirectory(directory + @"\Textures");

            List <string> usedTextures = new List <string>();

            foreach (Mesh mesh in model.Meshes)
            {
                if (mesh.BaseDiffuse != null)
                {
                    usedTextures.Add(mesh.BaseDiffuse);
                }

                if (mesh.SpecMap != null)
                {
                    usedTextures.Add(mesh.SpecMap);
                }

                if (mesh.BumpMap != null)
                {
                    usedTextures.Add(mesh.BumpMap);
                }
            }

            foreach (string textureString in usedTextures)
            {
                using (MemoryStream textureMemoryStream = forgelightGame.CreateAssetMemoryStreamByName(textureString))
                {
                    if (textureMemoryStream == null)
                    {
                        continue;
                    }

                    if (!File.Exists(directory + @"\Textures\" + textureString))
                    {
                        try
                        {
                            using (FileStream file = File.Create(directory + @"\Textures\" + textureString))
                            {
                                byte[] bytes = new byte[textureMemoryStream.Length];
                                textureMemoryStream.Read(bytes, 0, (int)textureMemoryStream.Length);
                                file.Write(bytes, 0, bytes.Length);
                            }
                        }
                        catch (IOException) {}
                    }
                }
            }

            string path = directory + @"\" + Path.GetFileNameWithoutExtension(model.Name) + ".obj";

            if (!File.Exists(path))
            {
                using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write))
                {
                    using (StreamWriter streamWriter = new StreamWriter(fileStream))
                    {
                        //Custom Material
                        foreach (Mesh mesh in model.Meshes)
                        {
                            if (mesh.BaseDiffuse != null)
                            {
                                if (!File.Exists(directory + @"\" + Path.GetFileNameWithoutExtension(mesh.BaseDiffuse) + @".mtl"))
                                {
                                    List <string> mtl = new List <string>();

                                    string[] baseMtl =
                                    {
                                        "newmtl " + Path.GetFileNameWithoutExtension(mesh.BaseDiffuse),
                                        "Ka 1.000000 1.000000 1.000000",
                                        "Kd 1.000000 1.000000 1.000000",
                                        "Ks 0.000000 0.000000 0.000000",
                                        "d 1.0",
                                        "illum 2",
                                        "map_Ka " + mesh.BaseDiffuse,
                                        "map_Kd " + mesh.BaseDiffuse,
                                        "map_d " + mesh.BaseDiffuse
                                    };

                                    mtl.AddRange(baseMtl);

                                    if (mesh.SpecMap != null)
                                    {
                                        mtl.Add("map_Ks " + mesh.BaseDiffuse);
                                        mtl.Add("map_Ns " + mesh.SpecMap);
                                    }

                                    if (mesh.BumpMap != null)
                                    {
                                        mtl.Add("bump " + mesh.BumpMap);
                                    }

                                    try
                                    {
                                        File.WriteAllLines(directory + @"\" + Path.GetFileNameWithoutExtension(mesh.BaseDiffuse) + @".mtl", mtl.ToArray());
                                    }

                                    //Another thread is already writing this material. No need to take any further action.
                                    catch (IOException) {}
                                }

                                streamWriter.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(mesh.BaseDiffuse) + ".mtl");
                            }
                        }

                        foreach (Mesh mesh in model.Meshes)
                        {
                            MaterialDefinition materialDefinition = forgelightGame.MaterialDefinitionManager.MaterialDefinitions[model.Materials[(int)mesh.MaterialIndex].MaterialDefinitionHash];
                            VertexLayout       vertexLayout       = forgelightGame.MaterialDefinitionManager.VertexLayouts[materialDefinition.DrawStyles[0].VertexLayoutNameHash];

                            //position
                            VertexLayout.Entry.DataTypes positionDataType;
                            int positionOffset;
                            int positionStreamIndex;

                            vertexLayout.GetEntryInfoFromDataUsageAndUsageIndex(VertexLayout.Entry.DataUsages.Position, 0, out positionDataType, out positionStreamIndex, out positionOffset);

                            Mesh.VertexStream positionStream = mesh.VertexStreams[positionStreamIndex];

                            for (int j = 0; j < mesh.VertexCount; ++j)
                            {
                                Vector3 position = ReadVector3(positionOffset, positionStream, j);

                                streamWriter.WriteLine("v " + position.x.ToString(format) + " " + position.y.ToString(format) + " " + position.z.ToString(format));
                            }

                            //texture coordinates
                            VertexLayout.Entry.DataTypes texCoord0DataType;
                            int texCoord0Offset;
                            int texCoord0StreamIndex;

                            bool texCoord0Present = vertexLayout.GetEntryInfoFromDataUsageAndUsageIndex(VertexLayout.Entry.DataUsages.Texcoord, 0, out texCoord0DataType, out texCoord0StreamIndex, out texCoord0Offset);

                            if (texCoord0Present)
                            {
                                Mesh.VertexStream texCoord0Stream = mesh.VertexStreams[texCoord0StreamIndex];

                                for (int j = 0; j < mesh.VertexCount; ++j)
                                {
                                    Vector2 texCoord;

                                    switch (texCoord0DataType)
                                    {
                                    case VertexLayout.Entry.DataTypes.Float2:
                                    {
                                        texCoord.x = BitConverter.ToSingle(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + 0);
                                        texCoord.y = 1.0f - BitConverter.ToSingle(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + 4);
                                        break;
                                    }

                                    case VertexLayout.Entry.DataTypes.float16_2:
                                    {
                                        texCoord.x = Half.FromBytes(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + texCoord0Offset + 0);
                                        texCoord.y = 1.0f - Half.FromBytes(texCoord0Stream.Data, (j * texCoord0Stream.BytesPerVertex) + texCoord0Offset + 2);
                                        break;
                                    }

                                    default:
                                        texCoord.x = 0;
                                        texCoord.y = 0;
                                        break;
                                    }

                                    streamWriter.WriteLine("vt " + texCoord.x.ToString(format) + " " + texCoord.y.ToString(format));
                                }
                            }
                        }

                        //faces
                        uint vertexCount = 0;

                        for (int i = 0; i < model.Meshes.Count; ++i)
                        {
                            Mesh mesh = model.Meshes[i];
                            streamWriter.WriteLine("g Mesh" + i);

                            //Custom Material
                            if (mesh.BaseDiffuse != null)
                            {
                                streamWriter.WriteLine("usemtl " + Path.GetFileNameWithoutExtension(mesh.BaseDiffuse));
                            }

                            for (int j = 0; j < mesh.IndexCount; j += 3)
                            {
                                uint index0, index1, index2;

                                switch (mesh.IndexSize)
                                {
                                case 2:
                                    index0 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 0) + 1;
                                    index1 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 2) + 1;
                                    index2 = vertexCount + BitConverter.ToUInt16(mesh.IndexData, (j * 2) + 4) + 1;
                                    break;

                                case 4:
                                    index0 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 0) + 1;
                                    index1 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 4) + 1;
                                    index2 = vertexCount + BitConverter.ToUInt32(mesh.IndexData, (j * 4) + 8) + 1;
                                    break;

                                default:
                                    index0 = 0;
                                    index1 = 0;
                                    index2 = 0;
                                    break;
                                }

                                streamWriter.WriteLine("f " + index2 + "/" + index2 + "/" + index2 + " " + index1 + "/" + index1 + "/" + index1 + " " + index0 + "/" + index0 + "/" + index0);
                            }

                            vertexCount += mesh.VertexCount;
                        }
                    }
                }
            }
        }