using System.Collections.Generic; using System.Linq; using UnityEngine; namespace TAO.VertexAnimation { public static class MeshSimplifier { // Convert mesh into Triangles. // Change triangles. // Generate new mesh data based on Triangles. // Everything is basically done through the triangle. // When something changes in the triangle all correlated sub data changes as well (uv, normals, verts, etc). public class Triangle { // Vertices (Vector3) // Normals (Vector3) // UVs (UV0, UV1, ..., UV7) // Other... public Vector3[] vertices = new Vector3[3]; public Vector3[] normals = new Vector3[3]; public Dictionary> uvs = new Dictionary>(); public float Perimeter() { return Vector3.Distance(vertices[0], vertices[1]) + Vector3.Distance(vertices[1], vertices[2]) + Vector3.Distance(vertices[2], vertices[0]); } // If two or more points have the same values the triangle has no surface area and will be 'zero'. public bool IsZero() { if (vertices[0] == vertices[1] || vertices[0] == vertices[2] || vertices[1] == vertices[2]) { return true; } return false; } // Returns the closest vertex index of a vertex within this triangle. public int GetClosestVertexIndex(Vector3 vertex) { float distance = Mathf.Infinity; int closestVertex = -1; for (int v = 0; v < vertices.Length; v++) { if (vertices[v] != vertex) { float dist = Vector3.Distance(vertices[v], vertex); if (dist < distance) { distance = dist; closestVertex = v; } } } return closestVertex; } // Update triangle by copying data from a point in this triangle. public bool UpdateVertex(int curVertexIndex, int newVertexIndex) { vertices[curVertexIndex] = vertices[newVertexIndex]; normals[curVertexIndex] = normals[newVertexIndex]; foreach (var uv in uvs) { uv.Value[curVertexIndex] = uv.Value[newVertexIndex]; } return true; } // Update triangle by copying data from an other triangle. public bool UpdateVertex(Triangle sourceTriangle, int sourceVertexIndex, int newSourceVertexIndex) { if (sourceTriangle != this) { Vector3 sourceVertex = sourceTriangle.vertices[sourceVertexIndex]; int index = System.Array.IndexOf(vertices, sourceVertex); if (index != -1) { // Set all the new data. vertices[index] = sourceTriangle.vertices[newSourceVertexIndex]; normals[index] = sourceTriangle.normals[newSourceVertexIndex]; foreach (var uv in uvs) { uv.Value[index] = sourceTriangle.uvs[uv.Key][newSourceVertexIndex]; } return true; } } return false; } } public static Mesh Simplify(this Mesh mesh, float quality) { string name = mesh.name; List triangles = mesh.ToTriangles(); int targetCount = Mathf.FloorToInt(triangles.Count * quality); int loopCount = 0; while (triangles.Count > targetCount) { // Sort by perimeter. // TODO: Better priority system. Maybe allow user to pass in method. if (loopCount % triangles.Count == 0) { triangles.SortByPerimeter(); } // Select tri/vert to simplify. int curTriIndex = 0; // TODO: Select vert by shortest total distance to the two other verts in the triangle. int curVertIndex = 0; Vector3 curVert = triangles[curTriIndex].vertices[curVertIndex]; // Select closest vert within triangle to merge into. int newVertIndex = triangles[curTriIndex].GetClosestVertexIndex(curVert); // Update all triangles. // TODO: Apply only to connected triangles. for (int t = 0; t < triangles.Count; t++) { triangles[t].UpdateVertex(triangles[curTriIndex], curVertIndex, newVertIndex); } triangles[curTriIndex].UpdateVertex(curVertIndex, newVertIndex); // Remove all zero triangles. triangles.RemoveAll(t => t.IsZero()); loopCount++; } mesh.Clear(); mesh = triangles.ToMesh(); mesh.name = name; return mesh; } public static List ToTriangles(this Mesh mesh) { List triangles = new List(); Vector3[] verts = mesh.vertices; Vector3[] normals = mesh.normals; int[] tris = mesh.triangles; Dictionary> uvs = new Dictionary>(); for (int u = 0; u < 8; u++) { List coordinates = new List(); mesh.GetUVs(u, coordinates); if (coordinates != null && coordinates.Any()) { uvs.Add(u, coordinates); } } for (int t = 0; t < tris.Length; t += 3) { Triangle tri = new Triangle(); tri.vertices[0] = verts[tris[t + 0]]; tri.vertices[1] = verts[tris[t + 1]]; tri.vertices[2] = verts[tris[t + 2]]; tri.normals[0] = normals[tris[t + 0]]; tri.normals[1] = normals[tris[t + 1]]; tri.normals[2] = normals[tris[t + 2]]; foreach (var uv in uvs) { if (tri.uvs.TryGetValue(uv.Key, out List coordinates)) { coordinates.Add(uv.Value[tris[t + 0]]); coordinates.Add(uv.Value[tris[t + 1]]); coordinates.Add(uv.Value[tris[t + 2]]); } else { tri.uvs.Add(uv.Key, new List { uv.Value[tris[t + 0]], uv.Value[tris[t + 1]], uv.Value[tris[t + 2]] }); } } triangles.Add(tri); } return triangles; } public static Mesh ToMesh(this List triangles) { Mesh mesh = new Mesh(); mesh.Clear(); List vertices = new List(triangles.Count * 3); List normals = new List(triangles.Count * 3); List tris = new List(triangles.Count * 3); Dictionary> uvs = new Dictionary>(); int skipped = 0; for (int t = 0; t < triangles.Count; t++) { for (int v = 0; v < triangles[t].vertices.Length; v++) { // Check for existing matching vert. int vIndex = vertices.IndexOf(triangles[t].vertices[v]); if (vIndex != -1) { // Check for existing matching normal. if (normals[vIndex] == triangles[t].normals[v]) { // We have a duplicate. // Don't add the data and instead point to existing. tris.Add(vIndex); skipped++; continue; } } // Add data when it doesn't exist. vertices.Add(triangles[t].vertices[v]); normals.Add(triangles[t].normals[v]); foreach (var uv in triangles[t].uvs) { if (uvs.TryGetValue(uv.Key, out List coordinates)) { coordinates.Add(uv.Value[v]); } else { uvs.Add(uv.Key, new List { uv.Value[v], }); } } tris.Add(t * 3 + v - skipped); } } // Large mesh support. if (vertices.Count > 65535) { mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; } mesh.vertices = vertices.ToArray(); mesh.normals = normals.ToArray(); foreach (var uv in uvs) { mesh.SetUVs(uv.Key, uv.Value); } mesh.triangles = tris.ToArray(); mesh.Optimize(); mesh.RecalculateBounds(); mesh.RecalculateTangents(); return mesh; } public static List SortByPerimeter(this List triangles) { triangles.Sort((x, y) => x.Perimeter().CompareTo(y.Perimeter())); return triangles; } } }