├── .gitattributes ├── .gitignore ├── LICENSE ├── ObjParser.sln ├── ObjParser ├── Extent.cs ├── Mtl.cs ├── Obj.cs ├── ObjParser.csproj ├── Properties │ └── AssemblyInfo.cs └── Types │ ├── Color.cs │ ├── Face.cs │ ├── IType.cs │ ├── Material.cs │ ├── TextureVertex.cs │ └── Vertex.cs ├── ObjParser_Tests ├── LoadObjTests.cs ├── ObjParser_Tests.csproj ├── Properties │ └── AssemblyInfo.cs ├── WriteObjTests.cs └── packages.config └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Roslyn cache directories 20 | *.ide/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | #NUNIT 27 | *.VisualState.xml 28 | TestResult.xml 29 | 30 | # Build Results of an ATL Project 31 | [Dd]ebugPS/ 32 | [Rr]eleasePS/ 33 | dlldata.c 34 | 35 | *_i.c 36 | *_p.c 37 | *_i.h 38 | *.ilk 39 | *.meta 40 | *.obj 41 | *.pch 42 | *.pdb 43 | *.pgc 44 | *.pgd 45 | *.rsp 46 | *.sbr 47 | *.tlb 48 | *.tli 49 | *.tlh 50 | *.tmp 51 | *.tmp_proj 52 | *.log 53 | *.vspscc 54 | *.vssscc 55 | .builds 56 | *.pidb 57 | *.svclog 58 | *.scc 59 | 60 | # Chutzpah Test files 61 | _Chutzpah* 62 | 63 | # Visual C++ cache files 64 | ipch/ 65 | *.aps 66 | *.ncb 67 | *.opensdf 68 | *.sdf 69 | *.cachefile 70 | 71 | # Visual Studio profiler 72 | *.psess 73 | *.vsp 74 | *.vspx 75 | 76 | # TFS 2012 Local Workspace 77 | $tf/ 78 | 79 | # Guidance Automation Toolkit 80 | *.gpState 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper*/ 84 | *.[Rr]e[Ss]harper 85 | *.DotSettings.user 86 | 87 | # JustCode is a .NET coding addin-in 88 | .JustCode 89 | 90 | # TeamCity is a build add-in 91 | _TeamCity* 92 | 93 | # DotCover is a Code Coverage Tool 94 | *.dotCover 95 | 96 | # NCrunch 97 | _NCrunch_* 98 | .*crunch*.local.xml 99 | 100 | # MightyMoose 101 | *.mm.* 102 | AutoTest.Net/ 103 | 104 | # Web workbench (sass) 105 | .sass-cache/ 106 | 107 | # Installshield output folder 108 | [Ee]xpress/ 109 | 110 | # DocProject is a documentation generator add-in 111 | DocProject/buildhelp/ 112 | DocProject/Help/*.HxT 113 | DocProject/Help/*.HxC 114 | DocProject/Help/*.hhc 115 | DocProject/Help/*.hhk 116 | DocProject/Help/*.hhp 117 | DocProject/Help/Html2 118 | DocProject/Help/html 119 | 120 | # Click-Once directory 121 | publish/ 122 | 123 | # Publish Web Output 124 | *.[Pp]ublish.xml 125 | *.azurePubxml 126 | ## TODO: Comment the next line if you want to checkin your 127 | ## web deploy settings but do note that will include unencrypted 128 | ## passwords 129 | #*.pubxml 130 | 131 | # NuGet Packages Directory 132 | packages/* 133 | ## TODO: If the tool you use requires repositories.config 134 | ## uncomment the next line 135 | #!packages/repositories.config 136 | 137 | # Enable "build/" folder in the NuGet Packages folder since 138 | # NuGet packages use it for MSBuild targets. 139 | # This line needs to be after the ignore of the build folder 140 | # (and the packages folder if the line above has been uncommented) 141 | !packages/build/ 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | bower_components/ 163 | 164 | # RIA/Silverlight projects 165 | Generated_Code/ 166 | 167 | # Backup & report files from converting an old project file 168 | # to a newer Visual Studio version. Backup files are not needed, 169 | # because we have git ;-) 170 | _UpgradeReport_Files/ 171 | Backup*/ 172 | UpgradeLog*.XML 173 | UpgradeLog*.htm 174 | 175 | # SQL Server files 176 | *.mdf 177 | *.ldf 178 | 179 | # Business Intelligence projects 180 | *.rdl.data 181 | *.bim.layout 182 | *.bim_*.settings 183 | 184 | # Microsoft Fakes 185 | FakesAssemblies/ 186 | 187 | # LightSwitch generated files 188 | GeneratedArtifacts/ 189 | _Pvt_Extensions/ 190 | ModelManifest.xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stefan Gordon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /ObjParser.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjParser", "ObjParser\ObjParser.csproj", "{04AA9B94-B460-4E73-BAF0-0154494BFE82}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjParser_Tests", "ObjParser_Tests\ObjParser_Tests.csproj", "{067456C0-086C-46A8-B37F-1405717B7BFC}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {04AA9B94-B460-4E73-BAF0-0154494BFE82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {04AA9B94-B460-4E73-BAF0-0154494BFE82}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {04AA9B94-B460-4E73-BAF0-0154494BFE82}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {04AA9B94-B460-4E73-BAF0-0154494BFE82}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {067456C0-086C-46A8-B37F-1405717B7BFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {067456C0-086C-46A8-B37F-1405717B7BFC}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {067456C0-086C-46A8-B37F-1405717B7BFC}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {067456C0-086C-46A8-B37F-1405717B7BFC}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /ObjParser/Extent.cs: -------------------------------------------------------------------------------- 1 | namespace ObjParser 2 | { 3 | public class Extent 4 | { 5 | public double XMax { get; set; } 6 | public double XMin { get; set; } 7 | public double YMax { get; set; } 8 | public double YMin { get; set; } 9 | public double ZMax { get; set; } 10 | public double ZMin { get; set; } 11 | 12 | public double XSize { get { return XMax - XMin; } } 13 | public double YSize { get { return YMax - YMin; } } 14 | public double ZSize { get { return ZMax - ZMin; } } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ObjParser/Mtl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using ObjParser.Types; 6 | 7 | namespace ObjParser 8 | { 9 | public class Mtl 10 | { 11 | public List MaterialList; 12 | 13 | /// 14 | /// Constructor. Initializes VertexList, FaceList and TextureList. 15 | /// 16 | public Mtl() 17 | { 18 | MaterialList = new List(); 19 | } 20 | 21 | /// 22 | /// Load .obj from a filepath. 23 | /// 24 | /// 25 | public void LoadMtl(string path) 26 | { 27 | LoadMtl(File.ReadAllLines(path)); 28 | } 29 | 30 | /// 31 | /// Load .obj from a stream. 32 | /// 33 | /// 34 | public void LoadMtl(Stream data) 35 | { 36 | using (var reader = new StreamReader(data)) 37 | { 38 | LoadMtl(reader.ReadToEnd().Split(Environment.NewLine.ToCharArray())); 39 | } 40 | } 41 | 42 | /// 43 | /// Load .mtl from a list of strings. 44 | /// 45 | /// 46 | public void LoadMtl(IEnumerable data) 47 | { 48 | foreach (var line in data) 49 | { 50 | processLine(line); 51 | } 52 | } 53 | 54 | public void WriteMtlFile(string path, string[] headerStrings) 55 | { 56 | using (var outStream = File.OpenWrite(path)) 57 | using (var writer = new StreamWriter(outStream)) 58 | { 59 | // Write some header data 60 | WriteHeader(writer, headerStrings); 61 | 62 | MaterialList.ForEach(v => writer.WriteLine(v)); 63 | } 64 | } 65 | 66 | private void WriteHeader(StreamWriter writer, string[] headerStrings) 67 | { 68 | if (headerStrings == null || headerStrings.Length == 0) 69 | { 70 | writer.WriteLine("# Generated by ObjParser"); 71 | return; 72 | } 73 | 74 | foreach (var line in headerStrings) 75 | { 76 | writer.WriteLine("# " + line); 77 | } 78 | } 79 | 80 | private Material currentMaterial() 81 | { 82 | if (MaterialList.Count > 0) return MaterialList.Last(); 83 | return new Material(); 84 | } 85 | 86 | /// 87 | /// Parses and loads a line from an OBJ file. 88 | /// Currently only supports V, VT, F and MTLLIB prefixes 89 | /// 90 | private void processLine(string line) 91 | { 92 | string[] parts = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 93 | 94 | if (parts.Length > 0) 95 | { 96 | Material CurrentMaterial = currentMaterial(); 97 | Color c = new Color(); 98 | switch (parts[0]) 99 | { 100 | case "newmtl": 101 | CurrentMaterial = new Material(); 102 | CurrentMaterial.Name = parts[1]; 103 | MaterialList.Add(CurrentMaterial); 104 | break; 105 | case "Ka": 106 | c.LoadFromStringArray(parts); 107 | CurrentMaterial.AmbientReflectivity = c; 108 | break; 109 | case "Kd": 110 | c.LoadFromStringArray(parts); 111 | CurrentMaterial.DiffuseReflectivity = c; 112 | break; 113 | case "Ks": 114 | c.LoadFromStringArray(parts); 115 | CurrentMaterial.SpecularReflectivity = c; 116 | break; 117 | case "Ke": 118 | c.LoadFromStringArray(parts); 119 | CurrentMaterial.EmissiveCoefficient = c; 120 | break; 121 | case "Tf": 122 | c.LoadFromStringArray(parts); 123 | CurrentMaterial.TransmissionFilter = c; 124 | break; 125 | case "Ni": 126 | CurrentMaterial.OpticalDensity = float.Parse(parts[1]); 127 | break; 128 | case "d": 129 | CurrentMaterial.Dissolve = float.Parse(parts[1]); 130 | break; 131 | case "illum": 132 | CurrentMaterial.IlluminationModel = int.Parse(parts[1]); 133 | break; 134 | case "Ns": 135 | CurrentMaterial.SpecularExponent = float.Parse(parts[1]); 136 | break; 137 | } 138 | } 139 | } 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ObjParser/Obj.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using ObjParser.Types; 6 | 7 | namespace ObjParser 8 | { 9 | public class Obj 10 | { 11 | public List VertexList; 12 | public List FaceList; 13 | public List TextureList; 14 | 15 | public Extent Size { get; set; } 16 | 17 | public string UseMtl { get; set; } 18 | public string Mtl { get; set; } 19 | 20 | /// 21 | /// Constructor. Initializes VertexList, FaceList and TextureList. 22 | /// 23 | public Obj() 24 | { 25 | VertexList = new List(); 26 | FaceList = new List(); 27 | TextureList = new List(); 28 | } 29 | 30 | /// 31 | /// Load .obj from a filepath. 32 | /// 33 | /// 34 | public void LoadObj(string path) 35 | { 36 | LoadObj(File.ReadAllLines(path)); 37 | } 38 | 39 | /// 40 | /// Load .obj from a stream. 41 | /// 42 | /// 43 | public void LoadObj(Stream data) 44 | { 45 | using (var reader = new StreamReader(data)) 46 | { 47 | LoadObj(reader.ReadToEnd().Split(Environment.NewLine.ToCharArray())); 48 | } 49 | } 50 | 51 | /// 52 | /// Load .obj from a list of strings. 53 | /// 54 | /// 55 | public void LoadObj(IEnumerable data) 56 | { 57 | foreach (var line in data) 58 | { 59 | processLine(line); 60 | } 61 | 62 | updateSize(); 63 | } 64 | 65 | public void WriteObjFile(string path, string[] headerStrings) 66 | { 67 | using (var outStream = File.OpenWrite(path)) 68 | using (var writer = new StreamWriter(outStream)) 69 | { 70 | // Write some header data 71 | WriteHeader(writer, headerStrings); 72 | 73 | if (!string.IsNullOrEmpty(Mtl)) 74 | { 75 | writer.WriteLine("mtllib " + Mtl); 76 | } 77 | 78 | VertexList.ForEach(v => writer.WriteLine(v)); 79 | TextureList.ForEach(tv => writer.WriteLine(tv)); 80 | string lastUseMtl = ""; 81 | foreach (Face face in FaceList) { 82 | if (face.UseMtl != null && !face.UseMtl.Equals(lastUseMtl)) { 83 | writer.WriteLine("usemtl " + face.UseMtl); 84 | lastUseMtl = face.UseMtl; 85 | } 86 | writer.WriteLine(face); 87 | } 88 | } 89 | } 90 | 91 | private void WriteHeader(StreamWriter writer, string[] headerStrings) 92 | { 93 | if (headerStrings == null || headerStrings.Length == 0) 94 | { 95 | writer.WriteLine("# Generated by ObjParser"); 96 | return; 97 | } 98 | 99 | foreach (var line in headerStrings) 100 | { 101 | writer.WriteLine("# " + line); 102 | } 103 | } 104 | 105 | /// 106 | /// Sets our global object size with an extent object 107 | /// 108 | private void updateSize() 109 | { 110 | // If there are no vertices then size should be 0. 111 | if (VertexList.Count == 0) 112 | { 113 | Size = new Extent 114 | { 115 | XMax = 0, 116 | XMin = 0, 117 | YMax = 0, 118 | YMin = 0, 119 | ZMax = 0, 120 | ZMin = 0 121 | }; 122 | 123 | // Avoid an exception below if VertexList was empty. 124 | return; 125 | } 126 | 127 | Size = new Extent 128 | { 129 | XMax = VertexList.Max(v => v.X), 130 | XMin = VertexList.Min(v => v.X), 131 | YMax = VertexList.Max(v => v.Y), 132 | YMin = VertexList.Min(v => v.Y), 133 | ZMax = VertexList.Max(v => v.Z), 134 | ZMin = VertexList.Min(v => v.Z) 135 | }; 136 | } 137 | 138 | /// 139 | /// Parses and loads a line from an OBJ file. 140 | /// Currently only supports V, VT, F and MTLLIB prefixes 141 | /// 142 | private void processLine(string line) 143 | { 144 | string[] parts = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 145 | 146 | if (parts.Length > 0) 147 | { 148 | switch (parts[0]) 149 | { 150 | case "usemtl": 151 | UseMtl = parts[1]; 152 | break; 153 | case "mtllib": 154 | Mtl = parts[1]; 155 | break; 156 | case "v": 157 | Vertex v = new Vertex(); 158 | v.LoadFromStringArray(parts); 159 | VertexList.Add(v); 160 | v.Index = VertexList.Count(); 161 | break; 162 | case "f": 163 | Face f = new Face(); 164 | f.LoadFromStringArray(parts); 165 | f.UseMtl = UseMtl; 166 | FaceList.Add(f); 167 | break; 168 | case "vt": 169 | TextureVertex vt = new TextureVertex(); 170 | vt.LoadFromStringArray(parts); 171 | TextureList.Add(vt); 172 | vt.Index = TextureList.Count(); 173 | break; 174 | 175 | } 176 | } 177 | } 178 | 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /ObjParser/ObjParser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {04AA9B94-B460-4E73-BAF0-0154494BFE82} 8 | Library 9 | Properties 10 | ObjParser 11 | ObjParser 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /ObjParser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ObjParser")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ObjParser")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("04aa9b94-b460-4e73-baf0-0154494bfe82")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ObjParser/Types/Color.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ObjParser.Types 10 | { 11 | public class Color : IType 12 | { 13 | public float r { get; set; } 14 | public float g { get; set; } 15 | public float b { get; set; } 16 | 17 | public Color() 18 | { 19 | this.r = 1f; 20 | this.g = 1f; 21 | this.b = 1f; 22 | } 23 | 24 | public void LoadFromStringArray(string[] data) 25 | { 26 | if (data.Length != 4) return; 27 | r = float.Parse(data[1]); 28 | g = float.Parse(data[2]); 29 | b = float.Parse(data[3]); 30 | } 31 | 32 | public override string ToString() 33 | { 34 | return string.Format("{0} {1} {2}", r, g, b); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ObjParser/Types/Face.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ObjParser.Types 10 | { 11 | public class Face : IType 12 | { 13 | public const int MinimumDataLength = 4; 14 | public const string Prefix = "f"; 15 | 16 | public string UseMtl { get; set; } 17 | public int[] VertexIndexList { get; set; } 18 | public int[] TextureVertexIndexList { get; set; } 19 | 20 | public void LoadFromStringArray(string[] data) 21 | { 22 | if (data.Length < MinimumDataLength) 23 | throw new ArgumentException("Input array must be of minimum length " + MinimumDataLength, "data"); 24 | 25 | if (!data[0].ToLower().Equals(Prefix)) 26 | throw new ArgumentException("Data prefix must be '" + Prefix + "'", "data"); 27 | 28 | int vcount = data.Count() - 1; 29 | VertexIndexList = new int[vcount]; 30 | TextureVertexIndexList = new int[vcount]; 31 | 32 | bool success; 33 | 34 | for (int i = 0; i < vcount; i++) 35 | { 36 | string[] parts = data[i + 1].Split('/'); 37 | 38 | int vindex; 39 | success = int.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vindex); 40 | if (!success) throw new ArgumentException("Could not parse parameter as int"); 41 | VertexIndexList[i] = vindex; 42 | 43 | if (parts.Count() > 1) 44 | { 45 | success = int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vindex); 46 | if (success) { 47 | TextureVertexIndexList[i] = vindex; 48 | } 49 | } 50 | } 51 | } 52 | 53 | // HACKHACK this will write invalid files if there are no texture vertices in 54 | // the faces, need to identify that and write an alternate format 55 | public override string ToString() 56 | { 57 | StringBuilder b = new StringBuilder(); 58 | b.Append("f"); 59 | 60 | for (int i = 0; i < VertexIndexList.Count(); i++) 61 | { 62 | if (i < TextureVertexIndexList.Length) 63 | { 64 | b.AppendFormat(" {0}/{1}", VertexIndexList[i], TextureVertexIndexList[i]); 65 | } 66 | else 67 | { 68 | b.AppendFormat(" {0}", VertexIndexList[i]); 69 | } 70 | } 71 | 72 | return b.ToString(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ObjParser/Types/IType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ObjParser.Types 9 | { 10 | interface IType 11 | { 12 | void LoadFromStringArray(string[] data); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ObjParser/Types/Material.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ObjParser.Types 10 | { 11 | public class Material : IType 12 | { 13 | public string Name { get; set; } 14 | public Color AmbientReflectivity { get; set; } 15 | public Color DiffuseReflectivity { get; set; } 16 | public Color SpecularReflectivity { get; set; } 17 | public Color TransmissionFilter { get; set; } 18 | public Color EmissiveCoefficient { get; set; } 19 | public float SpecularExponent { get; set; } 20 | public float OpticalDensity { get; set; } 21 | public float Dissolve { get; set; } 22 | public float IlluminationModel { get; set; } 23 | 24 | public Material() 25 | { 26 | this.Name = "DefaultMaterial"; 27 | this.AmbientReflectivity = new Color(); 28 | this.DiffuseReflectivity = new Color(); 29 | this.SpecularReflectivity = new Color(); 30 | this.TransmissionFilter = new Color(); 31 | this.EmissiveCoefficient = new Color(); 32 | this.SpecularExponent = 0; 33 | this.OpticalDensity = 1.0f; 34 | this.Dissolve = 1.0f; 35 | this.IlluminationModel = 0; 36 | } 37 | 38 | public void LoadFromStringArray(string[] data) 39 | { 40 | } 41 | 42 | public override string ToString() 43 | { 44 | StringBuilder b = new StringBuilder(); 45 | b.AppendLine("newmtl " + Name); 46 | 47 | b.AppendLine(string.Format("Ka {0}", AmbientReflectivity)); 48 | b.AppendLine(string.Format("Kd {0}", DiffuseReflectivity)); 49 | b.AppendLine(string.Format("Ks {0}", SpecularReflectivity)); 50 | b.AppendLine(string.Format("Tf {0}", TransmissionFilter)); 51 | b.AppendLine(string.Format("Ke {0}", EmissiveCoefficient)); 52 | b.AppendLine(string.Format("Ns {0}", SpecularExponent)); 53 | b.AppendLine(string.Format("Ni {0}", OpticalDensity)); 54 | b.AppendLine(string.Format("d {0}", Dissolve)); 55 | b.AppendLine(string.Format("illum {0}", IlluminationModel)); 56 | 57 | return b.ToString(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ObjParser/Types/TextureVertex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ObjParser.Types 10 | { 11 | public class TextureVertex : IType 12 | { 13 | public const int MinimumDataLength = 3; 14 | public const string Prefix = "vt"; 15 | 16 | public double X { get; set; } 17 | 18 | public double Y { get; set; } 19 | 20 | public int Index { get; set; } 21 | 22 | public void LoadFromStringArray(string[] data) 23 | { 24 | if (data.Length < MinimumDataLength) 25 | throw new ArgumentException("Input array must be of minimum length " + MinimumDataLength, "data"); 26 | 27 | if (!data[0].ToLower().Equals(Prefix)) 28 | throw new ArgumentException("Data prefix must be '" + Prefix + "'", "data"); 29 | 30 | bool success; 31 | 32 | double x, y; 33 | 34 | success = double.TryParse(data[1], NumberStyles.Any, CultureInfo.InvariantCulture, out x); 35 | if (!success) throw new ArgumentException("Could not parse X parameter as double"); 36 | 37 | success = double.TryParse(data[2], NumberStyles.Any, CultureInfo.InvariantCulture, out y); 38 | if (!success) throw new ArgumentException("Could not parse Y parameter as double"); 39 | X = x; 40 | Y = y; 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return string.Format("vt {0} {1}", X, Y); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ObjParser/Types/Vertex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ObjParser.Types 9 | { 10 | public class Vertex : IType 11 | { 12 | public const int MinimumDataLength = 4; 13 | public const string Prefix = "v"; 14 | 15 | public double X { get; set; } 16 | 17 | public double Y { get; set; } 18 | 19 | public double Z { get; set; } 20 | 21 | public int Index { get; set; } 22 | 23 | public void LoadFromStringArray(string[] data) 24 | { 25 | if (data.Length < MinimumDataLength) 26 | throw new ArgumentException("Input array must be of minimum length " + MinimumDataLength, "data"); 27 | 28 | if (!data[0].ToLower().Equals(Prefix)) 29 | throw new ArgumentException("Data prefix must be '" + Prefix + "'", "data"); 30 | 31 | bool success; 32 | 33 | double x, y, z; 34 | 35 | success = double.TryParse(data[1], NumberStyles.Any, CultureInfo.InvariantCulture, out x); 36 | if (!success) throw new ArgumentException("Could not parse X parameter as double"); 37 | 38 | success = double.TryParse(data[2], NumberStyles.Any, CultureInfo.InvariantCulture, out y); 39 | if (!success) throw new ArgumentException("Could not parse Y parameter as double"); 40 | 41 | success = double.TryParse(data[3], NumberStyles.Any, CultureInfo.InvariantCulture, out z); 42 | if (!success) throw new ArgumentException("Could not parse Z parameter as double"); 43 | 44 | X = x; 45 | Y = y; 46 | Z = z; 47 | } 48 | 49 | public override string ToString() 50 | { 51 | return string.Format("v {0} {1} {2}", X, Y, Z); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ObjParser_Tests/LoadObjTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using ObjParser; 4 | 5 | namespace ObjParser_Tests 6 | { 7 | [TestFixture] 8 | public class LoadObjTests 9 | { 10 | private Obj obj; 11 | private Mtl mtl; 12 | 13 | [SetUp] 14 | public void SetUp() 15 | { 16 | obj = new Obj(); 17 | mtl = new Mtl(); 18 | } 19 | 20 | #region Vertex 21 | [Test] 22 | public void LoadObj_OneVert_OneVertCount() 23 | { 24 | // Arrange 25 | var objFile = new[] 26 | { 27 | "v 0.0 0.0 0.0" 28 | }; 29 | 30 | // Act 31 | obj.LoadObj(objFile); 32 | 33 | // Assert 34 | Assert.IsTrue(obj.VertexList.Count == 1); 35 | } 36 | 37 | [Test] 38 | public void LoadOBj_TwoVerts_TwoVertCount() 39 | { 40 | // Arrange 41 | var objFile = new[] 42 | { 43 | "v 0.0 0.0 0.0", 44 | "v 1.0 1.0 1.0" 45 | }; 46 | 47 | // Act 48 | obj.LoadObj(objFile); 49 | 50 | // Assert 51 | Assert.IsTrue(obj.VertexList.Count == 2); 52 | } 53 | 54 | [Test] 55 | public void LoadObj_EmptyObj_EmptyObjNoVertsNoFaces() 56 | { 57 | // Arrange 58 | var objFile = new string[] {}; 59 | 60 | // Act 61 | obj.LoadObj(objFile); 62 | 63 | // Assert 64 | Assert.IsTrue(obj.VertexList.Count == 0); 65 | Assert.IsTrue(obj.FaceList.Count == 0); 66 | } 67 | 68 | [Test] 69 | public void LoadObj_NoVertPositions_ThrowsArgumentException() 70 | { 71 | // Arrange 72 | var objFile = new[] 73 | { 74 | "v 0.0 0.0 0.0", 75 | "v" 76 | }; 77 | 78 | // Act 79 | 80 | // Assert 81 | Assert.That(() => obj.LoadObj(objFile), Throws.TypeOf()); 82 | } 83 | 84 | [Test] 85 | public void LoadObj_CommaSeperatedVertPositions_ThrowsArgumentException() 86 | { 87 | // Arrange 88 | var objFile = new[] 89 | { 90 | // Valid 91 | "v 0, 0, 0,", 92 | 93 | // Invalid 94 | "v 0.1, 0.1, 0.2,", 95 | "v 0.1, 0.1, 0.3,", 96 | "v 0.1, 0.1, 0.4," 97 | }; 98 | 99 | // Act 100 | 101 | // Assert 102 | Assert.That(() => obj.LoadObj(objFile), Throws.TypeOf()); 103 | } 104 | 105 | [Test] 106 | public void LoadObj_LettersInsteadOfPositions_ThrowsArgumentException() 107 | { 108 | // Arrange 109 | var objFile = new[] 110 | { 111 | "v a b c" 112 | }; 113 | 114 | // Act 115 | 116 | // Assert 117 | Assert.That(() => obj.LoadObj(objFile), Throws.TypeOf()); 118 | } 119 | #endregion 120 | 121 | #region TextureVertex 122 | [Test] 123 | public void LoadObj_OneTextureVert_OneTextureVertCount() { 124 | // Arrange 125 | var objFile = new[] 126 | { 127 | "vt 0.0 0.0" 128 | }; 129 | 130 | // Act 131 | obj.LoadObj(objFile); 132 | 133 | // Assert 134 | Assert.IsTrue(obj.TextureList.Count == 1); 135 | } 136 | 137 | [Test] 138 | public void LoadOBj_TwoTextureVerts_TwoTextureVertCount() { 139 | // Arrange 140 | var objFile = new[] 141 | { 142 | "vt 0.0 0.0", 143 | "vt 1.0 1.0" 144 | }; 145 | 146 | // Act 147 | obj.LoadObj(objFile); 148 | 149 | // Assert 150 | Assert.IsTrue(obj.TextureList.Count == 2); 151 | } 152 | 153 | [Test] 154 | public void LoadOBj_TwoTextureVerts_TwoTextureVertValues() { 155 | // Arrange 156 | var objFile = new[] 157 | { 158 | "vt 5.0711 0.0003", 159 | "vt 5.4612 1.0000" 160 | }; 161 | 162 | // Act 163 | obj.LoadObj(objFile); 164 | 165 | // Assert 166 | Assert.IsTrue(obj.TextureList.Count == 2); 167 | Assert.AreEqual(5.0711d, obj.TextureList[0].X); 168 | Assert.AreEqual(0.0003d, obj.TextureList[0].Y); 169 | Assert.AreEqual(5.4612d, obj.TextureList[1].X); 170 | Assert.AreEqual(1.0000d, obj.TextureList[1].Y); 171 | } 172 | #endregion 173 | 174 | #region Mtl 175 | [Test] 176 | public void Mtl_LoadMtl_TwoMaterials() { 177 | // Arrange 178 | var mtlFile = new[] 179 | { 180 | "newmtl Material", 181 | "Ns 96.078431", 182 | "Ka 1.000000 1.000000 1.000000", 183 | "Kd 0.630388 0.620861 0.640000", 184 | "Ks 0.500000 0.500000 0.500000", 185 | "Ke 0.000000 0.000000 0.000000", 186 | "Tf 0.000000 0.000000 0.000000", 187 | "Ni 1.000000", 188 | "d 1.000000", 189 | "illum 2", 190 | "", 191 | "newmtl Material.001", 192 | "Ns 96.078431", 193 | "Ka 1.000000 1.000000 1.000000", 194 | "Kd 0.640000 0.026578 0.014364", 195 | "Ks 0.500000 0.500000 0.500000", 196 | "Ke 0.000000 0.000000 0.000000", 197 | "Ni 1.000000", 198 | "d 1.000000", 199 | "illum 2" 200 | }; 201 | 202 | // Act 203 | mtl.LoadMtl(mtlFile); 204 | 205 | // Assert 206 | Assert.AreEqual(2, mtl.MaterialList.Count); 207 | ObjParser.Types.Material first = mtl.MaterialList[0]; 208 | Assert.AreEqual("Material", first.Name); 209 | Assert.AreEqual(96.078431f, first.SpecularExponent); 210 | Assert.AreEqual(1.0f, first.AmbientReflectivity.r); 211 | Assert.AreEqual(1.0f, first.AmbientReflectivity.g); 212 | Assert.AreEqual(1.0f, first.AmbientReflectivity.b); 213 | Assert.AreEqual(0.630388f, first.DiffuseReflectivity.r); 214 | Assert.AreEqual(0.620861f, first.DiffuseReflectivity.g); 215 | Assert.AreEqual(0.640000f, first.DiffuseReflectivity.b); 216 | Assert.AreEqual(0.5f, first.SpecularReflectivity.r); 217 | Assert.AreEqual(0.5f, first.SpecularReflectivity.g); 218 | Assert.AreEqual(0.5f, first.SpecularReflectivity.b); 219 | Assert.AreEqual(0.0f, first.EmissiveCoefficient.r); 220 | Assert.AreEqual(0.0f, first.EmissiveCoefficient.g); 221 | Assert.AreEqual(0.0f, first.EmissiveCoefficient.b); 222 | Assert.AreEqual(0.0f, first.TransmissionFilter.r); 223 | Assert.AreEqual(0.0f, first.TransmissionFilter.g); 224 | Assert.AreEqual(0.0f, first.TransmissionFilter.b); 225 | Assert.AreEqual(1.0f, first.OpticalDensity); 226 | Assert.AreEqual(1.0f, first.Dissolve); 227 | Assert.AreEqual(2, first.IlluminationModel); 228 | 229 | ObjParser.Types.Material second = mtl.MaterialList[1]; 230 | Assert.AreEqual("Material.001", second.Name); 231 | Assert.AreEqual(96.078431f, second.SpecularExponent); 232 | } 233 | #endregion 234 | 235 | #region Face 236 | [Test] 237 | public void LoadObj_FourVertsSingleFace_FourVertsOneFaceCount() 238 | { 239 | // Arrange 240 | var objFile = new[] 241 | { 242 | "v -0.500000 -0.500000 0.500000", 243 | "v 0.500000 -0.500000 0.500000", 244 | "v -0.500000 0.500000 0.500000", 245 | "v 0.500000 0.500000 0.500000", 246 | "f 1/1/1 2/2/1 3/3/1" 247 | }; 248 | 249 | // Act 250 | obj.LoadObj(objFile); 251 | 252 | // Assert 253 | Assert.IsTrue(obj.VertexList.Count == 4); 254 | Assert.IsTrue(obj.FaceList.Count == 1); 255 | Assert.IsNull(obj.FaceList[0].UseMtl); 256 | } 257 | 258 | [Test] 259 | public void LoadObj_FourVertsThreeFace_TwoMaterialsCount() { 260 | // Arrange 261 | var objFile = new[] 262 | { 263 | "v -0.500000 -0.500000 0.500000", 264 | "v 0.500000 -0.500000 0.500000", 265 | "v -0.500000 0.500000 0.500000", 266 | "v 0.500000 0.500000 0.500000", 267 | "usemtl Material", 268 | "f 1/1/1 2/2/1 3/3/1", 269 | "usemtl Material.001", 270 | "f 1/1/1 2/2/1 3/3/1", 271 | "f 1/1/1 2/2/1 3/3/1" 272 | }; 273 | 274 | // Act 275 | obj.LoadObj(objFile); 276 | 277 | // Assert 278 | Assert.IsTrue(obj.VertexList.Count == 4); 279 | Assert.IsTrue(obj.FaceList.Count == 3); 280 | Assert.AreEqual(obj.FaceList[0].UseMtl, "Material"); 281 | Assert.AreEqual(obj.FaceList[1].UseMtl, "Material.001"); 282 | Assert.AreEqual(obj.FaceList[2].UseMtl, "Material.001"); 283 | } 284 | 285 | [Test] 286 | public void LoadObj_FourVertsTwoFace_OneMaterialCount() { 287 | // Arrange 288 | var objFile = new[] 289 | { 290 | "v -0.500000 -0.500000 0.500000", 291 | "v 0.500000 -0.500000 0.500000", 292 | "v -0.500000 0.500000 0.500000", 293 | "v 0.500000 0.500000 0.500000", 294 | "f 1/1/1 2/2/1 3/3/1", 295 | "usemtl Material", 296 | "f 1/1/1 2/2/1 3/3/1" 297 | }; 298 | 299 | // Act 300 | obj.LoadObj(objFile); 301 | 302 | // Assert 303 | Assert.IsTrue(obj.VertexList.Count == 4); 304 | Assert.IsTrue(obj.FaceList.Count == 2); 305 | Assert.IsNull(obj.FaceList[0].UseMtl); 306 | Assert.AreEqual(obj.FaceList[1].UseMtl, "Material"); 307 | } 308 | 309 | [Test] 310 | public void LoadObj_FourVertsSingleFaceNoTextureVerts_FourVertsOneFaceCount() { 311 | // Arrange 312 | var objFile = new[] 313 | { 314 | "v -0.500000 -0.500000 0.500000", 315 | "v 0.500000 -0.500000 0.500000", 316 | "v -0.500000 0.500000 0.500000", 317 | "v 0.500000 0.500000 0.500000", 318 | "f 1//1 2//1 3//1" 319 | }; 320 | 321 | // Act 322 | obj.LoadObj(objFile); 323 | 324 | // Assert 325 | Assert.IsTrue(obj.VertexList.Count == 4); 326 | Assert.IsTrue(obj.FaceList.Count == 1); 327 | } 328 | #endregion 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /ObjParser_Tests/ObjParser_Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {067456C0-086C-46A8-B37F-1405717B7BFC} 7 | Library 8 | Properties 9 | ObjParser_Tests 10 | ObjParser_Tests 11 | v4.5.2 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\NUnit.3.4.0\lib\net45\nunit.framework.dll 35 | True 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {04AA9B94-B460-4E73-BAF0-0154494BFE82} 50 | ObjParser 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /ObjParser_Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ObjParser_Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ObjParser_Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("067456c0-086c-46a8-b37f-1405717b7bfc")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ObjParser_Tests/WriteObjTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using NUnit.Framework; 4 | using ObjParser; 5 | 6 | namespace ObjParser_Tests 7 | { 8 | [TestFixture] 9 | public class WriteObjTests 10 | { 11 | private Obj obj; 12 | private Mtl mtl; 13 | 14 | [SetUp] 15 | public void SetUp() 16 | { 17 | obj = new Obj(); 18 | mtl = new Mtl(); 19 | } 20 | 21 | #region Obj 22 | [Test] 23 | public void Obj_WriteObj_TwoMaterials() { 24 | string tempfilepath = Path.GetTempFileName(); 25 | string[] headers = new string[] { "ObjParser" }; 26 | 27 | // Arrange 28 | var objFile = new[] 29 | { 30 | "v -0.500000 -0.500000 0.500000", 31 | "v 0.500000 -0.500000 0.500000", 32 | "v -0.500000 0.500000 0.500000", 33 | "v 0.500000 0.500000 0.500000", 34 | "vt 5.0711 0.0003", 35 | "vt 5.4612 1.0000", 36 | "usemtl Material", 37 | "f 1/1/1 2/2/1 3/3/1", 38 | "usemtl Material.001", 39 | "f 1/1/1 2/2/1 3/3/1", 40 | "f 1/1/1 2/2/1 3/3/1" 41 | }; 42 | 43 | // Act 44 | obj.LoadObj(objFile); 45 | obj.WriteObjFile(tempfilepath, headers); 46 | obj = new Obj(); 47 | obj.LoadObj(tempfilepath); 48 | File.Delete(tempfilepath); 49 | 50 | // Assert 51 | Assert.IsTrue(obj.VertexList.Count == 4); 52 | Assert.IsTrue(obj.FaceList.Count == 3); 53 | Assert.AreEqual("Material", obj.FaceList[0].UseMtl); 54 | Assert.AreEqual("Material.001", obj.FaceList[1].UseMtl); 55 | Assert.AreEqual("Material.001", obj.FaceList[2].UseMtl); 56 | Assert.AreEqual(5.0711d, obj.TextureList[0].X); 57 | Assert.AreEqual(0.0003d, obj.TextureList[0].Y); 58 | Assert.AreEqual(5.4612d, obj.TextureList[1].X); 59 | Assert.AreEqual(1.0000d, obj.TextureList[1].Y); 60 | } 61 | 62 | [Test] 63 | public void Obj_WriteObj_NoMaterials() { 64 | string tempfilepath = Path.GetTempFileName(); 65 | string[] headers = new string[] { "ObjParser" }; 66 | 67 | // Arrange 68 | var objFile = new[] 69 | { 70 | "v -0.500000 -0.500000 0.500000", 71 | "v 0.500000 -0.500000 0.500000", 72 | "v -0.500000 0.500000 0.500000", 73 | "v 0.500000 0.500000 0.500000", 74 | "f 1/1/1 2/2/1 3/3/1", 75 | "f 1/1/1 2/2/1 3/3/1", 76 | "f 1/1/1 2/2/1 3/3/1" 77 | }; 78 | 79 | // Act 80 | obj.LoadObj(objFile); 81 | obj.WriteObjFile(tempfilepath, headers); 82 | obj = new Obj(); 83 | obj.LoadObj(tempfilepath); 84 | File.Delete(tempfilepath); 85 | 86 | // Assert 87 | Assert.IsTrue(obj.VertexList.Count == 4); 88 | Assert.IsTrue(obj.FaceList.Count == 3); 89 | Assert.IsNull(obj.FaceList[0].UseMtl); 90 | Assert.IsNull(obj.FaceList[1].UseMtl); 91 | Assert.IsNull(obj.FaceList[2].UseMtl); 92 | } 93 | #endregion 94 | 95 | #region Mtl 96 | [Test] 97 | public void Mtl_WriteMtl_TwoMaterials() { 98 | string tempfilepath = Path.GetTempFileName(); 99 | string[] headers = new string[] { "ObjParser" }; 100 | 101 | // Arrange 102 | var mtlFile = new[] 103 | { 104 | "newmtl Material", 105 | "Ns 96.078431", 106 | "Ka 1.000000 1.000000 1.000000", 107 | "Kd 0.630388 0.620861 0.640000", 108 | "Ks 0.500000 0.500000 0.500000", 109 | "Ke 0.000000 0.000000 0.000000", 110 | "Tf 0.000000 0.000000 0.000000", 111 | "Ni 1.000000", 112 | "d 1.000000", 113 | "illum 2", 114 | "", 115 | "newmtl Material.001", 116 | "Ns 96.078431", 117 | "Ka 1.000000 1.000000 1.000000", 118 | "Kd 0.640000 0.026578 0.014364", 119 | "Ks 0.500000 0.500000 0.500000", 120 | "Ke 0.000000 0.000000 0.000000", 121 | "Ni 1.000000", 122 | "d 1.000000", 123 | "illum 2" 124 | }; 125 | 126 | // Act 127 | mtl.LoadMtl(mtlFile); 128 | mtl.WriteMtlFile(tempfilepath, headers); 129 | mtl = new Mtl(); 130 | mtl.LoadMtl(tempfilepath); 131 | File.Delete(tempfilepath); 132 | 133 | // Assert 134 | Assert.AreEqual(2, mtl.MaterialList.Count); 135 | ObjParser.Types.Material first = mtl.MaterialList[0]; 136 | Assert.AreEqual("Material", first.Name); 137 | Assert.AreEqual(96.078431f, first.SpecularExponent); 138 | Assert.AreEqual(1.0f, first.AmbientReflectivity.r); 139 | Assert.AreEqual(1.0f, first.AmbientReflectivity.g); 140 | Assert.AreEqual(1.0f, first.AmbientReflectivity.b); 141 | Assert.AreEqual(0.630388f, first.DiffuseReflectivity.r); 142 | Assert.AreEqual(0.620861f, first.DiffuseReflectivity.g); 143 | Assert.AreEqual(0.640000f, first.DiffuseReflectivity.b); 144 | Assert.AreEqual(0.5f, first.SpecularReflectivity.r); 145 | Assert.AreEqual(0.5f, first.SpecularReflectivity.g); 146 | Assert.AreEqual(0.5f, first.SpecularReflectivity.b); 147 | Assert.AreEqual(0.0f, first.EmissiveCoefficient.r); 148 | Assert.AreEqual(0.0f, first.EmissiveCoefficient.g); 149 | Assert.AreEqual(0.0f, first.EmissiveCoefficient.b); 150 | Assert.AreEqual(0.0f, first.TransmissionFilter.r); 151 | Assert.AreEqual(0.0f, first.TransmissionFilter.g); 152 | Assert.AreEqual(0.0f, first.TransmissionFilter.b); 153 | Assert.AreEqual(1.0f, first.OpticalDensity); 154 | Assert.AreEqual(1.0f, first.Dissolve); 155 | Assert.AreEqual(2, first.IlluminationModel); 156 | 157 | ObjParser.Types.Material second = mtl.MaterialList[1]; 158 | Assert.AreEqual("Material.001", second.Name); 159 | Assert.AreEqual(96.078431f, second.SpecularExponent); 160 | } 161 | #endregion 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /ObjParser_Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ObjParser 2 | A WaveFront Obj Parser and Writer in C# 3 | 4 | ## Summary 5 | As part of the Pyrite3D project I have done significant work with parsing Obj files. I wanted to share some of that in a more reusable form, so I have extracted the basic parsing and writing of Obj files to a standalone library here. 6 | 7 | This is a pretty naive implementation, and only supports V, VT, F and MTLLIB entries in the file, but more can be added very easily. 8 | 9 | All data is parsed into .net generic collections where you can operate on it, then write the file back out. 10 | 11 | Also, see http://www.stefangordon.com/parsing-wavefront-obj-files-in-c/ 12 | --------------------------------------------------------------------------------