mirror of
				https://github.com/maxartz15/VertexAnimation.git
				synced 2025-11-04 02:15:59 +01:00 
			
		
		
		
	MeshSimplifier
Basic setup to simplify meshes and generate LODs.
This commit is contained in:
		@@ -243,5 +243,5 @@ namespace TAO.VertexAnimation
 | 
			
		||||
			SkinnedMeshRenderer target = gameObject.AddComponent<SkinnedMeshRenderer>();
 | 
			
		||||
			target.Combine(skinnedMeshes, meshes);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								Runtime/Scripts/ModelBaker/MeshLodGenerator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Runtime/Scripts/ModelBaker/MeshLodGenerator.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
 | 
			
		||||
namespace TAO.VertexAnimation
 | 
			
		||||
{
 | 
			
		||||
	public static class MeshLodGenerator
 | 
			
		||||
	{
 | 
			
		||||
		public static Mesh[] GenerateLOD(this Mesh mesh, int lods, float[] quality)
 | 
			
		||||
		{
 | 
			
		||||
			Mesh[] lodMeshes = new Mesh[lods];
 | 
			
		||||
 | 
			
		||||
			for (int lm = 0; lm < lodMeshes.Length; lm++)
 | 
			
		||||
			{
 | 
			
		||||
				lodMeshes[lm] = mesh.Copy();
 | 
			
		||||
				lodMeshes[lm] = lodMeshes[lm].Simplify(quality[lm]);
 | 
			
		||||
				lodMeshes[lm].name = string.Format("{0}_LOD{1}", lodMeshes[lm].name, lm);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return lodMeshes;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Mesh[] GenerateLOD(this Mesh mesh, int lods, AnimationCurve qualityCurve)
 | 
			
		||||
		{
 | 
			
		||||
			float[] quality = new float[lods];
 | 
			
		||||
 | 
			
		||||
			for (int q = 0; q < quality.Length; q++)
 | 
			
		||||
			{
 | 
			
		||||
				quality[q] = qualityCurve.Evaluate(1f / quality.Length * q);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return GenerateLOD(mesh, lods, quality);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								Runtime/Scripts/ModelBaker/MeshLodGenerator.cs.meta
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Runtime/Scripts/ModelBaker/MeshLodGenerator.cs.meta
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
fileFormatVersion: 2
 | 
			
		||||
guid: c65b5fa78d8074a439c6ba86df4c4ffc
 | 
			
		||||
MonoImporter:
 | 
			
		||||
  externalObjects: {}
 | 
			
		||||
  serializedVersion: 2
 | 
			
		||||
  defaultReferences: []
 | 
			
		||||
  executionOrder: 0
 | 
			
		||||
  icon: {instanceID: 0}
 | 
			
		||||
  userData: 
 | 
			
		||||
  assetBundleName: 
 | 
			
		||||
  assetBundleVariant: 
 | 
			
		||||
							
								
								
									
										289
									
								
								Runtime/Scripts/ModelBaker/MeshSimplifier.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								Runtime/Scripts/ModelBaker/MeshSimplifier.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,289 @@
 | 
			
		||||
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 List<Vector3> vertices = new List<Vector3>(3);
 | 
			
		||||
			public List<Vector3> normals = new List<Vector3>(3);
 | 
			
		||||
			public Dictionary<int, List<Vector2>> uvs = new Dictionary<int, List<Vector2>>();
 | 
			
		||||
	
 | 
			
		||||
			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.Count; 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 = vertices.IndexOf(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<Triangle> 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<Triangle> ToTriangles(this Mesh mesh)
 | 
			
		||||
		{
 | 
			
		||||
			List<Triangle> triangles = new List<Triangle>();
 | 
			
		||||
	
 | 
			
		||||
			List<Vector3> verts = new List<Vector3>(mesh.vertices);
 | 
			
		||||
			List<Vector3> normals = new List<Vector3>(mesh.normals);
 | 
			
		||||
			List<int> tris = new List<int>(mesh.triangles);
 | 
			
		||||
	
 | 
			
		||||
			Dictionary<int, List<Vector2>> uvs = new Dictionary<int, List<Vector2>>();
 | 
			
		||||
			for (int u = 0; u < 8; u++)
 | 
			
		||||
			{
 | 
			
		||||
				List<Vector2> coordinates = new List<Vector2>();
 | 
			
		||||
				mesh.GetUVs(u, coordinates);
 | 
			
		||||
	
 | 
			
		||||
				if (coordinates != null && coordinates.Any())
 | 
			
		||||
				{
 | 
			
		||||
					uvs.Add(u, coordinates);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
	
 | 
			
		||||
			for (int t = 0; t < tris.Count; t += 3)
 | 
			
		||||
			{
 | 
			
		||||
				Triangle tri = new Triangle();
 | 
			
		||||
	
 | 
			
		||||
				tri.vertices.Add(verts[tris[t + 0]]);
 | 
			
		||||
				tri.vertices.Add(verts[tris[t + 1]]);
 | 
			
		||||
				tri.vertices.Add(verts[tris[t + 2]]);
 | 
			
		||||
	
 | 
			
		||||
				tri.normals.Add(normals[tris[t + 0]]);
 | 
			
		||||
				tri.normals.Add(normals[tris[t + 1]]);
 | 
			
		||||
				tri.normals.Add(normals[tris[t + 2]]);
 | 
			
		||||
	
 | 
			
		||||
				foreach (var uv in uvs)
 | 
			
		||||
				{
 | 
			
		||||
					if (tri.uvs.TryGetValue(uv.Key, out List<Vector2> 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<Vector2>
 | 
			
		||||
						{ 
 | 
			
		||||
							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<Triangle> triangles)
 | 
			
		||||
		{
 | 
			
		||||
			Mesh mesh = new Mesh();
 | 
			
		||||
			mesh.Clear();
 | 
			
		||||
	
 | 
			
		||||
			List<Vector3> vertices = new List<Vector3>(triangles.Count * 3);
 | 
			
		||||
			List<int> tris = new List<int>(triangles.Count * 3);
 | 
			
		||||
			List<Vector3> normals = new List<Vector3>(triangles.Count * 3);
 | 
			
		||||
			Dictionary<int, List<Vector2>> uvs = new Dictionary<int, List<Vector2>>();
 | 
			
		||||
	
 | 
			
		||||
			int skipped = 0;
 | 
			
		||||
			for (int t = 0; t < triangles.Count; t++)
 | 
			
		||||
			{
 | 
			
		||||
				for (int v = 0; v < triangles[t].vertices.Count; 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<Vector2> coordinates))
 | 
			
		||||
						{
 | 
			
		||||
							coordinates.Add(uv.Value[v]);
 | 
			
		||||
						}
 | 
			
		||||
						else
 | 
			
		||||
						{
 | 
			
		||||
							uvs.Add(uv.Key, new List<Vector2>
 | 
			
		||||
							{
 | 
			
		||||
								uv.Value[v],
 | 
			
		||||
							});
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
	
 | 
			
		||||
					tris.Add(t * 3 + v - skipped);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
	
 | 
			
		||||
			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<Triangle> SortByPerimeter(this List<Triangle> triangles)
 | 
			
		||||
		{
 | 
			
		||||
			triangles.Sort((x, y) => x.Perimeter().CompareTo(y.Perimeter()));
 | 
			
		||||
	
 | 
			
		||||
			return triangles;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								Runtime/Scripts/ModelBaker/MeshSimplifier.cs.meta
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Runtime/Scripts/ModelBaker/MeshSimplifier.cs.meta
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
fileFormatVersion: 2
 | 
			
		||||
guid: 9a779acf4568ca24484403e1eb61b7f3
 | 
			
		||||
MonoImporter:
 | 
			
		||||
  externalObjects: {}
 | 
			
		||||
  serializedVersion: 2
 | 
			
		||||
  defaultReferences: []
 | 
			
		||||
  executionOrder: 0
 | 
			
		||||
  icon: {instanceID: 0}
 | 
			
		||||
  userData: 
 | 
			
		||||
  assetBundleName: 
 | 
			
		||||
  assetBundleVariant: 
 | 
			
		||||
							
								
								
									
										32
									
								
								Runtime/Scripts/ModelBaker/MeshUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Runtime/Scripts/ModelBaker/MeshUtils.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
 | 
			
		||||
namespace TAO.VertexAnimation
 | 
			
		||||
{
 | 
			
		||||
	public static class MeshUtils
 | 
			
		||||
	{
 | 
			
		||||
		public static Mesh Copy(this Mesh mesh)
 | 
			
		||||
		{
 | 
			
		||||
			Mesh copy = new Mesh
 | 
			
		||||
			{
 | 
			
		||||
				name = mesh.name,
 | 
			
		||||
				vertices = mesh.vertices,
 | 
			
		||||
				triangles = mesh.triangles,
 | 
			
		||||
				normals = mesh.normals,
 | 
			
		||||
				tangents = mesh.tangents,
 | 
			
		||||
				colors = mesh.colors,
 | 
			
		||||
				uv = mesh.uv,
 | 
			
		||||
				uv2 = mesh.uv2,
 | 
			
		||||
				uv3 = mesh.uv3,
 | 
			
		||||
				uv4 = mesh.uv4,
 | 
			
		||||
				uv5 = mesh.uv5,
 | 
			
		||||
				uv6 = mesh.uv6,
 | 
			
		||||
				uv7 = mesh.uv7,
 | 
			
		||||
				uv8 = mesh.uv8,
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			copy.RecalculateBounds();
 | 
			
		||||
 | 
			
		||||
			return copy;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								Runtime/Scripts/ModelBaker/MeshUtils.cs.meta
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Runtime/Scripts/ModelBaker/MeshUtils.cs.meta
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
fileFormatVersion: 2
 | 
			
		||||
guid: af0818363d7ad6640b54b45d0b879c52
 | 
			
		||||
MonoImporter:
 | 
			
		||||
  externalObjects: {}
 | 
			
		||||
  serializedVersion: 2
 | 
			
		||||
  defaultReferences: []
 | 
			
		||||
  executionOrder: 0
 | 
			
		||||
  icon: {instanceID: 0}
 | 
			
		||||
  userData: 
 | 
			
		||||
  assetBundleName: 
 | 
			
		||||
  assetBundleVariant: 
 | 
			
		||||
		Reference in New Issue
	
	Block a user