/// <summary> /// Write the contents of a single obj & mtl from a set of models. /// </summary> /// <param name="name"></param> /// <param name="models"></param> /// <param name="objContents"></param> /// <param name="mtlContents"></param> /// <param name="textures"></param> /// <param name="options"></param> /// <returns></returns> public static bool Export(string name, IEnumerable <Model> models, out string objContents, out string mtlContents, out List <string> textures, ObjOptions options = null) { if (models == null || models.Count() < 1) { objContents = null; mtlContents = null; textures = null; return(false); } Dictionary <Material, string> materialMap = null; if (options == null) { options = new ObjOptions(); } mtlContents = WriteMtlContents(models, options, out materialMap, out textures); objContents = WriteObjContents(name, models, materialMap, options); return(true); }
/// <summary> /// Write the material file for an OBJ. This function handles making the list of Materials unique & ensuring unique names for each group. Material to named mtl group are stored in materialMap. /// </summary> /// <param name="models"></param> /// <param name="options"></param> /// <param name="materialMap"></param> /// <param name="textures"></param> /// <returns></returns> static string WriteMtlContents(IEnumerable <Model> models, ObjOptions options, out Dictionary <Material, string> materialMap, out List <string> textures) { materialMap = new Dictionary <Material, string>(); foreach (Model model in models) { for (int i = 0, c = model.submeshCount; i < c; i++) { Material material = model.materials[i]; if (material == null) { continue; } if (!materialMap.ContainsKey(material)) { string escapedName = material.name.Replace(" ", "_"); string name = escapedName; int nameIncrement = 1; while (materialMap.Any(x => x.Value.Equals(name))) { name = string.Format("{0}_{1}", escapedName, nameIncrement++); } materialMap.Add(material, name); } } } StringBuilder sb = new StringBuilder(); textures = new List <string>(); foreach (KeyValuePair <Material, string> group in materialMap) { Material mat = group.Key; sb.AppendLine(string.Format("newmtl {0}", group.Value)); // Texture maps if (mat.shader != null) { foreach (var texPropertyName in mat.GetTexturePropertyNames()) { Texture texture = mat.GetTexture(texPropertyName); string textureName = null; #if UNITY_EDITOR string path = texture != null?UnityEditor.AssetDatabase.GetAssetPath(texture) : null; if (!string.IsNullOrEmpty(path)) { if (options.copyTextures) { textures.Add(path); } // remove "Assets/" from start of path path = path.Substring(7, path.Length - 7); textureName = options.copyTextures ? Path.GetFileName(path) : string.Format("{0}/{1}", Application.dataPath, path); } else #endif if (texture) { textureName = texture.name; } if (!string.IsNullOrEmpty(textureName)) { string mtlKey; if (s_TextureMapKeys.TryGetValue(texPropertyName, out mtlKey)) { Vector2 offset = mat.GetTextureOffset(texPropertyName); Vector2 scale = mat.GetTextureScale(texPropertyName); if (options.textureOffsetScale) { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0} -o {1} {2} -s {3} {4} {5}", mtlKey, offset.x, offset.y, scale.x, scale.y, textureName)); } else { sb.AppendLine(string.Format("{0} {1}", mtlKey, textureName)); } } } } } if (mat.HasProperty("_Color")) { Color color = mat.color; // Diffuse sb.AppendLine(string.Format("Kd {0}", string.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", color.r, color.g, color.b))); // Transparency sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "d {0}", color.a)); } else { sb.AppendLine("Kd 1.0 1.0 1.0"); sb.AppendLine("d 1.0"); } sb.AppendLine(); } return(sb.ToString()); }
static string WriteObjContents(string name, IEnumerable <Model> models, Dictionary <Material, string> materialMap, ObjOptions options) { // Empty names in OBJ groups can crash some 3d programs (meshlab) if (string.IsNullOrEmpty(name)) { name = "default"; } StringBuilder sb = new StringBuilder(); sb.AppendLine("# ARFace.ObjExporter"); sb.AppendLine(string.Format("# {0}", System.DateTime.Now)); sb.AppendLine(); sb.AppendLine(string.Format("mtllib ./{0}.mtl", name.Replace(" ", "_"))); sb.AppendLine(string.Format("o {0}", name)); sb.AppendLine(); // obj orders indices 1 indexed int positionOffset = 1; int normalOffset = 1; int textureOffset = 1; bool reverseWinding = options.handedness == ObjOptions.Handedness.Right; float handedness = options.handedness == ObjOptions.Handedness.Left ? 1f : -1f; foreach (Model model in models) { int subMeshCount = model.submeshCount; Matrix4x4 matrix = options.applyTransforms ? model.matrix : Matrix4x4.identity; int vertexCount = model.vertexCount; Vector3[] positions = model.vertices; Color[] colors = model.mesh.colors; Vector2[] textures0 = model.mesh.uv; Vector3[] normals = model.mesh.normals; if (colors != null && colors.Length == 0) { colors = null; } if (textures0 != null && textures0.Length == 0) { textures0 = null; } if (normals != null && normals.Length == 0) { normals = null; } // Can skip this entirely if handedness matches Unity & not applying transforms. // matrix is set to identity if not applying transforms. if (options.handedness != ObjOptions.Handedness.Left || options.applyTransforms) { for (int i = 0; i < vertexCount; i++) { if (positions != null) { positions[i] = matrix.MultiplyPoint3x4(positions[i]); positions[i].x *= handedness; } if (normals != null) { normals[i] = matrix.MultiplyVector(normals[i]); normals[i].x *= handedness; } } } sb.AppendLine(string.Format("g {0}", model.name)); Dictionary <int, int> positionIndexMap; var positionCount = AppendPositions(sb, positions, colors, true, options.vertexColors, out positionIndexMap); sb.AppendLine(); Dictionary <int, int> textureIndexMap; var textureCount = AppendArrayVec2(sb, textures0, "vt", true, out textureIndexMap); sb.AppendLine(); Dictionary <int, int> normalIndexMap; var normalCount = AppendArrayVec3(sb, normals, "vn", true, out normalIndexMap); sb.AppendLine(); // Material assignment for (int submeshIndex = 0; submeshIndex < subMeshCount; submeshIndex++) { Submesh submesh = model.submeshes[submeshIndex]; string materialName = ""; if (materialMap.TryGetValue(model.materials[submeshIndex], out materialName)) { sb.AppendLine(string.Format("usemtl {0}", materialName)); } else { sb.AppendLine(string.Format("usemtl {0}", "null")); } int[] indexes = submesh.m_Indexes; int inc = submesh.m_Topology == MeshTopology.Quads ? 4 : 3; int inc1 = inc - 1; int o0 = reverseWinding ? inc1 : 0; int o1 = reverseWinding ? inc1 - 1 : 1; int o2 = reverseWinding ? inc1 - 2 : 2; int o3 = reverseWinding ? inc1 - 3 : 3; for (int ff = 0; ff < indexes.Length; ff += inc) { int p0 = positionIndexMap[indexes[ff + o0]] + positionOffset; int p1 = positionIndexMap[indexes[ff + o1]] + positionOffset; int p2 = positionIndexMap[indexes[ff + o2]] + positionOffset; int t0 = -1; int t1 = -1; int t2 = -1; if (textureIndexMap != null) { t0 = textureIndexMap[indexes[ff + o0]] + textureOffset; t1 = textureIndexMap[indexes[ff + o1]] + textureOffset; t2 = textureIndexMap[indexes[ff + o2]] + textureOffset; } int n0 = -1; int n1 = -1; int n2 = -1; if (normalIndexMap != null) { n0 = normalIndexMap[indexes[ff + o0]] + normalOffset; n1 = normalIndexMap[indexes[ff + o1]] + normalOffset; n2 = normalIndexMap[indexes[ff + o2]] + normalOffset; } if (inc == 4) { int p3 = positionIndexMap[indexes[ff + o3]] + positionOffset; int n3 = normalIndexMap[indexes[ff + o3]] + normalOffset; int t3 = textureIndexMap[indexes[ff + o3]] + textureOffset; // sb.AppendLine(string.Format(CultureInfo.InvariantCulture, // "f {0}/{4}/{8} {1}/{5}/{9} {2}/{6}/{10} {3}/{7}/{11}", // p0, p1, p2, p3, // t0, t1, t2, t3, // n0, n1, n2, n3 // )); throw new NotSupportedException(); } else { string format; if (textureIndexMap != null && normalIndexMap != null) { format = "f {0}/{3}/{6} {1}/{4}/{7} {2}/{5}/{8}"; } else if (textureIndexMap == null && normalIndexMap != null) { format = "f {0}//{6} {1}//{7} {2}//{8}"; } else if (textureIndexMap != null && normalIndexMap == null) { format = "f {0}/{3} {1}/{4} {2}/{5}"; } else { format = "f {0} {1} {2}"; } sb.AppendLine(string.Format(CultureInfo.InvariantCulture, format, p0, p1, p2, t0, t1, t2, n0, n1, n2 )); } } sb.AppendLine(); } positionOffset += positionCount; normalOffset += normalCount; textureOffset += textureCount; } return(sb.ToString()); }