C# OBJ模型解析的封装(网上看到的保留一份)
/// <author>Lukas Eibensteiner</author> /// <date>19.02.2013</date> /// <summary>Example of a Wavefront OBJ 3D model importer</summary> using SlimDX; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Games { /// <summary> /// Class for reading a 3D mesh in the Wavefront OBJ format from a stream. /// </summary> public class WavefrontReader { /// <summary> /// Enum for describing the semantic meaning of a line in an OBJ file. /// </summary> private enum DataType { /// <summary> /// The line contains nothing or has no or an undefined keyword. /// </summary> Empty, /// <summary> /// The line contains a comment. /// </summary> Comment, /// <summary> /// The line contains a group definition. /// </summary> Group, /// <summary> /// The line contains a smoothing group definitio. /// </summary> SmoothingGroup, /// <summary> /// The line contains a position vector definition. /// </summary> Position, /// <summary> /// The line contains a normal vector definition. /// </summary> Normal, /// <summary> /// The line contains a texture coordinate definition. /// </summary> TexCoord, /// <summary> /// The line contains a face definition. /// </summary> Face, } // Dictionary mapping the DataType enumeration to the corresponding keyword. private static Dictionary<DataType, string> Keywords { get { return new Dictionary<DataType, string>() { { DataType.Comment, "#" }, { DataType.Group, "g" }, { DataType.SmoothingGroup, "s" }, { DataType.Position, "v" }, { DataType.TexCoord, "vt" }, { DataType.Normal, "vn" }, { DataType.Face, "f" }, }; } } /// <summary> /// Reads a WavefrontObject instance from the stream. /// </summary> /// <param name="stream"> /// Stream containing the OBJ file content. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="stream"/> is <c>null</c>. /// </exception> /// <exception cref="IOException"> /// Error while reading from the stream. /// </exception> public WavefrontObject Read(Stream stream) { if (stream == null) throw new ArgumentNullException("stream"); // Create the stream reader for the file var reader = new StreamReader(stream); // Store the lines here var lines = new List<string>(); // Store the current line here var current = string.Empty; // Read the file line by line and normalize them while ((current = reader.ReadLine()) != null) lines.Add(NormalizeLine(current)); // Create empty mesh instance var obj = new WavefrontObject(); // Iterate over all lines foreach (string line in lines) { // Get line type and content DataType type = GetType(line); string content = GetContent(line, type); // Line is a position if (type == DataType.Position) obj.Positions.Add(ParseVector3(content)); // Line is a texture coordinate if (type == DataType.TexCoord) obj.Texcoords.Add(ParseVector2(content)); // Line is a normal vector if (type == DataType.Normal) obj.Normals.Add(ParseVector3(content)); // Line is a mesh sub group if (type == DataType.Group) obj.Groups.Add(new WavefrontFaceGroup() { Name = content }); // Line is a polygon if (type == DataType.Face) { // Create the default group for all faces outside a group if (obj.Groups.Count == 0) obj.Groups.Add(new WavefrontFaceGroup()); // Add the face to the last group added obj.Groups.Last().Faces.Add(ParseFace(content)); } } return obj; } // Trim beginning and end and collapse all whitespace in a string to single space. private string NormalizeLine(string line) { return System.Text.RegularExpressions.Regex.Replace(line.Trim(), @"\s+", " "); } // Get the type of data stored in the specified line. private DataType GetType(string line) { // Iterate over the keywords foreach (var item in Keywords) { var type = item.Key; var keyword = item.Value; // Line starts with current keyword if (line.ToLower().StartsWith(keyword.ToLower() + " ")) { // Return current type return type; } } // No type return DataType.Empty; } // Remove the keyword from the start of the line and return the result. // Returns an empty string if the specified type was DataType.Empty. private string GetContent(string line, DataType type) { // If empty return empty string, // else remove the keyword from the start return type == DataType.Empty ? string.Empty : line.Substring(Keywords[type].Length).Trim(); } // Create an array of floats of arbitary length from a string representation, // where the floats are spearated by whitespace. private static float[] ParseFloatArray(string str, int count) { var floats = new float[count]; var segments = str.Split(' '); for (int i = 0; i < count; i++) { if (i < segments.Length) { try { floats[i] = float.Parse(segments[i], System.Globalization.CultureInfo.InvariantCulture); } catch { floats[i] = 0; } } } return floats; } // Parse a 3D vector from a string definition in the form of: 2.0 3.0 1.0 private Vector2 ParseVector2(string str) { var components = ParseFloatArray(str, 3); var vec = new Vector2(components[0], components[1]); return components[2] == 0 ? vec : vec / components[2]; } // Parse a 3D vector from a string definition in the form of: 1.0 2.0 3.0 1.0 private Vector3 ParseVector3(string str) { var components = ParseFloatArray(str, 4); var vec = new Vector3(components[0], components[1], components[2]); return components[3] == 0 ? vec : vec / components[3]; } // Parse a OBJ face from a string definition. private WavefrontFace ParseFace(string str) { // Split the face definition at whitespace var segments = str.Split(new Char[0], StringSplitOptions.RemoveEmptyEntries); var vertices = new List<WavefrontVertex>(); // Iterate over the segments foreach (string segment in segments) { // Parse and add the vertex vertices.Add(ParseVertex(segment)); } // Create and return the face return new WavefrontFace() { Vertices = vertices, }; } // Parse an OBJ vertex from a string definition in the forms of: // 1/2/3 // 1//3 // 1/2 // 1 private WavefrontVertex ParseVertex(string str) { // Split the string definition at the slash separator var segments = str.Split('/'); // Store the vertex indices here var indices = new int[3]; // Iterate 3 times for (int i = 0; i < 3; i++) { // If no segment exists at the location or the segment can not be passed to an integer // Set the index to zero if (segments.Length <= i || !int.TryParse(segments[i], out indices[i])) indices[i] = 0; } // Create the new vertex return new WavefrontVertex() { Position = indices[0], Texcoord = indices[1], Normal = indices[2], }; } } /// <summary> /// Class representing a Wavefront OBJ 3D mesh. /// </summary> public class WavefrontObject { public WavefrontObject() { Groups = new List<WavefrontFaceGroup>(); Positions = new List<Vector3>(); Texcoords = new List<Vector2>(); Normals = new List<Vector3>(); } // Lists containing the vertex components public List<Vector3> Positions { get; private set; } public List<Vector2> Texcoords { get; private set; } public List<Vector3> Normals { get; private set; } // List of sub meshes public List<WavefrontFaceGroup> Groups { get; private set; } } /// <summary> /// Struct representing an Wavefront OBJ face group. /// </summary> /// <remarks> /// Groups contain faces and subdivide a geometry into smaller objects. /// </remarks> public class WavefrontFaceGroup { public WavefrontFaceGroup() { Faces = new List<WavefrontFace>(); } // Name of the sub mesh public string Name { get; set; } // A list of faces public List<WavefrontFace> Faces { get; set; } // Get the total number of triangles public int TriangleCount { get { var count = 0; foreach (var face in Faces) count += face.TriangleCount; return count; } } } /// <summary> /// A struct representing a Wavefront OBJ geometry face. /// </summary> /// <remarks> /// A face is described through a list of OBJ vertices. /// It can consist of three or more vertices an can therefore be split up /// into one or more triangles. /// </remarks> public struct WavefrontFace { public List<WavefrontVertex> Vertices { get; set; } // Number of triangles the face (polygon) consists of public int TriangleCount { get { return Vertices.Count - 2; } } // Number of vertices public int VertexCount { get { return Vertices.Count; } } } /// <summary> /// A struct representing an Wavefront OBJ vertex. /// </summary> /// <remarks> /// OBJ vertices are indexed vertices so instead of vectors /// it has an index for the position, texture coordinate and normal. /// Each of those indices points to a location in a list of vectors. /// </remarks> public struct WavefrontVertex { public WavefrontVertex(int position, int texcoord, int normal) : this() { Position = position; Texcoord = texcoord; Normal = normal; } // Inidices of the vertex components public int Position { get; set; } public int Normal { get; set; } public int Texcoord { get; set; } } }