public static void ExportOBJ(XObject mesh, string filename, Matrix transform, bool flipV = true, string textureExtension = null, bool splitMaterials = false) { if (mesh.DataType.NameData != "Mesh") { throw new ArgumentException("'mesh' must be a Mesh object!"); } int vertexCount = (int)mesh["nVertices"].Values[0]; int faceCount = (int)mesh["nFaces"].Values[0]; XObject meshNormals = null; XObject meshTextureCoords = null; XObject meshMaterialList = null; XObject meshVertexColors = null; foreach (XChildObject child in mesh.Children) { if (child.Object.DataType.NameData == "MeshNormals") { meshNormals = child.Object; } else if (child.Object.DataType.NameData == "MeshTextureCoords") { meshTextureCoords = child.Object; } else if (child.Object.DataType.NameData == "MeshMaterialList") { meshMaterialList = child.Object; } else if ((LOMNTool.Program.Config.GetValueOrDefault("OBJ", "ExportVertexColors", "false").ToLower() == "true" || LOMNTool.Program.Config.GetValueOrDefault("OBJ", "ExportVertexColors", "false").ToLower() == "zbrush") && child.Object.DataType.NameData == "MeshVertexColors") { meshVertexColors = child.Object; } } using (System.IO.StreamWriter writer = new System.IO.StreamWriter(filename, false)) using (System.IO.StreamWriter matWriter = new System.IO.StreamWriter(System.IO.Path.ChangeExtension(filename, ".mtl"))) { writer.WriteLine("mtllib " + System.IO.Path.GetFileNameWithoutExtension(filename) + ".mtl"); // Write materials for (int i = 0; i < (int)meshMaterialList["nMaterials"].Values[0]; i++) { matWriter.WriteLine("newmtl Material_" + i.ToString().PadLeft(3, '0') + "_Mat"); XObject material = meshMaterialList[i].Object; XObjectStructure faceColor = (XObjectStructure)material["faceColor"].Values[0]; float specExponent = (float)(double)material["power"].Values[0]; XObjectStructure specularColor = (XObjectStructure)material["specularColor"].Values[0]; XObjectStructure emissiveColor = (XObjectStructure)material["emissiveColor"].Values[0]; matWriter.WriteLine("Kd " + (double)faceColor["red"].Values[0] + " " + (double)faceColor["green"].Values[0] + " " + (double)faceColor["blue"].Values[0]); matWriter.WriteLine("d " + (double)faceColor["alpha"].Values[0]); matWriter.WriteLine("Tr " + (1.0 - (double)faceColor["alpha"].Values[0])); matWriter.WriteLine("Ns " + specExponent); // Hack for games with white spec color but zero exponent that means no spec if (specExponent > 0.0) { matWriter.WriteLine("Ks " + (double)specularColor["red"].Values[0] + " " + (double)specularColor["green"].Values[0] + " " + (double)specularColor["blue"].Values[0]); } else { matWriter.WriteLine("Ks 0 0 0"); } matWriter.WriteLine("Ke " + (double)emissiveColor["red"].Values[0] + " " + (double)emissiveColor["green"].Values[0] + " " + (double)emissiveColor["blue"].Values[0]); // Look for the possible TextureFilename foreach (XChildObject child in material.Children) { if (child.Object.DataType.NameData == "TextureFilename") { string texFilename = (string)child.Object["filename"].Values[0]; if (textureExtension != null) { texFilename = System.IO.Path.ChangeExtension(texFilename, textureExtension); } matWriter.WriteLine("map_Kd " + texFilename); break; } } } // Gather positions for (int i = 0; i < vertexCount; i++) { Vector3 pos = Vector((XObjectStructure)mesh["vertices"].Values[i]); Vector4 pos2 = Vector3.Transform(pos, transform); writer.WriteLine("v " + pos2.X + " " + pos2.Y + " " + pos2.Z); } /*if (LOMNTool.Program.Config.GetValueOrDefault("OBJ", "ExportVertexColors", "false").ToLower() == "zbrush" && meshVertexColors != null) * { * writer.WriteLine("\n\n# Here goes my attempt at writing the MRGB block for ZBrush polypaint:"); * * writer.Write("#MRGB "); * foreach * * writer.WriteLine("# End of MRGB block"); * }*/ // Gather normals int normalCount = (int)meshNormals["nNormals"].Values[0]; for (int i = 0; i < normalCount; i++) { Vector3 norm = Vector((XObjectStructure)meshNormals["normals"].Values[i]); Vector4 norm2 = Vector3.Transform(norm, transform); writer.WriteLine("vn " + norm2.X + " " + norm2.Y + " " + norm2.Z); } // Gather texture coordinates int uvCount = (int)meshTextureCoords["nTextureCoords"].Values[0]; if (uvCount != vertexCount) { throw new FormatException("Different number of vertices and texture coordinates!"); } for (int i = 0; i < uvCount; i++) { Vector2 uv = TexCoord((XObjectStructure)meshTextureCoords["textureCoords"].Values[i]); if (flipV) { uv.Y = 1.0f - uv.Y; } writer.WriteLine("vt " + uv.X + " " + uv.Y); } if (meshVertexColors != null) { int colorCount = (int)meshVertexColors["nVertexColors"].Values[0]; if (colorCount != vertexCount) { throw new FormatException("ExportOBJ: Mesh vertex count (" + vertexCount + ") isn't equal to the vertex color count! (" + colorCount + ")"); } Vector4[] colors = new Vector4[colorCount]; foreach (XObjectStructure value in meshVertexColors["vertexColors"].Values) { int index = (int)value["index"].Values[0]; Vector4 color = XUtils.ColorRGBA((XObjectStructure)value.Members[1].Values[0]); // ["indexColor"] if (colors[index] == Vector4.Zero) { colors[index] = color; } else { Console.WriteLine("ExportCOLLADA: Multiple colors defined for vertex " + index + "!"); } } switch (LOMNTool.Program.Config.GetValueOrDefault("OBJ", "ExportVertexColors", "false").ToLower()) { case "true": for (int i = 0; i < colorCount; i++) { writer.WriteLine("vc " + colors[i].X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + colors[i].Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + colors[i].Z.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + colors[i].W.ToString(System.Globalization.CultureInfo.InvariantCulture)); } break; case "zbrush": writer.WriteLine("\n\n# Here goes my attempt at writing the MRGB block for ZBrush polypaint:"); writer.Write("#MRGB "); for (int i = 0; i < colors.Length; i++) { writer.Write("FF"); writer.Write(((byte)(colors[i].X * colors[i].W * 255)).ToString("X2")); writer.Write(((byte)(colors[i].Y * colors[i].W * 255)).ToString("X2")); writer.Write(((byte)(colors[i].Z * colors[i].W * 255)).ToString("X2")); if (i % 64 == 0 && i > 0 && i < colors.Length - 1) { writer.Write("\n#MRGB "); } } writer.WriteLine("\n# End of MRGB block\n"); break; } } // Write each face int mtl = -1; for (int i = 0; i < faceCount; i++) { if ((int)(meshNormals["nFaceNormals"].Values[0]) == 0) { Console.WriteLine("[ERROR]: No normals!"); } XObjectStructure face = (XObjectStructure)mesh["faces"].Values[i]; XObjectStructure faceNormals = (XObjectStructure)meshNormals["faceNormals"].Values[i]; int newMaterialIndex = (int)meshMaterialList["faceIndexes"].Values[i]; if (newMaterialIndex != mtl) { if (splitMaterials) { writer.WriteLine("g Material_" + newMaterialIndex.ToString().PadLeft(3, '0')); } writer.WriteLine("usemtl Material_" + newMaterialIndex.ToString().PadLeft(3, '0') + "_Mat"); mtl = newMaterialIndex; } writer.Write("f "); for (int v = 0; v < (int)face["nFaceVertexIndices"].Values[0]; v++) { int vIndex = (int)face["faceVertexIndices"].Values[v] + 1; int nIndex = (int)faceNormals["faceVertexIndices"].Values[v] + 1; writer.Write(vIndex + "/" + vIndex + "/" + nIndex + (meshVertexColors != null ? "/" + vIndex : "") + " "); } writer.WriteLine(); } } }