mirror of
https://github.com/maxartz15/VertexAnimation.git
synced 2024-11-12 15:35:30 +01:00
MeshSimplifier
Basic setup to simplify meshes and generate LODs.
This commit is contained in:
parent
d3b4d34409
commit
73d25ac453
@ -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)
|
||||
{
|
||||
|
@ -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:
|
Loading…
Reference in New Issue
Block a user