/* (C) 2015 AARO4130 DO NOT USE PARTS OF, OR THE ENTIRE SCRIPT, AND CLAIM AS YOUR OWN WORK */ using System; using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; #if UNITY_EDITOR using UnityEditor; #endif public class OBJLoader { public static bool splitByMaterial = false; public static string[] searchPaths = new string[] { "", "%FileName%_Textures" + Path.DirectorySeparatorChar }; public static Material defaultMaterial; public static Shader defaultShader; //structures struct OBJFace { public string materialName; public string meshName; public int[] indexes; } //functions #if UNITY_EDITOR [MenuItem("GameObject/Import From OBJ")] static void ObjLoadMenu() { string pth = UnityEditor.EditorUtility.OpenFilePanel("Import OBJ", "", "obj"); if (!string.IsNullOrEmpty(pth)) { System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch(); s.Start(); new OBJLoader(pth).LoadOBJFile(); Debug.Log("OBJ load took " + s.ElapsedMilliseconds + "ms"); s.Stop(); } } #endif private Vector3 ParseVectorFromCMPS(string[] cmps) { float x = float.Parse(cmps[1]); float y = float.Parse(cmps[2]); if (cmps.Length == 4) { float z = float.Parse(cmps[3]); return new Vector3(x, y, z); } return new Vector2(x, y); } private Color ParseColorFromCMPS(string[] cmps, float scalar = 1.0f) { float Kr = float.Parse(cmps[1]) * scalar; float Kg = float.Parse(cmps[2]) * scalar; float Kb = float.Parse(cmps[3]) * scalar; return new Color(Kr, Kg, Kb); } public static string OBJGetFilePath(string path, string basePath, string fileName) { foreach (string sp in searchPaths) { string s = sp.Replace("%FileName%", fileName); if (File.Exists(basePath + s + path)) { return basePath + s + path; } else if (File.Exists(path)) { return path; } } return null; } public OBJLoader(string filePath) { this.filePath = filePath; } private Material[] LoadMTLFile(string fn) { Material currentMaterial = null; List matlList = new List(); FileInfo mtlFileInfo = new FileInfo(fn); string baseFileName = Path.GetFileNameWithoutExtension(fn); string mtlFileDirectory = mtlFileInfo.Directory.FullName + Path.DirectorySeparatorChar; foreach (string ln in File.ReadAllLines(fn)) { string l = ln.Trim().Replace(" ", " "); string[] cmps = l.Split(' '); string data = l.Remove(0, l.IndexOf(' ') + 1); if (cmps[0] == "newmtl") { if (currentMaterial != null) { matlList.Add(currentMaterial); } currentMaterial = new Material(defaultShader); currentMaterial.name = data; } else if (cmps[0] == "Kd") { currentMaterial.SetColor("_Color", ParseColorFromCMPS(cmps)); } else if (cmps[0] == "map_Kd") { //TEXTURE string fpth = OBJGetFilePath(data, mtlFileDirectory, baseFileName); if (fpth != null) currentMaterial.SetTexture("_MainTex", TextureLoader.LoadTexture(fpth)); } else if (cmps[0] == "map_Bump") { //TEXTURE string fpth = OBJGetFilePath(data, mtlFileDirectory, baseFileName); if (fpth != null) { currentMaterial.SetTexture("_BumpMap", TextureLoader.LoadTexture(fpth, true)); currentMaterial.EnableKeyword("_NORMALMAP"); } } else if (cmps[0] == "Ks") { currentMaterial.SetColor("_SpecColor", ParseColorFromCMPS(cmps)); } else if (cmps[0] == "Ka") { currentMaterial.SetColor("_EmissionColor", ParseColorFromCMPS(cmps, 0.05f)); currentMaterial.EnableKeyword("_EMISSION"); } else if (cmps[0] == "d") { float visibility = float.Parse(cmps[1]); if (visibility < 1) { Color temp = currentMaterial.color; temp.a = visibility; currentMaterial.SetColor("_Color", temp); //TRANSPARENCY ENABLER currentMaterial.SetFloat("_Mode", 3); currentMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); currentMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); currentMaterial.SetInt("_ZWrite", 0); currentMaterial.DisableKeyword("_ALPHATEST_ON"); currentMaterial.EnableKeyword("_ALPHABLEND_ON"); currentMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON"); currentMaterial.renderQueue = 3000; } } else if (cmps[0] == "Ns") { float Ns = float.Parse(cmps[1]); Ns = (Ns / 1000); currentMaterial.SetFloat("_Glossiness", Ns); } } if (currentMaterial != null) { matlList.Add(currentMaterial); } return matlList.ToArray(); } private string filePath; private string meshName; private bool hasNormals = false; //OBJ LISTS private List vertices = new List(); private List normals = new List(); private List uvs = new List(); //UMESH LISTS private List uvertices = new List(); private List unormals = new List(); private List uuvs = new List(); //MESH CONSTRUCTION private List materialNames = new List(); private List objectNames = new List(); private Dictionary hashtable = new Dictionary(); private List faceList = new List(); private string cmaterial = ""; private string cmesh = "default"; //CACHE private List mtlFiles = new List(); private List materialCache = new List(); public GameObject LoadOBJFile() { ParseObjFile(); GameObject parentObject = BuildUnityObjects(); return parentObject; } public void ParseObjFile() { meshName = Path.GetFileNameWithoutExtension(filePath); FileInfo OBJFileInfo = new FileInfo(filePath); foreach (string ln in File.ReadAllLines(filePath)) { if (ln.Length > 0 && ln[0] != '#') { string l = ln.Trim().Replace(" ", " "); string[] cmps = l.Split(' '); string data = l.Remove(0, l.IndexOf(' ') + 1); if (cmps[0] == "mtllib") { //load cache string pth = OBJGetFilePath(data, OBJFileInfo.Directory.FullName + Path.DirectorySeparatorChar, meshName); if (pth != null) mtlFiles.Add(pth); } else if ((cmps[0] == "g" || cmps[0] == "o") && splitByMaterial == false) { cmesh = data; if (!objectNames.Contains(cmesh)) { objectNames.Add(cmesh); } } else if (cmps[0] == "usemtl") { cmaterial = data; if (!materialNames.Contains(cmaterial)) { materialNames.Add(cmaterial); } if (splitByMaterial) { if (!objectNames.Contains(cmaterial)) { objectNames.Add(cmaterial); } } } else if (cmps[0] == "v") { //VERTEX vertices.Add(ParseVectorFromCMPS(cmps)); } else if (cmps[0] == "vn") { //VERTEX NORMAL normals.Add(ParseVectorFromCMPS(cmps)); } else if (cmps[0] == "vt") { //VERTEX UV uvs.Add(ParseVectorFromCMPS(cmps)); } else if (cmps[0] == "f") { int[] indexes = new int[cmps.Length - 1]; for (int i = 1; i < cmps.Length; i++) { string felement = cmps[i]; int vertexIndex = -1; int normalIndex = -1; int uvIndex = -1; if (felement.Contains("//")) { //doubleslash, no UVS. string[] elementComps = felement.Split('/'); vertexIndex = int.Parse(elementComps[0]) - 1; normalIndex = int.Parse(elementComps[2]) - 1; } else if (felement.Count(x => x == '/') == 2) { //contains everything string[] elementComps = felement.Split('/'); vertexIndex = int.Parse(elementComps[0]) - 1; uvIndex = int.Parse(elementComps[1]) - 1; normalIndex = int.Parse(elementComps[2]) - 1; } else if (!felement.Contains("/")) { //just vertex inedx vertexIndex = int.Parse(felement) - 1; } else { //vertex and uv string[] elementComps = felement.Split('/'); vertexIndex = int.Parse(elementComps[0]) - 1; uvIndex = int.Parse(elementComps[1]) - 1; } string hashEntry = vertexIndex + "|" + normalIndex + "|" + uvIndex; if (hashtable.ContainsKey(hashEntry)) { indexes[i - 1] = hashtable[hashEntry]; } else { //create a new hash entry indexes[i - 1] = hashtable.Count; hashtable[hashEntry] = hashtable.Count; uvertices.Add(vertices[vertexIndex]); if (normalIndex < 0 || (normalIndex > (normals.Count - 1))) { unormals.Add(Vector3.zero); } else { hasNormals = true; unormals.Add(normals[normalIndex]); } if (uvIndex < 0 || (uvIndex > (uvs.Count - 1))) { uuvs.Add(Vector2.zero); } else { uuvs.Add(uvs[uvIndex]); } } } if (indexes.Length < 5 && indexes.Length >= 3) { OBJFace f1 = new OBJFace(); f1.materialName = cmaterial; f1.indexes = new int[] { indexes[0], indexes[1], indexes[2] }; f1.meshName = (splitByMaterial) ? cmaterial : cmesh; faceList.Add(f1); if (indexes.Length > 3) { OBJFace f2 = new OBJFace(); f2.materialName = cmaterial; f2.meshName = (splitByMaterial) ? cmaterial : cmesh; f2.indexes = new int[] { indexes[2], indexes[3], indexes[0] }; faceList.Add(f2); } } } } } if (objectNames.Count == 0) objectNames.Add("default"); } public GameObject BuildUnityObjects() { //build objects GameObject parentObject = new GameObject(meshName); foreach (var matfile in mtlFiles) { materialCache.AddRange(LoadMTLFile(matfile)); } foreach (string obj in objectNames) { GameObject subObject = new GameObject(obj); subObject.transform.parent = parentObject.transform; subObject.transform.localScale = new Vector3(-1, 1, 1); //Create mesh Mesh m = new Mesh(); m.name = obj; //LISTS FOR REORDERING List processedVertices = new List(); List processedNormals = new List(); List processedUVs = new List(); List processedIndexes = new List(); Dictionary remapTable = new Dictionary(); //POPULATE MESH List meshMaterialNames = new List(); OBJFace[] ofaces = faceList.Where(x => x.meshName == obj).ToArray(); foreach (string mn in materialNames) { OBJFace[] faces = ofaces.Where(x => x.materialName == mn).ToArray(); if (faces.Length > 0) { meshMaterialNames.Add(mn); if (m.subMeshCount != meshMaterialNames.Count) m.subMeshCount = meshMaterialNames.Count; var indexes = faces.SelectMany(f => f.indexes).ToArray(); for (int i = 0; i < indexes.Length; i++) { int idx = indexes[i]; //build remap table if (remapTable.ContainsKey(idx)) { //ezpz indexes[i] = remapTable[idx]; } else { processedVertices.Add(uvertices[idx]); processedNormals.Add(unormals[idx]); processedUVs.Add(uuvs[idx]); remapTable[idx] = processedVertices.Count - 1; indexes[i] = remapTable[idx]; } } processedIndexes.Add(indexes); } } //apply stuff m.vertices = processedVertices.ToArray(); m.normals = processedNormals.ToArray(); m.uv = processedUVs.ToArray(); for (int i = 0; i < processedIndexes.Count; i++) { m.SetTriangles(processedIndexes[i], i); } if (!hasNormals) { m.RecalculateNormals(); } m.RecalculateBounds(); MeshFilter mf = subObject.AddComponent(); MeshRenderer mr = subObject.AddComponent(); Material[] processedMaterials = new Material[meshMaterialNames.Count]; for (int i = 0; i < meshMaterialNames.Count; i++) { if (materialCache == null) { processedMaterials[i] = defaultMaterial; } else { Material mfn = materialCache.Find(x => x.name == meshMaterialNames[i]); if (mfn == null) { processedMaterials[i] = defaultMaterial; } else { processedMaterials[i] = mfn; } } processedMaterials[i].name = meshMaterialNames[i]; } mr.materials = processedMaterials; mf.mesh = m; } return parentObject; } }