public override bool Write(string file, ProcessedGeometryData data)
        {
            int tr = 0;
            int svt = 0;

            StreamWriter sw = new StreamWriter(file, false, Encoding.ASCII);

            StreamWriter mtlSw = null;
            if (data.ExportConfig.ExportMaterials)
            {
                string mtlFile = Path.Combine(Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file)) + ".mtl";
                mtlSw = new StreamWriter(mtlFile, false, Encoding.ASCII);
            }

            foreach (DataSet set in data.Data)
            {
                sw.WriteLine("o " + set.Texture); //Material name
                sw.WriteLine("usemtl " + set.Texture + "_mat");

                sw.WriteLine(" ");
                sw.WriteLine("#Vertices");
                for (int v = 0; v < set.verts.Count; v++)
                {
                    sw.WriteLine("v " + set.verts[v].RawToString());
                    tr++;
                }

                if (data.ExportConfig.ExportUVs)
                {
                    sw.WriteLine(" ");
                    sw.WriteLine("#UVs");
                    for (int t = 0; t < set.uvs.Count; t++)
                    {
                        sw.WriteLine("vt " + set.uvs[t].RawToString());
                    }
                }

                if (data.ExportConfig.ExportNormals)
                {
                    sw.WriteLine(" ");
                    sw.WriteLine("#Normals");
                    for (int n = 0; n < set.normals.Count; n++)
                    {
                        sw.WriteLine("vn " + set.normals[n].RawToString());
                    }
                }

                sw.WriteLine(" ");
                sw.WriteLine("#Faces");

                sw.WriteLine("g " + set.Texture); //Material name
                sw.WriteLine("s 1");
                for (int f = svt; f < tr; f += 3)
                {
                    int ind = f + 1;
                    int ind2 = f + 2;
                    int ind3 = f + 3;

                    Vector3 normal = set.normals[f - svt];
                    if (normal.X > 0 || normal.Y > 0 || normal.Z < 0)
                    {
                        ind = f + 1;
                        ind2 = f + 3;
                        ind3 = f + 2;
                    }

                    sw.WriteLine("f " + ind.ToString() + "/" + ind.ToString() + "/" + ind.ToString() + " " +
                        ind2.ToString() + "/" + ind2.ToString() + "/" + ind2.ToString() + " " +
                        ind3.ToString() + "/" + ind3.ToString() + "/" + ind3.ToString());
                }

                sw.WriteLine(" ");

                svt = tr;

                if (data.ExportConfig.ExportMaterials)
                {
                    WriteMTLEntry(mtlSw, set.Texture + "_mat", set.Texture + ".png");
                }
            }

            sw.Close();
            if (mtlSw != null)
                mtlSw.Close();

            return true;
        }
 public virtual bool Write(string file, ProcessedGeometryData data)
 {
     return false;
 }
        public bool Export(object arg, TaskProgressReport p)
        {
            int maxTotalTask = 8; //6 normals + geom build + file write
            int currentTotalTask = 0;

            PartTaskProgressReport rep = (PartTaskProgressReport)p;

            rep.SetTitle("Preparing input data");
            rep.Report();

            Dictionary<Vector4, BlockRange[]> ranges = new Dictionary<Vector4, BlockRange[]>();
            List<CustomBlockData> customData = new List<CustomBlockData>();

            Vector3[] normalsToFollow = new Vector3[] { new Vector3(1, 0, 0), new Vector3(-1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, -1, 0), new Vector3(0, 0, 1), new Vector3(0, 0, -1) };

            bool[, ,] customBuilt = new bool[_in.BlockIDs.GetLength(0), _in.BlockIDs.GetLength(1), _in.BlockIDs.GetLength(2)];
            for (int n = 0; n < normalsToFollow.Length; n++)
            {
                Vector3 normal = normalsToFollow[n];
                BlockSide normalSide = Block.GetSideFromNormal(normal);

                Vector2 xy = GetXYByNormal(normal);
                Point3 xyLength = GetLengthAxisByNormal(normal); //X:x, Y:y, Z:level

                int xs = _in.BlockIDs.GetLength(xyLength.X);
                int ys = _in.BlockIDs.GetLength(xyLength.Y);
                int zs = _in.BlockIDs.GetLength(xyLength.Z);

                rep.SetTitle("Processing side: " + TranslateNormal(normal));
                rep.Report();

                for (int lvl = 0; lvl < _in.BlockIDs.GetLength(xyLength.Z); lvl++)
                {
                    BlockData[,] lvlData = new BlockData[xs, ys];

                    int partMax = xs * ys;
                    int partProg = 0;

                    for (int a = 0; a < _in.BlockIDs.GetLength(xyLength.X); a++)
                    {
                        for (int b = 0; b < _in.BlockIDs.GetLength(xyLength.Y); b++)
                        {
                            Point3 pos = ConvertLevelAndXYToPos(a, b, lvl, xyLength);

                            if (_in.BlockIDs[pos.X, pos.Y, pos.Z] != 0)
                            {
                                BlockData bd = GetBlockDataAt(pos);
                                uint block = bd.GetGlobalID();
                                Block bl = Block.Blocks[block];
                                if (bl != null && !customBuilt[pos.X, pos.Y, pos.Z])
                                {
                                    bool canExportFace = (!_cfg.DontExportOuterFaces && !_cfg.InteriorOnly);

                                    if (_cfg.DontExportOuterFaces && CheckFace(pos, normal))
                                        canExportFace = true;
                                    else if (_cfg.InteriorOnly && CheckFaceInterior(pos, normal))
                                        canExportFace = true;

                                    if (canExportFace)
                                    {
                                        if (bl.IsFullyCustomModel())
                                        {
                                            List<CustomBlockData> dat = bl.GenerateModel(_in.BlockMetadatas[pos.X, pos.Y, pos.Z], bd, GetBlockDataAt(new Point3(pos.X + 1, pos.Y, pos.Z)), GetBlockDataAt(new Point3(pos.X - 1, pos.Y, pos.Z)), GetBlockDataAt(new Point3(pos.X, pos.Y + 1, pos.Z)), GetBlockDataAt(new Point3(pos.X, pos.Y - 1, pos.Z)), GetBlockDataAt(new Point3(pos.X, pos.Y, pos.Z + 1)), GetBlockDataAt(new Point3(pos.X, pos.Y, pos.Z - 1)), this, pos);
                                            for (int x = 0; x < dat.Count; x++)
                                            {
                                                dat[x].Vertex1 += pos.ToVector3();
                                                dat[x].Vertex2 += pos.ToVector3();
                                                dat[x].Vertex3 += pos.ToVector3();
                                                if (!dat[x].IsOneTriangle)
                                                    dat[x].Vertex4 += pos.ToVector3();
                                                dat[x].Source = bd;
                                            }
                                            customData.AddRange(dat);
                                            customBuilt[pos.X, pos.Y, pos.Z] = true;
                                        }
                                        else
                                        {
                                            if (bl.IsFullSide(normalSide))
                                            {
                                                if (CanGenerateSide(pos, normal))
                                                {
                                                    lvlData[a, b] = bd;
                                                }
                                            }
                                            else
                                            {
                                                List<CustomBlockData> dat = bl.GenerateSide(normalSide, _in.BlockMetadatas[pos.X, pos.Y, pos.Z], bd, GetBlockDataAt(new Point3(pos.X + 1, pos.Y, pos.Z)), GetBlockDataAt(new Point3(pos.X - 1, pos.Y, pos.Z)), GetBlockDataAt(new Point3(pos.X, pos.Y + 1, pos.Z)), GetBlockDataAt(new Point3(pos.X, pos.Y - 1, pos.Z)), GetBlockDataAt(new Point3(pos.X, pos.Y, pos.Z + 1)), GetBlockDataAt(new Point3(pos.X, pos.Y, pos.Z - 1)));
                                                for (int x = 0; x < dat.Count; x++)
                                                {
                                                    dat[x].Vertex1 += pos.ToVector3();
                                                    dat[x].Vertex2 += pos.ToVector3();
                                                    dat[x].Vertex3 += pos.ToVector3();
                                                    if (!dat[x].IsOneTriangle)
                                                        dat[x].Vertex4 += pos.ToVector3();
                                                    dat[x].Source = bd;
                                                }
                                                customData.AddRange(dat);
                                            }
                                        }
                                    }
                                }
                            }

                            partProg++;
                            rep.SetPartPercent((int)(((float)partProg / (float)partMax) * 100f));
                            rep.Report();
                        }
                    }

                    rep.SetTitle("Square-angulating level: " + lvl.ToString());
                    rep.Report();

                    if (_cfg.OptimizeModel)
                    {
                        List<BlockRange> sq = Squareangulate(lvlData);
                        if (sq.Count > 0)
                        {
                            ranges.Add(new Vector4(normal.X, normal.Y, normal.Z, lvl), sq.ToArray());
                        }
                    }
                    else
                    {
                        List<BlockRange> converted = new List<BlockRange>();
                        for (int x = 0; x < lvlData.GetLength(0); x++)
                        {
                            for (int y = 0; y < lvlData.GetLength(1); y++)
                            {
                                BlockData bd = lvlData[x, y];

                                if (bd == null)
                                    continue;

                                BlockRange range = new BlockRange();
                                range.Block = bd;
                                range.From = new PointF(x, y);
                                range.To = new PointF(x, y);

                                converted.Add(range);
                            }
                        }

                        ranges.Add(new Vector4(normal.X, normal.Y, normal.Z, lvl), converted.ToArray());
                    }
                }

                currentTotalTask++;
                rep.SetTotalPercent((int)(((float)currentTotalTask / (float)maxTotalTask) * 100f));
                rep.Report();
            }

            rep.SetTitle("Building geometry");
            rep.Report();

            Dictionary<string, DataSet> datas = new Dictionary<string, DataSet>();

            int pairIndex = 0;
            foreach (KeyValuePair<Vector4, BlockRange[]> pair in ranges)
            {
                Vector3 normal = new Vector3(pair.Key.X, pair.Key.Y, pair.Key.Z);
                int level = (int)pair.Key.Level;

                Vector3 addNormal = new Vector3(normal.X, normal.Y, normal.Z);
                if (addNormal.X < 0)
                    addNormal.X = 0;
                if (addNormal.Y < 0)
                    addNormal.Y = 0;
                if (addNormal.Z < 0)
                    addNormal.Z = 0;

                for (int x = 0; x < pair.Value.Length; x++)
                {
                    WriteRange(pair.Value[x], normal, level, addNormal, ref datas);
                }

                pairIndex++;
                rep.SetTotalPercent((int)(((float)pairIndex / (float)ranges.Count) * 100f));
                rep.Report();
            }

            for (int x = 0; x < customData.Count; x++)
            {
                WriteCustomData(customData[x], ref datas);
            }

            rep.SetTotalPercent(100);
            rep.Report();

            if (_cfg.CenterObject)
            {
                rep.SetTitle("Centering geometry");
                rep.Report();

                Vector3 min = new Vector3(0, 0, 0);
                Vector3 max = new Vector3(0, 0, 0);
                foreach (KeyValuePair<string, DataSet> pair2 in datas)
                {
                    foreach (Vector3 v in pair2.Value.verts)
                    {
                        min.X = Math.Min(min.X, v.X);
                        min.Y = Math.Min(min.Y, v.Y);
                        min.Z = Math.Min(min.Z, v.Z);

                        max.X = Math.Max(max.X, v.X);
                        max.Y = Math.Max(max.Y, v.Y);
                        max.Z = Math.Max(max.Z, v.Z);
                    }
                }

                Vector3 move = (max - min) / 2;
                foreach (KeyValuePair<string, DataSet> pair2 in datas)
                {
                    for (int x = 0; x < pair2.Value.verts.Count; x++)
                    {
                        pair2.Value.verts[x] -= move;
                    }
                }
            }

            currentTotalTask++;
            rep.SetTotalPercent((int)(((float)currentTotalTask / (float)maxTotalTask) * 100f));

            rep.SetTitle("Creating vertex data");
            rep.Report();

            ProcessedGeometryData geom = new ProcessedGeometryData();
            geom.ExportConfig = _cfg;
            geom.Data = datas.Values.ToList();

            //Export model file
            _outputWriter.Write(_outputFile, geom);

            //Export textures
            if (_cfg.ExportTextures)
            {
                rep.SetTitle("Exporting textures");
                rep.Report();

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

                string textureOutput = Path.Combine(Path.GetDirectoryName(_outputFile), _cfg.TextureOutputFolder);

                ResourcePack rs = new ResourcePack(_cfg.ResourcePack);
                rs.Open();

                foreach (KeyValuePair<string, DataSet> pair2 in datas)
                {
                    string tex = pair2.Value.Texture;

                    if (!rs.SaveBlockTexture(tex, textureOutput))
                        failedTextures.Add(tex);
                }

                rs.Close();
            }

            currentTotalTask++;
            rep.SetTotalPercent((int)(((float)currentTotalTask / (float)maxTotalTask) * 100f));

            return true;
        }