using System.Collections.Generic; using UnityEngine; using System; #if UNITY_EDITOR using UnityEditor; #endif namespace BrainFailProductions.PolyFew.AsImpL { /// /// Build the game object hierarchy with meshes and materials from a DataSet and a MaterialData list. /// public class ObjectBuilder { /// /// Optional build options /// public ImportOptions buildOptions = null; #if UNITY_EDITOR /// /// Alternative texture path used to route loading requests to a proper asset database folder. /// Set by . /// public string alternativeTexPath = null; #endif private BuildStatus buildStatus = new BuildStatus(); private DataSet currDataSet; private GameObject currParentObj; private Dictionary currMaterials; private List materialData; /// /// Get the indexed list of imported materials /// public Dictionary ImportedMaterials { get { return currMaterials; } } /// /// Get the number of imported materials or 0 if nothing has been imported. /// public int NumImportedMaterials { get { return currMaterials != null ? currMaterials.Count : 0; } } private static int MAX_VERTICES_LIMIT_FOR_A_MESH = 65000; private static int MAX_INDICES_LIMIT_FOR_A_MESH = 65000; // maximum number of vertices that can be used for triangles private static int MAX_VERT_COUNT = (MAX_VERTICES_LIMIT_FOR_A_MESH - 2) / 3 * 3; /// /// Initialize the importing of materials /// /// List of material data /// If true and materialData is null and vertex colors are available, then use them public void InitBuildMaterials(List materialData, bool hasColors) { this.materialData = materialData; currMaterials = new Dictionary(); if (materialData == null || materialData.Count == 0) { string shaderName = "VertexLit"; if (hasColors) { shaderName = "Unlit/Simple Vertex Colors Shader"; if (Shader.Find(shaderName) == null) { shaderName = "Mobile/Particles/Alpha Blended"; } Debug.Log("No material library defined. Using vertex colors."); } else { Debug.LogWarning("No material library defined. Using a default material."); } currMaterials.Add("default", new Material(Shader.Find(shaderName))); } } /// /// Import materials step by step. Call this until it returns false. /// /// Progress information to be updated /// Return true if in progress, false otherwise. public bool BuildMaterials(ProgressInfo info) { if (materialData == null) { Debug.LogWarning("No material library defined."); return false; } if (info.materialsLoaded >= materialData.Count) { return false; } MaterialData matData = materialData[info.materialsLoaded]; info.materialsLoaded++; if (currMaterials.ContainsKey(matData.materialName)) { Debug.LogWarning("Duplicate material found: " + matData.materialName + ". Repeated occurence ignored"); } else { currMaterials.Add(matData.materialName, BuildMaterial(matData)); } return info.materialsLoaded < materialData.Count; } /// /// Initialize the asynchronous objects building. /// Call this once before calling StartBuildObjectAsync(). /// /// data set used to build the object /// game object to which the object will be attached /// dictionary mapping from materil name to material public void StartBuildObjectAsync(DataSet dataSet, GameObject parentObj, Dictionary materials = null) { currDataSet = dataSet; currParentObj = parentObj; if (materials != null) { currMaterials = materials; } } /// /// Build an object in more steps, one game object at a time. /// Call StartBuildObjectAsync() once, then call this until it returns true. /// /// progress information data updated on each call /// public bool BuildObjectAsync(ref ProgressInfo info) { bool result = BuildNextObject(currParentObj, currMaterials); info.objectsLoaded = buildStatus.objCount; info.groupsLoaded = buildStatus.subObjCount; info.numGroups = buildStatus.numGroups; return result; } /// /// Calculate tangent space vectors for a mesh. /// /// /// Mesh to be filled with tangents /// TODO: move this to a general utility class? public static void Solve(Mesh origMesh) { if (origMesh.uv == null || origMesh.uv.Length == 0) { Debug.LogWarning("Unable to compute tangent space vectors - texture coordinates not defined."); return; } if (origMesh.vertices == null || origMesh.vertices.Length == 0) { Debug.LogWarning("Unable to compute tangent space vectors - vertices not defined."); return; } if (origMesh.normals == null || origMesh.normals.Length == 0) { Debug.LogWarning("Unable to compute tangent space vectors - normals not defined."); return; } if (origMesh.triangles == null || origMesh.triangles.Length == 0) { Debug.LogWarning("Unable to compute tangent space vectors - triangles not defined."); return; } Vector3[] vertices = origMesh.vertices; Vector3[] normals = origMesh.normals; Vector2[] texcoords = origMesh.uv; int[] triangles = origMesh.triangles; int triVertCount = origMesh.triangles.Length; int maxVertIdx = -1; for (int i = 0; i < triangles.Length; i++) { if (maxVertIdx < triangles[i]) { maxVertIdx = triangles[i]; } } if (vertices.Length <= maxVertIdx) { Debug.LogWarning("Unable to compute tangent space vectors - not enough vertices: " + vertices.Length.ToString()); return; } if (normals.Length <= maxVertIdx) { Debug.LogWarning("Unable to compute tangent space vectors - not enough normals."); return; } if (texcoords.Length <= maxVertIdx) { Debug.LogWarning("Unable to compute tangent space vectors - not enough UVs."); return; } int vertexCount = origMesh.vertexCount; Vector4[] tangents = new Vector4[vertexCount]; Vector3[] tan1 = new Vector3[vertexCount]; Vector3[] tan2 = new Vector3[vertexCount]; int triangleCount = triangles.Length / 3; int tri = 0; for (int i = 0; i < triangleCount; i++) { int i1 = triangles[tri]; int i2 = triangles[tri + 1]; int i3 = triangles[tri + 2]; Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; Vector3 v3 = vertices[i3]; Vector2 w1 = texcoords[i1]; Vector2 w2 = texcoords[i2]; Vector2 w3 = texcoords[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float z1 = v2.z - v1.z; float z2 = v3.z - v1.z; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float r = 1.0f / (s1 * t2 - s2 * t1); Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; tri += 3; } for (int i = 0; i < vertexCount; i++) { Vector3 n = normals[i]; Vector3 t = tan1[i]; // Gram-Schmidt orthogonalize Vector3.OrthoNormalize(ref n, ref t); tangents[i].x = t.x; tangents[i].y = t.y; tangents[i].z = t.z; // Calculate handedness tangents[i].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0f) ? -1.0f : 1.0f; } origMesh.tangents = tangents; } /// /// Build mesh colliders for objects with a mesh filter. /// /// Game object to process (if it hasn't a mesh filter nothing happens) /// Build a convex mesh collider. /// Set collider as "trigger" /// Inflate the convex mesh /// Amout to be inflated public static void BuildMeshCollider(GameObject targetObject, bool convex = false, bool isTrigger = false, bool inflateMesh = false, float skinWidth = 0.01f) { MeshFilter meshFilter = targetObject.GetComponent(); if (meshFilter != null && meshFilter.sharedMesh != null) { Mesh objectMesh = meshFilter.sharedMesh; MeshCollider meshCollider = targetObject.AddComponent(); // Note: the order of these assignments is important meshCollider.sharedMesh = objectMesh; if (convex) { #if !UNITY_2018_3_OR_NEWER meshCollider.skinWidth = skinWidth; meshCollider.inflateMesh = inflateMesh; #endif meshCollider.convex = convex; meshCollider.isTrigger = isTrigger; } } } /// /// Build an object once at a time, to be reiterated until false is returned. /// /// Game object to which the new objects will be attached /// Materials from the previously loaded library /// Return true until no more objects can be added, then false. protected bool BuildNextObject(GameObject parentObj, Dictionary 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 vertIdxSet = new HashSet(); 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 GameObject ImportSubObject(GameObject parentObj, DataSet.ObjectData objData, Dictionary 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 vIdxCount = new Dictionary(); 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(); go.AddComponent(); 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(); 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; } /// /// Build a Unity Material from MaterialData /// /// material data /// Unity material private Material BuildMaterial(MaterialData md) { string shaderName = "Standard";// (md.illumType == 2) ? "Standard (Specular setup)" : "Standard"; bool specularMode = false;// (md.specularTex != null); ModelUtil.MtlBlendMode mode = md.overallAlpha < 1.0f ? ModelUtil.MtlBlendMode.TRANSPARENT : ModelUtil.MtlBlendMode.OPAQUE; bool useUnlit = buildOptions != null && buildOptions.litDiffuse && md.diffuseTex != null && md.bumpTex == null && md.opacityTex == null && md.specularTex == null && !md.hasReflectionTex; bool? diffuseIsTransparent = null; if (useUnlit) { // do not use unlit shader if the texture has transparent pixels diffuseIsTransparent = ModelUtil.ScanTransparentPixels(md.diffuseTex, ref mode); } if (useUnlit && !diffuseIsTransparent.Value) { shaderName = "Unlit/Texture"; } else if (specularMode) { shaderName = "Standard (Specular setup)"; } Material newMaterial = new Material(Shader.Find(shaderName)); // "Standard (Specular setup)" newMaterial.name = md.materialName; float shinLog = Mathf.Log(md.shininess, 2); // get the metallic value from the shininess float metallic = Mathf.Clamp01(shinLog / 10.0f); // get the smoothness from the shininess float smoothness = Mathf.Clamp01(shinLog / 10.0f); if (specularMode) { newMaterial.SetColor("_SpecColor", md.specularColor); newMaterial.SetFloat("_Shininess", md.shininess / 1000.0f); //m.color = new Color( md.diffuse.r, md.diffuse.g, md.diffuse.b, md.alpha); } else { newMaterial.SetFloat("_Metallic", metallic); //m.SetFloat( "_Glossiness", md.shininess ); } if (md.diffuseTex != null) { // diffuse if (md.opacityTex != null) { // diffuse + opacity: // update diffuse texture if an opacity map was found int w = md.diffuseTex.width; int h = md.diffuseTex.width; Texture2D albedoTexture = new Texture2D(w, h, TextureFormat.ARGB32, false); Color col = new Color(); for (int x = 0; x < albedoTexture.width; x++) { for (int y = 0; y < albedoTexture.height; y++) { col = md.diffuseTex.GetPixel(x, y); col.a *= md.opacityTex.GetPixel(x, y).grayscale; // blend diffuse and opacity textures albedoTexture.SetPixel(x, y, col); } } albedoTexture.name = md.diffuseTexPath; albedoTexture.Apply(); // mode = ModelUtil.MtlBlendMode.TRANSPARENT; // The map_d value is multiplied by the d value --> Fade mode mode = ModelUtil.MtlBlendMode.FADE; #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.opacityTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref albedoTexture, texAssetPath, "_alpha"); } } #endif newMaterial.SetTexture("_MainTex", albedoTexture); } else {// md.opacityTex == null // diffuse without opacity: if there are transparent pixels ==> transparent material if (!diffuseIsTransparent.HasValue) { diffuseIsTransparent = ModelUtil.ScanTransparentPixels(md.diffuseTex, ref mode); } newMaterial.SetTexture("_MainTex", md.diffuseTex); } //Debug.LogFormat("Diffuse set for {0}",m.name); } else if (md.opacityTex != null) { // opacity without diffuse //mode = ModelUtil.MtlBlendMode.TRANSPARENT; mode = ModelUtil.MtlBlendMode.FADE; int w = md.opacityTex.width; int h = md.opacityTex.width; Texture2D albedoTexture = new Texture2D(w, h, TextureFormat.ARGB32, false); Color col = new Color(); bool detected = false; for (int x = 0; x < albedoTexture.width; x++) { for (int y = 0; y < albedoTexture.height; y++) { col = md.diffuseColor; col.a = md.overallAlpha * md.opacityTex.GetPixel(x, y).grayscale; ModelUtil.DetectMtlBlendFadeOrCutout(col.a, ref mode, ref detected); //if (md.alpha == 1.0f && col.a == 0.0f) mode = ModelUtil.MtlBlendMode.CUTOUT; albedoTexture.SetPixel(x, y, col); } } albedoTexture.name = md.diffuseTexPath; albedoTexture.Apply(); #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.opacityTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref albedoTexture, texAssetPath, "_op"); } } #endif newMaterial.SetTexture("_MainTex", albedoTexture); } md.diffuseColor.a = md.overallAlpha; newMaterial.SetColor("_Color", md.diffuseColor); md.emissiveColor.a = md.overallAlpha; newMaterial.SetColor("_EmissionColor", md.emissiveColor); if (md.emissiveColor.r > 0 || md.emissiveColor.g > 0 || md.emissiveColor.b > 0) { newMaterial.EnableKeyword("_EMISSION"); } if (md.bumpTex != null) { // bump map defined // TODO: if importing assets do not create a nomal map, change importer settings // let (improperly) assign a normal map to the bumb map // if the file name contains a specific tag // TODO: customize normal map tag if (md.bumpTexPath.Contains("_normal_map")) { newMaterial.EnableKeyword("_NORMALMAP"); newMaterial.SetFloat("_BumpScale", 0.25f); // lower the bump effect with the normal map newMaterial.SetTexture("_BumpMap", md.bumpTex); } else { // calculate normal map Texture2D normalMap = ModelUtil.HeightToNormalMap(md.bumpTex); #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.bumpTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref normalMap, texAssetPath, "_nm", true); } } else #endif { newMaterial.SetTexture("_BumpMap", normalMap); //newMaterial.SetTexture("_BumpMap", md.bumpTex); newMaterial.EnableKeyword("_NORMALMAP"); newMaterial.SetFloat("_BumpScale", 1.0f); // adjust the bump effect with the normal map } } } if (md.specularTex != null) { Texture2D glossTexture = new Texture2D(md.specularTex.width, md.specularTex.height, TextureFormat.ARGB32, false); Color col = new Color(); float pix = 0.0f; for (int x = 0; x < glossTexture.width; x++) { for (int y = 0; y < glossTexture.height; y++) { pix = md.specularTex.GetPixel(x, y).grayscale; // red = metallic col.r = metallic * pix;// md.specular.grayscale*pix; col.g = col.r; col.b = col.r; // alpha = smoothness // if reflecting set maximum smoothness value, else use a precomputed value if (md.hasReflectionTex) col.a = pix; else col.a = pix * smoothness; glossTexture.SetPixel(x, y, col); } } glossTexture.Apply(); #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.specularTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref glossTexture, texAssetPath, "_spec"); } } #endif if (specularMode) { newMaterial.EnableKeyword("_SPECGLOSSMAP"); newMaterial.SetTexture("_SpecGlossMap", glossTexture); } else { newMaterial.EnableKeyword("_METALLICGLOSSMAP"); newMaterial.SetTexture("_MetallicGlossMap", glossTexture); } //m.SetTexture( "_MetallicGlossMap", md.specularLevelTex ); } // replace the texture with Unity environment reflection if (md.hasReflectionTex) { if (md.overallAlpha < 1.0f) { Color col = Color.white; col.a = md.overallAlpha; newMaterial.SetColor("_Color", col); mode = ModelUtil.MtlBlendMode.FADE; } // the "amount of" info is missing, using a default value if (md.specularTex != null) { newMaterial.SetFloat("_Metallic", metallic);// 1.0f); } // usually the reflection texture is not blurred newMaterial.SetFloat("_Glossiness", 1.0f); } ModelUtil.SetupMaterialWithBlendMode(newMaterial, mode); //#if UNITY_EDITOR // if (!string.IsNullOrEmpty(alternateTexPath)) // { // string path = alternateTexPath + "../Materials/" + m.name + ".mat"; // path = path.Replace("Textures/../", ""); // Debug.LogFormat("Creating material asset in {0}", path); // AssetDatabase.CreateAsset(m, path); // m = AssetDatabase.LoadAssetAtPath(path); // } //#endif return newMaterial; } #if UNITY_2017_3_OR_NEWER /// /// Check if the GPU support for 32 bit indices is enabled and available. /// /// /// GPU support for 32 bit indices is not guaranteed on all platforms; /// for example Android devices with Mali-400 GPU do not support them. /// /// True if the GPU support for 32 bit indices is enabled and available. private bool Using32bitIndices() { if (buildOptions != null && !buildOptions.use32bitIndices) { // Do not use at all 32 bit indices only if explicitly required. return false; } #if UNITY_ANDROID string graphicsDeviceName = SystemInfo.graphicsDeviceName; // If nothing is rendered on your device problably a new device check must be added here. if (graphicsDeviceName.Contains("Mali") && graphicsDeviceName.Contains("400")) { // Android devices with Mali-400 GPU do not support 32 bit indices return false; } #endif return true; } #endif public class ProgressInfo { public int materialsLoaded = 0; public int objectsLoaded = 0; public int groupsLoaded = 0; public int numGroups = 0; } private class BuildStatus { // true if a new object must be created public bool newObject = true; // counter for objects public int objCount = 0; // counter for sub objects public int subObjCount = 0; // number of added indices public int idxCount = 0; // index of the last group public int grpIdx = 0; // number of the groups for the last object public int numGroups = 0; // index of the first face index in the group public int grpFaceIdx = 0; // index of the last mesh part if the group is splitted into parts public int meshPartIdx = 0; // total number of face indices processed public int totFaceIdxCount = 0; // current OBJ object public GameObject currObjGameObject = null; internal GameObject subObjParent; } } }