private GameObject ImportSubObject(GameObject parentObj, DataSet.ObjectData objData, Dictionary <string, Material> mats) { bool conv2sided = buildOptions != null && buildOptions.convertToDoubleSided; GameObject go = new GameObject(); go.name = objData.name; int count = 0; if (parentObj.transform) { while (parentObj.transform.Find(go.name)) { count++; go.name = objData.name + count; } } go.transform.SetParent(parentObj.transform, false); if (objData.allFaces.Count == 0) { throw new InvalidOperationException("Failed to parse vertex and uv data. It might be that the file is corrupt or is not a valid wavefront OBJ file."); //Debug.LogWarning("Sub object: " + objData.name + " has no face defined. Creating empty game object."); //return go; } //Debug.Log( "Importing sub object:" + objData.Name ); // count vertices needed for all the faces and map face indices to new vertices Dictionary <string, int> vIdxCount = new Dictionary <string, int>(); int vcount = 0; foreach (DataSet.FaceIndices fi in objData.allFaces) { string key = DataSet.GetFaceIndicesKey(fi); int idx; // avoid duplicates if (!vIdxCount.TryGetValue(key, out idx)) { vIdxCount.Add(key, vcount); vcount++; } } int arraySize = conv2sided ? vcount * 2 : vcount; Vector3[] newVertices = new Vector3[arraySize]; Vector2[] newUVs = new Vector2[arraySize]; Vector3[] newNormals = new Vector3[arraySize]; Color32[] newColors = new Color32[arraySize]; bool hasColors = currDataSet.colorList.Count > 0; foreach (DataSet.FaceIndices fi in objData.allFaces) { string key = DataSet.GetFaceIndicesKey(fi); int k = vIdxCount[key]; newVertices[k] = currDataSet.vertList[fi.vertIdx]; if (conv2sided) { newVertices[vcount + k] = newVertices[k]; } if (hasColors) { newColors[k] = currDataSet.colorList[fi.vertIdx]; if (conv2sided) { newColors[vcount + k] = newColors[k]; } } if (currDataSet.uvList.Count > 0) { newUVs[k] = currDataSet.uvList[fi.uvIdx]; if (conv2sided) { newUVs[vcount + k] = newUVs[k]; } } if (currDataSet.normalList.Count > 0 && fi.normIdx >= 0) { newNormals[k] = currDataSet.normalList[fi.normIdx]; if (conv2sided) { newNormals[vcount + k] = -newNormals[k]; } } } bool objectHasNormals = (currDataSet.normalList.Count > 0 && objData.hasNormals); bool objectHasColors = (currDataSet.colorList.Count > 0 && objData.hasColors); bool objectHasUVs = (currDataSet.uvList.Count > 0); int n = objData.faceGroups[0].faces.Count; int numIndices = conv2sided ? n * 2 : n; MeshFilter meshFilter = go.AddComponent <MeshFilter>(); go.AddComponent <MeshRenderer>(); Mesh mesh = new Mesh(); #if UNITY_2017_3_OR_NEWER if (Using32bitIndices()) { if (arraySize > MAX_VERT_COUNT || numIndices > MAX_INDICES_LIMIT_FOR_A_MESH) { mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; } } #endif mesh.name = go.name; meshFilter.sharedMesh = mesh; mesh.vertices = newVertices; if (objectHasUVs) { mesh.uv = newUVs; } if (objectHasNormals) { mesh.normals = newNormals; } if (objectHasColors) { mesh.colors32 = newColors; } Material material; string matName = (objData.faceGroups[0].materialName != null) ? objData.faceGroups[0].materialName : "default"; Renderer renderer = go.GetComponent <Renderer>(); if (mats.ContainsKey(matName)) { material = mats[matName]; renderer.sharedMaterial = material; #if UNITY_5_6_OR_NEWER RendererExtensions.UpdateGIMaterials(renderer); #else DynamicGI.UpdateMaterials(renderer); #endif } else { if (mats.ContainsKey("default")) { material = mats["default"]; renderer.sharedMaterial = material; Debug.LogWarning("Material: " + matName + " not found. Using the default material."); } else { Debug.LogError("Material: " + matName + " not found."); } } int[] indices = new int[numIndices]; for (int s = 0; s < n; s++) { DataSet.FaceIndices fi = objData.faceGroups[0].faces[s]; string key = DataSet.GetFaceIndicesKey(fi); indices[s] = vIdxCount[key]; } if (conv2sided) { for (int s = 0; s < n; s++) { indices[s + n] = vcount + indices[s / 3 * 3 + 2 - s % 3]; } } mesh.SetTriangles(indices, 0); if (!objectHasNormals) { mesh.RecalculateNormals(); } if (objectHasUVs) { Solve(mesh); } if (buildOptions != null && buildOptions.buildColliders) { #if UNITY_2018_3_OR_NEWER BuildMeshCollider(go, buildOptions.colliderConvex, buildOptions.colliderTrigger); #else BuildMeshCollider(go, buildOptions.colliderConvex, buildOptions.colliderTrigger, buildOptions.colliderInflate, buildOptions.colliderSkinWidth); #endif } return(go); }
/// <summary> /// Parse the OBJ file to extract geometry data. /// </summary> /// <param name="objDataText">OBJ file text</param> /// <returns>Execution is splitted into steps to not freeze the caller method.</returns> #pragma warning disable protected void ParseGeometryData(string objDataText) { string[] lines = objDataText.Split("\n".ToCharArray()); bool isFirstInGroup = true; bool isFaceIndexPlus = true; objLoadingProgress.message = "Parsing geometry data..."; // store separators, used multiple times char[] separators = new char[] { ' ', '\t' }; for (int i = 0; i < lines.Length; i++) { string line = lines[i].Trim(); if (line.Length > 0 && line[0] == '#') { // comment line continue; } string[] p = line.Split(separators, StringSplitOptions.RemoveEmptyEntries); if (p.Length == 0) { // empty line continue; } string parameters = null; if (line.Length > p[0].Length) { parameters = line.Substring(p[0].Length + 1).Trim(); } switch (p[0]) { case "o": dataSet.AddObject(parameters); isFirstInGroup = true; break; case "g": isFirstInGroup = true; dataSet.AddGroup(parameters); break; case "v": dataSet.AddVertex(ConvertVec3(ParseFloat(p[1]), ParseFloat(p[2]), ParseFloat(p[3]))); if (p.Length >= 7) { // 7 for "v x y z r g b" // 8 for "v x y z r g b w" // w is the weight required for rational curves and surfaces. It is // not required for non - rational curves and surfaces.If you do not // specify a value for w, the default is 1.0. [http://paulbourke.net/dataformats/obj/] dataSet.AddColor(new Color(ParseFloat(p[4]), ParseFloat(p[5]), ParseFloat(p[6]), 1f)); } break; case "vt": dataSet.AddUV(new Vector2(ParseFloat(p[1]), ParseFloat(p[2]))); break; case "vn": dataSet.AddNormal(ConvertVec3(ParseFloat(p[1]), ParseFloat(p[2]), ParseFloat(p[3]))); break; case "f": { int numVerts = p.Length - 1; DataSet.FaceIndices[] face = new DataSet.FaceIndices[numVerts]; if (isFirstInGroup) { isFirstInGroup = false; string[] c = p[1].Trim().Split("/".ToCharArray()); isFaceIndexPlus = (int.Parse(c[0]) >= 0); } GetFaceIndicesByOneFaceLine(face, p, isFaceIndexPlus); if (numVerts == 3) { dataSet.AddFaceIndices(face[0]); dataSet.AddFaceIndices(face[2]); dataSet.AddFaceIndices(face[1]); } else { // Triangulate the polygon // TODO: Texturing and lighting work better with a triangulation that maximizes triangles areas. // TODO: the following true must be replaced to a proper option (disabled by default) as soon as a proper triangulation method is implemented. Triangulator.Triangulate(dataSet, face); // TODO: Maybe triangulation could be done in ObjectImporter instead. } } break; case "mtllib": if (!string.IsNullOrEmpty(parameters)) { mtlLib = parameters; } break; case "usemtl": if (!string.IsNullOrEmpty(parameters)) { dataSet.AddMaterialName(DataSet.FixMaterialName(parameters)); } break; } // update progress only sometimes if (i % 7000 == 0) { objLoadingProgress.percentage = LOAD_PHASE_PERC * i / lines.Length; //return; } } objLoadingProgress.percentage = LOAD_PHASE_PERC; //dataSet.PrintSummary(); }
/// <summary> /// Build an object once at a time, to be reiterated until false is returned. /// </summary> /// <param name="parentObj">Game object to which the new objects will be attached</param> /// <param name="mats">Materials from the previously loaded library</param> /// <returns>Return true until no more objects can be added, then false.</returns> protected bool BuildNextObject(GameObject parentObj, Dictionary <string, Material> mats) { // if all the objects were built stop here if (buildStatus.objCount >= currDataSet.objectList.Count) { return(false); } // get the next object in the list DataSet.ObjectData objData = currDataSet.objectList[buildStatus.objCount]; if (buildStatus.newObject) { if (buildStatus.objCount == 0 && objData.name == "default") { buildStatus.currObjGameObject = parentObj; } else { buildStatus.currObjGameObject = new GameObject(); buildStatus.currObjGameObject.transform.parent = parentObj.transform; buildStatus.currObjGameObject.name = objData.name; // restore the scale if the parent was rescaled buildStatus.currObjGameObject.transform.localScale = Vector3.one; } buildStatus.subObjParent = buildStatus.currObjGameObject; //if (od.Name != "default") go.name = od.Name; //Debug.Log("Object: " + objData.name); buildStatus.newObject = false; buildStatus.subObjCount = 0; buildStatus.idxCount = 0; buildStatus.grpIdx = 0; buildStatus.grpFaceIdx = 0; buildStatus.meshPartIdx = 0; buildStatus.totFaceIdxCount = 0; buildStatus.numGroups = Mathf.Max(1, objData.faceGroups.Count); } bool splitLargeMeshes = true; #if UNITY_2017_3_OR_NEWER // GPU support for 32 bit indices is not guaranteed on all platforms; // for example Android devices with Mali-400 GPU do not support them. // This check is performed in Using32bitIndices(). // If nothing is rendered on your device problably Using32bitIndices() must be updated. if (Using32bitIndices()) { splitLargeMeshes = false; } #endif bool splitGrp = false; DataSet.FaceGroupData grp = new DataSet.FaceGroupData(); grp.name = objData.faceGroups[buildStatus.grpIdx].name; grp.materialName = objData.faceGroups[buildStatus.grpIdx].materialName; // data for sub-object DataSet.ObjectData subObjData = new DataSet.ObjectData(); subObjData.hasNormals = objData.hasNormals; subObjData.hasColors = objData.hasColors; HashSet <int> vertIdxSet = new HashSet <int>(); bool conv2sided = buildOptions != null && buildOptions.convertToDoubleSided; int maxIdx4mesh = conv2sided ? MAX_INDICES_LIMIT_FOR_A_MESH / 2 : MAX_INDICES_LIMIT_FOR_A_MESH; // copy blocks of face indices to each sub-object data for (int f = buildStatus.grpFaceIdx; f < objData.faceGroups[buildStatus.grpIdx].faces.Count; f++) { // if large meshed must be split and // if passed the max num of vertices and not at the last iteration if (splitLargeMeshes && (vertIdxSet.Count / 3 > MAX_VERT_COUNT / 3 || subObjData.allFaces.Count / 3 > maxIdx4mesh / 3)) { // split the group across more objects splitGrp = true; buildStatus.grpFaceIdx = f; Debug.LogWarningFormat("Maximum vertex number for a mesh exceeded.\nSplitting object {0} (group {1}, starting from index {2})...", grp.name, buildStatus.grpIdx, f); break; } DataSet.FaceIndices fi = objData.faceGroups[buildStatus.grpIdx].faces[f]; subObjData.allFaces.Add(fi); grp.faces.Add(fi); vertIdxSet.Add(fi.vertIdx); } if (splitGrp || buildStatus.meshPartIdx > 0) { buildStatus.meshPartIdx++; } // create an empty (group) object in case the group has been splitted if (buildStatus.meshPartIdx == 1) { GameObject grpObj = new GameObject(); grpObj.transform.SetParent(buildStatus.currObjGameObject.transform, false); grpObj.name = grp.name; buildStatus.subObjParent = grpObj; } // add a suffix to the group name in case the group has been splitted if (buildStatus.meshPartIdx > 0) { grp.name = buildStatus.subObjParent.name + "_MeshPart" + buildStatus.meshPartIdx; } subObjData.name = grp.name; // add the group to the sub object data subObjData.faceGroups.Add(grp); // update the start index buildStatus.idxCount += subObjData.allFaces.Count; if (!splitGrp) { buildStatus.grpFaceIdx = 0; buildStatus.grpIdx++; } buildStatus.totFaceIdxCount += subObjData.allFaces.Count; GameObject subobj = ImportSubObject(buildStatus.subObjParent, subObjData, mats); if (subobj == null) { Debug.LogWarningFormat("Error loading sub object n.{0}.", buildStatus.subObjCount); } //else Debug.LogFormat( "Imported face indices: {0} to {1}", buildStatus.totFaceIdxCount - sub_od.AllFaces.Count, buildStatus.totFaceIdxCount ); buildStatus.subObjCount++; if (buildStatus.totFaceIdxCount >= objData.allFaces.Count || buildStatus.grpIdx >= objData.faceGroups.Count) { if (buildStatus.totFaceIdxCount != objData.allFaces.Count) { Debug.LogWarningFormat("Imported face indices: {0} of {1}", buildStatus.totFaceIdxCount, objData.allFaces.Count); return(false); } buildStatus.objCount++; buildStatus.newObject = true; } return(true); }
private void GetFaceIndicesByOneFaceLine(DataSet.FaceIndices[] faces, string[] p, bool isFaceIndexPlus) { if (isFaceIndexPlus) { for (int j = 1; j < p.Length; j++) { string[] c = p[j].Trim().Split("/".ToCharArray()); DataSet.FaceIndices fi = new DataSet.FaceIndices(); // vertex int vi = int.Parse(c[0]); fi.vertIdx = vi - 1; // uv if (c.Length > 1 && c[1] != "") { int vu = int.Parse(c[1]); fi.uvIdx = vu - 1; } // normal if (c.Length > 2 && c[2] != "") { int vn = int.Parse(c[2]); fi.normIdx = vn - 1; } else { fi.normIdx = -1; } faces[j - 1] = fi; } } else { // for minus index int vertexCount = dataSet.vertList.Count; int uvCount = dataSet.uvList.Count; for (int j = 1; j < p.Length; j++) { string[] c = p[j].Trim().Split("/".ToCharArray()); DataSet.FaceIndices fi = new DataSet.FaceIndices(); // vertex int vi = int.Parse(c[0]); fi.vertIdx = vertexCount + vi; // uv if (c.Length > 1 && c[1] != "") { int vu = int.Parse(c[1]); fi.uvIdx = uvCount + vu; } // normal if (c.Length > 2 && c[2] != "") { int vn = int.Parse(c[2]); fi.normIdx = vertexCount + vn; } else { fi.normIdx = -1; } faces[j - 1] = fi; } } }