diff --git a/QuickOutline/Scripts/Outline.cs b/QuickOutline/Scripts/Outline.cs index 26d8788..c6d489c 100644 --- a/QuickOutline/Scripts/Outline.cs +++ b/QuickOutline/Scripts/Outline.cs @@ -218,37 +218,64 @@ void LoadSmoothNormals() { } } + /// + /// Compute smoothed normals by averaging normals around points that have split normals. + /// + /// List of normals indexed by vertex index List SmoothNormals(Mesh mesh) { - // Group vertices by location - var groups = mesh.vertices.Select((vertex, index) => new KeyValuePair(vertex, index)).GroupBy(pair => pair.Key); - - // Copy normals to a new list - var smoothNormals = new List(mesh.normals); - - // Average normals for grouped vertices - foreach (var group in groups) { - - // Skip single vertices - if (group.Count() == 1) { - continue; + // Find matched vertices and assign them an ID. To compute the matching we + // need a dictionary but we can lose it immediately. + var ids = new int[mesh.vertexCount]; + int numIDs; + { + var idMap = new Dictionary(); + var vertices = mesh.vertices; + for (int i = 0, n = mesh.vertexCount; i < n; ++i) { + // Performance note: the bulk of the time is here. With .NET 5 support + // we can do TryAdd first and TryGetValue only on duplicated vertices. + // Ideally we'd have a TryAdd that returns the actual value and do it all + // in just one lookup. + int id; + if (!idMap.TryGetValue(vertices[i], out id)) { + id = idMap.Count; + idMap.Add(vertices[i], id); + } + ids[i] = id; } + numIDs = idMap.Count; + } - // Calculate the average normal - var smoothNormal = Vector3.zero; - - foreach (var pair in group) { - smoothNormal += smoothNormals[pair.Value]; + // Compute the normals for the shared IDs by adding them up. + var normals = mesh.normals; + var averagedNormals = new Vector3[numIDs]; + var counts = new int[numIDs]; + for (int i = 0, n = mesh.vertexCount; i < n; ++i) { + var id = ids[i]; + int count = counts[id]; + if (count == 0) { + counts[id] = 1; + averagedNormals[id] = normals[i]; + } else { + counts[id] = count + 1; + averagedNormals[id] += normals[i]; } + } - smoothNormal.Normalize(); - - // Assign smooth normal to each vertex - foreach (var pair in group) { - smoothNormals[pair.Value] = smoothNormal; + // Convert from sums to averages. + for (int id = 0, n = averagedNormals.Length; id < n; ++id) { + var count = counts[id]; + if (count != 1) { + averagedNormals[id] /= (float)count; } } + // Now create a new list with the indexing of the original vertices. + var smoothNormals = new List(mesh.vertexCount); + for (int i = 0, n = mesh.vertexCount; i < n; ++i) { + smoothNormals.Add(averagedNormals[ids[i]]); + } + return smoothNormals; }