using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; namespace FlatKit { public static class MeshSmoother { private const int SmoothNormalUVChannel = 2; /// /// Performs normal smoothing on the current mesh filter associated with this component asynchronously. /// This method will not try and re-smooth meshes which have already been smoothed. /// /// A task which will complete once normal smoothing is finished. public static Task SmoothNormalsA(Mesh mesh) { // Create a copy of the vertices and normals and apply the smoothing in an async task. var vertices = mesh.vertices; var normals = mesh.normals; var asyncTask = Task.Run(() => CalculateSmoothNormals(vertices, normals)); // Once the async task is complete, apply the smoothed normals to the mesh on the main thread. return asyncTask.ContinueWith(i => { mesh.SetUVs(SmoothNormalUVChannel, i.Result); }, TaskScheduler.FromCurrentSynchronizationContext()); } public static void SmoothNormals(Mesh mesh) { var result = CalculateSmoothNormals(mesh.vertices, mesh.normals); mesh.SetUVs(SmoothNormalUVChannel, result); } public static void ClearNormalsUV(Mesh mesh) { mesh.SetUVs(SmoothNormalUVChannel, (Vector3[])null); } public static bool HasSmoothNormals(Mesh mesh) { return mesh.uv3 != null && mesh.uv3.Length > 0; } /// /// This method groups vertices in a mesh that share the same location in space then averages the normals of those vertices. /// For example, if you imagine the 3 vertices that make up one corner of a cube. Normally there will be 3 normals facing in the direction /// of each face that touches that corner. This method will take those 3 normals and average them into a normal that points in the /// direction from the center of the cube to the corner of the cube. /// /// A list of vertices that represent a mesh. /// A list of normals that correspond to each vertex passed in via the vertices param. /// A list of normals which are smoothed, or averaged, based on share vertex position. public static List CalculateSmoothNormals(IReadOnlyList vertices, IReadOnlyList normals) { var watch = System.Diagnostics.Stopwatch.StartNew(); // Group all vertices that share the same location in space. var groupedVertices = new Dictionary>>(); for (int i = 0; i < vertices.Count; ++i) { var vertex = vertices[i]; if (!groupedVertices.TryGetValue(vertex, out var group)) { group = new List>(); groupedVertices[vertex] = group; } group.Add(new KeyValuePair(i, vertex)); } var smoothNormals = new List(normals); // If we don't hit the degenerate case of each vertex is its own group (no vertices shared a location), average the normals of each group. if (groupedVertices.Count != vertices.Count) { foreach (var group in groupedVertices) { var smoothingGroup = group.Value; // No need to smooth a group of one. if (smoothingGroup.Count != 1) { var smoothedNormal = smoothingGroup.Aggregate(Vector3.zero, (current, vertex) => current + normals[vertex.Key]); smoothedNormal.Normalize(); foreach (var vertex in smoothingGroup) { smoothNormals[vertex.Key] = smoothedNormal; } } } } Debug.Log($"[Flat Kit] Generated smooth normals for {vertices.Count} vertices in " + $"{watch.ElapsedMilliseconds} ms."); return smoothNormals; } } }