using System; using System.Collections.Generic; using System.Drawing; using global::System.Globalization; using global::System.IO; using System.Linq; using global::System.Threading; using Microsoft.VisualBasic.CompilerServices; using global::Pilz.S3DFileParser.Exceptions; namespace Pilz.S3DFileParser.ObjModule { public class ObjFile { public static Object3D FromFile(string FileName, bool LoadMaterials, UpAxis UpAxis) { var curThread = Thread.CurrentThread; var curCultInfo = curThread.CurrentCulture; var newCultInfo = new CultureInfo(curCultInfo.Name); newCultInfo.NumberFormat.NumberDecimalSeparator = "."; newCultInfo.NumberFormat.PercentDecimalSeparator = "."; newCultInfo.NumberFormat.CurrencyDecimalSeparator = "."; newCultInfo.NumberFormat.NumberGroupSeparator = ","; newCultInfo.NumberFormat.PercentGroupSeparator = ","; newCultInfo.NumberFormat.CurrencyGroupSeparator = ","; curThread.CurrentCulture = newCultInfo; var newObj = new Object3D(); var newMesh = new Mesh(); string curObjPath = Path.GetDirectoryName(FileName); var mtllibs = new Dictionary(); MaterialLib curMaterialLib = null; Material curMaterial = null; var srObj = new StreamReader(FileName, System.Text.Encoding.ASCII); string line = ""; while (!srObj.EndOfStream) { line = srObj.ReadLine().Trim(); if (!string.IsNullOrEmpty(line)) { switch (true) { case object _ when line.StartsWith("mtllib "): { string name = line.Substring(7); if (!mtllibs.ContainsKey(name)) { string mtlfile = Path.Combine(curObjPath, name); if (!File.Exists(mtlfile)) throw new MaterialException("Material Library not found!"); var newmtl = new MaterialLib(); newmtl.FromFile(mtlfile, LoadMaterials); mtllibs.Add(name, newmtl); curMaterialLib = newmtl; foreach (KeyValuePair kvp in curMaterialLib.Materials) { if (!newObj.Materials.ContainsKey(kvp.Key)) newObj.Materials.Add(kvp.Key, kvp.Value); } } else { curMaterialLib = mtllibs[name]; } break; } case object _ when line.StartsWith("usemtl "): { curMaterial = curMaterialLib.Materials[line.Substring(7)]; break; } case object _ when line.StartsWith("v "): { if (line.Contains("nan")) line = line.Replace("nan", "0"); var splitXYZ = line.Substring(2).Split(' '); double tX = Convert.ToDouble(splitXYZ[0]); double tY = Convert.ToDouble(splitXYZ[1]); double tZ = Convert.ToDouble(splitXYZ[2]); var v = new Vertex(); switch (UpAxis) { case UpAxis.Y: { v.X = tX; v.Y = tY; v.Z = tZ; break; } case UpAxis.Z: { v.X = tY; v.Y = tZ; v.Z = tX; break; } } newMesh.Vertices.Add(v); break; } case object _ when line.StartsWith("vt "): { var uvstr = line.Substring(3).Split(' '); var uv = new UV() { U = Convert.ToSingle(uvstr[0]), V = Convert.ToSingle(uvstr[1]) }; newMesh.UVs.Add(uv); break; } case object _ when line.StartsWith("vn "): { var splitXYZ = line.Substring(3).Split(' '); float tX = Convert.ToSingle(splitXYZ[0]); float tY = Convert.ToSingle(splitXYZ[1]); float tZ = Convert.ToSingle(splitXYZ[2]); var n = new Normal(); switch (UpAxis) { case UpAxis.Y: { n.X = tX; n.Y = tY; n.Z = tZ; break; } case UpAxis.Z: { n.X = tZ; n.Y = tY; n.Z = tX; break; } } newMesh.Normals.Add(n); break; } case object _ when line.StartsWith("vc "): { var splitRGB = line.Substring(3).Split(' '); float tX = Convert.ToSingle(splitRGB[0]); float tY = Convert.ToSingle(splitRGB[1]); float tZ = Convert.ToSingle(splitRGB[2]); var vc = new VertexColor(); switch (UpAxis) { case UpAxis.Y: { vc.R = tX; vc.G = tY; vc.B = tZ; break; } case UpAxis.Z: { vc.R = tY; vc.G = tZ; vc.B = tX; break; } } newMesh.VertexColors.Add(vc); break; } case object _ when line.StartsWith("f "): { var tri = new Face() { Material = curMaterial }; foreach (string xyz1 in line.Substring(2).Split(' ')) { var xyz = xyz1.Trim(); if (string.IsNullOrEmpty(xyz)) continue; string[] splitsub = null; var p = new Point(); switch (true) { case object _ when xyz.Contains("/"): { splitsub = xyz.Split('/'); break; } case object _ when xyz.Contains(@"\"): { splitsub = xyz.Split('\\'); break; } default: { splitsub = new string[] { 0.ToString(), 0.ToString(), 0.ToString() }; break; } } string v1 = splitsub[0]; string v2 = splitsub[1]; string v3 = splitsub[2]; if (!string.IsNullOrEmpty(v1)) { p.Vertex = newMesh.Vertices[Convert.ToInt32(v1) - 1]; } if (!string.IsNullOrEmpty(v2)) { p.UV = newMesh.UVs[Convert.ToInt32(v2) - 1]; } else { var newUV = new UV() { U = 0f, V = 0f }; p.UV = newUV; newMesh.UVs.Add(newUV); } if (!string.IsNullOrEmpty(v3)) { p.Normal = newMesh.Normals[Convert.ToInt32(v3) - 1]; } if (splitsub.Count() > 3) { string v4 = splitsub[3]; if (!string.IsNullOrEmpty(v4)) p.VertexColor = newMesh.VertexColors[Convert.ToInt32(v4) - 1]; } tri.Points.Add(p); } newMesh.Faces.Add(tri); break; } } } } newObj.Meshes.Add(newMesh); curThread.CurrentCulture = curCultInfo; srObj.Close(); return newObj; } public static void ToFile(string FileName, Object3D obj) { var fs = new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite); var sw = new StreamWriter(fs, System.Text.Encoding.ASCII); if (obj.Materials.Count > 0) { string mtlName = Path.GetFileNameWithoutExtension(FileName) + ".mtl"; string mtlFile = Path.Combine(Path.GetDirectoryName(FileName), mtlName); sw.WriteLine($"mtllib {mtlName}"); MaterialLib.ToFile(mtlFile, obj); } int curVertCount = 1; int curUVCount = 1; int curNormCount = 1; int curVertColCount = 1; foreach (Mesh m in obj.Meshes) { foreach (Vertex vert in m.Vertices) sw.WriteLine($"v {vert.X.ToString().Replace(",", ".")} {vert.Y.ToString().Replace(",", ".")} {vert.Z.ToString().Replace(",", ".")}"); foreach (UV uv in m.UVs) sw.WriteLine($"vt {uv.U.ToString().Replace(",", ".")} {uv.V.ToString().Replace(",", ".")}"); foreach (Normal norm in m.Normals) sw.WriteLine($"vn {norm.X.ToString().Replace(",", ".")} {norm.Y.ToString().Replace(",", ".")} {norm.Z.ToString().Replace(",", ".")}"); foreach (VertexColor vertcol in m.VertexColors) sw.WriteLine($"vc {vertcol.R.ToString().Replace(",", ".")} {vertcol.G.ToString().Replace(",", ".")} {vertcol.B.ToString().Replace(",", ".")}"); Material curMtl = null; foreach (Face f in m.Faces) { if (!ReferenceEquals(curMtl, f.Material)) { curMtl = f.Material; sw.WriteLine($"usemtl _{GetIndexOfMaterialInList(obj, curMtl)}"); } sw.Write("f"); foreach (Point p in f.Points) { sw.Write(" "); sw.Write(curVertCount + m.Vertices.IndexOf(p.Vertex)); sw.Write("/"); if (p.UV is object) sw.Write(curUVCount + m.UVs.IndexOf(p.UV)); sw.Write("/"); if (p.Normal is object) sw.Write(curNormCount + m.Normals.IndexOf(p.Normal)); if (m.VertexColors.Count > 0) { sw.Write("/"); if (p.VertexColor is object) sw.Write(curVertColCount + m.VertexColors.IndexOf(p.VertexColor)); } } sw.WriteLine(); } curVertCount += m.Vertices.Count; curUVCount += m.UVs.Count; curNormCount += m.Normals.Count; curVertColCount += m.VertexColors.Count; } sw.Flush(); fs.Close(); } public static int GetIndexOfMaterialInList(Object3D obj, Material matToFind) { for (int Index = 0, loopTo = obj.Materials.Count - 1; Index <= loopTo; Index++) { if (obj.Materials.ElementAt(Index).Value.Equals(matToFind)) { return Index; } } return -1; } } public class MaterialLib { public Dictionary Materials { get; private set; } = new Dictionary(); private readonly Dictionary LoadedImages = new Dictionary(); public void FromFile(string fileName, bool LoadMaterials) { LoadedImages.Clear(); string curMatLibPath = Path.GetDirectoryName(fileName); Material curMat = null; string curName = ""; var srMtl = new StreamReader(fileName, System.Text.Encoding.ASCII); string line = ""; while (!srMtl.EndOfStream) { line = srMtl.ReadLine(); switch (true) { case object _ when line.StartsWith("newmtl "): { curMat = new Material(); curName = line.Substring(7); Materials.Add(curName, curMat); break; } case object _ when line.ToLower().StartsWith("kd "): { var splitColor = line.Substring(3).Split(' '); var col = Color.FromArgb((int)Convert.ToSingle(Math.Round(255d * Conversions.ToDouble(splitColor[0]))), (int)Convert.ToSingle(Math.Round(255d * Conversions.ToDouble(splitColor[1]))), (int)Convert.ToSingle(Math.Round(255d * Conversions.ToDouble(splitColor[2])))); curMat.Color = col; break; } case object _ when line.ToLower().StartsWith("d "): { curMat.Opacity = Convert.ToSingle(line.Substring(2)); break; } case object _ when line.ToLower().StartsWith("tr "): { curMat.Opacity = 1f - Convert.ToSingle(line.Substring(2)); break; } case object _ when line.ToLower().StartsWith("map_kd "): { if (LoadMaterials) { string mtlpath = line.Substring(7); string combipath = Path.Combine(curMatLibPath, line.Substring(7)); string imgfile; if (File.Exists(combipath)) { imgfile = combipath; } else if (File.Exists(line.Substring(7))) { imgfile = mtlpath; } else { imgfile = ""; } if (!string.IsNullOrEmpty(imgfile)) { if (LoadedImages.ContainsKey(imgfile)) { curMat.Image = LoadedImages[imgfile]; } else { var fs = new FileStream(imgfile, FileMode.Open, FileAccess.Read); curMat.Image = Image.FromStream(fs); fs.Close(); bool imgExists = false; foreach (var kvp in LoadedImages) { if (!imgExists && ((Bitmap)kvp.Value).IsTheSameAs((Bitmap)curMat.Image)) { curMat.Image = kvp.Value; imgExists = true; } } if (!imgExists) { LoadedImages.Add(imgfile, curMat.Image); } } } } break; } } } srMtl.Close(); } public static void ToFile(string fileName, Object3D obj) { var fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite); var sw = new StreamWriter(fs, System.Text.Encoding.ASCII); string imgDirName = Path.GetFileNameWithoutExtension(fileName); string imgDirFull = Path.Combine(Path.GetDirectoryName(fileName), Path.GetFileNameWithoutExtension(fileName)); foreach (KeyValuePair kvp in obj.Materials) { var mat = kvp.Value; string name = "_" + ObjFile.GetIndexOfMaterialInList(obj, mat); sw.WriteLine($"newmtl {name}"); if (mat.Color is object) { sw.WriteLine($"kd {(mat.Color.Value.R / 255d).ToString().Replace(",", ".")} {(mat.Color.Value.G / 255d).ToString().Replace(",", ".")} {(mat.Color.Value.B / 255d).ToString().Replace(",", ".")}"); } if (mat.Opacity is object) { sw.WriteLine($"d {mat.Opacity.Value.ToString().Replace(",", ".")}"); } if (mat.Image is object) { string imgFile = name + ".png"; if (!Directory.Exists(imgDirFull)) Directory.CreateDirectory(imgDirFull); mat.Image.Save(Path.Combine(imgDirFull, imgFile), System.Drawing.Imaging.ImageFormat.Png); sw.WriteLine($"map_kd {Path.Combine(imgDirName, imgFile)}"); } } sw.Flush(); fs.Close(); } } }