using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; // Face remove by angle namespace RayFire { public class RFCombineMesh { // Combined mesh data List trianglesSubId; List> triangles; List vertices; List normals; List uv; List uv2; List colors; List tangents; // Constructor RFCombineMesh() { trianglesSubId = new List(); triangles = new List>(); vertices = new List(); normals = new List(); uv = new List(); uv2 = new List(); colors = new List(); tangents = new List(); } /// ///////////////////////////////////////////////////////// /// Combine /// ///////////////////////////////////////////////////////// // Set combined mesh data public static RFCombineMesh GetCombinedMesh(Transform transForm, List meshList, List transList, List> matIdList, List invertNormals) { // Check all meshes and convert to tris int meshVertIdOffset = 0; RFCombineMesh cMesh = new RFCombineMesh(); Mesh mesh; for (int m = 0; m < meshList.Count; m++) { // Get local mesh mesh = meshList[m]; // Collect combined vertices list cMesh.vertices.AddRange(mesh.vertices.Select(t => transForm.InverseTransformPoint(transList[m].TransformPoint(t)))); // Collect combined normals list for (int i = 0; i < mesh.normals.Length; i++) if (invertNormals[m] == false) cMesh.normals.Add (mesh.normals[i]); else cMesh.normals.Add (-mesh.normals[i]); // Collect combined uvs list cMesh.uv.AddRange(mesh.uv.ToList()); cMesh.uv2.AddRange(mesh.uv2.ToList()); cMesh.colors.AddRange(mesh.colors.ToList()); // Collect combined tangents list TODO FLIP NORMAL FOR INVERTED cMesh.tangents.AddRange(mesh.tangents.ToList()); // Iterate every submesh for (int s = 0; s < mesh.subMeshCount; s++) { // Get all triangles verts ids int[] tris = mesh.GetTriangles(s); // Invert normals if (invertNormals[m] == true) tris = tris.Reverse().ToArray(); // Increment by mesh vertices id offset for (int i = 0; i < tris.Length; i++) tris[i] += meshVertIdOffset; // Collect triangles with material which already has other triangles. >> add to existing list if (cMesh.trianglesSubId.Contains(matIdList[m][s]) == true) // TODO change to hashset { int ind = cMesh.trianglesSubId.IndexOf(matIdList[m][s]); cMesh.triangles[ind].AddRange(tris.ToList()); } else { // Collect sub mesh triangles >> Create new list cMesh.triangles.Add(tris.ToList()); // Check every triangle and collect tris material id cMesh.trianglesSubId.Add(matIdList[m][s]); } } // Offset verts ids per mesh meshVertIdOffset += mesh.vertices.Length; } return cMesh; } // Create combined mesh public static Mesh CreateMesh (RFCombineMesh cMesh, string name, IndexFormat indexFormat = IndexFormat.UInt16) { // Create combined mesh Mesh newMesh = new Mesh(); newMesh.indexFormat = indexFormat; newMesh.name = name + "_Comb"; newMesh.SetVertices(cMesh.vertices); // Set triangles by submeshes newMesh.subMeshCount = cMesh.trianglesSubId.Count; for (int i = 0; i < cMesh.trianglesSubId.Count; i++) newMesh.SetTriangles(cMesh.triangles[i], cMesh.trianglesSubId[i]); // Normals IMPORTANT Do not RecalculateNormals(), cause normal artifacts newMesh.SetNormals(cMesh.normals); // newMesh.RecalculateNormals(); // UVs newMesh.SetUVs(0, cMesh.uv); newMesh.SetUVs(1, cMesh.uv2); newMesh.SetColors (cMesh.colors); // Tangents newMesh.SetTangents(cMesh.tangents); newMesh.RecalculateTangents(); // Bounds newMesh.RecalculateBounds(); return newMesh; } /// ///////////////////////////////////////////////////////// /// Shatter /// ///////////////////////////////////////////////////////// // Combine list of meshfilters public static Mesh CombineShatter (RayfireShatter shatter, Transform root, List filters) { // Get meshes and tms List meshList = new List(); List transList = new List(); List> matList = new List>(); // Verts amount // TODO max vert amount check // int totalVerts = 0; // Collect meshes, transforms and materials by meshfilter list GetMeshTransMatLists (filters, ref meshList, ref transList, ref matList, 4, 0.05f); // Get all materials list List allMaterials = GetAllMaterials(transList, matList); // Set materials list to shatter shatter.materials = allMaterials.ToArray(); // Collect material ids per submesh List> matIdList = GetMatIdList(transList, matList, allMaterials); // Get invert list List invertNormals = GetInvertList(transList); // Create combined mesh data RFCombineMesh cMesh = GetCombinedMesh(root, meshList, transList, matIdList, invertNormals); // Create combined mesh and return TODO input index format in case of more than 65k vertices return CreateMesh (cMesh, root.name); } // Collect meshes, transforms and materials by meshfilter list static void GetMeshTransMatLists (List filters, ref List meshList, ref List transList, ref List> matList, int verts, float size) { // Collect mesh, tm and mats for meshfilter foreach (var mf in filters) { // Filters if (mf.sharedMesh.vertexCount < verts) continue; MeshRenderer mr = mf.GetComponent(); if (mr != null && mr.bounds.size.magnitude < size) continue; // Collect mats List mats = new List(); if (mr != null) mats = mr.sharedMaterials.ToList(); // Collect meshList.Add(mf.sharedMesh); transList.Add(mf.transform); matList.Add(mats); } } // Get all materials list public static List GetAllMaterials(List transList, List> matList) { List allMaterials = new List(); for (int f = 0; f < transList.Count; f++) for (int m = 0; m < matList[f].Count; m++) if (allMaterials.Contains(matList[f][m]) == false) allMaterials.Add(matList[f][m]); return allMaterials; } // Collect material ids per submesh public static List> GetMatIdList(List transList, List> matList, List allMaterials) { List> matIdList = new List>(); for (int f = 0; f < transList.Count; f++) matIdList.Add(matList[f].Select(t => allMaterials.IndexOf(t)).ToList()); return matIdList; } // Get invert list by transforms public static List GetInvertList(List transList) { List invertNormals = new List(); for (int f = 0; f < transList.Count; f++) { // Get invert normals because of negative scale bool invert = false; if (transList[f].localScale.x < 0) invert = !invert; if (transList[f].localScale.y < 0) invert = !invert; if (transList[f].localScale.z < 0) invert = !invert; invertNormals.Add(invert); } return invertNormals; } } [AddComponentMenu("RayFire/Rayfire Combine")] [HelpURL("https://rayfirestudios.com/unity-online-help/components/unity-combine-component/")] public class RayfireCombine : MonoBehaviour { public enum CombType { Children = 0, ObjectsList = 1, } // UI public CombType type; public List objects; public bool meshFilters = true; public bool skinnedMeshes = true; public bool particleSystems = true; public float sizeThreshold = 0.1f; public int vertexThreshold = 5; public IndexFormat indexFormat = IndexFormat.UInt16; private Transform transForm; private MeshFilter mFilter; private MeshRenderer mRenderer; private List invertNormals; private List transList; private List mFilters; private List skinnedMeshRends; private List particleRends; private List meshList; private List> matIdList; private List> matList; // Combined mesh data private List allMaterials; /// ///////////////////////////////////////////////////////// /// Combine /// ///////////////////////////////////////////////////////// // Combine meshes public void Combine() { // Set combine data if (SetData() == false) return; // Get combine mesh data RFCombineMesh cMesh = RFCombineMesh.GetCombinedMesh(transForm, meshList, transList, matIdList, invertNormals); // Set mesh to object mFilter.sharedMesh = RFCombineMesh.CreateMesh (cMesh, name, indexFormat); // Set mesh renderer and materials mRenderer.sharedMaterials = allMaterials.ToArray(); } // Set data bool SetData() { transForm = GetComponent(); // Reset mesh mFilter = GetComponent(); if (mFilter == null) mFilter = gameObject.AddComponent(); mFilter.sharedMesh = null; // Reset mesh renderer mRenderer = GetComponent(); if (mRenderer == null) mRenderer = gameObject.AddComponent(); mRenderer.sharedMaterials = new Material[]{}; // Get targets if (type == CombType.Children) { if (meshFilters == true) mFilters = GetComponentsInChildren().ToList(); if (skinnedMeshes == true) skinnedMeshRends = GetComponentsInChildren().ToList(); if (particleSystems == true) particleRends = GetComponentsInChildren().ToList(); } if (type == CombType.ObjectsList) { mFilters = new List(); if (meshFilters == true) { foreach (var obj in objects) { MeshFilter mf = obj.GetComponent(); if (mf != null) if (mf.sharedMesh != null) mFilters.Add (mf); } } skinnedMeshRends = new List(); if (skinnedMeshes == true) { foreach (var obj in objects) { SkinnedMeshRenderer sk = obj.GetComponent(); if (sk != null) if (sk.sharedMesh != null) skinnedMeshRends.Add (sk); } } particleRends = new List(); if (particleSystems == true) { foreach (var obj in objects) { ParticleSystemRenderer pr = obj.GetComponent(); if (pr != null) particleRends.Add (pr); } } } // Clear mesh filters without mesh for (int i = mFilters.Count - 1; i >= 0; i--) if (mFilters[i].sharedMesh == null) mFilters.RemoveAt (i); // Clear skinned meshes without mesh if (skinnedMeshRends != null && skinnedMeshRends.Count > 0) for (int i = skinnedMeshRends.Count - 1; i >= 0; i--) if (skinnedMeshRends[i].sharedMesh == null) skinnedMeshRends.RemoveAt(i); // Get meshes and tms meshList = new List(); transList = new List(); matList = new List>(); // Verts amount int totalVerts = 0; // Collect mesh, tm and mats for meshfilter if (mFilters != null && mFilters.Count > 0) foreach (var mf in mFilters) { // Filters if (mf.sharedMesh.vertexCount < vertexThreshold) continue; MeshRenderer mr = mf.GetComponent(); if (mr != null && mr.bounds.size.magnitude < sizeThreshold) continue; // Collect mats List mats = new List(); if (mr != null) mats = mr.sharedMaterials.ToList(); // Collect meshList.Add(mf.sharedMesh); transList.Add(mf.transform); matList.Add(mats); // Collect verts totalVerts += mf.sharedMesh.vertexCount; } // Collect mesh, tm and mats for skinned mesh if (skinnedMeshRends != null && skinnedMeshRends.Count > 0) foreach (var sk in skinnedMeshRends) { // SKip by vertex amount if (sk.sharedMesh.vertexCount < vertexThreshold) continue; if (sk.bounds.size.magnitude < sizeThreshold) continue; // Collect meshList.Add(RFMesh.BakeMesh(sk)); transList.Add(sk.transform); matList.Add(sk.sharedMaterials.ToList()); // Collect verts totalVerts += sk.sharedMesh.vertexCount; } // Particle system if (particleRends != null && particleRends.Count > 0) { GameObject g = new GameObject(); foreach (var pr in particleRends) { Mesh m = new Mesh(); pr.BakeMesh (m, true); // SKip by vertex amount if (m.vertexCount < vertexThreshold) continue; if (m.bounds.size.magnitude < sizeThreshold) continue; // Collect meshList.Add (m); transList.Add (g.transform); matList.Add (pr.sharedMaterials.ToList()); // Collect verts totalVerts += m.vertexCount; } DestroyImmediate (g); } // No meshes if (meshList.Count == 0) { Debug.Log("No meshes to combine"); return false; } // Vert limit reached if (totalVerts >= 65535) { Debug.Log ("RayFire Combine: " + name + " combined mesh has more than 65535 vertices. UInt32 mesh Index Format will be used.", gameObject); // indexFormat = IndexFormat.UInt32; } // Get invert list by transforms invertNormals = RFCombineMesh.GetInvertList(transList); // Get all materials list allMaterials = RFCombineMesh.GetAllMaterials(transList, matList); // Collect material ids per submesh matIdList = RFCombineMesh.GetMatIdList(transList, matList, allMaterials); return true; } // ///////////////////////////////////////////////////////// // Other // ///////////////////////////////////////////////////////// /* public void Detach() { meshFilter = GetComponent(); transForm = GetComponent(); // Get all triangles with verts data List tris = GetTris(meshFilter.sharedMesh); // Set neib tris for (int i = 0; i < tris.Count; i++) foreach (var tri in tris) //if (tri.neibTris.Count < 3) if (CompareTri(tris[i], tri) == true) { tris[i].neibTris.Add(tri); //tri.neibTris.Add(tris[i]); } elements = new List(); int subMeshId = 0; while (tris.Count > 0) { List subTris = new List(); List checkTris = new List(); checkTris.Add(tris[0]); while (checkTris.Count > 0) { if (subTris.Contains(checkTris[0]) == false) { checkTris[0].subMeshId = subMeshId; subTris.Add(checkTris[0]); int ind = tris.IndexOf(checkTris[0]); if (ind >= 0) tris.RemoveAt(ind); } foreach (var neibTri in checkTris[0].neibTris) if (subTris.Contains(neibTri) == false) checkTris.Add(neibTri); checkTris.RemoveAt(0); } Element elem = new Element(); elem.tris.AddRange(subTris); elements.Add(elem); subMeshId++; } } // Match tris by shared verts private bool CompareTri(Tri tri1, Tri tri2) { if (tri1 == tri2) return false; foreach (int id in tri1.ids) if (tri2.ids.Contains(id) == true) return true; return false; } //[ContextMenu("MeshData")] public void GetMeshData() { meshFilter = GetComponent(); // Check for same position List weldGroups = GetWeldGroups(meshFilter.sharedMesh.vertices, 0.001f); // Get all triangles with verts data List tris = GetTris(meshFilter.sharedMesh); // Create new tri list with modified tri. Excluded welded vertices List remapVertIds = new List(); List excludeVertIds = new List(); foreach (WeldGroup weld in weldGroups) for (int i = 1; i < weld.verts.Count; i++) { remapVertIds.Add(weld.verts[0]); excludeVertIds.Add(weld.verts[i]); } // Remap vertices for tris foreach (Tri tri in tris) { for (int i = 0; i < tri.ids.Count; i++) { for (int j = 0; j < excludeVertIds.Count; j++) { if (tri.ids[i] == excludeVertIds[j]) { tri.ids[i] = remapVertIds[j]; tri.vpos[i] = meshFilter.sharedMesh.vertices[tri.ids[i]]; tri.vnormal[i] = meshFilter.sharedMesh.normals[tri.ids[i]]; } } } } // Set new triangles array List newTriangles = new List(); foreach (Tri tri in tris) newTriangles.AddRange(tri.ids); GameObject go = new GameObject(); go.transform.position = transform.position + new Vector3(0, 0, 1.5f); go.transform.rotation = transform.rotation; MeshFilter mf = go.AddComponent(); MeshRenderer mr = go.AddComponent(); mr.sharedMaterials = GetComponent().sharedMaterials; Mesh mesh = new Mesh(); mesh.name = meshFilter.sharedMesh.name + "_welded"; mesh.vertices = meshFilter.sharedMesh.vertices; mesh.triangles = newTriangles.ToArray(); mesh.normals = meshFilter.sharedMesh.normals; mesh.uv = meshFilter.sharedMesh.uv; mesh.RecalculateNormals(); mesh.RecalculateBounds(); mf.sharedMesh = mesh; } // Get tris List GetTris(Mesh mesh) { List tris = new List(); for (int i = 0; i < mesh.triangles.Length; i++) { Tri tri = new Tri(); // Gt vert ids int id0 = mesh.triangles[i + 0]; int id1 = mesh.triangles[i + 1]; int id2 = mesh.triangles[i + 2]; // Save vert id tri.ids.Add(id0); tri.ids.Add(id1); tri.ids.Add(id2); // Save vert position tri.vpos.Add(mesh.vertices[id0]); tri.vpos.Add(mesh.vertices[id1]); tri.vpos.Add(mesh.vertices[id2]); // Save normal tri.vnormal.Add(mesh.normals[id0]); tri.vnormal.Add(mesh.normals[id1]); tri.vnormal.Add(mesh.normals[id2]); i += 2; tris.Add(tri); } return tris; } // Get index of vertex which share same/close position by threshold List GetWeldGroups(Vector3[] vertices, float threshold) { List list = new List(); List weldGroups = new List(); for (int i = 0; i < vertices.Length; i++) { // Already checked if (list.Contains(i) == true) continue; WeldGroup weld = new WeldGroup(); for (int v = 0; v < vertices.Length; v++) { // Comparing with self if (i == v) continue; // Already checked if (list.Contains(v) == true) continue; // Save if close if (Vector3.Distance(vertices[i], vertices[v]) < threshold) { list.Add(v); if (weld.verts.Contains(i) == false) weld.verts.Add(i); if (weld.verts.Contains(v) == false) weld.verts.Add(v); } } if (weld.verts.Count > 0) weldGroups.Add(weld); } return weldGroups; }*/ } }