Advertisement
Guest User

Wavefront OBJ 3D Model Importer

a guest
Feb 19th, 2013
1,386
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 12.69 KB | None | 0 0
  1. /// <author>Lukas Eibensteiner</author>
  2. /// <date>19.02.2013</date>
  3. /// <summary>Example of a Wavefront OBJ 3D model importer</summary>
  4.  
  5. using SlimDX;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Linq;
  10.  
  11. namespace Games
  12. {
  13.     /// <summary>
  14.     ///     Class for reading a 3D mesh in the Wavefront OBJ format from a stream.
  15.     /// </summary>
  16.     public class WavefrontReader
  17.     {
  18.         /// <summary>
  19.         /// Enum for describing the semantic meaning of a line in an OBJ file.
  20.         /// </summary>
  21.         private enum DataType
  22.         {
  23.             /// <summary>
  24.             /// The line contains nothing or has no or an undefined keyword.
  25.             /// </summary>
  26.             Empty,
  27.  
  28.             /// <summary>
  29.             /// The line contains a comment.
  30.             /// </summary>
  31.             Comment,
  32.  
  33.             /// <summary>
  34.             /// The line contains a group definition.
  35.             /// </summary>
  36.             Group,
  37.  
  38.             /// <summary>
  39.             /// The line contains a smoothing group definitio.
  40.             /// </summary>
  41.             SmoothingGroup,
  42.  
  43.             /// <summary>
  44.             /// The line contains a position vector definition.
  45.             /// </summary>
  46.             Position,
  47.  
  48.             /// <summary>
  49.             /// The line contains a normal vector definition.
  50.             /// </summary>
  51.             Normal,
  52.  
  53.             /// <summary>
  54.             /// The line contains a texture coordinate definition.
  55.             /// </summary>
  56.             TexCoord,
  57.  
  58.             /// <summary>
  59.             /// The line contains a face definition.
  60.             /// </summary>
  61.             Face,
  62.         }
  63.  
  64.         // Dictionary mapping the DataType enumeration to the corresponding keyword.
  65.         private static Dictionary<DataType, string> Keywords
  66.         {
  67.             get
  68.             {
  69.                 return new Dictionary<DataType, string>()
  70.                 {
  71.                     { DataType.Comment,         "#"     },
  72.                     { DataType.Group,           "g"     },
  73.                     { DataType.SmoothingGroup,  "s"     },
  74.                     { DataType.Position,        "v"     },
  75.                     { DataType.TexCoord,        "vt"    },
  76.                     { DataType.Normal,          "vn"    },
  77.                     { DataType.Face,            "f"     },
  78.                 };
  79.             }
  80.         }
  81.  
  82.         /// <summary>
  83.         ///     Reads a WavefrontObject instance from the stream.
  84.         /// </summary>
  85.         /// <param name="stream">
  86.         ///     Stream containing the OBJ file content.
  87.         /// </param>
  88.         /// <exception cref="ArgumentNullException">
  89.         ///     <paramref name="stream"/> is <c>null</c>.
  90.         /// </exception>
  91.         /// <exception cref="IOException">
  92.         ///     Error while reading from the stream.
  93.         /// </exception>
  94.         public WavefrontObject Read(Stream stream)
  95.         {
  96.             if (stream == null)
  97.                 throw new ArgumentNullException("stream");
  98.  
  99.             // Create the stream reader for the file
  100.             var reader = new StreamReader(stream);
  101.  
  102.             // Store the lines here
  103.             var lines = new List<string>();
  104.  
  105.             // Store the current line here
  106.             var current = string.Empty;
  107.  
  108.             // Read the file line by line and normalize them
  109.             while ((current = reader.ReadLine()) != null)
  110.                 lines.Add(NormalizeLine(current));
  111.  
  112.             // Create empty mesh instance
  113.             var obj = new WavefrontObject();
  114.  
  115.             // Iterate over all lines
  116.             foreach (string line in lines)
  117.             {
  118.                 // Get line type and content
  119.                 DataType type = GetType(line);
  120.                 string content = GetContent(line, type);
  121.  
  122.                 // Line is a position
  123.                 if (type == DataType.Position)
  124.                     obj.Positions.Add(ParseVector3(content));
  125.  
  126.                 // Line is a texture coordinate
  127.                 if (type == DataType.TexCoord)
  128.                     obj.Texcoords.Add(ParseVector2(content));
  129.  
  130.                 // Line is a normal vector
  131.                 if (type == DataType.Normal)
  132.                     obj.Normals.Add(ParseVector3(content));
  133.  
  134.                 // Line is a mesh sub group
  135.                 if (type == DataType.Group)
  136.                     obj.Groups.Add(new WavefrontFaceGroup() { Name = content });
  137.  
  138.                 // Line is a polygon
  139.                 if (type == DataType.Face)
  140.                 {
  141.                     // Create the default group for all faces outside a group
  142.                     if (obj.Groups.Count == 0)
  143.                         obj.Groups.Add(new WavefrontFaceGroup());
  144.  
  145.                     // Add the face to the last group added
  146.                     obj.Groups.Last().Faces.Add(ParseFace(content));
  147.                 }
  148.             }
  149.  
  150.             return obj;
  151.         }
  152.  
  153.         // Trim beginning and end and collapse all whitespace in a string to single space.
  154.         private string NormalizeLine(string line)
  155.         {
  156.             return System.Text.RegularExpressions.Regex.Replace(line.Trim(), @"\s+", " ");
  157.         }
  158.  
  159.         // Get the type of data stored in the specified line.
  160.         private DataType GetType(string line)
  161.         {
  162.             // Iterate over the keywords
  163.             foreach (var item in Keywords)
  164.             {
  165.                 var type = item.Key;
  166.                 var keyword = item.Value;
  167.  
  168.                 // Line starts with current keyword
  169.                 if (line.ToLower().StartsWith(keyword.ToLower() + " "))
  170.                 {
  171.                     // Return current type
  172.                     return type;
  173.                 }
  174.             }
  175.  
  176.             // No type
  177.             return DataType.Empty;
  178.         }
  179.  
  180.         // Remove the keyword from the start of the line and return the result.
  181.         // Returns an empty string if the specified type was DataType.Empty.
  182.         private string GetContent(string line, DataType type)
  183.         {
  184.             // If empty return empty string,
  185.             // else remove the keyword from the start
  186.             return type == DataType.Empty
  187.                 ? string.Empty
  188.                 : line.Substring(Keywords[type].Length).Trim();
  189.         }
  190.  
  191.         // Create an array of floats of arbitary length from a string representation,
  192.         // where the floats are spearated by whitespace.
  193.         private static float[] ParseFloatArray(string str, int count)
  194.         {
  195.             var floats = new float[count];
  196.  
  197.             var segments = str.Split(' ');
  198.  
  199.             for (int i = 0; i < count; i++)
  200.             {
  201.                 if (i < segments.Length)
  202.                 {
  203.                     try
  204.                     {
  205.                         floats[i] = float.Parse(segments[i], System.Globalization.CultureInfo.InvariantCulture);
  206.                     }
  207.                     catch
  208.                     {
  209.                         floats[i] = 0;
  210.                     }
  211.                 }
  212.             }
  213.  
  214.             return floats;
  215.         }
  216.  
  217.         // Parse a 3D vector from a string definition in the form of: 2.0 3.0 1.0
  218.         private Vector2 ParseVector2(string str)
  219.         {
  220.             var components = ParseFloatArray(str, 3);
  221.  
  222.             var vec = new Vector2(components[0], components[1]);
  223.  
  224.             return components[2] == 0
  225.                 ? vec
  226.                 : vec / components[2];
  227.         }
  228.  
  229.         // Parse a 3D vector from a string definition in the form of: 1.0 2.0 3.0 1.0
  230.         private Vector3 ParseVector3(string str)
  231.         {
  232.             var components = ParseFloatArray(str, 4);
  233.  
  234.             var vec = new Vector3(components[0], components[1], components[2]);
  235.  
  236.             return components[3] == 0
  237.                 ? vec
  238.                 : vec / components[3];
  239.         }
  240.  
  241.         // Parse a OBJ face from a string definition.
  242.         private WavefrontFace ParseFace(string str)
  243.         {
  244.             // Split the face definition at whitespace
  245.             var segments = str.Split(new Char[0], StringSplitOptions.RemoveEmptyEntries);
  246.  
  247.             var vertices = new List<WavefrontVertex>();
  248.  
  249.             // Iterate over the segments
  250.             foreach (string segment in segments)
  251.             {
  252.                 // Parse and add the vertex
  253.                 vertices.Add(ParseVertex(segment));
  254.             }
  255.  
  256.             // Create and return the face
  257.             return new WavefrontFace()
  258.             {
  259.                 Vertices = vertices,
  260.             };
  261.         }
  262.  
  263.         // Parse an OBJ vertex from a string definition in the forms of:
  264.         //     1/2/3
  265.         //     1//3
  266.         //     1/2
  267.         //     1
  268.         private WavefrontVertex ParseVertex(string str)
  269.         {
  270.             // Split the string definition at the slash separator
  271.             var segments = str.Split('/');
  272.  
  273.             // Store the vertex indices here
  274.             var indices = new int[3];
  275.  
  276.             // Iterate 3 times
  277.             for (int i = 0; i < 3; i++)
  278.             {
  279.                 // If no segment exists at the location or the segment can not be passed to an integer
  280.                 // Set the index to zero
  281.                 if (segments.Length <= i || !int.TryParse(segments[i], out indices[i]))
  282.                     indices[i] = 0;
  283.             }
  284.  
  285.             // Create the new vertex
  286.             return new WavefrontVertex()
  287.             {
  288.                 Position = indices[0],
  289.                 Texcoord = indices[1],
  290.                 Normal = indices[2],
  291.             };
  292.         }
  293.     }
  294.  
  295.     /// <summary>
  296.     ///     Class representing a Wavefront OBJ 3D mesh.
  297.     /// </summary>
  298.     public class WavefrontObject
  299.     {
  300.         public WavefrontObject()
  301.         {
  302.             Groups = new List<WavefrontFaceGroup>();
  303.             Positions = new List<Vector3>();
  304.             Texcoords = new List<Vector2>();
  305.             Normals = new List<Vector3>();
  306.         }
  307.  
  308.         // Lists containing the vertex components
  309.         public List<Vector3> Positions { get; private set; }
  310.         public List<Vector2> Texcoords { get; private set; }
  311.         public List<Vector3> Normals { get; private set; }
  312.  
  313.         // List of sub meshes
  314.         public List<WavefrontFaceGroup> Groups { get; private set; }
  315.     }
  316.  
  317.     /// <summary>
  318.     ///     Struct representing an Wavefront OBJ face group.
  319.     /// </summary>
  320.     /// <remarks>
  321.     ///     Groups contain faces and subdivide a geometry into smaller objects.
  322.     /// </remarks>
  323.     public class WavefrontFaceGroup
  324.     {
  325.         public WavefrontFaceGroup()
  326.         {
  327.             Faces = new List<WavefrontFace>();
  328.         }
  329.  
  330.         // Name of the sub mesh
  331.         public string Name { get; set; }
  332.  
  333.         // A list of faces
  334.         public List<WavefrontFace> Faces { get; set; }
  335.  
  336.         // Get the total number of triangles
  337.         public int TriangleCount
  338.         {
  339.             get
  340.             {
  341.                 var count = 0;
  342.  
  343.                 foreach (var face in Faces)
  344.                     count += face.TriangleCount;
  345.  
  346.                 return count;
  347.             }
  348.         }
  349.     }
  350.  
  351.     /// <summary>
  352.     ///     A struct representing a Wavefront OBJ geometry face.
  353.     /// </summary>
  354.     /// <remarks>
  355.     ///     A face is described through a list of OBJ vertices.
  356.     ///     It can consist of three or more vertices an can therefore be split up
  357.     ///     into one or more triangles.
  358.     /// </remarks>
  359.     public struct WavefrontFace
  360.     {
  361.         public List<WavefrontVertex> Vertices { get; set; }
  362.  
  363.         // Number of triangles the face (polygon) consists of
  364.         public int TriangleCount
  365.         {
  366.             get
  367.             {
  368.                 return Vertices.Count - 2;
  369.             }
  370.         }
  371.  
  372.         // Number of vertices
  373.         public int VertexCount
  374.         {
  375.             get { return Vertices.Count; }
  376.         }
  377.     }
  378.  
  379.     /// <summary>
  380.     ///     A struct representing an Wavefront OBJ vertex.
  381.     /// </summary>
  382.     /// <remarks>
  383.     ///     OBJ vertices are indexed vertices so instead of vectors
  384.     ///     it has an index for the position, texture coordinate and normal.
  385.     ///     Each of those indices points to a location in a list of vectors.
  386.     /// </remarks>
  387.     public struct WavefrontVertex
  388.     {
  389.         public WavefrontVertex(int position, int texcoord, int normal)
  390.             : this()
  391.         {
  392.             Position = position;
  393.             Texcoord = texcoord;
  394.             Normal = normal;
  395.         }
  396.  
  397.         // Inidices of the vertex components
  398.         public int Position { get; set; }
  399.         public int Normal { get; set; }
  400.         public int Texcoord { get; set; }
  401.     }
  402. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement