/// <summary> /// Load an OBJ file from a stream. No materials will be loaded, and will instead be supplemented by a blank white material. /// </summary> /// <param name="input">Input OBJ stream</param> /// <returns>Returns a GameObject represeting the OBJ file, with each imported object as a child.</returns> public GameObject Load(Stream input) { var inputReader = new StreamReader(input); var reader = new StringReader(inputReader.ReadToEnd()); Dictionary <string, OBJObjectBuilder> builderDict = new Dictionary <string, OBJObjectBuilder>(); OBJObjectBuilder currentBuilder = null; string currentMaterial = "default"; //lists for face data //prevents excess GC List <int> vertexIndices = new List <int>(); List <int> normalIndices = new List <int>(); List <int> uvIndices = new List <int>(); //helper func Action <string> setCurrentObjectFunc = (string objectName) => { if (!builderDict.TryGetValue(objectName, out currentBuilder)) { currentBuilder = new OBJObjectBuilder(objectName, this); builderDict[objectName] = currentBuilder; } }; //create default object setCurrentObjectFunc.Invoke("default"); //do the reading for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (StringExtension.IsNullOrWhiteSpace(line)) { continue; } string processedLine = line.Clean(); string[] splitLine = processedLine.Split(' '); //comment or blank if (processedLine[0] == '#' || splitLine.Length < 2) { continue; } //load material library if (splitLine[0] == "mtllib" && Materials == null) { string mtlLibPath = processedLine.Substring(7); LoadMaterialLibrary(mtlLibPath); } //vtx if (splitLine[0] == "v") { Vertices.Add(OBJLoaderHelper.VectorFromStrArray(splitLine)); continue; } //normal if (splitLine[0] == "vn") { Normals.Add(OBJLoaderHelper.VectorFromStrArray(splitLine)); continue; } //uv if (splitLine[0] == "vt") { UVs.Add(OBJLoaderHelper.VectorFromStrArray(splitLine)); continue; } //new material if (splitLine[0] == "usemtl") { string materialName = processedLine.Substring(7); currentMaterial = materialName; if (SplitMode == SplitMode.Material) { setCurrentObjectFunc.Invoke(materialName); } continue; } //new object if ((splitLine[0] == "o" || splitLine[0] == "g") && SplitMode == SplitMode.Object) { string objectName = processedLine.Substring(2); setCurrentObjectFunc.Invoke(objectName); continue; } //face data (the fun part) if (splitLine[0] == "f") { //loop through indices for (int i = 1; i < splitLine.Length; i++) { string faceLoop = splitLine[i]; int vertexIndex = int.MinValue; int normalIndex = int.MinValue; int uvIndex = int.MinValue; //parse face loop if (faceLoop.Contains("//")) { //vertex and normal string[] slashSplits = faceLoop.Split('/'); vertexIndex = OBJLoaderHelper.FastIntParse(slashSplits[0]); normalIndex = OBJLoaderHelper.FastIntParse(slashSplits[2]); } else if (faceLoop.Contains("/")) { //get slash splits string[] slashSplits = faceLoop.Split('/'); if (slashSplits.Length > 2) { //vertex, uv, and normal vertexIndex = OBJLoaderHelper.FastIntParse(slashSplits[0]); uvIndex = OBJLoaderHelper.FastIntParse(slashSplits[1]); normalIndex = OBJLoaderHelper.FastIntParse(slashSplits[2]); } else { //vertex, and uv vertexIndex = OBJLoaderHelper.FastIntParse(slashSplits[0]); uvIndex = OBJLoaderHelper.FastIntParse(slashSplits[1]); } } else { //just vertex index vertexIndex = OBJLoaderHelper.FastIntParse(faceLoop); } //"postprocess" indices if (vertexIndex > int.MinValue) { if (vertexIndex < 0) { vertexIndex = Vertices.Count - vertexIndex; } vertexIndex--; } if (normalIndex > int.MinValue) { if (normalIndex < 0) { normalIndex = Normals.Count - normalIndex; } normalIndex--; } if (uvIndex > int.MinValue) { if (uvIndex < 0) { uvIndex = UVs.Count - uvIndex; } uvIndex--; } //set array values vertexIndices.Add(vertexIndex); normalIndices.Add(normalIndex); uvIndices.Add(uvIndex); } //push to builder currentBuilder.PushFace(currentMaterial, vertexIndices, normalIndices, uvIndices); //clear lists vertexIndices.Clear(); normalIndices.Clear(); uvIndices.Clear(); } } //finally, put it all together GameObject obj = new GameObject(_objInfo != null ? Path.GetFileNameWithoutExtension(_objInfo.Name) : "WavefrontObject"); obj.transform.localScale = new Vector3(-1f, 1f, 1f); foreach (var builder in builderDict) { //empty object if (builder.Value.PushedFaceCount == 0) { continue; } var builtObj = builder.Value.Build(); builtObj.transform.SetParent(obj.transform, false); } return(obj); }
public GameObject Build() { var go = new GameObject(_name); //add meshrenderer var mr = go.AddComponent <MeshRenderer>(); int submesh = 0; //locate the material for each submesh Material[] materialArray = new Material[_materialIndices.Count]; foreach (var kvp in _materialIndices) { Material material = null; if (_loader.Materials == null) { material = OBJLoaderHelper.CreateNullMaterial(); material.name = kvp.Key; } else { if (!_loader.Materials.TryGetValue(kvp.Key, out material)) { material = OBJLoaderHelper.CreateNullMaterial(); material.name = kvp.Key; _loader.Materials[kvp.Key] = material; } } materialArray[submesh] = material; submesh++; } mr.sharedMaterials = materialArray; //add meshfilter var mf = go.AddComponent <MeshFilter>(); submesh = 0; var msh = new Mesh() { name = _name, indexFormat = (_vertices.Count > 65535) ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16, subMeshCount = _materialIndices.Count }; //set vertex data msh.SetVertices(_vertices); msh.SetNormals(_normals); msh.SetUVs(0, _uvs); //set faces foreach (var kvp in _materialIndices) { msh.SetTriangles(kvp.Value, submesh); submesh++; } //recalculations if (recalculateNormals) { msh.RecalculateNormals(); } //msh.RecalculateTangents(); //msh.RecalculateBounds(); mf.sharedMesh = msh; return(go); }