/// <summary> /// Processes a line from an OBJ file. /// </summary> /// <param name="line">A line from an OBJ file.</param> /// <param name="state">An object that manages state between calls.</param> private void ProcessObjLine(string line, ObjFileProcessorState state) { // Trim out comments (allow comments trailing on a line) int commentStart = line.IndexOf('#'); if (commentStart != -1) { line = line.Substring(0, commentStart); } // Tokenize line string[] tokens = line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); // Process line based on the keyword used if (tokens.Length > 0) { int? v; float x, y, z; Vertex[] faceVertices; int?[] vt, vn; switch (tokens[0]) { case "v": // Vertex if (tokens.Length != 4) { throw new IOException("Vertices in the OBJ file must have 3 coordinates."); } x = Single.Parse(tokens[1], CultureInfo.InvariantCulture); y = Single.Parse(tokens[2], CultureInfo.InvariantCulture); z = Single.Parse(tokens[3], CultureInfo.InvariantCulture); Vertices.Add(new VertexTraits(x, y, z)); break; case "vt": // Vertex texture if (tokens.Length != 3 && tokens.Length != 4) { throw new IOException("Texture coordinates in the OBJ file must have 2 or 3 coordinates."); } x = Single.Parse(tokens[1], CultureInfo.InvariantCulture); y = Single.Parse(tokens[2], CultureInfo.InvariantCulture); state.VertexTextureCoords.Add(new Point(x, y)); break; case "vn": // Vertex normal if (tokens.Length != 4) { throw new IOException("Vertex normals in the OBJ file must have 3 coordinates."); } x = Single.Parse(tokens[1], CultureInfo.InvariantCulture); y = Single.Parse(tokens[2], CultureInfo.InvariantCulture); z = Single.Parse(tokens[3], CultureInfo.InvariantCulture); state.VertexNormals.Add(new Vector3D(x, y, z)); break; case "f": // Face faceVertices = new Vertex[tokens.Length - 1]; vt = new int?[tokens.Length - 1]; vn = new int?[tokens.Length - 1]; // Parse vertex/texture coordinate/normal indices for (int i = 0; i < faceVertices.Length; ++i) { string[] vertexTokens = tokens[i + 1].Split("/".ToCharArray()); v = Int32.Parse(vertexTokens[0]); if (vertexTokens.Length > 1 && vertexTokens[1].Length > 0) { vt[i] = Int32.Parse(vertexTokens[1]); } else { Traits.HasTextureCoordinates = false; } if (vertexTokens.Length > 2 && vertexTokens[2].Length > 0) { vn[i] = Int32.Parse(vertexTokens[2]); } else { Traits.HasFaceVertexNormals = false; } faceVertices[i] = Vertices[v.Value - 1]; } Face[] addedFaces = Faces.AddTriangles(faceVertices); // Set texture coordinates and normals if any are given for (int i = 0; i < faceVertices.Length; ++i) { Halfedge faceVertex; if (vt[i].HasValue || vn[i].HasValue) { foreach (Face f in addedFaces) { faceVertex = f.FindHalfedgeTo(faceVertices[i]); if (faceVertex != null) // Make sure vertex belongs to face if triangularization is on { if (vt[i].HasValue) { faceVertex.Traits.TextureCoordinate = state.VertexTextureCoords[vt[i].Value - 1]; } if (vn[i].HasValue) { faceVertex.Traits.Normal = state.VertexNormals[vn[i].Value - 1]; } } } } } break; } } }