diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82925af --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.meta diff --git a/OBJ/src/GeometryBuffer.cs b/OBJ/src/GeometryBuffer.cs index 4895df3..443d258 100644 --- a/OBJ/src/GeometryBuffer.cs +++ b/OBJ/src/GeometryBuffer.cs @@ -53,13 +53,16 @@ public GeometryBuffer() { public void PushObject(string name) { //Debug.Log("Adding new object " + name + ". Current is empty: " + isEmpty); + string currentMaterial = current.groups [current.groups.Count - 1].materialName; + if(isEmpty) objects.Remove(current); ObjectData n = new ObjectData(); n.name = name; objects.Add(n); - + GroupData g = new GroupData(); + g.materialName = currentMaterial; g.name = "default"; n.groups.Add(g); @@ -68,8 +71,11 @@ public void PushObject(string name) { } public void PushGroup(string name) { + string currentMaterial = current.groups [current.groups.Count - 1].materialName; + if(curgr.isEmpty) current.groups.Remove(curgr); GroupData g = new GroupData(); + g.materialName = currentMaterial; if (name == null) { name = "Unnamed-"+unnamedGroupIndex; unnamedGroupIndex++; @@ -126,7 +132,7 @@ public void Trace() { public bool hasNormals { get { return normals.Count > 0; } } public static int MAX_VERTICES_LIMIT_FOR_A_MESH = 64999; - + public void PopulateMeshes(GameObject[] gs, Dictionary mats) { if(gs.Length != numObjects) return; // Should not happen unless obj file is corrupt... Debug.Log("PopulateMeshes GameObjects count:"+gs.Length); @@ -140,7 +146,7 @@ public void PopulateMeshes(GameObject[] gs, Dictionary mats) { Vector3[] tvertices = new Vector3[od.allFaces.Count]; Vector2[] tuvs = new Vector2[od.allFaces.Count]; Vector3[] tnormals = new Vector3[od.allFaces.Count]; - + int k = 0; foreach(FaceIndices fi in od.allFaces) { if (k >= MAX_VERTICES_LIMIT_FOR_A_MESH) { @@ -152,18 +158,18 @@ public void PopulateMeshes(GameObject[] gs, Dictionary mats) { if(hasNormals && fi.vn >= 0) tnormals[k] = normals[fi.vn]; k++; } - + Mesh m = (gs[i].GetComponent(typeof(MeshFilter)) as MeshFilter).mesh; m.vertices = tvertices; if(hasUVs) m.uv = tuvs; if(objectHasNormals) m.normals = tnormals; - + if(od.groups.Count == 1) { Debug.Log("PopulateMeshes only one group: "+od.groups[0].name); GroupData gd = od.groups[0]; string matName = (gd.materialName != null) ? gd.materialName : "default"; // MAYBE: "default" may not enough. if (mats.ContainsKey(matName)) { - gs[i].renderer.material = mats[matName]; + gs[i].GetComponent().material = mats[matName]; Debug.Log("PopulateMeshes mat:"+matName+" set."); } else { @@ -173,7 +179,7 @@ public void PopulateMeshes(GameObject[] gs, Dictionary mats) { for(int j = 0; j < triangles.Length; j++) triangles[j] = j; m.triangles = triangles; - + } else { int gl = od.groups.Count; Material[] materials = new Material[gl]; @@ -198,38 +204,11 @@ public void PopulateMeshes(GameObject[] gs, Dictionary mats) { m.SetTriangles(triangles, j); } - gs[i].renderer.materials = materials; + gs[i].GetComponent().materials = materials; } if (!objectHasNormals) { m.RecalculateNormals(); } } } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/OBJ/src/OBJ.cs b/OBJ/src/OBJ.cs index 84a17bc..0e8842d 100644 --- a/OBJ/src/OBJ.cs +++ b/OBJ/src/OBJ.cs @@ -9,7 +9,12 @@ public class OBJ : MonoBehaviour { public string objPath; - +#if UNITY_5 + public bool useLegacyShaders = false; // compatibility option for previous users of OBJ.cs using Unity5 +#else + private bool useLegacyShaders = true; +#endif + /* OBJ file tags */ private const string O = "o"; private const string G = "g"; @@ -35,7 +40,31 @@ public class OBJ : MonoBehaviour { private const string MAP_KE = "map_Ke"; // Emissive texture private const string MAP_BUMP = "map_bump"; // Bump map texture private const string BUMP = "bump"; // Bump map texture + + /* Material shaders */ +#if UNITY_5 + private const string DIFFUSE_SHADER = "Standard"; + private const string SPECULAR_SHADER = "Standard (Specular setup)"; + private const string BUMPED_DIFFUSE_SHADER = "Standard"; + private const string BUMPED_SPECULAR_SHADER = "Standard (Specular setup)"; + private const string LEGACY_DIFFUSE_SHADER = "Legacy Shaders/Diffuse"; + private const string LEGACY_SPECULAR_SHADER = "Legacy Shaders/Specular"; + private const string LEGACY_BUMPED_DIFFUSE_SHADER = "Legacy Shaders/Bumped Diffuse"; + private const string LEGACY_BUMPED_SPECULAR_SHADER = "Legacy Shaders/Bumped Specular"; +#else + private const string DIFFUSE_SHADER = "Diffuse"; + private const string SPECULAR_SHADER = "Specular"; + private const string BUMPED_DIFFUSE_SHADER = "Bumped Diffuse"; + private const string BUMPED_SPECULAR_SHADER = "Bumped Specular"; + + private const string LEGACY_DIFFUSE_SHADER = DIFFUSE_SHADER; + private const string LEGACY_SPECULAR_SHADER = SPECULAR_SHADER; + private const string LEGACY_BUMPED_DIFFUSE_SHADER = BUMPED_DIFFUSE_SHADER; + private const string LEGACY_BUMPED_SPECULAR_SHADER = BUMPED_SPECULAR_SHADER; +#endif + + private string basepath; private string mtllib; private GeometryBuffer buffer; @@ -43,65 +72,73 @@ public class OBJ : MonoBehaviour { void Start () { buffer = new GeometryBuffer (); + StartCoroutine (Load (objPath)); } public IEnumerator Load(string path) { + yield return 0; // play nice by not hogging the main thread + basepath = (path.IndexOf("/") == -1) ? "" : path.Substring(0, path.LastIndexOf("/") + 1); - + WWW loader = new WWW(path); yield return loader; - SetGeometryData(loader.text); + yield return StartCoroutine(SetGeometryData(loader.text)); if(hasMaterials) { - loader = new WWW(basepath + mtllib); Debug.Log("base path = "+basepath); Debug.Log("MTL path = "+(basepath + mtllib)); + string mtlPath = basepath + mtllib; + loader = new WWW (mtlPath); yield return loader; + if (loader.error != null) { Debug.LogError(loader.error); } else { SetMaterialData(loader.text); } - - foreach(MaterialData m in materialData) { - if(m.diffuseTexPath != null) { - WWW texloader = GetTextureLoader(m, m.diffuseTexPath); - yield return texloader; - if (texloader.error != null) { - Debug.LogError(texloader.error); - } else { - m.diffuseTex = texloader.texture; + + if (materialData != null) { + foreach(MaterialData m in materialData) { + if(m.diffuseTexPath != null) { + string texpath = basepath + m.diffuseTexPath; + loader = new WWW(texpath); + yield return loader; + + if (loader.error != null) { + Debug.LogError(loader.error); + } else { + m.diffuseTex = GetTexture(loader); + } } - } - if(m.bumpTexPath != null) { - WWW texloader = GetTextureLoader(m, m.bumpTexPath); - yield return texloader; - if (texloader.error != null) { - Debug.LogError(texloader.error); - } else { - m.bumpTex = texloader.texture; + + if(m.bumpTexPath != null) { + string texpath = basepath + m.bumpTexPath; + loader = new WWW(texpath); + yield return loader; + + if (loader.error != null) { + Debug.LogError(loader.error); + } else { + m.bumpTex = GetTexture(loader); + } } } } } - - Build(); + Build(); } - - private WWW GetTextureLoader(MaterialData m, string texpath) { - char[] separators = {'/', '\\'}; - string[] components = texpath.Split(separators); - string filename = components[components.Length-1]; - string ext = Path.GetExtension(filename).ToLower(); - if (ext != ".png" && ext != ".jpg") { + + private Texture2D GetTexture(WWW loader) { + string ext = Path.GetExtension(loader.url).ToLower(); + if (ext != ".png" && ext != ".jpg" && ext != ".tga") { Debug.LogWarning("maybe unsupported texture format:"+ext); } - WWW texloader = new WWW(basepath + filename); - Debug.Log("texture path for material("+m.name+") = "+(basepath + filename)); - return texloader; + + return (ext == ".tga" ? TGALoader.LoadTGA (new MemoryStream(loader.bytes)) : loader.texture); + // refactor this method to add support for more formats } private void GetFaceIndicesByOneFaceLine(FaceIndices[] faces, string[] p, bool isFaceIndexPlus) { @@ -155,18 +192,22 @@ private void GetFaceIndicesByOneFaceLine(FaceIndices[] faces, string[] p, bool i } } - private void SetGeometryData(string data) { + private IEnumerator SetGeometryData(string data) { + yield return 0; // play nice by not hogging the main thread + string[] lines = data.Split("\n".ToCharArray()); - Regex regexWhitespaces = new Regex(@"\s+"); + bool isFirstInGroup = true; bool isFaceIndexPlus = true; + for(int i = 0; i < lines.Length; i++) { string l = lines[i].Trim(); - - if(l.IndexOf("#") != -1) { // comment line + + if(l.Length > 0 && l[0] == '#') { // comment line continue; } - string[] p = regexWhitespaces.Split(l); + string[] p = l.Replace(" ", " ").Split(' '); + switch(p[0]) { case O: buffer.PushObject(p[1].Trim()); @@ -221,8 +262,8 @@ private void SetGeometryData(string data) { buffer.PushMaterialName(p[1].Trim()); break; } + if (i % 7000 == 0) yield return 0; // play nice with main thread while parsing large objs } - // buffer.Trace(); } @@ -231,7 +272,7 @@ private float cf(string v) { return float.Parse(v); } catch(Exception e) { - print(e); + Debug.LogError("Error parsing: " + v + ": " + e); return 0; } } @@ -241,7 +282,7 @@ private int ci(string v) { return int.Parse(v); } catch(Exception e) { - print(e); + Debug.LogError(e); return 0; } } @@ -274,7 +315,7 @@ private void SetMaterialData(string data) { materialData = new List(); MaterialData current = new MaterialData(); Regex regexWhitespaces = new Regex(@"\s+"); - + for(int i = 0; i < lines.Length; i++) { string l = lines[i].Trim(); @@ -318,19 +359,28 @@ private void SetMaterialData(string data) { Debug.Log("this line was not processed :" +l ); break; } - } + } } private Material GetMaterial(MaterialData md) { Material m; + string shaderName; if(md.illumType == 2) { - string shaderName = (md.bumpTex != null)? "Bumped Specular" : "Specular"; + if (useLegacyShaders) { + shaderName = (md.bumpTex != null)? LEGACY_BUMPED_SPECULAR_SHADER : LEGACY_SPECULAR_SHADER; + } else { + shaderName = (md.bumpTex != null)? BUMPED_SPECULAR_SHADER : SPECULAR_SHADER; + } m = new Material(Shader.Find(shaderName)); m.SetColor("_SpecColor", md.specular); m.SetFloat("_Shininess", md.shininess); } else { - string shaderName = (md.bumpTex != null)? "Bumped Diffuse" : "Diffuse"; + if (useLegacyShaders) { + shaderName = (md.bumpTex != null)? LEGACY_BUMPED_DIFFUSE_SHADER : LEGACY_DIFFUSE_SHADER; + } else { + shaderName = (md.bumpTex != null)? BUMPED_DIFFUSE_SHADER : DIFFUSE_SHADER; + } m = new Material(Shader.Find(shaderName)); } diff --git a/OBJ/src/TGALoader.cs b/OBJ/src/TGALoader.cs new file mode 100644 index 0000000..535e957 --- /dev/null +++ b/OBJ/src/TGALoader.cs @@ -0,0 +1,79 @@ +// This was made by aaro4130 on the Unity forums. Thanks boss! +// It's been optimized and slimmed down for the purpose of loading Quake 3 TGA textures from memory streams. + +using System; +using System.IO; +using UnityEngine; + +public static class TGALoader +{ + + /*public static Texture2D LoadTGA(string fileName) + { + using (var imageFile = File.OpenRead(fileName)) + { + return LoadTGA(imageFile); + } + }*/ + + public static Texture2D LoadTGA(Stream TGAStream) + { + + using (BinaryReader r = new BinaryReader(TGAStream)) + { + // Skip some header info we don't care about. + // Even if we did care, we have to move the stream seek point to the beginning, + // as the previous method in the workflow left it at the end. + r.BaseStream.Seek(12, SeekOrigin.Begin); + + short width = r.ReadInt16(); + short height = r.ReadInt16(); + int bitDepth = r.ReadByte(); + + // Skip a byte of header information we don't care about. + r.BaseStream.Seek(1, SeekOrigin.Current); + + Texture2D tex = new Texture2D(width, height); + Color32[] pulledColors = new Color32[width * height]; + int length = width * height; + + if (bitDepth == 32) + { + for (int row = 1; row <= height; row++) + { + for (int col = 0; col < width; col++) + { + byte red = r.ReadByte(); + byte green = r.ReadByte(); + byte blue = r.ReadByte(); + byte alpha = r.ReadByte(); + + // pulledColors [i] = new Color32(blue, green, red, alpha); + pulledColors [length - (row * width) + col] = new Color32(blue, green, red, alpha); + } + } + } else if (bitDepth == 24) + { + for (int row = 1; row <= height; row++) + { + for (int col = 0; col < width; col++) + { + byte red = r.ReadByte(); + byte green = r.ReadByte(); + byte blue = r.ReadByte(); + + pulledColors [length - (row * width) + col] = new Color32(blue, green, red, 1); + } + } + } else + { + throw new Exception("TGA texture had non 32/24 bit depth."); + } + + tex.SetPixels32(pulledColors); + tex.Apply(); + return tex; + + } + } +} \ No newline at end of file diff --git a/README b/README index fb770d5..050d3fe 100644 --- a/README +++ b/README @@ -7,10 +7,14 @@ http://www.everyday3d.com/blog/index.php/2010/05/24/loading-3d-models-runtime-un Many thanks to the original author, Bartek Drozdz for publishing the code under MIT license. +- Upgraded to work with Unity 5 Shaders, while also supporting Unity 4 users. +- Supports loading from local file system (file:// prefix url) as well as remotely over WWW. +- Supports PNG/JPG/TGA textures. + License: MIT Notes: - please put all texture files on the same directory of . obj file. - please use use this form of URL for local files. ex) file:///Users/someone/somepath/model.obj - Bump map is not correctly working. TODO: convert hight map to normal map, make tangent data. - + - Arbitrary polygons is not supported yet, only triangles. See https://github.com/mtschoen/unity-obj-loader/ for quad support. diff --git a/objDemo.unity b/objDemo.unity index c1fe27c..e3663dc 100644 Binary files a/objDemo.unity and b/objDemo.unity differ