using System; using System.Collections.Generic; using System.Text; using System.IO; using OpenMetaverse; using OpenMetaverse.Rendering; namespace PrimWorkshop { public static class MeshToOBJ { public static bool MeshesToOBJ(List meshes, string filename) { StringBuilder obj = new StringBuilder(); StringBuilder mtl = new StringBuilder(); FileInfo objFileInfo = new FileInfo(filename); string mtlFilename = objFileInfo.FullName.Substring(objFileInfo.DirectoryName.Length + 1, objFileInfo.FullName.Length - (objFileInfo.DirectoryName.Length + 1) - 4) + ".mtl"; obj.AppendLine("# Created by libprimrender"); obj.AppendLine("mtllib ./" + mtlFilename); obj.AppendLine(); mtl.AppendLine("# Created by libprimrender"); mtl.AppendLine(); for (int i = 0; i < meshes.Count; i++) { FacetedMesh mesh = meshes[i]; for (int j = 0; j < mesh.Faces.Count; j++) { Face face = mesh.Faces[j]; if (face.Vertices.Count > 2) { string mtlName = String.Format("material{0}-{1}", i, face.ID); Primitive.TextureEntryFace tex = face.TextureFace; string texName = tex.TextureID.ToString() + ".tga"; // FIXME: Convert the source to TGA (if needed) and copy to the destination float shiny = 0.00f; switch (tex.Shiny) { case Shininess.High: shiny = 1.00f; break; case Shininess.Medium: shiny = 0.66f; break; case Shininess.Low: shiny = 0.33f; break; } obj.AppendFormat("g face{0}-{1}{2}", i, face.ID, Environment.NewLine); mtl.AppendLine("newmtl " + mtlName); mtl.AppendFormat("Ka {0} {1} {2}{3}", tex.RGBA.R, tex.RGBA.G, tex.RGBA.B, Environment.NewLine); mtl.AppendFormat("Kd {0} {1} {2}{3}", tex.RGBA.R, tex.RGBA.G, tex.RGBA.B, Environment.NewLine); //mtl.AppendFormat("Ks {0} {1} {2}{3}"); mtl.AppendLine("Tr " + tex.RGBA.A); mtl.AppendLine("Ns " + shiny); mtl.AppendLine("illum 1"); if (tex.TextureID != UUID.Zero && tex.TextureID != Primitive.TextureEntry.WHITE_TEXTURE) mtl.AppendLine("map_Kd ./" + texName); mtl.AppendLine(); // Write the vertices, texture coordinates, and vertex normals for this side for (int k = 0; k < face.Vertices.Count; k++) { Vertex vertex = face.Vertices[k]; #region Vertex Vector3 pos = vertex.Position; // Apply scaling pos *= mesh.Prim.Scale; // Apply rotation pos *= mesh.Prim.Rotation; // The root prim position is sim-relative, while child prim positions are // parent-relative. We want to apply parent-relative translations but not // sim-relative ones if (mesh.Prim.ParentID != 0) pos += mesh.Prim.Position; obj.AppendFormat("v {0} {1} {2}{3}", pos.X, pos.Y, pos.Z, Environment.NewLine); #endregion Vertex #region Texture Coord obj.AppendFormat("vt {0} {1}{2}", vertex.TexCoord.X, vertex.TexCoord.Y, Environment.NewLine); #endregion Texture Coord #region Vertex Normal // HACK: Sometimes normals are getting set to if (!Single.IsNaN(vertex.Normal.X) && !Single.IsNaN(vertex.Normal.Y) && !Single.IsNaN(vertex.Normal.Z)) obj.AppendFormat("vn {0} {1} {2}{3}", vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z, Environment.NewLine); else obj.AppendLine("vn 0.0 1.0 0.0"); #endregion Vertex Normal } obj.AppendFormat("# {0} vertices{1}", face.Vertices.Count, Environment.NewLine); obj.AppendLine(); obj.AppendLine("usemtl " + mtlName); #region Elements // Write all of the faces (triangles) for this side for (int k = 0; k < face.Indices.Count / 3; k++) { obj.AppendFormat("f -{0}/-{0}/-{0} -{1}/-{1}/-{1} -{2}/-{2}/-{2}{3}", face.Vertices.Count - face.Indices[k * 3 + 0], face.Vertices.Count - face.Indices[k * 3 + 1], face.Vertices.Count - face.Indices[k * 3 + 2], Environment.NewLine); } obj.AppendFormat("# {0} elements{1}", face.Indices.Count / 3, Environment.NewLine); obj.AppendLine(); #endregion Elements } } } try { File.WriteAllText(filename, obj.ToString()); File.WriteAllText(mtlFilename, mtl.ToString()); } catch (Exception) { return false; } return true; } } }