mirror of
				https://github.com/maxartz15/VertexAnimation.git
				synced 2025-10-31 16:45:51 +01:00 
			
		
		
		
	MeshSimplifier
Basic setup to simplify meshes and generate LODs.
This commit is contained in:
		| @@ -16,6 +16,8 @@ namespace TAO.VertexAnimation.Editor | ||||
| 		public int textureWidth = 512; | ||||
|  | ||||
| 		public bool generateLODS = true; | ||||
| 		// TODO: Improve curve/lod settings. LOD-Mesh Quality pair. | ||||
| 		//public Vector2[] lodLevels = new Vector2[4] { new Vector2(32, 100), new Vector2(32, 65), new Vector2(32, 30), new Vector2(3, 0) }; | ||||
| 		public AnimationCurve lodCurve = new AnimationCurve(new Keyframe(0, 1), new Keyframe(1, 0.01f)); | ||||
| 		public bool saveBakedDataToAsset = true; | ||||
| 		public bool generateAnimationBook = true; | ||||
| @@ -41,8 +43,7 @@ namespace TAO.VertexAnimation.Editor | ||||
|  | ||||
| 			if (generateLODS) | ||||
| 			{ | ||||
| 				// TODO: LODs. | ||||
| 				meshes = new Mesh[1] { bakedData.mesh }; | ||||
| 				meshes = bakedData.mesh.GenerateLOD(3, lodCurve); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| @@ -57,7 +58,10 @@ namespace TAO.VertexAnimation.Editor | ||||
| 			AssetDatabaseUtils.RemoveChildAssets(this, new Object[2] { book, material }); | ||||
|  | ||||
| 			// TODO: LODs | ||||
| 			AssetDatabase.AddObjectToAsset(bakedData.mesh, this); | ||||
| 			foreach (var m in meshes) | ||||
| 			{ | ||||
| 				AssetDatabase.AddObjectToAsset(m, this); | ||||
| 			} | ||||
|  | ||||
| 			foreach (var pm in bakedData.positionMaps) | ||||
| 			{ | ||||
|   | ||||
							
								
								
									
										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