├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── build ├── BuildRelease.ps1 ├── Resources │ ├── PowershellFunctions.ps1 │ └── nuget.exe └── nuspec │ └── FileFormatWavefront │ ├── FileFormatWavefront.nuspec │ └── lib │ └── net40 │ ├── FileFormatWavefront.XML │ └── FileFormatWavefront.dll ├── models ├── Motherboard │ ├── Motherboard.mtl │ └── Textures │ │ ├── mb-ps-2.png │ │ └── p4sba-mb.jpg └── Palm │ ├── Bottom_T.jpg │ ├── Bottom_Trunk.bmp │ ├── Date Palm.mtl │ └── Source.md └── source ├── FileFormatWavefront.sln ├── FileFormatWavefront ├── Extensions │ └── StringExtensions.cs ├── FileFormatMtl.cs ├── FileFormatObj.cs ├── FileFormatWavefront.csproj ├── FileLoadResult.cs ├── Internals │ └── LineData.cs ├── Message.cs ├── MessageType.cs ├── Model │ ├── Colour.cs │ ├── Face.cs │ ├── Group.cs │ ├── Index.cs │ ├── Material.cs │ ├── Scene.cs │ ├── TextureMap.cs │ ├── UV.cs │ └── Vertex.cs └── Properties │ └── AssemblyInfo.cs ├── ObjValidator ├── ObjValidator.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs └── WavefrontObjectLoader ├── App.config ├── Details ├── DetailsUi.cs ├── DetailsUiBuilder.cs ├── FacesUi.cs ├── IDetailsUi.cs ├── PropertyGridUi.cs ├── UvsUi.cs └── VerticesUi.cs ├── Extensions └── RichTextBoxExtensions.cs ├── FormModelLoader.Designer.cs ├── FormModelLoader.cs ├── FormModelLoader.resx ├── Program.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings └── WavefrontObjectLoader.csproj /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Support 'GitHub Sponsors' funding. 2 | github: dwmkerr 3 | -------------------------------------------------------------------------------- /.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 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | [Bb]in/ 15 | [Oo]bj/ 16 | 17 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 18 | !packages/*/build/ 19 | 20 | # MSTest test Results 21 | [Tt]est[Rr]esult*/ 22 | [Bb]uild[Ll]og.* 23 | 24 | *_i.c 25 | *_p.c 26 | *.ilk 27 | *.meta 28 | *.obj 29 | *.pch 30 | *.pdb 31 | *.pgc 32 | *.pgd 33 | *.rsp 34 | *.sbr 35 | *.tlb 36 | *.tli 37 | *.tlh 38 | *.tmp 39 | *.tmp_proj 40 | *.log 41 | *.vspscc 42 | *.vssscc 43 | .builds 44 | *.pidb 45 | *.log 46 | *.scc 47 | 48 | # Visual C++ cache files 49 | ipch/ 50 | *.aps 51 | *.ncb 52 | *.opensdf 53 | *.sdf 54 | *.cachefile 55 | 56 | # Visual Studio profiler 57 | *.psess 58 | *.vsp 59 | *.vspx 60 | 61 | # Guidance Automation Toolkit 62 | *.gpState 63 | 64 | # ReSharper is a .NET coding add-in 65 | _ReSharper*/ 66 | *.[Rr]e[Ss]harper 67 | 68 | # TeamCity is a build add-in 69 | _TeamCity* 70 | 71 | # DotCover is a Code Coverage Tool 72 | *.dotCover 73 | 74 | # NCrunch 75 | *.ncrunch* 76 | .*crunch*.local.xml 77 | 78 | # Installshield output folder 79 | [Ee]xpress/ 80 | 81 | # DocProject is a documentation generator add-in 82 | DocProject/buildhelp/ 83 | DocProject/Help/*.HxT 84 | DocProject/Help/*.HxC 85 | DocProject/Help/*.hhc 86 | DocProject/Help/*.hhk 87 | DocProject/Help/*.hhp 88 | DocProject/Help/Html2 89 | DocProject/Help/html 90 | 91 | # Click-Once directory 92 | publish/ 93 | 94 | # Publish Web Output 95 | *.Publish.xml 96 | *.pubxml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | # ========================= 136 | # Windows detritus 137 | # ========================= 138 | 139 | # Windows image file caches 140 | Thumbs.db 141 | ehthumbs.db 142 | 143 | # Folder config file 144 | Desktop.ini 145 | 146 | # Recycle Bin used on file shares 147 | $RECYCLE.BIN/ 148 | 149 | # Mac crap 150 | .DS_Store 151 | build/1.* 152 | build/nuspec/FileFormatWavefront/lib 153 | build/nuspec/FileFormatWavefront/lib/ 154 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dave Kerr 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | file-format-wavefront 2 | ===================== 3 | 4 | A simple .NET library to load data from Wavefront *.obj and *.mlb files. 5 | 6 | ❗ **Deprecated** - this library bas been deprecated. I recommened using the [`SharpGL.Serialization`](https://github.com/dwmkerr/sharpgl/tree/master/source/SharpGL/Core/SharpGL.Serialization) package which supports Wavefront, Discreet, and more. 7 | 8 | Installation 9 | ------------ 10 | 11 | You can use Nuget to install the library. 12 | 13 | ```` 14 | PM> Install-Package FileFormatWavefront 15 | ```` 16 | 17 | Usage 18 | ----- 19 | 20 | To load data from a Wavefront Object File, use the ``FileFormatObj`` class: 21 | 22 | ````csharp 23 | // Use the File Format object to load the test data. 24 | bool loadTextureImages = true; 25 | var result = FileFormatObj.Load("MyFile.obj", loadTextureImages); 26 | ```` 27 | 28 | The object that is returned is a ``FileLoadResult`` object. The object contains a property ``Model`` which is the Scene of data loaded. 29 | 30 | Texture map images can optionally be loaded, but you can skip this if you are not going to need the image or will load it yourself (whether or not the image is loaded, a ``TextureMap`` object is still created with the image path as part of it). 31 | 32 | The object also contains a collection of ``Message`` objects describing any warnings or errors encountered loading the file. An example of iterating through the vertices and messages would look like this: 33 | 34 | ````csharp 35 | 36 | // Show each vertex. 37 | foreach(Vertex v in result.Model.Vertices) 38 | { 39 | Console.Write("{0} {1} {2}", v.x, v.y, v.z); 40 | } 41 | 42 | // Show each message. 43 | foreach (var message in result.Messages) 44 | { 45 | Console.WriteLine("{0}: {1}", message.MessageType, message.Details); 46 | Console.WriteLine("{0}: {1}", message.FileName, message.LineNumber); 47 | } 48 | 49 | ```` 50 | 51 | The ``Model`` property is of type ``Scene`` and contains the following members: 52 | 53 | * ``Vertices`` all vertex data. 54 | * ``Uvs`` all material coordinate data. 55 | * ``Normals`` all normal data. 56 | * ``Materials`` all material data. 57 | * ``Groups`` all objects (groups of faces). 58 | * ``UngroupedFaces`` all faces not grouped into objects. 59 | 60 | Associated *.mlb (material library) files are loaded automatically. If you want to load a material library only, you can use the ``FileFormatMlb`` class in the same way as the ``FileFormatObj`` class. 61 | -------------------------------------------------------------------------------- /build/BuildRelease.ps1: -------------------------------------------------------------------------------- 1 | # IMPORTANT: Make sure that the path to msbuild is correct! 2 | $msbuild = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe" 3 | if ((Test-Path $msbuild) -eq $false) { 4 | Write-Host "Cannot find msbuild at '$msbuild'." 5 | Break 6 | } 7 | 8 | # Load useful functions. 9 | . .\Resources\PowershellFunctions.ps1 10 | 11 | # Keep track of the 'release' folder location - it's the root of everything else. 12 | # We can also build paths to the key locations we'll use. 13 | $scriptParentPath = Split-Path -parent $MyInvocation.MyCommand.Definition 14 | $folderReleaseRoot = $scriptParentPath 15 | $folderSourceRoot = Split-Path -parent $folderReleaseRoot 16 | $folderSolutionsRoot = Join-Path $folderSourceRoot "source" 17 | $folderNuspecRoot = Join-Path $folderSourceRoot "build\nuspec" 18 | 19 | # Part 1 - Build the main libraries. 20 | Write-Host "Preparing to build the core library..." 21 | $solutionCoreLibraries = Join-Path $folderSolutionsRoot "FileFormatWavefront.sln" 22 | . $msbuild $solutionCoreLibraries /p:Configuration=Release /verbosity:minimal 23 | 24 | # Part 2 - Get the version number of the core library, use this to build the destination release folder. 25 | $folderBinaries = Join-Path $folderSolutionsRoot "FileFormatWavefront\bin\Release" 26 | $releaseVersion = [Reflection.Assembly]::LoadFile((Join-Path $folderBinaries "FileFormatWavefront.dll")).GetName().Version 27 | Write-Host "Built Core Library. Release Version: $releaseVersion" 28 | 29 | # Part 3 - Copy the core library to the release. 30 | $folderRelease = Join-Path $folderReleaseRoot $releaseVersion 31 | $folderReleaseCore = Join-Path $folderRelease "Core" 32 | EnsureEmptyFolderExists($folderReleaseCore) 33 | CopyItems (Join-Path $folderBinaries "*.*") $folderReleaseCore 34 | 35 | # Part 4 - Build the Nuget Package 36 | Write-Host "Preparing to build the Nuget Package..." 37 | $folderReleasePackage = Join-Path $folderRelease "Package" 38 | EnsureEmptyFolderExists($folderReleasePackage) 39 | $nuget = Join-Path $scriptParentPath "Resources\nuget.exe" 40 | 41 | CopyItems (Join-Path $folderReleaseCore "*.*") (Join-Path $folderNuspecRoot "FileFormatWavefront\lib\net40") 42 | . $nuget pack (Join-Path $folderNuspecRoot "FileFormatWavefront\FileFormatWavefront.nuspec") -Version $releaseVersion -OutputDirectory $folderReleasePackage 43 | 44 | # We're done! 45 | Write-Host "Successfully built version: $releaseVersion" -------------------------------------------------------------------------------- /build/Resources/PowershellFunctions.ps1: -------------------------------------------------------------------------------- 1 | # Copy items to a destination folder, creating the folder if needed. 2 | function CopyItems($source, $destinationFolder) { 3 | 4 | # Create the any folders or subfolders up to the destination that don't exist. 5 | EnsureFolderExists($destinationFolder) 6 | 7 | # Now copy the items. 8 | Copy-Item $source -Destination $destinationFolder 9 | } 10 | 11 | # Ensures that a folder exists. 12 | function EnsureFolderExists($folder) { 13 | 14 | # Create the any folders or subfolders up to the destination that don't exist. 15 | if (!(Test-Path -path $folder)) { 16 | New-Item $folder -Type Directory 17 | } 18 | } 19 | 20 | # Ensures that a folder exists and deletes anything in it. 21 | function EnsureEmptyFolderExists($folder) { 22 | EnsureFolderExists($folder) 23 | Remove-Item -Recurse -Force $folder 24 | EnsureFolderExists($folder) 25 | } -------------------------------------------------------------------------------- /build/Resources/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwmkerr/file-format-wavefront/42c1b22c718ecae55bcea3d48966800cc964c675/build/Resources/nuget.exe -------------------------------------------------------------------------------- /build/nuspec/FileFormatWavefront/FileFormatWavefront.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | FileFormatWavefront 5 | 0.0.1.1 6 | FileFormatWavefront 7 | Dave Kerr 8 | 9 | https://github.com/dwmkerr/file-format-wavefront 10 | false 11 | FileFormatWavefront is a simple class library that lets you load Wavefront *.obj or *.mlb files. 12 | FileFormatWavefront is a simple class library that lets you load Wavefront *.obj or *.mlb files. 13 | Copyright © Dave Kerr 2014 14 | 15 | -------------------------------------------------------------------------------- /build/nuspec/FileFormatWavefront/lib/net40/FileFormatWavefront.XML: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FileFormatWavefront 5 | 6 | 7 | 8 | 9 | Loads a scene into a FileLoadResult object from a Wavefromt *.obj file. 10 | All warning and error messages are in the returned object's 'Messages' collection. 11 | The actual scene data is in the returned object's 'Model' data. 12 | 13 | The path to the *.obj file. 14 | A containing the file load result. 15 | 16 | 17 | 18 | Internally used to reads the scene. 19 | 20 | The reader. 21 | The path. 22 | The file load result. 23 | 24 | 25 | 26 | The data separators, any valid value that can separate data in a line. 27 | 28 | 29 | 30 | 31 | A file loader for the Wavefront *.mtl file format. 32 | 33 | 34 | 35 | 36 | Loads materials from the specified stream. 37 | 38 | The path. 39 | The results of the file load. 40 | 41 | 42 | 43 | Read the material data from the specified stream. 44 | 45 | The reader. 46 | The path. This can be null - it is only used for recording diagnostic information. 47 | The results of the file load. 48 | 49 | 50 | 51 | Represents the results of a file loading operation. 52 | 53 | The type of the model. 54 | 55 | 56 | 57 | Initializes a new instance of the class. 58 | 59 | The messages. 60 | The model. 61 | 62 | 63 | 64 | Gets the loaded model. 65 | 66 | 67 | 68 | 69 | Gets the file load messages. 70 | 71 | 72 | 73 | 74 | This helper class aids us in dealing with Wavefront line data. 75 | 76 | 77 | 78 | 79 | Represents a colour. 80 | 81 | 82 | 83 | 84 | The red component. 85 | 86 | 87 | 88 | 89 | The green component. 90 | 91 | 92 | 93 | 94 | The blue component. 95 | 96 | 97 | 98 | 99 | The alpha component. 100 | 101 | 102 | 103 | 104 | Represents a face. 105 | 106 | 107 | 108 | 109 | Gets the material. 110 | 111 | 112 | 113 | 114 | Gets the indices. 115 | 116 | 117 | 118 | 119 | Represents a group of faces. 120 | 121 | 122 | 123 | 124 | Gets the group names. A group doesn't necessarily have to have any names. 125 | Groups can also share names (i.e one group can be part of another). 126 | 127 | 128 | 129 | 130 | Gets the smoothing group. 131 | 132 | 133 | 134 | 135 | Gets the faces. 136 | 137 | 138 | 139 | 140 | Represents an index. 141 | 142 | 143 | 144 | 145 | The vertex index. 146 | 147 | 148 | 149 | 150 | The uv index. 151 | 152 | 153 | 154 | 155 | The normal index. 156 | 157 | 158 | 159 | 160 | Represents a material. 161 | 162 | 163 | 164 | 165 | Gets the name. 166 | 167 | 168 | 169 | 170 | Gets the ambient. 171 | 172 | 173 | 174 | 175 | Gets the diffuse. 176 | 177 | 178 | 179 | 180 | Gets the specular. 181 | 182 | 183 | 184 | 185 | Gets the shininess. 186 | 187 | 188 | 189 | 190 | Gets the transparency. 191 | 192 | 193 | 194 | 195 | Gets the ambient texture map. 196 | 197 | 198 | 199 | 200 | Gets the diffuse texture map. 201 | 202 | 203 | 204 | 205 | Gets the specular texture map. 206 | 207 | 208 | 209 | 210 | Gets the specular highlight texture map. 211 | 212 | 213 | 214 | 215 | Gets the alpha texture map. 216 | 217 | 218 | 219 | 220 | Gets the bump texture map. 221 | 222 | 223 | 224 | 225 | Gets the illumination model. 226 | See the specification at http://paulbourke.net/dataformats/mtl/ for details on this value. 227 | 228 | 229 | The illumination model. 230 | 231 | 232 | 233 | 234 | Represent a scene of data loaded from an *.obj file. 235 | 236 | 237 | 238 | 239 | Gets the vertices. 240 | 241 | 242 | 243 | 244 | Gets the uvs. 245 | 246 | 247 | 248 | 249 | Gets the normals. 250 | 251 | 252 | 253 | 254 | Gets the faces which don't belong to any groups. 255 | 256 | 257 | 258 | 259 | Gets the groups. 260 | 261 | 262 | 263 | 264 | Gets the materials. 265 | 266 | 267 | 268 | 269 | Represents a message of a specific severity relating to the loading of data from a file. 270 | 271 | 272 | 273 | 274 | The message type. 275 | 276 | 277 | 278 | 279 | The file name. May be null. 280 | 281 | 282 | 283 | 284 | The line number. May be null. 285 | 286 | 287 | 288 | 289 | The actual message details. 290 | 291 | 292 | 293 | 294 | The exception associated with the message, may be null. 295 | 296 | 297 | 298 | 299 | Initializes a new instance of the class. 300 | 301 | Type of the message. 302 | Name of the file. 303 | The line number. 304 | The message details. 305 | The exception. 306 | 307 | 308 | 309 | Gets the type of the message. 310 | 311 | 312 | 313 | 314 | Gets the name of the file. 315 | 316 | 317 | 318 | 319 | Gets the line number. 320 | 321 | 322 | 323 | 324 | Gets the details. 325 | 326 | 327 | 328 | 329 | Gets the exception. 330 | 331 | 332 | 333 | 334 | Represents a message type for a loading message. 335 | 336 | 337 | 338 | 339 | A warning message - indicates something may be wrong with the data. 340 | 341 | 342 | 343 | 344 | An error message - indicates that we KNOW something is wrong with the data. 345 | 346 | 347 | 348 | 349 | Represents a texture map. 350 | 351 | 352 | 353 | 354 | Gets the path to the texture file. 355 | 356 | 357 | 358 | 359 | Represents a texture coordinate. 360 | 361 | 362 | 363 | 364 | The u coordinate. 365 | 366 | 367 | 368 | 369 | The v coordinate. 370 | 371 | 372 | 373 | 374 | Represents a vertex. 375 | 376 | 377 | 378 | 379 | The x coordinate. 380 | 381 | 382 | 383 | 384 | The y cooredinate. 385 | 386 | 387 | 388 | 389 | The z coordinate. 390 | 391 | 392 | 393 | 394 | String extensions used internally just make make the comparison with a string 395 | and a line type a little cleaner. 396 | 397 | 398 | 399 | 400 | Determines whether this string matches the specified line type. 401 | 402 | The string. 403 | The line type. 404 | True if the string matches the linetype. 405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /build/nuspec/FileFormatWavefront/lib/net40/FileFormatWavefront.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwmkerr/file-format-wavefront/42c1b22c718ecae55bcea3d48966800cc964c675/build/nuspec/FileFormatWavefront/lib/net40/FileFormatWavefront.dll -------------------------------------------------------------------------------- /models/Motherboard/Motherboard.mtl: -------------------------------------------------------------------------------- 1 | # Max2Mtl Version 4.0 Mar 10th, 2001 2 | # 3 | # Multi/Sub Material__35 (8) to come 4 | # 5 | newmtl 01_-_Standard 6 | Ka 0.6 0.6 0.6 7 | Kd 0.6 0.6 0.6 8 | Ks 0.9 0.9 0.9 9 | d 1.0 10 | Ns 0.0 11 | illum 2 12 | # 13 | newmtl Material__147 14 | Ka 0.8 0.8 0.8 15 | Kd 0.8 0.8 0.8 16 | Ks 0.9 0.9 0.9 17 | d 1.0 18 | Ns 0.0 19 | illum 2 20 | # 21 | newmtl Material__148 22 | Ka 0.8 0.6 0.3 23 | Kd 0.8 0.6 0.3 24 | Ks 0.9 0.9 0.9 25 | d 1.0 26 | Ns 15.0 27 | illum 2 28 | # 29 | newmtl Material__151 30 | Ka 0.9 0.9 0.9 31 | Kd 0.9 0.9 0.9 32 | Ks 0.9 0.9 0.9 33 | d 1.0 34 | Ns 10.8 35 | illum 2 36 | # 37 | newmtl Material__149 38 | Ka 0.2 0.2 0.5 39 | Kd 0.2 0.2 0.5 40 | Ks 0.9 0.9 0.9 41 | d 1.0 42 | Ns 2.6 43 | illum 2 44 | # 45 | newmtl Material__154 46 | Ka 0.8 0.8 0.8 47 | Kd 0.8 0.8 0.8 48 | Ks 0.9 0.9 0.9 49 | d 1.0 50 | Ns 0.0 51 | illum 2 52 | # 53 | newmtl Material__155 54 | Ka 0.2 0.3 0.2 55 | Kd 0.2 0.3 0.2 56 | Ks 0.9 0.9 0.9 57 | d 1.0 58 | Ns 0.0 59 | illum 2 60 | # 61 | newmtl Material__150 62 | Ka 0.1 0.1 0.1 63 | Kd 0.1 0.1 0.1 64 | Ks 0.9 0.9 0.9 65 | d 1.0 66 | Ns 0.0 67 | illum 2 68 | # 69 | # Multi/Sub Material__35 done 70 | # 71 | # EOF 72 | -------------------------------------------------------------------------------- /models/Motherboard/Textures/mb-ps-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwmkerr/file-format-wavefront/42c1b22c718ecae55bcea3d48966800cc964c675/models/Motherboard/Textures/mb-ps-2.png -------------------------------------------------------------------------------- /models/Motherboard/Textures/p4sba-mb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwmkerr/file-format-wavefront/42c1b22c718ecae55bcea3d48966800cc964c675/models/Motherboard/Textures/p4sba-mb.jpg -------------------------------------------------------------------------------- /models/Palm/Bottom_T.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwmkerr/file-format-wavefront/42c1b22c718ecae55bcea3d48966800cc964c675/models/Palm/Bottom_T.jpg -------------------------------------------------------------------------------- /models/Palm/Bottom_Trunk.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwmkerr/file-format-wavefront/42c1b22c718ecae55bcea3d48966800cc964c675/models/Palm/Bottom_Trunk.bmp -------------------------------------------------------------------------------- /models/Palm/Date Palm.mtl: -------------------------------------------------------------------------------- 1 | newmtl Bottom_Trunk 2 | Ka 0 0 0 3 | Ns 1 4 | Ks 1 1 1 5 | Kd 0.440946 0.417621 0.371041 6 | map_Kd Bottom_Trunk.bmp 7 | -------------------------------------------------------------------------------- /models/Palm/Source.md: -------------------------------------------------------------------------------- 1 | http://tf3dm.com/3d-model/date-palm-2286.html -------------------------------------------------------------------------------- /source/FileFormatWavefront.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileFormatWavefront", "FileFormatWavefront\FileFormatWavefront.csproj", "{878EC1C4-D57E-4749-AE1F-41F393977655}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WavefrontObjectLoader", "WavefrontObjectLoader\WavefrontObjectLoader.csproj", "{EA7A4D48-969F-4E19-8BAB-BBC051F1C3EE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {878EC1C4-D57E-4749-AE1F-41F393977655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {878EC1C4-D57E-4749-AE1F-41F393977655}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {878EC1C4-D57E-4749-AE1F-41F393977655}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {878EC1C4-D57E-4749-AE1F-41F393977655}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {EA7A4D48-969F-4E19-8BAB-BBC051F1C3EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {EA7A4D48-969F-4E19-8BAB-BBC051F1C3EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {EA7A4D48-969F-4E19-8BAB-BBC051F1C3EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {EA7A4D48-969F-4E19-8BAB-BBC051F1C3EE}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /source/FileFormatWavefront/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FileFormatWavefront.Extensions 4 | { 5 | /// 6 | /// String extensions used internally just make make the comparison with a string 7 | /// and a line type a little cleaner. 8 | /// 9 | internal static class StringExtensions 10 | { 11 | /// 12 | /// Determines whether this string matches the specified line type. 13 | /// 14 | /// The string. 15 | /// The line type. 16 | /// True if the string matches the linetype. 17 | public static bool IsLineType(this string @this, string lineType) 18 | { 19 | return string.Equals(@this, lineType, StringComparison.InvariantCultureIgnoreCase); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/FileFormatMtl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Linq; 6 | using FileFormatWavefront.Extensions; 7 | using FileFormatWavefront.Internals; 8 | using FileFormatWavefront.Model; 9 | 10 | namespace FileFormatWavefront 11 | { 12 | /// 13 | /// A file loader for the Wavefront *.mtl file format. 14 | /// 15 | public static class FileFormatMtl 16 | { 17 | /// 18 | /// Loads materials from the specified stream. 19 | /// 20 | /// The path. 21 | /// if set to true texture images 22 | /// will be loaded and set in the property. 23 | /// The results of the file load. 24 | public static FileLoadResult> Load(string path, bool loadTextureImages) 25 | { 26 | // Create a streamreader and read the data. 27 | using(var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) 28 | using (var streamReader = new StreamReader(stream)) 29 | return Read(streamReader, path, loadTextureImages); 30 | } 31 | 32 | /// 33 | /// Read the material data from the specified stream. 34 | /// 35 | /// The reader. 36 | /// The path. This can be null - it is only used for recording diagnostic information. 37 | /// if set to true [load texture images]. 38 | /// 39 | /// The results of the file load. 40 | /// 41 | private static FileLoadResult> Read(TextReader reader, string path, bool loadTextureImages) 42 | { 43 | // The model we are loading is a list of materials. During loading, we'll keep 44 | // track of messages that may be useful to consumers. 45 | var materials = new List(); 46 | var messages = new List(); 47 | 48 | // As we load, we're enriching the data of a Material object. 49 | Material currentMaterial = null; 50 | 51 | // Go through each line, keeping track of the line number. 52 | var lineNumberCounter = 0; 53 | string line; 54 | while ((line = reader.ReadLine()) != null) 55 | { 56 | ++lineNumberCounter; 57 | 58 | // Strip any comments from the line and skip empty lines. 59 | line = LineData.StripComments(line); 60 | if (string.IsNullOrWhiteSpace(line)) 61 | continue; 62 | 63 | // Try and read the line type and data. 64 | string lineType, lineData; 65 | if (LineData.TryReadLineType(line, out lineType, out lineData) == false) 66 | continue; 67 | 68 | if (lineType.IsLineType(LineTypeNewMaterial)) 69 | { 70 | // Add a new material to the list, store it as the current one and set the name. 71 | currentMaterial = new Material { Name = lineData }; 72 | materials.Add(currentMaterial); 73 | } 74 | else if (currentMaterial != null) 75 | { 76 | if (lineType.IsLineType(LineTypeMaterialAmbient)) 77 | { 78 | currentMaterial.Ambient = ReadColour(lineData); 79 | } 80 | else if (lineType.IsLineType(LineTypeMaterialDiffuse)) 81 | { 82 | currentMaterial.Diffuse = ReadColour(lineData); 83 | } 84 | else if (lineType.IsLineType(LineTypeMaterialSpecular)) 85 | { 86 | currentMaterial.Specular = ReadColour(lineData); 87 | } 88 | else if (lineType.IsLineType(LineTypeMaterialShininess)) 89 | { 90 | currentMaterial.Shininess = float.Parse(lineData); 91 | } 92 | else if (lineType.IsLineType(LineTypeOpticalDensity)) 93 | { 94 | currentMaterial.OpticalDensity = float.Parse(lineData); 95 | } 96 | else if (lineType.IsLineType(LineTypeBumpStrength)) 97 | { 98 | currentMaterial.BumpStrength = float.Parse(lineData); 99 | } 100 | else if (lineType.IsLineType(LineTypeTextureMapAmbient)) 101 | { 102 | currentMaterial.TextureMapAmbient = ReadTextureMap(path, lineNumberCounter, messages, lineData, loadTextureImages); 103 | } 104 | else if (lineType.IsLineType(LineTypeTextureMapDiffuse)) 105 | { 106 | currentMaterial.TextureMapDiffuse = ReadTextureMap(path, lineNumberCounter, messages, lineData, loadTextureImages); 107 | } 108 | else if (lineType.IsLineType(LineTypeTextureMapSpecular)) 109 | { 110 | currentMaterial.TextureMapSpecular = ReadTextureMap(path, lineNumberCounter, messages, lineData, loadTextureImages); 111 | } 112 | else if (lineType.IsLineType(LineTypeTextureMapSpecularHighlight)) 113 | { 114 | currentMaterial.TextureMapSpecularHighlight = ReadTextureMap(path, lineNumberCounter, messages, lineData, loadTextureImages); 115 | } 116 | else if (lineType.IsLineType(LineTypeTextureMapAlpha)) 117 | { 118 | currentMaterial.TextureMapAlpha = ReadTextureMap(path, lineNumberCounter, messages, lineData, loadTextureImages); 119 | } 120 | else if (lineType.IsLineType(LineTypeTextureMapBump)) 121 | { 122 | currentMaterial.TextureMapBump = ReadTextureMap(path, lineNumberCounter, messages, lineData, loadTextureImages); 123 | } 124 | else if (lineType.IsLineType(LineTypeDissolve) || lineType.IsLineType(LineTypeTransparent)) 125 | { 126 | // Read the transparency. 127 | currentMaterial.Transparency = float.Parse(lineData); 128 | } 129 | else if(lineType.IsLineType(LineTypeIlluminationModel)) 130 | { 131 | currentMaterial.IlluminationModel = int.Parse(lineData); 132 | } 133 | else 134 | { 135 | // Anything we encounter here we don't understand. 136 | messages.Add(new Message(MessageType.Warning, path, lineNumberCounter, 137 | string.Format("Skipped unknown line type '{0}'.", lineType))); 138 | } 139 | } 140 | else 141 | { 142 | // Anything we encounter here we don't understand. 143 | messages.Add(new Message(MessageType.Warning, path, lineNumberCounter, 144 | string.Format("Skipped unknown or out of context line type '{0}'.", lineType))); 145 | } 146 | 147 | } 148 | 149 | // Return the model and messages as a file load result. 150 | return new FileLoadResult>(materials, messages); 151 | } 152 | 153 | private static TextureMap ReadTextureMap(string fileName, int lineNumber, List messages, string lineData, bool loadTextureBitmaps) 154 | { 155 | // TODO: Support texture map options. http://paulbourke.net/dataformats/mtl/ 156 | var textureMap = new TextureMap { Path = lineData }; 157 | if(loadTextureBitmaps == false) 158 | return textureMap; 159 | 160 | // If we have quotes, we've got a way to get the path explicitly. 161 | string textureFileName; 162 | var quotePos = lineData.IndexOf('"'); 163 | if(quotePos != -1) 164 | { 165 | var quoteEndPos = lineData.IndexOf('"', quotePos + 1); 166 | if(quoteEndPos == -1) 167 | { 168 | messages.Add(new Message(MessageType.Error, fileName, lineNumber, 169 | "The texture file is specified incorrectly.")); 170 | return null; 171 | } 172 | textureFileName = lineData.Substring(quoteEndPos + 1, quoteEndPos - quotePos - 1); 173 | } 174 | else 175 | { 176 | // If we don't have quotes, we'll have to assume that the last part of the line is the texture. 177 | textureFileName = lineData.Split(' ').Last(); 178 | } 179 | 180 | try 181 | { 182 | var path = Path.Combine(Path.GetDirectoryName(fileName), textureFileName); 183 | textureMap.Image = Image.FromFile(path); 184 | } 185 | catch (Exception exception) 186 | { 187 | messages.Add(new Message(MessageType.Error, fileName, lineNumber, 188 | string.Format("Failed to load the texture map image file '{0}'", lineData), exception)); 189 | } 190 | return textureMap; 191 | } 192 | 193 | private static Colour ReadColour(string lineData) 194 | { 195 | 196 | // Get the colour parts. 197 | var colourParts = lineData.Split(new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 198 | return new Colour 199 | { 200 | r = float.Parse(colourParts[0]), 201 | g = float.Parse(colourParts[1]), 202 | b = float.Parse(colourParts[2]), 203 | a = colourParts.Length == 4 ? float.Parse(colourParts[3]) : 1.0f, 204 | }; 205 | } 206 | 207 | private const string LineTypeNewMaterial = "newmtl"; 208 | private const string LineTypeMaterialAmbient = "Ka"; 209 | private const string LineTypeMaterialDiffuse = "Kd"; 210 | private const string LineTypeMaterialSpecular = "Ks"; 211 | private const string LineTypeMaterialShininess = "Ns"; 212 | private const string LineTypeOpticalDensity = "Ni"; 213 | private const string LineTypeBumpStrength = "Km"; 214 | private const string LineTypeDissolve = "d"; 215 | private const string LineTypeTransparent = "Tr"; 216 | private const string LineTypeTextureMapAmbient = "map_Ka"; 217 | private const string LineTypeTextureMapDiffuse = "map_Kd"; 218 | private const string LineTypeTextureMapSpecular = "map_Ks"; 219 | private const string LineTypeTextureMapSpecularHighlight = "map_Ns"; 220 | private const string LineTypeTextureMapAlpha = "map_d"; 221 | private const string LineTypeTextureMapBump = "map_bump"; 222 | private const string LineTypeIlluminationModel = "illum"; 223 | 224 | } 225 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/FileFormatObj.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using FileFormatWavefront.Extensions; 6 | using FileFormatWavefront.Internals; 7 | using FileFormatWavefront.Model; 8 | 9 | namespace FileFormatWavefront 10 | { 11 | public static class FileFormatObj 12 | { 13 | 14 | /// 15 | /// Loads a scene into a FileLoadResult object from a Wavefromt *.obj file. 16 | /// All warning and error messages are in the returned object's 'Messages' collection. 17 | /// The actual scene data is in the returned object's 'Model' data. 18 | /// 19 | /// The path to the *.obj file. 20 | /// if set to true texture images 21 | /// will be loaded and set in the property. 22 | /// 23 | /// A containing the file load result. 24 | /// 25 | public static FileLoadResult Load(string path, bool loadTextureImages) 26 | { 27 | // Create a stream reader. 28 | using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) 29 | { 30 | using (var reader = new StreamReader(stream)) 31 | { 32 | // Read the scene. 33 | return ReadScene(reader, path, loadTextureImages); 34 | } 35 | } 36 | } 37 | 38 | /// 39 | /// Internally used to reads the scene. 40 | /// 41 | /// The reader. 42 | /// The path. 43 | /// if set to true [load texture images]. 44 | /// 45 | /// The file load result. 46 | /// 47 | private static FileLoadResult ReadScene(StreamReader reader, string path, bool loadTextureImages) 48 | { 49 | // Keep track of messages and the raw data we will use to build a scene. 50 | var messages = new List(); 51 | var uvs = new List(); 52 | var normals = new List(); 53 | var vertices = new List(); 54 | var interimFaces = new List(); 55 | var materials = new List(); 56 | var groups = new List(); 57 | string objectName = null; 58 | 59 | // State changing data is loaded as we go through the file - once loaded, state changing 60 | // data applies to all subsequent elements until it is explicitly changed by introducing 61 | // new state changing data. 62 | Group currentGroup = null; 63 | string currentMaterialName = null; 64 | 65 | // Read line by line. 66 | string line; 67 | int lineNumberCounter = 0; 68 | while ((line = reader.ReadLine()) != null) 69 | { 70 | ++lineNumberCounter; 71 | 72 | // Strip any comments from the line and skip empty lines. 73 | line = LineData.StripComments(line); 74 | if (string.IsNullOrWhiteSpace(line)) 75 | continue; 76 | 77 | // Try and read the line type and data. 78 | string lineType, lineData; 79 | if (LineData.TryReadLineType(line, out lineType, out lineData) == false) 80 | continue; 81 | 82 | // Read texture coordinates. 83 | if (lineType.IsLineType(LineTypeTextureCoordinate)) 84 | { 85 | try 86 | { 87 | // Split the line data into texture coordinates. 88 | var dataStrings = lineData.Split(dataSeparators, StringSplitOptions.RemoveEmptyEntries); 89 | 90 | // Add the UV. 91 | uvs.Add(new UV 92 | { 93 | u = float.Parse(dataStrings[0]), 94 | v = float.Parse(dataStrings[1]) 95 | }); 96 | } 97 | catch (Exception exception) 98 | { 99 | messages.Add(new Message(MessageType.Error, path, lineNumberCounter, 100 | "There was an error reading the texture coordinate data.", exception)); 101 | } 102 | } 103 | else if (lineType.IsLineType(LineTypeNormalCoordinate)) 104 | { 105 | try 106 | { 107 | // Split the line data into normal coordinates. 108 | var dataStrings = lineData.Split(dataSeparators, StringSplitOptions.RemoveEmptyEntries); 109 | normals.Add(new Vertex 110 | { 111 | x = float.Parse(dataStrings[0]), 112 | y = float.Parse(dataStrings[1]), 113 | z = float.Parse(dataStrings[2]) 114 | }); 115 | } 116 | catch (Exception exception) 117 | { 118 | messages.Add(new Message(MessageType.Error, path, lineNumberCounter, 119 | "There was an error reading the normal data.", exception)); 120 | } 121 | } 122 | else if (lineType.IsLineType(LineTypeVertex)) 123 | { 124 | try 125 | { 126 | // Split the line data into vertex coordinates. 127 | var dataStrings = lineData.Split(dataSeparators, StringSplitOptions.RemoveEmptyEntries); 128 | vertices.Add(new Vertex 129 | { 130 | x = float.Parse(dataStrings[0]), 131 | y = float.Parse(dataStrings[1]), 132 | z = float.Parse(dataStrings[2]) 133 | }); 134 | } 135 | catch (Exception exception) 136 | { 137 | messages.Add(new Message(MessageType.Error, path, lineNumberCounter, 138 | "There was an error reading the vertex data.", exception)); 139 | } 140 | } 141 | else if (lineType.IsLineType(LineTypeFace)) 142 | { 143 | try 144 | { 145 | var indices = new List(); 146 | 147 | // Split the line data into index strings. 148 | var indexStrings = lineData.Split(dataSeparators, StringSplitOptions.RemoveEmptyEntries); 149 | foreach (var indexString in indexStrings) 150 | { 151 | // Split the parts. 152 | var parts = indexString.Split(new[] { '/' }, StringSplitOptions.None); 153 | var vertex = MapIndex(vertices.Count, int.Parse(parts[0])); 154 | var uv = (parts.Length > 1 && parts[1].Length > 0) ? (int?)MapIndex(uvs.Count, int.Parse(parts[1])) : null; 155 | var normal = (parts.Length > 2 && parts[2].Length > 0) ? (int?)MapIndex(normals.Count, int.Parse(parts[2])) : null; 156 | indices.Add(new Index 157 | { 158 | vertex = vertex, 159 | uv = uv, 160 | normal = normal 161 | }); 162 | } 163 | interimFaces.Add(new InterimFace 164 | { 165 | materialName = currentMaterialName, 166 | indices = indices, 167 | group = currentGroup 168 | }); 169 | } 170 | catch (Exception exception) 171 | { 172 | messages.Add(new Message(MessageType.Error, path, lineNumberCounter, 173 | "There was an error reading the index data.", exception)); 174 | } 175 | } 176 | else if (lineType.IsLineType(LineTypeMaterialLibrary)) 177 | { 178 | // The material file path is the line data. 179 | var materialPath = lineData; 180 | 181 | // If the path is relative, make it absolute based on the current directory (if we've been passed a path). 182 | if (Path.IsPathRooted(lineData) == false && path != null) 183 | materialPath = Path.Combine(Path.GetDirectoryName(path), materialPath); 184 | 185 | // Read the material file. 186 | try 187 | { 188 | var fileLoadResult = FileFormatMtl.Load(materialPath, loadTextureImages); 189 | materials.AddRange(fileLoadResult.Model); 190 | messages.AddRange(fileLoadResult.Messages); 191 | } 192 | catch (Exception exception) 193 | { 194 | messages.Add(new Message(MessageType.Error, path, lineNumberCounter, 195 | string.Format("Failed to load material file '{0}'.", materialPath), exception)); 196 | } 197 | } 198 | else if (lineType.IsLineType(LineTypeUseMaterial)) 199 | { 200 | // The material name is simply the line data. 201 | currentMaterialName = lineData; 202 | } 203 | else if(lineType.IsLineType(LineTypeGroup)) 204 | { 205 | // Create a new group. 206 | var groupNames = lineData.Split(dataSeparators, StringSplitOptions.RemoveEmptyEntries); 207 | currentGroup = new Group(groupNames); 208 | groups.Add(currentGroup); 209 | } 210 | else if(lineType.IsLineType(LineTypeSmoothingGroup)) 211 | { 212 | // If we have no current group, we cannot set a smoothing group. 213 | if(currentGroup == null) 214 | { 215 | messages.Add(new Message(MessageType.Warning, path, lineNumberCounter, 216 | string.Format("Cannot set smoothing group '{0}' as the current context has no group.", lineData))); 217 | } 218 | else 219 | { 220 | // The smoothing group is an int, if we can get it. 221 | int smoothingGroup; 222 | if(int.TryParse(lineData, out smoothingGroup)) 223 | currentGroup.SetSmoothingGroup(smoothingGroup); 224 | currentGroup.SetSmoothingGroup(null); 225 | } 226 | } 227 | else if(lineType.IsLineType(LineTypeObjectName)) 228 | { 229 | // Set the object name, warning if it's already set. 230 | if (objectName != null) 231 | { 232 | messages.Add(new Message(MessageType.Warning, path, lineNumberCounter, 233 | string.Format("An object name statement to set the name to '{0}' will overwrite the current object name '{1}'.", lineData, objectName))); 234 | } 235 | objectName = lineData; 236 | } 237 | else 238 | { 239 | messages.Add(new Message(MessageType.Warning, path, lineNumberCounter, 240 | string.Format("Skipped unknown line type '{0}'.", lineType))); 241 | } 242 | } 243 | 244 | // Currently we don't have faces, just indexes and material names. But now that we've loaded 245 | // the entire file, we can map the material names to the actual materials. 246 | var ungroupedFaces = new List(); 247 | foreach (var interimFace in interimFaces) 248 | { 249 | // If we have a material named but not in the set of materials, warn. 250 | var material = materials.FirstOrDefault(m => m.Name == interimFace.materialName); 251 | if (material == null) 252 | messages.Add(new Message(MessageType.Warning, path, lineNumberCounter, 253 | string.Format("Material '{0}' is referenced for a face, but not included in any material files.", interimFace.materialName))); 254 | 255 | // If the face is grouped, add it to the group. Otherwise add it to the ungrouped faces. 256 | var face = new Face(material, interimFace.indices); 257 | if (interimFace.group != null) 258 | interimFace.group.AddFace(face); 259 | else 260 | ungroupedFaces.Add(face); 261 | } 262 | 263 | return new FileLoadResult(new Scene(vertices, uvs, normals, ungroupedFaces, groups, materials, objectName), messages); 264 | } 265 | 266 | /// 267 | /// Maps an index defined in the file. 268 | /// Indexes are 1 based, so we fix this. Also, if they're negative they're 1 based 269 | /// but going backwards from the last element - which is why we need the . 270 | /// 271 | /// The current element count. 272 | /// The index. 273 | /// 274 | private static int MapIndex(int currentElementCount, int index) 275 | { 276 | return (index > 0) ? index - 1 : currentElementCount + index; 277 | } 278 | 279 | private const string LineTypeTextureCoordinate = "vt"; 280 | private const string LineTypeNormalCoordinate = "vn"; 281 | private const string LineTypeVertex = "v"; 282 | private const string LineTypeFace = "f"; 283 | private const string LineTypeMaterialLibrary = "mtllib"; 284 | private const string LineTypeUseMaterial = "usemtl"; 285 | private const string LineTypeGroup = "g"; 286 | private const string LineTypeSmoothingGroup = "s"; 287 | private const string LineTypeObjectName = "o"; 288 | 289 | /// 290 | /// The data separators, any valid value that can separate data in a line. 291 | /// 292 | private static readonly char[] dataSeparators = new[] { ' ' }; 293 | 294 | internal class InterimFace 295 | { 296 | public Group group; 297 | public string materialName; 298 | public List indices; 299 | public bool smoothShading; 300 | } 301 | } 302 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/FileFormatWavefront.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {878EC1C4-D57E-4749-AE1F-41F393977655} 8 | Library 9 | Properties 10 | FileFormatWavefront 11 | FileFormatWavefront 12 | v4.0 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 | bin\Release\FileFormatWavefront.XML 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /source/FileFormatWavefront/FileLoadResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace FileFormatWavefront 5 | { 6 | /// 7 | /// Represents the results of a file loading operation. 8 | /// 9 | /// The type of the model. 10 | public class FileLoadResult 11 | { 12 | private readonly TModel model; 13 | private readonly List messages; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The messages. 19 | /// The model. 20 | internal FileLoadResult(TModel model, List messages) 21 | { 22 | this.model = model; 23 | this.messages = messages; 24 | } 25 | 26 | /// 27 | /// Gets the loaded model. 28 | /// 29 | public TModel Model 30 | { 31 | get { return model; } 32 | } 33 | 34 | /// 35 | /// Gets the file load messages. 36 | /// 37 | public ReadOnlyCollection Messages 38 | { 39 | get { return messages.AsReadOnly(); } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Internals/LineData.cs: -------------------------------------------------------------------------------- 1 | namespace FileFormatWavefront.Internals 2 | { 3 | /// 4 | /// This helper class aids us in dealing with Wavefront line data. 5 | /// 6 | internal static class LineData 7 | { 8 | public static string StripComments(string line) 9 | { 10 | // If there's a comment character, remove everything that follows it. 11 | var commentStartIndex = line.IndexOf('#'); 12 | return commentStartIndex == -1 ? line : line.Substring(0, commentStartIndex); 13 | } 14 | 15 | public static bool TryReadLineType(string line, out string lineType, out string lineData) 16 | { 17 | // The line type is the first set of characters in the line, such as: 18 | // vt, vn, v etc. 19 | // Remember - lines can have whitespace. 20 | var strippedLine = line.TrimStart(); 21 | 22 | // Loop through the string until we find the first whitespace character - everything before it 23 | // is the line type. 24 | for(var i=0; i 6 | /// Represents a message of a specific severity relating to the loading of data from a file. 7 | /// 8 | public class Message 9 | { 10 | /// 11 | /// The message type. 12 | /// 13 | private readonly MessageType messageType; 14 | 15 | /// 16 | /// The file name. May be null. 17 | /// 18 | private readonly string fileName; 19 | 20 | /// 21 | /// The line number. May be null. 22 | /// 23 | private readonly int? lineNumber; 24 | 25 | /// 26 | /// The actual message details. 27 | /// 28 | private readonly string details; 29 | 30 | /// 31 | /// The exception associated with the message, may be null. 32 | /// 33 | private readonly Exception exception; 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// Type of the message. 39 | /// Name of the file. 40 | /// The line number. 41 | /// The message details. 42 | /// The exception. 43 | public Message(MessageType messageType, string fileName, int? lineNumber, string details, Exception exception = null) 44 | { 45 | this.messageType = messageType; 46 | this.fileName = fileName; 47 | this.lineNumber = lineNumber; 48 | this.details = details; 49 | this.exception = exception; 50 | } 51 | 52 | /// 53 | /// Gets the type of the message. 54 | /// 55 | public MessageType MessageType 56 | { 57 | get { return messageType; } 58 | } 59 | 60 | /// 61 | /// Gets the name of the file. 62 | /// 63 | public string FileName 64 | { 65 | get { return fileName; } 66 | } 67 | 68 | /// 69 | /// Gets the line number. 70 | /// 71 | public int? LineNumber 72 | { 73 | get { return lineNumber; } 74 | } 75 | 76 | /// 77 | /// Gets the details. 78 | /// 79 | public string Details 80 | { 81 | get { return details; } 82 | } 83 | 84 | /// 85 | /// Gets the exception. 86 | /// 87 | public Exception Exception 88 | { 89 | get { return exception; } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/MessageType.cs: -------------------------------------------------------------------------------- 1 | namespace FileFormatWavefront 2 | { 3 | /// 4 | /// Represents a message type for a loading message. 5 | /// 6 | public enum MessageType 7 | { 8 | /// 9 | /// A warning message - indicates something may be wrong with the data. 10 | /// 11 | Warning, 12 | 13 | /// 14 | /// An error message - indicates that we KNOW something is wrong with the data. 15 | /// 16 | Error 17 | } 18 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/Colour.cs: -------------------------------------------------------------------------------- 1 | namespace FileFormatWavefront.Model 2 | { 3 | /// 4 | /// Represents a colour. 5 | /// 6 | public struct Colour 7 | { 8 | /// 9 | /// The red component. 10 | /// 11 | public float r; 12 | 13 | /// 14 | /// The green component. 15 | /// 16 | public float g; 17 | 18 | /// 19 | /// The blue component. 20 | /// 21 | public float b; 22 | 23 | /// 24 | /// The alpha component. 25 | /// 26 | public float a; 27 | } 28 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/Face.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace FileFormatWavefront.Model 5 | { 6 | /// 7 | /// Represents a face. 8 | /// 9 | public class Face 10 | { 11 | private readonly List indices; 12 | private readonly Material material; 13 | 14 | internal Face(Material material, List indices) 15 | { 16 | this.material = material; 17 | this.indices = indices; 18 | } 19 | 20 | /// 21 | /// Gets the material. 22 | /// 23 | public Material Material 24 | { 25 | get { return material; } 26 | } 27 | 28 | 29 | /// 30 | /// Gets the indices. 31 | /// 32 | public ReadOnlyCollection Indices 33 | { 34 | get { return indices.AsReadOnly(); } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/Group.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace FileFormatWavefront.Model 8 | { 9 | /// 10 | /// Represents a group of faces. 11 | /// 12 | public class Group 13 | { 14 | private readonly List names = new List(); 15 | private readonly List faces = new List(); 16 | private int? smoothingGroup; 17 | 18 | internal Group(IEnumerable names) 19 | { 20 | this.names.AddRange(names); 21 | } 22 | 23 | internal void SetSmoothingGroup(int? smoothingGroup) 24 | { 25 | this.smoothingGroup = smoothingGroup; 26 | } 27 | internal void AddFace(Face face) 28 | { 29 | faces.Add(face); 30 | } 31 | 32 | /// 33 | /// Gets the group names. A group doesn't necessarily have to have any names. 34 | /// Groups can also share names (i.e one group can be part of another). 35 | /// 36 | public List Names 37 | { 38 | get { return names; } 39 | } 40 | 41 | /// 42 | /// Gets the smoothing group. 43 | /// 44 | public int? SmoothingGroup 45 | { 46 | get { return smoothingGroup; } 47 | } 48 | 49 | /// 50 | /// Gets the faces. 51 | /// 52 | public ReadOnlyCollection Faces 53 | { 54 | get { return faces.AsReadOnly(); } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/Index.cs: -------------------------------------------------------------------------------- 1 | namespace FileFormatWavefront.Model 2 | { 3 | /// 4 | /// Represents an index. 5 | /// 6 | public struct Index 7 | { 8 | /// 9 | /// The vertex index. 10 | /// 11 | public int vertex; 12 | 13 | /// 14 | /// The uv index. 15 | /// 16 | public int? uv; 17 | 18 | /// 19 | /// The normal index. 20 | /// 21 | public int? normal; 22 | } 23 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/Material.cs: -------------------------------------------------------------------------------- 1 | namespace FileFormatWavefront.Model 2 | { 3 | /// 4 | /// Represents a material. 5 | /// 6 | public class Material 7 | { 8 | /// 9 | /// Gets the name. 10 | /// 11 | public string Name { get; internal set; } 12 | 13 | /// 14 | /// Gets the ambient. 15 | /// 16 | public Colour Ambient { get; internal set; } 17 | 18 | /// 19 | /// Gets the diffuse. 20 | /// 21 | public Colour Diffuse { get; internal set; } 22 | /// 23 | /// Gets the specular. 24 | /// 25 | public Colour Specular { get; internal set; } 26 | /// 27 | /// Gets the shininess. 28 | /// 29 | public float Shininess { get; internal set; } 30 | /// 31 | /// Gets the transparency. 32 | /// 33 | public float? Transparency { get; internal set; } 34 | 35 | /// 36 | /// Gets the ambient texture map. 37 | /// 38 | public TextureMap TextureMapAmbient { get; internal set; } 39 | 40 | /// 41 | /// Gets the diffuse texture map. 42 | /// 43 | public TextureMap TextureMapDiffuse { get; internal set; } 44 | 45 | /// 46 | /// Gets the specular texture map. 47 | /// 48 | public TextureMap TextureMapSpecular { get; internal set; } 49 | 50 | /// 51 | /// Gets the specular highlight texture map. 52 | /// 53 | public TextureMap TextureMapSpecularHighlight { get; internal set; } 54 | 55 | /// 56 | /// Gets the alpha texture map. 57 | /// 58 | public TextureMap TextureMapAlpha { get; internal set; } 59 | 60 | /// 61 | /// Gets the bump texture map. 62 | /// 63 | public TextureMap TextureMapBump { get; internal set; } 64 | 65 | /// 66 | /// Gets the illumination model. 67 | /// See the specification at http://paulbourke.net/dataformats/mtl/ for details on this value. 68 | /// 69 | /// 70 | /// The illumination model. 71 | /// 72 | public int IlluminationModel { get; internal set; } 73 | 74 | /// 75 | /// Gets the optical density, also known as the Index of Refraction. 76 | /// 77 | public float? OpticalDensity { get; internal set; } 78 | 79 | /// 80 | /// Gets the occasionally used bump strength. 81 | /// 82 | public float? BumpStrength { get; internal set; } 83 | } 84 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/Scene.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace FileFormatWavefront.Model 8 | { 9 | /// 10 | /// Represent a scene of data loaded from an *.obj file. 11 | /// 12 | public class Scene 13 | { 14 | private readonly List vertices; 15 | private readonly List uvs; 16 | private readonly List normals; 17 | private readonly List ungroupedFaces; 18 | private readonly List groups; 19 | private readonly List materials; 20 | private readonly string objectName; 21 | 22 | internal Scene(List vertices, List uvs, List normals, List ungroupedFaces, List groups, List materials, 23 | string objectName) 24 | { 25 | this.vertices = vertices; 26 | this.uvs = uvs; 27 | this.normals = normals; 28 | this.ungroupedFaces = ungroupedFaces; 29 | this.groups = groups; 30 | this.materials = materials; 31 | this.objectName = objectName; 32 | } 33 | 34 | /// 35 | /// Gets the vertices. 36 | /// 37 | public ReadOnlyCollection Vertices 38 | { 39 | get { return vertices.AsReadOnly(); } 40 | } 41 | 42 | /// 43 | /// Gets the uvs. 44 | /// 45 | public ReadOnlyCollection Uvs 46 | { 47 | get { return uvs.AsReadOnly(); } 48 | } 49 | 50 | /// 51 | /// Gets the normals. 52 | /// 53 | public ReadOnlyCollection Normals 54 | { 55 | get { return normals.AsReadOnly(); } 56 | } 57 | 58 | /// 59 | /// Gets the faces which don't belong to any groups. 60 | /// 61 | public ReadOnlyCollection UngroupedFaces 62 | { 63 | get { return ungroupedFaces.AsReadOnly(); } 64 | } 65 | 66 | /// 67 | /// Gets the groups. 68 | /// 69 | public ReadOnlyCollection Groups 70 | { 71 | get { return groups.AsReadOnly(); } 72 | } 73 | 74 | /// 75 | /// Gets the materials. 76 | /// 77 | public ReadOnlyCollection Materials 78 | { 79 | get { return materials.AsReadOnly(); } 80 | } 81 | 82 | /// 83 | /// Gets the name of the object in the file. This can be (and in many cases will be) null. 84 | /// 85 | public string ObjectName { get { return objectName; } } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/TextureMap.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace FileFormatWavefront.Model 4 | { 5 | /// 6 | /// Represents a texture map. 7 | /// 8 | public class TextureMap 9 | { 10 | /// 11 | /// Gets the path to the texture file. 12 | /// 13 | public string Path { get; internal set; } 14 | 15 | /// 16 | /// Gets the texture image. 17 | /// Note that this is only set if the file is loaded with the 'loadTextureImages' option set to true. 18 | /// 19 | public Image Image { get; internal set; } 20 | } 21 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/UV.cs: -------------------------------------------------------------------------------- 1 | namespace FileFormatWavefront.Model 2 | { 3 | /// 4 | /// Represents a texture coordinate. 5 | /// 6 | public struct UV 7 | { 8 | /// 9 | /// The u coordinate. 10 | /// 11 | public float u; 12 | 13 | /// 14 | /// The v coordinate. 15 | /// 16 | public float v; 17 | } 18 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/Model/Vertex.cs: -------------------------------------------------------------------------------- 1 | namespace FileFormatWavefront.Model 2 | { 3 | /// 4 | /// Represents a vertex. 5 | /// 6 | public struct Vertex 7 | { 8 | /// 9 | /// The x coordinate. 10 | /// 11 | public float x; 12 | 13 | /// 14 | /// The y cooredinate. 15 | /// 16 | public float y; 17 | 18 | /// 19 | /// The z coordinate. 20 | /// 21 | public float z; 22 | } 23 | } -------------------------------------------------------------------------------- /source/FileFormatWavefront/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("FileFormatWavefront")] 9 | [assembly: AssemblyDescription("A simple library for loading data from Wavefront object and material files.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("FileFormatWavefront")] 13 | [assembly: AssemblyCopyright("Copyright © Dave Kerr 2014")] 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("531450da-a9e7-408f-a841-6dfcbf6d5a4a")] 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.3.0")] 36 | [assembly: AssemblyFileVersion("1.0.3.0")] 37 | -------------------------------------------------------------------------------- /source/ObjValidator/ObjValidator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {497F077E-A8E2-4B49-AC6E-9BECDCD4CBA9} 8 | Exe 9 | Properties 10 | ObjValidator 11 | ObjValidator 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {878ec1c4-d57e-4749-ae1f-41f393977655} 50 | FileFormatWavefront 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /source/ObjValidator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FileFormatWavefront; 4 | 5 | namespace ObjValidator 6 | { 7 | class Program 8 | { 9 | static int Main(string[] args) 10 | { 11 | // Get the path. 12 | if(args.Length < 1 || string.IsNullOrWhiteSpace(args[0])) 13 | { 14 | Console.WriteLine("Please provide a path to an *.obj file."); 15 | return 1; 16 | } 17 | 18 | // Use the File Format object to load the test data. 19 | var result = FileFormatObj.Load(args[0]); 20 | 21 | // If there are no messages, then the file is completely valid. 22 | if(!result.Messages.Any()) 23 | { 24 | Console.WriteLine("The file loaded with no validation errors or warnings."); 25 | } 26 | 27 | // Show each message. 28 | foreach (var message in result.Messages) 29 | { 30 | Console.WriteLine("{0}: {1}", message.MessageType, message.Details); 31 | Console.WriteLine("{0}: {1}", message.FileName, message.LineNumber); 32 | } 33 | 34 | Console.WriteLine("Complete. Loaded:"); 35 | Console.WriteLine(" Vertices: {0}", result.Model.Vertices.Count); 36 | Console.WriteLine(" UVS: {0}", result.Model.Uvs.Count); 37 | Console.WriteLine(" Normals: {0}", result.Model.Normals.Count); 38 | Console.WriteLine(" Ungrouped Faces: {0}", result.Model.UngroupedFaces.Count); 39 | Console.WriteLine(" Groups: {0}", result.Model.Groups.Count); 40 | Console.WriteLine(" Faces in Groups: {0}", result.Model.Groups.Select(g => g.Faces.Count).Sum()); 41 | Console.WriteLine(" Materials: {0}", result.Model.Materials.Count); 42 | Console.WriteLine("with:"); 43 | Console.WriteLine(" Warnings: {0}", result.Messages.Count(m => m.MessageType == MessageType.Warning)); 44 | Console.WriteLine(" Errors: {0}", result.Messages.Count(m => m.MessageType == MessageType.Error)); 45 | 46 | Console.WriteLine("Any key to close."); 47 | Console.ReadKey(); 48 | 49 | return 0; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/ObjValidator/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("ObjValidator")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ObjValidator")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 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("6f2b6f85-33d1-4a13-98ca-70a7fb76330c")] 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 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Details/DetailsUi.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace WavefrontObjectLoader.Details 4 | { 5 | public abstract class DetailsUi : IDetailsUi 6 | { 7 | public abstract void Create(TModel model); 8 | 9 | public abstract Control Ui { get; } 10 | public abstract void Destroy(); 11 | } 12 | } -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Details/DetailsUiBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using FileFormatWavefront.Model; 8 | 9 | namespace WavefrontObjectLoader.Details 10 | { 11 | public static class DetailsUiBuilder 12 | { 13 | public static IDetailsUi BuildUi(object data) 14 | { 15 | var vertices = data as ReadOnlyCollection; 16 | var uvs = data as ReadOnlyCollection; 17 | var faces = data as ReadOnlyCollection; 18 | if (vertices != null) 19 | { 20 | var ui = new VerticesUi(); 21 | ui.Create(vertices); 22 | return ui; 23 | } 24 | else if (uvs != null) 25 | { 26 | var ui = new UvsUi(); 27 | ui.Create(uvs); 28 | return ui; 29 | } 30 | else if (faces != null) 31 | { 32 | var ui = new FacesUi(); 33 | ui.Create(faces); 34 | return ui; 35 | } 36 | else 37 | { 38 | var ui = new PropertyGridUi(); 39 | ui.Create(data); 40 | return ui; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Details/FacesUi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Text; 3 | using System.Windows.Forms; 4 | using FileFormatWavefront.Model; 5 | 6 | namespace WavefrontObjectLoader.Details 7 | { 8 | public class FacesUi : DetailsUi> 9 | { 10 | private DataGridView dataGridView; 11 | private ReadOnlyCollection faces; 12 | 13 | public override void Create(ReadOnlyCollection model) 14 | { 15 | faces = model; 16 | 17 | // Create a datagrid view. 18 | dataGridView = new DataGridView(); 19 | dataGridView.Dock = DockStyle.Fill; 20 | 21 | // Enable virtual mode. 22 | dataGridView.VirtualMode = true; 23 | dataGridView.ReadOnly = true; 24 | dataGridView.AllowUserToAddRows = false; 25 | dataGridView.AllowUserToDeleteRows = false; 26 | 27 | // Connect the virtual-mode events to event handlers. 28 | dataGridView.CellValueNeeded += DataGridViewOnCellValueNeeded; 29 | 30 | // Add the columns. 31 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 32 | { 33 | HeaderText = "Face Index" 34 | }); 35 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 36 | { 37 | HeaderText = "Index Count" 38 | }); 39 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 40 | { 41 | HeaderText = "Indices" 42 | }); 43 | 44 | dataGridView.AutoSizeColumnsMode = 45 | DataGridViewAutoSizeColumnsMode.DisplayedCells; 46 | dataGridView.RowCount = model.Count; 47 | } 48 | 49 | private void DataGridViewOnCellValueNeeded(object sender, DataGridViewCellValueEventArgs dataGridViewCellValueEventArgs) 50 | { 51 | switch (dataGridViewCellValueEventArgs.ColumnIndex) 52 | { 53 | case 0: 54 | dataGridViewCellValueEventArgs.Value = dataGridViewCellValueEventArgs.RowIndex; 55 | break; 56 | case 1: 57 | dataGridViewCellValueEventArgs.Value = faces[dataGridViewCellValueEventArgs.RowIndex].Indices.Count; 58 | break; 59 | case 2: 60 | { 61 | var face = faces[dataGridViewCellValueEventArgs.RowIndex]; 62 | var builder = new StringBuilder(); 63 | foreach (var index in face.Indices) 64 | { 65 | builder.AppendFormat("{0}/{1}/{2} ", index.vertex, 66 | index.uv.HasValue ? index.uv.ToString() : string.Empty, 67 | index.normal.HasValue ? index.normal.ToString() : string.Empty); 68 | } 69 | 70 | dataGridViewCellValueEventArgs.Value = builder.ToString(); 71 | } 72 | break; 73 | } 74 | } 75 | 76 | public override Control Ui 77 | { 78 | get { return dataGridView; } 79 | } 80 | 81 | public override void Destroy() 82 | { 83 | dataGridView.CellValueNeeded -= DataGridViewOnCellValueNeeded; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Details/IDetailsUi.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace WavefrontObjectLoader.Details 4 | { 5 | public interface IDetailsUi 6 | { 7 | Control Ui { get; } 8 | void Destroy(); 9 | } 10 | } -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Details/PropertyGridUi.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace WavefrontObjectLoader.Details 4 | { 5 | public class PropertyGridUi : DetailsUi 6 | { 7 | private object model; 8 | private PropertyGrid propertyGrid; 9 | public override void Create(object model) 10 | { 11 | this.model = model; 12 | propertyGrid = new PropertyGrid {SelectedObject = model}; 13 | } 14 | 15 | public override Control Ui 16 | { 17 | get { return propertyGrid; } 18 | } 19 | 20 | public override void Destroy() 21 | { 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Details/UvsUi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Windows.Forms; 3 | using FileFormatWavefront.Model; 4 | 5 | namespace WavefrontObjectLoader.Details 6 | { 7 | public class UvsUi : DetailsUi> 8 | { 9 | private DataGridView dataGridView; 10 | private ReadOnlyCollection uvs; 11 | 12 | public override void Create(ReadOnlyCollection model) 13 | { 14 | uvs = model; 15 | 16 | // Create a datagrid view. 17 | dataGridView = new DataGridView(); 18 | dataGridView.Dock = DockStyle.Fill; 19 | 20 | // Enable virtual mode. 21 | dataGridView.VirtualMode = true; 22 | dataGridView.ReadOnly = true; 23 | dataGridView.AllowUserToAddRows = false; 24 | dataGridView.AllowUserToDeleteRows = false; 25 | 26 | // Connect the virtual-mode events to event handlers. 27 | dataGridView.CellValueNeeded += DataGridViewOnCellValueNeeded; 28 | 29 | // Add the columns. 30 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 31 | { 32 | HeaderText = "Index" 33 | }); 34 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 35 | { 36 | HeaderText = "U" 37 | }); 38 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 39 | { 40 | HeaderText = "V" 41 | }); 42 | 43 | dataGridView.AutoSizeColumnsMode = 44 | DataGridViewAutoSizeColumnsMode.DisplayedCells; 45 | dataGridView.RowCount = model.Count; 46 | } 47 | 48 | private void DataGridViewOnCellValueNeeded(object sender, DataGridViewCellValueEventArgs dataGridViewCellValueEventArgs) 49 | { 50 | switch (dataGridViewCellValueEventArgs.ColumnIndex) 51 | { 52 | case 0: 53 | dataGridViewCellValueEventArgs.Value = dataGridViewCellValueEventArgs.RowIndex; 54 | break; 55 | case 1: 56 | dataGridViewCellValueEventArgs.Value = uvs[dataGridViewCellValueEventArgs.RowIndex].u; 57 | break; 58 | case 2: 59 | dataGridViewCellValueEventArgs.Value = uvs[dataGridViewCellValueEventArgs.RowIndex].v; 60 | break; 61 | } 62 | } 63 | 64 | public override Control Ui 65 | { 66 | get { return dataGridView; } 67 | } 68 | 69 | public override void Destroy() 70 | { 71 | dataGridView.CellValueNeeded -= DataGridViewOnCellValueNeeded; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Details/VerticesUi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Windows.Forms; 3 | using FileFormatWavefront.Model; 4 | 5 | namespace WavefrontObjectLoader.Details 6 | { 7 | public class VerticesUi : DetailsUi> 8 | { 9 | private DataGridView dataGridView; 10 | private ReadOnlyCollection vertices; 11 | 12 | public override void Create(ReadOnlyCollection model) 13 | { 14 | vertices = model; 15 | 16 | // Create a datagrid view. 17 | dataGridView = new DataGridView(); 18 | dataGridView.Dock = DockStyle.Fill; 19 | 20 | // Enable virtual mode. 21 | dataGridView.VirtualMode = true; 22 | dataGridView.ReadOnly = true; 23 | dataGridView.AllowUserToAddRows = false; 24 | dataGridView.AllowUserToDeleteRows = false; 25 | 26 | // Connect the virtual-mode events to event handlers. 27 | dataGridView.CellValueNeeded += DataGridViewOnCellValueNeeded; 28 | 29 | // Add the columns. 30 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 31 | { 32 | HeaderText = "Index" 33 | }); 34 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 35 | { 36 | HeaderText = "X" 37 | }); 38 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 39 | { 40 | HeaderText = "Y" 41 | }); 42 | dataGridView.Columns.Add(new DataGridViewTextBoxColumn 43 | { 44 | HeaderText = "Z" 45 | }); 46 | 47 | dataGridView.AutoSizeColumnsMode = 48 | DataGridViewAutoSizeColumnsMode.DisplayedCells; 49 | dataGridView.RowCount = model.Count; 50 | } 51 | 52 | private void DataGridViewOnCellValueNeeded(object sender, DataGridViewCellValueEventArgs dataGridViewCellValueEventArgs) 53 | { 54 | switch (dataGridViewCellValueEventArgs.ColumnIndex) 55 | { 56 | case 0: 57 | dataGridViewCellValueEventArgs.Value = dataGridViewCellValueEventArgs.RowIndex; 58 | break; 59 | case 1: 60 | dataGridViewCellValueEventArgs.Value = vertices[dataGridViewCellValueEventArgs.RowIndex].x; 61 | break; 62 | case 2: 63 | dataGridViewCellValueEventArgs.Value = vertices[dataGridViewCellValueEventArgs.RowIndex].y; 64 | break; 65 | case 3: 66 | dataGridViewCellValueEventArgs.Value = vertices[dataGridViewCellValueEventArgs.RowIndex].z; 67 | break; 68 | } 69 | } 70 | 71 | public override Control Ui 72 | { 73 | get { return dataGridView; } 74 | } 75 | 76 | public override void Destroy() 77 | { 78 | dataGridView.CellValueNeeded -= DataGridViewOnCellValueNeeded; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Extensions/RichTextBoxExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | 9 | namespace WavefrontObjectLoader.Extensions 10 | { 11 | public static class RichTextBoxExtensions 12 | { 13 | public static void AppendText(this RichTextBox box, string text, Color color) 14 | { 15 | box.SelectionStart = box.TextLength; 16 | box.SelectionLength = 0; 17 | 18 | box.SelectionColor = color; 19 | box.AppendText(text); 20 | box.SelectionColor = box.ForeColor; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/FormModelLoader.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WavefrontObjectLoader 2 | { 3 | partial class FormModelLoader 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.toolStripContainer1 = new System.Windows.Forms.ToolStripContainer(); 32 | this.statusStrip1 = new System.Windows.Forms.StatusStrip(); 33 | this.toolStripStatusLabelBusyStatus = new System.Windows.Forms.ToolStripStatusLabel(); 34 | this.toolStripProgressBarBusy = new System.Windows.Forms.ToolStripProgressBar(); 35 | this.splitContainerMainAndOutput = new System.Windows.Forms.SplitContainer(); 36 | this.splitContainerTreeAndDetails = new System.Windows.Forms.SplitContainer(); 37 | this.treeViewModel = new System.Windows.Forms.TreeView(); 38 | this.richTextBoxOutput = new System.Windows.Forms.RichTextBox(); 39 | this.menuStrip1 = new System.Windows.Forms.MenuStrip(); 40 | this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 41 | this.loadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 42 | this.toolStripContainer1.BottomToolStripPanel.SuspendLayout(); 43 | this.toolStripContainer1.ContentPanel.SuspendLayout(); 44 | this.toolStripContainer1.TopToolStripPanel.SuspendLayout(); 45 | this.toolStripContainer1.SuspendLayout(); 46 | this.statusStrip1.SuspendLayout(); 47 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerMainAndOutput)).BeginInit(); 48 | this.splitContainerMainAndOutput.Panel1.SuspendLayout(); 49 | this.splitContainerMainAndOutput.Panel2.SuspendLayout(); 50 | this.splitContainerMainAndOutput.SuspendLayout(); 51 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerTreeAndDetails)).BeginInit(); 52 | this.splitContainerTreeAndDetails.Panel1.SuspendLayout(); 53 | this.splitContainerTreeAndDetails.SuspendLayout(); 54 | this.menuStrip1.SuspendLayout(); 55 | this.SuspendLayout(); 56 | // 57 | // toolStripContainer1 58 | // 59 | // 60 | // toolStripContainer1.BottomToolStripPanel 61 | // 62 | this.toolStripContainer1.BottomToolStripPanel.Controls.Add(this.statusStrip1); 63 | // 64 | // toolStripContainer1.ContentPanel 65 | // 66 | this.toolStripContainer1.ContentPanel.Controls.Add(this.splitContainerMainAndOutput); 67 | this.toolStripContainer1.ContentPanel.Size = new System.Drawing.Size(784, 516); 68 | this.toolStripContainer1.Dock = System.Windows.Forms.DockStyle.Fill; 69 | this.toolStripContainer1.Location = new System.Drawing.Point(0, 0); 70 | this.toolStripContainer1.Name = "toolStripContainer1"; 71 | this.toolStripContainer1.Size = new System.Drawing.Size(784, 562); 72 | this.toolStripContainer1.TabIndex = 0; 73 | this.toolStripContainer1.Text = "toolStripContainer1"; 74 | // 75 | // toolStripContainer1.TopToolStripPanel 76 | // 77 | this.toolStripContainer1.TopToolStripPanel.Controls.Add(this.menuStrip1); 78 | // 79 | // statusStrip1 80 | // 81 | this.statusStrip1.Dock = System.Windows.Forms.DockStyle.None; 82 | this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 83 | this.toolStripStatusLabelBusyStatus, 84 | this.toolStripProgressBarBusy}); 85 | this.statusStrip1.Location = new System.Drawing.Point(0, 0); 86 | this.statusStrip1.Name = "statusStrip1"; 87 | this.statusStrip1.Size = new System.Drawing.Size(784, 22); 88 | this.statusStrip1.TabIndex = 0; 89 | this.statusStrip1.Text = "statusStrip1"; 90 | // 91 | // toolStripStatusLabelBusyStatus 92 | // 93 | this.toolStripStatusLabelBusyStatus.Name = "toolStripStatusLabelBusyStatus"; 94 | this.toolStripStatusLabelBusyStatus.Size = new System.Drawing.Size(32, 17); 95 | this.toolStripStatusLabelBusyStatus.Text = "Busy"; 96 | this.toolStripStatusLabelBusyStatus.Visible = false; 97 | // 98 | // toolStripProgressBarBusy 99 | // 100 | this.toolStripProgressBarBusy.Name = "toolStripProgressBarBusy"; 101 | this.toolStripProgressBarBusy.Size = new System.Drawing.Size(100, 16); 102 | this.toolStripProgressBarBusy.Style = System.Windows.Forms.ProgressBarStyle.Marquee; 103 | this.toolStripProgressBarBusy.Visible = false; 104 | // 105 | // splitContainerMainAndOutput 106 | // 107 | this.splitContainerMainAndOutput.Dock = System.Windows.Forms.DockStyle.Fill; 108 | this.splitContainerMainAndOutput.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; 109 | this.splitContainerMainAndOutput.Location = new System.Drawing.Point(0, 0); 110 | this.splitContainerMainAndOutput.Name = "splitContainerMainAndOutput"; 111 | this.splitContainerMainAndOutput.Orientation = System.Windows.Forms.Orientation.Horizontal; 112 | // 113 | // splitContainerMainAndOutput.Panel1 114 | // 115 | this.splitContainerMainAndOutput.Panel1.Controls.Add(this.splitContainerTreeAndDetails); 116 | // 117 | // splitContainerMainAndOutput.Panel2 118 | // 119 | this.splitContainerMainAndOutput.Panel2.Controls.Add(this.richTextBoxOutput); 120 | this.splitContainerMainAndOutput.Size = new System.Drawing.Size(784, 516); 121 | this.splitContainerMainAndOutput.SplitterDistance = 372; 122 | this.splitContainerMainAndOutput.TabIndex = 0; 123 | // 124 | // splitContainerTreeAndDetails 125 | // 126 | this.splitContainerTreeAndDetails.Dock = System.Windows.Forms.DockStyle.Fill; 127 | this.splitContainerTreeAndDetails.Location = new System.Drawing.Point(0, 0); 128 | this.splitContainerTreeAndDetails.Name = "splitContainerTreeAndDetails"; 129 | // 130 | // splitContainerTreeAndDetails.Panel1 131 | // 132 | this.splitContainerTreeAndDetails.Panel1.Controls.Add(this.treeViewModel); 133 | this.splitContainerTreeAndDetails.Size = new System.Drawing.Size(784, 372); 134 | this.splitContainerTreeAndDetails.SplitterDistance = 261; 135 | this.splitContainerTreeAndDetails.TabIndex = 0; 136 | // 137 | // treeViewModel 138 | // 139 | this.treeViewModel.Dock = System.Windows.Forms.DockStyle.Fill; 140 | this.treeViewModel.Location = new System.Drawing.Point(0, 0); 141 | this.treeViewModel.Name = "treeViewModel"; 142 | this.treeViewModel.Size = new System.Drawing.Size(261, 372); 143 | this.treeViewModel.TabIndex = 0; 144 | this.treeViewModel.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeViewModel_AfterSelect); 145 | // 146 | // richTextBoxOutput 147 | // 148 | this.richTextBoxOutput.BackColor = System.Drawing.Color.White; 149 | this.richTextBoxOutput.Dock = System.Windows.Forms.DockStyle.Fill; 150 | this.richTextBoxOutput.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 151 | this.richTextBoxOutput.Location = new System.Drawing.Point(0, 0); 152 | this.richTextBoxOutput.Name = "richTextBoxOutput"; 153 | this.richTextBoxOutput.ReadOnly = true; 154 | this.richTextBoxOutput.Size = new System.Drawing.Size(784, 140); 155 | this.richTextBoxOutput.TabIndex = 0; 156 | this.richTextBoxOutput.Text = ""; 157 | // 158 | // menuStrip1 159 | // 160 | this.menuStrip1.Dock = System.Windows.Forms.DockStyle.None; 161 | this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 162 | this.fileToolStripMenuItem}); 163 | this.menuStrip1.Location = new System.Drawing.Point(0, 0); 164 | this.menuStrip1.Name = "menuStrip1"; 165 | this.menuStrip1.Size = new System.Drawing.Size(784, 24); 166 | this.menuStrip1.TabIndex = 0; 167 | this.menuStrip1.Text = "menuStrip1"; 168 | // 169 | // fileToolStripMenuItem 170 | // 171 | this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 172 | this.loadToolStripMenuItem}); 173 | this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; 174 | this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); 175 | this.fileToolStripMenuItem.Text = "&File"; 176 | // 177 | // loadToolStripMenuItem 178 | // 179 | this.loadToolStripMenuItem.Name = "loadToolStripMenuItem"; 180 | this.loadToolStripMenuItem.Size = new System.Drawing.Size(109, 22); 181 | this.loadToolStripMenuItem.Text = "&Load..."; 182 | this.loadToolStripMenuItem.Click += new System.EventHandler(this.loadToolStripMenuItem_Click); 183 | // 184 | // FormModelLoader 185 | // 186 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 187 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 188 | this.ClientSize = new System.Drawing.Size(784, 562); 189 | this.Controls.Add(this.toolStripContainer1); 190 | this.MainMenuStrip = this.menuStrip1; 191 | this.Name = "FormModelLoader"; 192 | this.Text = "Wavefront Model Loader"; 193 | this.toolStripContainer1.BottomToolStripPanel.ResumeLayout(false); 194 | this.toolStripContainer1.BottomToolStripPanel.PerformLayout(); 195 | this.toolStripContainer1.ContentPanel.ResumeLayout(false); 196 | this.toolStripContainer1.TopToolStripPanel.ResumeLayout(false); 197 | this.toolStripContainer1.TopToolStripPanel.PerformLayout(); 198 | this.toolStripContainer1.ResumeLayout(false); 199 | this.toolStripContainer1.PerformLayout(); 200 | this.statusStrip1.ResumeLayout(false); 201 | this.statusStrip1.PerformLayout(); 202 | this.splitContainerMainAndOutput.Panel1.ResumeLayout(false); 203 | this.splitContainerMainAndOutput.Panel2.ResumeLayout(false); 204 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerMainAndOutput)).EndInit(); 205 | this.splitContainerMainAndOutput.ResumeLayout(false); 206 | this.splitContainerTreeAndDetails.Panel1.ResumeLayout(false); 207 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerTreeAndDetails)).EndInit(); 208 | this.splitContainerTreeAndDetails.ResumeLayout(false); 209 | this.menuStrip1.ResumeLayout(false); 210 | this.menuStrip1.PerformLayout(); 211 | this.ResumeLayout(false); 212 | 213 | } 214 | 215 | #endregion 216 | 217 | private System.Windows.Forms.ToolStripContainer toolStripContainer1; 218 | private System.Windows.Forms.MenuStrip menuStrip1; 219 | private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; 220 | private System.Windows.Forms.ToolStripMenuItem loadToolStripMenuItem; 221 | private System.Windows.Forms.SplitContainer splitContainerMainAndOutput; 222 | private System.Windows.Forms.RichTextBox richTextBoxOutput; 223 | private System.Windows.Forms.StatusStrip statusStrip1; 224 | private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelBusyStatus; 225 | private System.Windows.Forms.ToolStripProgressBar toolStripProgressBarBusy; 226 | private System.Windows.Forms.SplitContainer splitContainerTreeAndDetails; 227 | private System.Windows.Forms.TreeView treeViewModel; 228 | } 229 | } 230 | 231 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/FormModelLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Drawing; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows.Forms; 12 | using FileFormatWavefront; 13 | using FileFormatWavefront.Model; 14 | using WavefrontObjectLoader.Details; 15 | using WavefrontObjectLoader.Extensions; 16 | 17 | namespace WavefrontObjectLoader 18 | { 19 | public partial class FormModelLoader : Form 20 | { 21 | public FormModelLoader() 22 | { 23 | InitializeComponent(); 24 | 25 | AddOutput("Choose File > Load to load a Wavefront *.obj file"); 26 | } 27 | 28 | private void loadToolStripMenuItem_Click(object sender, EventArgs e) 29 | { 30 | if (isBusy) 31 | return; 32 | // Create a file open dialog to load the file. 33 | var openFileDialog = new OpenFileDialog(); 34 | openFileDialog.Filter = "Wavefront Object Files (*.obj)|*.obj|All Files (*.*)|*.*"; 35 | if (openFileDialog.ShowDialog(this) == DialogResult.OK) 36 | { 37 | LoadFileAsync(openFileDialog.FileName); 38 | } 39 | } 40 | 41 | private async void LoadFileAsync(string path) 42 | { 43 | // Reset the UI. 44 | DestroyDetailsUi(); 45 | treeViewModel.Nodes.Clear(); 46 | 47 | // Create a loading message, write it and set the app to busy. 48 | var loadingMessage = string.Format("Loading {0}", Path.GetFileName(path)); 49 | AddOutput(loadingMessage); 50 | StartBusy(loadingMessage); 51 | 52 | // Start a stopwatch for the actual loading. 53 | var stopwatch = new Stopwatch(); 54 | stopwatch.Start(); 55 | 56 | // Load the model. 57 | var result = await Task.Run(() => FileFormatObj.Load(path, true)); 58 | StopBusy(); 59 | stopwatch.Stop(); 60 | 61 | // Write a little bit of summary information. 62 | AddOutput(string.Format("Loaded {0} in {1}, {2} warning(s), {3} error(s).", 63 | Path.GetFileName(path), 64 | stopwatch.Elapsed, 65 | result.Messages.Count(m => m.MessageType == MessageType.Warning), 66 | result.Messages.Count(m => m.MessageType == MessageType.Error))); 67 | if (!string.IsNullOrEmpty(result.Model.ObjectName)) 68 | AddOutput(string.Format("The object is named '{0}'.", result.Model.ObjectName)); 69 | 70 | // Show the result. 71 | ShowResult(result); 72 | } 73 | 74 | private void StartBusy(string busyText) 75 | { 76 | isBusy = true; 77 | toolStripStatusLabelBusyStatus.Text = busyText; 78 | toolStripStatusLabelBusyStatus.Visible = true; 79 | toolStripProgressBarBusy.Visible = true; 80 | 81 | // Disable UI we don't want offered when busy. 82 | loadToolStripMenuItem.Enabled = false; 83 | } 84 | 85 | private void StopBusy() 86 | { 87 | toolStripStatusLabelBusyStatus.Text = string.Empty; 88 | toolStripStatusLabelBusyStatus.Visible = false; 89 | toolStripProgressBarBusy.Visible = false; 90 | isBusy = false; 91 | 92 | // Re-eanble UI we don't want offered when busy. 93 | loadToolStripMenuItem.Enabled = true; 94 | } 95 | 96 | private void ShowResult(FileLoadResult result) 97 | { 98 | // Write results to the output window. 99 | int count = 0; 100 | foreach (var message in result.Messages) 101 | { 102 | var messageColour = message.MessageType == MessageType.Error ? Color.Red : Color.DarkOrange; 103 | AddOutput(string.Format("{0}: {1}", message.MessageType, message.Details), messageColour); 104 | AddOutput(string.Format("{0}: {1}", message.FileName, message.LineNumber), messageColour); 105 | count++; 106 | if (count == 1000) 107 | { 108 | AddOutput("Message count exceeds 1000, no more messages will be displayed.", Color.Red); 109 | break; 110 | } 111 | } 112 | 113 | PopulateModelTreeView(result.Model); 114 | } 115 | 116 | private void PopulateModelTreeView(Scene scene) 117 | { 118 | // Populate the model tree view. 119 | treeViewModel.Nodes.Clear(); 120 | 121 | // Add the vertices, normals and uvs. 122 | treeViewModel.Nodes.Add(new TreeNode 123 | { 124 | Text = "Vertices", 125 | Tag = scene.Vertices 126 | }); 127 | treeViewModel.Nodes.Add(new TreeNode 128 | { 129 | Text = "UVs", 130 | Tag = scene.Uvs 131 | }); 132 | treeViewModel.Nodes.Add(new TreeNode 133 | { 134 | Text = "Normals", 135 | Tag = scene.Normals 136 | }); 137 | treeViewModel.Nodes.Add(new TreeNode 138 | { 139 | Text = "Ungrouped Faces", 140 | Tag = scene.UngroupedFaces 141 | }); 142 | 143 | // Add the groups. 144 | var treeNodeGroups = new TreeNode 145 | { 146 | Text = "Groups", 147 | Tag = scene.Groups 148 | }; 149 | foreach (var group in scene.Groups) 150 | { 151 | var treeNodeGroup = new TreeNode 152 | { 153 | Text = group.Names.Any() ? string.Join(" ", group.Names) : "", 154 | Tag = group 155 | }; 156 | treeNodeGroup.Nodes.Add(new TreeNode 157 | { 158 | Text = "Faces", 159 | Tag = group.Faces 160 | }); 161 | treeNodeGroups.Nodes.Add(treeNodeGroup); 162 | } 163 | treeViewModel.Nodes.Add(treeNodeGroups); 164 | 165 | // Add the materials. 166 | var treeNodeMaterials = new TreeNode 167 | { 168 | Text = "Materials", 169 | Tag = scene.Materials 170 | }; 171 | foreach (var material in scene.Materials) 172 | { 173 | treeNodeMaterials.Nodes.Add(new TreeNode 174 | { 175 | Text = !string.IsNullOrEmpty(material.Name) ? material.Name : "", 176 | Tag = material 177 | }); 178 | } 179 | treeViewModel.Nodes.Add(treeNodeMaterials); 180 | } 181 | 182 | private void ClearOutput() 183 | { 184 | richTextBoxOutput.ResetText(); 185 | } 186 | 187 | private void AddOutput(string content) 188 | { 189 | richTextBoxOutput.AppendText(content + "\r\n"); 190 | } 191 | 192 | private void AddOutput(string content, Color color) 193 | { 194 | richTextBoxOutput.AppendText(content + "\r\n", color); 195 | } 196 | 197 | private volatile bool isBusy = false; 198 | private IDetailsUi currentDetailsUi; 199 | 200 | private void treeViewModel_AfterSelect(object sender, TreeViewEventArgs e) 201 | { 202 | CreateDetailsUi( e.Node != null ? e.Node.Tag: null); 203 | 204 | 205 | } 206 | 207 | private void DestroyDetailsUi() 208 | { 209 | if (currentDetailsUi != null) 210 | { 211 | // Delete old ui. 212 | splitContainerTreeAndDetails.Panel2.Controls.Clear(); 213 | currentDetailsUi.Destroy(); 214 | } 215 | } 216 | 217 | private void CreateDetailsUi(object model) 218 | { 219 | DestroyDetailsUi(); 220 | if (model == null) 221 | return; 222 | currentDetailsUi = DetailsUiBuilder.BuildUi(model); 223 | currentDetailsUi.Ui.Dock = DockStyle.Fill; 224 | splitContainerTreeAndDetails.Panel2.Controls.Add(currentDetailsUi.Ui); 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/FormModelLoader.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 132, 17 122 | 123 | 124 | 17, 17 125 | 126 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace WavefrontObjectLoader 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | Application.Run(new FormModelLoader()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/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("WavefrontObjectLoader")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WavefrontObjectLoader")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 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("9499bc49-b0ea-4c93-8568-15299c76bbee")] 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 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18408 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WavefrontObjectLoader.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WavefrontObjectLoader.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18408 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WavefrontObjectLoader.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/WavefrontObjectLoader/WavefrontObjectLoader.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EA7A4D48-969F-4E19-8BAB-BBC051F1C3EE} 8 | WinExe 9 | Properties 10 | WavefrontObjectLoader 11 | WavefrontObjectLoader 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Form 57 | 58 | 59 | FormModelLoader.cs 60 | 61 | 62 | 63 | 64 | FormModelLoader.cs 65 | 66 | 67 | ResXFileCodeGenerator 68 | Resources.Designer.cs 69 | Designer 70 | 71 | 72 | True 73 | Resources.resx 74 | 75 | 76 | SettingsSingleFileGenerator 77 | Settings.Designer.cs 78 | 79 | 80 | True 81 | Settings.settings 82 | True 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | {878ec1c4-d57e-4749-ae1f-41f393977655} 91 | FileFormatWavefront 92 | 93 | 94 | 95 | 102 | --------------------------------------------------------------------------------