479 lines
17 KiB
C#
479 lines
17 KiB
C#
/*
|
|
(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<Material> matlList = new List<Material>();
|
|
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<Vector3> vertices = new List<Vector3>();
|
|
private List<Vector3> normals = new List<Vector3>();
|
|
private List<Vector2> uvs = new List<Vector2>();
|
|
|
|
//UMESH LISTS
|
|
private List<Vector3> uvertices = new List<Vector3>();
|
|
private List<Vector3> unormals = new List<Vector3>();
|
|
private List<Vector2> uuvs = new List<Vector2>();
|
|
|
|
//MESH CONSTRUCTION
|
|
private List<string> materialNames = new List<string>();
|
|
private List<string> objectNames = new List<string>();
|
|
private Dictionary<string, int> hashtable = new Dictionary<string, int>();
|
|
private List<OBJFace> faceList = new List<OBJFace>();
|
|
private string cmaterial = "";
|
|
private string cmesh = "default";
|
|
|
|
//CACHE
|
|
private List<string> mtlFiles = new List<string>();
|
|
private List<Material> materialCache = new List<Material>();
|
|
|
|
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<Vector3> processedVertices = new List<Vector3>();
|
|
List<Vector3> processedNormals = new List<Vector3>();
|
|
List<Vector2> processedUVs = new List<Vector2>();
|
|
List<int[]> processedIndexes = new List<int[]>();
|
|
Dictionary<int, int> remapTable = new Dictionary<int, int>();
|
|
|
|
//POPULATE MESH
|
|
List<string> meshMaterialNames = new List<string>();
|
|
|
|
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<MeshFilter>();
|
|
MeshRenderer mr = subObject.AddComponent<MeshRenderer>();
|
|
|
|
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;
|
|
}
|
|
} |