/*
* Author: Zoodinger
*
*/
using System;
using System.Collections.Generic;
using UnityEngine;
namespace ZooUtils
{
public static class NormalSolver
{
/// <summary>
/// Recalculate the normals of a given mesh but attempt to smooth distinct
/// vertices that are in the same position.
/// </summary>
/// <param name="mesh"></param>
/// <param name="angle">
/// The smoothing angle. Note that triangles that already share
/// the same vertex will be smooth regardless of the angle!
/// </param>
public static void Recalculate(Mesh mesh, float angle)
{
Recalculate(mesh, angle, 4);
}
/// <summary>
/// Recalculate the normals of a given mesh but attempt to smooth distinct
/// vertices that are in the same position.
/// </summary>
public static void Recalculate(Mesh mesh)
{
Recalculate(mesh, 60, 4);
}
/// <summary>
/// Recalculate the normals of a mesh but attempt to smooth distinct vertices
/// that are in the same position.
/// </summary>
/// <param name="mesh"></param>
/// <param name="angle">
/// The smoothing angle. Note that triangles that already share
/// the same vertex will be smooth regardless of the angle!
/// </param>
/// <param name="digitTolerance">
/// How many decimal digits to take into account. A
/// good value is between 3 and 5, but it depends on your model. This value
/// is clamped between 0 and 6.
/// </param>
public static void Recalculate(Mesh mesh, float angle, int digitTolerance)
{
digitTolerance = Mathf.Clamp(digitTolerance, 0, 6);
VertexKey.DigitTolerance = 1;
for (int i = 0; i < digitTolerance; ++i) { VertexKey.DigitTolerance *= 10; }
var triangles = mesh.GetTriangles(0);
var vertices = mesh.vertices;
var triNormals = new Vector3[triangles.Length / 3]; //Holds the normal of each triangle
var normals = new Vector3[vertices.Length];
var dictionary = new Dictionary<VertexKey, VertexEntry>();
for (int i = 0; i < triangles.Length; i += 3)
{
int i1 = triangles[i];
int i2 = triangles[i + 1];
int i3 = triangles[i + 2];
//Calculate the normal of the triangle
Vector3 p1 = vertices[i2] - vertices[i1];
Vector3 p2 = vertices[i3] - vertices[i1];
Vector3 normal = Vector3.Cross(p1, p2).normalized;
int triIndex = i / 3;
triNormals[triIndex] = normal;
VertexEntry entry;
VertexKey key;
//For each of the three points of the triangle
// > Add this triangle as part of the triangles they're connected to.
if (!dictionary.TryGetValue(key = new VertexKey(vertices[i1]), out entry))
{
entry = new VertexEntry();
dictionary.Add(key, entry);
}
entry.Add(i1, triIndex);
if (!dictionary.TryGetValue(key = new VertexKey(vertices[i2]), out entry))
{
entry = new VertexEntry();
dictionary.Add(key, entry);
}
entry.Add(i2, triIndex);
if (!dictionary.TryGetValue(key = new VertexKey(vertices[i3]), out entry))
{
entry = new VertexEntry();
dictionary.Add(key, entry);
}
entry.Add(i3, triIndex);
}
//Foreach point in space (not necessarily the same vertex index!)
//{
// Foreach triangle T1 that point belongs to
// {
// Foreach other triangle T2 (including self) that point belongs to and that
// meets any of the following:
// 1) The corresponding vertex is actually the same vertex
// 2) The angle between the two triangles is less than the smoothing angle
// {
// > Add to temporary Vector3
// }
// > Divide temporary Vector3 by count of T2 to find the average
// > Assign the normal to corresponding vertex of T1
// }
//}
foreach (var value in dictionary.Values)
{
for (int i = 0; i < value.Count; ++i)
{
var sum = new Vector3();
int countAvg = 0;
for (int j = 0; j < value.Count; ++j)
{
if (value.VertexIndex[i] == value.VertexIndex[j]
|| AngleCheck(
triNormals[value.TriangleIndex[i]],
triNormals[value.TriangleIndex[j]],
angle))
{
sum += triNormals[value.TriangleIndex[j]];
++countAvg;
}
}
sum /= countAvg;
normals[value.VertexIndex[i]] = sum;
}
}
mesh.normals = normals;
}
private static bool AngleCheck(Vector3 normal1, Vector3 normal2, float angle)
{
//We have to clamp this value, despite the fact that the dot result should always
// return values between -1 and 1 inclusive. Sometimes, float inaccuracies make
// Mathf.Acos return NaN!
float dot = Mathf.Clamp(Vector3.Dot(normal1, normal2), -0.99999f, 0.99999f);
float acos = Mathf.
Acos(dot
) * Mathf.
Rad2Deg; }
private sealed class VertexEntry
{
public int[] TriangleIndex = new int[4];
public int[] VertexIndex = new int[4];
private int _reserved = 4;
public int Count { get; private set; }
public void Add(int vertIndex, int triIndex)
{
//Auto-resize the arrays when needed
if (_reserved == Count)
{
_reserved *= 2;
Array.Resize(ref TriangleIndex, _reserved);
Array.Resize(ref VertexIndex, _reserved);
}
TriangleIndex[Count] = triIndex;
VertexIndex[Count] = vertIndex;
++Count;
}
}
// If you want to have larger values, change int to long.
// Note that 6-digit tolerance only allows values of about up to 1000 as coordinates.
private sealed class VertexKey
{
private readonly int _x;
private readonly int _y;
private readonly int _z;
public static int DigitTolerance;
public VertexKey(Vector3 position)
{
_x = (int)(Math.Round(position.x * DigitTolerance));
_y = (int)(Math.Round(position.y * DigitTolerance));
_z = (int)(Math.Round(position.z * DigitTolerance));
}
public override bool Equals(object obj)
{
var key = obj as VertexKey;
return key != null && _x == key._x && _y == key._y && _z == key._z;
}
public override int GetHashCode()
{
return (_x ^ _y ^ _z).GetHashCode();
}
}
}
}